18
Temporaries,
19
} from "./Codegen";
20
import {
21
+ Identifier,
22
Instruction,
23
+ InstructionKind,
24
ReactiveBasicBlock,
25
ReactiveFunction,
26
ReactiveScope,
40
statements.pop();
41
}
42
}
43
+ if (cx.nextCacheIndex !== 0) {
44
+ statements.unshift(
45
+ t.variableDeclaration("const", [
46
+ t.variableDeclarator(
47
+ t.identifier("$"),
48
+ t.callExpression(
49
+ t.memberExpression(
50
+ t.identifier("React"),
51
+ t.identifier("useMemoCache")
52
+ ),
53
+ []
54
+ )
55
+ ),
56
+ ])
57
+ );
58
+ }
59
return createFunctionDeclaration(
60
fn.loc,
61
fn.id !== null ? convertIdentifier(fn.id) : null,
67
}
68
69
class Context {
70
+ #nextCacheIndex: number = 0;
71
+ #identifiers: Set<Identifier> = new Set();
72
temp: Temporaries = new Map();
73
+
74
+ get nextCacheIndex(): number {
75
+ return this.#nextCacheIndex++;
76
+ }
77
+
78
+ declare(identifier: Identifier): void {
79
+ this.#identifiers.add(identifier);
80
+ }
81
+
82
+ declared(identifier: Identifier): boolean {
83
+ return this.#identifiers.has(identifier);
84
+ }
85
}
86
87
function codegenBlock(
93
switch (item.kind) {
94
case "instruction": {
95
const statement = codegenInstructionNullable(
64
- cx.temp,
96
+ cx,
97
item.instruction,
98
codegenInstructionValue(cx.temp, item.instruction.value)
99
);
134
scope: ReactiveScope,
135
block: ReactiveBasicBlock
136
): void {
105
- // TODO @josephsavona: Emit memoized blocks!
106
- const body = codegenBlock(cx, block).body;
107
- statements.push(...body);
137
+ const cacheStoreStatements: Array<t.Statement> = [];
138
+ const cacheLoadStatements: Array<t.Statement> = [];
139
+ const changeIdentifiers: Array<t.Identifier> = [];
140
+ for (const dep of scope.dependencies) {
141
+ const index = cx.nextCacheIndex;
142
+ const changeIdentifier = t.identifier(`c_${index}`);
143
+ const depValue = codegenPlace(cx.temp, dep);
144
+
145
+ changeIdentifiers.push(changeIdentifier);
146
+ statements.push(
147
+ t.variableDeclaration("const", [
148
+ t.variableDeclarator(
149
+ changeIdentifier,
150
+ t.binaryExpression(
151
+ "!==",
152
+ t.memberExpression(
153
+ t.identifier("$"),
154
+ t.numericLiteral(index),
155
+ true
156
+ ),
157
+ depValue
158
+ )
159
+ ),
160
+ ])
161
+ );
162
+ cacheStoreStatements.push(
163
+ t.expressionStatement(
164
+ t.assignmentExpression(
165
+ "=",
166
+ t.memberExpression(t.identifier("$"), t.numericLiteral(index), true),
167
+ depValue
168
+ )
169
+ )
170
+ );
171
+ }
172
+ for (const output of scope.outputs) {
173
+ const index = cx.nextCacheIndex;
174
+
175
+ // TODO @josephsavona: ensure change and temp variables have non-conflicting names
176
+ output.name ??= `t${index}`;
177
+
178
+ const name = convertIdentifier(output);
179
+ cx.declare(output);
180
+ statements.push(t.variableDeclaration("let", [t.variableDeclarator(name)]));
181
+ cacheStoreStatements.push(
182
+ t.expressionStatement(
183
+ t.assignmentExpression(
184
+ "=",
185
+ t.memberExpression(t.identifier("$"), t.numericLiteral(index), true),
186
+ name
187
+ )
188
+ )
189
+ );
190
+ cacheLoadStatements.push(
191
+ t.expressionStatement(
192
+ t.assignmentExpression(
193
+ "=",
194
+ name,
195
+ t.memberExpression(t.identifier("$"), t.numericLiteral(index), true)
196
+ )
197
+ )
198
+ );
199
+ }
200
+ const testCondition =
201
+ (changeIdentifiers as Array<t.Expression>).reduce(
202
+ (acc: t.Expression | null, ident: t.Expression) => {
203
+ if (acc == null) {
204
+ return ident;
205
+ }
206
+ return t.logicalExpression("||", acc, ident);
207
+ },
208
+ null as t.Expression | null
209
+ ) ?? t.booleanLiteral(true); // TODO @josephsavona handle case of empty dependencies
210
+
211
+ const computationBlock = codegenBlock(cx, block);
212
+ computationBlock.body.push(...cacheStoreStatements);
213
+ const memoBlock = t.blockStatement(cacheLoadStatements);
214
+ statements.push(t.ifStatement(testCondition, computationBlock, memoBlock));
215
}
216
217
function codegenTerminal(cx: Context, terminal: ReactiveTerminal): t.Statement {
280
}
281
282
export function codegenInstructionNullable(
176
- temp: Temporaries,
283
+ cx: Context,
284
instr: Instruction,
285
value: t.Expression
286
): t.Statement | null {
180
- const statement = codegenInstruction(temp, instr, value);
287
+ let statement;
288
+ if (
289
+ instr.lvalue !== null &&
290
+ instr.lvalue.place.memberPath === null &&
291
+ cx.declared(instr.lvalue.place.identifier)
292
+ ) {
293
+ statement = codegenInstruction(
294
+ cx.temp,
295
+ {
296
+ ...instr,
297
+ lvalue: {
298
+ ...instr.lvalue,
299
+ kind: InstructionKind.Reassign,
300
+ },
301
+ },
302
+ value
303
+ );
304
+ } else {
305
+ statement = codegenInstruction(cx.temp, instr, value);
306
+ }
307
if (statement.type === "EmptyStatement") {
308
return null;
309
}