summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/LocalSubtyping.cpp29
-rw-r--r--test/lit/passes/local-subtyping.wast49
2 files changed, 77 insertions, 1 deletions
diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp
index 19ce2f275..24a2e48e4 100644
--- a/src/passes/LocalSubtyping.cpp
+++ b/src/passes/LocalSubtyping.cpp
@@ -27,6 +27,7 @@
#include <ir/local-graph.h>
#include <ir/utils.h>
#include <pass.h>
+#include <wasm-builder.h>
#include <wasm.h>
namespace wasm {
@@ -178,10 +179,36 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> {
get->type = func->getLocalType(get->index);
}
for (auto* set : FindAll<LocalSet>(func->body).list) {
+ auto newType = func->getLocalType(set->index);
if (set->isTee()) {
- set->type = func->getLocalType(set->index);
+ set->type = newType;
set->finalize();
}
+
+ // If this set was not processed earlier - that is, if it is in
+ // unreachable code - then it may have an incompatible type. That is,
+ // If we saw a reachable set that writes type A, and this set writes
+ // type B, we may have specialized the local type to A, but the value
+ // of type B in this unreachable set is no longer valid to write to
+ // that local. In such a case we must do additional work.
+ if (!Type::isSubType(set->value->type, newType)) {
+ // The type is incompatible. To fix this, replace
+ //
+ // (set (bad-value))
+ //
+ // with
+ //
+ // (set (block
+ // (drop (bad-value))
+ // (unreachable)
+ // ))
+ //
+ // (We cannot just ignore the bad value, as it may contain a break to
+ // a target that is necessary for validation.)
+ Builder builder(*getModule());
+ set->value = builder.makeSequence(builder.makeDrop(set->value),
+ builder.makeUnreachable());
+ }
}
// Also update their parents.
diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast
index c24126e1a..f655eba1e 100644
--- a/test/lit/passes/local-subtyping.wast
+++ b/test/lit/passes/local-subtyping.wast
@@ -284,4 +284,53 @@
;; A get that is not reachable. We must still update its type.
(local.get $temp)
)
+
+ ;; CHECK: (func $incompatible-sets (result i32)
+ ;; CHECK-NEXT: (local $temp (ref null $none_=>_i32))
+ ;; CHECK-NEXT: (local.set $temp
+ ;; CHECK-NEXT: (ref.func $incompatible-sets)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.tee $temp
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.tee $temp
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $incompatible-sets (result i32)
+ (local $temp funcref)
+ ;; Set a value that allows us to specialize the local type.
+ (local.set $temp
+ (ref.func $incompatible-sets)
+ )
+ ;; Make all code unreachable from here.
+ (unreachable)
+ ;; In unreachable code, assign values that are not compatible with the more
+ ;; specific type we will optimize to. Those cannot be left as they are, and
+ ;; will be fixed up so that they validate. (All we need is validation, as
+ ;; their contents do not matter, given they are not reached.)
+ (drop
+ (local.tee $temp
+ (ref.null func)
+ )
+ )
+ (local.set $temp
+ (ref.null func)
+ )
+ (unreachable)
+ )
)