ReviewOS

zig-utils/zig-js

Runtime: evaluate nested template substitutions without native recursion

#1029
Closed chris opened this 8 days ago ·
8 days ago

Follow-up to #986 and #935.

Problem

Nested untagged template substitutions still recurse through native Zig frames even though flat expression chains use an iterative spine. The parser lowers each substitution to a chain whose right operand is an internal ToString unary; nesting templates alternates evalChain -> evalUnary(.to_string) -> evalChain and bypasses the left-spine iteration.

On current main 8794ccc5, a 300-level nested template evaluates to "7" in a normal build but reaches the production source-nesting guard and escapes as RangeError: Maximum call stack size exceeded. under TSan. The backtrace repeats evalChain, foldChainLink, evalUnary, and eval for more than 1,300 frames.

Reproducer:

function nest(open, core, close, n) {
  return open.repeat(n) + core + close.repeat(n);
}
(0, eval)(nest("`${", "7", "}`", 300)) === "7";

Baseline evidence:

  • zig build test-runtime -Dtest-filter='nested template substitutions evaluate without native recursion': passes
  • zig build test-runtime -Dtsan=true -Dtest-filter='nested template substitutions evaluate without native recursion': fails with an uncaught production RangeError
  • no ThreadSanitizer race diagnostic

Scope

  • Evaluate nested template substitutions with bounded native stack.
  • Preserve left-to-right evaluation, logical short-circuiting, private-brand checks, debugger/step checkpoints, and GC rooting.
  • Preserve the compact pointer-spine path and allocation profile for ordinary flat chains.
  • Add normal and TSan regression coverage at depth 300.
  • Run focused runtime, semantic unit, and sharded full-unit gates.

No-workaround rule

Do not lower the source depth, change the sanitizer-only expectation, enlarge the native-stack redzone, or weaken the production RangeError boundary. Remove the avoidable native recursion.

8 days ago

Implemented and pushed to main in 18a6fb2.

The interpreter now detects nested template-substitution chains and evaluates their chain links with an explicit action stack. This removes native recursion through evalChain -> evalUnary(.to_string) -> eval while preserving left-to-right effects, logical short-circuiting, private-brand checks, and temporary GC roots. Ordinary flat chains retain the compact existing fast path.

Evidence:

  • Baseline at 8794ccc: the depth-300 focused case passed normally but failed under TSan with an uncaught RangeError and 1,376 repeated stack frames.
  • Candidate focused runtime case: 1/1 normal and 1/1 TSan.
  • Complete focused runtime suite: 36/36 normal and 36/36 TSan, with no race report.
  • Standalone interpreter semantic filters: template 2/2; comparisons/logical 2/2.
  • Full unit suite on current main: 2,567 passed, 1 skipped, 0 failed, 0 leaked across 10 shards.
  • Existing 20,000-link flat-chain probe, five alternating samples per binary: baseline median 4.866 s; candidate median 4.721 s (-2.98%). No flat-chain regression was observed.

The test262 corpus is absent in this checkout, so no test262 flip claim is made.

Sign in to comment on this issue.