diff --git a/src/wasm-stack.h b/src/wasm-stack.h index fede0bf4ccf..c13595c2745 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -643,6 +643,7 @@ class StackIROptimizer { void removeAt(Index i); Index getNumConsumedValues(StackInst* inst); bool canRemoveSetGetPair(Index setIndex, Index getIndex); + bool isConsumedBySymmetricOp(Index getIndex); std::unordered_set findStringViewDeferredGets(); }; diff --git a/src/wasm/wasm-stack-opts.cpp b/src/wasm/wasm-stack-opts.cpp index 0050bd95fe5..3c1b8a69dce 100644 --- a/src/wasm/wasm-stack-opts.cpp +++ b/src/wasm/wasm-stack-opts.cpp @@ -21,6 +21,7 @@ #include "ir/branch-utils.h" #include "ir/iteration.h" #include "ir/local-graph.h" +#include "ir/properties.h" #include "pass.h" #include "wasm-stack.h" #include "wasm.h" @@ -124,6 +125,26 @@ void StackIROptimizer::vacuum() { } } +// Whether the local.get at getIndex is consumed by an operation whose operands +// can be swapped. +bool StackIROptimizer::isConsumedBySymmetricOp(Index getIndex) { + Index k = getIndex + 1; + while (k < insts.size() && !insts[k]) { + ++k; + } + if (k >= insts.size()) { + return false; + } + auto* inst = insts[k]; + if (inst->op != StackInst::Basic) { + return false; + } + if (auto* binary = inst->origin->dynCast()) { + return Properties::isSymmetric(binary); + } + return inst->origin->is(); +} + // If ordered properly, we can avoid a local.set/local.get pair, // and use the value directly from the stack, for example // [..produce a value on the stack..] @@ -219,11 +240,26 @@ void StackIROptimizer::local2Stack() { // can reach the set. if (values.size() > 0) { Index j = values.size() - 1; + // If an actual value is in the way then the set's value is not on + // top of the stack at the get. That is fine if exactly one value is + // in the way and it is consumed together with the get by an + // operation with interchangeable operands, as then the order does + // not matter. + bool intervening = false; while (1) { - // If there's an actual value in the way, we've failed. auto setIndex = values[j]; if (setIndex == null) { - break; + if (intervening || !isConsumedBySymmetricOp(getIndex)) { + // More than one value is in the way, or the value that is in + // the way is not consumed together with the get: give up. + break; + } + intervening = true; + if (j == 0) { + break; + } + j--; + continue; } auto* set = insts[setIndex]->origin->cast(); if (set->index == get->index) { diff --git a/test/lit/passes/optimize-stack-ir.wast b/test/lit/passes/optimize-stack-ir.wast index 123be3afb8a..5c3b591062f 100644 --- a/test/lit/passes/optimize-stack-ir.wast +++ b/test/lit/passes/optimize-stack-ir.wast @@ -6,16 +6,18 @@ ;; CHECK: (type $FUNCSIG$v (func)) (type $FUNCSIG$v (func)) - ;; CHECK: (type $5 (func (result i32))) - ;; CHECK: (type $6 (func (param i32) (result i32))) + ;; CHECK: (type $5 (func (result i32))) + ;; CHECK: (type $FUNCSIG$vf (func (param f32))) (type $FUNCSIG$vf (func (param f32))) ;; CHECK: (type $4 (func (result f64))) ;; CHECK: (type $FUNCSIG$ddd (func (param f64 f64) (result f64))) + ;; CHECK: (type $10 (func (param f32) (result f32))) + ;; CHECK: (type $FUNCSIG$id (func (param f64) (result i32))) (type $FUNCSIG$id (func (param f64) (result i32))) (type $FUNCSIG$ddd (func (param f64 f64) (result f64))) @@ -29,6 +31,10 @@ ;; CHECK: (type $9 (func (param i32 i64))) (type $9 (func (param i32 i64))) + ;; CHECK: (type $15 (func (param eqref) (result i32))) + + ;; CHECK: (type $16 (func (param eqref) (result eqref))) + ;; CHECK: (import "env" "_emscripten_asm_const_vi" (func $_emscripten_asm_const_vi (type $FUNCSIG$v))) (import "env" "_emscripten_asm_const_vi" (func $_emscripten_asm_const_vi)) @@ -1097,24 +1103,44 @@ ) ) - ;; CHECK: (func $local-to-stack-1c-no (type $6) (param $x i32) (result i32) + ;; CHECK: (func $local-to-stack-1c (type $6) (param $x i32) (result i32) ;; CHECK-NEXT: (local $temp i32) ;; CHECK-NEXT: i32.const 1 ;; CHECK-NEXT: call $local-to-stack - ;; CHECK-NEXT: local.set $temp ;; CHECK-NEXT: i32.const 2 ;; CHECK-NEXT: call $local-to-stack ;; CHECK-NEXT: drop ;; CHECK-NEXT: i32.const 3 - ;; CHECK-NEXT: local.get $temp ;; CHECK-NEXT: i32.add ;; CHECK-NEXT: ) - (func $local-to-stack-1c-no (param $x i32) (result i32) + (func $local-to-stack-1c (param $x i32) (result i32) (local $temp i32) (local.set $temp (call $local-to-stack (i32.const 1))) (drop (call $local-to-stack (i32.const 2))) (i32.add - (i32.const 3) ;; this is in the way + (i32.const 3) ;; in the way, but the add is commutative so we can + (local.get $temp) ;; still use the value on the stack + ) + ) + + ;; CHECK: (func $local-to-stack-1d-sub-no (type $6) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: i32.const 1 + ;; CHECK-NEXT: call $local-to-stack + ;; CHECK-NEXT: local.set $temp + ;; CHECK-NEXT: i32.const 2 + ;; CHECK-NEXT: call $local-to-stack + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: i32.const 3 + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: i32.sub + ;; CHECK-NEXT: ) + (func $local-to-stack-1d-sub-no (param $x i32) (result i32) + (local $temp i32) + (local.set $temp (call $local-to-stack (i32.const 1))) + (drop (call $local-to-stack (i32.const 2))) + (i32.sub + (i32.const 3) ;; not commutative: the set/get pair must stay (local.get $temp) ) ) @@ -1292,29 +1318,27 @@ (local.get $temp2) ) - ;; CHECK: (func $local-to-stack-overlapping-multi-8-no (type $6) (param $x i32) (result i32) + ;; CHECK: (func $local-to-stack-overlapping-multi-8 (type $6) (param $x i32) (result i32) ;; CHECK-NEXT: (local $temp1 i32) ;; CHECK-NEXT: (local $temp2 i32) ;; CHECK-NEXT: i32.const 1 ;; CHECK-NEXT: call $local-to-stack-multi-4 - ;; CHECK-NEXT: local.set $temp1 ;; CHECK-NEXT: i32.const 1 ;; CHECK-NEXT: call $local-to-stack-multi-4 ;; CHECK-NEXT: i32.const 3 ;; CHECK-NEXT: call $local-to-stack-multi-4 ;; CHECK-NEXT: drop - ;; CHECK-NEXT: local.get $temp1 ;; CHECK-NEXT: i32.add ;; CHECK-NEXT: ) - (func $local-to-stack-overlapping-multi-8-no (param $x i32) (result i32) + (func $local-to-stack-overlapping-multi-8 (param $x i32) (result i32) (local $temp1 i32) (local $temp2 i32) (local.set $temp1 (call $local-to-stack-multi-4 (i32.const 1))) (local.set $temp2 (call $local-to-stack-multi-4 (i32.const 1))) (drop (call $local-to-stack-multi-4 (i32.const 3))) - (i32.add - (local.get $temp2) ;; the timing - (local.get $temp1) ;; it sucks + (i32.add ;; the operands are flipped, but the add is commutative + (local.get $temp2) + (local.get $temp1) ) ) @@ -1459,4 +1483,92 @@ ) ) ) + + ;; A ref.eq with an intervening value: its operands can be swapped. + ;; CHECK: (func $local-to-stack-ref-eq (type $15) (param $x eqref) (result i32) + ;; CHECK-NEXT: (local $temp eqref) + ;; CHECK-NEXT: local.get $x + ;; CHECK-NEXT: call $eqref-value + ;; CHECK-NEXT: local.get $x + ;; CHECK-NEXT: call $eqref-value + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: local.get $x + ;; CHECK-NEXT: ref.eq + ;; CHECK-NEXT: ) + (func $local-to-stack-ref-eq (param $x eqref) (result i32) + (local $temp eqref) + (local.set $temp (call $eqref-value (local.get $x))) + (drop (call $eqref-value (local.get $x))) + (ref.eq + (local.get $x) + (local.get $temp) + ) + ) + + ;; CHECK: (func $eqref-value (type $16) (param $x eqref) (result eqref) + ;; CHECK-NEXT: local.get $x + ;; CHECK-NEXT: call $eqref-value + ;; CHECK-NEXT: ) + (func $eqref-value (param $x eqref) (result eqref) + (call $eqref-value (local.get $x)) + ) + + ;; f32.min has swappable operands, so the set/get pair can be removed. + ;; CHECK: (func $local-to-stack-f32-min (type $10) (param $x f32) (result f32) + ;; CHECK-NEXT: (local $temp f32) + ;; CHECK-NEXT: local.get $x + ;; CHECK-NEXT: call $float-value + ;; CHECK-NEXT: local.get $x + ;; CHECK-NEXT: call $float-value + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: local.get $x + ;; CHECK-NEXT: f32.min + ;; CHECK-NEXT: ) + (func $local-to-stack-f32-min (param $x f32) (result f32) + (local $temp f32) + (local.set $temp (call $float-value (local.get $x))) + (drop (call $float-value (local.get $x))) + (f32.min + (local.get $x) + (local.get $temp) + ) + ) + + ;; CHECK: (func $float-value (type $10) (param $x f32) (result f32) + ;; CHECK-NEXT: local.get $x + ;; CHECK-NEXT: call $float-value + ;; CHECK-NEXT: ) + (func $float-value (param $x f32) (result f32) + (call $float-value (local.get $x)) + ) + + ;; Two values are in the way, so the set/get pair must stay. + ;; CHECK: (func $local-to-stack-two-in-the-way (type $6) (param $x i32) (result i32) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: i32.const 1 + ;; CHECK-NEXT: call $local-to-stack + ;; CHECK-NEXT: local.set $temp + ;; CHECK-NEXT: i32.const 2 + ;; CHECK-NEXT: call $local-to-stack + ;; CHECK-NEXT: drop + ;; CHECK-NEXT: i32.const 3 + ;; CHECK-NEXT: call $local-to-stack + ;; CHECK-NEXT: i32.const 4 + ;; CHECK-NEXT: call $local-to-stack + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: i32.add + ;; CHECK-NEXT: i32.add + ;; CHECK-NEXT: ) + (func $local-to-stack-two-in-the-way (param $x i32) (result i32) + (local $temp i32) + (local.set $temp (call $local-to-stack (i32.const 1))) + (drop (call $local-to-stack (i32.const 2))) + (i32.add + (call $local-to-stack (i32.const 3)) + (i32.add + (call $local-to-stack (i32.const 4)) + (local.get $temp) + ) + ) + ) )