Skip to content
Merged
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
148 changes: 141 additions & 7 deletions Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ internal sealed class JintForInForOfStatement : JintStatement<Statement>
private readonly JintExpression? _forInVarInitializer;
private readonly string? _forInVarName;

// Per-iteration environment reuse: for-of/for-in create a fresh binding per iteration with
// no copy step, so when nothing in the body (or a destructuring default) captures the
// environment, one fixed-slot environment reset per iteration is unobservable — and its
// stable identity keeps per-node slot caches in the body hot. The pooled instance lives on
// this handler (per statement list); the Interlocked + engine-identity discipline mirrors
// JintForStatement._cachedLoopEnv (a cached env must never pin a foreign engine, #2560).
private readonly bool _canReuseIterationEnv;
private readonly Key[]? _iterationSlotNames;
private readonly Binding[]? _iterationSlotTemplates;
private DeclarativeEnvironment? _cachedIterationEnv;

public JintForInForOfStatement(ForInStatement statement) : base(statement)
{
_leftNode = statement.Left;
Expand All @@ -53,6 +64,8 @@ public JintForInForOfStatement(ForInStatement statement) : base(statement)
_forInVarInitializer = JintExpression.Build(varDecl.Declarations[0].Init!);
_forInVarName = id.Name;
}

InitializeIterationEnvReuse(out _canReuseIterationEnv, out _iterationSlotNames, out _iterationSlotTemplates);
}

public JintForInForOfStatement(ForOfStatement statement) : base(statement)
Expand All @@ -64,6 +77,55 @@ public JintForInForOfStatement(ForOfStatement statement) : base(statement)
InitializeLhs(out _lhsKind, out _disposeHint, out _tdzNames, out _destructuring, out _assignmentPattern, out _expr);
_body = new ProbablyBlockStatement(_forBody);
_right = JintExpression.Build(_rightExpression);

InitializeIterationEnvReuse(out _canReuseIterationEnv, out _iterationSlotNames, out _iterationSlotTemplates);
}

private void InitializeIterationEnvReuse(out bool canReuse, out Key[]? slotNames, out Binding[]? slotTemplates)
{
canReuse = false;
slotNames = null;
slotTemplates = null;

// Only plain let/const heads qualify (using/await-using register per-iteration dispose
// resources on the environment), with 1-16 bindings, and nothing in the body — or in a
// destructuring pattern's default-value expressions — may capture or escape the
// per-iteration environment (closures, direct eval).
if (_lhsKind != LhsKind.LexicalBinding
|| _disposeHint != DisposeHint.Normal
|| _tdzNames is null
|| _tdzNames.Count is 0 or > 16)
{
return;
}

if (JintFunctionDefinition.EnvironmentEscapeAstVisitor.IsCapturing(_forBody)
|| JintFunctionDefinition.EnvironmentEscapeAstVisitor.MayEscape(_forBody))
{
return;
}

if (_destructuring
&& (JintFunctionDefinition.EnvironmentEscapeAstVisitor.IsCapturing(_leftNode)
|| JintFunctionDefinition.EnvironmentEscapeAstVisitor.MayEscape(_leftNode)))
{
return;
}

var kind = ((VariableDeclaration) _leftNode).Kind;
var names = new Key[_tdzNames.Count];
var templates = new Binding[_tdzNames.Count];
for (var i = 0; i < _tdzNames.Count; i++)
{
names[i] = _tdzNames[i];
templates[i] = kind == VariableDeclarationKind.Const
? new Binding(null!, canBeDeleted: false, mutable: false, strict: true)
: new Binding(null!, canBeDeleted: false, mutable: true, strict: false);
}

slotNames = names;
slotTemplates = templates;
canReuse = true;
}

