diff options
-rw-r--r-- | src/ir/type-updating.cpp | 67 | ||||
-rw-r--r-- | src/ir/type-updating.h | 9 | ||||
-rw-r--r-- | src/passes/SignatureRefining.cpp | 10 | ||||
-rw-r--r-- | test/lit/passes/signature-refining.wast | 46 |
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) + ) + ) +) |