diff options
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/SignatureRefining.cpp | 207 | ||||
-rw-r--r-- | src/passes/pass.cpp | 4 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | test/lit/help/optimization-opts.test | 3 | ||||
-rw-r--r-- | test/lit/passes/signature-refining.wast | 492 |
6 files changed, 708 insertions, 0 deletions
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 3313637ba..c3f42d41f 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -68,6 +68,7 @@ set(passes_SOURCES RoundTrip.cpp SetGlobals.cpp StackIR.cpp + SignatureRefining.cpp Strip.cpp StripTargetFeatures.cpp RedundantSetElimination.cpp diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp new file mode 100644 index 000000000..8e8ecfbe2 --- /dev/null +++ b/src/passes/SignatureRefining.cpp @@ -0,0 +1,207 @@ +/* + * Copyright 2021 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Apply more specific subtypes to signature/function types where possible. +// +// This differs from DeadArgumentElimination's refineArgumentTypes() etc. in +// that DAE will modify the type of a function. It can only do that if the +// function's type is not observable, which means it is not taken by reference. +// On the other hand, this pass will modify the signature types themselves, +// which means it can optimize functions whose reference is taken, and it does +// so while considering all users of the type (across all functions sharing that +// type, and all call_refs using it). +// +// TODO: optimize results too and not just params. +// + +#include "ir/find_all.h" +#include "ir/lubs.h" +#include "ir/module-utils.h" +#include "ir/type-updating.h" +#include "pass.h" +#include "wasm-type.h" +#include "wasm.h" + +using namespace std; + +namespace wasm { + +namespace { + +struct SignatureRefining : public Pass { + // Maps each heap type to the possible refinement of the types in their + // signatures. We will fill this during analysis and then use it while doing + // an update of the types. If a type has no improvement that we can find, it + // will not appear in this map. + std::unordered_map<HeapType, Signature> newSignatures; + + void run(PassRunner* runner, Module* module) override { + if (getTypeSystem() != TypeSystem::Nominal) { + Fatal() << "SignatureRefining requires nominal typing"; + } + + if (!module->tables.empty()) { + // When there are tables we must also take their types into account, which + // would require us to take call_indirect, element segments, etc. into + // account. For now, do nothing if there are tables. + // TODO + return; + } + + // First, find all the calls and call_refs. + + struct CallInfo { + std::vector<Call*> calls; + std::vector<CallRef*> callRefs; + }; + + ModuleUtils::ParallelFunctionAnalysis<CallInfo> analysis( + *module, [&](Function* func, CallInfo& info) { + if (func->imported()) { + return; + } + info.calls = std::move(FindAll<Call>(func->body).list); + info.callRefs = std::move(FindAll<CallRef>(func->body).list); + }); + + // A map of types to the calls and call_refs that use that type. + std::unordered_map<HeapType, CallInfo> allCallsTo; + + // Combine all the information we gathered into that map. + for (auto& [func, info] : analysis.map) { + // For direct calls, add each call to the type of the function being + // called. + for (auto* call : info.calls) { + allCallsTo[module->getFunction(call->target)->type].calls.push_back( + call); + } + + // For indirect calls, add each call_ref to the type the call_ref uses. + for (auto* callRef : info.callRefs) { + auto calledType = callRef->target->type; + if (calledType != Type::unreachable) { + allCallsTo[calledType.getHeapType()].callRefs.push_back(callRef); + } + } + } + + // Compute optimal LUBs. + std::unordered_set<HeapType> seen; + for (auto& func : module->functions) { + auto type = func->type; + if (!seen.insert(type).second) { + continue; + } + + auto sig = type.getSignature(); + + auto numParams = sig.params.size(); + std::vector<LUBFinder> paramLUBs(numParams); + + auto updateLUBs = [&](const ExpressionList& operands) { + for (Index i = 0; i < numParams; i++) { + paramLUBs[i].noteUpdatableExpression(operands[i]); + } + }; + + auto& callsTo = allCallsTo[type]; + for (auto* call : callsTo.calls) { + updateLUBs(call->operands); + } + for (auto* callRef : callsTo.callRefs) { + updateLUBs(callRef->operands); + } + + // Find the final LUBs, and see if we found an improvement. + std::vector<Type> newParamsTypes; + for (auto& lub : paramLUBs) { + if (!lub.noted()) { + break; + } + newParamsTypes.push_back(lub.getBestPossible()); + } + if (newParamsTypes.size() < numParams) { + // We did not have type information to calculate a LUB (no calls, or + // some param is always unreachable), so there is nothing we can improve + // here. Other passes might remove the type entirely. + continue; + } + auto newParams = Type(newParamsTypes); + if (newParams != func->getParams()) { + // We found an improvement! + newSignatures[type] = Signature(newParams, Type::none); + for (auto& lub : paramLUBs) { + lub.updateNulls(); + } + } + } + + if (newSignatures.empty()) { + // We found nothing to optimize. + return; + } + + // Update function contents for their new parameter types. + struct CodeUpdater : public WalkerPass<PostWalker<CodeUpdater>> { + bool isFunctionParallel() override { return true; } + + SignatureRefining& parent; + Module& wasm; + + CodeUpdater(SignatureRefining& parent, Module& wasm) + : parent(parent), wasm(wasm) {} + + CodeUpdater* create() override { return new CodeUpdater(parent, wasm); } + + void doWalkFunction(Function* func) { + auto iter = parent.newSignatures.find(func->type); + if (iter != parent.newSignatures.end()) { + std::vector<Type> newParamsTypes; + for (auto param : iter->second.params) { + newParamsTypes.push_back(param); + } + TypeUpdating::updateParamTypes(func, newParamsTypes, wasm); + } + } + }; + CodeUpdater(*this, *module).run(runner, module); + + // Rewrite the types. + class TypeRewriter : public GlobalTypeRewriter { + SignatureRefining& parent; + + public: + TypeRewriter(Module& wasm, SignatureRefining& parent) + : GlobalTypeRewriter(wasm), parent(parent) {} + + void modifySignature(HeapType oldSignatureType, Signature& sig) override { + auto iter = parent.newSignatures.find(oldSignatureType); + if (iter != parent.newSignatures.end()) { + sig.params = getTempType(iter->second.params); + } + } + }; + + TypeRewriter(*module, *this).update(); + } +}; + +} // anonymous namespace + +Pass* createSignatureRefiningPass() { return new SignatureRefining(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index d20a5edcd..92206deff 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -342,6 +342,9 @@ void PassRegistry::registerPasses() { registerPass("set-globals", "sets specified globals to specified values", createSetGlobalsPass); + registerPass("signature-refining", + "apply more specific subtypes to signature types where possible", + createSignatureRefiningPass); registerPass("simplify-globals", "miscellaneous globals-related optimizations", createSimplifyGlobalsPass); @@ -530,6 +533,7 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() { if (wasm->features.hasGC() && getTypeSystem() == TypeSystem::Nominal && options.optimizeLevel >= 2) { addIfNoDWARFIssues("type-refining"); + addIfNoDWARFIssues("signature-refining"); addIfNoDWARFIssues("global-refining"); // Global type optimization can remove fields that are not needed, which can // remove ref.funcs that were once assigned to vtables but are no longer diff --git a/src/passes/passes.h b/src/passes/passes.h index 5a67b1d82..8a2c4cacc 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -112,6 +112,7 @@ Pass* createRedundantSetEliminationPass(); Pass* createRoundTripPass(); Pass* createSafeHeapPass(); Pass* createSetGlobalsPass(); +Pass* createSignatureRefiningPass(); Pass* createSimplifyLocalsPass(); Pass* createSimplifyGlobalsPass(); Pass* createSimplifyGlobalsOptimizingPass(); diff --git a/test/lit/help/optimization-opts.test b/test/lit/help/optimization-opts.test index 6667c45bf..53c74605f 100644 --- a/test/lit/help/optimization-opts.test +++ b/test/lit/help/optimization-opts.test @@ -458,6 +458,9 @@ ;; CHECK-NEXT: --set-globals sets specified globals to ;; CHECK-NEXT: specified values ;; CHECK-NEXT: +;; CHECK-NEXT: --signature-refining apply more specific subtypes to +;; CHECK-NEXT: signature types where possible +;; CHECK-NEXT: ;; CHECK-NEXT: --simplify-globals miscellaneous globals-related ;; CHECK-NEXT: optimizations ;; CHECK-NEXT: diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast new file mode 100644 index 000000000..71eec54b2 --- /dev/null +++ b/test/lit/passes/signature-refining.wast @@ -0,0 +1,492 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --nominal --signature-refining -all -S -o - | filecheck %s + +(module + ;; $func is defined with an anyref parameter but always called with a $struct, + ;; and we can specialize the heap type to that. That will both update the + ;; heap type's definition as well as the types of the parameters as printed + ;; on the function (which are derived from the heap type). + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct) + ) + ) +) + +(module + ;; As above, but the call is via call_ref. + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call_ref + (struct.new $struct) + (ref.func $func) + ) + ) +) + +(module + ;; A combination of call types, and the LUB is affected by all of them: one + ;; call uses a nullable $struct, the other a non-nullable dataref, so the LUB + ;; is a nullable dataref. + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref null data)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x (ref null data)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (ref.as_data + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (local $struct (ref null $struct)) + (call $func + ;; Use a local to avoid a ref.null being updated. + (local.get $struct) + ) + (call_ref + (ref.as_data + (struct.new $struct) + ) + (ref.func $func) + ) + ) +) + +(module + ;; Multiple functions with the same heap type. Again, the LUB is in the + ;; middle, this time the parent $struct and not a subtype. + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct-sub1 (struct_subtype $struct)) + (type $struct-sub1 (struct_subtype $struct)) + + ;; CHECK: (type $struct-sub2 (struct_subtype $struct)) + (type $struct-sub2 (struct_subtype $struct)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (func $func-1 (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-1 (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $func-2 (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-2 (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func-1 + ;; CHECK-NEXT: (struct.new_default $struct-sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $func-2 + ;; CHECK-NEXT: (struct.new_default $struct-sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func-1 + (struct.new $struct-sub1) + ) + (call $func-2 + (struct.new $struct-sub2) + ) + ) +) + +(module + ;; As above, but now only one of the functions is called. The other is still + ;; updated, though, as they share a heap type. + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func-1 (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-1 (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $func-2 (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-2 (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func-1 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func-1 + (struct.new $struct) + ) + ) +) + +(module + ;; Define a field in the struct of the signature type that will be updated, + ;; to check for proper validation after the update. + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $struct (struct_subtype (field (ref $sig)) data)) + (type $struct (struct_subtype (field (ref $sig)) data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (local $temp (ref null $sig)) + ;; CHECK-NEXT: (local $2 anyref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ;; Define a local of the signature type that is updated. + (local $temp (ref null $sig)) + ;; Do a local.get of the param, to verify its type is valid. + (drop + (local.get $x) + ) + ;; Copy between the param and the local, to verify their types are still + ;; compatible after the update. Note that we will need to add a fixup local + ;; here, as $x's new type becomes too specific to be assigned the value + ;; here. + (local.set $x + (local.get $temp) + ) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct + (ref.func $func) + ) + ) + ) +) + +(module + ;; An unreachable value does not prevent optimization: we will update the + ;; param to be $struct. + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct) + ) + (call_ref + (unreachable) + (ref.func $func) + ) + ) +) + +(module + ;; When we have only unreachable values, there is nothing to optimize, and we + ;; should not crash. + + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param anyref) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call_ref + (unreachable) + (ref.func $func) + ) + ) +) + +(module + ;; When we have no calls, there is nothing to optimize, and we should not + ;; crash. + + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param anyref) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (func $func (type $sig) (param $x anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) +) + +(module + ;; Test multiple fields in multiple types. + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig-1 (func_subtype (param (ref null data) anyref) func)) + (type $sig-1 (func_subtype (param anyref) (param anyref) func)) + ;; CHECK: (type $sig-2 (func_subtype (param anyref (ref $struct)) func)) + (type $sig-2 (func_subtype (param anyref) (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func-2) + + ;; CHECK: (func $func-1 (type $sig-1) (param $x (ref null data)) (param $y anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-1 (type $sig-1) (param $x anyref) (param $y anyref) + ) + + ;; CHECK: (func $func-2 (type $sig-2) (param $x anyref) (param $y (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-2 (type $sig-2) (param $x anyref) (param $y anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (local $any anyref) + ;; CHECK-NEXT: (local $data (ref null data)) + ;; CHECK-NEXT: (local $func funcref) + ;; CHECK-NEXT: (call $func-1 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (local.get $data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $func-1 + ;; CHECK-NEXT: (local.get $data) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $func-2 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (local.get $func) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (ref.func $func-2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (local $any (ref null any)) + (local $data (ref null data)) + (local $func (ref null func)) + + (call $func-1 + (struct.new $struct) + (local.get $data) + ) + (call $func-1 + (local.get $data) + (local.get $any) + ) + (call $func-2 + (struct.new $struct) + (struct.new $struct) + ) + (call_ref + (local.get $func) + (struct.new $struct) + (ref.func $func-2) + ) + ) +) + +(module + ;; The presence of a table prevents us from doing any optimizations. + + ;; CHECK: (type $sig (func_subtype (param anyref) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + (table 1 1 anyref) + + ;; CHECK: (table $0 1 1 anyref) + + ;; CHECK: (func $func (type $sig) (param $x anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct) + ) + ) +) + +(module + ;; Pass a null in one call to the function. The null can be updated which + ;; allows us to refine (but the new type must be nullable). + + ;; CHECK: (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref null $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + (type $struct (struct_subtype data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $sig) (param $x (ref null $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct) + ) + (call $func + (ref.null data) + ) + ) +) |