diff --git a/Jint/Runtime/Environments/DeclarativeEnvironment.cs b/Jint/Runtime/Environments/DeclarativeEnvironment.cs
index dab650499..92aa3d5df 100644
--- a/Jint/Runtime/Environments/DeclarativeEnvironment.cs
+++ b/Jint/Runtime/Environments/DeclarativeEnvironment.cs
@@ -225,6 +225,19 @@ internal sealed override void CreateImmutableBinding(Key name, bool strict = tru
_dictionary.CreateImmutableBinding(name, strict);
}
+ ///
+ /// Slot-lane variant of for lexical declarations whose slot
+ /// index was already resolved against this environment's slot layout by the caller. Only
+ /// valid for let/const targets (DisposeHint.Normal); mirrors the slot arm of
+ /// exactly.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void InitializeSlotBinding(int index, JsValue value)
+ {
+ ref var binding = ref _slots![index];
+ binding = binding.ChangeValue(value);
+ }
+
internal sealed override void InitializeBinding(Key name, JsValue value, DisposeHint hint)
{
if (_slots is not null)
diff --git a/Jint/Runtime/ExecutionContextStack.cs b/Jint/Runtime/ExecutionContextStack.cs
index 582422168..3077c0581 100644
--- a/Jint/Runtime/ExecutionContextStack.cs
+++ b/Jint/Runtime/ExecutionContextStack.cs
@@ -21,7 +21,9 @@ public void ReplaceTopLexicalEnvironment(Environment newEnv)
var array = _stack._array;
var size = _stack._size;
ref var executionContext = ref array[size - 1];
- executionContext = executionContext.UpdateLexicalEnvironment(newEnv);
+ // In-place single-field write instead of rebuilding the whole 10-field struct; the
+ // instance lives in a mutable array slot, so casting away the field's readonly is safe.
+ Unsafe.AsRef(in executionContext.LexicalEnvironment) = newEnv;
}
public void ReplaceTopVariableEnvironment(Environment newEnv)
@@ -29,7 +31,7 @@ public void ReplaceTopVariableEnvironment(Environment newEnv)
var array = _stack._array;
var size = _stack._size;
ref var executionContext = ref array[size - 1];
- executionContext = executionContext.UpdateVariableEnvironment(newEnv);
+ Unsafe.AsRef(in executionContext.VariableEnvironment) = newEnv;
}
public void ReplaceTopPrivateEnvironment(PrivateEnvironment? newEnv)
diff --git a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs
index c6c860345..fa7073ed5 100644
--- a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs
+++ b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs
@@ -140,6 +140,25 @@ public Completion ExecuteBlock(EvaluationContext context)
data.OuterEnvironment = oldEnv;
}
}
+ else if (!blockEnv.HasDisposeResources)
+ {
+ // Nothing was registered for dispose (no using/await-using declaration ran), so the
+ // dispose state machine is a no-op — finalize inline instead of round-tripping a
+ // DisposeStepResult through CompleteDispose. Mirrors its finalize arm exactly.
+ suspendable?.Data.Clear(this);
+ if (oldEnv is not null)
+ {
+ engine.UpdateLexicalEnvironment(oldEnv);
+
+ if (blockEnv._slots is not null)
+ {
+ blockEnv._outerEnv = null;
+ ResetSlots(blockEnv._slots, _blockState.SlotTemplates!);
+ _cachedEnv = blockEnv;
+ }
+ }
+ return blockValue;
+ }
else
{
return CompleteDispose(
diff --git a/Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs b/Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs
index e8457325c..64c94868d 100644
--- a/Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs
+++ b/Jint/Runtime/Interpreter/Statements/JintVariableDeclaration.cs
@@ -1,5 +1,6 @@
using Jint.Native;
using Jint.Native.Function;
+using Jint.Runtime.Environments;
using Jint.Runtime.Interpreter.Expressions;
namespace Jint.Runtime.Interpreter.Statements;
@@ -15,6 +16,27 @@ private sealed class ResolvedDeclaration
internal JintExpression? Init;
internal JintIdentifierExpression? LeftIdentifierExpression;
internal bool EvalOrArguments;
+ internal bool CanUseSlotLane;
+ internal SlotLane? Lane;
+ }
+
+ ///
+ /// Monomorphic cache mapping a slotted environment's layout (by -array
+ /// identity, stable across engines for shared prepared scripts) to the declared name's slot
+ /// index, so repeated lexical initialization skips the Reference rent/return and name scan.
+ /// A single reference field keeps publication atomic on shared handler trees; Index -1
+ /// records "no slot in this layout" so misses don't rescan.
+ ///
+ private sealed class SlotLane
+ {
+ internal readonly Key[] SlotNames;
+ internal readonly int Index;
+
+ internal SlotLane(Key[] slotNames, int index)
+ {
+ SlotNames = slotNames;
+ Index = index;
+ }
}
public JintVariableDeclaration(VariableDeclaration statement) : base(statement)
@@ -43,13 +65,24 @@ public JintVariableDeclaration(VariableDeclaration statement) : base(statement)
}
var leftIdentifier = left as JintIdentifierExpression;
+
+ // let/const identifier targets whose initializer needs no naming side channel
+ // (function definitions and class expressions call SetFunctionName/EvaluateWithName
+ // with the reference's name) can initialize straight into a resolved slot.
+ var canUseSlotLane = statement.Kind is VariableDeclarationKind.Let or VariableDeclarationKind.Const
+ && leftIdentifier is not null
+ && leftIdentifier.HasEvalOrArguments == false
+ && (declaration.Init is null
+ || (declaration.Init is not ClassExpression && !declaration.Init.IsFunctionDefinition()));
+
_declarations[i] = new ResolvedDeclaration
{
Left = left,
LeftPattern = pattern,
LeftIdentifierExpression = leftIdentifier,
EvalOrArguments = leftIdentifier?.HasEvalOrArguments == true,
- Init = init
+ Init = init,
+ CanUseSlotLane = canUseSlotLane,
};
}
}
@@ -61,34 +94,51 @@ protected override Completion ExecuteInternal(EvaluationContext context)
{
if (_statement.Kind != VariableDeclarationKind.Var && declaration.Left != null)
{
- var lhs = (Reference) declaration.Left.Evaluate(context);
- var value = JsValue.Undefined;
- if (declaration.Init != null)
+ // Slot lane: the declaration's own binding lives in the current environment by
+ // construction (blocks/loops instantiate their env before executing statements),
+ // so when that env stores bindings in slots we can initialize the slot directly
+ // and skip the Reference rent/evaluate/return round-trip and the name scan.
+ if (declaration.CanUseSlotLane
+ && engine.ExecutionContext.LexicalEnvironment is DeclarativeEnvironment denv
+ && denv._slotNames is not null)
{
- if (declaration.Init is JintClassExpression classExpr && declaration.Init._expression.IsAnonymousFunctionDefinition())
- {
- value = classExpr.EvaluateWithName(context, lhs.ReferencedName.ToString()).Clone();
- }
- else
+ var lane = declaration.Lane;
+ if (lane is null || !ReferenceEquals(lane.SlotNames, denv._slotNames))
{
- value = declaration.Init.GetValue(context).Clone();
+ declaration.Lane = lane = new SlotLane(
+ denv._slotNames,
+ denv.FindSlotIndex(declaration.LeftIdentifierExpression!.Identifier.Key));
}
- // Check for generator suspension after evaluating initializer
- if (context.IsSuspended())
- {
- engine._referencePool.Return(lhs);
- return new Completion(CompletionType.Normal, value, _statement);
- }
-
- if (declaration.Init._expression.IsFunctionDefinition() && declaration.Init is not JintClassExpression)
+ if (lane.Index >= 0)
{
- ((Function) value).SetFunctionName(lhs.ReferencedName);
+ JsValue laneValue;
+ if (declaration.Init is null)
+ {
+ laneValue = JsValue.Undefined;
+ }
+ else
+ {
+ laneValue = declaration.Init.GetValue(context).Clone();
+
+ // Check for generator suspension after evaluating initializer
+ if (context.IsSuspended())
+ {
+ return new Completion(CompletionType.Normal, laneValue, _statement);
+ }
+ }
+
+ denv.InitializeSlotBinding(lane.Index, laneValue);
+ continue;
}
}
- lhs.InitializeReferencedBinding(value, _statement.Kind.GetDisposeHint());
- engine._referencePool.Return(lhs);
+ // Slow arm kept out-of-line so this method stays compact for the var path.
+ var completion = InitializeLexicalBindingSlow(context, engine, declaration);
+ if (completion is not null)
+ {
+ return completion.Value;
+ }
}
else if (declaration.Init != null)
{
@@ -160,4 +210,43 @@ protected override Completion ExecuteInternal(EvaluationContext context)
return Completion.Empty();
}
+
+ ///
+ /// Initializes a lexical (let/const/using) identifier binding through the Reference-based
+ /// path (targets the slot lane can't serve). Returns a completion only when the initializer
+ /// suspended (generator/async); null means the binding was initialized and the caller
+ /// continues with the next declarator.
+ ///
+ private Completion? InitializeLexicalBindingSlow(EvaluationContext context, Engine engine, ResolvedDeclaration declaration)
+ {
+ var lhs = (Reference) declaration.Left!.Evaluate(context);
+ var value = JsValue.Undefined;
+ if (declaration.Init != null)
+ {
+ if (declaration.Init is JintClassExpression classExpr && declaration.Init._expression.IsAnonymousFunctionDefinition())
+ {
+ value = classExpr.EvaluateWithName(context, lhs.ReferencedName.ToString()).Clone();
+ }
+ else
+ {
+ value = declaration.Init.GetValue(context).Clone();
+ }
+
+ // Check for generator suspension after evaluating initializer
+ if (context.IsSuspended())
+ {
+ engine._referencePool.Return(lhs);
+ return new Completion(CompletionType.Normal, value, _statement);
+ }
+
+ if (declaration.Init._expression.IsFunctionDefinition() && declaration.Init is not JintClassExpression)
+ {
+ ((Function) value).SetFunctionName(lhs.ReferencedName);
+ }
+ }
+
+ lhs.InitializeReferencedBinding(value, _statement.Kind.GetDisposeHint());
+ engine._referencePool.Return(lhs);
+ return null;
+ }
}