Skip to content

Commit 84347ec

Browse files
committed
[compiler] Inferred effect dependencies now include optional chains
Inferred effect dependencies now include optional chains. This is a temporary solution while #32099 and its followups are worked on. Ideally, we should model reactive scope dependencies in the IR similarly to `ComputeIR` -- dependencies should be hoisted and all references rewritten to use the hoisted dependencies. (will add more test cases)
1 parent 2083beb commit 84347ec

File tree

4 files changed

+496
-101
lines changed

4 files changed

+496
-101
lines changed
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
import {
2+
Place,
3+
ReactiveScopeDependency,
4+
Identifier,
5+
makeInstructionId,
6+
InstructionKind,
7+
GeneratedSource,
8+
BlockId,
9+
makeTemporaryIdentifier,
10+
Effect,
11+
GotoVariant,
12+
HIR,
13+
} from './HIR';
14+
import {CompilerError} from '../CompilerError';
15+
import {Environment} from './Environment';
16+
import HIRBuilder from './HIRBuilder';
17+
import {lowerValueToTemporary} from './BuildHIR';
18+
import {printDependency} from '../ReactiveScopes/PrintReactiveFunction';
19+
20+
type DependencyInstructions = {
21+
place: Place;
22+
value: HIR;
23+
exitBlockId: BlockId;
24+
};
25+
26+
export function buildDependencyInstructions(
27+
dep: ReactiveScopeDependency,
28+
env: Environment,
29+
): DependencyInstructions {
30+
const builder = new HIRBuilder(env, {
31+
entryBlockKind: 'value',
32+
});
33+
let result: Place;
34+
if (dep.path.every(path => !path.optional)) {
35+
const last = writeNonOptionalDependency(dep, env, builder);
36+
result = {
37+
kind: 'Identifier',
38+
identifier: last,
39+
effect: Effect.Freeze,
40+
reactive: dep.reactive,
41+
loc: GeneratedSource,
42+
};
43+
} else {
44+
const last = writeOptionalDependency(
45+
dep.path.length - 1,
46+
dep,
47+
builder,
48+
null,
49+
);
50+
result = {
51+
kind: 'Identifier',
52+
identifier: last,
53+
effect: Effect.Freeze,
54+
reactive: dep.reactive,
55+
loc: GeneratedSource,
56+
};
57+
}
58+
59+
const exitBlockId = builder.terminate(
60+
{
61+
kind: 'unsupported',
62+
loc: GeneratedSource,
63+
id: makeInstructionId(0),
64+
},
65+
null,
66+
);
67+
return {
68+
place: result,
69+
value: builder.build(),
70+
exitBlockId,
71+
};
72+
}
73+
function writeNonOptionalDependency(
74+
dep: ReactiveScopeDependency,
75+
env: Environment,
76+
builder: HIRBuilder,
77+
): Identifier {
78+
const loc = dep.identifier.loc;
79+
let last: Identifier = makeTemporaryIdentifier(env.nextIdentifierId, loc);
80+
builder.push({
81+
lvalue: {
82+
identifier: last,
83+
kind: 'Identifier',
84+
effect: Effect.Mutate,
85+
reactive: dep.reactive,
86+
loc,
87+
},
88+
value: {
89+
kind: 'LoadLocal',
90+
place: {
91+
identifier: dep.identifier,
92+
kind: 'Identifier',
93+
effect: Effect.Freeze,
94+
reactive: dep.reactive,
95+
loc,
96+
},
97+
loc,
98+
},
99+
id: makeInstructionId(1),
100+
loc: loc,
101+
});
102+
103+
for (const path of dep.path) {
104+
const next = makeTemporaryIdentifier(env.nextIdentifierId, loc);
105+
builder.push({
106+
lvalue: {
107+
identifier: next,
108+
kind: 'Identifier',
109+
effect: Effect.Mutate,
110+
reactive: dep.reactive,
111+
loc,
112+
},
113+
value: {
114+
kind: 'PropertyLoad',
115+
object: {
116+
identifier: last,
117+
kind: 'Identifier',
118+
effect: Effect.Freeze,
119+
reactive: dep.reactive,
120+
loc,
121+
},
122+
property: path.property,
123+
loc,
124+
},
125+
id: makeInstructionId(1),
126+
loc: loc,
127+
});
128+
last = next;
129+
}
130+
return last;
131+
}
132+
133+
function writeOptionalDependency(
134+
idx: number,
135+
dep: ReactiveScopeDependency,
136+
builder: HIRBuilder,
137+
parentAlternate: BlockId | null,
138+
): Identifier {
139+
const env = builder.environment;
140+
CompilerError.invariant(
141+
idx >= 0 && !dep.path.slice(0, idx + 1).every(path => !path.optional),
142+
{
143+
reason: '[WriteOptional] Expected optional path',
144+
description: `${idx} ${printDependency(dep)}`,
145+
loc: GeneratedSource,
146+
},
147+
);
148+
const continuationBlock = builder.reserve(builder.currentBlockKind());
149+
const consequent = builder.reserve('value');
150+
151+
const returnPlace: Place = {
152+
kind: 'Identifier',
153+
identifier: makeTemporaryIdentifier(env.nextIdentifierId, GeneratedSource),
154+
effect: Effect.Mutate,
155+
reactive: dep.reactive,
156+
loc: GeneratedSource,
157+
};
158+
159+
let alternate;
160+
if (parentAlternate != null) {
161+
alternate = parentAlternate;
162+
} else {
163+
/**
164+
* Make outermost alternate block
165+
* $N = Primitive undefined
166+
* $M = StoreLocal $OptionalResult = $N
167+
* goto fallthrough
168+
*/
169+
alternate = builder.enter('value', () => {
170+
const temp = lowerValueToTemporary(builder, {
171+
kind: 'Primitive',
172+
value: undefined,
173+
loc: GeneratedSource,
174+
});
175+
lowerValueToTemporary(builder, {
176+
kind: 'StoreLocal',
177+
lvalue: {kind: InstructionKind.Const, place: {...returnPlace}},
178+
value: {...temp},
179+
type: null,
180+
loc: GeneratedSource,
181+
});
182+
return {
183+
kind: 'goto',
184+
variant: GotoVariant.Break,
185+
block: continuationBlock.id,
186+
id: makeInstructionId(0),
187+
loc: GeneratedSource,
188+
};
189+
});
190+
}
191+
192+
let testIdentifier: Identifier | null = null;
193+
const testBlock = builder.enter('value', () => {
194+
const firstOptional = dep.path.findIndex(path => path.optional);
195+
if (idx === firstOptional) {
196+
// Lower test block
197+
testIdentifier = writeNonOptionalDependency(
198+
{
199+
identifier: dep.identifier,
200+
reactive: dep.reactive,
201+
path: dep.path.slice(0, idx),
202+
},
203+
env,
204+
builder,
205+
);
206+
} else {
207+
testIdentifier = writeOptionalDependency(
208+
idx - 1,
209+
dep,
210+
builder,
211+
alternate,
212+
);
213+
}
214+
215+
return {
216+
kind: 'branch',
217+
test: {
218+
identifier: testIdentifier,
219+
effect: Effect.Freeze,
220+
kind: 'Identifier',
221+
loc: GeneratedSource,
222+
reactive: dep.reactive,
223+
},
224+
consequent: consequent.id,
225+
alternate,
226+
id: makeInstructionId(0),
227+
loc: GeneratedSource,
228+
fallthrough: continuationBlock.id,
229+
};
230+
});
231+
232+
builder.enterReserved(consequent, () => {
233+
CompilerError.invariant(testIdentifier !== null, {
234+
reason: 'Satisfy type checker',
235+
description: null,
236+
loc: null,
237+
suggestions: null,
238+
});
239+
240+
const tmpConsequent = lowerValueToTemporary(builder, {
241+
kind: 'PropertyLoad',
242+
object: {
243+
identifier: testIdentifier,
244+
kind: 'Identifier',
245+
effect: Effect.Freeze,
246+
reactive: dep.reactive,
247+
loc: GeneratedSource,
248+
},
249+
property: dep.path[idx].property,
250+
loc: GeneratedSource,
251+
});
252+
lowerValueToTemporary(builder, {
253+
kind: 'StoreLocal',
254+
lvalue: {kind: InstructionKind.Const, place: {...returnPlace}},
255+
value: {...tmpConsequent},
256+
type: null,
257+
loc: GeneratedSource,
258+
});
259+
return {
260+
kind: 'goto',
261+
variant: GotoVariant.Break,
262+
block: continuationBlock.id,
263+
id: makeInstructionId(0),
264+
loc: GeneratedSource,
265+
};
266+
});
267+
builder.terminateWithContinuation(
268+
{
269+
kind: 'optional',
270+
optional: dep.path[idx].optional,
271+
test: testBlock,
272+
fallthrough: continuationBlock.id,
273+
id: makeInstructionId(0),
274+
loc: GeneratedSource,
275+
},
276+
continuationBlock,
277+
);
278+
279+
return returnPlace.identifier;
280+
}

0 commit comments

Comments
 (0)