mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
@@ -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 {
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user