diff --git a/compiler/forget/src/HIR/Codegen.ts b/compiler/forget/src/HIR/Codegen.ts index 2fc72377ac..e8aa9dbf97 100644 --- a/compiler/forget/src/HIR/Codegen.ts +++ b/compiler/forget/src/HIR/Codegen.ts @@ -91,7 +91,14 @@ type Temporaries = Map; class CodegenVisitor implements - Visitor, t.Expression, t.Statement, t.SwitchCase> + Visitor< + Array, + Array, + Array, + t.Expression, + t.Statement, + t.SwitchCase + > { depth: number = 0; temp: Map = new Map(); @@ -100,10 +107,77 @@ class CodegenVisitor this.depth++; return []; } - enterValueBlock(): t.Statement[] { - this.depth++; - return []; + appendBlock( + block: t.Statement[], + item: t.Statement, + blockId?: BlockId | undefined + ): void { + if (item.type === "EmptyStatement") { + return; + } + if (blockId !== undefined) { + block.push( + createLabelledStatement( + item.loc, + t.identifier(codegenLabel(blockId)), + item + ) + ); + } else { + block.push(item); + } } + leaveBlock(block: t.Statement[]): t.Statement { + this.depth--; + return t.blockStatement(block); + } + enterValueBlock(): t.Statement[] { + return this.enterBlock(); + } + appendValueBlock(block: t.Statement[], item: t.Statement): void { + this.appendBlock(block, item); + } + leaveValueBlock(block: t.Statement[], place: t.Expression): t.Expression { + this.depth--; + if (block.length === 0) { + return place; + } + const expressions = block.map((stmt) => { + switch (stmt.type) { + case "ExpressionStatement": + return stmt.expression; + default: + todoInvariant( + false, + `Handle conversion of ${stmt.type} to expression` + ); + } + }); + expressions.push(place); + return t.sequenceExpression(expressions); + } + + enterInitBlock(block: t.Statement[]): t.Statement[] { + return this.enterBlock(); + } + + appendInitBlock(block: t.Statement[], item: t.Statement): void { + this.appendBlock(block, item); + } + leaveInitBlock(block: t.Statement[]): t.Statement[] { + switch (block.length) { + case 0: { + return [t.emptyStatement()]; + } + case 1: { + return [block[0]]; + } + default: { + return [t.blockStatement(block)]; + } + } + } + visitValue(value: InstructionValue): t.Expression { return codegenInstructionValue(this.temp, value); } @@ -191,8 +265,25 @@ class CodegenVisitor return createWhileStatement(terminal.loc, terminal.test, terminal.loop); } case "for": { + const initBlock = terminal.init; + invariant( + initBlock.length === 1, + "Expected for init to be a single expression or statement" + ); + const initStatement = initBlock[0]!; + let init; + if (initStatement.type === "VariableDeclaration") { + init = initStatement; + } else if (initStatement.type === "ExpressionStatement") { + init = initStatement.expression; + } else { + invariant( + false, + `Expected 'for' init block to contain variable declaration or an expression, got '${initStatement.type}'.` + ); + } return t.forStatement( - terminal.init as any, // TODO: make sure it's a variable declaration + init, terminal.test, terminal.update, terminal.loop @@ -225,49 +316,6 @@ class CodegenVisitor visitCase(test: t.Expression | null, block: t.Statement): t.SwitchCase { return t.switchCase(test, [block]); } - appendBlock( - block: t.Statement[], - item: t.Statement, - blockId?: BlockId | undefined - ): void { - if (item.type === "EmptyStatement") { - return; - } - if (blockId !== undefined) { - block.push( - createLabelledStatement( - item.loc, - t.identifier(codegenLabel(blockId)), - item - ) - ); - } else { - block.push(item); - } - } - leaveBlock(block: t.Statement[]): t.Statement { - this.depth--; - return t.blockStatement(block); - } - leaveValueBlock(block: t.Statement[], place: t.Expression): t.Expression { - this.depth--; - if (block.length === 0) { - return place; - } - const expressions = block.map((stmt) => { - switch (stmt.type) { - case "ExpressionStatement": - return stmt.expression; - default: - todoInvariant( - false, - `Handle conversion of ${stmt.type} to expression` - ); - } - }); - expressions.push(place); - return t.sequenceExpression(expressions); - } } function codegenLabel(id: BlockId): string { diff --git a/compiler/forget/src/HIR/HIRTreeVisitor.ts b/compiler/forget/src/HIR/HIRTreeVisitor.ts index 399bd64442..7979e8646a 100644 --- a/compiler/forget/src/HIR/HIRTreeVisitor.ts +++ b/compiler/forget/src/HIR/HIRTreeVisitor.ts @@ -27,25 +27,35 @@ import { * * See the {@link Visitor} interface for more about implementing a visitor. */ -export function visitTree( +export function visitTree< + TBlock, + TInit, + TValueBlock, + TValue, + TStatement, + TCase +>( fn: HIRFunction, - visitor: Visitor -): TItem { + visitor: Visitor +): TStatement { const cx = new Context(fn.body); const driver = new Driver(cx, visitor); return driver.traverseBlock(cx.block(fn.body.entry)); } -class Driver { +class Driver { cx: Context; - visitor: Visitor; + visitor: Visitor; - constructor(cx: Context, visitor: Visitor) { + constructor( + cx: Context, + visitor: Visitor + ) { this.cx = cx; this.visitor = visitor; } - traverseBlock(block: BasicBlock): TItem { + traverseBlock(block: BasicBlock): TStatement { const blockValue = this.visitor.enterBlock(); this.visitBlock(block, blockValue); return this.visitor.leaveBlock(blockValue); @@ -110,7 +120,7 @@ class Driver { } this.visitor.visitTerminalId(terminal.id); - let consequent: TItem | null = null; + let consequent: TStatement | null = null; if (this.cx.isScheduled(terminal.consequent)) { consequent = this.visitBreak(terminal.consequent); } else { @@ -119,7 +129,7 @@ class Driver { ); } - let alternate: TItem | null = null; + let alternate: TStatement | null = null; if (alternateId !== null) { if (this.cx.isScheduled(alternateId)) { alternate = this.visitBreak(alternateId); @@ -233,26 +243,6 @@ class Driver { break; } case "while": { - const testBlock = this.cx.ir.blocks.get(terminal.test)!; - const testTerminal = testBlock.terminal; - invariant( - testTerminal.kind === "if", - "Expected while loop test block to end in an if" - ); - const testValueBlock = this.visitor.enterValueBlock(); - for (const instr of testBlock.instructions) { - const value = this.visitor.visitValue(instr.value, instr.id); - const item = this.visitor.visitInstruction(instr, value); - this.visitor.appendBlock(testValueBlock, item); - } - const testValueLast = this.visitor.visitValue( - testTerminal.test, - testTerminal.id - ); - const testValue = this.visitor.leaveValueBlock( - testValueBlock, - testValueLast - ); const fallthroughId = terminal.fallthrough !== null && !this.cx.isScheduled(terminal.fallthrough) @@ -271,7 +261,17 @@ class Driver { scheduleIds.push(scheduleId); this.visitor.visitTerminalId(terminal.id); - let loopBody: TItem; + const testBlock = this.cx.ir.blocks.get(terminal.test)!; + const testTerminal = testBlock.terminal; + invariant( + testTerminal.kind === "if", + "Expected while loop test block to end in an if" + ); + const testValue = this.visitValueBlock(blockValue, testBlock, { + value: testTerminal.test, + id: testTerminal.id, + }); + let loopBody: TStatement; if (loopId) { loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!); } else { @@ -331,18 +331,15 @@ class Driver { ); scheduleIds.push(scheduleId); + this.visitor.visitTerminalId(terminal.id); + const initBlock = this.cx.ir.blocks.get(terminal.init)!; const initTerminal = initBlock.terminal; invariant( initTerminal.kind === "goto", "Expected for loop init block to end in a goto" ); - - let initItem; - for (const instr of initBlock.instructions) { - const value = this.visitor.visitValue(instr.value, instr.id); - initItem = this.visitor.visitInstruction(instr, value); - } + const initValue = this.visitInitBlock(blockValue, initBlock); const testBlock = this.cx.ir.blocks.get(terminal.test)!; const testTerminal = testBlock.terminal; @@ -350,20 +347,10 @@ class Driver { testTerminal.kind === "if", "Expected for loop test block to end in an if" ); - const testValueBlock = this.visitor.enterValueBlock(); - for (const instr of testBlock.instructions) { - const value = this.visitor.visitValue(instr.value, instr.id); - const item = this.visitor.visitInstruction(instr, value); - this.visitor.appendBlock(testValueBlock, item); - } - const testValueLast = this.visitor.visitValue( - testTerminal.test, - testTerminal.id - ); - const testValue = this.visitor.leaveValueBlock( - testValueBlock, - testValueLast - ); + const testValue = this.visitValueBlock(blockValue, testBlock, { + value: testTerminal.test, + id: testTerminal.id, + }); const updateBlock = this.cx.ir.blocks.get(terminal.update)!; const updateTerminal = updateBlock.terminal; @@ -371,12 +358,9 @@ class Driver { updateTerminal.kind === "goto", "Expected for loop update block to end in a goto" ); - let updateValue; - for (const instr of updateBlock.instructions) { - updateValue = this.visitor.visitValue(instr.value, instr.id); - } + const updateValue = this.visitValueBlock(blockValue, updateBlock); - let loopBody: TItem; + let loopBody: TStatement; if (loopId) { loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!); } else { @@ -396,9 +380,9 @@ class Driver { blockValue, this.visitor.visitTerminal({ kind: "for", - init: initItem as any, + init: initValue, test: testValue, - update: updateValue as any, + update: updateValue, loop: loopBody, }), fallthroughId @@ -409,9 +393,9 @@ class Driver { blockValue, this.visitor.visitTerminal({ kind: "for", - init: initItem as any, + init: initValue, test: testValue, - update: updateValue as any, + update: updateValue, loop: loopBody, }) ); @@ -450,12 +434,50 @@ class Driver { } } - emptyBlock(): TItem { + visitInitBlock(parent: TBlock, block: BasicBlock): TInit { + const initBlock = this.visitor.enterInitBlock(parent); + for (const instr of block.instructions) { + const value = this.visitor.visitValue(instr.value, instr.id); + const item = this.visitor.visitInstruction(instr, value); + this.visitor.appendInitBlock(initBlock, item); + } + return this.visitor.leaveInitBlock(initBlock); + } + + visitValueBlock( + parent: TBlock, + block: BasicBlock, + terminalValue?: { value: InstructionValue; id: InstructionId } + ): TValue { + const valueBlock = this.visitor.enterValueBlock(parent); + const instructions = [...block.instructions]; + let lastValue: { value: InstructionValue; id: InstructionId }; + if (terminalValue != null) { + lastValue = terminalValue; + } else { + invariant(instructions.length > 0, "Value block may not be empty"); + const last = instructions.pop()!; + invariant( + last.lvalue === null, + "Expected value block to end in a value, not an assignment" + ); + lastValue = { value: last.value, id: last.id }; + } + for (const instr of instructions) { + const value = this.visitor.visitValue(instr.value, instr.id); + const item = this.visitor.visitInstruction(instr, value); + this.visitor.appendValueBlock(valueBlock, item); + } + const value = this.visitor.visitValue(lastValue.value, lastValue.id); + return this.visitor.leaveValueBlock(valueBlock, value); + } + + emptyBlock(): TStatement { const block = this.visitor.enterBlock(); return this.visitor.leaveBlock(block); } - visitBreak(block: BlockId): TItem | null { + visitBreak(block: BlockId): TStatement | null { const target = this.cx.getBreakTarget(block); if (target === null) { // TODO: we should always have a target @@ -477,7 +499,7 @@ class Driver { } } - visitContinue(block: BlockId): TItem | null { + visitContinue(block: BlockId): TStatement | null { const target = this.cx.getContinueTarget(block); invariant( target !== null, @@ -746,19 +768,62 @@ type ControlFlowTarget = * * TBlock = representation of a list of statements * TValue = represenation of an InstructionValue - * TItem = representation of an Instruction + * TStatement = representation of an Instruction * TCase = representation of a switch case */ -export interface Visitor { +export interface Visitor< + TBlock, + TInit, + TValueBlock, + TValue, + TStatement, + TCase +> { /** * Must create an "empty" instance of the visitor's represenation for * the contents of a block. */ enterBlock(): TBlock; - enterValueBlock(): TBlock; + /** + * Appends an item onto the given block, with an optional label. The label + * indicates that a break/continue will proceed to code *after* the given item. + */ + appendBlock(block: TBlock, item: TStatement, label?: BlockId): void; - leaveValueBlock(block: TBlock, value: TValue): TValue; + /** + * Converts the visitor's block representation into the representation of a + * block item, simultaneously "closing" the given block. The block will no + * longer be modified by the visitor driver. + */ + leaveBlock(block: TBlock): TStatement; + + /** + * Must create an "empty" instance of the visitor's representation for a value + * block, which can generally contain only expressions but not statements. The + * currently active parent block is passed as an argument. + * + * The exception is the for initializer, which may contain declarations but not + * other statement types. + */ + enterValueBlock(block: TBlock): TValueBlock; + + /** + * Appends an item onto the given value block. + */ + appendValueBlock(block: TValueBlock, item: TStatement): void; + + /** + * Converts the visitor's value block (and final value) to the visitor's + * value representation. + */ + leaveValueBlock(block: TValueBlock, value: TValue): TValue; + + enterInitBlock(block: TBlock): TValueBlock; + + appendInitBlock(block: TValueBlock, item: TStatement): void; + + leaveInitBlock(block: TValueBlock): TInit; /** * Convert an InstructionValue into the visitor's own representation @@ -770,7 +835,7 @@ export interface Visitor { * Convert an Instruction into the visitor's own representation of * a block item. */ - visitInstruction(instruction: Instruction, value: TValue): TItem; + visitInstruction(instruction: Instruction, value: TValue): TStatement; /** * Called when a terminal is reached, before processing any of its @@ -783,7 +848,7 @@ export interface Visitor { * have to be emitted — to the visitor's representation. The visitor * can choose to return null if this does not need to be represented. */ - visitImplicitTerminal(): TItem | null; + visitImplicitTerminal(): TStatement | null; /** * Converts a terminal into the visitor's own representation of a block @@ -791,50 +856,39 @@ export interface Visitor { * values and block ids will have already been converted into the visitor's * own representations. */ - visitTerminal(terminal: BlockTerminal): TItem; + visitTerminal( + terminal: BlockTerminal + ): TStatement; /** * Visits a switch case statement, which is collected into a switch terminal * variant. */ - visitCase(test: TValue | null, block: TItem): TCase; - - /** - * Appends an item onto the given block, with an optional label. The label - * indicates that a break/continue will proceed to code *after* the given item. - */ - appendBlock(block: TBlock, item: TItem, label?: BlockId): void; - - /** - * Converts the visitor's block representation into the representation of a - * block item, simultaneously "closing" the given block. The block will no - * longer be modified by the visitor driver. - */ - leaveBlock(block: TBlock): TItem; + visitCase(test: TValue | null, block: TStatement): TCase; } -export type BlockTerminal = +export type BlockTerminal = | { kind: "return"; loc: SourceLocation; value: TValue | null } | { kind: "throw"; value: TValue } | { kind: "if"; test: TValue; - consequent: TItem; - alternate: TItem | null; + consequent: TStatement; + alternate: TStatement | null; } | { kind: "switch"; test: TValue; cases: Array } | { kind: "while"; loc: SourceLocation; test: TValue; - loop: TItem; + loop: TStatement; } | { kind: "for"; - init: TItem; + init: TInit; test: TValue; update: TValue; - loop: TItem; + loop: TStatement; } | { kind: "break"; label: BlockId | null } | { kind: "continue"; label: BlockId | null }; diff --git a/compiler/forget/src/HIR/InferReactiveScopeDependencies.ts b/compiler/forget/src/HIR/InferReactiveScopeDependencies.ts index c785bfb60f..39ac2f36d2 100644 --- a/compiler/forget/src/HIR/InferReactiveScopeDependencies.ts +++ b/compiler/forget/src/HIR/InferReactiveScopeDependencies.ts @@ -35,7 +35,7 @@ export function instructionInScope( } class ScopeDependenciesVisitor - implements Visitor + implements Visitor { #identifiers: Map = new Map(); // Scopes that are currently active at this point in the traversal @@ -89,6 +89,9 @@ class ScopeDependenciesVisitor case "switch": case "for": case "while": { + if (typeof terminal?.test?.kind !== "string") { + console.log(terminal); + } for (const operand of eachInstructionValueOperand(terminal.test)) { this.#addTerminalDependency(operand); } @@ -176,11 +179,15 @@ class ScopeDependenciesVisitor enterBlock(): void {} enterValueBlock(): void {} + enterInitBlock(block: void): void {} visitImplicitTerminal(): void | null {} visitCase(test: InstructionValue, block: void): void {} appendBlock(block: void, item: void, label?: BlockId | undefined): void {} + appendValueBlock(block: void, item: void): void {} + appendInitBlock(block: void, item: void): void {} leaveBlock(block: void): void {} leaveValueBlock(block: void, value: InstructionValue): InstructionValue { return value; } + leaveInitBlock(block: void): void {} } diff --git a/compiler/forget/src/HIR/InferReactiveScopes.ts b/compiler/forget/src/HIR/InferReactiveScopes.ts index 484eb39967..0f767f9553 100644 --- a/compiler/forget/src/HIR/InferReactiveScopes.ts +++ b/compiler/forget/src/HIR/InferReactiveScopes.ts @@ -15,10 +15,13 @@ import { InstructionId, InstructionValue, makeInstructionId, + MutableRange, ReactiveScope, ScopeId, } from "./HIR"; import { BlockTerminal, Visitor, visitTree } from "./HIRTreeVisitor"; +import { log } from "./logger"; +import { printFunction } from "./PrintHIR"; import { eachInstructionOperand, eachInstructionValueOperand, @@ -120,6 +123,10 @@ export function inferReactiveScopes(fn: HIRFunction) { mergeScopesWithIdenticalRanges(fn); visitTree(fn, new AlignReactiveScopesToBlockScopeRangeVisitor()); + log( + () => + `AlignReactiveScopesToBlockScopeRangeVisitor:\n${printFunction(fn)}\n\n` + ); visitTree(fn, new MergeOverlappingReactiveScopesVisitor()); } @@ -158,10 +165,8 @@ type ShadowableReactiveScope = { shadowedBy: ReactiveScope | null; }; -// maybe we just merge cases of interleaving when at the same block scope - eh fine? -// then we can look at scopes that start at the same block scope and overlap class MergeOverlappingReactiveScopesVisitor - implements Visitor + implements Visitor { scopes: Array = []; seenScopes: Set = new Set(); @@ -257,6 +262,12 @@ class MergeOverlappingReactiveScopesVisitor enterValueBlock(): void { this.enterBlock(); } + enterInitBlock(block: void): void { + this.enterBlock(); + } + leaveInitBlock(block: void): void { + this.leaveBlock(); + } leaveValueBlock(block: void, value: void): void { this.leaveBlock(); } @@ -290,6 +301,8 @@ class MergeOverlappingReactiveScopesVisitor visitTerminal(terminal: BlockTerminal): void {} visitCase(test: void | null, block: void): void {} appendBlock(block: void, item: void, label?: BlockId | undefined): void {} + appendValueBlock(block: void, item: void): void {} + appendInitBlock(block: void, item: void): void {} leaveBlock(block: void): void { this.scopes.pop(); if (this.scopes.length === 0) { @@ -313,9 +326,13 @@ type PendingReactiveScope = { active: boolean; scope: ReactiveScope }; /** * Aligns scopes to block scope boundaries. + * + * TODO @josephsavona this algorithm isn't quite right. we need to ensure that + * reactive scopes can only be closed (end updated) at the same block scope as they + * were opened (start encountered). */ class AlignReactiveScopesToBlockScopeRangeVisitor - implements Visitor + implements Visitor { // For each block scope (outer array) stores a list of ReactiveScopes that start // in that block scope. @@ -348,6 +365,8 @@ class AlignReactiveScopesToBlockScopeRangeVisitor this.blockScopes.push([]); } + appendBlock(block: void, item: void, label?: BlockId | undefined): void {} + leaveBlock(block: void): void { const lastScope = this.blockScopes.pop(); invariant( @@ -364,12 +383,21 @@ class AlignReactiveScopesToBlockScopeRangeVisitor enterValueBlock(): void { this.enterBlock(); } - + appendValueBlock(block: void, item: void): void {} leaveValueBlock(block: void, value: void): void { - this.leaveBlock(); + this.leaveBlock(block); + } + + enterInitBlock(block: void): void { + this.enterBlock(); + } + appendInitBlock(block: void, item: void): void {} + leaveInitBlock(block: void): void { + this.leaveBlock(block); } visitInstruction(instruction: Instruction, value: void): void { + this.visitId(instruction.id); const scope = getInstructionScope(instruction); if (scope !== null) { if (!this.seenScopes.has(scope.id)) { @@ -381,8 +409,6 @@ class AlignReactiveScopesToBlockScopeRangeVisitor }); } } - - this.visitId(instruction.id); } visitTerminalId(id: InstructionId): void { @@ -396,18 +422,28 @@ class AlignReactiveScopesToBlockScopeRangeVisitor // no-ops visitValue(value: InstructionValue): void {} visitCase(test: void | null, block: void): void {} - appendBlock(block: void, item: void, label?: BlockId | undefined): void {} } function getInstructionScope(instr: Instruction): ReactiveScope | null { - if (instr.lvalue !== null && instr.lvalue.place.identifier.scope !== null) { + if ( + instr.lvalue !== null && + instr.lvalue.place.identifier.scope !== null && + isActive(instr, instr.lvalue.place.identifier.scope.range) + ) { return instr.lvalue.place.identifier.scope; } else { for (const operand of eachInstructionOperand(instr)) { - if (operand.identifier.scope !== null) { + if ( + operand.identifier.scope !== null && + isActive(instr, operand.identifier.scope.range) + ) { return operand.identifier.scope; } } } return null; } + +function isActive(instr: Instruction, range: MutableRange): boolean { + return instr.id >= range.start && instr.id < range.end; +} diff --git a/compiler/forget/src/HIR/PrintHIRTree.ts b/compiler/forget/src/HIR/PrintHIRTree.ts index f64deaaec0..9910ce7fdf 100644 --- a/compiler/forget/src/HIR/PrintHIRTree.ts +++ b/compiler/forget/src/HIR/PrintHIRTree.ts @@ -25,7 +25,17 @@ export function printHIRTree(fn: HIRFunction): string { return visitTree(fn, new PrintVisitor()); } -class PrintVisitor implements Visitor, string, string, string> { +class PrintVisitor + implements + Visitor< + Array, + Array, + Array, + string, + string, + string + > +{ depth: number = 0; // for indentation enterBlock(): string[] { @@ -38,6 +48,12 @@ class PrintVisitor implements Visitor, string, string, string> { leaveValueBlock(block: string[], value: string): string { return this.leaveBlock(block); } + enterInitBlock(block: string[]): string[] { + return this.enterBlock(); + } + leaveInitBlock(block: string[]): string[] { + return block; + } visitValue(value: InstructionValue): string { return printMixedHIR(value); } @@ -142,6 +158,12 @@ class PrintVisitor implements Visitor, string, string, string> { block.push(`${prefix}bb${label}:`); } } + appendValueBlock(block: string[], item: string): void { + this.appendBlock(block, item); + } + appendInitBlock(block: string[], item: string): void { + this.appendBlock(block, item); + } leaveBlock(block: string[]): string { this.depth--; const prefix = " ".repeat(this.depth); diff --git a/compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md b/compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md index 081464c148..8543ea1bf1 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/ssa-for.expect.md @@ -20,7 +20,7 @@ bb0: [2] For init=bb3 test=bb1 loop=bb5 update=bb4 fallthrough=bb2 bb3: predecessor blocks: bb0 - [3] Const mutate i$8_@1[3:13] = 0 + [3] Const mutate i$8_@1[3:5] = 0 [4] Goto bb1 bb1: predecessor blocks: bb3 bb4 @@ -40,6 +40,7 @@ bb2: predecessor blocks: bb1 [13] Return read x$7_@0 scope3 [6:8]: + - dependency: read i$8_@1 - dependency: read $9_@2 ``` @@ -56,7 +57,7 @@ flowchart TB end subgraph bb3 bb3_instrs[" - [3] Const mutate i$8_@1[3:13] = 0 + [3] Const mutate i$8_@1[3:5] = 0 "] bb3_instrs --> bb3_terminal(["Goto"]) end