Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
0967b41
Allow funcref literals to have inexact types
tlively Oct 8, 2025
7cbdfea
marge
kripken Oct 21, 2025
82ef0e0
fix
kripken Oct 21, 2025
d857e7d
fix
kripken Oct 21, 2025
d1d2ed8
fix
kripken Oct 21, 2025
e26f676
fix
kripken Oct 21, 2025
7fda8cf
work
kripken Oct 22, 2025
5ce973b
work
kripken Oct 22, 2025
4b167eb
work
kripken Oct 22, 2025
7927749
work
kripken Oct 22, 2025
160123e
work
kripken Oct 22, 2025
b8bb97a
work
kripken Oct 22, 2025
89103b9
work
kripken Oct 22, 2025
24dea81
work
kripken Oct 22, 2025
d4aabd2
work
kripken Oct 22, 2025
a4fe585
work
kripken Oct 22, 2025
76eac4c
work
kripken Oct 22, 2025
020f119
work
kripken Oct 22, 2025
21588c7
work
kripken Oct 22, 2025
fdd7253
work
kripken Oct 22, 2025
7a58395
work
kripken Oct 22, 2025
4616380
work
kripken Oct 22, 2025
46826b0
work
kripken Oct 22, 2025
73024ea
work
kripken Oct 22, 2025
4de7694
work
kripken Oct 22, 2025
d03376d
work
kripken Oct 22, 2025
82061c8
work
kripken Oct 22, 2025
1859cf0
work
kripken Oct 22, 2025
1791066
work
kripken Oct 22, 2025
7278c6a
format
kripken Oct 22, 2025
31573ac
work
kripken Oct 22, 2025
b099733
fix
kripken Oct 22, 2025
34837c3
fix
kripken Oct 22, 2025
616b23a
fix
kripken Oct 23, 2025
46b9d70
work
kripken Oct 23, 2025
8b5f7dd
fix
kripken Oct 23, 2025
c541380
work
kripken Oct 24, 2025
982af15
work
kripken Oct 24, 2025
e2de806
work
kripken Oct 24, 2025
73bffc8
work
kripken Oct 24, 2025
8931d56
update
kripken Oct 24, 2025
c2f9d15
format
kripken Oct 24, 2025
44faf20
fix
kripken Oct 24, 2025
719bf2c
fix
kripken Oct 24, 2025
c24956c
fix
kripken Oct 24, 2025
aea5996
undo
kripken Oct 24, 2025
e2d9c70
update.tests
kripken Oct 24, 2025
d360f0b
fix
kripken Oct 24, 2025
2d25b8d
fix
kripken Oct 28, 2025
bc122d5
fix
kripken Oct 28, 2025
5de1db0
fix
kripken Oct 28, 2025
61e0b66
Merge remote-tracking branch 'origin/main' into import.func.type
kripken Oct 31, 2025
8341afd
fix signature of branch-hinting function
kripken Oct 31, 2025
7d40a94
Merge remote-tracking branch 'origin/main' into import.func.type
kripken Nov 4, 2025
457b0e5
failing test
kripken Nov 4, 2025
848151a
fix the types of imported ref.funcs in the interpreter
kripken Nov 4, 2025
a6ff39a
Merge remote-tracking branch 'myself/import.func.type' into import.fu…
kripken Nov 4, 2025
e5081e3
fix spec tests
kripken Nov 4, 2025
b99dd75
fix another spec test
kripken Nov 4, 2025
87e1094
fmt
kripken Nov 4, 2025
de14c6a
TODO: ExtractFunction casts
kripken Nov 5, 2025
2802d94
Update test/lit/exec/imported-func.wast
kripken Nov 5, 2025
33189d5
Update test/lit/exec/imported-func.wast
kripken Nov 5, 2025
aab3067
Clean up test
kripken Nov 5, 2025
aa7290d
Simplify gufa test
kripken Nov 5, 2025
89cf0c0
test exact casts too
kripken Nov 5, 2025
0a93101
split no-cd
kripken Nov 5, 2025
03f7970
Revert "split no-cd"
kripken Nov 5, 2025
b77d0f9
todo
kripken Nov 5, 2025
e26652b
live dangerously
kripken Nov 5, 2025
d4bf64f
Revert "live dangerously"
kripken Nov 6, 2025
2b75641
go
kripken Nov 6, 2025
9322179
more
kripken Nov 6, 2025
3de0316
more
kripken Nov 6, 2025
176e8eb
more
kripken Nov 6, 2025
f40d2b1
work
kripken Nov 6, 2025
32939a1
comment
kripken Nov 6, 2025
b29bee0
test
kripken Nov 6, 2025
1e2feac
format
kripken Nov 6, 2025
a53f762
typo
kripken Nov 6, 2025
4291474
Show the current downside in a test
kripken Nov 6, 2025
9e0d1e0
Merge remote-tracking branch 'myself/global.func.imp' into import.fun…
kripken Nov 6, 2025
27222bb
fix
kripken Nov 6, 2025
1f3d700
Add a test for a global and function with identical name
kripken Nov 6, 2025
e1a8629
Merge remote-tracking branch 'myself/global.func.imp' into import.fun…
kripken Nov 6, 2025
7013b9f
Merge remote-tracking branch 'origin/main' into import.func.typePLUSgfi
kripken Nov 6, 2025
ed50e3c
validation: move function exactness check to visitFunction
kripken Nov 6, 2025
f1264d5
fix
kripken Nov 6, 2025
c96c5ab
fix
kripken Nov 6, 2025
7ba83e8
fix
kripken Nov 6, 2025
b780760
fix
kripken Nov 6, 2025
4b6963b
fix
kripken Nov 6, 2025
e23c1e6
fix
kripken Nov 6, 2025
54f1f35
fix
kripken Nov 6, 2025
f4a3446
fix
kripken Nov 6, 2025
ea44995
fix
kripken Nov 6, 2025
405b044
fix
kripken Nov 6, 2025
d5ee239
fix
kripken Nov 6, 2025
c5243e6
fix
kripken Nov 6, 2025
920e019
fix
kripken Nov 6, 2025
84c82f2
fix
kripken Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion scripts/fuzz_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2482,7 +2482,7 @@ def get_random_opts():
# disabled, its dependent features need to be disabled as well.
IMPLIED_FEATURE_OPTS = {
'--disable-reference-types': ['--disable-gc', '--disable-exception-handling', '--disable-strings'],
'--disable-gc': ['--disable-strings', '--disable-stack-switching'],
'--disable-gc': ['--disable-strings', '--disable-stack-switching', '--disable-custom-descriptors'],
}

