diff --git a/CHANGELOG.md b/CHANGELOG.md index 224563f612a..074acfba2fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ full changeset diff at the end of each section. Current Trunk ------------- + - C and JS APIs now assume RefFuncs are created after imported functions (non- + imported functions can still be created later). This is necessary because + imported function types can vary (due to Custom Descriptors), and we need to + look up that type at RefFunc creation time. - The --mod-asyncify-never-unwind and --mod-asyncify-always-and-only-unwind passed were deleted. They only existed to support the lazy code loading support in emscripten that was removed. (#7893) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 6ddf2cab6fe..5b5fb11b517 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2482,7 +2482,7 @@ def get_random_opts(): # disabled, its dependent features need to be disabled as well. IMPLIED_FEATURE_OPTS = { '--disable-reference-types': ['--disable-gc', '--disable-exception-handling', '--disable-strings'], - '--disable-gc': ['--disable-strings', '--disable-stack-switching'], + '--disable-gc': ['--disable-strings', '--disable-stack-switching', '--disable-custom-descriptors'], } print(''' diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index bc16fe08db3..e418f583c8e 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -157,7 +157,7 @@ Literal fromBinaryenLiteral(BinaryenLiteral x) { } } if (heapType.isSignature()) { - return Literal::makeFunc(Name(x.func), heapType); + return Literal::makeFunc(Name(x.func), type); } assert(heapType.isData()); WASM_UNREACHABLE("TODO: gc data"); @@ -1609,8 +1609,21 @@ BinaryenExpressionRef BinaryenRefAs(BinaryenModuleRef module, BinaryenExpressionRef BinaryenRefFunc(BinaryenModuleRef module, const char* func, BinaryenHeapType type) { - return static_cast( - Builder(*(Module*)module).makeRefFunc(func, HeapType(type))); + // We can assume imports have been created at this point in time, but not + // other defined functions. See if the function exists already, and assume it + // is non-imported if not. TODO: If we want to allow creating imports later, + // we would need an API addition or change. + auto* wasm = (Module*)module; + if (wasm->getFunctionOrNull(func)) { + // Use the HeapType constructor, which will do a lookup on the module. + return static_cast( + Builder(*(Module*)module).makeRefFunc(func, HeapType(type))); + } else { + // Assume non-imported, and provide the full type for that. + Type full = Type(HeapType(type), NonNullable, Exact); + return static_cast( + Builder(*(Module*)module).makeRefFunc(func, full)); + } } BinaryenExpressionRef BinaryenRefEq(BinaryenModuleRef module, diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 8636a7c98cb..ced96c1c357 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -116,9 +116,7 @@ void ReFinalize::visitMemoryGrow(MemoryGrow* curr) { curr->finalize(); } void ReFinalize::visitRefNull(RefNull* curr) { curr->finalize(); } void ReFinalize::visitRefIsNull(RefIsNull* curr) { curr->finalize(); } void ReFinalize::visitRefFunc(RefFunc* curr) { - // TODO: should we look up the function and update the type from there? This - // could handle a change to the function's type, but is also not really what - // this class has been meant to do. + curr->finalize(curr->type.getHeapType(), *getModule()); } void ReFinalize::visitRefEq(RefEq* curr) { curr->finalize(); } void ReFinalize::visitTableGet(TableGet* curr) { curr->finalize(); } diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 380fef8385b..c49e6758a36 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -73,6 +73,7 @@ #include "ir/export-utils.h" #include "ir/module-utils.h" #include "ir/names.h" +#include "ir/utils.h" #include "pass.h" #include "support/insert_ordered.h" #include "wasm-builder.h" @@ -274,7 +275,8 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) { activeBase.index + Index(activeSegment->data.size())}; Builder builder(module); - activeSegment->data.push_back(builder.makeRefFunc(func, type)); + auto funcType = Type(type, NonNullable, Inexact); + activeSegment->data.push_back(builder.makeRefFunc(func, funcType)); addSlot(func, newSlot); if (activeTable->initial <= newSlot.index) { @@ -339,6 +341,7 @@ struct ModuleSplitter { void setupTablePatching(); void shareImportableItems(); void removeUnusedSecondaryElements(); + void updateIR(); ModuleSplitter(Module& primary, const Config& config) : config(config), primary(primary), tableManager(primary), @@ -355,6 +358,7 @@ struct ModuleSplitter { setupTablePatching(); shareImportableItems(); removeUnusedSecondaryElements(); + updateIR(); } }; @@ -372,7 +376,7 @@ void ModuleSplitter::setupJSPI() { // Add an imported function to load the secondary module. auto import = Builder::makeFunction( ModuleSplitting::LOAD_SECONDARY_MODULE, - Type(Signature(Type::none, Type::none), NonNullable, Exact), + Type(Signature(Type::none, Type::none), NonNullable, Inexact), {}); import->module = ENV; import->base = ModuleSplitting::LOAD_SECONDARY_MODULE; @@ -516,6 +520,7 @@ void ModuleSplitter::exportImportFunction(Name funcName, func->hasExplicitName = primaryFunc->hasExplicitName; func->module = config.importNamespace; func->base = exportName; + func->type = func->type.with(Inexact); secondary->addFunction(std::move(func)); } } @@ -790,9 +795,8 @@ void ModuleSplitter::setupTablePatching() { placeholder->name = Names::getValidFunctionName( primary, std::string("placeholder_") + placeholder->base.toString()); placeholder->hasExplicitName = true; - placeholder->type = secondaryFunc->type; - elem = Builder(primary).makeRefFunc(placeholder->name, - placeholder->type.getHeapType()); + placeholder->type = secondaryFunc->type.with(Inexact); + elem = Builder(primary).makeRefFunc(placeholder->name, placeholder->type); primary.addFunction(std::move(placeholder)); }); @@ -833,8 +837,7 @@ void ModuleSplitter::setupTablePatching() { // primarySeg->data[i] is a placeholder, so use the secondary // function. auto* func = replacement->second; - auto* ref = Builder(secondary).makeRefFunc(func->name, - func->type.getHeapType()); + auto* ref = Builder(secondary).makeRefFunc(func->name, func->type); secondaryElems.push_back(ref); ++replacement; } else if (auto* get = primarySeg->data[i]->dynCast()) { @@ -876,7 +879,7 @@ void ModuleSplitter::setupTablePatching() { } auto* func = curr->second; currData.push_back( - Builder(secondary).makeRefFunc(func->name, func->type.getHeapType())); + Builder(secondary).makeRefFunc(func->name, func->type)); } if (currData.size()) { finishSegment(); @@ -971,11 +974,37 @@ void ModuleSplitter::removeUnusedSecondaryElements() { // code size in the primary module as well. for (auto& secondaryPtr : secondaries) { PassRunner runner(secondaryPtr.get()); + // Do not validate here in the middle, as the IR still needs updating later. + runner.options.validate = false; runner.add("remove-unused-module-elements"); runner.run(); } } +void ModuleSplitter::updateIR() { + // Imported functions may need type updates. + struct Fixer : public PostWalker { + void visitRefFunc(RefFunc* curr) { + auto& wasm = *getModule(); + auto* func = wasm.getFunction(curr->func); + if (func->type != curr->type) { + // This became an import, and lost exactness. + assert(!func->type.isExact()); + assert(curr->type.isExact()); + if (wasm.features.hasCustomDescriptors()) { + // Add a cast, as the parent may depend on the exactness to validate. + replaceCurrent(Builder(wasm).makeRefCast(curr, curr->type)); + } + curr->type = curr->type.with(Inexact); + } + } + } fixer; + fixer.walkModule(&primary); + for (auto& secondaryPtr : secondaries) { + fixer.walkModule(secondaryPtr.get()); + } +} + } // anonymous namespace Results splitFunctions(Module& primary, const Config& config) { diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index b9f2213fc9a..01b06cc62ad 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -27,6 +27,7 @@ #include "ir/module-utils.h" #include "ir/possible-contents.h" #include "support/insert_ordered.h" +#include "wasm-type.h" #include "wasm.h" namespace std { @@ -641,9 +642,15 @@ struct InfoCollector addRoot(curr); } void visitRefFunc(RefFunc* curr) { - addRoot(curr, - PossibleContents::literal( - Literal::makeFunc(curr->func, curr->type.getHeapType()))); + if (!getModule()->getFunction(curr->func)->imported()) { + // This is not imported, so we know the exact function literal. + addRoot( + curr, + PossibleContents::literal(Literal::makeFunc(curr->func, *getModule()))); + } else { + // This is imported, so it might be anything of the proper type. + addRoot(curr); + } // The presence of a RefFunc indicates the function may be called // indirectly, so add the relevant connections for this particular function. @@ -1861,8 +1868,7 @@ void TNHOracle::infer() { // lot of other optimizations become possible anyhow. auto target = possibleTargets[0]->name; info.inferences[call->target] = - PossibleContents::literal(Literal::makeFunc( - target, wasm.getFunction(target)->type.getHeapType())); + PossibleContents::literal(Literal::makeFunc(target, wasm)); continue; } diff --git a/src/ir/properties.h b/src/ir/properties.h index 06209f7091a..8b2938378aa 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -116,7 +116,7 @@ inline Literal getLiteral(const Expression* curr) { } else if (auto* n = curr->dynCast()) { return Literal(n->type); } else if (auto* r = curr->dynCast()) { - return Literal::makeFunc(r->func, r->type.getHeapType()); + return Literal::makeFunc(r->func, r->type); } else if (auto* i = curr->dynCast()) { if (auto* c = i->value->dynCast()) { return Literal::makeI31(c->value.geti32(), diff --git a/src/literal.h b/src/literal.h index 10636f8b89a..4d09a5883cb 100644 --- a/src/literal.h +++ b/src/literal.h @@ -30,6 +30,7 @@ namespace wasm { +class Module; class Literals; struct FuncData; struct GCData; @@ -70,6 +71,9 @@ class Literal { public: // Type of the literal. Immutable because the literal's payload depends on it. + // For references to defined heap types, this is almost always an exact type. + // The exception is references to imported functions, since the function + // provided at instantiation time may have a subtype of the import type. const Type type; Literal() : v128(), type(Type::none) {} @@ -90,7 +94,7 @@ class Literal { explicit Literal(const std::array&); explicit Literal(const std::array&); explicit Literal(const std::array&); - explicit Literal(std::shared_ptr funcData, HeapType type); + explicit Literal(std::shared_ptr funcData, Type type); explicit Literal(std::shared_ptr gcData, HeapType type); explicit Literal(std::shared_ptr exnData); explicit Literal(std::shared_ptr contData); @@ -252,7 +256,8 @@ class Literal { } // Simple way to create a function from the name and type, without a full // FuncData. - static Literal makeFunc(Name func, HeapType type); + static Literal makeFunc(Name func, Type type); + static Literal makeFunc(Name func, Module& wasm); static Literal makeI31(int32_t value, Shareability share) { auto lit = Literal(Type(HeapTypes::i31.getBasic(share), NonNullable)); lit.i32 = value | 0x80000000; diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 122aaf0e1d6..500c79ba315 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1419,6 +1419,9 @@ struct ParseModuleTypesCtx : TypeParserCtx, return in.err(pos, "expected signature type"); } f->type = f->type.with(type.type); + if (f->imported()) { + f->type = f->type.with(Inexact); + } // If we are provided with too many names (more than the function has), we // will error on that later when we check the signature matches the type. // For now, avoid asserting in setLocalName. @@ -1601,7 +1604,7 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { elems.push_back(expr); } void appendFuncElem(std::vector& elems, Name func) { - auto type = wasm.getFunction(func)->type.getHeapType(); + auto type = wasm.getFunction(func)->type; elems.push_back(builder.makeRefFunc(func, type)); } diff --git a/src/passes/ExtractFunction.cpp b/src/passes/ExtractFunction.cpp index df53afbf2ba..6faa40b9efd 100644 --- a/src/passes/ExtractFunction.cpp +++ b/src/passes/ExtractFunction.cpp @@ -22,6 +22,7 @@ #include +#include "ir/utils.h" #include "pass.h" #include "wasm-builder.h" #include "wasm.h" @@ -37,6 +38,7 @@ static void extract(PassRunner* runner, Module* module, Name name) { func->module = "env"; func->base = func->name; func->vars.clear(); + func->type = func->type.with(Inexact); func->body = nullptr; } else { found = true; @@ -46,6 +48,10 @@ static void extract(PassRunner* runner, Module* module, Name name) { Fatal() << "could not find the function to extract\n"; } + // Update function references after making things imports. + ReFinalize().run(runner, module); + ReFinalize().walkModuleCode(module); + // Leave just one export, for the thing we want. module->exports.clear(); module->updateMaps(); diff --git a/src/passes/FuncCastEmulation.cpp b/src/passes/FuncCastEmulation.cpp index 17c12a5aebd..d4f2de4009a 100644 --- a/src/passes/FuncCastEmulation.cpp +++ b/src/passes/FuncCastEmulation.cpp @@ -178,7 +178,7 @@ struct FuncCastEmulation : public Pass { } auto* thunk = iter->second; ref->func = thunk->name; - ref->finalize(thunk->type.getHeapType()); + ref->finalize(thunk->type.getHeapType(), *module); } } diff --git a/src/passes/InstrumentBranchHints.cpp b/src/passes/InstrumentBranchHints.cpp index 34af0953662..601ab72a2a4 100644 --- a/src/passes/InstrumentBranchHints.cpp +++ b/src/passes/InstrumentBranchHints.cpp @@ -102,6 +102,7 @@ #include "ir/names.h" #include "ir/parents.h" #include "ir/properties.h" +#include "ir/utils.h" #include "pass.h" #include "support/string.h" #include "wasm-builder.h" @@ -193,12 +194,15 @@ struct InstrumentBranchHints auto* func = module->getFunction(existing); func->body = Builder(*module).makeNop(); func->module = func->base = Name(); + func->type = func->type.with(Exact); } // Add our import. auto* func = module->addFunction(Builder::makeFunction( Names::getValidFunctionName(*module, BASE), - Signature({Type::i32, Type::i32, Type::i32}, Type::none), + Type(Signature({Type::i32, Type::i32, Type::i32}, Type::none), + NonNullable, + Exact), {})); func->module = MODULE; func->base = BASE; @@ -206,6 +210,10 @@ struct InstrumentBranchHints // Walk normally, using logBranch as we go. Super::doWalkModule(module); + + // Update ref.func type changes. + ReFinalize().run(getPassRunner(), module); + ReFinalize().walkModuleCode(module); } }; diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index b3525b3d5a8..5cc34680fb5 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -148,7 +148,7 @@ struct LegalizeJSInterface : public Pass { } curr->func = iter->second->name; - curr->finalize(iter->second->type.getHeapType()); + curr->finalize(iter->second->type.getHeapType(), *getModule()); } }; @@ -278,7 +278,7 @@ struct LegalizeJSInterface : public Pass { legalIm->hasExplicitName = true; auto stub = std::make_unique(); stub->name = Name(std::string("legalfunc$") + im->name.toString()); - stub->type = im->type; + stub->type = im->type.with(Exact); stub->hasExplicitName = true; auto* call = module->allocator.alloc(); @@ -309,7 +309,7 @@ struct LegalizeJSInterface : public Pass { stub->body = call; } legalIm->type = - Type(Signature(Type(params), call->type), NonNullable, Exact); + Type(Signature(Type(params), call->type), NonNullable, Inexact); auto* stubPtr = stub.get(); if (!module->getFunctionOrNull(stub->name)) { @@ -334,7 +334,7 @@ struct LegalizeJSInterface : public Pass { } // Failing that create a new function import. auto import = Builder::makeFunction( - name, Type(Signature(params, results), NonNullable, Exact), {}); + name, Type(Signature(params, results), NonNullable, Inexact), {}); import->module = ENV; import->base = name; auto* ret = import.get(); @@ -390,6 +390,7 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { // Prune an import by implementing it in a trivial manner. if (imported) { func->module = func->base = Name(); + func->type = func->type.with(Exact); Builder builder(*module); if (sig.results == Type::none) { @@ -409,6 +410,9 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { } } + // RefFunc types etc. need updating. + ReFinalize().run(getPassRunner(), module); + // TODO: globals etc. } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 19e8d9b3ee6..705722f498b 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3082,6 +3082,12 @@ void PrintSExpression::handleSignature(Function* curr, requiresExplicitFuncType(curr->type.getHeapType())) { o << " (type "; printHeapTypeName(curr->type.getHeapType()) << ')'; + if (full) { + // Print the full type in a comment. TODO the spec may add this too + o << " (; "; + printTypeOrName(curr->type, o, currModule); + o << " ;)"; + } } bool inParam = false; Index i = 0; diff --git a/src/shell-interface.h b/src/shell-interface.h index 8c45efff7d9..2c38f6456d4 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -148,7 +148,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { } return Flow(); }), - import->type.getHeapType()); + import->type); } else if (import->module == ENV && import->base == EXIT) { return Literal(std::make_shared(import->name, nullptr, @@ -157,7 +157,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { std::cout << "exit()\n"; throw ExitException(); }), - import->type.getHeapType()); + import->type); } else if (auto* inst = getImportInstance(import)) { return inst->getExportedFunction(import->base); } diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index a59b5deee4f..1f93ff29196 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -211,7 +211,7 @@ struct LoggingExternalInterface : public ShellExternalInterface { }; // Use a null instance because this is a host function. return Literal(std::make_shared(import->name, nullptr, f), - import->type.getHeapType()); + import->type); } void throwJSException() { diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 5c0fc8268ff..032b3eb48e6 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -379,6 +379,12 @@ void TranslateToFuzzReader::build() { } finalizeTable(); shuffleExports(); + + // We may turn various function imports into defined functions. Refinalize at + // the end to update all references to them, which may become exact. + PassRunner runner(&wasm); + ReFinalize().run(&runner, &wasm); + ReFinalize().walkModuleCode(&wasm); } void TranslateToFuzzReader::setupMemory() { @@ -949,12 +955,13 @@ void TranslateToFuzzReader::addImportLoggingSupport() { if (!preserveImportsAndExports) { func->module = "fuzzing-support"; func->base = baseName; + func->type = Type(Signature(type, Type::none), NonNullable, Inexact); } else { // We cannot add an import, so just make it a trivial function (this is // simpler than avoiding calls to logging in all the rest of the logic). func->body = builder.makeNop(); + func->type = Type(Signature(type, Type::none), NonNullable, Exact); } - func->type = Type(Signature(type, Type::none), NonNullable, Exact); wasm.addFunction(std::move(func)); } } @@ -975,6 +982,7 @@ void TranslateToFuzzReader::addImportCallingSupport() { func->base.startsWith("call-ref")) { // Make it non-imported, and with a simple body. func->module = func->base = Name(); + func->type = func->type.with(Exact); auto results = func->getResults(); func->body = results.isConcrete() ? makeConst(results) : makeNop(Type::none); @@ -1003,7 +1011,7 @@ void TranslateToFuzzReader::addImportCallingSupport() { func->module = "fuzzing-support"; func->base = "call-export"; func->type = - Type(Signature({Type::i32, Type::i32}, Type::none), NonNullable, Exact); + Type(Signature({Type::i32, Type::i32}, Type::none), NonNullable, Inexact); wasm.addFunction(std::move(func)); } @@ -1017,7 +1025,7 @@ void TranslateToFuzzReader::addImportCallingSupport() { func->name = callExportCatchImportName; func->module = "fuzzing-support"; func->base = "call-export-catch"; - func->type = Type(Signature(Type::i32, Type::i32), NonNullable, Exact); + func->type = Type(Signature(Type::i32, Type::i32), NonNullable, Inexact); wasm.addFunction(std::move(func)); } @@ -1036,7 +1044,7 @@ void TranslateToFuzzReader::addImportCallingSupport() { func->type = Type(Signature({Type(HeapType::func, Nullable), Type::i32}, Type::none), NonNullable, - Exact); + Inexact); wasm.addFunction(std::move(func)); } @@ -1051,7 +1059,7 @@ void TranslateToFuzzReader::addImportCallingSupport() { func->base = "call-ref-catch"; func->type = Type(Signature(Type(HeapType::func, Nullable), Type::i32), NonNullable, - Exact); + Inexact); wasm.addFunction(std::move(func)); } } @@ -1069,11 +1077,12 @@ void TranslateToFuzzReader::addImportThrowingSupport() { if (!preserveImportsAndExports) { func->module = "fuzzing-support"; func->base = "throw"; + func->type = Type(Signature(Type::i32, Type::none), NonNullable, Inexact); } else { // As with logging, implement in a trivial way when we cannot add imports. func->body = builder.makeNop(); + func->type = Type(Signature(Type::i32, Type::none), NonNullable, Exact); } - func->type = Type(Signature(Type::i32, Type::none), NonNullable, Exact); wasm.addFunction(std::move(func)); } @@ -1114,7 +1123,7 @@ void TranslateToFuzzReader::addImportTableSupport() { func->base = "table-get"; func->type = Type(Signature({Type::i32}, Type(HeapType::func, Nullable)), NonNullable, - Exact); + Inexact); wasm.addFunction(std::move(func)); } @@ -1128,7 +1137,7 @@ void TranslateToFuzzReader::addImportTableSupport() { func->type = Type(Signature({Type::i32, Type(HeapType::func, Nullable)}, Type::none), NonNullable, - Exact); + Inexact); wasm.addFunction(std::move(func)); } } @@ -1149,7 +1158,7 @@ void TranslateToFuzzReader::addImportSleepSupport() { func->module = "fuzzing-support"; func->base = "sleep"; func->type = - Type(Signature({Type::i32, Type::i32}, Type::i32), NonNullable, Exact); + Type(Signature({Type::i32, Type::i32}, Type::i32), NonNullable, Inexact); wasm.addFunction(std::move(func)); } @@ -1234,8 +1243,7 @@ void TranslateToFuzzReader::useImportedFunctions() { auto name = Names::getValidFunctionName(wasm, "primary_" + exp->name.toString()); // We can import it as its own type, or any (declared) supertype. - // TODO: this will be inexact eventually - auto type = getSuperType(func->type).with(NonNullable).with(Exact); + auto type = getSuperType(func->type).with(NonNullable).with(Inexact); auto import = builder.makeFunction(name, type, {}); import->module = "primary"; import->base = exp->name; @@ -2236,6 +2244,7 @@ void TranslateToFuzzReader::modifyInitialFunctions() { if (func->imported()) { FunctionCreationContext context(*this, func); func->module = func->base = Name(); + func->type = func->type.with(Exact); func->body = make(func->getResults()); } } @@ -3607,7 +3616,7 @@ Expression* TranslateToFuzzReader::makeRefFuncConst(Type type) { funcContext->func->type.getHeapType().getShared() == share && !oneIn(4)) { auto* target = funcContext->func; - return builder.makeRefFunc(target->name, target->type.getHeapType()); + return builder.makeRefFunc(target->name, target->type); } } // Look for a proper function starting from a random location, and loop from diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 427ed2d5dcb..b33fb5aa999 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -103,7 +103,7 @@ class EvallingModuleRunner : public ModuleRunnerBase { } // This needs to be duplicated from ModuleRunner, unfortunately. - Literal makeFuncData(Name name, HeapType type) { + Literal makeFuncData(Name name, Type type) { auto allocation = std::make_shared(name, this, [this, name](Literals arguments) { return callFunction(name, arguments); @@ -319,8 +319,9 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { }; // Use a null instance because these are either host functions or imported // from unknown sources. + // TODO: Be more precise about the types we allow these imports to have. return Literal(std::make_shared(import->name, nullptr, f), - import->type.getHeapType()); + import->type); } Tag* getImportedTag(Tag* tag) override { @@ -369,7 +370,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { throw FailToEvalException("tableLoad of non-literal"); } if (auto* r = value->dynCast()) { - return instance->makeFuncData(r->func, r->type.getHeapType()); + return instance->makeFuncData(r->func, r->type); } return Properties::getLiteral(value); } diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index 958c45fce3c..bf9c13d898a 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -571,7 +571,8 @@ void updateTypes(Module& wasm) { } void visitRefFunc(RefFunc* curr) { - curr->finalize(getModule()->getFunction(curr->func)->type.getHeapType()); + curr->finalize(getModule()->getFunction(curr->func)->type.getHeapType(), + *getModule()); } void visitFunction(Function* curr) { diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 5689df5e2be..b1c10146dac 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -700,10 +700,19 @@ class Builder { ret->finalize(); return ret; } + RefFunc* makeRefFunc(Name func, Type type) { + auto* ret = wasm.allocator.alloc(); + ret->func = func; + // Just apply the type, trusting it completely. This is safe to do even in + // the middle of an operation (where the Module is in the process of being + // altered, and should not be read from, which finalize normally does). + ret->type = type; + return ret; + } RefFunc* makeRefFunc(Name func, HeapType heapType) { auto* ret = wasm.allocator.alloc(); ret->func = func; - ret->finalize(heapType); + ret->finalize(heapType, wasm); return ret; } RefEq* makeRefEq(Expression* left, Expression* right) { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 724d73dfb29..a915518edb8 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -359,7 +359,7 @@ class ExpressionRunner : public OverriddenVisitor { Execute, }; - Literal makeFuncData(Name name, HeapType type) { + Literal makeFuncData(Name name, Type type) { // Identify the interpreter, but do not provide a way to actually call the // function. auto allocation = std::make_shared(name, this); @@ -1883,7 +1883,7 @@ class ExpressionRunner : public OverriddenVisitor { return Literal(int32_t(value.isNull())); } Flow visitRefFunc(RefFunc* curr) { - return self()->makeFuncData(curr->func, curr->type.getHeapType()); + return self()->makeFuncData(curr->func, curr->type); } Flow visitRefEq(RefEq* curr) { VISIT(flow, curr->left) @@ -3202,7 +3202,7 @@ class ModuleRunnerBase : public ExpressionRunner { [this, func](const Literals& arguments) -> Flow { return callFunction(func->name, arguments); }), - func->type.getHeapType()); + func->type); } // get an exported global @@ -3504,14 +3504,14 @@ class ModuleRunnerBase : public ExpressionRunner { // The call.without.effects intrinsic is a call to an import that actually // calls the given function reference that is the final argument. target = arguments.back().getFunc(); - funcType = arguments.back().type; + funcType = funcType.with(arguments.back().type.getHeapType()); arguments.pop_back(); } if (curr->isReturn) { // Return calls are represented by their arguments followed by a reference // to the function to be called. - arguments.push_back(self()->makeFuncData(target, funcType.getHeapType())); + arguments.push_back(self()->makeFuncData(target, funcType)); return Flow(RETURN_CALL_FLOW, std::move(arguments)); } @@ -4470,8 +4470,7 @@ class ModuleRunnerBase : public ExpressionRunner { auto funcName = funcValue.getFunc(); auto* func = self()->getModule()->getFunction(funcName); return Literal(std::make_shared( - self()->makeFuncData(func->name, func->type.getHeapType()), - curr->type.getHeapType())); + self()->makeFuncData(funcName, func->type), curr->type.getHeapType())); } Flow visitContBind(ContBind* curr) { Literals arguments; @@ -4816,7 +4815,7 @@ class ModuleRunnerBase : public ExpressionRunner { // not the original function that was called, and the original has been // returned from already; we should call the last return_called // function). - auto target = self()->makeFuncData(name, function->type.getHeapType()); + auto target = self()->makeFuncData(name, function->type); self()->pushResumeEntry({target}, "function-target"); } @@ -4965,7 +4964,7 @@ class ModuleRunner : public ModuleRunnerBase { std::map> linkedInstances = {}) : ModuleRunnerBase(wasm, externalInterface, linkedInstances) {} - Literal makeFuncData(Name name, HeapType type) { + Literal makeFuncData(Name name, Type type) { // As the super's |makeFuncData|, but here we also provide a way to // actually call the function. auto allocation = diff --git a/src/wasm.h b/src/wasm.h index e1bec596219..3f3ac1d7434 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1376,7 +1376,7 @@ class RefFunc : public SpecificExpression { Name func; void finalize(); - void finalize(HeapType heapType); + void finalize(HeapType heapType, Module& wasm); }; class RefEq : public SpecificExpression { diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 98d7c61d383..ad5bf765daf 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -71,17 +71,21 @@ Literal::Literal(const uint8_t init[16]) : type(Type::v128) { memcpy(&v128, init, 16); } -Literal::Literal(std::shared_ptr funcData, HeapType type) - : funcData(funcData), type(type, NonNullable, Exact) { +Literal::Literal(std::shared_ptr funcData, Type type) + : funcData(funcData), type(type) { assert(funcData); assert(type.isSignature()); } -Literal Literal::makeFunc(Name func, HeapType type) { +Literal Literal::makeFunc(Name func, Type type) { // Provide only the name of the function, without execution info. return Literal(std::make_shared(func), type); } +Literal Literal::makeFunc(Name func, Module& wasm) { + return makeFunc(func, wasm.getFunction(func)->type); +} + Literal::Literal(std::shared_ptr gcData, HeapType type) : gcData(gcData), type(type, gcData ? NonNullable : Nullable, diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index ebab1eb24b2..2e85bfb5e0c 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2895,7 +2895,7 @@ void WasmBinaryReader::readImports() { "'s type must be a signature. Given: " + type.toString()); } auto curr = - builder.makeFunction(name, Type(type, NonNullable, Exact), {}); + builder.makeFunction(name, Type(type, NonNullable, Inexact), {}); curr->hasExplicitName = isExplicit; curr->module = module; curr->base = base; diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 7394f1ac722..b7487a9d101 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2389,8 +2389,14 @@ void FunctionValidator::visitRefFunc(RefFunc* curr) { func->type, curr, "function reference type must match referenced function type"); - shouldBeTrue( - curr->type.isExact(), curr, "function reference should be exact"); + if (func->imported()) { + shouldBeTrue(!curr->type.isExact(), + curr, + "imported function reference should be inexact"); + } else { + shouldBeTrue( + curr->type.isExact(), curr, "defined function reference should be exact"); + } } void FunctionValidator::visitRefEq(RefEq* curr) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 8364f537c19..17a3fe1156c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -827,8 +827,10 @@ void RefFunc::finalize() { assert(type.isSignature()); } -void RefFunc::finalize(HeapType heapType) { - type = Type(heapType, NonNullable, Exact); +void RefFunc::finalize(HeapType heapType, Module& wasm) { + type = Type(heapType, + NonNullable, + wasm.getFunction(func)->imported() ? Inexact : Exact); } void RefEq::finalize() { diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index 2633bdb3714..e1c56b99879 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -89,8 +89,8 @@ class PossibleContentsTest : public testing::Test { PossibleContents nonNullFuncGlobal = PossibleContents::global("funcGlobal", Type(HeapType::func, NonNullable)); - PossibleContents nonNullFunc = PossibleContents::literal( - Literal::makeFunc("func", Signature(Type::none, Type::none))); + PossibleContents nonNullFunc = PossibleContents::literal(Literal::makeFunc( + "func", Type(Signature(Type::none, Type::none), NonNullable, Exact))); PossibleContents exactI32 = PossibleContents::exactType(Type::i32); PossibleContents exactAnyref = PossibleContents::exactType(anyref); diff --git a/test/lit/passes/gsi-debug.wast b/test/lit/passes/gsi-debug.wast index 4c690652543..10d9befa120 100644 --- a/test/lit/passes/gsi-debug.wast +++ b/test/lit/passes/gsi-debug.wast @@ -33,7 +33,7 @@ ;; CHECK: (global $global-other i32 (i32.const 123456)) (global $global-other i32 (i32.const 123456)) - ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) + ;; CHECK: (func $test (type $1) (; (ref (exact $func.0)) ;) (param $struct (ref null $struct)) ;; CHECK-NEXT: ;;@ drop.c:10:1 ;; CHECK-NEXT: (drop ;; CHECK-NEXT: ;;@ struct.c:20:2 diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index 721c27eeb66..93962d1935e 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1154,3 +1154,114 @@ ) ) ) + +;; We cannot know the types of imported functions, so we should not be able to +;; optimize this exact cast. +(module + ;; CHECK: (type $func (sub (func))) + (type $func (sub (func))) + (type $sub (sub $func (func))) + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (import "" "" (func $f (type $func))) + (import "" "" (func $f (type $func))) + ;; CHECK: (elem declare func $f) + + ;; CHECK: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK-NEXT: (ref.test (ref (exact $func)) + ;; CHECK-NEXT: (ref.func $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (result i32) + (ref.test (ref (exact $func)) + (ref.func $f) + ) + ) +) + +;; As above, but now the cast is to a subtype. We should not be able to optimize +;; this either. +(module + ;; CHECK: (type $func (sub (func))) + (type $func (sub (func))) + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (type $sub (sub $func (func))) + (type $sub (sub $func (func))) + ;; CHECK: (import "" "" (func $f (type $func))) + (import "" "" (func $f (type $func))) + ;; CHECK: (elem declare func $f) + + ;; CHECK: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK-NEXT: (ref.test (ref $sub) + ;; CHECK-NEXT: (block (result (ref $func)) + ;; CHECK-NEXT: (ref.func $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (result i32) + (ref.test (ref $sub) + ;; TODO: Remove this block. It is currently necessary because we + ;; incorrectly type the function reference as exact, so without the block + ;; the cast type above would be "improved" to (ref nofunc). + (block (result (ref $func)) + (ref.func $f) + ) + ) + ) +) + +;; This is another exact cast, but now the imported type is final, so we know +;; it will succeed. +(module + ;; CHECK: (type $func (func)) + (type $func (sub final (func))) + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (import "" "" (func $f (type $func))) + (import "" "" (func $f (type $func))) + ;; CHECK: (elem declare func $f) + + ;; CHECK: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK-NEXT: (ref.test (ref (exact $func)) + ;; CHECK-NEXT: (ref.func $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (result i32) + (ref.test (ref (exact $func)) + (ref.func $f) + ) + ) +) + +;; Now we use a ref.cast instead of a ref.test with the exact cast to the final +;; type. We cannot optimize even though we know the cast will succeed because +;; the Wasm type of the function reference is inexact. +(module + ;; CHECK: (type $func (func)) + (type $func (sub final (func))) + ;; CHECK: (type $1 (func (result funcref))) + + ;; CHECK: (import "" "" (func $f (type $func))) + (import "" "" (func $f (type $func))) + ;; CHECK: (elem declare func $f) + + ;; CHECK: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result funcref) + ;; CHECK-NEXT: (ref.cast (ref (exact $func)) + ;; CHECK-NEXT: (ref.func $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (result funcref) + (ref.cast (ref (exact $func)) + (ref.func $f) + ) + ) +) diff --git a/test/lit/passes/local-subtyping-nn.wast b/test/lit/passes/local-subtyping-nn.wast index 476e4d4b045..3754230d82f 100644 --- a/test/lit/passes/local-subtyping-nn.wast +++ b/test/lit/passes/local-subtyping-nn.wast @@ -9,7 +9,7 @@ ;; CHECK: (func $non-nullable (type $1) ;; CHECK-NEXT: (local $x (ref none)) - ;; CHECK-NEXT: (local $y (ref (exact $0))) + ;; CHECK-NEXT: (local $y (ref $0)) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null none) diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index 4679ff9d660..4d18c8ac1f9 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -84,7 +84,7 @@ ;; more specific type. A similar thing with a parameter, however, is not a ;; thing we can optimize. Also, ignore a local with zero assignments. ;; CHECK: (func $simple-local-but-not-param (type $8) (param $x funcref) - ;; CHECK-NEXT: (local $y (ref (exact $1))) + ;; CHECK-NEXT: (local $y (ref $1)) ;; CHECK-NEXT: (local $unused funcref) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.func $i32) @@ -179,9 +179,9 @@ ;; In some cases multiple iterations are necessary, as one inferred new type ;; applies to a get which then allows another inference. ;; CHECK: (func $multiple-iterations (type $0) - ;; CHECK-NEXT: (local $x (ref (exact $1))) - ;; CHECK-NEXT: (local $y (ref (exact $1))) - ;; CHECK-NEXT: (local $z (ref (exact $1))) + ;; CHECK-NEXT: (local $x (ref $1)) + ;; CHECK-NEXT: (local $y (ref $1)) + ;; CHECK-NEXT: (local $z (ref $1)) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.func $i32) ;; CHECK-NEXT: ) @@ -209,8 +209,8 @@ ;; Sometimes a refinalize is necessary in between the iterations. ;; CHECK: (func $multiple-iterations-refinalize (type $2) (param $i i32) - ;; CHECK-NEXT: (local $x (ref (exact $1))) - ;; CHECK-NEXT: (local $y (ref (exact $4))) + ;; CHECK-NEXT: (local $x (ref $1)) + ;; CHECK-NEXT: (local $y (ref $4)) ;; CHECK-NEXT: (local $z (ref func)) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.func $i32) diff --git a/test/lit/wasm-split/exact.wast b/test/lit/wasm-split/exact.wast new file mode 100644 index 00000000000..895b26e8eab --- /dev/null +++ b/test/lit/wasm-split/exact.wast @@ -0,0 +1,132 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Get references to both functions from both modules, and ensure they are +;; exact. Test in both custom descriptors mode, and without (without, we do not +;; add a cast). + +;; RUN: wasm-split -all -g %s --keep-funcs=foo -o1 %t.1.wasm -o2 %t.2.wasm +;; RUN: wasm-opt -all %t.1.wasm --print | filecheck %s --check-prefix=PRIMARY +;; RUN: wasm-opt -all %t.2.wasm --print | filecheck %s --check-prefix=SECONDARY + +;; RUN: wasm-split -all --disable-custom-descriptors -g %s --keep-funcs=foo -o1 %t.1.wasm -o2 %t.2.wasm +;; RUN: wasm-opt -all --disable-custom-descriptors %t.1.wasm --print | filecheck %s --check-prefix=PRIMARY_NOCD +;; RUN: wasm-opt -all --disable-custom-descriptors %t.2.wasm --print | filecheck %s --check-prefix=SECONDARY_NOCD + +(module + ;; PRIMARY: (type $func (sub (func))) + ;; PRIMARY_NOCD: (type $func (sub (func))) + (type $func (sub (func))) + + ;; PRIMARY: (import "placeholder.deferred" "0" (func $placeholder_0 (type $func))) + + ;; PRIMARY: (table $0 1 funcref) + + ;; PRIMARY: (elem $0 (i32.const 0) $placeholder_0) + + ;; PRIMARY: (elem declare func $foo $trampoline_bar) + + ;; PRIMARY: (export "foo" (func $foo)) + + ;; PRIMARY: (export "table" (table $0)) + + ;; PRIMARY: (func $foo (type $func) + ;; PRIMARY-NEXT: (local $exact (ref (exact $func))) + ;; PRIMARY-NEXT: (local.set $exact + ;; PRIMARY-NEXT: (ref.func $foo) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: (local.set $exact + ;; PRIMARY-NEXT: (ref.func $trampoline_bar) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: ) + ;; PRIMARY_NOCD: (import "placeholder.deferred" "0" (func $placeholder_0 (type $func))) + + ;; PRIMARY_NOCD: (table $0 1 funcref) + + ;; PRIMARY_NOCD: (elem $0 (i32.const 0) $placeholder_0) + + ;; PRIMARY_NOCD: (elem declare func $foo $trampoline_bar) + + ;; PRIMARY_NOCD: (export "foo" (func $foo)) + + ;; PRIMARY_NOCD: (export "table" (table $0)) + + ;; PRIMARY_NOCD: (func $foo (type $func) + ;; PRIMARY_NOCD-NEXT: (local $exact (ref $func)) + ;; PRIMARY_NOCD-NEXT: (local.set $exact + ;; PRIMARY_NOCD-NEXT: (ref.func $foo) + ;; PRIMARY_NOCD-NEXT: ) + ;; PRIMARY_NOCD-NEXT: (local.set $exact + ;; PRIMARY_NOCD-NEXT: (ref.func $trampoline_bar) + ;; PRIMARY_NOCD-NEXT: ) + ;; PRIMARY_NOCD-NEXT: ) + (func $foo (type $func) + (local $exact (ref (exact $func))) + (local.set $exact + (ref.func $foo) + ) + (local.set $exact + (ref.func $bar) + ) + ) + + ;; SECONDARY: (type $0 (sub (func))) + + ;; SECONDARY: (import "primary" "table" (table $timport$0 1 funcref)) + + ;; SECONDARY: (import "primary" "foo" (func $foo (type $0))) + + ;; SECONDARY: (elem $0 (i32.const 0) $bar) + + ;; SECONDARY: (elem declare func $foo) + + ;; SECONDARY: (func $bar (type $0) + ;; SECONDARY-NEXT: (local $exact (ref (exact $0))) + ;; SECONDARY-NEXT: (local.set $exact + ;; SECONDARY-NEXT: (ref.cast (ref (exact $0)) + ;; SECONDARY-NEXT: (ref.func $foo) + ;; SECONDARY-NEXT: ) + ;; SECONDARY-NEXT: ) + ;; SECONDARY-NEXT: (local.set $exact + ;; SECONDARY-NEXT: (ref.func $bar) + ;; SECONDARY-NEXT: ) + ;; SECONDARY-NEXT: ) + ;; SECONDARY_NOCD: (type $0 (sub (func))) + + ;; SECONDARY_NOCD: (import "primary" "table" (table $timport$0 1 funcref)) + + ;; SECONDARY_NOCD: (import "primary" "foo" (func $foo (type $0))) + + ;; SECONDARY_NOCD: (elem $0 (i32.const 0) $bar) + + ;; SECONDARY_NOCD: (elem declare func $foo) + + ;; SECONDARY_NOCD: (func $bar (type $0) + ;; SECONDARY_NOCD-NEXT: (local $exact (ref $0)) + ;; SECONDARY_NOCD-NEXT: (local.set $exact + ;; SECONDARY_NOCD-NEXT: (ref.func $foo) + ;; SECONDARY_NOCD-NEXT: ) + ;; SECONDARY_NOCD-NEXT: (local.set $exact + ;; SECONDARY_NOCD-NEXT: (ref.func $bar) + ;; SECONDARY_NOCD-NEXT: ) + ;; SECONDARY_NOCD-NEXT: ) + (func $bar (type $func) + (local $exact (ref (exact $func))) + (local.set $exact + (ref.func $foo) + ) + (local.set $exact + (ref.func $bar) + ) + ) +) +;; PRIMARY: (func $trampoline_bar (type $func) +;; PRIMARY-NEXT: (call_indirect $0 (type $func) +;; PRIMARY-NEXT: (i32.const 0) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) + +;; PRIMARY_NOCD: (func $trampoline_bar (type $func) +;; PRIMARY_NOCD-NEXT: (call_indirect $0 (type $func) +;; PRIMARY_NOCD-NEXT: (i32.const 0) +;; PRIMARY_NOCD-NEXT: ) +;; PRIMARY_NOCD-NEXT: ) diff --git a/test/lit/wasm-split/multi-split-escape-names.wast b/test/lit/wasm-split/multi-split-escape-names.wast index 91fc3196d43..4476f32e9a9 100644 --- a/test/lit/wasm-split/multi-split-escape-names.wast +++ b/test/lit/wasm-split/multi-split-escape-names.wast @@ -38,12 +38,16 @@ ;; MOD1-NEXT: ) ;; MOD1-NEXT: (drop ;; MOD1-NEXT: (call_ref $1 - ;; MOD1-NEXT: (ref.func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29) + ;; MOD1-NEXT: (ref.cast (ref (exact $1)) + ;; MOD1-NEXT: (ref.func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29) + ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: (drop ;; MOD1-NEXT: (call_ref $0 - ;; MOD1-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) + ;; MOD1-NEXT: (ref.cast (ref (exact $0)) + ;; MOD1-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) + ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: (i32.const 0) @@ -84,7 +88,9 @@ ;; MOD2: (func $wasm::Literal::Literal\28std::__2::array\20const&\29 (result i64) ;; MOD2-NEXT: (drop ;; MOD2-NEXT: (call_ref $1 - ;; MOD2-NEXT: (ref.func $trampoline_wasm::Type::getFeatures\28\29\20const) + ;; MOD2-NEXT: (ref.cast (ref (exact $1)) + ;; MOD2-NEXT: (ref.func $trampoline_wasm::Type::getFeatures\28\29\20const) + ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: (drop @@ -94,7 +100,9 @@ ;; MOD2-NEXT: ) ;; MOD2-NEXT: (drop ;; MOD2-NEXT: (call_ref $0 - ;; MOD2-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) + ;; MOD2-NEXT: (ref.cast (ref (exact $0)) + ;; MOD2-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) + ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: (i64.const 0) @@ -135,12 +143,16 @@ ;; MOD3: (func $std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29 (result f32) ;; MOD3-NEXT: (drop ;; MOD3-NEXT: (call_ref $1 - ;; MOD3-NEXT: (ref.func $trampoline_wasm::Type::getFeatures\28\29\20const) + ;; MOD3-NEXT: (ref.cast (ref (exact $1)) + ;; MOD3-NEXT: (ref.func $trampoline_wasm::Type::getFeatures\28\29\20const) + ;; MOD3-NEXT: ) ;; MOD3-NEXT: ) ;; MOD3-NEXT: ) ;; MOD3-NEXT: (drop ;; MOD3-NEXT: (call_ref $0 - ;; MOD3-NEXT: (ref.func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29) + ;; MOD3-NEXT: (ref.cast (ref (exact $0)) + ;; MOD3-NEXT: (ref.func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29) + ;; MOD3-NEXT: ) ;; MOD3-NEXT: ) ;; MOD3-NEXT: ) ;; MOD3-NEXT: (drop diff --git a/test/lit/wasm-split/multi-split.wast b/test/lit/wasm-split/multi-split.wast index a16049eca82..a9c63f26490 100644 --- a/test/lit/wasm-split/multi-split.wast +++ b/test/lit/wasm-split/multi-split.wast @@ -61,12 +61,16 @@ ;; MOD1-NEXT: ) ;; MOD1-NEXT: (drop ;; MOD1-NEXT: (call_ref $0 - ;; MOD1-NEXT: (ref.func $trampoline_B) + ;; MOD1-NEXT: (ref.cast (ref (exact $0)) + ;; MOD1-NEXT: (ref.func $trampoline_B) + ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: (drop ;; MOD1-NEXT: (call_ref $1 - ;; MOD1-NEXT: (ref.func $trampoline_C) + ;; MOD1-NEXT: (ref.cast (ref (exact $1)) + ;; MOD1-NEXT: (ref.func $trampoline_C) + ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: (i32.const 0) @@ -93,12 +97,16 @@ ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: (drop ;; MOD1-OPTIONS-NEXT: (call_ref $0 - ;; MOD1-OPTIONS-NEXT: (ref.func $trampoline_B) + ;; MOD1-OPTIONS-NEXT: (ref.cast (ref (exact $0)) + ;; MOD1-OPTIONS-NEXT: (ref.func $trampoline_B) + ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: (drop ;; MOD1-OPTIONS-NEXT: (call_ref $1 - ;; MOD1-OPTIONS-NEXT: (ref.func $trampoline_C) + ;; MOD1-OPTIONS-NEXT: (ref.cast (ref (exact $1)) + ;; MOD1-OPTIONS-NEXT: (ref.func $trampoline_C) + ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: (i32.const 0) @@ -139,7 +147,9 @@ ;; MOD2: (func $B (result i64) ;; MOD2-NEXT: (drop ;; MOD2-NEXT: (call_ref $0 - ;; MOD2-NEXT: (ref.func $trampoline_A) + ;; MOD2-NEXT: (ref.cast (ref (exact $0)) + ;; MOD2-NEXT: (ref.func $trampoline_A) + ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: (drop @@ -149,7 +159,9 @@ ;; MOD2-NEXT: ) ;; MOD2-NEXT: (drop ;; MOD2-NEXT: (call_ref $1 - ;; MOD2-NEXT: (ref.func $trampoline_C) + ;; MOD2-NEXT: (ref.cast (ref (exact $1)) + ;; MOD2-NEXT: (ref.func $trampoline_C) + ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: (i64.const 0) @@ -171,7 +183,9 @@ ;; MOD2-OPTIONS: (func $B (result i64) ;; MOD2-OPTIONS-NEXT: (drop ;; MOD2-OPTIONS-NEXT: (call_ref $0 - ;; MOD2-OPTIONS-NEXT: (ref.func $trampoline_A) + ;; MOD2-OPTIONS-NEXT: (ref.cast (ref (exact $0)) + ;; MOD2-OPTIONS-NEXT: (ref.func $trampoline_A) + ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: (drop @@ -181,7 +195,9 @@ ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: (drop ;; MOD2-OPTIONS-NEXT: (call_ref $1 - ;; MOD2-OPTIONS-NEXT: (ref.func $trampoline_C) + ;; MOD2-OPTIONS-NEXT: (ref.cast (ref (exact $1)) + ;; MOD2-OPTIONS-NEXT: (ref.func $trampoline_C) + ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: (i64.const 0) @@ -222,12 +238,16 @@ ;; MOD3: (func $C (result f32) ;; MOD3-NEXT: (drop ;; MOD3-NEXT: (call_ref $0 - ;; MOD3-NEXT: (ref.func $trampoline_A) + ;; MOD3-NEXT: (ref.cast (ref (exact $0)) + ;; MOD3-NEXT: (ref.func $trampoline_A) + ;; MOD3-NEXT: ) ;; MOD3-NEXT: ) ;; MOD3-NEXT: ) ;; MOD3-NEXT: (drop ;; MOD3-NEXT: (call_ref $1 - ;; MOD3-NEXT: (ref.func $trampoline_B) + ;; MOD3-NEXT: (ref.cast (ref (exact $1)) + ;; MOD3-NEXT: (ref.func $trampoline_B) + ;; MOD3-NEXT: ) ;; MOD3-NEXT: ) ;; MOD3-NEXT: ) ;; MOD3-NEXT: (drop @@ -254,12 +274,16 @@ ;; MOD3-OPTIONS: (func $C (result f32) ;; MOD3-OPTIONS-NEXT: (drop ;; MOD3-OPTIONS-NEXT: (call_ref $0 - ;; MOD3-OPTIONS-NEXT: (ref.func $trampoline_A) + ;; MOD3-OPTIONS-NEXT: (ref.cast (ref (exact $0)) + ;; MOD3-OPTIONS-NEXT: (ref.func $trampoline_A) + ;; MOD3-OPTIONS-NEXT: ) ;; MOD3-OPTIONS-NEXT: ) ;; MOD3-OPTIONS-NEXT: ) ;; MOD3-OPTIONS-NEXT: (drop ;; MOD3-OPTIONS-NEXT: (call_ref $1 - ;; MOD3-OPTIONS-NEXT: (ref.func $trampoline_B) + ;; MOD3-OPTIONS-NEXT: (ref.cast (ref (exact $1)) + ;; MOD3-OPTIONS-NEXT: (ref.func $trampoline_B) + ;; MOD3-OPTIONS-NEXT: ) ;; MOD3-OPTIONS-NEXT: ) ;; MOD3-OPTIONS-NEXT: ) ;; MOD3-OPTIONS-NEXT: (drop diff --git a/test/lit/wasm-split/ref.func.wast b/test/lit/wasm-split/ref.func.wast index ef36e44704c..306d8b64fe3 100644 --- a/test/lit/wasm-split/ref.func.wast +++ b/test/lit/wasm-split/ref.func.wast @@ -73,7 +73,9 @@ ;; SECONDARY: (func $second (type $0) ;; SECONDARY-NEXT: (drop - ;; SECONDARY-NEXT: (ref.func $prime) + ;; SECONDARY-NEXT: (ref.cast (ref (exact $0)) + ;; SECONDARY-NEXT: (ref.func $prime) + ;; SECONDARY-NEXT: ) ;; SECONDARY-NEXT: ) ;; SECONDARY-NEXT: (drop ;; SECONDARY-NEXT: (ref.func $second) diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index 344e931f9e0..126178d95e2 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -9,15 +9,15 @@ total [table-data] : 71 [tables] : 1 [tags] : 0 - [total] : 15952 + [total] : 15937 [vars] : 628 Binary : 1172 - Block : 2734 - Break : 525 + Block : 2747 + Break : 485 Call : 628 CallIndirect : 146 Const : 2648 - Drop : 185 + Drop : 197 GlobalGet : 1418 GlobalSet : 1052 If : 866 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index c1d0ccfe916..d3cbfaa21fc 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -9,19 +9,19 @@ total [table-data] : 5 [tables] : 2 [tags] : 1 - [total] : 547 + [total] : 548 [vars] : 32 ArrayNewFixed : 7 AtomicRMW : 1 Binary : 25 - Block : 80 + Block : 81 BrOn : 1 - Break : 8 + Break : 7 Call : 18 CallIndirect : 3 CallRef : 1 Const : 128 - Drop : 2 + Drop : 3 GlobalGet : 47 GlobalSet : 36 If : 24