From aa844bd2779df8e0a968e7e1a9e347387608bfb5 Mon Sep 17 00:00:00 2001 From: cs01 Date: Mon, 13 Apr 2026 21:07:25 -0700 Subject: [PATCH] Revert "perf: bounds-check elimination in loop-indexed array access (#506)" This reverts commit dcf1cd3993b43f0e7c13c115421ded471439b62d. --- src/codegen/expressions/access/index.ts | 33 +- .../infrastructure/generator-context.ts | 14 - src/codegen/llvm-generator.ts | 41 -- src/codegen/statements/control-flow.ts | 20 - src/codegen/statements/loop-safety.ts | 393 ------------------ .../fixtures/arrays/array-bounds-elim-safe.ts | 29 -- .../arrays/array-bounds-elim-unsafe-offset.ts | 11 - .../arrays/array-bounds-elim-unsafe-pop.ts | 22 - 8 files changed, 6 insertions(+), 557 deletions(-) delete mode 100644 src/codegen/statements/loop-safety.ts delete mode 100644 tests/fixtures/arrays/array-bounds-elim-safe.ts delete mode 100644 tests/fixtures/arrays/array-bounds-elim-unsafe-offset.ts delete mode 100644 tests/fixtures/arrays/array-bounds-elim-unsafe-pop.ts diff --git a/src/codegen/expressions/access/index.ts b/src/codegen/expressions/access/index.ts index df812ea4..d1113300 100644 --- a/src/codegen/expressions/access/index.ts +++ b/src/codegen/expressions/access/index.ts @@ -38,7 +38,6 @@ export interface IndexAccessGeneratorContext { ensureDouble(value: string): string; setUsesJson(value: boolean): void; emitError(message: string, loc?: SourceLocation, suggestion?: string): never; - isSafeIndex(indexName: string, arrayName: string): boolean; } /** @@ -182,19 +181,6 @@ export class IndexAccessGenerator { return indexValue; } - // Returns true when the given index access is a direct `arr[i]` pattern where - // the (i, arr) pair has been proven safe by loop analysis, so we can skip the - // runtime bounds check entirely. - private isProvenSafeAccess(expr: IndexAccessNode | IndexAccessAssignmentNode): boolean { - const obj = expr.object as ExprBase; - const idx = expr.index as ExprBase; - if (obj.type !== "variable") return false; - if (idx.type !== "variable") return false; - const arrName = (expr.object as VariableNode).name; - const idxName = (expr.index as VariableNode).name; - return this.ctx.isSafeIndex(idxName, arrName); - } - private emitBoundsCheck(arrayPtr: string, arrayType: string, index: string): void { const lenPtr = this.ctx.nextTemp(); this.ctx.emit( @@ -232,9 +218,7 @@ export class IndexAccessGenerator { stringArrayPtr = cast; } - if (!this.isProvenSafeAccess(expr)) { - this.emitBoundsCheck(stringArrayPtr, "%StringArray", index); - } + this.emitBoundsCheck(stringArrayPtr, "%StringArray", index); const dataPtr = this.ctx.nextTemp(); this.ctx.emit( @@ -265,9 +249,7 @@ export class IndexAccessGenerator { arrayPtr = cast; } - if (!this.isProvenSafeAccess(expr)) { - this.emitBoundsCheck(arrayPtr, "%Array", index); - } + this.emitBoundsCheck(arrayPtr, "%Array", index); const dataPtr = this.ctx.nextTemp(); this.ctx.emit(`${dataPtr} = getelementptr inbounds %Array, %Array* ${arrayPtr}, i32 0, i32 0`); @@ -334,10 +316,9 @@ export class IndexAccessGenerator { const index = this.toI32Index(indexDouble); const contiguousStride = this.getContiguousStride(expr); - const safeAccess = this.isProvenSafeAccess(expr); const arrayType = this.ctx.getVariableType(arrayPtr); if (arrayType === "%ObjectArray*") { - if (!safeAccess) this.emitBoundsCheck(arrayPtr, "%ObjectArray", index); + this.emitBoundsCheck(arrayPtr, "%ObjectArray", index); if (contiguousStride > 0) { return this.emitContiguousElementPtr(arrayPtr, "%ObjectArray", index, contiguousStride); } @@ -353,7 +334,7 @@ export class IndexAccessGenerator { if (arrayType === "i8*") { const arrayCast = this.ctx.nextTemp(); this.ctx.emit(`${arrayCast} = bitcast i8* ${arrayPtr} to %ObjectArray*`); - if (!safeAccess) this.emitBoundsCheck(arrayCast, "%ObjectArray", index); + this.emitBoundsCheck(arrayCast, "%ObjectArray", index); if (contiguousStride > 0) { return this.emitContiguousElementPtr(arrayCast, "%ObjectArray", index, contiguousStride); } @@ -366,7 +347,7 @@ export class IndexAccessGenerator { return this.emitPointerArrayElementPtr(data, index); } - if (!safeAccess) this.emitBoundsCheck(arrayPtr, "%ObjectArray", index); + this.emitBoundsCheck(arrayPtr, "%ObjectArray", index); if (contiguousStride > 0) { return this.emitContiguousElementPtr(arrayPtr, "%ObjectArray", index, contiguousStride); } @@ -385,9 +366,7 @@ export class IndexAccessGenerator { const index = this.toI32Index(indexDouble); - if (!this.isProvenSafeAccess(expr)) { - this.emitBoundsCheck(arrayPtr, "%Uint8Array", index); - } + this.emitBoundsCheck(arrayPtr, "%Uint8Array", index); const dataFieldPtr = this.ctx.nextTemp(); this.ctx.emit( diff --git a/src/codegen/infrastructure/generator-context.ts b/src/codegen/infrastructure/generator-context.ts index 5aa2c29c..6c8054a1 100644 --- a/src/codegen/infrastructure/generator-context.ts +++ b/src/codegen/infrastructure/generator-context.ts @@ -988,12 +988,6 @@ export interface IGeneratorContext { currentVarDeclKey: string | null; setCurrentVarDeclKey(key: string | null): void; getCurrentVarDeclKey(): string | null; - - // Bounds-check elimination (perf) — IMPORTANT: keep at END of interface. - pushSafeIndexScope(): void; - popSafeIndexScope(): void; - addSafeIndex(indexName: string, arrayName: string): void; - isSafeIndex(indexName: string, arrayName: string): boolean; } /** @@ -2318,12 +2312,4 @@ export class MockGeneratorContext implements IGeneratorContext { getCurrentVarDeclKey(): string | null { return this.currentVarDeclKey; } - - // Bounds-check elimination stubs for MockGeneratorContext (no-ops). - pushSafeIndexScope(): void {} - popSafeIndexScope(): void {} - addSafeIndex(_indexName: string, _arrayName: string): void {} - isSafeIndex(_indexName: string, _arrayName: string): boolean { - return false; - } } diff --git a/src/codegen/llvm-generator.ts b/src/codegen/llvm-generator.ts index c0996e7c..d0072241 100644 --- a/src/codegen/llvm-generator.ts +++ b/src/codegen/llvm-generator.ts @@ -330,14 +330,6 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { public usesGC: number = 0; public usesMathRandom: number = 0; - // Bounds-check elimination: parallel arrays tracking (indexVar, arrayVar) pairs - // that are proven safe within the currently active loop scope. - // Parallel arrays used instead of Map for self-hosting compatibility. - public safeIndexVars: string[] = []; - public safeIndexArrays: string[] = []; - // Per-loop-scope frame: each entry is how many pairs were added when that scope began. - public safeIndexFrames: number[] = []; - public emitError(message: string, loc?: SourceLocation, suggestion?: string): never { this.diagnostics.error(message, loc, suggestion); const output = this.diagnostics.formatDiagnostic( @@ -1685,39 +1677,6 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { // LLVMGenerator-specific fields not in BaseGenerator this.stringBuilderSlen.clear(); this.stringBuilderScap.clear(); - this.safeIndexVars.length = 0; - this.safeIndexArrays.length = 0; - this.safeIndexFrames.length = 0; - } - - // Push a new loop scope for bounds-check elimination tracking - pushSafeIndexScope(): void { - this.safeIndexFrames.push(this.safeIndexVars.length); - } - - // Pop the most recent loop scope, removing any pairs added within it - popSafeIndexScope(): void { - if (this.safeIndexFrames.length === 0) return; - const frameStart = this.safeIndexFrames[this.safeIndexFrames.length - 1]; - this.safeIndexFrames.pop(); - this.safeIndexVars.length = frameStart; - this.safeIndexArrays.length = frameStart; - } - - // Record that indexName indexing into arrayName is proven in-bounds - addSafeIndex(indexName: string, arrayName: string): void { - this.safeIndexVars.push(indexName); - this.safeIndexArrays.push(arrayName); - } - - // Check whether indexName indexing arrayName is known safe in some enclosing loop scope - isSafeIndex(indexName: string, arrayName: string): boolean { - for (let i = this.safeIndexVars.length - 1; i >= 0; i--) { - if (this.safeIndexVars[i] === indexName && this.safeIndexArrays[i] === arrayName) { - return true; - } - } - return false; } getThisPointer(): string | null { diff --git a/src/codegen/statements/control-flow.ts b/src/codegen/statements/control-flow.ts index a57a8742..48d65a47 100644 --- a/src/codegen/statements/control-flow.ts +++ b/src/codegen/statements/control-flow.ts @@ -32,7 +32,6 @@ import type { FieldInfo } from "../infrastructure/type-resolver/types.js"; import { stripOptional } from "../infrastructure/type-system.js"; import { setWantsI1 } from "../expressions/condition-generator.js"; import { tryOptimizeWhileLoopMap } from "./loop-idiom.js"; -import { analyzeWhileSafety, analyzeForSafety } from "./loop-safety.js"; interface ExprBase { type: string; @@ -324,17 +323,7 @@ export class ControlFlowGenerator { this.ctx.setCurrentLabel(bodyLabel); this.loopContinueLabels.push(condLabel); this.loopBreakLabels.push(endLabel); - - // Bounds-check elimination: if the loop matches a safe pattern, register - // the (indexVar, arrayVar) pair so arr[i] inside body skips its bounds check. - const whileSafePat = analyzeWhileSafety(whileStmt); - this.ctx.pushSafeIndexScope(); - if (whileSafePat) { - this.ctx.addSafeIndex(whileSafePat.indexName, whileSafePat.arrayName); - } - this.ctx.generateBlock(whileStmt.body, params); - this.ctx.popSafeIndexScope(); this.loopContinueLabels.pop(); this.loopBreakLabels.pop(); const bodyHasTerminator = this.ctx.lastInstructionIsTerminator(); @@ -486,16 +475,7 @@ export class ControlFlowGenerator { this.ctx.setCurrentLabel(bodyLabel); this.loopContinueLabels.push(updateLabel); this.loopBreakLabels.push(endLabel); - - // Bounds-check elimination: classic-for-loop safe index registration. - const forSafePat = analyzeForSafety(forStmt as ForStatement); - this.ctx.pushSafeIndexScope(); - if (forSafePat) { - this.ctx.addSafeIndex(forSafePat.indexName, forSafePat.arrayName); - } - this.ctx.generateBlock(forStmt.body, params); - this.ctx.popSafeIndexScope(); this.loopContinueLabels.pop(); this.loopBreakLabels.pop(); const bodyHasTerminator3 = this.ctx.lastInstructionIsTerminator(); diff --git a/src/codegen/statements/loop-safety.ts b/src/codegen/statements/loop-safety.ts deleted file mode 100644 index d04181f4..00000000 --- a/src/codegen/statements/loop-safety.ts +++ /dev/null @@ -1,393 +0,0 @@ -// Loop safety analysis for bounds-check elimination. -// -// Detects simple loop patterns where a counter variable is provably -// always in-bounds for an array access: -// -// Pattern A: for (let i = 0; i < arr.length; i++) { ... arr[i] ... } -// Pattern B: let i = 0; while (i < arr.length) { ... arr[i] ... i = i + 1 } -// Pattern C: while (i < arr.length) { ... arr[i] ... i = i + k } (k > 0 const) -// -// For each matched pattern we register the (indexVar, arrayVar) pair in the -// generator context so that `arr[i]` inside the loop body emits a fast-path -// GEP without the usual icmp/branch/assume bounds check sequence. -// -// Soundness rules (any of these fail -> bail out, keep the bounds check): -// - index variable must only be incremented by a non-negative constant -// inside the loop body -// - array variable must not be reassigned, pushed to, popped from, spliced, -// shifted, unshifted, sliced-back, or otherwise mutated inside the body -// - the condition must be exactly `i < arr.length` - -import { - Expression, - Statement, - BlockStatement, - WhileStatement, - ForStatement, - BinaryNode, - MemberAccessNode, - VariableNode, - AssignmentStatement, - MethodCallNode, - IndexAccessAssignmentNode, - IfStatement, - ForOfStatement, - SwitchStatement, - DoWhileStatement, - ReturnStatement, - ThrowStatement, - VariableDeclaration, - CallNode, - UnaryNode, - IndexAccessNode, - ConditionalExpressionNode, - NumberNode, -} from "../../ast/types.js"; - -interface ExprBase { - type: string; -} -interface StmtBase { - type: string; -} - -// Pattern descriptor: index variable name paired with its safe array name. -export interface SafeLoopPattern { - indexName: string; - arrayName: string; -} - -function getVarName(expr: Expression): string | null { - if ((expr as ExprBase).type !== "variable") return null; - return (expr as VariableNode).name; -} - -// Detects `arr.length` as a member access expression, returning `arr` name. -function extractLengthOf(expr: Expression): string | null { - if ((expr as ExprBase).type !== "member_access") return null; - const m = expr as MemberAccessNode; - if (m.property !== "length") return null; - return getVarName(m.object); -} - -// Detects condition `i < arr.length` (strict less-than only). -function detectLengthBoundedCondition(cond: Expression): SafeLoopPattern | null { - if ((cond as ExprBase).type !== "binary") return null; - const bin = cond as BinaryNode; - if (bin.op !== "<") return null; - const idxName = getVarName(bin.left); - if (!idxName) return null; - const arrName = extractLengthOf(bin.right); - if (!arrName) return null; - return { indexName: idxName, arrayName: arrName }; -} - -// Detects `i = i + c` or `i = i + k` with non-negative c. -// Returns true if the assignment is a monotone forward increment of `varName`. -function isForwardIncrement(stmt: Statement, varName: string): boolean { - if ((stmt as StmtBase).type !== "assignment") return false; - const assign = stmt as AssignmentStatement; - if (assign.name !== varName) return false; - if ((assign.value as ExprBase).type !== "binary") return false; - const bin = assign.value as BinaryNode; - if (bin.op !== "+") return false; - const leftIsVar = getVarName(bin.left) === varName; - const rightIsVar = getVarName(bin.right) === varName; - // Either i = i + X or i = X + i - if (leftIsVar) { - return isNonNegativeIncrement(bin.right); - } - if (rightIsVar) { - return isNonNegativeIncrement(bin.left); - } - return false; -} - -// Returns true if expr is a numeric literal >= 0. -function isNonNegativeIncrement(expr: Expression): boolean { - if ((expr as ExprBase).type === "number") { - const n = (expr as NumberNode).value; - return n >= 0; - } - return false; -} - -// Returns true if `expr` (assignment update clause for a for-loop) is a -// forward increment of `varName` such as `i++` or `i = i + 1`. -function isForUpdateIncrement(update: Statement | Expression | null, varName: string): boolean { - if (!update) return false; - const u = update as StmtBase; - if (u.type === "assignment") { - return isForwardIncrement(update as Statement, varName); - } - if (u.type === "unary") { - const un = update as UnaryNode; - if (un.op === "++" || un.op === "+=" || un.op === "postfix++" || un.op === "postfix+=") { - return getVarName(un.operand) === varName; - } - } - return false; -} - -// Walk a block recursively and return true if it contains any statement that -// could invalidate the (indexName, arrayName) safety guarantee. -function bodyInvalidatesSafety( - block: BlockStatement, - indexName: string, - arrayName: string, -): boolean { - const stmts = block.statements; - for (let i = 0; i < stmts.length; i++) { - if (stmtInvalidatesSafety(stmts[i], indexName, arrayName)) return true; - } - return false; -} - -function stmtInvalidatesSafety(stmt: Statement, indexName: string, arrayName: string): boolean { - const t = (stmt as StmtBase).type; - - // Any write to the index variable must be a forward increment - if (t === "assignment") { - const a = stmt as AssignmentStatement; - if (a.name === indexName) { - if (!isForwardIncrement(stmt, indexName)) return true; - return false; - } - if (a.name === arrayName) { - // direct reassignment of the array variable — unsafe - return true; - } - // Walk the RHS for expression-level mutations of array (push calls etc.) - if (exprMutatesArray(a.value, arrayName)) return true; - return false; - } - - // index_access_assignment: `arr[idx] = val` — for numeric/string/object arrays - // this does NOT change length, but to stay safe if the target is our array, - // skip (we could keep accepting it for Uint8Array reads, but the index itself - // needs checking). Only bail if the array is OUR array AND the assignment - // could overwrite a different slot than arr[i]. - if (t === "index_access_assignment") { - const ia = stmt as IndexAccessAssignmentNode; - const tgtName = getVarName(ia.object); - if (tgtName === arrayName) { - // length doesn't change, this is fine for bounds of OTHER indices - // still walk value / index for method calls that mutate - if (exprMutatesArray(ia.index, arrayName)) return true; - if (exprMutatesArray(ia.value as Expression, arrayName)) return true; - return false; - } - if (exprMutatesArray(ia.index, arrayName)) return true; - if (exprMutatesArray(ia.value as Expression, arrayName)) return true; - return false; - } - - if (t === "variable_declaration") { - const vd = stmt as VariableDeclaration; - if (vd.name === indexName || vd.name === arrayName) return true; - if (vd.value && exprMutatesArray(vd.value, arrayName)) return true; - return false; - } - - if (t === "if") { - const ifs = stmt as IfStatement; - if (exprMutatesArray(ifs.condition, arrayName)) return true; - if (bodyInvalidatesSafety(ifs.thenBlock, indexName, arrayName)) return true; - if (ifs.elseBlock && bodyInvalidatesSafety(ifs.elseBlock, indexName, arrayName)) return true; - return false; - } - - if (t === "while") { - const w = stmt as WhileStatement; - if (exprMutatesArray(w.condition, arrayName)) return true; - if (bodyInvalidatesSafety(w.body, indexName, arrayName)) return true; - return false; - } - - if (t === "do_while") { - const dw = stmt as DoWhileStatement; - if (exprMutatesArray(dw.condition, arrayName)) return true; - if (bodyInvalidatesSafety(dw.body, indexName, arrayName)) return true; - return false; - } - - if (t === "for") { - const f = stmt as ForStatement; - if (f.init && stmtInvalidatesSafety(f.init as Statement, indexName, arrayName)) return true; - if (f.condition && exprMutatesArray(f.condition, arrayName)) return true; - if (f.update) { - const up = f.update as StmtBase; - if (up.type === "assignment") { - if (stmtInvalidatesSafety(f.update as Statement, indexName, arrayName)) return true; - } else if (exprMutatesArray(f.update as Expression, arrayName)) { - return true; - } - } - if (bodyInvalidatesSafety(f.body, indexName, arrayName)) return true; - return false; - } - - if (t === "for_of") { - const fo = stmt as ForOfStatement; - if (fo.variableName === indexName || fo.variableName === arrayName) return true; - if (exprMutatesArray(fo.iterable, arrayName)) return true; - if (bodyInvalidatesSafety(fo.body, indexName, arrayName)) return true; - return false; - } - - if (t === "return") { - const r = stmt as ReturnStatement; - if (r.value && exprMutatesArray(r.value, arrayName)) return true; - return false; - } - - if (t === "throw") { - const th = stmt as ThrowStatement; - if (th.argument && exprMutatesArray(th.argument, arrayName)) return true; - return false; - } - - if (t === "block") { - return bodyInvalidatesSafety(stmt as BlockStatement, indexName, arrayName); - } - - if (t === "switch") { - const sw = stmt as SwitchStatement; - if (exprMutatesArray(sw.discriminant, arrayName)) return true; - for (let i = 0; i < sw.cases.length; i++) { - const cs = sw.cases[i]; - for (let j = 0; j < cs.consequent.length; j++) { - if (stmtInvalidatesSafety(cs.consequent[j], indexName, arrayName)) return true; - } - } - return false; - } - - // Method calls used as statements: check for mutating methods. - if (t === "method_call_statement" || t === "method_call") { - if (exprMutatesArray(stmt as unknown as Expression, arrayName)) return true; - return false; - } - - // break/continue — safe. - if (t === "break" || t === "continue") return false; - - // Unknown statement type — bail to be safe. - return true; -} - -const MUTATING_METHODS: string[] = [ - "push", - "pop", - "shift", - "unshift", - "splice", - "sort", - "reverse", - "fill", - "copyWithin", -]; - -function isMutatingMethod(name: string): boolean { - for (let i = 0; i < MUTATING_METHODS.length; i++) { - if (MUTATING_METHODS[i] === name) return true; - } - return false; -} - -// Recursively walk an expression for any call that could mutate arrayName's length -// or any nested expression that itself mutates the array. -function exprMutatesArray(expr: Expression, arrayName: string): boolean { - if (!expr) return false; - const t = (expr as ExprBase).type; - - if (t === "method_call") { - const mc = expr as MethodCallNode; - const objName = getVarName(mc.object); - if (objName === arrayName && isMutatingMethod(mc.method)) return true; - if (exprMutatesArray(mc.object, arrayName)) return true; - for (let i = 0; i < mc.args.length; i++) { - if (exprMutatesArray(mc.args[i], arrayName)) return true; - } - return false; - } - - if (t === "call") { - const c = expr as CallNode; - for (let i = 0; i < c.args.length; i++) { - if (exprMutatesArray(c.args[i], arrayName)) return true; - } - return false; - } - - if (t === "binary") { - const b = expr as BinaryNode; - return exprMutatesArray(b.left, arrayName) || exprMutatesArray(b.right, arrayName); - } - - if (t === "unary") { - const u = expr as UnaryNode; - return exprMutatesArray(u.operand, arrayName); - } - - if (t === "member_access") { - const m = expr as MemberAccessNode; - return exprMutatesArray(m.object, arrayName); - } - - if (t === "index_access") { - const ia = expr as IndexAccessNode; - return exprMutatesArray(ia.object, arrayName) || exprMutatesArray(ia.index, arrayName); - } - - if (t === "conditional") { - const c = expr as ConditionalExpressionNode; - return ( - exprMutatesArray(c.condition, arrayName) || - exprMutatesArray(c.consequent, arrayName) || - exprMutatesArray(c.alternate, arrayName) - ); - } - - return false; -} - -// Analyze a while statement. Returns the pattern if safe, null otherwise. -export function analyzeWhileSafety(whileStmt: WhileStatement): SafeLoopPattern | null { - const pat = detectLengthBoundedCondition(whileStmt.condition); - if (!pat) return null; - - // Body must contain a forward-increment of indexName as a statement - // somewhere (not strict — just ensures termination is plausible). - if (!bodyContainsForwardIncrement(whileStmt.body, pat.indexName)) return null; - - // Body must not invalidate safety. - if (bodyInvalidatesSafety(whileStmt.body, pat.indexName, pat.arrayName)) return null; - - return pat; -} - -function bodyContainsForwardIncrement(block: BlockStatement, varName: string): boolean { - const stmts = block.statements; - for (let i = 0; i < stmts.length; i++) { - if (isForwardIncrement(stmts[i], varName)) return true; - } - return false; -} - -// Analyze a for statement. Returns the pattern if safe, null otherwise. -export function analyzeForSafety(forStmt: ForStatement): SafeLoopPattern | null { - if (!forStmt.condition) return null; - const pat = detectLengthBoundedCondition(forStmt.condition); - if (!pat) return null; - - // Update must be forward increment of indexName - if (!isForUpdateIncrement(forStmt.update, pat.indexName)) return null; - - // Body must not invalidate safety (index var is updated in `update`, not body, - // so we don't require body increment here — we still reject body-side updates - // of the index). - if (bodyInvalidatesSafety(forStmt.body, pat.indexName, pat.arrayName)) return null; - - return pat; -} diff --git a/tests/fixtures/arrays/array-bounds-elim-safe.ts b/tests/fixtures/arrays/array-bounds-elim-safe.ts deleted file mode 100644 index 2c2d4544..00000000 --- a/tests/fixtures/arrays/array-bounds-elim-safe.ts +++ /dev/null @@ -1,29 +0,0 @@ -// @test-description: bounds-check elimination for safe loop indices -// Exercises the loop pattern `while (i < arr.length)` — all accesses must -// still produce correct values even when the bounds check is elided. -const arr: number[] = [10, 20, 30, 40, 50]; -let sum = 0; -let i = 0; -while (i < arr.length) { - sum = sum + arr[i]; - i = i + 1; -} - -const strs: string[] = ["a", "b", "c", "d"]; -let joined = ""; -let j = 0; -while (j < strs.length) { - joined = joined + strs[j]; - j = j + 1; -} - -// Classic for-loop with i < arr.length. -const nums: number[] = [1, 2, 3, 4, 5, 6]; -let prod = 1; -for (let k = 0; k < nums.length; k = k + 1) { - prod = prod * nums[k]; -} - -if (sum === 150 && joined === "abcd" && prod === 720) { - console.log("TEST_PASSED"); -} diff --git a/tests/fixtures/arrays/array-bounds-elim-unsafe-offset.ts b/tests/fixtures/arrays/array-bounds-elim-unsafe-offset.ts deleted file mode 100644 index e96701de..00000000 --- a/tests/fixtures/arrays/array-bounds-elim-unsafe-offset.ts +++ /dev/null @@ -1,11 +0,0 @@ -// @test-exit-code: 1 -// @test-description: bounds-check is NOT elided for offset indices -// Even though `i < arr.length`, `arr[i + 5]` is NOT proven in-bounds, -// so the bounds check must still fire at runtime. -const arr: number[] = [1, 2, 3]; -let i = 0; -while (i < arr.length) { - const v = arr[i + 5]; - console.log(v); - i = i + 1; -} diff --git a/tests/fixtures/arrays/array-bounds-elim-unsafe-pop.ts b/tests/fixtures/arrays/array-bounds-elim-unsafe-pop.ts deleted file mode 100644 index 525c226f..00000000 --- a/tests/fixtures/arrays/array-bounds-elim-unsafe-pop.ts +++ /dev/null @@ -1,22 +0,0 @@ -// @test-description: bounds-check elimination bails when loop body mutates the array -// The pop() shrinks arr mid-iteration; without the bounds check, subsequent -// accesses would read past the new end. We explicitly compare arr.length each -// iteration since pop shortens the array. -const arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8]; -let total = 0; -let i = 0; -while (i < arr.length) { - total = total + arr[i]; - arr.pop(); - i = i + 1; -} - -// i=0: arr=[1..8], read 1, pop -> [1..7] -// i=1: arr=[1..7], read 2, pop -> [1..6] -// i=2: arr=[1..6], read 3, pop -> [1..5] -// i=3: arr=[1..5], read 4, pop -> [1..4] -// i=4: arr=[1..4], 4 < 4 false, stop. -// total = 1 + 2 + 3 + 4 = 10 -if (total === 10) { - console.log("TEST_PASSED"); -}