summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2022-05-31 12:29:36 -0700
committerGitHub <noreply@github.com>2022-05-31 12:29:36 -0700
commit4d0646a6ea6d0104d8a5997f1d607d91e0a661a2 (patch)
treee7fe83c1f13f0607a6cd49f120d280c768c83299
parent07d90673f1765c3ddff03f7748caefe9a7d14ffa (diff)
downloadbinaryen-4d0646a6ea6d0104d8a5997f1d607d91e0a661a2.tar.gz
binaryen-4d0646a6ea6d0104d8a5997f1d607d91e0a661a2.tar.bz2
binaryen-4d0646a6ea6d0104d8a5997f1d607d91e0a661a2.zip
[WasmGC] Fix non-nullable locals and try-catch in SimplifyLocals (#4703)
Binaryen will not change dominance in SimplifyLocals, however, the current spec's notion of dominance is simpler than ours, and we must not optimize certain cases in order to still validate. See details in the comment and test. Helps #4702
-rw-r--r--src/passes/SimplifyLocals.cpp41
-rw-r--r--test/lit/passes/simplify-locals-gc-nn.wast90
2 files changed, 128 insertions, 3 deletions
diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp
index 35980f503..5cf3b8241 100644
--- a/src/passes/SimplifyLocals.cpp
+++ b/src/passes/SimplifyLocals.cpp
@@ -299,12 +299,47 @@ struct SimplifyLocals
Expression** currp) {
Expression* curr = *currp;
- // Expressions that may throw cannot be sinked into 'try'. At the start of
- // 'try', we drop all sinkables that may throw.
+ // Certain expressions cannot be sinked into 'try', and so at the start of
+ // 'try' we forget about them.
if (curr->is<Try>()) {
std::vector<Index> invalidated;
for (auto& [index, info] : self->sinkables) {
- if (info.effects.throws()) {
+ // Expressions that may throw cannot be moved into a try (which might
+ // catch them, unlike before the move).
+ //
+ // Also, non-nullable local.sets cannot be moved into a try, as that may
+ // change dominance from the perspective of the spec
+ //
+ // (local.set $x X)
+ // (try
+ // ..
+ // (Y
+ // (local.get $x))
+ // (catch
+ // (Z
+ // (local.get $x)))
+ //
+ // =>
+ //
+ // (try
+ // ..
+ // (Y
+ // (local.tee $x X))
+ // (catch
+ // (Z
+ // (local.get $x)))
+ //
+ // After sinking the set, the tee does not dominate the get in the
+ // catch, at least not in the simple way the spec defines it, see
+ // https://github.com/WebAssembly/function-references/issues/44#issuecomment-1083146887
+ // We have more refined information about control flow and dominance
+ // than the spec, and so we would see if ".." can throw or not (only if
+ // it can throw is there a branch to the catch, which can change
+ // dominance). To stay compliant with the spec, however, we must not
+ // move code regardless of whether ".." can throw - we must simply keep
+ // the set outside of the try.
+ if (info.effects.throws() ||
+ self->getFunction()->getLocalType(index).isNonNullable()) {
invalidated.push_back(index);
}
}
diff --git a/test/lit/passes/simplify-locals-gc-nn.wast b/test/lit/passes/simplify-locals-gc-nn.wast
new file mode 100644
index 000000000..eec97a4d4
--- /dev/null
+++ b/test/lit/passes/simplify-locals-gc-nn.wast
@@ -0,0 +1,90 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --simplify-locals -all --enable-gc-nn-locals -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (func $test-nn
+ ;; CHECK-NEXT: (local $nn (ref any))
+ ;; CHECK-NEXT: (local.set $nn
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test-nn
+ (local $nn (ref any))
+ ;; We should not sink this set into the try, as the spec does not allow it
+ ;; even though we are not changing dominance (we are not changing it,
+ ;; because there is nothing that can throw in the try's body that can reach
+ ;; the catch_all before the local.set that we move there). See
+ ;; https://github.com/WebAssembly/function-references/issues/44#issuecomment-1083146887
+ (local.set $nn
+ (ref.as_non_null
+ (ref.null any)
+ )
+ )
+ (try
+ (do
+ (drop
+ (local.get $nn)
+ )
+ )
+ (catch_all
+ (drop
+ (local.get $nn)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $test-nullable
+ ;; CHECK-NEXT: (local $nullable anyref)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (local.set $nullable
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test-nullable
+ ;; As above, but now the local is nullable. Here we *can* optimize the set
+ ;; into the try.
+ (local $nullable (ref null any))
+ (local.set $nullable
+ (ref.as_non_null
+ (ref.null any)
+ )
+ )
+ (try
+ (do
+ (drop
+ (local.get $nullable)
+ )
+ )
+ (catch_all
+ (drop
+ (local.get $nullable)
+ )
+ )
+ )
+ ))