diff options
-rw-r--r-- | src/passes/LocalSubtyping.cpp | 29 | ||||
-rw-r--r-- | test/lit/passes/local-subtyping.wast | 49 |
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) + ) ) |