11
Environment,
12
FunctionExpression,
13
GeneratedSource,
14
+ GotoTerminal,
15
GotoVariant,
16
HIRFunction,
17
IdentifierId,
20
Place,
21
isStatementBlockKind,
22
makeInstructionId,
23
+ mergeConsecutiveBlocks,
24
promoteTemporary,
25
reversePostorderBlocks,
26
} from '../HIR';
75
* - All return statements in the original function expression are replaced with a
76
* StoreLocal to the temporary we allocated before plus a Goto to the fallthrough
77
* block (code following the CallExpression).
78
+ *
79
+ * Note that if the inliined function has only one return, we avoid the labeled block
80
+ * and fully inline the code. The original return is replaced with an assignmen to the
81
+ * IIFE's call expression lvalue.
82
*/
83
export function inlineImmediatelyInvokedFunctionExpressions(
84
fn: HIRFunction,
152
*/
153
block.instructions.length = ii;
154
149
- /*
150
- * To account for complex control flow within the lambda, we treat the lambda
151
- * as if it were a single labeled statement, and replace all returns with gotos
152
- * to the label fallthrough.
153
- */
154
- const newTerminal: LabelTerminal = {
155
- block: body.loweredFunc.func.body.entry,
156
- id: makeInstructionId(0),
157
- kind: 'label',
158
- fallthrough: continuationBlockId,
159
- loc: block.terminal.loc,
160
- };
161
- block.terminal = newTerminal;
155
+ if (hasSingleExitReturnTerminal(body.loweredFunc.func)) {
156
+ block.terminal = {
157
+ kind: 'goto',
158
+ block: body.loweredFunc.func.body.entry,
159
+ id: block.terminal.id,
160
+ loc: block.terminal.loc,
161
+ variant: GotoVariant.Break,
162
+ } as GotoTerminal;
163
+ for (const block of body.loweredFunc.func.body.blocks.values()) {
164
+ if (block.terminal.kind === 'return') {
165
+ block.instructions.push({
166
+ id: makeInstructionId(0),
167
+ loc: block.terminal.loc,
168
+ lvalue: instr.lvalue,
169
+ value: {
170
+ kind: 'LoadLocal',
171
+ loc: block.terminal.loc,
172
+ place: block.terminal.value,
173
+ },
174
+ effects: null,
175
+ });
176
+ block.terminal = {
177
+ kind: 'goto',
178
+ block: continuationBlockId,
179
+ id: block.terminal.id,
180
+ loc: block.terminal.loc,
181
+ variant: GotoVariant.Break,
182
+ } as GotoTerminal;
183
+ }
184
+ }
185
+ for (const [id, block] of body.loweredFunc.func.body.blocks) {
186
+ block.preds.clear();
187
+ fn.body.blocks.set(id, block);
188
+ }
189
+ } else {
190
+ /*
191
+ * To account for multiple returns within the lambda, we treat the lambda
192
+ * as if it were a single labeled statement, and replace all returns with gotos
193
+ * to the label fallthrough.
194
+ */
195
+ const newTerminal: LabelTerminal = {
196
+ block: body.loweredFunc.func.body.entry,
197
+ id: makeInstructionId(0),
198
+ kind: 'label',
199
+ fallthrough: continuationBlockId,
200
+ loc: block.terminal.loc,
201
+ };
202
+ block.terminal = newTerminal;
203
163
- // We store the result in the IIFE temporary
164
- const result = instr.lvalue;
204
+ // We store the result in the IIFE temporary
205
+ const result = instr.lvalue;
206
166
- // Declare the IIFE temporary
167
- declareTemporary(fn.env, block, result);
207
+ // Declare the IIFE temporary
208
+ declareTemporary(fn.env, block, result);
209
169
- // Promote the temporary with a name as we require this to persist
170
- promoteTemporary(result.identifier);
210
+ // Promote the temporary with a name as we require this to persist
211
+ if (result.identifier.name == null) {
212
+ promoteTemporary(result.identifier);
213
+ }
214
172
- /*
173
- * Rewrite blocks from the lambda to replace any `return` with a
174
- * store to the result and `goto` the continuation block
175
- */
176
- for (const [id, block] of body.loweredFunc.func.body.blocks) {
177
- block.preds.clear();
178
- rewriteBlock(fn.env, block, continuationBlockId, result);
179
- fn.body.blocks.set(id, block);
215
+ /*
216
+ * Rewrite blocks from the lambda to replace any `return` with a
217
+ * store to the result and `goto` the continuation block
218
+ */
219
+ for (const [id, block] of body.loweredFunc.func.body.blocks) {
220
+ block.preds.clear();
221
+ rewriteBlock(fn.env, block, continuationBlockId, result);
222
+ fn.body.blocks.set(id, block);
223
+ }
224
}
225
226
/*
243
244
if (inlinedFunctions.size !== 0) {
245
// Remove instructions that define lambdas which we inlined
202
- for (const [, block] of fn.body.blocks) {
246
+ for (const block of fn.body.blocks.values()) {
247
retainWhere(
248
block.instructions,
249
instr => !inlinedFunctions.has(instr.lvalue.identifier.id),
257
reversePostorderBlocks(fn.body);
258
markInstructionIds(fn.body);
259
markPredecessors(fn.body);
260
+ mergeConsecutiveBlocks(fn);
261
+ }
262
+}
263
+
264
+/**
265
+ * Returns true if the function has a single exit terminal (throw/return) which is a return
266
+ */
267
+function hasSingleExitReturnTerminal(fn: HIRFunction): boolean {
268
+ let hasReturn = false;
269
+ let exitCount = 0;
270
+ for (const [, block] of fn.body.blocks) {
271
+ if (block.terminal.kind === 'return' || block.terminal.kind === 'throw') {
272
+ hasReturn ||= block.terminal.kind === 'return';
273
+ exitCount++;
274
+ }
275
}
276
+ return exitCount === 1 && hasReturn;
277
}
278
279
/*