Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/wasm-stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<LocalGet*> findStringViewDeferredGets();
};

Expand Down
40 changes: 38 additions & 2 deletions src/wasm/wasm-stack-opts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<Binary>()) {
return Properties::isSymmetric(binary);
}
return inst->origin->is<RefEq>();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also check for RefEq

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added RefEq and a test for it.


// 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..]
Expand Down Expand Up @@ -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<LocalSet>();
if (set->index == get->index) {
Expand Down
140 changes: 126 additions & 14 deletions test/lit/passes/optimize-stack-ir.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand All @@ -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))

Expand Down Expand Up @@ -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)
)
)
Expand Down Expand Up @@ -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)
)
)

Expand Down Expand Up @@ -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)
)
)
)
)
Loading