10
Effect,
11
HIRFunction,
12
IdentifierId,
13
+ Instruction,
14
Place,
15
SpreadPattern,
16
+ makeInstructionId,
17
+ markInstructionIds,
18
} from "../HIR";
19
+import { createTemporaryPlace } from "../HIR/HIRBuilder";
20
import { HookKind } from "../HIR/ObjectShape";
21
22
/*
30
export function dropManualMemoization(func: HIRFunction): void {
31
const hooks = new Map<IdentifierId, HookKind>();
32
const react = new Set<IdentifierId>();
33
+ let hasChanges = false;
34
for (const [_, block] of func.body.blocks) {
30
- for (const instr of block.instructions) {
35
+ let nextInstructions: Array<Instruction> | null = null;
36
+ for (let i = 0; i < block.instructions.length; i++) {
37
+ const instr = block.instructions[i]!;
38
+ if (nextInstructions !== null) {
39
+ nextInstructions.push(instr);
40
+ }
41
switch (instr.value.kind) {
42
case "LoadGlobal": {
43
if (
81
});
82
}
83
/*
74
- * TODO(gsn): Consider inlining the function passed to useMemo,
75
- * rather than just calling it directly.
76
- *
84
* Replace the hook callee with the fn arg.
85
*
86
* before:
80
- * foo = Call useMemo$2($9, $10)
87
+ * $1 = LoadGlobal useMemo // load the useMemo global
88
+ * $2 = FunctionExpression ... // memo function
89
+ * $3 = ArrayExpression [ ... ] // deps array
90
+ * $4 = Call $1 ($2, $3 ) // invoke useMemo w fn and deps
91
*
92
* after:
83
- * foo = Call $9()
93
+ * $1 = LoadGlobal useMemo // load the useMemo global (dead code)
94
+ * $2 = FunctionExpression ... // memo function
95
+ * $3 = ArrayExpression [ ... ] // deps array (dead code)
96
+ * $4 = Call $2 () // invoke the memo function itself
97
+ *
98
+ * Note that a later pass (InlineImmediatelyInvokedFunctionExpressions) will
99
+ * inline the useMemo callback along with any other immediately invoked IIFEs.
100
*/
101
if (fn.kind === "Identifier") {
102
instr.value = {
109
args: [],
110
loc: instr.value.loc,
111
};
112
+ if (
113
+ func.env.config.enablePreserveExistingMemoizationGuarantees
114
+ ) {
115
+ /**
116
+ * When this flag is enabled we also compile in a 'Memoize' instruction
117
+ * to preserve the intended memoization boundary:
118
+ *
119
+ * Normal output:
120
+ * $1 = LoadGlobal useMemo // load the useMemo global (dead code)
121
+ * $2 = FunctionExpression ... // memo function
122
+ * $3 = ArrayExpression [ ... ] // deps array (dead code)
123
+ * $4 = Call $2 () // invoke the memo function itself
124
+ *
125
+ * Output w flag enabled:
126
+ * $1 = LoadGlobal useMemo // load the useMemo global (dead code)
127
+ * $2 = FunctionExpression ... // memo function
128
+ * $3 = ArrayExpression [ ... ] // deps array (dead code)
129
+ * $5 = Call $2 () // invoke the memo function itself
130
+ * $4 = Memoize $5 // preserve memo information
131
+ *
132
+ * Note that we synthesize a new temporary for the call ($5) and use
133
+ * the original lvalue for the result of the Memoize instruction, so that
134
+ * we don't have to rewrite subsequent instructions.
135
+ */
136
+ const lvalue = instr.lvalue;
137
+ const temp = createTemporaryPlace(func.env);
138
+ instr.lvalue = { ...temp };
139
+ nextInstructions =
140
+ nextInstructions ?? block.instructions.slice(0, i + 1);
141
+ nextInstructions.push({
142
+ id: makeInstructionId(0),
143
+ lvalue,
144
+ value: {
145
+ kind: "Memoize",
146
+ value: temp,
147
+ loc: instr.loc,
148
+ },
149
+ loc: instr.loc,
150
+ });
151
+ }
152
}
153
} else if (hookKind === "useCallback") {
154
const [fn] = instr.value.args as Array<
166
* Instead of a Call, just alias the callback directly.
167
*
168
* before:
113
- * foo = Call useCallback$8($19)
169
+ * $1 = LoadGlobal useCallback
170
+ * $2 = FunctionExpression ... // the callback being memoized
171
+ * $3 = ArrayExpression ... // deps array
172
+ * $3 = Call $1 ( $2, $3 ) // invoke useCallback
173
*
174
* after:
116
- * foo = $19
175
+ * $1 = LoadGlobal useCallback // dead code
176
+ * $2 = FunctionExpression ... // the callback being memoized
177
+ * $3 = ArrayExpression ... // deps array (dead code)
178
+ * $3 = LoadLocal $2 // reference the function
179
*/
180
if (fn.kind === "Identifier") {
119
- instr.value = {
120
- kind: "LoadLocal",
121
- place: {
122
- kind: "Identifier",
123
- identifier: fn.identifier,
124
- effect: Effect.Unknown,
125
- reactive: false,
181
+ if (
182
+ func.env.config.enablePreserveExistingMemoizationGuarantees
183
+ ) {
184
+ /**
185
+ * With the flag enabled the output changes to use a Memoize instruction instead
186
+ * a loadlocal to load the function expression into the original temporary:
187
+ *
188
+ * Normal output:
189
+ * $1 = LoadGlobal useCallback // dead code
190
+ * $2 = FunctionExpression ... // the callback being memoized
191
+ * $3 = ArrayExpression ... // deps array (dead code)
192
+ * $3 = LoadLocal $2 // reference the function
193
+ *
194
+ * With flag enabled:
195
+ * $1 = LoadGlobal useCallback // dead code
196
+ * $2 = FunctionExpression ... // the callback being memoized
197
+ * $3 = ArrayExpression ... // deps array (dead code)
198
+ * $3 = Memoize $2 // reference the function
199
+ *
200
+ * Note the s/LoadLocal/Memoize/
201
+ */
202
+ instr.value = {
203
+ kind: "Memoize",
204
+ value: {
205
+ kind: "Identifier",
206
+ identifier: fn.identifier,
207
+ effect: Effect.Unknown,
208
+ reactive: false,
209
+ loc: instr.value.loc,
210
+ },
211
loc: instr.value.loc,
127
- },
128
- loc: instr.value.loc,
129
- };
212
+ };
213
+ } else {
214
+ instr.value = {
215
+ kind: "LoadLocal",
216
+ place: {
217
+ kind: "Identifier",
218
+ identifier: fn.identifier,
219
+ effect: Effect.Unknown,
220
+ reactive: false,
221
+ loc: instr.value.loc,
222
+ },
223
+ loc: instr.value.loc,
224
+ };
225
+ }
226
}
227
}
228
}
230
}
231
}
232
}
233
+ if (nextInstructions !== null) {
234
+ block.instructions = nextInstructions;
235
+ hasChanges = true;
236
+ }
237
+ }
238
+ if (hasChanges) {
239
+ markInstructionIds(func.body);
240
}
241
}