Improve "value block" handling

This is a pre-req to construct reactive scopes in #857. "Value blocks" such as 
`for` init/test/update and `while` test need to be consistently wrapped in 
enter/leave calls so that we can extend the range of values properly. We also 
need to handle `for` init blocks a bit differently, since they allow variable 
declarations but not other types of statements. 

This PR ensures that we use consistent methods for handling value blocks (`for` 
test/update and `while` test) and treats `for` init as a new type with its own 
enter/append/leave visitor functions.
This commit is contained in:
Joseph Savona
2022-12-12 11:20:10 -08:00
parent c8bb9ea0af
commit b8d78a94a8
6 changed files with 320 additions and 152 deletions
+96 -48
View File
@@ -91,7 +91,14 @@ type Temporaries = Map<IdentifierId, t.Expression>;
class CodegenVisitor
implements
Visitor<Array<t.Statement>, t.Expression, t.Statement, t.SwitchCase>
Visitor<
Array<t.Statement>,
Array<t.Statement>,
Array<t.Statement>,
t.Expression,
t.Statement,
t.SwitchCase
>
{
depth: number = 0;
temp: Map<IdentifierId, t.Expression> = 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 {
+143 -89
View File
@@ -27,25 +27,35 @@ import {
*
* See the {@link Visitor} interface for more about implementing a visitor.
*/
export function visitTree<TBlock, TValue, TItem, TCase>(
export function visitTree<
TBlock,
TInit,
TValueBlock,
TValue,
TStatement,
TCase
>(
fn: HIRFunction,
visitor: Visitor<TBlock, TValue, TItem, TCase>
): TItem {
visitor: Visitor<TBlock, TInit, TValueBlock, TValue, TStatement, TCase>
): TStatement {
const cx = new Context(fn.body);
const driver = new Driver(cx, visitor);
return driver.traverseBlock(cx.block(fn.body.entry));
}
class Driver<TBlock, TValue, TItem, TCase> {
class Driver<TBlock, TInit, TValueBlock, TValue, TStatement, TCase> {
cx: Context;
visitor: Visitor<TBlock, TValue, TItem, TCase>;
visitor: Visitor<TBlock, TInit, TValueBlock, TValue, TStatement, TCase>;
constructor(cx: Context, visitor: Visitor<TBlock, TValue, TItem, TCase>) {
constructor(
cx: Context,
visitor: Visitor<TBlock, TInit, TValueBlock, TValue, TStatement, TCase>
) {
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<TBlock, TValue, TItem, TCase> {
}
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<TBlock, TValue, TItem, TCase> {
);
}
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<TBlock, TValue, TItem, TCase> {
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<TBlock, TValue, TItem, TCase> {
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<TBlock, TValue, TItem, TCase> {
);
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<TBlock, TValue, TItem, TCase> {
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<TBlock, TValue, TItem, TCase> {
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<TBlock, TValue, TItem, TCase> {
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<TBlock, TValue, TItem, TCase> {
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<TBlock, TValue, TItem, TCase> {
}
}
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<TBlock, TValue, TItem, TCase> {
}
}
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<TBlock, TValue, TItem, TCase> {
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<TBlock, TValue, TItem, TCase> {
* 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<TBlock, TValue, TItem, TCase> {
* 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<TBlock, TValue, TItem, TCase> {
* values and block ids will have already been converted into the visitor's
* own representations.
*/
visitTerminal(terminal: BlockTerminal<TBlock, TValue, TItem, TCase>): TItem;
visitTerminal(
terminal: BlockTerminal<TInit, TValue, TStatement, TCase>
): 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<TBlock, TValue, TItem, TCase> =
export type BlockTerminal<TInit, TValue, TStatement, TCase> =
| { 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<TCase> }
| {
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 };
@@ -35,7 +35,7 @@ export function instructionInScope(
}
class ScopeDependenciesVisitor
implements Visitor<void, InstructionValue, void, void>
implements Visitor<void, void, void, InstructionValue, void, void>
{
#identifiers: Map<Identifier, InstructionId> = 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 {}
}
+47 -11
View File
@@ -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<void, void, void, void>
implements Visitor<void, void, void, void, void, void>
{
scopes: Array<BlockScope> = [];
seenScopes: Set<ScopeId> = 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, void, void, void>): 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<void, void, void, void>
implements Visitor<void, void, void, void, void, void>
{
// 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;
}
+23 -1
View File
@@ -25,7 +25,17 @@ export function printHIRTree(fn: HIRFunction): string {
return visitTree(fn, new PrintVisitor());
}
class PrintVisitor implements Visitor<Array<string>, string, string, string> {
class PrintVisitor
implements
Visitor<
Array<string>,
Array<string>,
Array<string>,
string,
string,
string
>
{
depth: number = 0; // for indentation
enterBlock(): string[] {
@@ -38,6 +48,12 @@ class PrintVisitor implements Visitor<Array<string>, 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<Array<string>, 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);
@@ -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