Skip to content

Commit

Permalink
Improve environment initialization performance (#2045)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Jan 27, 2025
1 parent 8ebce9a commit fb0016d
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 180 deletions.
40 changes: 8 additions & 32 deletions Jint/Engine.Ast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,7 @@ public CachedHoistingScope(Program program)
VarNames = new List<Key>();
GatherVarNames(Scope, VarNames);

LexNames = new List<CachedLexicalName>();
GatherLexNames(Scope, LexNames);
LexNames = DeclarationCacheBuilder.Build(Scope._lexicalDeclarations);
}

internal static void GatherVarNames(HoistingScope scope, List<Key> boundNames)
Expand All @@ -182,31 +181,9 @@ internal static void GatherVarNames(HoistingScope scope, List<Key> boundNames)
}
}

internal static void GatherLexNames(HoistingScope scope, List<CachedLexicalName> boundNames)
{
var lexDeclarations = scope._lexicalDeclarations;
if (lexDeclarations != null)
{
var temp = new List<Key>();
for (var i = 0; i < lexDeclarations.Count; i++)
{
var d = lexDeclarations[i];
temp.Clear();
d.GetBoundNames(temp);
for (var j = 0; j < temp.Count; j++)
{
boundNames.Add(new CachedLexicalName(temp[j], d.IsConstantDeclaration()));
}
}
}
}

[StructLayout(LayoutKind.Auto)]
internal readonly record struct CachedLexicalName(Key Name, bool Constant);

public HoistingScope Scope { get; }
public List<Key> VarNames { get; }
public List<CachedLexicalName> LexNames { get; }
public DeclarationCache LexNames { get; }
}

internal static class AstPreparationExtensions
Expand All @@ -225,26 +202,25 @@ internal static List<Key> GetVarNames(this Program program, HoistingScope hoisti
}
else
{
boundNames = new List<Key>();
boundNames = [];
CachedHoistingScope.GatherVarNames(hoistingScope, boundNames);
}

return boundNames;
}

