diff options
-rw-r--r-- | src/pass.h | 17 | ||||
-rw-r--r-- | src/passes/SimplifyGlobals.cpp | 16 | ||||
-rw-r--r-- | test/lit/passes/simplify-globals_func-effects.wast | 91 |
3 files changed, 116 insertions, 8 deletions
diff --git a/src/pass.h b/src/pass.h index f4b15a320..2cb661e72 100644 --- a/src/pass.h +++ b/src/pass.h @@ -488,10 +488,19 @@ public: assert(getPassRunner()); // Parallel pass running is implemented in the PassRunner. if (isFunctionParallel()) { - // TODO: We should almost certainly be propagating pass options here, but - // that is a widespread change, so make sure it doesn't unacceptably - // regress compile times. - PassRunner runner(module /*, getPassOptions()*/); + // Reduce opt/shrink levels to a maximum of one in nested runners like + // these, to balance runtime. We definitely want the full levels in the + // main passes we run, but nested pass runners are of secondary + // importance. + // TODO Investigate the impact of allowing the levels to just pass + // through. That seems to cause at least some regression in compile + // times in -O3, however, but with careful measurement we may find + // the benefits are worth it. For now -O1 is a reasonable compromise + // as it has basically linear runtime, unlike -O2 and -O3. + auto options = getPassOptions(); + options.optimizeLevel = std::min(options.optimizeLevel, 1); + options.shrinkLevel = std::min(options.shrinkLevel, 1); + PassRunner runner(module, options); runner.setIsNested(true); runner.add(create()); runner.run(); diff --git a/src/passes/SimplifyGlobals.cpp b/src/passes/SimplifyGlobals.cpp index 463261d35..f9bccab23 100644 --- a/src/passes/SimplifyGlobals.cpp +++ b/src/passes/SimplifyGlobals.cpp @@ -364,12 +364,20 @@ struct ConstantGlobalApplier } return; } - // Otherwise, invalidate if we need to. - EffectAnalyzer effects(getPassOptions(), *getModule()); - effects.visit(curr); - assert(effects.globalsWritten.empty()); // handled above + + // Otherwise, invalidate if we need to. Note that we handled a GlobalSet + // earlier, but also need to handle calls. A general call forces us to + // forget everything, but in some cases we can do better, if we have a call + // and have computed function effects for it. + ShallowEffectAnalyzer effects(getPassOptions(), *getModule(), curr); if (effects.calls) { + // Forget everything. currConstantGlobals.clear(); + } else { + // Forget just the globals written, if any. + for (auto writtenGlobal : effects.globalsWritten) { + currConstantGlobals.erase(writtenGlobal); + } } } diff --git a/test/lit/passes/simplify-globals_func-effects.wast b/test/lit/passes/simplify-globals_func-effects.wast new file mode 100644 index 000000000..8257c5ce7 --- /dev/null +++ b/test/lit/passes/simplify-globals_func-effects.wast @@ -0,0 +1,91 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --generate-global-effects --simplify-globals -S -o - | filecheck %s + +;; Compute function effects and then simplify globals. We must handle the case +;; of an expression that is not a global.set that sets a global - a function +;; whose effects we've computed to include some sets to globals. + +(module + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (global $A (mut i32) (i32.const 10)) + (global $A (mut i32) (i32.const 10)) + + ;; CHECK: (global $B i32 (i32.const 20)) + (global $B (mut i32) (i32.const 20)) + + ;; CHECK: (global $C (mut i32) (i32.const 30)) + (global $C (mut i32) (i32.const 30)) + + ;; CHECK: (func $set + ;; CHECK-NEXT: (global.set $A + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set + (global.set $A + (i32.const 0) + ) + ) + + ;; CHECK: (func $test + ;; CHECK-NEXT: (global.set $A + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $C + ;; CHECK-NEXT: (i32.const 33) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 33) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $set) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 33) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (global.set $A + (i32.const 11) + ) + (global.set $C + (i32.const 33) + ) + ;; We can infer $A here since we see the write to it, above. + (drop + (global.get $A) + ) + ;; We can infer $B since we'll prove it is immutable. + (drop + (global.get $B) + ) + ;; We can infer $C here since we see the write to it, above. + (drop + (global.get $C) + ) + ;; This call sets $A. After the call we can no longer infer $A, but we can + ;; still infer the others. + (call $set) + (drop + (global.get $A) + ) + (drop + (global.get $B) + ) + (drop + (global.get $C) + ) + ) +) |