summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/type-updating.cpp67
-rw-r--r--src/ir/type-updating.h9
-rw-r--r--src/passes/SignatureRefining.cpp10
-rw-r--r--test/lit/passes/signature-refining.wast46
4 files changed, 112 insertions, 20 deletions
diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp
index ff154dd2f..b9bbf7a03 100644
--- a/src/ir/type-updating.cpp
+++ b/src/ir/type-updating.cpp
@@ -137,6 +137,26 @@ void GlobalTypeRewriter::update() {
}
void visitExpression(Expression* curr) {
+ // local.get and local.tee are special in that their type is tied to the
+ // type of the local in the function, which is tied to the signature. That
+ // means we must update it based on the signature, and not on the old type
+ // in the local.
+ //
+ // We have already updated function signatures by the time we get here,
+ // which means we can just apply the current local type that we see (there
+ // is no need to call getNew(), which we already did on the function's
+ // signature itself).
+ if (auto* get = curr->dynCast<LocalGet>()) {
+ curr->type = getFunction()->getLocalType(get->index);
+ return;
+ } else if (auto* tee = curr->dynCast<LocalSet>()) {
+ // Rule out a local.set and unreachable code.
+ if (tee->type != Type::none && tee->type != Type::unreachable) {
+ curr->type = getFunction()->getLocalType(tee->index);
+ }
+ return;
+ }
+
// Update the type to the new one.
curr->type = getNew(curr->type);
@@ -172,6 +192,16 @@ void GlobalTypeRewriter::update() {
CodeUpdater updater(oldToNewTypes);
PassRunner runner(&wasm);
+
+ // Update functions first, so that we see the updated types for locals (which
+ // can change if the function signature changes).
+ for (auto& func : wasm.functions) {
+ func->type = updater.getNew(func->type);
+ for (auto& var : func->vars) {
+ var = updater.getNew(var);
+ }
+ }
+
updater.run(&runner, &wasm);
updater.walkModuleCode(&wasm);
@@ -185,12 +215,6 @@ void GlobalTypeRewriter::update() {
for (auto& global : wasm.globals) {
global->type = updater.getNew(global->type);
}
- for (auto& func : wasm.functions) {
- func->type = updater.getNew(func->type);
- for (auto& var : func->vars) {
- var = updater.getNew(var);
- }
- }
for (auto& tag : wasm.tags) {
tag->sig = updater.getNew(tag->sig);
}
@@ -323,7 +347,8 @@ Expression* fixLocalGet(LocalGet* get, Module& wasm) {
void updateParamTypes(Function* func,
const std::vector<Type>& newParamTypes,
- Module& wasm) {
+ Module& wasm,
+ LocalUpdatingMode localUpdating) {
// Before making this update, we must be careful if the param was "reused",
// specifically, if it is assigned a less-specific type in the body then
// we'd get a validation error when we refine it. To handle that, if a less-
@@ -369,7 +394,11 @@ void updateParamTypes(Function* func,
if (iter != paramFixups.end()) {
auto fixup = iter->second;
contents.push_back(builder.makeLocalSet(
- fixup, builder.makeLocalGet(index, newParamTypes[index])));
+ fixup,
+ builder.makeLocalGet(index,
+ localUpdating == Update
+ ? newParamTypes[index]
+ : func->getLocalType(index))));
}
}
contents.push_back(func->body);
@@ -391,17 +420,19 @@ void updateParamTypes(Function* func,
}
// Update local.get/local.tee operations that use the modified param type.
- for (auto* get : gets.list) {
- auto index = get->index;
- if (func->isParam(index)) {
- get->type = newParamTypes[index];
+ if (localUpdating == Update) {
+ for (auto* get : gets.list) {
+ auto index = get->index;
+ if (func->isParam(index)) {
+ get->type = newParamTypes[index];
+ }
}
- }
- for (auto* set : sets.list) {
- auto index = set->index;
- if (func->isParam(index) && set->isTee()) {
- set->type = newParamTypes[index];
- set->finalize();
+ for (auto* set : sets.list) {
+ auto index = set->index;
+ if (func->isParam(index) && set->isTee()) {
+ set->type = newParamTypes[index];
+ set->finalize();
+ }
}
}
diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h
index d1c4af6bc..f6df449ae 100644
--- a/src/ir/type-updating.h
+++ b/src/ir/type-updating.h
@@ -415,9 +415,16 @@ Expression* fixLocalGet(LocalGet* get, Module& wasm);
// function does are to update the types of local.get/tee operations,
// refinalize, etc., basically all operations necessary to ensure validation
// with the new types.
+//
+// While doing so, we can either update or not update the types of local.get and
+// local.tee operations. (We do not update them here if we'll be doing an update
+// later in the caller, which is the case if we are rewriting function types).
+enum LocalUpdatingMode { Update, DoNotUpdate };
+
void updateParamTypes(Function* func,
const std::vector<Type>& newParamTypes,
- Module& wasm);
+ Module& wasm,
+ LocalUpdatingMode localUpdating = Update);
} // namespace TypeUpdating
diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp
index 2c2146c3f..bde1c4fb9 100644
--- a/src/passes/SignatureRefining.cpp
+++ b/src/passes/SignatureRefining.cpp
@@ -243,7 +243,15 @@ struct SignatureRefining : public Pass {
for (auto param : iter->second.params) {
newParamsTypes.push_back(param);
}
- TypeUpdating::updateParamTypes(func, newParamsTypes, wasm);
+ // Do not update local.get/local.tee here, as we will do so in
+ // GlobalTypeRewriter::updateSignatures, below. (Doing an update here
+ // would leave the IR in an inconsistent state of a partial update;
+ // instead, do the full update at the end.)
+ TypeUpdating::updateParamTypes(
+ func,
+ newParamsTypes,
+ wasm,
+ TypeUpdating::LocalUpdatingMode::DoNotUpdate);
}
}
};
diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast
index 19558d997..8e5d3b75e 100644
--- a/test/lit/passes/signature-refining.wast
+++ b/test/lit/passes/signature-refining.wast
@@ -674,3 +674,49 @@
(nop)
)
)
+
+(module
+ ;; CHECK: (type $ref|${}|_i32_=>_none (func_subtype (param (ref ${}) i32) func))
+
+ ;; CHECK: (type ${} (struct_subtype data))
+ (type ${} (struct_subtype data))
+
+ ;; CHECK: (func $foo (type $ref|${}|_i32_=>_none) (param $ref (ref ${})) (param $i32 i32)
+ ;; CHECK-NEXT: (local $2 eqref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (ref.null eq)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (param $ref eqref) (param $i32 i32)
+ (call $foo
+ ;; The only reference to the ${} type is in this block signature. Even
+ ;; this will go away in the internal ReFinalize (which makes the block
+ ;; type unreachable).
+ (block (result (ref ${}))
+ (unreachable)
+ )
+ (i32.const 0)
+ )
+ ;; Write something of type eqref into $ref. When we refine the type of the
+ ;; parameter from eqref to ${} we must do something here, as we can no
+ ;; longer just write this (ref.null eq) into a parameter of the more
+ ;; refined type. While doing so, we must not be confused by the fact that
+ ;; the only mention of ${} in the original module gets removed during our
+ ;; processing, as mentioned in the earlier comment. This is a regression
+ ;; test for a crash because of that.
+ (local.set $ref
+ (ref.null eq)
+ )
+ )
+)