From 0967b41a76e52a65bc2c5469993966ce26dc9ce1 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 8 Oct 2025 12:26:09 -0700 Subject: [PATCH 01/94] Allow funcref literals to have inexact types Update the Literal constructors for funcrefs to take Type instead of HeapType to allow them to be given inexact function references types when the referenced function is an import. Use the new capability to give references to imported functions inexact types in GUFA. Add a test where this change fixes a misoptimization as well as tests where this change simply changes the nature of the misoptimization. Future PRs will fix these tests. --- src/binaryen-c.cpp | 2 +- src/ir/possible-contents.cpp | 11 ++-- src/ir/properties.h | 2 +- src/literal.h | 9 ++- src/shell-interface.h | 4 +- src/tools/execution-results.h | 2 +- src/tools/wasm-ctor-eval.cpp | 5 +- src/wasm-interpreter.h | 18 +++--- src/wasm/literal.cpp | 12 +++- test/gtest/possible-contents.cpp | 4 +- test/lit/passes/gufa.wast | 105 +++++++++++++++++++++++++++++++ 11 files changed, 147 insertions(+), 27 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index a8f39c5abdc..cd01d25e0c4 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"); diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index aba87d79db2..9fbefa030d1 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,9 @@ struct InfoCollector addRoot(curr); } void visitRefFunc(RefFunc* curr) { - addRoot(curr, - PossibleContents::literal( - Literal::makeFunc(curr->func, curr->type.getHeapType()))); + addRoot( + curr, + PossibleContents::literal(Literal::makeFunc(curr->func, *getModule()))); // The presence of a RefFunc indicates the function may be called // indirectly, so add the relevant connections for this particular function. @@ -1859,8 +1860,8 @@ void TNHOracle::infer() { // as other opts will make this call direct later, after which a // 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)); + info.inferences[call->target] = + 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 f20213e0529..92b7ce7689f 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/shell-interface.h b/src/shell-interface.h index 11abe6bac55..b059ecf2b3b 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -148,7 +148,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { } return Flow(); }), - import->type); + Type(import->type, NonNullable, Exact)); } 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); + Type(import->type, NonNullable, Exact)); } 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 aad6ed35102..d06d0e03aa0 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -202,7 +202,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); + Type(import->type, NonNullable, Exact)); } void throwJSException() { diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 8abdcc9e666..ca904926230 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); + Type(import->type, NonNullable, Exact)); } // We assume the table is not modified FIXME diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 4fe7cea6bbf..2a751378bf2 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -346,7 +346,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); @@ -1870,7 +1870,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) @@ -3177,7 +3177,7 @@ class ModuleRunnerBase : public ExpressionRunner { [this, func](const Literals& arguments) -> Flow { return callFunction(func->name, arguments); }), - func->type); + Type(func->type, NonNullable, Exact)); } // get an exported global @@ -3459,12 +3459,12 @@ class ModuleRunnerBase : public ExpressionRunner { Literals arguments; VISIT_ARGUMENTS(flow, curr->operands, arguments); auto* func = wasm.getFunction(curr->target); - auto funcType = func->type; + auto funcType = Type(func->type, NonNullable, Exact); if (Intrinsics(*self()->getModule()).isCallWithoutEffects(func)) { // 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.getHeapType(); + funcType = funcType.with(arguments.back().type.getHeapType()); arguments.pop_back(); } @@ -4419,8 +4419,9 @@ class ModuleRunnerBase : public ExpressionRunner { } auto funcName = funcValue.getFunc(); auto* func = self()->getModule()->getFunction(funcName); + auto funcType = Type(func->type, NonNullable, Exact); return Literal(std::make_shared( - self()->makeFuncData(func->name, func->type), curr->type.getHeapType())); + self()->makeFuncData(funcName, funcType), curr->type.getHeapType())); } Flow visitContBind(ContBind* curr) { Literals arguments; @@ -4767,7 +4768,8 @@ 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); + auto funcType = Type(function->type, NonNullable, Exact); + auto target = self()->makeFuncData(name, funcType); self()->pushResumeEntry({target}, "function-target"); } @@ -4916,7 +4918,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/literal.cpp b/src/wasm/literal.cpp index 98d7c61d383..bfec71c6447 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -71,17 +71,23 @@ 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) { + auto* f = wasm.getFunction(func); + auto exact = f->imported() ? Inexact : Exact; + return makeFunc(func, Type(f->type, NonNullable, exact)); +} + Literal::Literal(std::shared_ptr gcData, HeapType type) : gcData(gcData), type(type, gcData ? NonNullable : Nullable, 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/gufa.wast b/test/lit/passes/gufa.wast index 721c27eeb66..7d20881b33e 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1154,3 +1154,108 @@ ) ) ) + +;; 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. +;; TODO: We misoptimize this. Fix it! +(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: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK-NEXT: (ref.test (ref $sub) + ;; CHECK-NEXT: (unreachable) + ;; 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. +;; TODO: We misoptimize here, too! +(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: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result funcref) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $test (export "test") (result funcref) + (ref.cast (ref (exact $func)) + (ref.func $f) + ) + ) +) From 82ef0e0636f6855f8322439e3b6c0968726193ad Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Oct 2025 16:47:27 -0700 Subject: [PATCH 02/94] fix --- src/wasm-interpreter.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f2e04df846e..edc93ae2ba7 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4816,8 +4816,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 funcType = Type(function->type, NonNullable, Exact); - auto target = self()->makeFuncData(name, funcType); + auto target = self()->makeFuncData(name, function->type); self()->pushResumeEntry({target}, "function-target"); } From d857e7d2b46cd2560fb1bbeb8e91605564fdbda1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Oct 2025 16:48:48 -0700 Subject: [PATCH 03/94] fix --- src/wasm-interpreter.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index edc93ae2ba7..7c37f931fef 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3499,7 +3499,7 @@ class ModuleRunnerBase : public ExpressionRunner { Literals arguments; VISIT_ARGUMENTS(flow, curr->operands, arguments); auto* func = wasm.getFunction(curr->target); - auto funcType = Type(func->type, NonNullable, Exact); + auto funcType = func->type; if (Intrinsics(*self()->getModule()).isCallWithoutEffects(func)) { // The call.without.effects intrinsic is a call to an import that actually // calls the given function reference that is the final argument. @@ -3511,7 +3511,7 @@ class ModuleRunnerBase : public ExpressionRunner { 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)); } @@ -4469,9 +4469,8 @@ class ModuleRunnerBase : public ExpressionRunner { } auto funcName = funcValue.getFunc(); auto* func = self()->getModule()->getFunction(funcName); - auto funcType = Type(func->type, NonNullable, Exact); return Literal(std::make_shared( - self()->makeFuncData(funcName, funcType), curr->type.getHeapType())); + self()->makeFuncData(funcName, func->type), curr->type.getHeapType())); } Flow visitContBind(ContBind* curr) { Literals arguments; From d1d2ed8fafc92a79c0e435a4e781c4543dee48c3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Oct 2025 16:51:46 -0700 Subject: [PATCH 04/94] fix --- src/shell-interface.h | 4 ++-- src/tools/execution-results.h | 2 +- src/wasm-interpreter.h | 2 +- src/wasm/literal.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/shell-interface.h b/src/shell-interface.h index 1cde505dbf0..2c38f6456d4 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -148,7 +148,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { } return Flow(); }), - Type(import->type, NonNullable, Exact)); + 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(); }), - Type(import->type, NonNullable, Exact)); + 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 4ca5ac75f9c..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), - Type(import->type, NonNullable, Exact)); + import->type); } void throwJSException() { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 7c37f931fef..a915518edb8 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3202,7 +3202,7 @@ class ModuleRunnerBase : public ExpressionRunner { [this, func](const Literals& arguments) -> Flow { return callFunction(func->name, arguments); }), - Type(func->type, NonNullable, Exact)); + func->type); } // get an exported global diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index bfec71c6447..47e19fcdb8c 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -85,7 +85,7 @@ Literal Literal::makeFunc(Name func, Type type) { Literal Literal::makeFunc(Name func, Module& wasm) { auto* f = wasm.getFunction(func); auto exact = f->imported() ? Inexact : Exact; - return makeFunc(func, Type(f->type, NonNullable, exact)); + return makeFunc(func, f->type); } Literal::Literal(std::shared_ptr gcData, HeapType type) From e26f676c2b1170c829ecead8af11f727e6644bd6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 21 Oct 2025 16:53:24 -0700 Subject: [PATCH 05/94] fix --- src/tools/wasm-ctor-eval.cpp | 4 ++-- src/wasm/literal.cpp | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 50bf18391ed..b33fb5aa999 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -321,7 +321,7 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { // 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), - Type(import->type, NonNullable, Exact)); + import->type); } Tag* getImportedTag(Tag* tag) override { @@ -370,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/wasm/literal.cpp b/src/wasm/literal.cpp index 47e19fcdb8c..ad5bf765daf 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -83,9 +83,7 @@ Literal Literal::makeFunc(Name func, Type type) { } Literal Literal::makeFunc(Name func, Module& wasm) { - auto* f = wasm.getFunction(func); - auto exact = f->imported() ? Inexact : Exact; - return makeFunc(func, f->type); + return makeFunc(func, wasm.getFunction(func)->type); } Literal::Literal(std::shared_ptr gcData, HeapType type) From 7fda8cf63bf6ba6d984fffd54900d5b29f43cbd1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 09:57:56 -0700 Subject: [PATCH 06/94] work --- src/passes/FuncCastEmulation.cpp | 2 +- src/passes/LegalizeJSInterface.cpp | 2 +- src/tools/wasm-merge.cpp | 2 +- src/wasm-builder.h | 2 +- src/wasm.h | 2 +- src/wasm/wasm.cpp | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) 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/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index b3525b3d5a8..44834852ff2 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()); } }; diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index 958c45fce3c..cc1295e9747 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -571,7 +571,7 @@ 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..988d3b16c4f 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -703,7 +703,7 @@ class Builder { 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.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/wasm.cpp b/src/wasm/wasm.cpp index 8364f537c19..e02be6423d8 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -827,8 +827,8 @@ 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() { From 5ce973b6e6e233b1b8c117913b37a0dbe221c46b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 10:01:29 -0700 Subject: [PATCH 07/94] work --- src/wasm/wasm-validator.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 7394f1ac722..bac9c8878f3 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2389,8 +2389,13 @@ 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) { From 4b167ebf88e8d34b1ead252b84aa395c595caacd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 10:03:21 -0700 Subject: [PATCH 08/94] work --- src/wasm/wasm-binary.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 79277493bb3f900fe65c776b3cdc5841fb6050d8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 10:30:41 -0700 Subject: [PATCH 09/94] work --- src/tools/fuzzing/fuzzing.cpp | 2 +- src/wasm-builder.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index dca25fff59b..80b18d19ea7 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -3593,7 +3593,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/wasm-builder.h b/src/wasm-builder.h index 988d3b16c4f..7cde6e6479e 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -700,6 +700,13 @@ class Builder { ret->finalize(); return ret; } + RefFunc* makeRefFunc(Name func, Type type) { + auto* ret = wasm.allocator.alloc(); + ret->func = func; + ret->type = type; + ret->finalize(); + return ret; + } RefFunc* makeRefFunc(Name func, HeapType heapType) { auto* ret = wasm.allocator.alloc(); ret->func = func; From 160123eb152151a47ec6e69e5c525987d4ed3d5d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 10:36:09 -0700 Subject: [PATCH 10/94] work --- src/tools/fuzzing/fuzzing.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 80b18d19ea7..6bb2f3e3bb1 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -943,7 +943,7 @@ void TranslateToFuzzReader::addImportLoggingSupport() { // 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, Inexact); wasm.addFunction(std::move(func)); } } @@ -992,7 +992,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)); } @@ -1006,7 +1006,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)); } @@ -1025,7 +1025,7 @@ void TranslateToFuzzReader::addImportCallingSupport() { func->type = Type(Signature({Type(HeapType::func, Nullable), Type::i32}, Type::none), NonNullable, - Exact); + Inexact); wasm.addFunction(std::move(func)); } @@ -1040,7 +1040,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)); } } @@ -1062,7 +1062,7 @@ void TranslateToFuzzReader::addImportThrowingSupport() { // 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, Inexact); wasm.addFunction(std::move(func)); } @@ -1103,7 +1103,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)); } @@ -1117,7 +1117,7 @@ void TranslateToFuzzReader::addImportTableSupport() { func->type = Type(Signature({Type::i32, Type(HeapType::func, Nullable)}, Type::none), NonNullable, - Exact); + Inexact); wasm.addFunction(std::move(func)); } } @@ -1138,7 +1138,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)); } @@ -1223,8 +1223,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; From b8bb97a3328b815fc0652d5831e3df958504a6c9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 10:39:11 -0700 Subject: [PATCH 11/94] work --- src/passes/LegalizeJSInterface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 44834852ff2..eeee2fead3c 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -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(); From 89103b99b0d58848eeb99b09f11560ace163878a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 10:45:46 -0700 Subject: [PATCH 12/94] work --- src/passes/LegalizeJSInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index eeee2fead3c..136369b3c72 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -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(); From 24dea81a108337bda47518aed8e59e3c49cd06cd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 10:48:11 -0700 Subject: [PATCH 13/94] work --- src/parser/contexts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 122aaf0e1d6..e2d85473758 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1601,7 +1601,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)); } From d4aabd2f2ba978363093f2137bcccc771e44e913 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 10:59:33 -0700 Subject: [PATCH 14/94] work --- src/parser/contexts.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index e2d85473758..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. From a4fe5852dce1c592946749e681fa0a93ad48eaf3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 11:04:11 -0700 Subject: [PATCH 15/94] work --- src/passes/LegalizeJSInterface.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 136369b3c72..121ea732ab3 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -367,6 +367,8 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { } } + auto prunedImport = false; + for (auto& func : module->functions) { // If the function is neither exported nor imported, no problem. auto imported = func->imported(); @@ -390,6 +392,7 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { // Prune an import by implementing it in a trivial manner. if (imported) { func->module = func->base = Name(); + prunedImport = true; Builder builder(*module); if (sig.results == Type::none) { @@ -409,6 +412,21 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { } } + if (prunedImport) { + // fix up imports: their ref.funcs are now inexact. + struct Fixer : public WalkerPass> { + bool isFunctionParallel() override { return true; } + + std::unique_ptr create() override { + return std::make_unique(); + } + + void visitRefFunc(RefFunc* curr) { + curr->finalize(iter->second->type.getHeapType(), *getModule()); + } + }; + } + // TODO: globals etc. } From 76eac4c98b5dd12e546a0a488925707a897495ca Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 11:04:42 -0700 Subject: [PATCH 16/94] work --- src/passes/LegalizeJSInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 121ea732ab3..dfd01aa5082 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -422,7 +422,7 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { } void visitRefFunc(RefFunc* curr) { - curr->finalize(iter->second->type.getHeapType(), *getModule()); + curr->finalize(curr->type.getHeapType(), *getModule()); } }; } From 020f119519f44cbe00fc60b7276c57b533f77a3c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 11:06:56 -0700 Subject: [PATCH 17/94] work --- src/passes/LegalizeJSInterface.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index dfd01aa5082..f9ed82bb838 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -424,7 +424,9 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { void visitRefFunc(RefFunc* curr) { curr->finalize(curr->type.getHeapType(), *getModule()); } - }; + } fixer; + fixer.run(getPassRunner(), module); + fixer.runOnModuleCode(getPassRunner(), module); } // TODO: globals etc. From 21588c73fb6f9889b643921dccdac70fbce29376 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 11:10:49 -0700 Subject: [PATCH 18/94] work --- src/passes/Print.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 19e8d9b3ee6..eb7fd6fd419 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3082,6 +3082,9 @@ void PrintSExpression::handleSignature(Function* curr, requiresExplicitFuncType(curr->type.getHeapType())) { o << " (type "; printHeapTypeName(curr->type.getHeapType()) << ')'; + o << "(; "; + printTypeOrName(curr->type, o, currModule); + o << " ;)"; } bool inParam = false; Index i = 0; From fdd72532b58a44b08a1dcc3ea831d7263f50c18f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 11:11:11 -0700 Subject: [PATCH 19/94] work --- src/passes/Print.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index eb7fd6fd419..7979b15cbfa 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3082,7 +3082,7 @@ void PrintSExpression::handleSignature(Function* curr, requiresExplicitFuncType(curr->type.getHeapType())) { o << " (type "; printHeapTypeName(curr->type.getHeapType()) << ')'; - o << "(; "; + o << "(; "; // XXX printTypeOrName(curr->type, o, currModule); o << " ;)"; } From 7a583954e6ce4ed072a4d3fbd505d25aa592fabf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 11:13:22 -0700 Subject: [PATCH 20/94] work --- src/passes/LegalizeJSInterface.cpp | 1 + src/passes/Print.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index f9ed82bb838..a6299a0d1bd 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -392,6 +392,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); prunedImport = true; Builder builder(*module); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 7979b15cbfa..8452774df55 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3082,9 +3082,11 @@ void PrintSExpression::handleSignature(Function* curr, requiresExplicitFuncType(curr->type.getHeapType())) { o << " (type "; printHeapTypeName(curr->type.getHeapType()) << ')'; - o << "(; "; // XXX - printTypeOrName(curr->type, o, currModule); - o << " ;)"; + if (getenv("BINARYEN_PRINT_FULL")) { // XXX + o << " (; "; + printTypeOrName(curr->type, o, currModule); + o << " ;)"; + } } bool inParam = false; Index i = 0; From 46163805cc7651c4313c2be8fdf44f9c41d87913 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 12:34:59 -0700 Subject: [PATCH 21/94] work --- src/ir/ReFinalize.cpp | 4 +--- src/passes/LegalizeJSInterface.cpp | 16 ++-------------- 2 files changed, 3 insertions(+), 17 deletions(-) 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/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index a6299a0d1bd..af80d701af8 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -414,20 +414,8 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { } if (prunedImport) { - // fix up imports: their ref.funcs are now inexact. - struct Fixer : public WalkerPass> { - bool isFunctionParallel() override { return true; } - - std::unique_ptr create() override { - return std::make_unique(); - } - - void visitRefFunc(RefFunc* curr) { - curr->finalize(curr->type.getHeapType(), *getModule()); - } - } fixer; - fixer.run(getPassRunner(), module); - fixer.runOnModuleCode(getPassRunner(), module); + // RefFunc types need updating. + ReFinalize().run(getPassRunner(), module); } // TODO: globals etc. From 46826b02b9733d53642f5b571fc84698d2bb08f3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 12:36:01 -0700 Subject: [PATCH 22/94] work --- src/tools/fuzzing/fuzzing.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 6bb2f3e3bb1..aae15e1486b 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -379,6 +379,8 @@ void TranslateToFuzzReader::build() { } finalizeTable(); shuffleExports(); + + ReFinalize().run(getPassRunner(), module); } void TranslateToFuzzReader::setupMemory() { From 73024eabee1b742de46f6a454a152b1af526a9be Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 12:36:33 -0700 Subject: [PATCH 23/94] work --- src/tools/fuzzing/fuzzing.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index aae15e1486b..ee5c10aa0dd 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -380,7 +380,8 @@ void TranslateToFuzzReader::build() { finalizeTable(); shuffleExports(); - ReFinalize().run(getPassRunner(), module); + PassRunner runner; + ReFinalize().run(&runner, &wasm); } void TranslateToFuzzReader::setupMemory() { From 4de76944ed2dd9b2218522b335191d99301b527b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 12:36:55 -0700 Subject: [PATCH 24/94] work --- src/tools/fuzzing/fuzzing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index ee5c10aa0dd..63987aa9b9f 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -380,7 +380,7 @@ void TranslateToFuzzReader::build() { finalizeTable(); shuffleExports(); - PassRunner runner; + PassRunner runner(&wasm); ReFinalize().run(&runner, &wasm); } From d03376de66b92ec9e60769654988d998795e19f7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 12:41:18 -0700 Subject: [PATCH 25/94] work --- src/tools/fuzzing/fuzzing.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 63987aa9b9f..37c7611133b 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -380,8 +380,9 @@ void TranslateToFuzzReader::build() { finalizeTable(); shuffleExports(); - PassRunner runner(&wasm); - ReFinalize().run(&runner, &wasm); + // XXX + //PassRunner runner(&wasm); + //ReFinalize().run(&runner, &wasm); } void TranslateToFuzzReader::setupMemory() { @@ -967,6 +968,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); From 82061c8591796e9561939b2eb1f3f76f9889f93b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 12:45:09 -0700 Subject: [PATCH 26/94] work --- src/tools/fuzzing/fuzzing.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 37c7611133b..18c3bb436f3 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -942,12 +942,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, Inexact); wasm.addFunction(std::move(func)); } } @@ -1063,11 +1064,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, Inexact); wasm.addFunction(std::move(func)); } @@ -2226,6 +2228,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()); } } From 1859cf060d4ae635345d29b745e76559935ce9aa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 12:53:23 -0700 Subject: [PATCH 27/94] work --- src/tools/fuzzing/fuzzing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 18c3bb436f3..a7f2260f576 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -381,8 +381,8 @@ void TranslateToFuzzReader::build() { shuffleExports(); // XXX - //PassRunner runner(&wasm); - //ReFinalize().run(&runner, &wasm); + PassRunner runner(&wasm); + ReFinalize().run(&runner, &wasm); } void TranslateToFuzzReader::setupMemory() { From 17910669a33146df8856fb4e357fa8f815f5afcd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 15:24:29 -0700 Subject: [PATCH 28/94] work --- src/tools/fuzzing/fuzzing.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index a7f2260f576..ad3b224df63 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -383,6 +383,7 @@ void TranslateToFuzzReader::build() { // XXX PassRunner runner(&wasm); ReFinalize().run(&runner, &wasm); + ReFinalize().walkModuleCode(&wasm); } void TranslateToFuzzReader::setupMemory() { From 7278c6a51e619f9bbaf3dfd354798a608754db66 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 15:29:36 -0700 Subject: [PATCH 29/94] format --- src/tools/fuzzing/fuzzing.cpp | 3 ++- src/tools/wasm-merge.cpp | 3 ++- src/wasm/wasm-validator.cpp | 5 +++-- src/wasm/wasm.cpp | 4 +++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index ad3b224df63..f6ba87961e0 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -380,7 +380,8 @@ void TranslateToFuzzReader::build() { finalizeTable(); shuffleExports(); - // XXX + // 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); diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index cc1295e9747..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(), *getModule()); + curr->finalize(getModule()->getFunction(curr->func)->type.getHeapType(), + *getModule()); } void visitFunction(Function* curr) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index bac9c8878f3..b7487a9d101 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2390,8 +2390,9 @@ void FunctionValidator::visitRefFunc(RefFunc* curr) { curr, "function reference type must match referenced function type"); if (func->imported()) { - shouldBeTrue( - !curr->type.isExact(), curr, "imported function reference should be inexact"); + shouldBeTrue(!curr->type.isExact(), + curr, + "imported function reference should be inexact"); } else { shouldBeTrue( curr->type.isExact(), curr, "defined function reference should be exact"); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index e02be6423d8..17a3fe1156c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -828,7 +828,9 @@ void RefFunc::finalize() { } void RefFunc::finalize(HeapType heapType, Module& wasm) { - type = Type(heapType, NonNullable, wasm.getFunction(func)->imported() ? Inexact : Exact); + type = Type(heapType, + NonNullable, + wasm.getFunction(func)->imported() ? Inexact : Exact); } void RefEq::finalize() { From 31573ac47216e42ab5a2e76273200213295f1a1a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 16:42:42 -0700 Subject: [PATCH 30/94] work --- src/passes/Print.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 8452774df55..705722f498b 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3082,7 +3082,8 @@ void PrintSExpression::handleSignature(Function* curr, requiresExplicitFuncType(curr->type.getHeapType())) { o << " (type "; printHeapTypeName(curr->type.getHeapType()) << ')'; - if (getenv("BINARYEN_PRINT_FULL")) { // XXX + if (full) { + // Print the full type in a comment. TODO the spec may add this too o << " (; "; printTypeOrName(curr->type, o, currModule); o << " ;)"; From b099733ac2c3f7aa8228e974df575a052a332dfe Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 16:54:29 -0700 Subject: [PATCH 31/94] fix --- src/passes/ExtractFunction.cpp | 6 ++++++ test/lit/passes/gufa.wast | 7 ++----- test/lit/passes/local-subtyping-nn.wast | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/passes/ExtractFunction.cpp b/src/passes/ExtractFunction.cpp index df53afbf2ba..3419d865fde 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/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index 7d20881b33e..bbee4f63362 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1187,18 +1187,15 @@ (module ;; CHECK: (type $func (sub (func))) (type $func (sub (func))) + (type $sub (sub $func (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: (export "test" (func $test)) ;; CHECK: (func $test (type $1) (result i32) - ;; CHECK-NEXT: (ref.test (ref $sub) - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $test (export "test") (result i32) (ref.test (ref $sub) 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) From 34837c333a6337f46dfb71bb4d494d94fc21cf4b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 22 Oct 2025 16:55:42 -0700 Subject: [PATCH 32/94] fix --- test/lit/passes/gsi-debug.wast | 2 +- test/lit/passes/local-subtyping.wast | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) 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/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) From 616b23a0545e184eaa1ffbdb17263b61cfd4c485 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 23 Oct 2025 08:45:25 -0700 Subject: [PATCH 33/94] fix --- src/passes/LegalizeJSInterface.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index af80d701af8..5cc34680fb5 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -367,8 +367,6 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { } } - auto prunedImport = false; - for (auto& func : module->functions) { // If the function is neither exported nor imported, no problem. auto imported = func->imported(); @@ -393,7 +391,6 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { if (imported) { func->module = func->base = Name(); func->type = func->type.with(Exact); - prunedImport = true; Builder builder(*module); if (sig.results == Type::none) { @@ -413,10 +410,8 @@ struct LegalizeAndPruneJSInterface : public LegalizeJSInterface { } } - if (prunedImport) { - // RefFunc types need updating. - ReFinalize().run(getPassRunner(), module); - } + // RefFunc types etc. need updating. + ReFinalize().run(getPassRunner(), module); // TODO: globals etc. } From 46b9d702621fa3600b10436735741155a1a80fa1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 23 Oct 2025 11:27:18 -0700 Subject: [PATCH 34/94] work --- src/ir/module-splitting.cpp | 9 +++++---- src/wasm-builder.h | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 380fef8385b..ac6481c8c6f 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -274,7 +274,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) { @@ -792,7 +793,7 @@ void ModuleSplitter::setupTablePatching() { placeholder->hasExplicitName = true; placeholder->type = secondaryFunc->type; elem = Builder(primary).makeRefFunc(placeholder->name, - placeholder->type.getHeapType()); + placeholder->type); primary.addFunction(std::move(placeholder)); }); @@ -834,7 +835,7 @@ void ModuleSplitter::setupTablePatching() { // function. auto* func = replacement->second; auto* ref = Builder(secondary).makeRefFunc(func->name, - func->type.getHeapType()); + func->type); secondaryElems.push_back(ref); ++replacement; } else if (auto* get = primarySeg->data[i]->dynCast()) { @@ -876,7 +877,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(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 7cde6e6479e..b1c10146dac 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -703,8 +703,10 @@ class Builder { 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; - ret->finalize(); return ret; } RefFunc* makeRefFunc(Name func, HeapType heapType) { From 8b5f7ddaa2df65dd32cbe17a94e30a969e7eab79 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 23 Oct 2025 12:01:26 -0700 Subject: [PATCH 35/94] fix --- src/ir/possible-contents.cpp | 12 +++++++++--- test/lit/passes/gufa.wast | 19 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 61d59181c9a..01b06cc62ad 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -642,9 +642,15 @@ struct InfoCollector addRoot(curr); } void visitRefFunc(RefFunc* curr) { - addRoot( - curr, - PossibleContents::literal(Literal::makeFunc(curr->func, *getModule()))); + 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. diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index bbee4f63362..93962d1935e 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1183,19 +1183,25 @@ ;; As above, but now the cast is to a subtype. We should not be able to optimize ;; this either. -;; TODO: We misoptimize this. Fix it! (module ;; CHECK: (type $func (sub (func))) (type $func (sub (func))) - (type $sub (sub $func (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: (i32.const 0) + ;; 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) @@ -1237,7 +1243,6 @@ ;; 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. -;; TODO: We misoptimize here, too! (module ;; CHECK: (type $func (func)) (type $func (sub final (func))) @@ -1245,10 +1250,14 @@ ;; 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: (unreachable) + ;; 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)) From c5413809315a271898ec81a02c7dcb201787aeb3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 10:55:03 -0700 Subject: [PATCH 36/94] work --- CHANGELOG.md | 3 +++ src/binaryen-c.cpp | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 224563f612a..b5cdd0a9972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ full changeset diff at the end of each section. Current Trunk ------------- + - C and JS APIs for creating ref.funcs have separate versions for references to + imported functions, which now have different types in the IR (to allow more + optimization, and to follow the changing Custom Descriptor spec). - 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/src/binaryen-c.cpp b/src/binaryen-c.cpp index 0a91860e49d..e418f583c8e 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -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, From 982af15acb22f66ae76a33a963b4b53a14875500 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 11:00:20 -0700 Subject: [PATCH 37/94] work --- src/ir/module-splitting.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index ac6481c8c6f..f7d95ec6e31 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" @@ -340,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), @@ -356,6 +358,7 @@ struct ModuleSplitter { setupTablePatching(); shareImportableItems(); removeUnusedSecondaryElements(); + updateIR(); } }; @@ -977,6 +980,13 @@ void ModuleSplitter::removeUnusedSecondaryElements() { } } +void ModuleSplitter::updateIR() { + // Imported functions may need type updates. + PassRunner runner(&primary); + ReFinalize().run(&runner, &primary); + ReFinalize().walkModuleCode(&primary); +} + } // anonymous namespace Results splitFunctions(Module& primary, const Config& config) { From e2de80603fd732c112cf53999b019374165c65cf Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 11:06:07 -0700 Subject: [PATCH 38/94] work --- src/ir/module-splitting.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index f7d95ec6e31..a32abdd3b69 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -376,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; @@ -520,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)); } } @@ -794,7 +795,7 @@ void ModuleSplitter::setupTablePatching() { placeholder->name = Names::getValidFunctionName( primary, std::string("placeholder_") + placeholder->base.toString()); placeholder->hasExplicitName = true; - placeholder->type = secondaryFunc->type; + placeholder->type = secondaryFunc->type.with(Inexact); elem = Builder(primary).makeRefFunc(placeholder->name, placeholder->type); primary.addFunction(std::move(placeholder)); @@ -981,7 +982,8 @@ void ModuleSplitter::removeUnusedSecondaryElements() { } void ModuleSplitter::updateIR() { - // Imported functions may need type updates. + std::cout << "waka update\n"; + // Imported functions may need type updates. XXX unneeded PassRunner runner(&primary); ReFinalize().run(&runner, &primary); ReFinalize().walkModuleCode(&primary); From 73bffc8afd7a87e06b9794d94045b3dd6841c8bd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 11:08:58 -0700 Subject: [PATCH 39/94] work --- src/ir/module-splitting.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index a32abdd3b69..2ea66ba603a 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -982,8 +982,7 @@ void ModuleSplitter::removeUnusedSecondaryElements() { } void ModuleSplitter::updateIR() { - std::cout << "waka update\n"; - // Imported functions may need type updates. XXX unneeded + // Imported functions may need type updates. XXX is this done elsewhere? PassRunner runner(&primary); ReFinalize().run(&runner, &primary); ReFinalize().walkModuleCode(&primary); From 8931d560720399f5214246e841d3ecec7417685d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 11:17:30 -0700 Subject: [PATCH 40/94] update --- test/passes/fuzz_metrics_noprint.bin.txt | 8 ++++---- .../translate-to-fuzz_all-features_metrics_noprint.txt | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) 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 From c2f9d151065fb28e591fc2dcb3c846502d84986d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 11:27:41 -0700 Subject: [PATCH 41/94] format --- src/ir/module-splitting.cpp | 6 ++---- src/passes/ExtractFunction.cpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 2ea66ba603a..0a000032461 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -796,8 +796,7 @@ void ModuleSplitter::setupTablePatching() { primary, std::string("placeholder_") + placeholder->base.toString()); placeholder->hasExplicitName = true; placeholder->type = secondaryFunc->type.with(Inexact); - elem = Builder(primary).makeRefFunc(placeholder->name, - placeholder->type); + elem = Builder(primary).makeRefFunc(placeholder->name, placeholder->type); primary.addFunction(std::move(placeholder)); }); @@ -838,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); + auto* ref = Builder(secondary).makeRefFunc(func->name, func->type); secondaryElems.push_back(ref); ++replacement; } else if (auto* get = primarySeg->data[i]->dynCast()) { diff --git a/src/passes/ExtractFunction.cpp b/src/passes/ExtractFunction.cpp index 3419d865fde..6faa40b9efd 100644 --- a/src/passes/ExtractFunction.cpp +++ b/src/passes/ExtractFunction.cpp @@ -51,7 +51,7 @@ static void extract(PassRunner* runner, Module* module, Name name) { // 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(); From 44faf20f49a75ba8fa3aa859b697f3d1c97f691a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 12:38:24 -0700 Subject: [PATCH 42/94] fix --- src/ir/module-splitting.cpp | 25 +++++++++-- test/lit/wasm-split/exact.wast | 80 ++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 test/lit/wasm-split/exact.wast diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 0a000032461..8ca9a427824 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -980,10 +980,27 @@ void ModuleSplitter::removeUnusedSecondaryElements() { } void ModuleSplitter::updateIR() { - // Imported functions may need type updates. XXX is this done elsewhere? - PassRunner runner(&primary); - ReFinalize().run(&runner, &primary); - ReFinalize().walkModuleCode(&primary); + // 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 diff --git a/test/lit/wasm-split/exact.wast b/test/lit/wasm-split/exact.wast new file mode 100644 index 00000000000..9cab9048ffa --- /dev/null +++ b/test/lit/wasm-split/exact.wast @@ -0,0 +1,80 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; 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 + +;; Get references to both functions from both modules, and ensure they are +;; exact. + +(module + ;; PRIMARY: (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: ) + (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: ) + (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: ) From 719bf2ccb1237c6e9c79c86136f9e5eb7d84a1e7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 12:41:40 -0700 Subject: [PATCH 43/94] fix --- test/lit/wasm-split/exact.wast | 60 +++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/test/lit/wasm-split/exact.wast b/test/lit/wasm-split/exact.wast index 9cab9048ffa..895b26e8eab 100644 --- a/test/lit/wasm-split/exact.wast +++ b/test/lit/wasm-split/exact.wast @@ -1,14 +1,20 @@ ;; 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-opt -all %t.1.wasm --print | filecheck %s --check-prefix=PRIMARY +;; RUN: wasm-opt -all %t.2.wasm --print | filecheck %s --check-prefix=SECONDARY -;; Get references to both functions from both modules, and ensure they are -;; exact. +;; 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))) @@ -32,6 +38,27 @@ ;; 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 @@ -63,6 +90,25 @@ ;; 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 @@ -78,3 +124,9 @@ ;; 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: ) From c24956c0f73d5d343df88651c96e5a5285139fc2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 13:13:42 -0700 Subject: [PATCH 44/94] fix --- src/ir/module-splitting.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 8ca9a427824..c49e6758a36 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -974,6 +974,8 @@ 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(); } From aea599651412810234eefbc9dc75bb9e3efa2dfc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 13:35:12 -0700 Subject: [PATCH 45/94] undo --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5cdd0a9972..224563f612a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,6 @@ full changeset diff at the end of each section. Current Trunk ------------- - - C and JS APIs for creating ref.funcs have separate versions for references to - imported functions, which now have different types in the IR (to allow more - optimization, and to follow the changing Custom Descriptor spec). - 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) From e2d9c70bbcfafea951f52eac3d8b0a760e63d843 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 14:22:17 -0700 Subject: [PATCH 46/94] update.tests --- CHANGELOG.md | 4 ++ .../wasm-split/multi-split-escape-names.wast | 24 +++++++--- test/lit/wasm-split/multi-split.wast | 48 ++++++++++++++----- test/lit/wasm-split/ref.func.wast | 4 +- 4 files changed, 61 insertions(+), 19 deletions(-) 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/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) From d360f0b46122fa734d196fd52a057e023f9f0e0f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 24 Oct 2025 16:16:19 -0700 Subject: [PATCH 47/94] fix --- scripts/fuzz_opt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index e0950b58c9c..7ecc52cae23 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2459,7 +2459,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(''' From 2d25b8d13e48db0c683227808175a3b84109b34f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 13:20:17 -0700 Subject: [PATCH 48/94] fix --- src/passes/InstrumentBranchHints.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/InstrumentBranchHints.cpp b/src/passes/InstrumentBranchHints.cpp index 34af0953662..723e9f43b30 100644 --- a/src/passes/InstrumentBranchHints.cpp +++ b/src/passes/InstrumentBranchHints.cpp @@ -193,6 +193,7 @@ 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. From bc122d54a78de3b30da7774bd1b8dbdc4a2043f9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 13:23:25 -0700 Subject: [PATCH 49/94] fix --- src/passes/InstrumentBranchHints.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/passes/InstrumentBranchHints.cpp b/src/passes/InstrumentBranchHints.cpp index 723e9f43b30..dac32399fad 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" @@ -207,6 +208,9 @@ struct InstrumentBranchHints // Walk normally, using logBranch as we go. Super::doWalkModule(module); + + // Update ref.func type changes. + ReFinalize().run(getPassRunner(), module); } }; From 5de1db0dc9d01b1b7d16b0e5cd760a9703eff7d1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 28 Oct 2025 13:25:08 -0700 Subject: [PATCH 50/94] fix --- src/passes/InstrumentBranchHints.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/passes/InstrumentBranchHints.cpp b/src/passes/InstrumentBranchHints.cpp index dac32399fad..981856c3be5 100644 --- a/src/passes/InstrumentBranchHints.cpp +++ b/src/passes/InstrumentBranchHints.cpp @@ -211,6 +211,7 @@ struct InstrumentBranchHints // Update ref.func type changes. ReFinalize().run(getPassRunner(), module); + ReFinalize().walkModuleCode(module); } }; From 8341afda491a1372670706c0b6c1a23fd8bb7e21 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 31 Oct 2025 11:12:11 -0700 Subject: [PATCH 51/94] fix signature of branch-hinting function --- src/passes/InstrumentBranchHints.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/passes/InstrumentBranchHints.cpp b/src/passes/InstrumentBranchHints.cpp index 981856c3be5..601ab72a2a4 100644 --- a/src/passes/InstrumentBranchHints.cpp +++ b/src/passes/InstrumentBranchHints.cpp @@ -200,7 +200,9 @@ struct InstrumentBranchHints // 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; From 457b0e515d1b4610370c3c7e8933ed017fc8d60b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 10:14:32 -0800 Subject: [PATCH 52/94] failing test --- test/lit/exec/imported-func.wast | 23 ++++++++++++++++ test/lit/exec/imported-func.wast.second | 36 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 test/lit/exec/imported-func.wast create mode 100644 test/lit/exec/imported-func.wast.second diff --git a/test/lit/exec/imported-func.wast b/test/lit/exec/imported-func.wast new file mode 100644 index 00000000000..83ca2567c55 --- /dev/null +++ b/test/lit/exec/imported-func.wast @@ -0,0 +1,23 @@ + +;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s + +;; The second module import a function of type $C as type $A, then casts to $A, +;; $B, $C. All those casts should succeed. + +(module + (rec + (type $A (sub (func))) + (type $B (sub $A (func))) + (type $C (sub final $B (func))) + ) + + (func $C (export "func") (type $C) + ) +) + +;; CHECK: [fuzz-exec] calling func +;; CHECK-NEXT: [fuzz-exec] running second module +;; CHECK-NEXT: [fuzz-exec] calling cast-A +;; CHECK-NEXT: [fuzz-exec] calling cast-B +;; CHECK-NEXT: [fuzz-exec] calling cast-C + diff --git a/test/lit/exec/imported-func.wast.second b/test/lit/exec/imported-func.wast.second new file mode 100644 index 00000000000..db5f9f5fcd3 --- /dev/null +++ b/test/lit/exec/imported-func.wast.second @@ -0,0 +1,36 @@ +;; Import a function of type $C as type $A, cast to $A, $B, $C. All those casts +;; should succeed. Exact casts, however TODO +(module + (rec + (type $A (sub (func))) + (type $B (sub $A (func))) + (type $C (sub final $B (func))) + ) + + (import "primary" "func" (func $fimport (type $A))) + + (func $cast-A (export "cast-A") + (drop + (ref.cast (ref $A) + (ref.func $fimport) + ) + ) + ) + + (func $cast-B (export "cast-B") + (drop + (ref.cast (ref $B) + (ref.func $fimport) + ) + ) + ) + + (func $cast-C (export "cast-C") + (drop + (ref.cast (ref $C) + (ref.func $fimport) + ) + ) + ) +) + From 848151afb753d849f4d604b0971edab89682882c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 10:29:00 -0800 Subject: [PATCH 53/94] fix the types of imported ref.funcs in the interpreter --- src/wasm-interpreter.h | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index a915518edb8..f8e007fb249 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1883,6 +1883,15 @@ class ExpressionRunner : public OverriddenVisitor { return Literal(int32_t(value.isNull())); } Flow visitRefFunc(RefFunc* curr) { + // The type may differ from the type in the IR: An imported function may + // have a more refined type than it was imported as. Imports are handled in + // subclasses. + auto* func = self()->getModule()->getFunction(curr->func); + if (func->imported()) { + return NONCONSTANT_FLOW; + } + // This is a defined function, so the type of the reference matches the + // actual function. return self()->makeFuncData(curr->func, curr->type); } Flow visitRefEq(RefEq* curr) { @@ -3462,7 +3471,7 @@ class ModuleRunnerBase : public ExpressionRunner { SmallVector, 4> exceptionStack; protected: - // Returns a reference to the current value of a potentially imported global + // Returns a reference to the current value of a potentially imported global. Literals& getGlobal(Name name) { auto* inst = self(); auto* global = inst->wasm.getGlobal(name); @@ -3475,6 +3484,21 @@ class ModuleRunnerBase : public ExpressionRunner { return inst->globals[global->name]; } + // As above, but for a function. + Literal getFunction(Name name) { + auto* inst = self(); + auto* func = inst->wasm.getFunction(name); + if (!func->imported()) { + return self()->makeFuncData(name, func->type); + } + auto iter = inst->linkedInstances.find(func->module); + if (iter == inst->linkedInstances.end()) { + return externalInterface->getImportedFunction(func); + } + inst = iter->second.get(); + return inst->getExportedFunction(func->base); + } + // Get a tag object while looking through imports, i.e., this uses the name as // the name of the tag in the current module, and finds the actual canonical // Tag* object for it: the Tag in this module, if not imported, and if @@ -4230,6 +4254,12 @@ class ModuleRunnerBase : public ExpressionRunner { } return {}; } + Flow visitRefFunc(RefFunc* curr) { + // Handle both imported and defined functions by finding the actual one that + // is referred to here. + auto func = self()->getFunction(curr->func); + return self()->makeFuncData(curr->func, func.type); + } Flow visitArrayNewData(ArrayNewData* curr) { VISIT(offsetFlow, curr->offset) VISIT(sizeFlow, curr->size) From e5081e32a9b5c1e1a8ed8aee0a9440003182d3e8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 11:01:26 -0800 Subject: [PATCH 54/94] fix spec tests --- src/wasm-interpreter.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f8e007fb249..2b98c12af31 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3492,7 +3492,12 @@ class ModuleRunnerBase : public ExpressionRunner { return self()->makeFuncData(name, func->type); } auto iter = inst->linkedInstances.find(func->module); - if (iter == inst->linkedInstances.end()) { + // wasm-shell builds a "spectest" module, but does *not* provide print + // methods there. Those arrive from getImportedFunction(). So we must call + // getImportedFunction() even if the linked instance exists, in the case + // that it does not provide the export we want. TODO: fix wasm-shell + if (iter == inst->linkedInstances.end() || + !inst->wasm.getExportOrNull(func->base)) { return externalInterface->getImportedFunction(func); } inst = iter->second.get(); From b99dd75cf4cedb40970fc6fc31737a4201fb1b46 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 12:17:47 -0800 Subject: [PATCH 55/94] fix another spec test --- src/shell-interface.h | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/shell-interface.h b/src/shell-interface.h index 2c38f6456d4..1cf30b14197 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -102,13 +102,21 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { } virtual ~ShellExternalInterface() = default; - ModuleRunner* getImportInstance(Importable* import) { + ModuleRunner* getImportInstanceOrNull(Importable* import) { auto it = linkedInstances.find(import->module); if (it == linkedInstances.end()) { + return nullptr; + } + return it->second.get(); + } + + ModuleRunner* getImportInstance(Importable* import) { + auto* ret = getImportInstanceOrNull(import); + if (!ret) { Fatal() << "getImportInstance: unknown import: " << import->module.str << "." << import->base.str; } - return it->second.get(); + return ret; } void init(Module& wasm, ModuleRunner& instance) override { @@ -158,11 +166,14 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { throw ExitException(); }), import->type); - } else if (auto* inst = getImportInstance(import)) { + } else if (auto* inst = getImportInstanceOrNull(import)) { return inst->getExportedFunction(import->base); } - Fatal() << "getImportedFunction: unknown import: " << import->module.str - << "." << import->name.str; + // This is not a known import. Create a literal for it, which is good enough + // if it is never called (see the ref_func.wast spec test, which does that). + std::cerr << "warning: getImportedFunction: unknown import: " + << import->module.str << "." << import->name.str << '\n'; + return Literal::makeFunc(import->name, import->type); } Tag* getImportedTag(Tag* tag) override { From 87e1094fc9fda215b38768315bf875353fbc68b2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 12:21:37 -0800 Subject: [PATCH 56/94] fmt --- src/shell-interface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-interface.h b/src/shell-interface.h index 1cf30b14197..7a42617e711 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -172,7 +172,7 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { // This is not a known import. Create a literal for it, which is good enough // if it is never called (see the ref_func.wast spec test, which does that). std::cerr << "warning: getImportedFunction: unknown import: " - << import->module.str << "." << import->name.str << '\n'; + << import->module.str << "." << import->name.str << '\n'; return Literal::makeFunc(import->name, import->type); } From de14c6a69b77aad956016163bc2215ce6a6f5c2b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 16:59:25 -0800 Subject: [PATCH 57/94] TODO: ExtractFunction casts --- src/passes/ExtractFunction.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/passes/ExtractFunction.cpp b/src/passes/ExtractFunction.cpp index 6faa40b9efd..1e388892b00 100644 --- a/src/passes/ExtractFunction.cpp +++ b/src/passes/ExtractFunction.cpp @@ -51,6 +51,8 @@ static void extract(PassRunner* runner, Module* module, Name name) { // Update function references after making things imports. ReFinalize().run(runner, module); ReFinalize().walkModuleCode(module); + // TODO: Add casts when needed for exactness, like wasm-split, to handle + // places that need the type remain exact. // Leave just one export, for the thing we want. module->exports.clear(); From 2802d948a65ce0e594117d113c1453bd26eeed60 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 17:00:51 -0800 Subject: [PATCH 58/94] Update test/lit/exec/imported-func.wast Co-authored-by: Thomas Lively --- test/lit/exec/imported-func.wast | 1 - 1 file changed, 1 deletion(-) diff --git a/test/lit/exec/imported-func.wast b/test/lit/exec/imported-func.wast index 83ca2567c55..ff1bb0c6cc8 100644 --- a/test/lit/exec/imported-func.wast +++ b/test/lit/exec/imported-func.wast @@ -1,4 +1,3 @@ - ;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s ;; The second module import a function of type $C as type $A, then casts to $A, From 33189d561e731461fe2334e4fe98d46ac067283e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 17:01:07 -0800 Subject: [PATCH 59/94] Update test/lit/exec/imported-func.wast Co-authored-by: Thomas Lively --- test/lit/exec/imported-func.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/exec/imported-func.wast b/test/lit/exec/imported-func.wast index ff1bb0c6cc8..cc24f27b1c9 100644 --- a/test/lit/exec/imported-func.wast +++ b/test/lit/exec/imported-func.wast @@ -1,6 +1,6 @@ ;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s -;; The second module import a function of type $C as type $A, then casts to $A, +;; The second module imports a function of type $C as type $A, then casts to $A, ;; $B, $C. All those casts should succeed. (module From aab3067f7aa581010c3cc09b368acc63b3377b6e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 17:02:18 -0800 Subject: [PATCH 60/94] Clean up test --- test/lit/exec/imported-func.wast.second | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/exec/imported-func.wast.second b/test/lit/exec/imported-func.wast.second index db5f9f5fcd3..e6353c9cc4a 100644 --- a/test/lit/exec/imported-func.wast.second +++ b/test/lit/exec/imported-func.wast.second @@ -1,5 +1,5 @@ ;; Import a function of type $C as type $A, cast to $A, $B, $C. All those casts -;; should succeed. Exact casts, however TODO +;; should succeed. (module (rec (type $A (sub (func))) From aa7290da3495d75fef2c8a07baaf2ee3682a5de5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 17:12:31 -0800 Subject: [PATCH 61/94] Simplify gufa test --- test/lit/passes/gufa.wast | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index 93962d1935e..49676b493d3 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1198,25 +1198,18 @@ ;; 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: (ref.func $f) ;; 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) - ) + (ref.func $f) ) ) ) -;; This is another exact cast, but now the imported type is final, so we know -;; it will succeed. +;; This is another exact cast, but now the imported type is final. TODO: Use +;; finality in this pass, as we could optimize here. (module ;; CHECK: (type $func (func)) (type $func (sub final (func))) From 89cf0c0c197f3c35d420c38ad95f8217783aa99a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 4 Nov 2025 20:10:53 -0800 Subject: [PATCH 62/94] test exact casts too --- test/lit/exec/imported-func.wast | 9 +++++++- test/lit/exec/imported-func.wast.second | 28 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/test/lit/exec/imported-func.wast b/test/lit/exec/imported-func.wast index cc24f27b1c9..fb837616d52 100644 --- a/test/lit/exec/imported-func.wast +++ b/test/lit/exec/imported-func.wast @@ -1,7 +1,8 @@ ;; RUN: wasm-opt %s -all --fuzz-exec-before --fuzz-exec-second=%s.second -q -o /dev/null 2>&1 | filecheck %s ;; The second module imports a function of type $C as type $A, then casts to $A, -;; $B, $C. All those casts should succeed. +;; $B, $C. All those casts should succeed. Of the exact casts only to $C should +;; succeed. (module (rec @@ -19,4 +20,10 @@ ;; CHECK-NEXT: [fuzz-exec] calling cast-A ;; CHECK-NEXT: [fuzz-exec] calling cast-B ;; CHECK-NEXT: [fuzz-exec] calling cast-C +;; CHECK-NEXT: [fuzz-exec] calling cast-A-exact +;; CHECK-NEXT: [trap cast error] +;; CHECK-NEXT: [fuzz-exec] calling cast-B-exact +;; CHECK-NEXT: [trap cast error] +;; CHECK-NEXT: [fuzz-exec] calling cast-C-exact +;; CHECK-NEXT: [fuzz-exec] calling last diff --git a/test/lit/exec/imported-func.wast.second b/test/lit/exec/imported-func.wast.second index e6353c9cc4a..2719654b7c6 100644 --- a/test/lit/exec/imported-func.wast.second +++ b/test/lit/exec/imported-func.wast.second @@ -32,5 +32,33 @@ ) ) ) + + (func $cast-A-exact (export "cast-A-exact") + (drop + (ref.cast (ref (exact $A)) + (ref.func $fimport) + ) + ) + ) + + (func $cast-B-exact (export "cast-B-exact") + (drop + (ref.cast (ref (exact $B)) + (ref.func $fimport) + ) + ) + ) + + (func $cast-C-exact (export "cast-C-exact") + (drop + (ref.cast (ref (exact $C)) + (ref.func $fimport) + ) + ) + ) + + (func $last (export "last") + ;; Just a non-trapping method to make the test output easier to read. + ) ) From 0a93101d679736c7219528ce9af3b0bccfcedf7c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 5 Nov 2025 09:15:47 -0800 Subject: [PATCH 63/94] split no-cd --- src/ir/module-splitting.cpp | 6 ++---- test/lit/wasm-split/ref.func.wast | 33 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index c49e6758a36..eaebc609fd0 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -991,10 +991,8 @@ void ModuleSplitter::updateIR() { // 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)); - } + // 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); } } diff --git a/test/lit/wasm-split/ref.func.wast b/test/lit/wasm-split/ref.func.wast index 306d8b64fe3..97787b6a947 100644 --- a/test/lit/wasm-split/ref.func.wast +++ b/test/lit/wasm-split/ref.func.wast @@ -7,6 +7,16 @@ ;; Test that we handle ref.func operations properly as we split out $second. ;; ref.funcs that refer to the other module must be fixed up to refer to ;; something in the same module, that then trampolines to the other. + +;; Also test without custom descriptors. The primary module is unchanged here, +;; while the secondary one still has the cast, but without exactness (that cast +;; has become trivial: it was needed in the IR when exactness matters, but +;; after binary writing removes exactness, it becomes trivial). + +;; RUN: wasm-split %s -all --disable-custom-descriptors --split-funcs=second,second-in-table -g -o1 %t.1.wasm -o2 %t.2.wasm +;; RUN: wasm-dis -all %t.1.wasm | filecheck %s --check-prefix PRIMARY +;; RUN: wasm-dis -all %t.2.wasm | filecheck %s --check-prefix SECONDARY_NOCD + (module ;; PRIMARY: (type $0 (func)) @@ -81,6 +91,26 @@ ;; SECONDARY-NEXT: (ref.func $second) ;; SECONDARY-NEXT: ) ;; SECONDARY-NEXT: ) + ;; SECONDARY_NOCD: (type $0 (func)) + + ;; SECONDARY_NOCD: (import "primary" "table_2" (table $timport$0 2 funcref)) + + ;; SECONDARY_NOCD: (import "primary" "prime" (func $prime (type $0))) + + ;; SECONDARY_NOCD: (elem $0 (i32.const 0) $second $second-in-table) + + ;; SECONDARY_NOCD: (elem declare func $prime) + + ;; SECONDARY_NOCD: (func $second (type $0) + ;; SECONDARY_NOCD-NEXT: (drop + ;; SECONDARY_NOCD-NEXT: (ref.cast (ref $0) + ;; SECONDARY_NOCD-NEXT: (ref.func $prime) + ;; SECONDARY_NOCD-NEXT: ) + ;; SECONDARY_NOCD-NEXT: ) + ;; SECONDARY_NOCD-NEXT: (drop + ;; SECONDARY_NOCD-NEXT: (ref.func $second) + ;; SECONDARY_NOCD-NEXT: ) + ;; SECONDARY_NOCD-NEXT: ) (func $second (drop (ref.func $prime) @@ -100,11 +130,14 @@ ;; SECONDARY: (func $second-in-table (type $0) ;; SECONDARY-NEXT: ) + ;; SECONDARY_NOCD: (func $second-in-table (type $0) + ;; SECONDARY_NOCD-NEXT: ) (func $second-in-table ;; As above, but in the secondary module. We still don't need a trampoline ;; (but we will get a placeholder, as all split-out functions do). ) ) + ;; PRIMARY: (func $trampoline_second (type $0) ;; PRIMARY-NEXT: (call_indirect $1 (type $0) ;; PRIMARY-NEXT: (i32.const 0) From 03f79703f26e07b941bda0016614a9bf7e01edaa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 5 Nov 2025 10:42:36 -0800 Subject: [PATCH 64/94] Revert "split no-cd" This reverts commit 0a93101d679736c7219528ce9af3b0bccfcedf7c. --- src/ir/module-splitting.cpp | 6 ++++-- test/lit/wasm-split/ref.func.wast | 33 ------------------------------- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index eaebc609fd0..c49e6758a36 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -991,8 +991,10 @@ void ModuleSplitter::updateIR() { // This became an import, and lost exactness. assert(!func->type.isExact()); assert(curr->type.isExact()); - // Add a cast, as the parent may depend on the exactness to validate. - replaceCurrent(Builder(wasm).makeRefCast(curr, curr->type)); + 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); } } diff --git a/test/lit/wasm-split/ref.func.wast b/test/lit/wasm-split/ref.func.wast index 97787b6a947..306d8b64fe3 100644 --- a/test/lit/wasm-split/ref.func.wast +++ b/test/lit/wasm-split/ref.func.wast @@ -7,16 +7,6 @@ ;; Test that we handle ref.func operations properly as we split out $second. ;; ref.funcs that refer to the other module must be fixed up to refer to ;; something in the same module, that then trampolines to the other. - -;; Also test without custom descriptors. The primary module is unchanged here, -;; while the secondary one still has the cast, but without exactness (that cast -;; has become trivial: it was needed in the IR when exactness matters, but -;; after binary writing removes exactness, it becomes trivial). - -;; RUN: wasm-split %s -all --disable-custom-descriptors --split-funcs=second,second-in-table -g -o1 %t.1.wasm -o2 %t.2.wasm -;; RUN: wasm-dis -all %t.1.wasm | filecheck %s --check-prefix PRIMARY -;; RUN: wasm-dis -all %t.2.wasm | filecheck %s --check-prefix SECONDARY_NOCD - (module ;; PRIMARY: (type $0 (func)) @@ -91,26 +81,6 @@ ;; SECONDARY-NEXT: (ref.func $second) ;; SECONDARY-NEXT: ) ;; SECONDARY-NEXT: ) - ;; SECONDARY_NOCD: (type $0 (func)) - - ;; SECONDARY_NOCD: (import "primary" "table_2" (table $timport$0 2 funcref)) - - ;; SECONDARY_NOCD: (import "primary" "prime" (func $prime (type $0))) - - ;; SECONDARY_NOCD: (elem $0 (i32.const 0) $second $second-in-table) - - ;; SECONDARY_NOCD: (elem declare func $prime) - - ;; SECONDARY_NOCD: (func $second (type $0) - ;; SECONDARY_NOCD-NEXT: (drop - ;; SECONDARY_NOCD-NEXT: (ref.cast (ref $0) - ;; SECONDARY_NOCD-NEXT: (ref.func $prime) - ;; SECONDARY_NOCD-NEXT: ) - ;; SECONDARY_NOCD-NEXT: ) - ;; SECONDARY_NOCD-NEXT: (drop - ;; SECONDARY_NOCD-NEXT: (ref.func $second) - ;; SECONDARY_NOCD-NEXT: ) - ;; SECONDARY_NOCD-NEXT: ) (func $second (drop (ref.func $prime) @@ -130,14 +100,11 @@ ;; SECONDARY: (func $second-in-table (type $0) ;; SECONDARY-NEXT: ) - ;; SECONDARY_NOCD: (func $second-in-table (type $0) - ;; SECONDARY_NOCD-NEXT: ) (func $second-in-table ;; As above, but in the secondary module. We still don't need a trampoline ;; (but we will get a placeholder, as all split-out functions do). ) ) - ;; PRIMARY: (func $trampoline_second (type $0) ;; PRIMARY-NEXT: (call_indirect $1 (type $0) ;; PRIMARY-NEXT: (i32.const 0) From b77d0f952136138ed17e4dba38c98c151982e1ac Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 5 Nov 2025 10:43:16 -0800 Subject: [PATCH 65/94] todo --- src/ir/module-splitting.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index c49e6758a36..ac88a2e6383 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -993,6 +993,7 @@ void ModuleSplitter::updateIR() { assert(curr->type.isExact()); if (wasm.features.hasCustomDescriptors()) { // Add a cast, as the parent may depend on the exactness to validate. + // TODO: The cast may be needed even without CD enabled replaceCurrent(Builder(wasm).makeRefCast(curr, curr->type)); } curr->type = curr->type.with(Inexact); From e26652b4bc4d17f6ffbc26e5f2f1eeeed43d77e9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 5 Nov 2025 14:21:43 -0800 Subject: [PATCH 66/94] live dangerously --- src/ir/possible-contents.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 01b06cc62ad..bde799115e3 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -642,15 +642,18 @@ struct InfoCollector addRoot(curr); } void visitRefFunc(RefFunc* curr) { - 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); - } + // TODO: This is not quite right for imported functions. In that case this + // does not represent a reference to a specific function, but to what + // was imported. In particular, two different imports might have + // identical values, like two imported globals. As function references + // are not comparable in wasm, this is not immediately dangerous, but + // in theory optimizations could get tripped up here. + // We should either fix this (perhaps add a new form of Literal value) + // or document this as the meaning of function literals (i.e. they + // mean something very different if imported). + addRoot( + curr, + PossibleContents::literal(Literal::makeFunc(curr->func, *getModule()))); // The presence of a RefFunc indicates the function may be called // indirectly, so add the relevant connections for this particular function. From d4bf64f36be004c3fcee1115bfc42ffedf73867d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 5 Nov 2025 17:13:30 -0800 Subject: [PATCH 67/94] Revert "live dangerously" This reverts commit e26652b4bc4d17f6ffbc26e5f2f1eeeed43d77e9. --- src/ir/possible-contents.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index bde799115e3..01b06cc62ad 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -642,18 +642,15 @@ struct InfoCollector addRoot(curr); } void visitRefFunc(RefFunc* curr) { - // TODO: This is not quite right for imported functions. In that case this - // does not represent a reference to a specific function, but to what - // was imported. In particular, two different imports might have - // identical values, like two imported globals. As function references - // are not comparable in wasm, this is not immediately dangerous, but - // in theory optimizations could get tripped up here. - // We should either fix this (perhaps add a new form of Literal value) - // or document this as the meaning of function literals (i.e. they - // mean something very different if imported). - addRoot( - curr, - PossibleContents::literal(Literal::makeFunc(curr->func, *getModule()))); + 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. From 2b756413c7a4491f1bcbed35bcdef16426a0c2b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:24:31 -0800 Subject: [PATCH 68/94] go --- src/ir/possible-contents.cpp | 14 ++++++++++---- src/ir/possible-contents.h | 32 ++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index b9f2213fc9a..6a47836f53c 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -208,9 +208,9 @@ void PossibleContents::intersect(const PossibleContents& other) { // Note the global's information, if we started as a global. In that case, the // code below will refine our type but we can remain a global, which we will // accomplish by restoring our global status at the end. - std::optional globalName; + std::optional global; if (isGlobal()) { - globalName = getGlobal(); + global = getGlobal(); } if (hasFullCone() && other.hasFullCone()) { @@ -230,9 +230,9 @@ void PossibleContents::intersect(const PossibleContents& other) { value = ConeType{newType, std::min(newDepth, otherNewDepth)}; } - if (globalName) { + if (global) { // Restore the global but keep the new and refined type. - value = GlobalInfo{*globalName, getType()}; + value = GlobalInfo{global->name, global->kind, getType()}; } } @@ -2826,6 +2826,12 @@ void Flower::filterExpressionContents(PossibleContents& contents, void Flower::filterGlobalContents(PossibleContents& contents, const GlobalLocation& globalLoc) { + // Function imports are always immutable. + auto kind = contents.getGlobal().kind; + if (kind == ExternalKind::Function) { + return; + } + assert(kind == ExternalKind::Global); auto* global = wasm.getGlobal(globalLoc.name); if (global->mutable_ == Immutable) { // This is an immutable global. We never need to consider this value as diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index eafb3d8d400..c3f89fd0568 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -40,10 +40,10 @@ namespace wasm { // // * Literal: One possible constant value like an i32 of 42. // -// * Global: The name of a global whose value is here. We do not know -// the actual value at compile time, but we know it is equal -// to that global. Typically we can only infer this for -// immutable globals. +// * Global: An immutable global value, something who we can identify +// but do not know the concree value of at runtime. This can +// be either a literal (immutable) global, or an imported +// function (which is effectively the same). // // * ConeType: Any possible value of a particular type, and a possible // "cone" of a certain depth below it. If the depth is 0 @@ -85,6 +85,7 @@ class PossibleContents { struct GlobalInfo { Name name; + ExternalKind kind; // The type of contents. Note that this may not match the type of the // global, if we were filtered. For example: // @@ -99,7 +100,7 @@ class PossibleContents { // way. In principle, not having depth info can lead to loss of // precision. bool operator==(const GlobalInfo& other) const { - return name == other.name && type == other.type; + return name == other.name && kind == other.kind && type == other.type; } }; @@ -144,8 +145,8 @@ class PossibleContents { static PossibleContents none() { return PossibleContents{None()}; } static PossibleContents literal(Literal c) { return PossibleContents{c}; } - static PossibleContents global(Name name, Type type) { - return PossibleContents{GlobalInfo{name, type}}; + static PossibleContents global(Name name, ExternalKind kind, Type type) { + return PossibleContents{GlobalInfo{name, kind, type}}; } // Helper for a cone type with depth 0, i.e., an exact type. static PossibleContents exactType(Type type) { @@ -216,9 +217,9 @@ class PossibleContents { return std::get(value); } - Name getGlobal() const { + GlobalInfo getGlobal() const { assert(isGlobal()); - return std::get(value).name; + return std::get(value); } bool isNull() const { return isLiteral() && getLiteral().isNull(); } @@ -316,11 +317,16 @@ class PossibleContents { if (isLiteral()) { return builder.makeConstantExpression(getLiteral()); } else { - auto name = getGlobal(); + auto info = getGlobal(); // Note that we load the type from the module, rather than use the type // in the GlobalInfo, as that type may not match the global (see comment // in the GlobalInfo declaration above). - return builder.makeGlobalGet(name, wasm.getGlobal(name)->type); + if (info.kind == ExternalKind::Global) { + return builder.makeGlobalGet(name, wasm.getGlobal(name)->type); + } else { + assert(info.kind == ExternalKind::Function) { + return builder.makeRefFunc(name, wasm.getFunction(name)->type.getHeapType()); + } } } @@ -352,6 +358,7 @@ class PossibleContents { rehash(ret, getLiteral()); } else if (auto* global = std::get_if(&value)) { rehash(ret, global->name); + rehash(ret, global->kind); rehash(ret, global->type); } else if (auto* coneType = std::get_if(&value)) { rehash(ret, coneType->type); @@ -374,7 +381,8 @@ class PossibleContents { o << " HT: " << h; } } else if (isGlobal()) { - o << "GlobalInfo $" << getGlobal() << " T: " << getType(); + auto info = getGlobal(); + o << "GlobalInfo $" << info.name << " K: " << info.kind << " T: " << getType(); } else if (auto* coneType = std::get_if(&value)) { auto t = coneType->type; o << "ConeType " << t; From 93221796b32da3a723b5924972f34f2bc0e7a7fd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:26:46 -0800 Subject: [PATCH 69/94] more --- src/ir/possible-contents.h | 8 ++++---- test/gtest/possible-contents.cpp | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index c3f89fd0568..1fa9ec1889a 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -322,10 +322,10 @@ class PossibleContents { // in the GlobalInfo, as that type may not match the global (see comment // in the GlobalInfo declaration above). if (info.kind == ExternalKind::Global) { - return builder.makeGlobalGet(name, wasm.getGlobal(name)->type); + return builder.makeGlobalGet(info.name, wasm.getGlobal(info.name)->type); } else { - assert(info.kind == ExternalKind::Function) { - return builder.makeRefFunc(name, wasm.getFunction(name)->type.getHeapType()); + assert(info.kind == ExternalKind::Function); + return builder.makeRefFunc(info.name, wasm.getFunction(info.name)->type.getHeapType()); } } } @@ -382,7 +382,7 @@ class PossibleContents { } } else if (isGlobal()) { auto info = getGlobal(); - o << "GlobalInfo $" << info.name << " K: " << info.kind << " T: " << getType(); + o << "GlobalInfo $" << info.name << " K: " << int(info.kind) << " T: " << getType(); } else if (auto* coneType = std::get_if(&value)) { auto t = coneType->type; o << "ConeType " << t; diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index 2633bdb3714..9c81f26efb8 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -80,14 +80,14 @@ class PossibleContentsTest : public testing::Test { PossibleContents::literal(Literal::makeNull(HeapType::i31)); PossibleContents i32Global1 = - PossibleContents::global("i32Global1", Type::i32); + PossibleContents::global("i32Global1", ExternalKind::Global, Type::i32); PossibleContents i32Global2 = - PossibleContents::global("i32Global2", Type::i32); - PossibleContents f64Global = PossibleContents::global("f64Global", Type::f64); - PossibleContents anyGlobal = PossibleContents::global("anyGlobal", anyref); - PossibleContents funcGlobal = PossibleContents::global("funcGlobal", funcref); + PossibleContents::global("i32Global2", ExternalKind::Global, Type::i32); + PossibleContents f64Global = PossibleContents::global("f64Global", ExternalKind::Global, Type::f64); + PossibleContents anyGlobal = PossibleContents::global("anyGlobal", ExternalKind::Global, anyref); + PossibleContents funcGlobal = PossibleContents::global("funcGlobal", ExternalKind::Global, funcref); PossibleContents nonNullFuncGlobal = - PossibleContents::global("funcGlobal", Type(HeapType::func, NonNullable)); + PossibleContents::global("funcGlobal", ExternalKind::Global, Type(HeapType::func, NonNullable)); PossibleContents nonNullFunc = PossibleContents::literal( Literal::makeFunc("func", Signature(Type::none, Type::none))); @@ -787,7 +787,7 @@ TEST_F(PossibleContentsTest, TestStructCones) { // Filter a global to a more specific type. assertIntersection(funcGlobal, PossibleContents::coneType(signature), - PossibleContents::global("funcGlobal", signature)); + PossibleContents::global("funcGlobal", ExternalKind::Global, signature)); // Filter a global's nullability only. auto nonNullFuncRef = Type(HeapType::func, NonNullable); From 3de0316a9c103dd5c10b79c82ae9e635aae95b9d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:27:10 -0800 Subject: [PATCH 70/94] more --- src/ir/possible-contents.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 6a47836f53c..5b5a4eff34c 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -2842,7 +2842,7 @@ void Flower::filterGlobalContents(PossibleContents& contents, // a cone/exact type *and* that something is equal to a global, in some // cases. See https://github.com/WebAssembly/binaryen/pull/5083 if (contents.isMany() || contents.isConeType()) { - contents = PossibleContents::global(global->name, global->type); + contents = PossibleContents::global(global->name, ExternalKind::Global, global->type); // TODO: We could do better here, to set global->init->type instead of // global->type, or even the contents.getType() - either of those From 176e8eb4f5bceb76f765dea2eb19d7eaf2cc6a01 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:28:06 -0800 Subject: [PATCH 71/94] more --- test/lit/passes/gufa.wast | 104 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index 721c27eeb66..49676b493d3 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1154,3 +1154,107 @@ ) ) ) + +;; 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: (ref.func $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (result i32) + (ref.test (ref $sub) + (ref.func $f) + ) + ) +) + +;; This is another exact cast, but now the imported type is final. TODO: Use +;; finality in this pass, as we could optimize here. +(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) + ) + ) +) From f40d2b128d13bfafff2f24811fc182edadbdbae7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:35:57 -0800 Subject: [PATCH 72/94] work --- src/ir/possible-contents.cpp | 20 ++++---- test/lit/passes/gufa.wast | 98 +++++------------------------------- 2 files changed, 23 insertions(+), 95 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 5b5a4eff34c..b0b32c10d50 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -641,9 +641,17 @@ 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, curr->type.getHeapType()))); + } else { + // This is imported, so it is effectively a global. + addRoot(curr, + PossibleContents::global(curr->func, ExternalKind::Function, curr->type) + ); + } // The presence of a RefFunc indicates the function may be called // indirectly, so add the relevant connections for this particular function. @@ -2826,12 +2834,6 @@ void Flower::filterExpressionContents(PossibleContents& contents, void Flower::filterGlobalContents(PossibleContents& contents, const GlobalLocation& globalLoc) { - // Function imports are always immutable. - auto kind = contents.getGlobal().kind; - if (kind == ExternalKind::Function) { - return; - } - assert(kind == ExternalKind::Global); auto* global = wasm.getGlobal(globalLoc.name); if (global->mutable_ == Immutable) { // This is an immutable global. We never need to consider this value as diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index 49676b493d3..ffb256c2702 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1155,106 +1155,32 @@ ) ) -;; We cannot know the types of imported functions, so we should not be able to -;; optimize this exact cast. +;; Imported functions can be inferred. (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: (ref.func $f) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (export "test") (result i32) - (ref.test (ref $sub) - (ref.func $f) - ) - ) -) - -;; This is another exact cast, but now the imported type is final. TODO: Use -;; finality in this pass, as we could optimize here. -(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) - ) - ) -) + ;; CHECK: (type $0 (func)) -;; 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: (import "" "" (func $f (type $0))) + (import "" "" (func $f)) + ;; 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: (local $temp funcref) + ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (ref.func $f) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.func $f) ;; CHECK-NEXT: ) (func $test (export "test") (result funcref) - (ref.cast (ref (exact $func)) + (local $temp funcref) + (local.set $temp (ref.func $f) ) + (local.get $temp) ) ) + From 32939a17dad86ead6bc70c604d8e025795542df1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:37:27 -0800 Subject: [PATCH 73/94] comment --- test/lit/passes/gufa.wast | 1 + 1 file changed, 1 insertion(+) diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index ffb256c2702..a7cbf9ed38d 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1180,6 +1180,7 @@ (local.set $temp (ref.func $f) ) + ;; This will become a ref.func $f. (local.get $temp) ) ) From b29bee080e063534655b71dd54d4d95c252f8d72 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:58:22 -0800 Subject: [PATCH 74/94] test --- test/gtest/possible-contents.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index 9c81f26efb8..e5875f947d1 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -89,6 +89,11 @@ class PossibleContentsTest : public testing::Test { PossibleContents nonNullFuncGlobal = PossibleContents::global("funcGlobal", ExternalKind::Global, Type(HeapType::func, NonNullable)); + PossibleContents importedFunc1 = + PossibleContents::global("impfunc1", ExternalKind::Function, Type(HeapType::func, NonNullable)); + PossibleContents importedFunc2 = + PossibleContents::global("impfunc2", ExternalKind::Function, Type(HeapType::func, NonNullable)); + PossibleContents nonNullFunc = PossibleContents::literal( Literal::makeFunc("func", Signature(Type::none, Type::none))); @@ -114,6 +119,7 @@ class PossibleContentsTest : public testing::Test { PossibleContents coneAnyref = PossibleContents::coneType(anyref); PossibleContents coneFuncref = PossibleContents::coneType(funcref); PossibleContents coneFuncref1 = PossibleContents::coneType(funcref, 1); + PossibleContents coneNonNullFuncref = PossibleContents::coneType(Type(HeapType::func, NonNullable)); }; TEST_F(PossibleContentsTest, TestComparisons) { @@ -135,6 +141,9 @@ TEST_F(PossibleContentsTest, TestComparisons) { assertNotEqualSymmetric(i32Global1, exactI32); assertNotEqualSymmetric(i32Global1, many); + assertEqualSymmetric(importedFunc1, importedFunc1); + assertNotEqualSymmetric(importedFunc1, importedFunc2); + assertEqualSymmetric(exactI32, exactI32); assertNotEqualSymmetric(exactI32, exactAnyref); assertNotEqualSymmetric(exactI32, many); @@ -257,6 +266,10 @@ TEST_F(PossibleContentsTest, TestCombinations) { assertCombination(anyGlobal, anyNull, coneAnyref); assertCombination(anyGlobal, i31Null, coneAnyref); + + // Imported functions. + assertCombination(importedFunc1, importedFunc1, importedFunc1); + assertCombination(importedFunc1, importedFunc2, coneNonNullFuncref); } static PassOptions options; @@ -338,6 +351,13 @@ TEST_F(PossibleContentsTest, TestIntersection) { // Separate hierarchies. assertLackIntersection(funcGlobal, anyGlobal); + + // Imported functions. + assertHaveIntersection(importedFunc1, importedFunc1); + assertHaveIntersection(importedFunc1, exactFuncSignatureType); + assertHaveIntersection(importedFunc1, exactNonNullFuncSignatureType); + assertHaveIntersection(importedFunc1, importedFunc2); + assertHaveIntersection(importedFunc1, funcGlobal); } TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { @@ -484,6 +504,8 @@ TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { funcGlobal, nonNullFuncGlobal, nonNullFunc, + importedFunc1, + importedFunc2, exactI32, exactAnyref, exactFuncref, @@ -814,6 +836,13 @@ TEST_F(PossibleContentsTest, TestStructCones) { assertIntersection(funcGlobal, none, none); assertIntersection(PossibleContents::coneType(signature), none, none); + // Imported functions. TODO: These are not yet supported, and assert instead. + // assertIntersection( + // importedFunc1, importedFunc1, importedFunc1); + // assertIntersection( + // importedFunc1, PossibleContents::coneType(nonNullFuncRef), + // importedFunc1); + // Subcontents. This API only supports the case where one of the inputs is a // full cone type. // First, compare exact types to such a cone. From 1e2feacaa0136cd3d1285d765f6ff6b967561a5f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:58:50 -0800 Subject: [PATCH 75/94] format --- src/ir/possible-contents.cpp | 7 ++++--- src/ir/possible-contents.h | 9 ++++++--- test/gtest/possible-contents.cpp | 33 ++++++++++++++++++-------------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index b0b32c10d50..a0ef03c197d 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -649,8 +649,8 @@ struct InfoCollector } else { // This is imported, so it is effectively a global. addRoot(curr, - PossibleContents::global(curr->func, ExternalKind::Function, curr->type) - ); + PossibleContents::global( + curr->func, ExternalKind::Function, curr->type)); } // The presence of a RefFunc indicates the function may be called @@ -2844,7 +2844,8 @@ void Flower::filterGlobalContents(PossibleContents& contents, // a cone/exact type *and* that something is equal to a global, in some // cases. See https://github.com/WebAssembly/binaryen/pull/5083 if (contents.isMany() || contents.isConeType()) { - contents = PossibleContents::global(global->name, ExternalKind::Global, global->type); + contents = PossibleContents::global( + global->name, ExternalKind::Global, global->type); // TODO: We could do better here, to set global->init->type instead of // global->type, or even the contents.getType() - either of those diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 1fa9ec1889a..5a964a4035f 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -322,10 +322,12 @@ class PossibleContents { // in the GlobalInfo, as that type may not match the global (see comment // in the GlobalInfo declaration above). if (info.kind == ExternalKind::Global) { - return builder.makeGlobalGet(info.name, wasm.getGlobal(info.name)->type); + return builder.makeGlobalGet(info.name, + wasm.getGlobal(info.name)->type); } else { assert(info.kind == ExternalKind::Function); - return builder.makeRefFunc(info.name, wasm.getFunction(info.name)->type.getHeapType()); + return builder.makeRefFunc( + info.name, wasm.getFunction(info.name)->type.getHeapType()); } } } @@ -382,7 +384,8 @@ class PossibleContents { } } else if (isGlobal()) { auto info = getGlobal(); - o << "GlobalInfo $" << info.name << " K: " << int(info.kind) << " T: " << getType(); + o << "GlobalInfo $" << info.name << " K: " << int(info.kind) + << " T: " << getType(); } else if (auto* coneType = std::get_if(&value)) { auto t = coneType->type; o << "ConeType " << t; diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index e5875f947d1..ca1f4313a27 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -83,16 +83,19 @@ class PossibleContentsTest : public testing::Test { PossibleContents::global("i32Global1", ExternalKind::Global, Type::i32); PossibleContents i32Global2 = PossibleContents::global("i32Global2", ExternalKind::Global, Type::i32); - PossibleContents f64Global = PossibleContents::global("f64Global", ExternalKind::Global, Type::f64); - PossibleContents anyGlobal = PossibleContents::global("anyGlobal", ExternalKind::Global, anyref); - PossibleContents funcGlobal = PossibleContents::global("funcGlobal", ExternalKind::Global, funcref); - PossibleContents nonNullFuncGlobal = - PossibleContents::global("funcGlobal", ExternalKind::Global, Type(HeapType::func, NonNullable)); - - PossibleContents importedFunc1 = - PossibleContents::global("impfunc1", ExternalKind::Function, Type(HeapType::func, NonNullable)); - PossibleContents importedFunc2 = - PossibleContents::global("impfunc2", ExternalKind::Function, Type(HeapType::func, NonNullable)); + PossibleContents f64Global = + PossibleContents::global("f64Global", ExternalKind::Global, Type::f64); + PossibleContents anyGlobal = + PossibleContents::global("anyGlobal", ExternalKind::Global, anyref); + PossibleContents funcGlobal = + PossibleContents::global("funcGlobal", ExternalKind::Global, funcref); + PossibleContents nonNullFuncGlobal = PossibleContents::global( + "funcGlobal", ExternalKind::Global, Type(HeapType::func, NonNullable)); + + PossibleContents importedFunc1 = PossibleContents::global( + "impfunc1", ExternalKind::Function, Type(HeapType::func, NonNullable)); + PossibleContents importedFunc2 = PossibleContents::global( + "impfunc2", ExternalKind::Function, Type(HeapType::func, NonNullable)); PossibleContents nonNullFunc = PossibleContents::literal( Literal::makeFunc("func", Signature(Type::none, Type::none))); @@ -119,7 +122,8 @@ class PossibleContentsTest : public testing::Test { PossibleContents coneAnyref = PossibleContents::coneType(anyref); PossibleContents coneFuncref = PossibleContents::coneType(funcref); PossibleContents coneFuncref1 = PossibleContents::coneType(funcref, 1); - PossibleContents coneNonNullFuncref = PossibleContents::coneType(Type(HeapType::func, NonNullable)); + PossibleContents coneNonNullFuncref = + PossibleContents::coneType(Type(HeapType::func, NonNullable)); }; TEST_F(PossibleContentsTest, TestComparisons) { @@ -807,9 +811,10 @@ TEST_F(PossibleContentsTest, TestStructCones) { nonNullFunc, PossibleContents::coneType(signature), nonNullFunc); // Filter a global to a more specific type. - assertIntersection(funcGlobal, - PossibleContents::coneType(signature), - PossibleContents::global("funcGlobal", ExternalKind::Global, signature)); + assertIntersection( + funcGlobal, + PossibleContents::coneType(signature), + PossibleContents::global("funcGlobal", ExternalKind::Global, signature)); // Filter a global's nullability only. auto nonNullFuncRef = Type(HeapType::func, NonNullable); From a53f7625b1c327d0c535bf26c2662b3825c06df6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 09:08:32 -0800 Subject: [PATCH 76/94] typo --- src/ir/possible-contents.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 5a964a4035f..6e0f110adb8 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -41,9 +41,10 @@ namespace wasm { // * Literal: One possible constant value like an i32 of 42. // // * Global: An immutable global value, something who we can identify -// but do not know the concree value of at runtime. This can -// be either a literal (immutable) global, or an imported -// function (which is effectively the same). +// but do not know the actual value of at runtime. This can +// be either a wasm (immutable) Global, or an imported wasm +// Function (which is effectively the same: we can refer to +// it, but do not know what is being imported there). // // * ConeType: Any possible value of a particular type, and a possible // "cone" of a certain depth below it. If the depth is 0 From 42914741368b03ee17887df29032dd48c1f223bc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 10:06:11 -0800 Subject: [PATCH 77/94] Show the current downside in a test --- test/lit/passes/gufa.wast | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index 49676b493d3..80c28ee78cf 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1155,6 +1155,51 @@ ) ) +;; Imported functions can be inferred. +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (import "" "" (func $imported (type $0))) + (import "" "" (func $imported)) + + ;; CHECK: (elem declare func $defined $imported) + + ;; CHECK: (func $defined (type $0) + ;; CHECK-NEXT: (local $temp funcref) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (ref.func $imported) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (ref.func $defined) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.func $defined) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $defined + (local $temp funcref) + (local.set $temp + (ref.func $imported) + ) + ;; This will become a ref.func $imported. TODO + (drop + (local.get $temp) + ) + + ;; Test a defined function too. + (local.set $temp + (ref.func $defined) + ) + ;; This will become a ref.func $defined. + (drop + (local.get $temp) + ) + ) +) + ;; We cannot know the types of imported functions, so we should not be able to ;; optimize this exact cast. (module From 27222bb636a7c7205ba21edeb26ceb4f89c95118 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 10:33:43 -0800 Subject: [PATCH 78/94] fix --- src/ir/possible-contents.cpp | 2 -- src/passes/GUFA.cpp | 30 +++++++++++++++++++----------- test/lit/passes/gufa.wast | 4 ++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 64247c8c50d..f43702123ce 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -3142,8 +3142,6 @@ void Flower::dump(Location location) { std::cout << " sigparamloc " << '\n'; } else if (auto* loc = std::get_if(&location)) { std::cout << " sigresultloc " << loc->type << " : " << loc->index << '\n'; - } else if (auto* loc = std::get_if(&location)) { - std::cout << " rootloc " << loc->type << '\n'; } else { std::cout << " (other)\n"; } diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index c5d0bbbe3a7..a4567aaea6d 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -173,16 +173,9 @@ struct GUFAOptimizer } else { // The type is not compatible: we cannot place |c| in this location, even // though we have proven it is the only value possible here. - if (Properties::isConstantExpression(c)) { - // The type is not compatible and this is a simple constant expression - // like a ref.func. That means this code must be unreachable. (See below - // for the case of a non-constant.) - replaceCurrent(getDroppedChildrenAndAppend( - curr, wasm, options, builder.makeUnreachable())); - optimized = true; - } else { + if (c->is() || c->is()) { // This is not a constant expression, but we are certain it is the right - // value. Atm the only such case we handle is a global.get of an + // value. One such case that can happen is a global.get of an // immutable global. We don't know what the value will be, nor its // specific type, but we do know that a global.get will get that value // properly. However, in this case it does not have the right type for @@ -204,9 +197,24 @@ struct GUFAOptimizer // non-nullable type like a ref.as_non_null must have, so we cannot // simply replace it. // + // Similarly, an imported function can cause this: imagine + // + // (ref.cast (exact ..) + // (ref.func $imported) + // ) + // + // The output of the cast is exact, but |c| is a RefFunc of inexact + // type. (In practice this will either trap at runtime or not.) + // // For now, do nothing here, but in some cases we could probably - // optimize (e.g. by adding a ref.as_non_null in the example) TODO - assert(c->is()); + // optimize (e.g. by adding a ref.as_non_null in the first example) TODO + } else { + // Otherwise, the type is not compatible and this is a simple constant + // expression. That means this code must be unreachable. + assert(Properties::isConstantExpression(c)); + replaceCurrent(getDroppedChildrenAndAppend( + curr, wasm, options, builder.makeUnreachable())); + optimized = true; } } } diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index fb99f149ea4..cf9e46cc36e 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1170,7 +1170,7 @@ ;; CHECK-NEXT: (ref.func $imported) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: (ref.func $imported) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (ref.func $defined) @@ -1184,7 +1184,7 @@ (local.set $temp (ref.func $imported) ) - ;; This will become a ref.func $imported. TODO + ;; This will become a ref.func $imported. (drop (local.get $temp) ) From 1f3d700dccd81bf1f26fdf3d51d6716d0a79c015 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 11:11:52 -0800 Subject: [PATCH 79/94] Add a test for a global and function with identical name --- test/gtest/possible-contents.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index ca1f4313a27..4a42c0098a5 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -164,6 +164,23 @@ TEST_F(PossibleContentsTest, TestComparisons) { assertNotEqualSymmetric(exactNonNullAnyref, exactAnyref); } +TEST_F(PossibleContentsTest, TestComparisonsGlobals) { + // Check if two PossibleContents::global, one a wasm Global and one a wasm + // Function, and equal in their names and types, are still understood to be + // non-equal: the |kind| field differentiates them. + + PossibleContents wasmGlobal = PossibleContents::global( + "foo", ExternalKind::Global, Type(HeapType::func, NonNullable)); + PossibleContents wasmFunction = PossibleContents::global( + "foo", ExternalKind::Function, Type(HeapType::func, NonNullable)); + + assertNotEqualSymmetric(wasmGlobal, wasmFunction); + + // But they are equal to themselves, of course. + assertEqualSymmetric(wasmGlobal, wasmGlobal); + assertEqualSymmetric(wasmFunction, wasmFunction); +} + TEST_F(PossibleContentsTest, TestHash) { // Hashes should be deterministic. EXPECT_EQ(none.hash(), none.hash()); From ed50e3c6918108bc2952bcfb1c7277e191426d07 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 12:38:54 -0800 Subject: [PATCH 80/94] validation: move function exactness check to visitFunction --- src/wasm/wasm-validator.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index b7487a9d101..413dd215c6e 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2389,14 +2389,6 @@ void FunctionValidator::visitRefFunc(RefFunc* curr) { func->type, curr, "function reference type must match referenced function type"); - 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) { @@ -4167,6 +4159,15 @@ void FunctionValidator::visitFunction(Function* curr) { curr->name, "all used types should be allowed"); + if (curr->imported()) { + shouldBeTrue(!curr->type.isExact(), + curr, + "imported function should be inexact"); + } else { + shouldBeTrue( + curr->type.isExact(), curr, "defined function should be exact"); + } + // validate optional local names std::unordered_set seen; for (auto& pair : curr->localNames) { From f1264d5349df96605a73e48fa6ab34e2e1ad2910 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 12:49:01 -0800 Subject: [PATCH 81/94] fix --- src/passes/InstrumentBranchHints.cpp | 2 +- src/wasm/wasm-validator.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/passes/InstrumentBranchHints.cpp b/src/passes/InstrumentBranchHints.cpp index 601ab72a2a4..ac87b8f5ecf 100644 --- a/src/passes/InstrumentBranchHints.cpp +++ b/src/passes/InstrumentBranchHints.cpp @@ -202,7 +202,7 @@ struct InstrumentBranchHints Names::getValidFunctionName(*module, BASE), Type(Signature({Type::i32, Type::i32, Type::i32}, Type::none), NonNullable, - Exact), + Inexact), {})); func->module = MODULE; func->base = BASE; diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 413dd215c6e..e64604e5941 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4161,11 +4161,11 @@ void FunctionValidator::visitFunction(Function* curr) { if (curr->imported()) { shouldBeTrue(!curr->type.isExact(), - curr, + curr->name, "imported function should be inexact"); } else { shouldBeTrue( - curr->type.isExact(), curr, "defined function should be exact"); + curr->type.isExact(), curr->name, "defined function should be exact"); } // validate optional local names From c96c5abb5cfef5cf5c48e9b16db3eddad7e6fe56 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:00:07 -0800 Subject: [PATCH 82/94] fix --- src/abi/js.h | 3 ++- src/wasm/wasm-validator.cpp | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/abi/js.h b/src/abi/js.h index b8f56f14d64..54394dd9f10 100644 --- a/src/abi/js.h +++ b/src/abi/js.h @@ -58,7 +58,8 @@ inline void ensureHelpers(Module* wasm, IString specific = IString()) { if (specific.is() && name != specific) { return; } - auto func = Builder::makeFunction(name, Signature(params, results), {}); + auto func = Builder::makeFunction( + name, Type(Signature(params, results), NonNullable, Inexact), {}); func->module = ENV; func->base = name; wasm->addFunction(std::move(func)); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index e64604e5941..bd64dc4cead 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4160,9 +4160,8 @@ void FunctionValidator::visitFunction(Function* curr) { "all used types should be allowed"); if (curr->imported()) { - shouldBeTrue(!curr->type.isExact(), - curr->name, - "imported function should be inexact"); + shouldBeTrue( + !curr->type.isExact(), curr->name, "imported function should be inexact"); } else { shouldBeTrue( curr->type.isExact(), curr->name, "defined function should be exact"); From 7ba83e8ec0e8afde8db22636da5bd924d6afe9c5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:06:17 -0800 Subject: [PATCH 83/94] fix --- src/passes/JSPI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/JSPI.cpp b/src/passes/JSPI.cpp index 4f77074d2ab..6a95f43c2de 100644 --- a/src/passes/JSPI.cpp +++ b/src/passes/JSPI.cpp @@ -99,7 +99,7 @@ struct JSPI : public Pass { // version will be created. 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; From b78076067e09b24a0fe3bb32b307b6e69f9ad2a7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:20:17 -0800 Subject: [PATCH 84/94] fix --- src/passes/SafeHeap.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/passes/SafeHeap.cpp b/src/passes/SafeHeap.cpp index 52332149050..c8a2a1122f8 100644 --- a/src/passes/SafeHeap.cpp +++ b/src/passes/SafeHeap.cpp @@ -166,7 +166,9 @@ struct SafeHeap : public Pass { sbrk = existing->name; } else { auto import = Builder::makeFunction( - GET_SBRK_PTR, Signature(Type::none, addressType), {}); + GET_SBRK_PTR, + Type(Signature(Type::none, addressType), NonNullable, Inexact), + {}); getSbrkPtr = GET_SBRK_PTR; import->module = ENV; import->base = GET_SBRK_PTR; @@ -176,7 +178,9 @@ struct SafeHeap : public Pass { segfault = existing->name; } else { auto import = Builder::makeFunction( - SEGFAULT_IMPORT, Signature(Type::none, Type::none), {}); + SEGFAULT_IMPORT, + Type(Signature(Type::none, Type::none), NonNullable, Inexact), + {}); segfault = SEGFAULT_IMPORT; import->module = ENV; import->base = SEGFAULT_IMPORT; @@ -186,7 +190,9 @@ struct SafeHeap : public Pass { alignfault = existing->name; } else { auto import = Builder::makeFunction( - ALIGNFAULT_IMPORT, Signature(Type::none, Type::none), {}); + ALIGNFAULT_IMPORT, + Type(Signature(Type::none, Type::none), NonNullable, Inexact), + {}); alignfault = ALIGNFAULT_IMPORT; import->module = ENV; From 4b6963b50e1accc9fb557ccff9485440711aba3d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:22:40 -0800 Subject: [PATCH 85/94] fix --- src/passes/TrapMode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/TrapMode.cpp b/src/passes/TrapMode.cpp index 5c0bb708ec7..8a2b8f8c952 100644 --- a/src/passes/TrapMode.cpp +++ b/src/passes/TrapMode.cpp @@ -254,7 +254,7 @@ void ensureF64ToI64JSImport(TrappingFunctionContainer& trappingFunctions) { import->name = F64_TO_INT; import->module = ASM2WASM; import->base = F64_TO_INT; - import->type = import->type.with(Signature(Type::f64, Type::i32)); + import->type = Type(Signature(Type::f64, Type::i32), NonNullable, Inexact); trappingFunctions.addImport(import); } From e23c1e669dd7a7f10da4a0ac7b2538b9badb11dc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:28:27 -0800 Subject: [PATCH 86/94] fix --- src/passes/JSPI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/JSPI.cpp b/src/passes/JSPI.cpp index 6a95f43c2de..61b9441f115 100644 --- a/src/passes/JSPI.cpp +++ b/src/passes/JSPI.cpp @@ -278,7 +278,7 @@ struct JSPI : public Pass { call->type = im->getResults(); stub->body = block; wrapperIm->type = - Type(Signature(Type(params), call->type), NonNullable, Exact); + Type(Signature(Type(params), call->type), NonNullable, Inexact); if (wasmSplit && im->name == ModuleSplitting::LOAD_SECONDARY_MODULE) { // In non-debug builds the name of the JSPI wrapper function for loading From 54f1f3565bf7b0236e25b25879a29261e25cae61 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:29:31 -0800 Subject: [PATCH 87/94] fix --- src/passes/JSPI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/JSPI.cpp b/src/passes/JSPI.cpp index 61b9441f115..65cf944b47c 100644 --- a/src/passes/JSPI.cpp +++ b/src/passes/JSPI.cpp @@ -235,7 +235,7 @@ struct JSPI : public Pass { wrapperIm->base = im->base; auto stub = std::make_unique(); stub->name = Name(im->name.str); - stub->type = im->type; + stub->type = im->type.with(Exact); auto* call = module->allocator.alloc(); call->target = wrapperIm->name; From f4a3446da315009ab8f1e8d2932a338d997a409b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:48:47 -0800 Subject: [PATCH 88/94] fix --- src/passes/TraceCalls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/TraceCalls.cpp b/src/passes/TraceCalls.cpp index 9fa4e374dfa..665b429ff23 100644 --- a/src/passes/TraceCalls.cpp +++ b/src/passes/TraceCalls.cpp @@ -208,7 +208,7 @@ struct TraceCalls : public Pass { tracerName, Type(Signature(getTracerParamsType(info, f), Type::none), NonNullable, - Exact), + Inexact), {}); import->module = ENV; import->base = tracerName; From ea4499541413cb4903625249a93e0ad9a784e53d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:49:51 -0800 Subject: [PATCH 89/94] fix --- src/passes/StringLowering.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index fc93fdc17b3..cf2efad280e 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -359,7 +359,7 @@ struct StringLowering : public StringGathering { auto sig = Signature(params, results); Builder builder(*module); auto* func = module->addFunction( - builder.makeFunction(name, Type(sig, NonNullable, Exact), {})); + builder.makeFunction(name, Type(sig, NonNullable, Inexact), {})); func->module = WasmStringsModule; func->base = trueName; return name; From 405b04498dbc0ee0379d8aa0b7eb19b8cb1a209e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:51:19 -0800 Subject: [PATCH 90/94] fix --- src/passes/SignatureRefining.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp index bb45939fa3b..b3884fa7046 100644 --- a/src/passes/SignatureRefining.cpp +++ b/src/passes/SignatureRefining.cpp @@ -345,7 +345,7 @@ struct SignatureRefining : public Pass { auto name = Names::getValidFunctionName(*module, import->name); auto newImport = module->addFunction(Builder(*module).makeFunction( - name, Type(newType, NonNullable, Exact), {})); + name, Type(newType, NonNullable, Inexact), {})); // Copy the binaryen intrinsic module.base import names. newImport->module = import->module; From d5ee2393c00490aaad68507413abb69b2c5a7bb5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:52:15 -0800 Subject: [PATCH 91/94] fix --- src/passes/LogExecution.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/LogExecution.cpp b/src/passes/LogExecution.cpp index 98b74197ad1..879085d11bb 100644 --- a/src/passes/LogExecution.cpp +++ b/src/passes/LogExecution.cpp @@ -69,7 +69,7 @@ struct LogExecution : public WalkerPass> { void visitModule(Module* curr) { // Add the import auto import = - Builder::makeFunction(LOGGER, Signature(Type::i32, Type::none), {}); + Builder::makeFunction(LOGGER, Type(Signature(Type::i32, Type::none), NonNullable, Inexact), {}); if (loggerModule != "") { import->module = loggerModule; From c5243e605d7b5b6307c378bfe0c1d5441c61f418 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:53:05 -0800 Subject: [PATCH 92/94] fix --- src/passes/InstrumentLocals.cpp | 2 +- src/passes/InstrumentMemory.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/InstrumentLocals.cpp b/src/passes/InstrumentLocals.cpp index bf5de644cc4..6d3a2a452be 100644 --- a/src/passes/InstrumentLocals.cpp +++ b/src/passes/InstrumentLocals.cpp @@ -195,7 +195,7 @@ struct InstrumentLocals : public WalkerPass> { Index id = 0; void addImport(Module* wasm, Name name, Type params, Type results) { - auto import = Builder::makeFunction(name, Signature(params, results), {}); + auto import = Builder::makeFunction(name, Type(Signature(params, results), NonNullable, Inexact), {}); import->module = ENV; import->base = name; wasm->addFunction(std::move(import)); diff --git a/src/passes/InstrumentMemory.cpp b/src/passes/InstrumentMemory.cpp index d3b9f80f298..6aed2dbb8c3 100644 --- a/src/passes/InstrumentMemory.cpp +++ b/src/passes/InstrumentMemory.cpp @@ -345,7 +345,7 @@ struct AddInstrumentation : public WalkerPass> { InstructionFilter filter; void addImport(Module* curr, Name name, Type params, Type results) { - auto import = Builder::makeFunction(name, Signature(params, results), {}); + auto import = Builder::makeFunction(name, Type(Signature(params, results), NonNullable, Inexact), {}); import->module = ENV; import->base = name; curr->addFunction(std::move(import)); From 920e019eb2fbbc5637227236cefeeb123e4695de Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 13:53:26 -0800 Subject: [PATCH 93/94] fix --- src/passes/InstrumentLocals.cpp | 3 ++- src/passes/InstrumentMemory.cpp | 3 ++- src/passes/LogExecution.cpp | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/passes/InstrumentLocals.cpp b/src/passes/InstrumentLocals.cpp index 6d3a2a452be..267e806916e 100644 --- a/src/passes/InstrumentLocals.cpp +++ b/src/passes/InstrumentLocals.cpp @@ -195,7 +195,8 @@ struct InstrumentLocals : public WalkerPass> { Index id = 0; void addImport(Module* wasm, Name name, Type params, Type results) { - auto import = Builder::makeFunction(name, Type(Signature(params, results), NonNullable, Inexact), {}); + auto import = Builder::makeFunction( + name, Type(Signature(params, results), NonNullable, Inexact), {}); import->module = ENV; import->base = name; wasm->addFunction(std::move(import)); diff --git a/src/passes/InstrumentMemory.cpp b/src/passes/InstrumentMemory.cpp index 6aed2dbb8c3..ddbac6a1a69 100644 --- a/src/passes/InstrumentMemory.cpp +++ b/src/passes/InstrumentMemory.cpp @@ -345,7 +345,8 @@ struct AddInstrumentation : public WalkerPass> { InstructionFilter filter; void addImport(Module* curr, Name name, Type params, Type results) { - auto import = Builder::makeFunction(name, Type(Signature(params, results), NonNullable, Inexact), {}); + auto import = Builder::makeFunction( + name, Type(Signature(params, results), NonNullable, Inexact), {}); import->module = ENV; import->base = name; curr->addFunction(std::move(import)); diff --git a/src/passes/LogExecution.cpp b/src/passes/LogExecution.cpp index 879085d11bb..c194fdc5ac0 100644 --- a/src/passes/LogExecution.cpp +++ b/src/passes/LogExecution.cpp @@ -68,8 +68,8 @@ struct LogExecution : public WalkerPass> { void visitModule(Module* curr) { // Add the import - auto import = - Builder::makeFunction(LOGGER, Type(Signature(Type::i32, Type::none), NonNullable, Inexact), {}); + auto import = Builder::makeFunction( + LOGGER, Type(Signature(Type::i32, Type::none), NonNullable, Inexact), {}); if (loggerModule != "") { import->module = loggerModule; From 84c82f2a7c0126759e6dd9efc7023ce2ce47e9d7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 14:04:56 -0800 Subject: [PATCH 94/94] fix --- src/binaryen-c.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index e418f583c8e..1d4077145ec 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -5109,9 +5109,9 @@ void BinaryenAddFunctionImport(BinaryenModuleRef module, func->name = internalName; func->module = externalModuleName; func->base = externalBaseName; - // TODO: Take a HeapType rather than params and results. + // TODO: Take a Type rather than params and results. func->type = - Type(Signature(Type(params), Type(results)), NonNullable, Exact); + Type(Signature(Type(params), Type(results)), NonNullable, Inexact); ((Module*)module)->addFunction(std::move(func)); } else { // already exists so just set module and base