@@ -197,9 +197,10 @@ struct Scanner : public WalkerPass<PostWalker<Scanner>> {
197
197
OptInfo& optInfo;
198
198
};
199
199
200
- // Information in a basic block. We track relevant expressions, which are calls
201
- // calls to "once" functions, and writes to "once" globals.
200
+ // Information in a basic block.
202
201
struct BlockInfo {
202
+ // We track relevant expressions, which are call to "once" functions, and
203
+ // writes to "once" globals.
203
204
std::vector<Expression*> exprs;
204
205
};
205
206
@@ -312,18 +313,16 @@ struct Optimizer
312
313
optimizeOnce (set->name );
313
314
}
314
315
} else if (auto * call = expr->dynCast <Call>()) {
315
- if (optInfo.onceFuncs .at (call->target ).is ()) {
316
+ auto target = call->target ;
317
+ if (optInfo.onceFuncs .at (target).is ()) {
316
318
// The global used by the "once" func is written.
317
319
assert (call->operands .empty ());
318
- optimizeOnce (optInfo.onceFuncs .at (call-> target ));
320
+ optimizeOnce (optInfo.onceFuncs .at (target));
319
321
continue ;
320
322
}
321
323
322
- // This is not a call to a "once" func. However, we may have inferred
323
- // that it definitely sets some "once" globals before it returns, and
324
- // we can use that information.
325
- for (auto globalName :
326
- optInfo.onceGlobalsSetInFuncs .at (call->target )) {
324
+ // Note as written all globals the called function is known to write.
325
+ for (auto globalName : optInfo.onceGlobalsSetInFuncs .at (target)) {
327
326
onceGlobalsWritten.insert (globalName);
328
327
}
329
328
} else {
@@ -439,7 +438,110 @@ struct OnceReduction : public Pass {
439
438
lastOnceGlobalsSet = currOnceGlobalsSet;
440
439
continue ;
441
440
}
442
- return ;
441
+ break ;
442
+ }
443
+
444
+ // Finally, apply some optimizations to "once" functions themselves. We do
445
+ // this at the end to not modify them as we go, which could confuse the main
446
+ // part of this pass right before us.
447
+ optimizeOnceBodies (optInfo, module );
448
+ }
449
+
450
+ void optimizeOnceBodies (const OptInfo& optInfo, Module* module ) {
451
+ // Track which "once" functions we remove the exit logic from, as we cannot
452
+ // create loops without exit logic, see below.
453
+ std::unordered_set<Name> removedExitLogic;
454
+
455
+ // Iterate deterministically on functions, as the order matters (since we
456
+ // make decisions based on previous actions; see below).
457
+ for (auto & func : module ->functions ) {
458
+ if (!optInfo.onceFuncs .at (func->name ).is ()) {
459
+ // This is not a "once" function.
460
+ continue ;
461
+ }
462
+
463
+ // We optimize the case where the payload is trivial, that is where we
464
+ // have this:
465
+ //
466
+ // function foo() {
467
+ // if (!foo$once) return; // two lines of
468
+ // foo$once = 1; // early-exit code
469
+ // PAYLOAD
470
+ // }
471
+ //
472
+ // And PAYLOAD is simple.
473
+ auto * body = func->body ;
474
+ auto & list = body->cast <Block>()->list ;
475
+ if (list.size () == 2 ) {
476
+ // No payload at all; we don't need the early-exit code then.
477
+ //
478
+ // Note that this overlaps with SimplifyGlobals' optimization on
479
+ // "read-only-to-write" globals: with no payload, this global is really
480
+ // only read in order to write itself, and nothing more, so there is no
481
+ // observable behavior we need to preserve, and the global can be
482
+ // removed. We might as well handle this case here as well since we've
483
+ // done all the work up to here, and it is just one line to implement
484
+ // the nopping out. (And doing so here can accelerate the optimization
485
+ // pipeline by not needing to wait until the next SimplifyGlobals.)
486
+ ExpressionManipulator::nop (body);
487
+ continue ;
488
+ }
489
+ if (list.size () != 3 ) {
490
+ // Something non-trivial; too many items for us to consider.
491
+ continue ;
492
+ }
493
+ auto * payload = list[2 ];
494
+ if (auto * call = payload->dynCast <Call>()) {
495
+ if (optInfo.onceFuncs .at (call->target ).is ()) {
496
+ // All this "once" function does is call another. We do not need the
497
+ // early-exit logic in this one, then, because of the following
498
+ // reasoning. We are comparing these forms:
499
+ //
500
+ // // BEFORE
501
+ // function foo() {
502
+ // if (!foo$once) return; // two lines of
503
+ // foo$once = 1; // early-exit code
504
+ // bar();
505
+ // }
506
+ //
507
+ // to
508
+ //
509
+ // // AFTER
510
+ // function foo() {
511
+ // bar();
512
+ // }
513
+ //
514
+ // The question is whether different behavior can be observed between
515
+ // those two. There are two cases, when we enter foo:
516
+ //
517
+ // 1. foo has been called before. Then we early-exit in BEFORE, and
518
+ // in AFTER we call bar which will early-exit (since foo was
519
+ // called, which means bar was at least entered, which set its
520
+ // global; bar might be on the stack, if it called foo, so it has
521
+ // not necessarily fully executed - this is a tricky situation to
522
+ // handle in general, like recursive imports of modules in various
523
+ // languages - but we do know bar has been *entered*, which means
524
+ // the global was set).
525
+ // 2. foo has never been called before. In this case in BEFORE we set
526
+ // the global and call bar, and in AFTER we also call bar.
527
+ //
528
+ // Thus, the behavior is the same, and we can remove the early-exit
529
+ // lines.
530
+ //
531
+ // We must be careful of loops, however: If A calls B and B calls A,
532
+ // then at least one must keep the early-exit logic, or else they
533
+ // would infinitely loop if one is called. To avoid that, we track
534
+ // which functions we remove the early-exit logic from, and never
535
+ // remove the logic if we are calling such a function. (As a result,
536
+ // the order of iteration matters here, and so the outer loop in this
537
+ // function must be deterministic.)
538
+ if (!removedExitLogic.count (call->target )) {
539
+ ExpressionManipulator::nop (list[0 ]);
540
+ ExpressionManipulator::nop (list[1 ]);
541
+ removedExitLogic.insert (func->name );
542
+ }
543
+ }
544
+ }
443
545
}
444
546
}
445
547
};
0 commit comments