Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache more script-global information when preparing AST #1671

Merged
merged 4 commits into from
Nov 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 35 additions & 4 deletions Jint/Collections/HybridDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected HybridDictionary(StringDictionarySlim<TValue> dictionary)
{
if (_list.Count >= CutoverPoint - 1)
{
SwitchToDictionary(key, value);
SwitchToDictionary(key, value, tryAdd: false);
}
else
{
Expand Down Expand Up @@ -97,17 +97,27 @@ public void SetOrUpdateValue<TState>(Key key, Func<TValue, TState, TValue> updat
}
}

private void SwitchToDictionary(Key key, TValue value)
private bool SwitchToDictionary(Key key, TValue value, bool tryAdd)
{
var dictionary = new StringDictionarySlim<TValue>(InitialDictionarySize);
foreach (var pair in _list)
{
dictionary[pair.Key] = pair.Value;
}

dictionary[key] = value;
bool result;
if (tryAdd)
{
result = dictionary.TryAdd(key, value);
}
else
{
dictionary[key] = value;
result = true;
}
_dictionary = dictionary;
_list = null;
return result;
}

public int Count
Expand All @@ -116,6 +126,27 @@ public int Count
get => _dictionary?.Count ?? _list?.Count ?? 0;
}

public bool TryAdd(Key key, TValue value)
{
if (_dictionary != null)
{
return _dictionary.TryAdd(key, value);
}
else
{
_list ??= new ListDictionary<TValue>(key, value, _checkExistingKeys);

if (_list.Count + 1 >= CutoverPoint)
{
return SwitchToDictionary(key, value, tryAdd: true);
}
else
{
return _list.Add(key, value, tryAdd: true);
}
}
}

public void Add(Key key, TValue value)
{
if (_dictionary != null)
Expand All @@ -132,7 +163,7 @@ public void Add(Key key, TValue value)
{
if (_list.Count + 1 >= CutoverPoint)
{
SwitchToDictionary(key, value);
SwitchToDictionary(key, value, tryAdd: false);
}
else
{
Expand Down
7 changes: 6 additions & 1 deletion Jint/Collections/ListDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public int Count
get => _count;
}

public void Add(Key key, TValue value)
public bool Add(Key key, TValue value, bool tryAdd = false)
{
DictionaryNode last = null;
DictionaryNode node;
Expand All @@ -109,13 +109,18 @@ public void Add(Key key, TValue value)
var oldKey = node.Key;
if (checkExistingKeys && oldKey == key)
{
if (tryAdd)
{
return false;
}
ExceptionHelper.ThrowArgumentException();
}

last = node;
}

AddNode(key, value, last);
return true;
}

private void AddNode(Key key, TValue value, DictionaryNode last)
Expand Down
17 changes: 17 additions & 0 deletions Jint/Collections/StringDictionarySlim.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,23 @@ public void SetOrUpdateValue<TState>(Key key, Func<TValue, TState, TValue> updat
return ref AddKey(key, bucketIndex);
}

public bool TryAdd(Key key, TValue value)
{
Entry[] entries = _entries;
int bucketIndex = key.HashCode & (_buckets.Length - 1);
for (int i = _buckets[bucketIndex] - 1;
(uint)i < (uint)entries.Length; i = entries[i].next)
{
if (key.Name == entries[i].key.Name)
{
return false;
}
}

AddKey(key, bucketIndex) = value;
return true;
}

/// <summary>
/// Adds a new item and expects key to not to exist.
/// </summary>
Expand Down
96 changes: 95 additions & 1 deletion Jint/Engine.Ast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ namespace Jint;

public partial class Engine
{

/// <summary>
/// Prepares a script for the engine that includes static analysis data to speed up execution during run-time.
/// </summary>
Expand Down Expand Up @@ -70,7 +69,102 @@ public void NodeVisitor(Node node)
var function = (IFunction) node;
node.AssociatedData = JintFunctionDefinition.BuildState(function);
break;
case Nodes.Program:
node.AssociatedData = new CachedHoistingScope((Program) node);
break;
}
}
}
}

internal sealed class CachedHoistingScope
{
public CachedHoistingScope(Program program)
{
Scope = HoistingScope.GetProgramLevelDeclarations(program);

VarNames = new List<string>();
GatherVarNames(Scope, VarNames);

LexNames = new List<CachedLexicalName>();
GatherLexNames(Scope, LexNames);
}

internal static void GatherVarNames(HoistingScope scope, List<string> boundNames)
{
var varDeclarations = scope._variablesDeclarations;
if (varDeclarations != null)
{
for (var i = 0; i < varDeclarations.Count; i++)
{
var d = varDeclarations[i];
d.GetBoundNames(boundNames);
}
}
}

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

internal readonly record struct CachedLexicalName(string Name, bool Constant);

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

internal static class AstPreparationExtensions
{
internal static HoistingScope GetHoistingScope(this Program program)
{
return program.AssociatedData is CachedHoistingScope cached ? cached.Scope : HoistingScope.GetProgramLevelDeclarations(program);
}

internal static List<string> GetVarNames(this Program program, HoistingScope hoistingScope)
{
List<string> boundNames;
if (program.AssociatedData is CachedHoistingScope cached)
{
boundNames = cached.VarNames;
}
else
{
boundNames = new List<string>();
CachedHoistingScope.GatherVarNames(hoistingScope, boundNames);
}

return boundNames;
}

internal static List<CachedHoistingScope.CachedLexicalName> GetLexNames(this Program program, HoistingScope hoistingScope)
{
List<CachedHoistingScope.CachedLexicalName> boundNames;
if (program.AssociatedData is CachedHoistingScope cached)
{
boundNames = cached.LexNames;
}
else
{
boundNames = new List<CachedHoistingScope.CachedLexicalName>();
CachedHoistingScope.GatherLexNames(hoistingScope, boundNames);
}

return boundNames;
}
}
98 changes: 37 additions & 61 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -917,14 +917,12 @@ internal JsValue ResolveThisBinding()
Script script,
GlobalEnvironmentRecord env)
{
var strict = _isStrict || StrictModeScope.IsStrictModeCode;
var hoistingScope = HoistingScope.GetProgramLevelDeclarations(strict, script);
var hoistingScope = script.GetHoistingScope();
var functionDeclarations = hoistingScope._functionDeclarations;
var varDeclarations = hoistingScope._variablesDeclarations;
var lexDeclarations = hoistingScope._lexicalDeclarations;

var functionToInitialize = new LinkedList<JintFunctionDefinition>();
var declaredFunctionNames = new HashSet<string>();
var functionToInitialize = new List<JintFunctionDefinition>();
var declaredFunctionNames = new HashSet<string>(StringComparer.Ordinal);
var declaredVarNames = new List<string>();

var realm = Realm;
Expand All @@ -944,74 +942,56 @@ internal JsValue ResolveThisBinding()
}

