Skip to content

Commit 39cd18a

Browse files
authored
[StackIR] Drop a set/get pair across a symmetric operation (#9162)
local2Stack gives up when a value sits between a local.set and the matching local.get. If that value and the get are consumed by the same operation whose operands can be swapped, the pair can be removed anyway. This also removes the one size regression of the late optimize-instructions round (4 bytes on a float test module), and is a small win on average.
1 parent d1a6b25 commit 39cd18a

3 files changed

Lines changed: 165 additions & 16 deletions

File tree

‎src/wasm-stack.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,7 @@ class StackIROptimizer {
643643
void removeAt(Index i);
644644
Index getNumConsumedValues(StackInst* inst);
645645
bool canRemoveSetGetPair(Index setIndex, Index getIndex);
646+
bool isConsumedBySymmetricOp(Index getIndex);
646647
std::unordered_set<LocalGet*> findStringViewDeferredGets();
647648
};
648649

‎src/wasm/wasm-stack-opts.cpp‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "ir/branch-utils.h"
2222
#include "ir/iteration.h"
2323
#include "ir/local-graph.h"
24+
#include "ir/properties.h"
2425
#include "pass.h"
2526
#include "wasm-stack.h"
2627
#include "wasm.h"
@@ -124,6 +125,26 @@ void StackIROptimizer::vacuum() {
124125
}
125126
}
126127