internal static List<CachedHoistingScope.CachedLexicalName> GetLexNames(this Program program, HoistingScope hoistingScope)
internal static List<ScopedDeclaration> GetLexNames(this Program program, HoistingScope hoistingScope)
{
List<CachedHoistingScope.CachedLexicalName> boundNames;
DeclarationCache cache;
if (program.UserData is CachedHoistingScope cached)
{
boundNames = cached.LexNames;
cache = cached.LexNames;
}
else
{
boundNames = new List<CachedHoistingScope.CachedLexicalName>();
CachedHoistingScope.GatherLexNames(hoistingScope, boundNames);
cache = DeclarationCacheBuilder.Build(hoistingScope._lexicalDeclarations);
}

return boundNames;
return cache.Declarations;
}
}
56 changes: 31 additions & 25 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,19 +1004,22 @@ private void GlobalDeclarationInstantiation(
var lexNames = script.GetLexNames(hoistingScope);
for (var i = 0; i < lexNames.Count; i++)
{
var (dn, constant) = lexNames[i];
if (env.HasLexicalDeclaration(dn) || env.HasRestrictedGlobalProperty(dn))
var declaration = lexNames[i];
foreach (var dn in declaration.BoundNames)
{
ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{dn}' has already been declared");
}
if (env.HasLexicalDeclaration(dn) || env.HasRestrictedGlobalProperty(dn))
{
ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{dn}' has already been declared");
}

if (constant)
{
env.CreateImmutableBinding(dn, strict: true);
}
else
{
env.CreateMutableBinding(dn, canBeDeleted: false);
if (declaration.IsConstantDeclaration)
{
env.CreateImmutableBinding(dn, strict: true);
}
else
{
env.CreateMutableBinding(dn, canBeDeleted: false);
}
}
}

Expand Down Expand Up @@ -1114,8 +1117,7 @@ private void GlobalDeclarationInstantiation(
{
// NOTE: A separate Environment Record is needed to ensure that closures created by expressions
// in the formal parameter list do not have visibility of declarations in the function body.
var varEnvRec = JintEnvironment.NewDeclarativeEnvironment(this, env);
varEnv = varEnvRec;
varEnv = JintEnvironment.NewDeclarativeEnvironment(this, env);

UpdateVariableEnvironment(varEnv);

Expand All @@ -1124,7 +1126,7 @@ private void GlobalDeclarationInstantiation(
{
var pair = varsToInitialize[i];
var initialValue = pair.InitialValue ?? env.GetBindingValue(pair.Name, strict: false);
varEnvRec.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, initialValue);
varEnv.CreateMutableBindingAndInitialize(pair.Name, canBeDeleted: false, initialValue);
}
}

Expand All @@ -1147,20 +1149,24 @@ private void GlobalDeclarationInstantiation(

UpdateLexicalEnvironment(lexEnv);

if (configuration.LexicalDeclarations.Count > 0)
if (configuration.LexicalDeclarations?.Declarations.Count > 0)
{
var dictionary = lexEnv._dictionary ??= new HybridDictionary<Binding>(configuration.LexicalDeclarations.Count, checkExistingKeys: true);
dictionary.EnsureCapacity(dictionary.Count + configuration.LexicalDeclarations.Count);
for (var i = 0; i < configuration.LexicalDeclarations.Count; i++)
var lexicalDeclarations = configuration.LexicalDeclarations.Value.Declarations;
var dictionary = lexEnv._dictionary ??= new HybridDictionary<Binding>(lexicalDeclarations.Count, checkExistingKeys: true);
dictionary.EnsureCapacity(dictionary.Count + lexicalDeclarations.Count);
for (var i = 0; i < lexicalDeclarations.Count; i++)
{
var d = configuration.LexicalDeclarations[i];
if (d.IsConstantDeclaration)
{
dictionary[d.BoundName] = new Binding(null!, canBeDeleted: false, mutable: false, strict);
}
else
var declaration = lexicalDeclarations[i];
foreach (var bn in declaration.BoundNames)
{
dictionary[d.BoundName] = new Binding(null!, canBeDeleted: false, mutable: true, strict: false);
if (declaration.IsConstantDeclaration)
{
dictionary.CreateImmutableBinding(bn, strict);
}
else
{
dictionary.CreateMutableBinding(bn, canBeDeleted: false);
}
}
}
}
Expand Down
51 changes: 1 addition & 50 deletions Jint/HoistingScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,55 +71,6 @@ public static HoistingScope GetModuleLevelDeclarations(
treeWalker._lexicalNames);
}

public static List<Declaration>? GetLexicalDeclarations(BlockStatement statement)
{
List<Declaration>? lexicalDeclarations = null;
ref readonly var statementListItems = ref statement.Body;
for (var i = 0; i < statementListItems.Count; i++)
{
var node = statementListItems[i];
if (node.Type != NodeType.VariableDeclaration && node.Type != NodeType.FunctionDeclaration && node.Type != NodeType.ClassDeclaration)
{
continue;
}

if (node is VariableDeclaration { Kind: VariableDeclarationKind.Var })
{
continue;
}

lexicalDeclarations ??= new List<Declaration>();
lexicalDeclarations.Add((Declaration)node);
}

return lexicalDeclarations;
}

public static List<Declaration>? GetLexicalDeclarations(SwitchCase statement)
{
List<Declaration>? lexicalDeclarations = null;
ref readonly var statementListItems = ref statement.Consequent;
for (var i = 0; i < statementListItems.Count; i++)
{
var node = statementListItems[i];
if (node.Type != NodeType.VariableDeclaration)
{
continue;
}

var rootVariable = (VariableDeclaration)node;
if (rootVariable.Kind == VariableDeclarationKind.Var)
{
continue;
}

lexicalDeclarations ??= new List<Declaration>();
lexicalDeclarations.Add(rootVariable);
}

return lexicalDeclarations;
}

public static void GetImportsAndExports(
AstModule module,
out HashSet<ModuleRequest> requestedModules,
Expand Down Expand Up @@ -326,4 +277,4 @@ internal void Visit(Node node)
}
}
}
}
}
32 changes: 25 additions & 7 deletions Jint/Runtime/Environments/DeclarativeEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ internal void CreateImmutableBindingAndInitialize(Key name, bool strict, JsValue
internal sealed override void CreateMutableBinding(Key name, bool canBeDeleted = false)
{
_dictionary ??= new HybridDictionary<Binding>();
_dictionary[name] = new Binding(null!, canBeDeleted, mutable: true, strict: false);
_dictionary.CreateMutableBinding(name, canBeDeleted);
}

internal sealed override void CreateImmutableBinding(Key name, bool strict = true)
{
_dictionary ??= new HybridDictionary<Binding>();
_dictionary[name] = new Binding(null!, canBeDeleted: false, mutable: false, strict);
_dictionary.CreateImmutableBinding(name, strict);
}

internal sealed override void InitializeBinding(Key name, JsValue value)
Expand All @@ -69,14 +69,17 @@ internal sealed override void InitializeBinding(Key name, JsValue value)

internal sealed override void SetMutableBinding(Key name, JsValue value, bool strict)
{
if (_dictionary is null || !_dictionary.TryGetValue(name, out var binding))
_dictionary ??= new HybridDictionary<Binding>();

ref var binding = ref _dictionary.GetValueRefOrNullRef(name);
if (Unsafe.IsNullRef(ref binding))
{
if (strict)
{
ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
}

CreateMutableBindingAndInitialize(name, canBeDeleted: true, value);
_dictionary[name] = new Binding(value, canBeDeleted: true, mutable: true, strict: false);
return;
}

Expand All @@ -93,7 +96,7 @@ internal sealed override void SetMutableBinding(Key name, JsValue value, bool st

if (binding.Mutable)
{
_dictionary[name] = binding.ChangeValue(value);
binding = binding.ChangeValue(value);
}
else
{
Expand All @@ -116,7 +119,7 @@ internal override JsValue GetBindingValue(Key name, bool strict)
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void ThrowUninitializedBindingError(string name)
private void ThrowUninitializedBindingError(Key name)
{
ExceptionHelper.ThrowReferenceError(_engine.Realm, $"Cannot access '{name}' before initialization");
}
Expand Down Expand Up @@ -151,7 +154,7 @@ internal sealed override string[] GetAllBindingNames()
{
if (_dictionary is null)
{
return Array.Empty<string>();
return [];
}

var keys = new string[_dictionary.Count];
Expand Down Expand Up @@ -183,3 +186,18 @@ internal void TransferTo(List<Key> names, DeclarativeEnvironment env)
}
}
}

internal static class DictionaryExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void CreateMutableBinding<T>(this T dictionary, Key name, bool canBeDeleted = false) where T : IEngineDictionary<Key, Binding>
{
dictionary[name] = new Binding(null!, canBeDeleted, mutable: true, strict: false);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void CreateImmutableBinding<T>(this T dictionary, Key name, bool strict = true) where T : IEngineDictionary<Key, Binding>
{
dictionary[name] = new Binding(null!, canBeDeleted: false, mutable: false, strict);
}
}
18 changes: 8 additions & 10 deletions Jint/Runtime/Environments/FunctionEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public FunctionEnvironment(
{
_functionObject = functionObject;
NewTarget = newTarget;
if (functionObject._functionDefinition?.Function is ArrowFunctionExpression)
if (functionObject._functionDefinition?.Function.Type is NodeType.ArrowFunctionExpression)
{
_thisBindingStatus = ThisBindingStatus.Lexical;
}
Expand Down Expand Up @@ -107,21 +107,19 @@ internal void InitializeParameters(

var value = hasDuplicates ? Undefined : null;
var directSet = !hasDuplicates && (_dictionary is null || _dictionary.Count == 0);
_dictionary ??= new HybridDictionary<Binding>(parameterNames.Length, checkExistingKeys: !directSet);
for (uint i = 0; i < (uint) parameterNames.Length; i++)
{
var paramName = parameterNames[i];
if (directSet || _dictionary is null || !_dictionary.ContainsKey(paramName))
ref var binding = ref _dictionary.GetValueRefOrAddDefault(paramName, out var exists);
if (directSet || !exists)
{
var parameterValue = value;
if (arguments != null)
{
parameterValue = i < (uint) arguments.Length ? arguments[i] : Undefined;
}

_dictionary ??= new HybridDictionary<Binding>();
_dictionary[paramName] = new Binding(parameterValue!, canBeDeleted: false, mutable: true, strict: false);
var parameterValue = arguments?.At((int) i, Undefined) ?? value;
binding = new Binding(parameterValue!, canBeDeleted: false, mutable: true, strict: false);
}
}

_dictionary.CheckExistingKeys = true;
}

internal void AddFunctionParameters(EvaluationContext context, IFunction functionDeclaration, JsValue[] arguments)
Expand Down
Loading

0 comments on commit fb0016d

Please sign in to comment.