Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions Jint/Native/Function/ScriptFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ protected internal override JsValue Call(JsValue thisObject, JsCallArguments arg
Throw.TypeError(calleeContext.Realm, $"Class constructor {_functionDefinition.Name} cannot be invoked without 'new'");
}

// Check if slot array can be cached after this call
// Capture funcEnv for end-of-call pool return when bindings can't escape
state = _functionDefinition.Initialize();
if (state is { UseFixedSlots: true, EnvironmentMayEscape: false })
if (state is { EnvironmentMayEscape: false })
{
funcEnv = (FunctionEnvironment) calleeContext.LexicalEnvironment;
}
Expand Down Expand Up @@ -120,12 +120,19 @@ protected internal override JsValue Call(JsValue thisObject, JsCallArguments arg
}
finally
{
// Cache the slot array for reuse by next call to the same function (thread-safe)
if (funcEnv?._slots is { } slots)
if (funcEnv is not null)
{
System.Array.Clear(slots, 0, slots.Length);
Interlocked.Exchange(ref state!._cachedSlots, slots);
funcEnv._slots = null;
// Cache the slot array for reuse by next call to the same function (thread-safe)
if (funcEnv._slots is { } slots)
{
System.Array.Clear(slots, 0, slots.Length);
Interlocked.Exchange(ref state!._cachedSlots, slots);
funcEnv._slots = null;
}

// Return the env itself to the per-State pool so the next call to a function
// sharing this State (typically the same Function instance) avoids the allocation.
Interlocked.Exchange(ref state!._cachedEnv, funcEnv);
}
_engine.LeaveExecutionContext();
}
Expand Down
8 changes: 8 additions & 0 deletions Jint/Runtime/Environments/DeclarativeEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,14 @@ public void Clear()
_dictionary = null;
}

/// <summary>
/// Reset for env-pool reuse: drop any using/await-using disposable resources tracked from the previous call.
/// </summary>
internal void ClearDisposeCapability()
{
_disposeCapability = null;
}

internal void TransferTo(List<Key> names, DeclarativeEnvironment env)
{
var source = _dictionary!;
Expand Down
20 changes: 19 additions & 1 deletion Jint/Runtime/Environments/FunctionEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ private enum ThisBindingStatus

private JsValue? _thisValue;
private ThisBindingStatus _thisBindingStatus;
internal readonly Function _functionObject;
// Mutable so a pooled env can be re-bound to a different Function instance that shares the same State (e.g. function literals re-evaluated in loops).
internal Function _functionObject;

public FunctionEnvironment(
Engine engine,
Expand All @@ -42,6 +43,23 @@ public FunctionEnvironment(
}
}

/// <summary>
/// Re-initialize a pooled FunctionEnvironment for a new call. Leaves slot/dictionary handling
/// to the caller — they may either clear in place or replace with fresh storage.
/// </summary>
internal void Reset(Function functionObject, JsValue newTarget, Environment? outerEnv)
{
_functionObject = functionObject;
NewTarget = newTarget;
_outerEnv = outerEnv;
_thisValue = null;
_thisBindingStatus = functionObject._functionDefinition?.Function.Type is NodeType.ArrowFunctionExpression
? ThisBindingStatus.Lexical
: ThisBindingStatus.Uninitialized;
_dictionary?.Clear();
ClearDisposeCapability();
}


internal override bool HasThisBinding() => _thisBindingStatus != ThisBindingStatus.Lexical;

Expand Down
23 changes: 19 additions & 4 deletions Jint/Runtime/Environments/JintEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,27 @@ internal static DeclarativeEnvironment NewDeclarativeEnvironment(Engine engine,
/// </summary>
internal static FunctionEnvironment NewFunctionEnvironment(Engine engine, Function f, JsValue newTarget)
{
var env = new FunctionEnvironment(engine, f, newTarget)
var state = f._functionDefinition?.Initialize();
FunctionEnvironment env;

// Reuse a pooled FunctionEnvironment when the function's bindings cannot escape the call
// (no closure capture, not a generator/async). Re-bind to the new function/target/outer env
// and reset transient state. Slot storage is handled below.
if (state is { EnvironmentMayEscape: false }
&& Interlocked.Exchange(ref state._cachedEnv, null) is { } cachedEnv
&& ReferenceEquals(cachedEnv._engine, engine))
{
_outerEnv = f._environment
};
cachedEnv.Reset(f, newTarget, f._environment);
env = cachedEnv;
}
else
{
env = new FunctionEnvironment(engine, f, newTarget)
{
_outerEnv = f._environment,
};
}

var state = f._functionDefinition?.Initialize();
if (state is { UseFixedSlots: true })
{
env._slotNames = state.SlotNames;
Expand Down
28 changes: 19 additions & 9 deletions Jint/Runtime/Interpreter/JintFunctionDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ internal sealed class State
public bool CanUseFastFDI;
public bool EnvironmentMayEscape;
public Binding[]? _cachedSlots;
public Environments.FunctionEnvironment? _cachedEnv;

public SourceText SourceText;

Expand Down Expand Up @@ -558,18 +559,27 @@ internal static State BuildState(IFunction function, string? fullSourceText = nu
state.VarSlotCount = varsToInitialize.Count;
state.UseFixedSlots = true;
state.CanUseFastFDI = lexicalBindingCount == 0;

if (function.Generator || function.Async)
{
state.EnvironmentMayEscape = true;
}
else
{
state.EnvironmentMayEscape = EnvironmentEscapeAstVisitor.MayEscapeWithReferences(function, slotNames);
}
}
}

// Compute EnvironmentMayEscape unconditionally so consumers (e.g. FunctionEnvironment pooling)
// can rely on it without first checking UseFixedSlots. Generators / async functions / direct eval
// always escape; otherwise inspect the body. When the function qualified for fixed slots, prefer
// the slot-aware analysis (only escapes if a closure actually references a slot variable);
// otherwise fall back to the conservative "any inner closure means escape" check.
if (function.Generator || function.Async || state.NeedsEvalContext)
{
state.EnvironmentMayEscape = true;
}
else if (state.UseFixedSlots)
{
state.EnvironmentMayEscape = EnvironmentEscapeAstVisitor.MayEscapeWithReferences(function, state.SlotNames!);
}
else
{
state.EnvironmentMayEscape = EnvironmentEscapeAstVisitor.MayEscape(function);
}

state.SourceText = new SourceText(fullSourceText);

return state;
Expand Down