mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler] Improved terminal/fallthrough structure, support labels
[ghstack-poisoned]
This commit is contained in:
@@ -844,7 +844,7 @@ export function printPlace(place: Place): string {
|
||||
}
|
||||
|
||||
export function printIdentifier(id: Identifier): string {
|
||||
return `${printName(id.name)}\$${id.id}#${id.declarationId}${printScope(id.scope)}`;
|
||||
return `${printName(id.name)}\$${id.id}${printScope(id.scope)}`;
|
||||
}
|
||||
|
||||
function printName(name: IdentifierName | null): string {
|
||||
|
||||
+341
-169
@@ -5,18 +5,27 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import prettyFormat from 'pretty-format';
|
||||
import {CompilerError, SourceLocation} from '..';
|
||||
import {
|
||||
BlockId,
|
||||
DeclarationId,
|
||||
DoWhileTerminal,
|
||||
ForInTerminal,
|
||||
ForOfTerminal,
|
||||
ForTerminal,
|
||||
GotoVariant,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
IfTerminal,
|
||||
Instruction,
|
||||
LabelTerminal,
|
||||
Place,
|
||||
ReactiveScope,
|
||||
ScopeId,
|
||||
SwitchTerminal,
|
||||
WhileTerminal,
|
||||
} from '../HIR';
|
||||
import {printIdentifier, printInstruction} from '../HIR/PrintHIR';
|
||||
import {
|
||||
@@ -26,7 +35,6 @@ import {
|
||||
} from '../HIR/visitors';
|
||||
import {assertExhaustive, getOrInsertWith} from '../Utils/utils';
|
||||
import {
|
||||
BranchNode,
|
||||
GotoNode,
|
||||
ControlNode,
|
||||
EntryNode,
|
||||
@@ -40,12 +48,16 @@ import {
|
||||
ReactiveGraph,
|
||||
ReactiveId,
|
||||
ReactiveNode,
|
||||
ReturnNode,
|
||||
reversePostorderReactiveGraph,
|
||||
StoreNode,
|
||||
eachNodeDependency,
|
||||
FallthroughNode,
|
||||
printReactiveGraph,
|
||||
IfNode,
|
||||
PhiNode,
|
||||
ThrowNode,
|
||||
ReturnNode,
|
||||
LabelNode,
|
||||
} from './ReactiveIR';
|
||||
|
||||
export function buildReactiveGraph(fn: HIRFunction): ReactiveGraph {
|
||||
@@ -131,6 +143,7 @@ class Builder {
|
||||
|
||||
type Fallthrough =
|
||||
| {kind: 'Function'}
|
||||
| {kind: 'Label'; block: BlockId; fallthrough: ReactiveId}
|
||||
| {kind: 'If'; block: BlockId; fallthrough: ReactiveId};
|
||||
|
||||
class ControlContext {
|
||||
@@ -143,6 +156,7 @@ class ControlContext {
|
||||
> = new Map(),
|
||||
private scopes: Map<ScopeId, ReactiveId> = new Map(),
|
||||
private nodes: Set<ReactiveId> = new Set(),
|
||||
private breakMapping: Map<BlockId, ReactiveId> = new Map(),
|
||||
private parent: ControlContext | null = null,
|
||||
) {}
|
||||
|
||||
@@ -157,11 +171,44 @@ class ControlContext {
|
||||
*/
|
||||
new Map(),
|
||||
new Map(),
|
||||
// Track not-yet-depended on nodes in this context so the terminal node can depend on them
|
||||
new Set(),
|
||||
this.breakMapping,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
forkValue(): ControlContext {
|
||||
return new ControlContext(
|
||||
this.builder,
|
||||
this.fallthrough,
|
||||
new Map(),
|
||||
new Map(),
|
||||
new Set(),
|
||||
this.breakMapping,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
putBreakMapping(block: BlockId, node: ReactiveId): void {
|
||||
this.breakMapping.set(block, node);
|
||||
}
|
||||
|
||||
getBreakMapping(block: BlockId, loc: SourceLocation): ReactiveId {
|
||||
const node = this.breakMapping.get(block);
|
||||
if (node == null) {
|
||||
console.log(
|
||||
`No break mapping for bb${block}`,
|
||||
prettyFormat(this.breakMapping),
|
||||
);
|
||||
}
|
||||
CompilerError.invariant(node !== undefined, {
|
||||
reason: `Unset break mapping for bb${block}`,
|
||||
loc,
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
controlNode(control: ReactiveId, loc: SourceLocation): ReactiveId {
|
||||
const node: ControlNode = {
|
||||
kind: 'Control',
|
||||
@@ -169,7 +216,6 @@ class ControlContext {
|
||||
loc,
|
||||
outputs: [],
|
||||
control,
|
||||
dependencies: [],
|
||||
};
|
||||
this.putNode(node);
|
||||
return node.id;
|
||||
@@ -204,7 +250,10 @@ class ControlContext {
|
||||
}
|
||||
|
||||
loadBreakTarget(target: BlockId, loc: SourceLocation): ReactiveId {
|
||||
if (this.fallthrough.kind === 'If' && this.fallthrough.block === target) {
|
||||
if (
|
||||
(this.fallthrough.kind === 'If' || this.fallthrough.kind === 'Label') &&
|
||||
this.fallthrough.block === target
|
||||
) {
|
||||
return this.fallthrough.fallthrough;
|
||||
}
|
||||
if (this.parent != null) {
|
||||
@@ -489,168 +538,6 @@ function buildBlockScope(
|
||||
// handle the terminal
|
||||
const terminal = block.terminal;
|
||||
switch (terminal.kind) {
|
||||
case 'if': {
|
||||
const testDep = context.lookupTemporary(
|
||||
terminal.test.identifier,
|
||||
terminal.test.loc,
|
||||
);
|
||||
const test: NodeReference = {
|
||||
node: testDep,
|
||||
from: {...terminal.test},
|
||||
as: {...terminal.test},
|
||||
};
|
||||
const branchNodeId = context.nextReactiveId;
|
||||
const fallthroughNodeId = context.nextReactiveId;
|
||||
const joinFallthrough = {
|
||||
kind: 'If',
|
||||
block: terminal.fallthrough,
|
||||
fallthrough: fallthroughNodeId,
|
||||
} as const;
|
||||
const consequentContext = context.fork(joinFallthrough);
|
||||
const consequentControl = consequentContext.controlNode(
|
||||
branchNodeId,
|
||||
terminal.loc,
|
||||
);
|
||||
const consequent = buildBlockScope(
|
||||
fn,
|
||||
consequentContext,
|
||||
terminal.consequent,
|
||||
consequentControl,
|
||||
);
|
||||
const alternateContext = context.fork(joinFallthrough);
|
||||
const alternateControl = alternateContext.controlNode(
|
||||
branchNodeId,
|
||||
terminal.loc,
|
||||
);
|
||||
const alternate =
|
||||
terminal.alternate !== terminal.fallthrough
|
||||
? buildBlockScope(
|
||||
fn,
|
||||
alternateContext,
|
||||
terminal.alternate,
|
||||
alternateControl,
|
||||
)
|
||||
: alternateControl;
|
||||
|
||||
const branch: BranchNode = {
|
||||
kind: 'Branch',
|
||||
control,
|
||||
dependencies: [],
|
||||
id: branchNodeId,
|
||||
loc: terminal.loc,
|
||||
outputs: [],
|
||||
fallthrough: fallthroughNodeId,
|
||||
terminal: {
|
||||
kind: 'If',
|
||||
test,
|
||||
consequent: {
|
||||
entry: consequentControl,
|
||||
exit: consequent,
|
||||
},
|
||||
alternate: {
|
||||
entry: alternateControl,
|
||||
exit: alternate,
|
||||
},
|
||||
},
|
||||
};
|
||||
context.putNode(branch);
|
||||
const ifNode: FallthroughNode = {
|
||||
kind: 'Fallthrough',
|
||||
control: branch.id,
|
||||
id: fallthroughNodeId,
|
||||
loc: terminal.loc,
|
||||
outputs: [],
|
||||
branches: [consequent, alternate],
|
||||
};
|
||||
|
||||
const predecessors: Array<{
|
||||
enter: ReactiveId;
|
||||
exit: ReactiveId;
|
||||
context: ControlContext;
|
||||
}> = [
|
||||
{
|
||||
enter: consequentControl,
|
||||
exit: consequent,
|
||||
context: consequentContext,
|
||||
},
|
||||
{
|
||||
enter: alternateControl,
|
||||
exit: alternate,
|
||||
context: alternateContext,
|
||||
},
|
||||
];
|
||||
|
||||
const controlDependencies: Set<ReactiveId> = new Set();
|
||||
const joinedDeclarations: Map<DeclarationId, 'write' | 'read'> =
|
||||
new Map();
|
||||
const joinedScopes: Set<ScopeId> = new Set();
|
||||
for (const predecessorBlock of predecessors) {
|
||||
/*
|
||||
* track scopes that were mutated in any of the branches so that we can
|
||||
* establish control depends to order the branch/join relative to previous
|
||||
* subsequent mutations of those scopes
|
||||
*/
|
||||
for (const [scope] of predecessorBlock.context.eachScope()) {
|
||||
joinedScopes.add(scope);
|
||||
}
|
||||
/*
|
||||
* Track variables that were read/reassigned in any of the predecessors
|
||||
* so that subsequent writes/reads can take the join node as a control
|
||||
*/
|
||||
for (const [
|
||||
declarationId,
|
||||
{write, read},
|
||||
] of predecessorBlock.context.eachDeclaration()) {
|
||||
if (write) {
|
||||
joinedDeclarations.set(declarationId, 'write');
|
||||
} else if (read && !joinedDeclarations.has(declarationId)) {
|
||||
joinedDeclarations.set(declarationId, 'read');
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const scope of joinedScopes) {
|
||||
const scopeControl = context.loadScopeControl(scope);
|
||||
if (scopeControl != null) {
|
||||
controlDependencies.add(scopeControl);
|
||||
}
|
||||
context.recordScopeMutation(scope, ifNode.id);
|
||||
}
|
||||
// Add control dependencies and record reads/writes accordingly.
|
||||
for (const [declarationId, declType] of joinedDeclarations) {
|
||||
if (declType === 'write') {
|
||||
/*
|
||||
* If there was a write in any of the branches, we take a write
|
||||
* dependency (on the last read/write) and record the if as the
|
||||
* last write
|
||||
*/
|
||||
const writeControl = context.loadDeclarationControlForWrite(
|
||||
declarationId,
|
||||
terminal.loc,
|
||||
);
|
||||
if (writeControl != null) {
|
||||
controlDependencies.add(writeControl);
|
||||
}
|
||||
context.recordDeclarationWrite(declarationId, ifNode.id);
|
||||
} else {
|
||||
/*
|
||||
* If there were only reads in the branches, we take a read
|
||||
* dependency (on the last write) and record the if as the
|
||||
* last read
|
||||
*/
|
||||
const readControl =
|
||||
context.loadDeclarationControlForRead(declarationId);
|
||||
if (readControl != null) {
|
||||
controlDependencies.add(readControl);
|
||||
}
|
||||
context.recordDeclarationRead(declarationId, ifNode.id);
|
||||
}
|
||||
}
|
||||
|
||||
branch.dependencies = Array.from(controlDependencies);
|
||||
context.putNode(ifNode);
|
||||
lastNode = ifNode.id;
|
||||
break;
|
||||
}
|
||||
case 'return': {
|
||||
const valueDep = context.lookupTemporary(
|
||||
terminal.value.identifier,
|
||||
@@ -676,6 +563,31 @@ function buildBlockScope(
|
||||
lastNode = returnNode.id;
|
||||
break;
|
||||
}
|
||||
case 'throw': {
|
||||
const valueDep = context.lookupTemporary(
|
||||
terminal.value.identifier,
|
||||
terminal.value.loc,
|
||||
);
|
||||
const value: NodeReference = {
|
||||
node: valueDep,
|
||||
from: {...terminal.value},
|
||||
as: {...terminal.value},
|
||||
};
|
||||
const throwNode: ThrowNode = {
|
||||
kind: 'Throw',
|
||||
id: context.nextReactiveId,
|
||||
loc: terminal.loc,
|
||||
outputs: [],
|
||||
value,
|
||||
dependencies: context
|
||||
.uncontolledNodes()
|
||||
.filter(id => id !== valueDep),
|
||||
control,
|
||||
};
|
||||
context.putNode(throwNode);
|
||||
lastNode = throwNode.id;
|
||||
break;
|
||||
}
|
||||
case 'goto': {
|
||||
let target: ReactiveId;
|
||||
switch (terminal.variant) {
|
||||
@@ -711,15 +623,50 @@ function buildBlockScope(
|
||||
control,
|
||||
};
|
||||
context.putNode(node);
|
||||
context.putBreakMapping(block.id, node.id);
|
||||
lastNode = node.id;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
CompilerError.throwTodo({
|
||||
reason: `Support ${terminal.kind} nodes`,
|
||||
case 'unreachable': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Found unreachable code`,
|
||||
loc: terminal.loc,
|
||||
});
|
||||
}
|
||||
case 'unsupported': {
|
||||
CompilerError.invariant(false, {
|
||||
reason: `Found unsupported terminal`,
|
||||
loc: terminal.loc,
|
||||
});
|
||||
}
|
||||
case 'scope':
|
||||
case 'pruned-scope': {
|
||||
CompilerError.throwTodo({
|
||||
reason: `Support scopes`,
|
||||
loc: terminal.loc,
|
||||
});
|
||||
}
|
||||
case 'branch':
|
||||
case 'logical':
|
||||
case 'ternary':
|
||||
case 'sequence':
|
||||
case 'optional': {
|
||||
CompilerError.throwTodo({
|
||||
reason: `Support value blocks`,
|
||||
loc: terminal.loc,
|
||||
});
|
||||
}
|
||||
case 'try':
|
||||
case 'maybe-throw': {
|
||||
CompilerError.throwTodo({
|
||||
reason: `Support try/catch`,
|
||||
loc: terminal.loc,
|
||||
});
|
||||
}
|
||||
default: {
|
||||
lastNode = buildTerminal(fn, terminal, context, control);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue iteration in the fallthrough
|
||||
@@ -733,6 +680,231 @@ function buildBlockScope(
|
||||
return lastNode;
|
||||
}
|
||||
|
||||
function buildTerminal(
|
||||
fn: HIRFunction,
|
||||
terminal:
|
||||
| DoWhileTerminal
|
||||
| ForInTerminal
|
||||
| ForOfTerminal
|
||||
| ForTerminal
|
||||
| IfTerminal
|
||||
| LabelTerminal
|
||||
| SwitchTerminal
|
||||
| WhileTerminal,
|
||||
context: ControlContext,
|
||||
control: ReactiveId,
|
||||
): ReactiveId {
|
||||
let branches: Array<{
|
||||
enter: ReactiveId;
|
||||
exit: ReactiveId;
|
||||
context: ControlContext;
|
||||
}>;
|
||||
const fallthroughNodeId = context.nextReactiveId;
|
||||
let terminalNode: ReactiveNode;
|
||||
|
||||
switch (terminal.kind) {
|
||||
case 'if': {
|
||||
const testDep = context.lookupTemporary(
|
||||
terminal.test.identifier,
|
||||
terminal.test.loc,
|
||||
);
|
||||
const test: NodeReference = {
|
||||
node: testDep,
|
||||
from: {...terminal.test},
|
||||
as: {...terminal.test},
|
||||
};
|
||||
const ifNodeId = context.nextReactiveId;
|
||||
const joinFallthrough = {
|
||||
kind: 'If',
|
||||
block: terminal.fallthrough,
|
||||
fallthrough: fallthroughNodeId,
|
||||
} as const;
|
||||
const consequentContext = context.fork(joinFallthrough);
|
||||
const consequentControl = consequentContext.controlNode(
|
||||
ifNodeId,
|
||||
terminal.loc,
|
||||
);
|
||||
const consequent = buildBlockScope(
|
||||
fn,
|
||||
consequentContext,
|
||||
terminal.consequent,
|
||||
consequentControl,
|
||||
);
|
||||
const alternateContext = context.fork(joinFallthrough);
|
||||
const alternateControl = alternateContext.controlNode(
|
||||
ifNodeId,
|
||||
terminal.loc,
|
||||
);
|
||||
const alternate =
|
||||
terminal.alternate !== terminal.fallthrough
|
||||
? buildBlockScope(
|
||||
fn,
|
||||
alternateContext,
|
||||
terminal.alternate,
|
||||
alternateControl,
|
||||
)
|
||||
: alternateControl;
|
||||
|
||||
const node: IfNode = {
|
||||
kind: 'If',
|
||||
control,
|
||||
dependencies: [],
|
||||
id: ifNodeId,
|
||||
loc: terminal.loc,
|
||||
outputs: [],
|
||||
fallthrough: fallthroughNodeId,
|
||||
test,
|
||||
consequent: {
|
||||
entry: consequentControl,
|
||||
exit: consequent,
|
||||
},
|
||||
alternate: {
|
||||
entry: alternateControl,
|
||||
exit: alternate,
|
||||
},
|
||||
};
|
||||
context.putNode(node);
|
||||
branches = [
|
||||
{
|
||||
enter: consequentControl,
|
||||
exit: consequent,
|
||||
context: consequentContext,
|
||||
},
|
||||
{
|
||||
enter: alternateControl,
|
||||
exit: alternate,
|
||||
context: alternateContext,
|
||||
},
|
||||
];
|
||||
terminalNode = node;
|
||||
break;
|
||||
}
|
||||
case 'label': {
|
||||
const blockContext = context.fork({
|
||||
kind: 'Label',
|
||||
block: terminal.fallthrough,
|
||||
fallthrough: fallthroughNodeId,
|
||||
});
|
||||
const labelNodeId = context.nextReactiveId;
|
||||
const blockControl = blockContext.controlNode(labelNodeId, terminal.loc);
|
||||
const block = buildBlockScope(
|
||||
fn,
|
||||
blockContext,
|
||||
terminal.block,
|
||||
blockControl,
|
||||
);
|
||||
const node: LabelNode = {
|
||||
kind: 'Label',
|
||||
block: {entry: blockControl, exit: block},
|
||||
control,
|
||||
id: labelNodeId,
|
||||
loc: terminal.loc,
|
||||
outputs: [],
|
||||
dependencies: [],
|
||||
};
|
||||
context.putNode(node);
|
||||
branches = [{enter: blockControl, exit: block, context: blockContext}];
|
||||
terminalNode = node;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
terminal /* TODO */ as never,
|
||||
`Unexpected terminal kind '${(terminal as any).kind}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const fallthrough = fn.body.blocks.get(terminal.fallthrough)!;
|
||||
const phis: Array<PhiNode> = Array.from(fallthrough.phis).map(phi => {
|
||||
return {
|
||||
kind: 'Phi',
|
||||
place: phi.place,
|
||||
operands: new Map(
|
||||
Array.from(phi.operands, ([blockId, operand]) => {
|
||||
return [context.getBreakMapping(blockId, phi.place.loc), operand];
|
||||
}),
|
||||
),
|
||||
};
|
||||
});
|
||||
const fallthroughNode: FallthroughNode = {
|
||||
kind: 'Fallthrough',
|
||||
control: terminalNode.id,
|
||||
id: fallthroughNodeId,
|
||||
loc: terminal.loc,
|
||||
outputs: [],
|
||||
branches: branches.map(pred => pred.exit),
|
||||
phis,
|
||||
};
|
||||
context.putNode(fallthroughNode);
|
||||
|
||||
const controlDependencies: Set<ReactiveId> = new Set();
|
||||
const joinedDeclarations: Map<DeclarationId, 'write' | 'read'> = new Map();
|
||||
const joinedScopes: Set<ScopeId> = new Set();
|
||||
for (const predecessorBlock of branches) {
|
||||
/*
|
||||
* track scopes that were mutated in any of the branches so that we can
|
||||
* establish control depends to order the branch/join relative to previous
|
||||
* subsequent mutations of those scopes
|
||||
*/
|
||||
for (const [scope] of predecessorBlock.context.eachScope()) {
|
||||
joinedScopes.add(scope);
|
||||
}
|
||||
/*
|
||||
* Track variables that were read/reassigned in any of the predecessors
|
||||
* so that subsequent writes/reads can take the join node as a control
|
||||
*/
|
||||
for (const [
|
||||
declarationId,
|
||||
{write, read},
|
||||
] of predecessorBlock.context.eachDeclaration()) {
|
||||
if (write) {
|
||||
joinedDeclarations.set(declarationId, 'write');
|
||||
} else if (read && !joinedDeclarations.has(declarationId)) {
|
||||
joinedDeclarations.set(declarationId, 'read');
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const scope of joinedScopes) {
|
||||
const scopeControl = context.loadScopeControl(scope);
|
||||
if (scopeControl != null) {
|
||||
controlDependencies.add(scopeControl);
|
||||
}
|
||||
context.recordScopeMutation(scope, fallthroughNode.id);
|
||||
}
|
||||
// Add control dependencies and record reads/writes accordingly.
|
||||
for (const [declarationId, declType] of joinedDeclarations) {
|
||||
if (declType === 'write') {
|
||||
/*
|
||||
* If there was a write in any of the branches, we take a write
|
||||
* dependency (on the last read/write) and record the if as the
|
||||
* last write
|
||||
*/
|
||||
const writeControl = context.loadDeclarationControlForWrite(
|
||||
declarationId,
|
||||
terminal.loc,
|
||||
);
|
||||
if (writeControl != null) {
|
||||
controlDependencies.add(writeControl);
|
||||
}
|
||||
context.recordDeclarationWrite(declarationId, fallthroughNode.id);
|
||||
} else {
|
||||
/*
|
||||
* If there were only reads in the branches, we take a read
|
||||
* dependency (on the last write) and record the if as the
|
||||
* last read
|
||||
*/
|
||||
const readControl = context.loadDeclarationControlForRead(declarationId);
|
||||
if (readControl != null) {
|
||||
controlDependencies.add(readControl);
|
||||
}
|
||||
context.recordDeclarationRead(declarationId, fallthroughNode.id);
|
||||
}
|
||||
}
|
||||
|
||||
terminalNode.dependencies = Array.from(controlDependencies);
|
||||
return fallthroughNodeId;
|
||||
}
|
||||
|
||||
function getScopeForInstruction(instr: Instruction): ReactiveScope | null {
|
||||
let scope: ReactiveScope | null = null;
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
|
||||
@@ -52,16 +52,19 @@ export function makeReactiveId(id: number): ReactiveId {
|
||||
}
|
||||
|
||||
export type ReactiveNode =
|
||||
| EntryNode
|
||||
| LoadNode
|
||||
| StoreNode
|
||||
| LoadArgumentNode
|
||||
| InstructionNode
|
||||
| BranchNode
|
||||
| FallthroughNode
|
||||
| ControlNode
|
||||
| EntryNode
|
||||
| FallthroughNode
|
||||
| GotoNode
|
||||
| IfNode
|
||||
| InstructionNode
|
||||
| LabelNode
|
||||
| LoadArgumentNode
|
||||
| LoadNode
|
||||
| OptionalNode
|
||||
| ReturnNode
|
||||
| GotoNode;
|
||||
| StoreNode
|
||||
| ThrowNode;
|
||||
|
||||
export type NodeReference = {
|
||||
node: ReactiveId;
|
||||
@@ -129,6 +132,16 @@ export type ReturnNode = {
|
||||
control: ReactiveId;
|
||||
};
|
||||
|
||||
export type ThrowNode = {
|
||||
kind: 'Throw';
|
||||
id: ReactiveId;
|
||||
loc: SourceLocation;
|
||||
value: NodeReference;
|
||||
outputs: Array<ReactiveId>;
|
||||
dependencies: Array<ReactiveId>;
|
||||
control: ReactiveId;
|
||||
};
|
||||
|
||||
export type GotoNode = {
|
||||
kind: 'Goto';
|
||||
id: ReactiveId;
|
||||
@@ -140,24 +153,27 @@ export type GotoNode = {
|
||||
variant: GotoVariant;
|
||||
};
|
||||
|
||||
export type BranchNode = {
|
||||
kind: 'Branch';
|
||||
export type LabelNode = {
|
||||
kind: 'Label';
|
||||
id: ReactiveId;
|
||||
loc: SourceLocation;
|
||||
outputs: Array<ReactiveId>;
|
||||
dependencies: Array<ReactiveId>;
|
||||
control: ReactiveId;
|
||||
block: {entry: ReactiveId; exit: ReactiveId};
|
||||
};
|
||||
|
||||
export type IfNode = {
|
||||
kind: 'If';
|
||||
id: ReactiveId;
|
||||
loc: SourceLocation;
|
||||
outputs: Array<ReactiveId>;
|
||||
dependencies: Array<ReactiveId>; // values/scopes depended on by more than one branch, or by the terminal
|
||||
control: ReactiveId;
|
||||
fallthrough: ReactiveId;
|
||||
terminal: BranchTerminal;
|
||||
};
|
||||
|
||||
export type BranchTerminal = IfBranch;
|
||||
|
||||
export type IfBranch = {
|
||||
kind: 'If';
|
||||
test: NodeReference;
|
||||
consequent: {entry: ReactiveId; exit: ReactiveId};
|
||||
alternate: {entry: ReactiveId; exit: ReactiveId};
|
||||
fallthrough: ReactiveId;
|
||||
};
|
||||
|
||||
export type FallthroughNode = {
|
||||
@@ -167,6 +183,13 @@ export type FallthroughNode = {
|
||||
outputs: Array<ReactiveId>;
|
||||
control: ReactiveId; // always the corresponding branch node
|
||||
branches: Array<ReactiveId>; // the other control-flow paths that reach the fallthrough
|
||||
phis: Array<PhiNode>;
|
||||
};
|
||||
|
||||
export type PhiNode = {
|
||||
kind: 'Phi';
|
||||
place: Place;
|
||||
operands: Map<ReactiveId, Place>;
|
||||
};
|
||||
|
||||
export type ControlNode = {
|
||||
@@ -174,7 +197,17 @@ export type ControlNode = {
|
||||
id: ReactiveId;
|
||||
loc: SourceLocation;
|
||||
outputs: Array<ReactiveId>;
|
||||
dependencies: Array<ReactiveId>;
|
||||
control: ReactiveId;
|
||||
};
|
||||
|
||||
export type OptionalNode = {
|
||||
kind: 'Optional';
|
||||
id: ReactiveId;
|
||||
loc: SourceLocation;
|
||||
outputs: Array<ReactiveId>;
|
||||
object: NodeReference;
|
||||
continuation: NodeReference;
|
||||
optional: boolean;
|
||||
control: ReactiveId;
|
||||
};
|
||||
|
||||
@@ -241,30 +274,21 @@ export function reversePostorderReactiveGraph(graph: ReactiveGraph): void {
|
||||
graph.nodes = nodes;
|
||||
}
|
||||
|
||||
export function* eachBranchTerminalDependency(
|
||||
terminal: BranchTerminal,
|
||||
): Iterable<ReactiveId> {
|
||||
switch (terminal.kind) {
|
||||
case 'If': {
|
||||
yield terminal.test.node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function* eachNodeDependency(node: ReactiveNode): Iterable<ReactiveId> {
|
||||
switch (node.kind) {
|
||||
case 'Control':
|
||||
case 'Entry':
|
||||
case 'LoadArgument': {
|
||||
break;
|
||||
}
|
||||
case 'Goto':
|
||||
case 'Control': {
|
||||
case 'Label':
|
||||
case 'Goto': {
|
||||
yield* node.dependencies;
|
||||
break;
|
||||
}
|
||||
case 'Branch': {
|
||||
case 'If': {
|
||||
yield* node.dependencies;
|
||||
yield* eachBranchTerminalDependency(node.terminal);
|
||||
yield node.test.node;
|
||||
break;
|
||||
}
|
||||
case 'Fallthrough': {
|
||||
@@ -279,13 +303,19 @@ export function* eachNodeDependency(node: ReactiveNode): Iterable<ReactiveId> {
|
||||
yield node.value.node;
|
||||
break;
|
||||
}
|
||||
case 'Throw':
|
||||
case 'Return': {
|
||||
yield* node.dependencies;
|
||||
yield node.value.node;
|
||||
break;
|
||||
}
|
||||
case 'Value': {
|
||||
yield* [...node.dependencies.keys()];
|
||||
yield* node.dependencies.keys();
|
||||
break;
|
||||
}
|
||||
case 'Optional': {
|
||||
yield node.object.node;
|
||||
yield node.continuation.node;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -297,21 +327,11 @@ export function* eachNodeDependency(node: ReactiveNode): Iterable<ReactiveId> {
|
||||
}
|
||||
}
|
||||
|
||||
export function* eachBranchTerminalReference(
|
||||
terminal: BranchTerminal,
|
||||
): Iterable<NodeReference> {
|
||||
switch (terminal.kind) {
|
||||
case 'If': {
|
||||
yield terminal.test;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function* eachNodeReference(
|
||||
node: ReactiveNode,
|
||||
): Iterable<NodeReference> {
|
||||
switch (node.kind) {
|
||||
case 'Label':
|
||||
case 'Goto':
|
||||
case 'Entry':
|
||||
case 'Control':
|
||||
@@ -326,12 +346,13 @@ export function* eachNodeReference(
|
||||
yield node.value;
|
||||
break;
|
||||
}
|
||||
case 'Throw':
|
||||
case 'Return': {
|
||||
yield node.value;
|
||||
break;
|
||||
}
|
||||
case 'Branch': {
|
||||
yield* eachBranchTerminalReference(node.terminal);
|
||||
case 'If': {
|
||||
yield node.test;
|
||||
break;
|
||||
}
|
||||
case 'Fallthrough': {
|
||||
@@ -345,6 +366,11 @@ export function* eachNodeReference(
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case 'Optional': {
|
||||
yield node.object;
|
||||
yield node.continuation;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(node, `Unexpected node kind '${(node as any).kind}'`);
|
||||
}
|
||||
@@ -416,9 +442,7 @@ function writeReactiveNodes(
|
||||
break;
|
||||
}
|
||||
case 'Control': {
|
||||
buffer.push(
|
||||
`£${id} Control${control} deps=[${node.dependencies.map(id => `£${id}`).join(', ')}]`,
|
||||
);
|
||||
buffer.push(`£${id} Control${control}`);
|
||||
break;
|
||||
}
|
||||
case 'Load': {
|
||||
@@ -431,35 +455,48 @@ function writeReactiveNodes(
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'Throw': {
|
||||
buffer.push(
|
||||
`£${id} Throw ${printNodeReference(node.value)} deps=[${node.dependencies.map(id => `£${id}`).join(', ')}]${control}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'Return': {
|
||||
buffer.push(
|
||||
`£${id} Return ${printNodeReference(node.value)} deps=[${node.dependencies.map(id => `£${id}`).join(', ')}]${control}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'Branch': {
|
||||
case 'Label': {
|
||||
buffer.push(
|
||||
`£${id} Branch deps=[${node.dependencies.map(id => `£${id}`).join(', ')}]${control}`,
|
||||
`£${id} Label block=£${node.block.entry}:${node.block.exit} deps=[${node.dependencies.map(id => `£${id}`).join(', ')}]${control}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'If': {
|
||||
buffer.push(`£${id} If test=${printNodeReference(node.test)}`);
|
||||
buffer.push(
|
||||
` consequent=£${node.consequent.entry}:${node.consequent.exit} `,
|
||||
);
|
||||
buffer.push(
|
||||
` alternate=£${node.alternate.entry}:${node.alternate.exit} `,
|
||||
);
|
||||
buffer.push(
|
||||
` deps=[${node.dependencies.map(id => `£${id}`).join(', ')}]${control}`,
|
||||
);
|
||||
switch (node.terminal.kind) {
|
||||
case 'If': {
|
||||
buffer.push(
|
||||
` If test=${printNodeReference(node.terminal.test)} ` +
|
||||
`consequent=£${node.terminal.consequent.entry}:${node.terminal.consequent.exit} ` +
|
||||
`alternate=£${node.terminal.alternate.entry}:${node.terminal.alternate.exit}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// assertExhaustive(node.terminal, `Unsupported terminal kind ${(node.terminal as any).kind}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Fallthrough': {
|
||||
buffer.push(
|
||||
`£${id} Fallthrough${control} branches=[${node.branches.map(id => `£${id}`).join(', ')}]`,
|
||||
);
|
||||
for (const phi of node.phis) {
|
||||
const operands = [];
|
||||
for (const [pred, operand] of phi.operands) {
|
||||
operands.push(`£${pred} => ${printPlace(operand)}`);
|
||||
}
|
||||
buffer.push(` ${printPlace(phi.place)} => [${operands.join(', ')}]`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Value': {
|
||||
@@ -470,6 +507,13 @@ function writeReactiveNodes(
|
||||
buffer.push(' ' + printInstruction(node.value));
|
||||
break;
|
||||
}
|
||||
case 'Optional': {
|
||||
buffer.push(
|
||||
`£${id} Optional ${printNodeReference(node.object)} ` +
|
||||
`${node.optional ? '?.' : '.'} ${printNodeReference(node.continuation)} ${control}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(node, `Unexpected node kind ${(node as any).kind}`);
|
||||
}
|
||||
|
||||
+17
-9
@@ -6,8 +6,12 @@
|
||||
function Component(props) {
|
||||
let element = props.default;
|
||||
let other = element;
|
||||
if (props.cond) {
|
||||
element = <div></div>;
|
||||
label: if (props.cond) {
|
||||
if (props.ret) {
|
||||
break label;
|
||||
} else {
|
||||
element = <div></div>;
|
||||
}
|
||||
} else {
|
||||
element = <span></span>;
|
||||
}
|
||||
@@ -24,15 +28,19 @@ function Component(props) {
|
||||
const $ = _c(5);
|
||||
let element = props.default;
|
||||
const other = element;
|
||||
if (props.cond) {
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div />;
|
||||
$[0] = t0;
|
||||
bb0: if (props.cond) {
|
||||
if (props.ret) {
|
||||
break bb0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div />;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
element = t0;
|
||||
}
|
||||
element = t0;
|
||||
} else {
|
||||
let t0;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
|
||||
+6
-2
@@ -2,8 +2,12 @@
|
||||
function Component(props) {
|
||||
let element = props.default;
|
||||
let other = element;
|
||||
if (props.cond) {
|
||||
element = <div></div>;
|
||||
label: if (props.cond) {
|
||||
if (props.ret) {
|
||||
break label;
|
||||
} else {
|
||||
element = <div></div>;
|
||||
}
|
||||
} else {
|
||||
element = <span></span>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user