diff options
-rw-r--r-- | src/passes/Heap2Local.cpp | 44 | ||||
-rw-r--r-- | test/lit/passes/heap2local.wast | 318 |
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 |