128+
// Whether the local.get at getIndex is consumed by an operation whose operands
129+
// can be swapped.
130+
bool StackIROptimizer::isConsumedBySymmetricOp(Index getIndex) {
131+
Index k = getIndex + 1;
132+
while (k < insts.size() && !insts[k]) {
133+
++k;
134+
}
135+
if (k >= insts.size()) {
136+
return false;
137+
}
138+
auto* inst = insts[k];
139+
if (inst->op != StackInst::Basic) {
140+
return false;
141+
}
142+
if (auto* binary = inst->origin->dynCast<Binary>()) {
143+
return Properties::isSymmetric(binary);
144+
}
145+
return inst->origin->is<RefEq>();
146+
}
147+
127148
// If ordered properly, we can avoid a local.set/local.get pair,
128149
// and use the value directly from the stack, for example
129150
// [..produce a value on the stack..]
@@ -219,11 +240,26 @@ void StackIROptimizer::local2Stack() {
219240
// can reach the set.
220241
if (values.size() > 0) {
221242
Index j = values.size() - 1;
243+
// If an actual value is in the way then the set's value is not on
244+
// top of the stack at the get. That is fine if exactly one value is
245+
// in the way and it is consumed together with the get by an
246+
// operation with interchangeable operands, as then the order does
247+
// not matter.
248+
bool intervening = false;
222249
while (1) {
223-
// If there's an actual value in the way, we've failed.
224250
auto setIndex = values[j];
225251
if (setIndex == null) {
226-
break;
252+
if (intervening || !isConsumedBySymmetricOp(getIndex)) {
253+
// More than one value is in the way, or the value that is in
254+
// the way is not consumed together with the get: give up.
255+
break;
256+
}
257+
intervening = true;
258+
if (j == 0) {
259+
break;
260+
}
261+
j--;
262+
continue;
227263
}
228264
auto* set = insts[setIndex]->origin->cast<LocalSet>();
229265
if (set->index == get->index) {

‎test/lit/passes/optimize-stack-ir.wast‎

Lines changed: 126 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@
66
;; CHECK: (type $FUNCSIG$v (func))
77
(type $FUNCSIG$v (func))
88

9-
;; CHECK: (type $5 (func (result i32)))
10-
119
;; CHECK: (type $6 (func (param i32) (result i32)))
1210

11+
;; CHECK: (type $5 (func (result i32)))
12+
1313
;; CHECK: (type $FUNCSIG$vf (func (param f32)))
1414
(type $FUNCSIG$vf (func (param f32)))
1515
;; CHECK: (type $4 (func (result f64)))
1616

1717
;; CHECK: (type $FUNCSIG$ddd (func (param f64 f64) (result f64)))
1818

19+
;; CHECK: (type $10 (func (param f32) (result f32)))
20+
1921
;; CHECK: (type $FUNCSIG$id (func (param f64) (result i32)))
2022
(type $FUNCSIG$id (func (param f64) (result i32)))
2123
(type $FUNCSIG$ddd (func (param f64 f64) (result f64)))
@@ -29,6 +31,10 @@
2931
;; CHECK: (type $9 (func (param i32 i64)))
3032
(type $9 (func (param i32 i64)))
3133

34+
;; CHECK: (type $15 (func (param eqref) (result i32)))
35+
36+
;; CHECK: (type $16 (func (param eqref) (result eqref)))
37+
3238
;; CHECK: (import "env" "_emscripten_asm_const_vi" (func $_emscripten_asm_const_vi (type $FUNCSIG$v)))
3339
(import "env" "_emscripten_asm_const_vi" (func $_emscripten_asm_const_vi))
3440

@@ -1097,24 +1103,44 @@
10971103
)
10981104
)
10991105

1100-
;; CHECK: (func $local-to-stack-1c-no (type $6) (param $x i32) (result i32)
1106+
;; CHECK: (func $local-to-stack-1c (type $6) (param $x i32) (result i32)
11011107
;; CHECK-NEXT: (local $temp i32)
11021108
;; CHECK-NEXT: i32.const 1
11031109
;; CHECK-NEXT: call $local-to-stack
1104-
;; CHECK-NEXT: local.set $temp
11051110
;; CHECK-NEXT: i32.const 2
11061111
;; CHECK-NEXT: call $local-to-stack
11071112
;; CHECK-NEXT: drop
11081113
;; CHECK-NEXT: i32.const 3
1109-
;; CHECK-NEXT: local.get $temp
11101114
;; CHECK-NEXT: i32.add
11111115
;; CHECK-NEXT: )
1112-
(func $local-to-stack-1c-no (param $x i32) (result i32)
1116+
(func $local-to-stack-1c (param $x i32) (result i32)
11131117
(local $temp i32)
11141118
(local.set $temp (call $local-to-stack (i32.const 1)))
11151119
(drop (call $local-to-stack (i32.const 2)))
11161120
(i32.add
1117-
(i32.const 3) ;; this is in the way
1121+
(i32.const 3) ;; in the way, but the add is commutative so we can
1122+
(local.get $temp) ;; still use the value on the stack
1123+
)
1124+
)
1125+
1126+
;; CHECK: (func $local-to-stack-1d-sub-no (type $6) (param $x i32) (result i32)
1127+
;; CHECK-NEXT: (local $temp i32)
1128+
;; CHECK-NEXT: i32.const 1
1129+
;; CHECK-NEXT: call $local-to-stack
1130+
;; CHECK-NEXT: local.set $temp
1131+
;; CHECK-NEXT: i32.const 2
1132+
;; CHECK-NEXT: call $local-to-stack
1133+
;; CHECK-NEXT: drop
1134+
;; CHECK-NEXT: i32.const 3
1135+
;; CHECK-NEXT: local.get $temp
1136+
;; CHECK-NEXT: i32.sub
1137+
;; CHECK-NEXT: )
1138+
(func $local-to-stack-1d-sub-no (param $x i32) (result i32)
1139+
(local $temp i32)
1140+
(local.set $temp (call $local-to-stack (i32.const 1)))
1141+
(drop (call $local-to-stack (i32.const 2)))
1142+
(i32.sub
1143+
(i32.const 3) ;; not commutative: the set/get pair must stay
11181144
(local.get $temp)
11191145
)
11201146
)
@@ -1292,29 +1318,27 @@
12921318
(local.get $temp2)
12931319
)
12941320

1295-
;; CHECK: (func $local-to-stack-overlapping-multi-8-no (type $6) (param $x i32) (result i32)
1321+
;; CHECK: (func $local-to-stack-overlapping-multi-8 (type $6) (param $x i32) (result i32)
12961322
;; CHECK-NEXT: (local $temp1 i32)
12971323
;; CHECK-NEXT: (local $temp2 i32)
12981324
;; CHECK-NEXT: i32.const 1
12991325
;; CHECK-NEXT: call $local-to-stack-multi-4
1300-
;; CHECK-NEXT: local.set $temp1
13011326
;; CHECK-NEXT: i32.const 1
13021327
;; CHECK-NEXT: call $local-to-stack-multi-4
13031328
;; CHECK-NEXT: i32.const 3
13041329
;; CHECK-NEXT: call $local-to-stack-multi-4
13051330
;; CHECK-NEXT: drop
1306-
;; CHECK-NEXT: local.get $temp1
13071331
;; CHECK-NEXT: i32.add
13081332
;; CHECK-NEXT: )
1309-
(func $local-to-stack-overlapping-multi-8-no (param $x i32) (result i32)
1333+
(func $local-to-stack-overlapping-multi-8 (param $x i32) (result i32)
13101334
(local $temp1 i32)
13111335
(local $temp2 i32)
13121336
(local.set $temp1 (call $local-to-stack-multi-4 (i32.const 1)))
13131337
(local.set $temp2 (call $local-to-stack-multi-4 (i32.const 1)))
13141338
(drop (call $local-to-stack-multi-4 (i32.const 3)))
1315-
(i32.add
1316-
(local.get $temp2) ;; the timing
1317-
(local.get $temp1) ;; it sucks
1339+
(i32.add ;; the operands are flipped, but the add is commutative
1340+
(local.get $temp2)
1341+
(local.get $temp1)
13181342
)
13191343
)
13201344

@@ -1459,4 +1483,92 @@
14591483
)
14601484
)
14611485
)
1486+
1487+
;; A ref.eq with an intervening value: its operands can be swapped.
1488+
;; CHECK: (func $local-to-stack-ref-eq (type $15) (param $x eqref) (result i32)
1489+
;; CHECK-NEXT: (local $temp eqref)
1490+
;; CHECK-NEXT: local.get $x
1491+
;; CHECK-NEXT: call $eqref-value
1492+
;; CHECK-NEXT: local.get $x
1493+
;; CHECK-NEXT: call $eqref-value
1494+
;; CHECK-NEXT: drop
1495+
;; CHECK-NEXT: local.get $x
1496+
;; CHECK-NEXT: ref.eq
1497+
;; CHECK-NEXT: )
1498+
(func $local-to-stack-ref-eq (param $x eqref) (result i32)
1499+
(local $temp eqref)
1500+
(local.set $temp (call $eqref-value (local.get $x)))
1501+
(drop (call $eqref-value (local.get $x)))
1502+
(ref.eq
1503+
(local.get $x)
1504+
(local.get $temp)
1505+
)
1506+
)
1507+
1508+
;; CHECK: (func $eqref-value (type $16) (param $x eqref) (result eqref)
1509+
;; CHECK-NEXT: local.get $x
1510+
;; CHECK-NEXT: call $eqref-value
1511+
;; CHECK-NEXT: )
1512+
(func $eqref-value (param $x eqref) (result eqref)
1513+
(call $eqref-value (local.get $x))
1514+
)
1515+
1516+
;; f32.min has swappable operands, so the set/get pair can be removed.
1517+
;; CHECK: (func $local-to-stack-f32-min (type $10) (param $x f32) (result f32)
1518+
;; CHECK-NEXT: (local $temp f32)
1519+
;; CHECK-NEXT: local.get $x
1520+
;; CHECK-NEXT: call $float-value
1521+
;; CHECK-NEXT: local.get $x
1522+
;; CHECK-NEXT: call $float-value
1523+
;; CHECK-NEXT: drop
1524+
;; CHECK-NEXT: local.get $x
1525+
;; CHECK-NEXT: f32.min
1526+
;; CHECK-NEXT: )
1527+
(func $local-to-stack-f32-min (param $x f32) (result f32)
1528+
(local $temp f32)
1529+
(local.set $temp (call $float-value (local.get $x)))
1530+
(drop (call $float-value (local.get $x)))
1531+
(f32.min
1532+
(local.get $x)
1533+
(local.get $temp)
1534+
)
1535+
)
1536+
1537+
;; CHECK: (func $float-value (type $10) (param $x f32) (result f32)
1538+
;; CHECK-NEXT: local.get $x
1539+
;; CHECK-NEXT: call $float-value
1540+
;; CHECK-NEXT: )
1541+
(func $float-value (param $x f32) (result f32)
1542+
(call $float-value (local.get $x))
1543+
)
1544+
1545+
;; Two values are in the way, so the set/get pair must stay.
1546+
;; CHECK: (func $local-to-stack-two-in-the-way (type $6) (param $x i32) (result i32)
1547+
;; CHECK-NEXT: (local $temp i32)
1548+
;; CHECK-NEXT: i32.const 1
1549+
;; CHECK-NEXT: call $local-to-stack
1550+
;; CHECK-NEXT: local.set $temp
1551+
;; CHECK-NEXT: i32.const 2
1552+
;; CHECK-NEXT: call $local-to-stack
1553+
;; CHECK-NEXT: drop
1554+
;; CHECK-NEXT: i32.const 3
1555+
;; CHECK-NEXT: call $local-to-stack
1556+
;; CHECK-NEXT: i32.const 4
1557+
;; CHECK-NEXT: call $local-to-stack
1558+
;; CHECK-NEXT: local.get $temp
1559+
;; CHECK-NEXT: i32.add
1560+
;; CHECK-NEXT: i32.add
1561+
;; CHECK-NEXT: )
1562+
(func $local-to-stack-two-in-the-way (param $x i32) (result i32)
1563+
(local $temp i32)
1564+
(local.set $temp (call $local-to-stack (i32.const 1)))
1565+
(drop (call $local-to-stack (i32.const 2)))
1566+
(i32.add
1567+
(call $local-to-stack (i32.const 3))
1568+
(i32.add
1569+
(call $local-to-stack (i32.const 4))
1570+
(local.get $temp)
1571+
)
1572+
)
1573+
)
14621574
)

0 commit comments

Comments
 (0)