summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/SignatureRefining.cpp207
-rw-r--r--src/passes/pass.cpp4
-rw-r--r--src/passes/passes.h1
-rw-r--r--test/lit/help/optimization-opts.test3
-rw-r--r--test/lit/passes/signature-refining.wast492
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)
+ )
+ )
+)