From e150a62f1d838e96db37f5cef4d33b930820675a Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 25 Apr 2026 20:57:20 +0300 Subject: [PATCH] Inline small block-env slot reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cached block-environment reset path used SlotTemplates.AsSpan().CopyTo to copy the binding template into the live slot array on every iteration of let/const-bearing loops. For typical block scopes (1-3 bindings — e.g. the stopwatch-modern body's `const z, ms, rn`) the Span construction + generic CopyTo overhead is comparable to the work itself. Move the copy into a small static helper with AggressiveInlining and a hand-rolled <=4-element loop. The JIT can unroll this and avoid the intermediate Span. Larger block scopes still go through CopyTo. stopwatch benchmark (clean): within noise of Change 3 — confirms no regression. Cumulative baseline -> Change 6 (clean run, low std-dev): Execute (var): 265 ms -> 205 ms (-23%) Execute_ParsedScript:386 ms -> 216 ms (-44%) Execute (let): 458 ms -> 247 ms (-46%) Execute_ParsedScript:443 ms -> 244 ms (-45%) Allocations: 71 MB -> 38.5 MB (-46%) 98567 test262 + 2861 Jint.Tests pass; 0 regressions. (Earlier transient 2-4 failures on decodeURI/A2.5_T1 were timeouts under concurrent machine load — these tests do 1.3M-iteration byte-range loops and are load-sensitive; pass cleanly when isolated.) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Statements/JintBlockStatement.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs index 020e6049dd..2be32daff0 100644 --- a/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs @@ -79,7 +79,7 @@ public Completion ExecuteBlock(EvaluationContext context) { // Reuse environment: update outer reference and reset slots cachedEnv._outerEnv = oldEnv; - blockState.SlotTemplates.AsSpan().CopyTo(cachedEnv._slots); + ResetSlots(cachedEnv._slots!, blockState.SlotTemplates!); blockEnv = cachedEnv; } else @@ -244,6 +244,28 @@ protected override Completion ExecuteInternal(EvaluationContext context) return ExecuteBlock(context); } + /// + /// Reset the slots of a reused block environment to the pre-computed templates. + /// Hand-rolled small-array fast path: typical block scopes hold 1-3 bindings, where the + /// JIT can unroll this loop and avoid the Span construction + generic copy of CopyTo. + /// + [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); + } + } + /// /// Pre-computed block scope state, cached on AST node UserData. ///