summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/Heap2Local.cpp44
-rw-r--r--test/lit/passes/heap2local.wast318
2 files changed, 352 insertions, 10 deletions
diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp
index 519c45fdc..39c77bec4 100644
--- a/src/passes/Heap2Local.cpp
+++ b/src/passes/Heap2Local.cpp
@@ -349,6 +349,12 @@ struct EscapeAnalyzer {
void visitLocalSet(LocalSet* curr) { escapes = false; }
// Reference operations. TODO add more
+ void visitRefEq(RefEq* curr) {
+ // The reference is compared for identity, but nothing more.
+ escapes = false;
+ fullyConsumes = true;
+ }
+
void visitRefAs(RefAs* curr) {
// TODO General OptimizeInstructions integration, that is, since we know
// that our allocation is what flows into this RefAs, we can
@@ -507,14 +513,18 @@ struct EscapeAnalyzer {
// efficient, but it would need to be more complex.
struct Struct2Local : PostWalker<Struct2Local> {
StructNew* allocation;
- const EscapeAnalyzer& analyzer;
+
+ // The analyzer is not |const| because we update |analyzer.reached| as we go
+ // (see replaceCurrent, below).
+ EscapeAnalyzer& analyzer;
+
Function* func;
Module& wasm;
Builder builder;
const FieldList& fields;
Struct2Local(StructNew* allocation,
- const EscapeAnalyzer& analyzer,
+ EscapeAnalyzer& analyzer,
Function* func,
Module& wasm)
: allocation(allocation), analyzer(analyzer), func(func), wasm(wasm),
@@ -539,6 +549,15 @@ struct Struct2Local : PostWalker<Struct2Local> {
// In rare cases we may need to refinalize, see below.
bool refinalize = false;
+ Expression* replaceCurrent(Expression* expression) {
+ PostWalker<Struct2Local>::replaceCurrent(expression);
+ // Also update |reached|: we are replacing something that was reached, so
+ // logically the replacement is also reached. This update is necessary if
+ // the parent of an expression cares about whether a child was reached.
+ analyzer.reached.insert(expression);
+ return expression;
+ }
+
// Rewrite the code in visit* methods. The general approach taken is to
// replace the allocation with a null reference (which may require changing
// types in some places, like making a block return value nullable), and to
@@ -688,6 +707,27 @@ struct Struct2Local : PostWalker<Struct2Local> {
replaceCurrent(builder.makeBlock(contents));
}
+ void visitRefEq(RefEq* curr) {
+ if (!analyzer.reached.count(curr)) {
+ return;
+ }
+
+ if (curr->type == Type::unreachable) {
+ // The result does not matter. Leave things as they are (and let DCE
+ // handle it).
+ return;
+ }
+
+ // If our reference is compared to itself, the result is 1. If it is
+ // compared to something else, the result must be 0, as our reference does
+ // not escape to any other place.
+ int32_t result = analyzer.reached.count(curr->left) > 0 &&
+ analyzer.reached.count(curr->right) > 0;
+ // For simplicity, simply drop the RefEq and put a constant result after.
+ replaceCurrent(builder.makeSequence(builder.makeDrop(curr),
+ builder.makeConst(Literal(result))));
+ }
+
void visitRefAs(RefAs* curr) {
if (!analyzer.reached.count(curr)) {
return;
diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast
index 80eb9bd26..bd613e18c 100644
--- a/test/lit/passes/heap2local.wast
+++ b/test/lit/passes/heap2local.wast
@@ -13,9 +13,9 @@
;; CHECK: (type $struct.recursive (struct (field (mut (ref null $struct.recursive)))))
- ;; CHECK: (type $4 (func (param (ref null $struct.A))))
+ ;; CHECK: (type $4 (func (result i32)))
- ;; CHECK: (type $5 (func (result i32)))
+ ;; CHECK: (type $5 (func (param (ref null $struct.A))))
;; CHECK: (type $6 (func (result anyref)))
@@ -36,6 +36,10 @@
;; CHECK: (type $11 (func (param i32)))
+ ;; CHECK: (type $12 (func (param eqref) (result i32)))
+
+ ;; CHECK: (type $13 (func (param eqref eqref) (result i32)))
+
;; CHECK: (func $simple (type $1)
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (local $1 f64)
@@ -474,7 +478,7 @@
)
)
- ;; CHECK: (func $send-ref (type $4) (param $0 (ref null $struct.A))
+ ;; CHECK: (func $send-ref (type $5) (param $0 (ref null $struct.A))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $send-ref (param (ref null $struct.A))
@@ -887,7 +891,7 @@
)
)
- ;; CHECK: (func $tee (type $5) (result i32)
+ ;; CHECK: (func $tee (type $4) (result i32)
;; CHECK-NEXT: (local $ref (ref null $struct.A))
;; CHECK-NEXT: (local $1 i32)
;; CHECK-NEXT: (local $2 f64)
@@ -1786,7 +1790,7 @@
)
)
- ;; CHECK: (func $ref-as-non-null-through-local (type $5) (result i32)
+ ;; CHECK: (func $ref-as-non-null-through-local (type $4) (result i32)
;; CHECK-NEXT: (local $ref (ref null $struct.A))
;; CHECK-NEXT: (local $1 i32)
;; CHECK-NEXT: (local $2 f64)
@@ -1959,7 +1963,7 @@
(local.get $0)
)
- ;; CHECK: (func $to-param (type $4) (param $ref (ref null $struct.A))
+ ;; CHECK: (func $to-param (type $5) (param $ref (ref null $struct.A))
;; CHECK-NEXT: (local $1 i32)
;; CHECK-NEXT: (local $2 f64)
;; CHECK-NEXT: (drop
@@ -2006,7 +2010,7 @@
)
)
- ;; CHECK: (func $to-param-loop (type $4) (param $ref (ref null $struct.A))
+ ;; CHECK: (func $to-param-loop (type $5) (param $ref (ref null $struct.A))
;; CHECK-NEXT: (loop $loop
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct.A 0
@@ -2046,7 +2050,7 @@
)
)
- ;; CHECK: (func $ref-cast (type $5) (result i32)
+ ;; CHECK: (func $ref-cast (type $4) (result i32)
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (local $1 f64)
;; CHECK-NEXT: (local $2 i32)
@@ -2080,6 +2084,304 @@
)
)
)
+
+ ;; CHECK: (func $ref-eq (type $4) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 f64)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local $3 f64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (f64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $ref-eq (result i32)
+ ;; Comparing an allocation to something else results in 0, and we can
+ ;; optimize away the allocation.
+ (ref.eq
+ (struct.new $struct.A
+ (i32.const 0)
+ (f64.const 0)
+ )
+ (ref.null eq)
+ )
+ )
+
+ ;; CHECK: (func $ref-eq-flip (type $12) (param $other eqref) (result i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 f64)
+ ;; CHECK-NEXT: (local $3 i32)
+ ;; CHECK-NEXT: (local $4 f64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (local.get $other)
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $4
+ ;; CHECK-NEXT: (f64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (local.get $4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $ref-eq-flip (param $other eqref) (result i32)
+ ;; As above, but flipped, and compared to a local.
+ (ref.eq
+ (local.get $other)
+ (struct.new $struct.A
+ (i32.const 0)
+ (f64.const 0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-eq-self (type $4) (result i32)
+ ;; CHECK-NEXT: (local $eq eqref)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 f64)
+ ;; CHECK-NEXT: (local $3 i32)
+ ;; CHECK-NEXT: (local $4 f64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $4
+ ;; CHECK-NEXT: (f64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (local.get $4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ (func $ref-eq-self (result i32)
+ (local $eq eqref)
+ ;; Comparing to oneself results in 1, and we can optimize away the
+ ;; allocation.
+ (ref.eq
+ (local.tee $eq
+ (struct.new $struct.A
+ (i32.const 0)
+ (f64.const 0)
+ )
+ )
+ (local.get $eq)
+ )
+ )
+
+ ;; CHECK: (func $ref-eq-unreachable (type $4) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 f64)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local $3 f64)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (f64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $ref-eq-unreachable (result i32)
+ ;; When a child is unreachable, the result does not matter (but we should
+ ;; still emit validating code).
+ (ref.eq
+ (struct.new $struct.A
+ (i32.const 0)
+ (f64.const 0)
+ )
+ (unreachable)
+ )
+ )
+
+ ;; CHECK: (func $ref-eq-unreachable-flipped (type $4) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 f64)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local $3 f64)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (f64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $ref-eq-unreachable-flipped (result i32)
+ ;; As above, but with children flipped.
+ (ref.eq
+ (unreachable)
+ (struct.new $struct.A
+ (i32.const 0)
+ (f64.const 0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref-eq-unrelated (type $13) (param $x eqref) (param $y eqref) (result i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local $3 f64)
+ ;; CHECK-NEXT: (local $4 i32)
+ ;; CHECK-NEXT: (local $5 f64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (local.set $4
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $5
+ ;; CHECK-NEXT: (f64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (local.get $4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (local.get $5)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $ref-eq-unrelated (param $x eqref) (param $y eqref) (result i32)
+ ;; We know nothing about either ref.eq arm, and do nothing, despite
+ ;; another allocation in the function (which ensures we enter the
+ ;; optimization part of the pass; that other allocation can be removed).
+ (drop
+ (struct.new $struct.A
+ (i32.const 0)
+ (f64.const 0)
+ )
+ )
+ (ref.eq
+ (local.get $x)
+ (local.get $y)
+ )
+ )
+
+ ;; CHECK: (func $ref-eq-two (type $4) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 f64)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local $3 f64)
+ ;; CHECK-NEXT: (local $4 i32)
+ ;; CHECK-NEXT: (local $5 f64)
+ ;; CHECK-NEXT: (local $6 i32)
+ ;; CHECK-NEXT: (local $7 f64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (f64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (local.set $6
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $7
+ ;; CHECK-NEXT: (f64.const 2.2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $4
+ ;; CHECK-NEXT: (local.get $6)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $5
+ ;; CHECK-NEXT: (local.get $7)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $ref-eq-two (result i32)
+ ;; Two separate allocations. We can optimize them away, and the result is 0.
+ (ref.eq
+ (struct.new $struct.A
+ (i32.const 0)
+ (f64.const 0)
+ )
+ (struct.new $struct.A
+ (i32.const 1)
+ (f64.const 2.2)
+ )
+ )
+ )
)
(module