diff options
-rw-r--r-- | src/ir/localize.h | 42 | ||||
-rw-r--r-- | test/lit/passes/gto-removals.wast | 85 |
2 files changed, 118 insertions, 9 deletions
diff --git a/src/ir/localize.h b/src/ir/localize.h index 44ec5af32..270901c11 100644 --- a/src/ir/localize.h +++ b/src/ir/localize.h @@ -44,13 +44,20 @@ struct Localizer { } }; -// Replaces all children with gets of locals, if they have any effects. After -// this, the original input has only local.gets as inputs, or other things that -// have no interacting effects, and so those children can be reordered. +// Replaces all children with gets of locals, if they have any effects that +// interact with any of the others, or if they have side effects which cannot be +// removed. +// +// After this, the original input has only local.gets as inputs, or other things +// that have no interacting effects, and so those children can be reordered +// and/or removed as needed. +// // The sets of the locals are emitted on a |sets| property on the class. Those // must be emitted right before the input. +// // This stops at the first unreachable child, as there is no code executing // after that point anyhow. +// // TODO: use in more places struct ChildLocalizer { std::vector<LocalSet*> sets; @@ -62,18 +69,37 @@ struct ChildLocalizer { Builder builder(*wasm); ChildIterator iterator(input); auto& children = iterator.children; - // The children are in reverse order, so allocate the output first and - // apply items as we go. auto num = children.size(); + + // Compute the effects of all children. + std::vector<EffectAnalyzer> effects; + for (Index i = 0; i < num; i++) { + // The children are in reverse order in ChildIterator, but we want to + // process them in the normal order. + auto* child = *children[num - 1 - i]; + effects.emplace_back(options, *wasm, child); + } + + // Go through the children and move to locals those that we need to. for (Index i = 0; i < num; i++) { auto** childp = children[num - 1 - i]; auto* child = *childp; if (child->type == Type::unreachable) { break; } - // If there are effects, use a local for this. - // TODO: Compare interactions between their side effects. - if (EffectAnalyzer(options, *wasm, child).hasAnything()) { + + // Use a local if we need to. That is the case either if this has side + // effects we can't remove, or if it interacts with other children. + bool needLocal = effects[i].hasUnremovableSideEffects(); + if (!needLocal) { + for (Index j = 0; j < num; j++) { + if (j != i && effects[i].invalidates(effects[j])) { + needLocal = true; + break; + } + } + } + if (needLocal) { auto local = builder.addVar(func, child->type); sets.push_back(builder.makeLocalSet(local, child)); *childp = builder.makeLocalGet(local, child->type); diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index e74a09d5a..188019fab 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -394,16 +394,23 @@ ;; CHECK: (type $struct (struct_subtype (field i32) (field (rtt $struct)) data)) (type $struct (struct i32 f64 (ref any) (rtt $struct))) - ;; CHECK: (type $ref|any|_=>_none (func_subtype (param (ref any)) func)) ;; CHECK: (type $none_=>_none (func_subtype func)) + ;; CHECK: (type $ref|any|_=>_none (func_subtype (param (ref any)) func)) + ;; CHECK: (type $i32_=>_i32 (func_subtype (param i32) (result i32) func)) ;; CHECK: (type $i32_=>_f64 (func_subtype (param i32) (result f64) func)) ;; CHECK: (type $i32_=>_ref|any| (func_subtype (param i32) (result (ref any)) func)) + ;; CHECK: (global $imm-i32 i32 (i32.const 1234)) + (global $imm-i32 i32 (i32.const 1234)) + + ;; CHECK: (global $mut-i32 (mut i32) (i32.const 5678)) + (global $mut-i32 (mut i32) (i32.const 5678)) + ;; CHECK: (func $gets (param $x (ref any)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct 0 @@ -475,6 +482,82 @@ ) ) + ;; CHECK: (func $new-side-effect-global-imm + ;; CHECK-NEXT: (local $0 f64) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (call $helper1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (call $helper2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (global.get $imm-i32) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $new-side-effect-global-imm + ;; As above, the 2nd&3rd fields here will be removed. The first field does + ;; a global.get, which has effects, but those effects do not interact with + ;; anything else (since it is an immutable global), so we do not need a + ;; local for it. + (drop + (struct.new $struct + (global.get $imm-i32) + (call $helper1 (i32.const 0)) + (call $helper2 (i32.const 1)) + (rtt.canon $struct) + ) + ) + ) + + ;; CHECK: (func $new-side-effect-global-mut + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (local $2 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (global.get $mut-i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (call $helper1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (call $helper2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $new-side-effect-global-mut + ;; As above, but the global is mutable, so we will use a local: the calls + ;; might alter that global, in theory. + (drop + (struct.new $struct + (global.get $mut-i32) + (call $helper1 (i32.const 0)) + (call $helper2 (i32.const 1)) + (rtt.canon $struct) + ) + ) + ) + ;; CHECK: (func $new-unreachable ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) |