declaredFunctionNames.Add(fn);
functionToInitialize.AddFirst(new JintFunctionDefinition(d));
functionToInitialize.Add(new JintFunctionDefinition(d));
}
}
}

var boundNames = new List<string>();
if (varDeclarations != null)
var varNames = script.GetVarNames(hoistingScope);
for (var j = 0; j < varNames.Count; j++)
{
for (var i = 0; i < varDeclarations.Count; i++)
var vn = varNames[j];
if (env.HasLexicalDeclaration(vn))
{
var d = varDeclarations[i];
boundNames.Clear();
d.GetBoundNames(boundNames);
for (var j = 0; j < boundNames.Count; j++)
{
var vn = boundNames[j];

if (env.HasLexicalDeclaration(vn))
{
ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{vn}' has already been declared");
}

if (!declaredFunctionNames.Contains(vn))
{
var vnDefinable = env.CanDeclareGlobalVar(vn);
if (!vnDefinable)
{
ExceptionHelper.ThrowTypeError(realm);
}
ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{vn}' has already been declared");
}

declaredVarNames.Add(vn);
}
if (!declaredFunctionNames.Contains(vn))
{
var vnDefinable = env.CanDeclareGlobalVar(vn);
if (!vnDefinable)
{
ExceptionHelper.ThrowTypeError(realm);
}

declaredVarNames.Add(vn);
}
}

PrivateEnvironmentRecord? privateEnv = null;
if (lexDeclarations != null)
var lexNames = script.GetLexNames(hoistingScope);
for (var i = 0; i < lexNames.Count; i++)
{
for (var i = 0; i < lexDeclarations.Count; i++)
var (dn, constant) = lexNames[i];
if (env.HasVarDeclaration(dn) || env.HasLexicalDeclaration(dn) || env.HasRestrictedGlobalProperty(dn))
{
var d = lexDeclarations[i];
boundNames.Clear();
d.GetBoundNames(boundNames);
for (var j = 0; j < boundNames.Count; j++)
{
var dn = boundNames[j];
if (env.HasVarDeclaration(dn)
|| env.HasLexicalDeclaration(dn)
|| env.HasRestrictedGlobalProperty(dn))
{
ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{dn}' has already been declared");
}
ExceptionHelper.ThrowSyntaxError(realm, $"Identifier '{dn}' has already been declared");
}

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

foreach (var f in functionToInitialize)
// we need to go trough in reverse order to handle the hoisting correctly
for (var i = functionToInitialize.Count - 1; i > -1; i--)
{
var f = functionToInitialize[i];
var fn = f.Name!;

if (env.HasLexicalDeclaration(fn))
Expand All @@ -1023,11 +1003,7 @@ internal JsValue ResolveThisBinding()
env.CreateGlobalFunctionBinding(fn, fo, canBeDeleted: false);
}

for (var i = 0; i < declaredVarNames.Count; i++)
{
var vn = declaredVarNames[i];
env.CreateGlobalVarBinding(vn, canBeDeleted: false);
}
env.CreateGlobalVarBindings(declaredVarNames, canBeDeleted: false);
}

/// <summary>
Expand Down Expand Up @@ -1199,7 +1175,7 @@ private ArgumentsInstance CreateUnmappedArgumentsObject(JsValue[] argumentsList)
PrivateEnvironmentRecord? privateEnv,
bool strict)
{
var hoistingScope = HoistingScope.GetProgramLevelDeclarations(strict, script);
var hoistingScope = HoistingScope.GetProgramLevelDeclarations(script);

var lexEnvRec = (DeclarativeEnvironmentRecord) lexEnv;
var varEnvRec = varEnv;
Expand Down Expand Up @@ -1261,7 +1237,7 @@ private ArgumentsInstance CreateUnmappedArgumentsObject(JsValue[] argumentsList)

var functionDeclarations = hoistingScope._functionDeclarations;
var functionsToInitialize = new LinkedList<JintFunctionDefinition>();
var declaredFunctionNames = new HashSet<string>();
var declaredFunctionNames = new HashSet<string>(StringComparer.Ordinal);

if (functionDeclarations != null)
{
Expand Down