fix: popSafeIndexScope leaking entries across loop bodies in self-hosted build#510
Closed
fix: popSafeIndexScope leaking entries across loop bodies in self-hosted build#510
Conversation
Contributor
Benchmark Results (Linux x86-64)
CLI Tool Benchmarks
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Main-branch regression from #506
Main has been red since #506 (bounds-check elimination) merged at 22:40 UTC. Every subsequent merge (#507, #508) has inherited the same failure:
The test fixture is a one-liner:
Stage0 (node-driven chad) correctly catches this as
cannot use '-' between 'string' and 'number'. Stage1 (chad compiled by chad) silently accepts it and goes to linking. The asymmetry meant the self-hosted build of chad had a semantic-analysis hole specifically for binary-op type errors inside function bodies.Root cause
popSafeIndexScopeinsrc/codegen/llvm-generator.tsusedarr.length = Narray truncation to pop safe-index entries added within a loop scope:The self-hosted native compiler implements
arr.length = 0(full clear) but notarr.length = N(non-zero truncation) — the assignment is parsed and type-checked but codegen emits nothing for it. So when chad compiles itself:for (let i = 0; i < arr.length; i++)enters, pushes safe-index(i, arr), scope recordsframeStart=0.popSafeIndexScoperuns, the frame stack pops, BUTsafeIndexVars.length = 0fails to actually shrink the array. The(i, arr)entry stays forever.arr[i]access anywhere in the module with matching short names (iintoarr,stmtsintostmts, etc. — really common in compiler code) now triggers the "safe index" path and emits a bounds-check-free GEP.checkBinaryTypesDeep'sbinWalkStmts/binWalkStmt/checkBinExprchain, one of these over-eager eliminations corrupts control flow enough that the binary-op type check is effectively skipped on function-body traversal. The specific miscompilation is subtle (it's not a crash — the check is silently unreachable), but the end result is that"hello" - 5inside a function body slips through.checkMissingReturnsandcheckArgumentCountsare NOT affected because their walking code uses different variable names and doesn't hit the leaked entries.This is a double-bug: (1) the chad native compiler silently drops non-zero length assignments instead of erroring, and (2) #506 happened to be the first caller that relied on non-zero length assignment for correctness. Every other
arr.length = Nin chad's own codebase uses= 0(full clear, which DOES work), so nothing else broke.Fix
Replace the length-assignment with an explicit pop loop:
Array.prototype.pop()is implemented correctly by chad-native (every other codegen path uses it), so the pop loop works in both stage0 and stage1.Verification
Before fix: stage1 chad silently accepts
"hello" - 5inside a function body.After fix: stage1 chad correctly emits
cannot use '-' between 'string' and 'number'and exits 1.All 777/777 tests pass, stage1 self-hosting smoke test passes, stage1 chad catches the previously-missed type error.
Diff
One function in
src/codegen/llvm-generator.ts, 14 insertions / 3 deletions. No new dependencies, no API changes, no test fixture changes. Pure bug fix.Follow-up: the native compiler should error on non-zero length assignment
Shipping the fix first because main is red, but the deeper issue is that chad-native silently drops
arr.length = nonZeroinstead of erroring at compile time. Either:arr.pop()or explicit slicingEither fix would have caught #506 before it landed. I'd vote for the compile error — "unsupported: use .pop() or .splice()" — since chad has a generally honest "if we don't implement it, say so" posture.
Tracking that as a separate followup.