print('''
Expand Down
3 changes: 2 additions & 1 deletion src/abi/js.h
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
23 changes: 18 additions & 5 deletions src/binaryen-c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -1609,8 +1609,21 @@ BinaryenExpressionRef BinaryenRefAs(BinaryenModuleRef module,
BinaryenExpressionRef BinaryenRefFunc(BinaryenModuleRef module,
const char* func,
BinaryenHeapType type) {
return static_cast<Expression*>(
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<Expression*>(
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<Expression*>(
Builder(*(Module*)module).makeRefFunc(func, full));
}
}

BinaryenExpressionRef BinaryenRefEq(BinaryenModuleRef module,
Expand Down Expand Up @@ -5096,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
Expand Down
4 changes: 1 addition & 3 deletions src/ir/ReFinalize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
Expand Down
46 changes: 38 additions & 8 deletions src/ir/module-splitting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -274,7 +275,8 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) {
activeBase.index + Index(activeSegment->data.size())};

Builder builder(module);
activeSegment->data.push_back(builder.makeRefFunc(func, type));
auto funcType = Type(type, NonNullable, Inexact);
activeSegment->data.push_back(builder.makeRefFunc(func, funcType));

addSlot(func, newSlot);
if (activeTable->initial <= newSlot.index) {
Expand Down Expand Up @@ -339,6 +341,7 @@ struct ModuleSplitter {
void setupTablePatching();
void shareImportableItems();
void removeUnusedSecondaryElements();
void updateIR();

ModuleSplitter(Module& primary, const Config& config)
: config(config), primary(primary), tableManager(primary),
Expand All @@ -355,6 +358,7 @@ struct ModuleSplitter {
setupTablePatching();
shareImportableItems();
removeUnusedSecondaryElements();
updateIR();
}
};

Expand All @@ -372,7 +376,7 @@ void ModuleSplitter::setupJSPI() {
// Add an imported function to load the secondary module.
auto import = Builder::makeFunction(
ModuleSplitting::LOAD_SECONDARY_MODULE,
Type(Signature(Type::none, Type::none), NonNullable, Exact),
Type(Signature(Type::none, Type::none), NonNullable, Inexact),
{});
import->module = ENV;
import->base = ModuleSplitting::LOAD_SECONDARY_MODULE;
Expand Down Expand Up @@ -516,6 +520,7 @@ void ModuleSplitter::exportImportFunction(Name funcName,
func->hasExplicitName = primaryFunc->hasExplicitName;
func->module = config.importNamespace;
func->base = exportName;
func->type = func->type.with(Inexact);
secondary->addFunction(std::move(func));
}
}
Expand Down Expand Up @@ -790,9 +795,8 @@ void ModuleSplitter::setupTablePatching() {
placeholder->name = Names::getValidFunctionName(
primary, std::string("placeholder_") + placeholder->base.toString());
placeholder->hasExplicitName = true;
placeholder->type = secondaryFunc->type;
elem = Builder(primary).makeRefFunc(placeholder->name,
placeholder->type.getHeapType());
placeholder->type = secondaryFunc->type.with(Inexact);
elem = Builder(primary).makeRefFunc(placeholder->name, placeholder->type);
primary.addFunction(std::move(placeholder));
});

Expand Down Expand Up @@ -833,8 +837,7 @@ void ModuleSplitter::setupTablePatching() {
// primarySeg->data[i] is a placeholder, so use the secondary
// function.
auto* func = replacement->second;
auto* ref = Builder(secondary).makeRefFunc(func->name,
func->type.getHeapType());
auto* ref = Builder(secondary).makeRefFunc(func->name, func->type);
secondaryElems.push_back(ref);
++replacement;
} else if (auto* get = primarySeg->data[i]->dynCast<RefFunc>()) {
Expand Down Expand Up @@ -876,7 +879,7 @@ void ModuleSplitter::setupTablePatching() {
}
auto* func = curr->second;
currData.push_back(
Builder(secondary).makeRefFunc(func->name, func->type.getHeapType()));
Builder(secondary).makeRefFunc(func->name, func->type));
}
if (currData.size()) {
finishSegment();
Expand Down Expand Up @@ -971,11 +974,38 @@ void ModuleSplitter::removeUnusedSecondaryElements() {
// code size in the primary module as well.
for (auto& secondaryPtr : secondaries) {
PassRunner runner(secondaryPtr.get());
// Do not validate here in the middle, as the IR still needs updating later.
runner.options.validate = false;
runner.add("remove-unused-module-elements");
runner.run();
}
}

void ModuleSplitter::updateIR() {
// Imported functions may need type updates.
struct Fixer : public PostWalker<Fixer> {
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.
// TODO: The cast may be needed even without CD enabled
replaceCurrent(Builder(wasm).makeRefCast(curr, curr->type));
}
curr->type = curr->type.with(Inexact);
}
}
} fixer;
fixer.walkModule(&primary);
for (auto& secondaryPtr : secondaries) {
fixer.walkModule(secondaryPtr.get());
}
}

} // anonymous namespace

Results splitFunctions(Module& primary, const Config& config) {
Expand Down
12 changes: 5 additions & 7 deletions src/ir/possible-contents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -643,9 +644,9 @@ struct InfoCollector
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, curr->type.getHeapType())));
addRoot(
curr,
PossibleContents::literal(Literal::makeFunc(curr->func, *getModule())));
} else {
// This is imported, so it is effectively a global.
addRoot(curr,
Expand Down Expand Up @@ -1869,8 +1870,7 @@ void TNHOracle::infer() {
// lot of other optimizations become possible anyhow.
auto target = possibleTargets[0]->name;
info.inferences[call->target] =
PossibleContents::literal(Literal::makeFunc(
target, wasm.getFunction(target)->type.getHeapType()));
PossibleContents::literal(Literal::makeFunc(target, wasm));
continue;
}

Expand Down Expand Up @@ -3142,8 +3142,6 @@ void Flower::dump(Location location) {
std::cout << " sigparamloc " << '\n';
} else if (auto* loc = std::get_if<SignatureResultLocation>(&location)) {
std::cout << " sigresultloc " << loc->type << " : " << loc->index << '\n';
} else if (auto* loc = std::get_if<RootLocation>(&location)) {
std::cout << " rootloc " << loc->type << '\n';
} else {
std::cout << " (other)\n";
}
Expand Down
2 changes: 1 addition & 1 deletion src/ir/properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ inline Literal getLiteral(const Expression* curr) {
} else if (auto* n = curr->dynCast<RefNull>()) {
return Literal(n->type);
} else if (auto* r = curr->dynCast<RefFunc>()) {
return Literal::makeFunc(r->func, r->type.getHeapType());
return Literal::makeFunc(r->func, r->type);
} else if (auto* i = curr->dynCast<RefI31>()) {
if (auto* c = i->value->dynCast<Const>()) {
return Literal::makeI31(c->value.geti32(),
Expand Down
9 changes: 7 additions & 2 deletions src/literal.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

namespace wasm {

class Module;
class Literals;
struct FuncData;
struct GCData;
Expand Down Expand Up @@ -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) {}
Expand All @@ -90,7 +94,7 @@ class Literal {
explicit Literal(const std::array<Literal, 8>&);
explicit Literal(const std::array<Literal, 4>&);
explicit Literal(const std::array<Literal, 2>&);
explicit Literal(std::shared_ptr<FuncData> funcData, HeapType type);
explicit Literal(std::shared_ptr<FuncData> funcData, Type type);
explicit Literal(std::shared_ptr<GCData> gcData, HeapType type);
explicit Literal(std::shared_ptr<ExnData> exnData);
explicit Literal(std::shared_ptr<ContData> contData);
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion src/parser/contexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,9 @@ struct ParseModuleTypesCtx : TypeParserCtx<ParseModuleTypesCtx>,
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.
Expand Down Expand Up @@ -1601,7 +1604,7 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, AnnotationParserCtx {
elems.push_back(expr);
}
void appendFuncElem(std::vector<Expression*>& elems, Name func) {
auto type = wasm.getFunction(func)->type.getHeapType();
auto type = wasm.getFunction(func)->type;
elems.push_back(builder.makeRefFunc(func, type));
}

Expand Down
8 changes: 8 additions & 0 deletions src/passes/ExtractFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <cctype>

#include "ir/utils.h"
#include "pass.h"
#include "wasm-builder.h"
#include "wasm.h"
Expand All @@ -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;
Expand All @@ -46,6 +48,12 @@ 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);
// 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();
module->updateMaps();
Expand Down
2 changes: 1 addition & 1 deletion src/passes/FuncCastEmulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
30 changes: 19 additions & 11 deletions src/passes/GUFA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<GlobalGet>() || c->is<RefFunc>()) {
// 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
Expand All @@ -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<GlobalGet>());
// 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;
}
}
}
Expand Down
Loading
Loading