summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/ordering.h10
-rw-r--r--src/passes/GlobalTypeOptimization.cpp18
-rw-r--r--test/lit/passes/gto-removals.wast142
3 files changed, 142 insertions, 28 deletions
diff --git a/src/ir/ordering.h b/src/ir/ordering.h
index 55163759c..ed2c00ee2 100644
--- a/src/ir/ordering.h
+++ b/src/ir/ordering.h
@@ -34,11 +34,11 @@ namespace wasm {
//
// (temp = first, second, temp)
//
-Expression* getResultOfFirst(Expression* first,
- Expression* second,
- Function* func,
- Module* wasm,
- const PassOptions& passOptions) {
+inline Expression* getResultOfFirst(Expression* first,
+ Expression* second,
+ Function* func,
+ Module* wasm,
+ const PassOptions& passOptions) {
assert(first->type.isConcrete());
Builder builder(*wasm);
diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp
index 865fe8f69..2c0799874 100644
--- a/src/passes/GlobalTypeOptimization.cpp
+++ b/src/passes/GlobalTypeOptimization.cpp
@@ -24,6 +24,7 @@
#include "ir/effects.h"
#include "ir/localize.h"
+#include "ir/ordering.h"
#include "ir/struct-utils.h"
#include "ir/subtypes.h"
#include "ir/type-updating.h"
@@ -392,12 +393,19 @@ struct GlobalTypeOptimization : public Pass {
// Map to the new index.
curr->index = newIndex;
} else {
- // This field was removed, so just emit drops of our children (plus a
- // trap if the input is null).
+ // This field was removed, so just emit drops of our children, plus a
+ // trap if the ref is null. Note that we must preserve the order of
+ // operations here: the trap on a null ref happens after the value,
+ // which might have side effects.
Builder builder(*getModule());
- replaceCurrent(builder.makeSequence(
- builder.makeDrop(builder.makeRefAs(RefAsNonNull, curr->ref)),
- builder.makeDrop(curr->value)));
+ auto flipped = getResultOfFirst(curr->ref,
+ builder.makeDrop(curr->value),
+ getFunction(),
+ getModule(),
+ getPassOptions());
+ replaceCurrent(
+ builder.makeDrop(builder.makeRefAs(RefAsNonNull, flipped)));
+ addedLocals = true;
}
}
diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast
index f3cbae2aa..41c7fc950 100644
--- a/test/lit/passes/gto-removals.wast
+++ b/test/lit/passes/gto-removals.wast
@@ -22,20 +22,22 @@
(module
;; A write does not keep a field from being removed.
- ;; CHECK: (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
-
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct_subtype (field (mut funcref)) data))
+ ;; CHECK: (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
+
;; CHECK: (func $func (type $ref|$struct|_=>_none) (param $x (ref $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (block (result (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.null func)
- ;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func (param $x (ref $struct))
;; The fields of this set will be dropped, as we do not need to perform
@@ -140,15 +142,15 @@
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (block (result (ref $mut-struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $mut-struct $rw
;; CHECK-NEXT: (local.get $x)
@@ -164,15 +166,15 @@
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (block (result (ref $mut-struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (i32.const 2)
- ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $mut-struct $rw-2
;; CHECK-NEXT: (local.get $x)
@@ -798,3 +800,107 @@
(drop (struct.get $child1 0 (local.get $child1)))
)
)
+
+(module
+ ;; CHECK: (type ${mut:i8} (struct_subtype data))
+ (type ${mut:i8} (struct_subtype (field (mut i8)) data))
+
+ ;; CHECK: (type $none_=>_none (func_subtype func))
+
+ ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func))
+
+ ;; CHECK: (type $none_=>_ref|${mut:i8}| (func_subtype (result (ref ${mut:i8})) func))
+
+ ;; CHECK: (func $unreachable-set (type $none_=>_none)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (block (result (ref null ${mut:i8}))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $helper-i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null ${mut:i8})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreachable-set
+ ;; The struct type has no reads, so we want to remove all of the sets of it.
+ ;; This struct.set will trap on null, but first the call must run. When we
+ ;; optimize here we should be careful to not emit something with different
+ ;; ordering (naively emitting ref.as_non_null on the reference would trap
+ ;; before the call, so we must reorder).
+ (struct.set ${mut:i8} 0
+ (ref.null ${mut:i8})
+ (call $helper-i32)
+ )
+ )
+
+ ;; CHECK: (func $unreachable-set-2 (type $none_=>_none)
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (block (result (ref null ${mut:i8}))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (br $block)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null ${mut:i8})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreachable-set-2
+ ;; As above, but the side effects now are a br. Again, the br must happen
+ ;; before the trap (in fact, the br will skip the trap here).
+ (block
+ (struct.set ${mut:i8} 0
+ (ref.null ${mut:i8})
+ (br $block)
+ )
+ )
+ )
+
+ ;; CHECK: (func $unreachable-set-3 (type $none_=>_none)
+ ;; CHECK-NEXT: (local $0 (ref null ${mut:i8}))
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (block (result (ref ${mut:i8}))
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (call $helper-ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $helper-i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreachable-set-3
+ ;; As above, but now we have side effects in both children.
+ (block
+ (struct.set ${mut:i8} 0
+ (call $helper-ref)
+ (call $helper-i32)
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper-i32 (type $none_=>_i32) (result i32)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ (func $helper-i32 (result i32)
+ (i32.const 1)
+ )
+
+ ;; CHECK: (func $helper-ref (type $none_=>_ref|${mut:i8}|) (result (ref ${mut:i8}))
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $helper-ref (result (ref ${mut:i8}))
+ (unreachable)
+ )
+)