7
8
import { CompilerError } from "..";
9
import {
10
+ BlockId,
11
Effect,
12
HIRFunction,
13
Identifier,
14
IdentifierId,
15
Place,
16
+ computePostDominatorTree,
17
getHookKind,
18
} from "../HIR";
19
+import { PostDominator } from "../HIR/Dominator";
20
import {
21
eachInstructionLValue,
22
eachInstructionValueOperand,
91
reactiveIdentifiers.markReactive(place);
92
}
93
94
+ const postDominators = computePostDominatorTree(fn, {
95
+ includeThrowsAsExitNode: false,
96
+ });
97
const hasLoop = hasBackEdge(fn);
98
+ const postDominatorFrontierCache = new Map<BlockId, Set<BlockId>>();
99
do {
100
for (const [, block] of fn.body.blocks) {
101
for (const phi of block.phis) {
102
+ if (reactiveIdentifiers.isReactiveIdentifier(phi.id)) {
103
+ // Already marked reactive on a previous pass
104
+ continue;
105
+ }
106
+ let isPhiReactive = false;
107
for (const [, operand] of phi.operands) {
108
if (reactiveIdentifiers.isReactiveIdentifier(operand)) {
97
- reactiveIdentifiers.markReactiveIdentifier(phi.id);
109
+ isPhiReactive = true;
110
break;
111
}
112
}
113
+ if (isPhiReactive) {
114
+ reactiveIdentifiers.markReactiveIdentifier(phi.id);
115
+ } else {
116
+ // check to see if it has a reactive control dependency
117
+ for (const [pred, _operand] of phi.operands) {
118
+ let controlBlocks = postDominatorFrontierCache.get(pred);
119
+ if (controlBlocks === undefined) {
120
+ controlBlocks = postDominatorFrontier(fn, postDominators, pred);
121
+ postDominatorFrontierCache.set(pred, controlBlocks);
122
+ }
123
+ control: for (const blockId of controlBlocks) {
124
+ const controlBlock = fn.body.blocks.get(blockId)!;
125
+ switch (controlBlock.terminal.kind) {
126
+ case "if":
127
+ case "branch": {
128
+ if (
129
+ reactiveIdentifiers.isReactive(controlBlock.terminal.test)
130
+ ) {
131
+ // control dependency is reactive
132
+ reactiveIdentifiers.markReactiveIdentifier(phi.id);
133
+ break control;
134
+ }
135
+ break;
136
+ }
137
+ case "switch": {
138
+ if (
139
+ reactiveIdentifiers.isReactive(controlBlock.terminal.test)
140
+ ) {
141
+ // control dependency is reactive
142
+ reactiveIdentifiers.markReactiveIdentifier(phi.id);
143
+ break control;
144
+ }
145
+ for (const case_ of controlBlock.terminal.cases) {
146
+ if (
147
+ case_.test !== null &&
148
+ reactiveIdentifiers.isReactive(case_.test)
149
+ ) {
150
+ // control dependency is reactive
151
+ reactiveIdentifiers.markReactiveIdentifier(phi.id);
152
+ break control;
153
+ }
154
+ }
155
+ break;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
}
162
for (const instruction of block.instructions) {
163
const { value } = instruction;
227
} while (reactiveIdentifiers.snapshot() && hasLoop);
228
}
229
230
+/**
231
+ * Computes the post-dominator frontier of @param block. These are immediate successors of nodes that
232
+ * post-dominate @param targetId and from which execution may not reach @param block. Intuitively, these
233
+ * are the earliest blocks from which execution branches such that it may or may not reach the target block.
234
+ */
235
+function postDominatorFrontier(
236
+ fn: HIRFunction,
237
+ postDominators: PostDominator<BlockId>,
238
+ targetId: BlockId
239
+): Set<BlockId> {
240
+ const visited = new Set<BlockId>();
241
+ const frontier = new Set<BlockId>();
242
+ const targetPostDominators = postDominatorsOf(fn, postDominators, targetId);
243
+ for (const blockId of [...targetPostDominators, targetId]) {
244
+ if (visited.has(blockId)) {
245
+ continue;
246
+ }
247
+ visited.add(blockId);
248
+ const block = fn.body.blocks.get(blockId)!;
249
+ for (const pred of block.preds) {
250
+ if (!targetPostDominators.has(pred)) {
251
+ // The predecessor does not always reach this block, we found an item on the frontier!
252
+ frontier.add(pred);
253
+ }
254
+ }
255
+ }
256
+ return frontier;
257
+}
258
+
259
+function postDominatorsOf(
260
+ fn: HIRFunction,
261
+ postDominators: PostDominator<BlockId>,
262
+ targetId: BlockId
263
+): Set<BlockId> {
264
+ const result = new Set<BlockId>();
265
+ const visited = new Set<BlockId>();
266
+ const queue = [targetId];
267
+ while (queue.length) {
268
+ const currentId = queue.shift()!;
269
+ if (visited.has(currentId)) {
270
+ continue;
271
+ }
272
+ visited.add(currentId);
273
+ const current = fn.body.blocks.get(currentId)!;
274
+ for (const pred of current.preds) {
275
+ const predPostDominator = postDominators.get(pred) ?? pred;
276
+ if (predPostDominator === targetId || result.has(predPostDominator)) {
277
+ result.add(pred);
278
+ }
279
+ queue.push(pred);
280
+ }
281
+ }
282
+ return result;
283
+}
284
+
285
class ReactivityMap {
286
hasChanges: boolean = false;
287
reactive: Set<IdentifierId> = new Set();