private void InitializeLhs(
Expand Down Expand Up @@ -214,17 +276,19 @@ private bool HeadEvaluation(EvaluationContext context, [NotNullWhen(true)] out I
{
var engine = context.Engine;
var oldEnv = engine.ExecutionContext.LexicalEnvironment;
var tdz = JintEnvironment.NewDeclarativeEnvironment(engine, oldEnv);

// Spec only requires the TDZ environment when there are TDZ names to protect (lexical
// heads); var/assignment forms evaluate the right-hand side in the current environment.
if (_tdzNames != null)
{
var TDZEnvRec = tdz;
var tdz = JintEnvironment.NewDeclarativeEnvironment(engine, oldEnv);
foreach (var name in _tdzNames)
{
TDZEnvRec.CreateMutableBinding(name);
tdz.CreateMutableBinding(name);
}
}

engine.UpdateLexicalEnvironment(tdz);
engine.UpdateLexicalEnvironment(tdz);
}

// AnnexB B.3.6: evaluate for-in initializer before the right-hand expression
if (_forInVarInitializer is not null)
Expand All @@ -235,7 +299,10 @@ private bool HeadEvaluation(EvaluationContext context, [NotNullWhen(true)] out I
}

var exprValue = _right.GetValue(context);
engine.UpdateLexicalEnvironment(oldEnv);
if (_tdzNames != null)
{
engine.UpdateLexicalEnvironment(oldEnv);
}

// Check if execution suspended during the right-hand-side evaluation (e.g., await in array)
if (context.IsSuspended())
Expand Down Expand Up @@ -297,6 +364,26 @@ private Completion BodyEvaluation(
engine.UpdateLexicalEnvironment(oldEnv);
}

// Reusable fixed-slot iteration environment: gated on a non-suspendable context so a
// pooled env never round-trips through suspend/resume save-and-restore. ResetSlots at
// each iteration start re-establishes TDZ; the stable identity keeps body slot caches hot.
DeclarativeEnvironment? reusableEnv = null;
if (_canReuseIterationEnv && suspendable is null)
{
var cachedEnv = System.Threading.Interlocked.Exchange(ref _cachedIterationEnv, null);
if (cachedEnv is not null && ReferenceEquals(cachedEnv._engine, engine))
{
cachedEnv._outerEnv = oldEnv;
reusableEnv = cachedEnv;
}
else
{
reusableEnv = JintEnvironment.NewDeclarativeEnvironment(engine, oldEnv);
reusableEnv._slotNames = _iterationSlotNames;
reusableEnv._slots = (Binding[]) _iterationSlotTemplates!.Clone();
}
}

// Restore accumulated value if resuming
var v = suspendData?.AccumulatedValue ?? JsValue.Undefined;
var destructuring = _destructuring;
Expand Down Expand Up @@ -454,6 +541,15 @@ private Completion BodyEvaluation(
lhsRef = lhs!.Evaluate(context);
}
}
else if (reusableEnv is not null)
{
// Fresh per-iteration binding via slot reset (spec has no copy step for
// for-in/of, so reuse is unobservable without captures). The single
// identifier binding initializes straight into its slot below.
ResetSlots(reusableEnv._slots!, _iterationSlotTemplates!);
iterationEnv = reusableEnv;
engine.UpdateLexicalEnvironment(iterationEnv);
}
else
{
iterationEnv = JintEnvironment.NewDeclarativeEnvironment(engine, oldEnv);
Expand All @@ -478,7 +574,12 @@ private Completion BodyEvaluation(

if (!destructuring)
{
if (context.IsAbrupt())
if (reusableEnv is not null)
{
// single bound name -> slot 0; ChangeValue preserves the const/let flags
iterationEnv!.InitializeSlotBinding(0, nextValue);
}
else if (context.IsAbrupt())
{
close = true;
status = context.Completion;
Expand Down Expand Up @@ -726,10 +827,43 @@ private Completion BodyEvaluation(
}
}
}

// Park the reusable iteration environment for the next loop entry; reset at park
// time so the cached env doesn't root the completed loop's values or scope chain.
// Reuse is gated on non-suspendable contexts, so the loop cannot exit suspended.
if (reusableEnv is not null)
{
reusableEnv._outerEnv = null;
ResetSlots(reusableEnv._slots!, _iterationSlotTemplates!);
System.Threading.Interlocked.Exchange(ref _cachedIterationEnv, reusableEnv);
}

engine.UpdateLexicalEnvironment(oldEnv);
}
}

/// <summary>
/// Reset the slots of a reused iteration environment to the pre-computed templates (every
/// binding back to uninitialized/TDZ). Hand-rolled small-array fast path: for-of/for-in
/// heads hold 1-2 bindings.
/// </summary>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
private static void ResetSlots(Binding[] slots, Binding[] templates)
{
var len = slots.Length;
if (len == templates.Length && len <= 4)
{
for (var i = 0; i < len; i++)
{
slots[i] = templates[i];
}
}
else
{
templates.AsSpan().CopyTo(slots);
}
}

private void BindingInstantiation(Environment environment)
{
var envRec = (DeclarativeEnvironment) environment;
Expand Down
Loading