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; + } }