Skip to content

fix: apply interpreter register limit to JIT backends - #54

Merged
esrrhs merged 4 commits into
masterfrom
fix/jit-match-interp-reg-limit
Nov 11, 2026
Merged

esrrhs merged 4 commits into
masterfrom
fix/jit-match-interp-reg-limit

Conversation

@esrrhs

@esrrhs esrrhs commented Nov 10, 2026

Copy link
Copy Markdown
Owner

The interpreter already rejects a function whose peak register usage exceeds 255. JIT backends (TCC/GCC) previously could compile the same source, so INTERP and JIT could disagree on the same input. This change enforces the same per-function ceiling on every backend.

The shared check walks the same AllocLocal / AllocReg / FreeTo / EnsureStack rules used by interpreter codegen. The measured resource is peak concurrent register slots: parameters, semantic temporaries, and expression temporaries together, not a count of user locals or of generated C variables.

When INTERP is enabled, bytecode is still built once. JIT-only compiles run CheckRegisterLimit instead, so they do not emit bytecode, constant pools, or FuncProto. RegisterAndInit still runs after TCC/GCC registration so __fakelua_init side-effect order is unchanged.

Tests cover:

  • exact 256 slots compile and run on supported backends; 257 slots fail consistently
  • adjacent complex fixtures that mix multi-assignment, function calls, and table constructors just under and just over the limit, with result assertions below the limit
  • more than 256 constants still allowed
  • overflow inside a nested function

Enforce the same per-function 256-slot register ceiling on INTERP,
TCC, and GCC. The check measures peak concurrent register slots
from the shared AllocLocal/AllocReg/FreeTo/EnsureStack walk, which
includes parameters, semantic temporaries, and expression temps
rather than counting user locals or generated C variables.

JIT-only compiles run CheckRegisterLimit without emitting bytecode,
constant pools, or FuncProto. When INTERP is enabled, Build still
runs once; RegisterAndInit stays after TCC/GCC so __fakelua_init
order is unchanged.

Boundary tests cover exact 256/257 slots, adjacent complex
multi-assign/call/table-constructor cases, constants beyond 256,
and a nested function overflow.
@codecov-commenter

codecov-commenter commented Nov 10, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 95.48817% with 61 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.00%. Comparing base (49c63c7) to head (985a8d4).

Files with missing lines Patch % Lines
src/compile/semantic_analysis.cpp 95.61% 55 Missing ⚠️
src/interp/codegen.cpp 93.54% 6 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #54      +/-   ##
==========================================
+ Coverage   89.77%   90.00%   +0.22%     
==========================================
  Files         125      125              
  Lines       27317    28601    +1284     
==========================================
+ Hits        24523    25741    +1218     
- Misses       2794     2860      +66     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

ValidateGotoInBlock already rejected continue when loop_depth is 0,
but break was left to a later backend. After interpreter Build ran
before TCC/GCC, InterpCodegen threw "break not inside loop", and
exception.test_break_error failed on macOS and Windows because it
looked for "compile" (a C compiler diagnostic, or a Linux stack
frame path under src/compile/).

Diagnose the same language rule in semantic analysis, with a
stable source location, so every backend agrees. The test now
asserts that message instead of a platform-dependent substring.
Validate the shared register peak in SemanticAnalysis::Analyze with
FunctionRegisterUsage Alloc* lifetime rules, so all backends share
one compile-module check and compiler.cpp no longer couples JIT
gating to interpreter codegen.
Remove compile/register_limit.h and the shared FunctionRegisterUsage
abstraction. SemanticAnalysis::Analyze calls CheckRegisterLimits, an
internal alloc-only peak checker in semantic_analysis.cpp (no bytecode,
FuncProto, InterpUnit, or State string intern). InterpCodegen again owns
interpreter codegen. Constants stay uncapped; 256/257 and 234/235
boundaries are unchanged.
@esrrhs
esrrhs merged commit 74528f2 into master Nov 11, 2026
9 checks passed
@esrrhs
esrrhs deleted the fix/jit-match-interp-reg-limit branch November 11, 2026 02:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants