Skip to content

Commit 0edd3cf

Browse files
authored
[Custom Descriptors] Make imported functions inexact (#7993)
Defined functions remain exact, but imported ones are inexact. This is a step along the recent Custom Descriptors spec changes. * New RefFunc::finalize and Literal::makeFunc variants get the module, and look up the type there. * New Builder::makeRefFunc variant gets a Type and applies it. The HeapType variant does a lookup on the module (so the Type one is more efficient/applicable if the IR is not fully built yet). * ReFinalize now updates RefFunc types (following the pattern of a few other places). C and JS APIs now assume RefFuncs are created after imported functions (so we can look up the type of the import; see changelog, this seems the least-annoying way to update here, avoiding new APIs, and less breakage for users - hopefully none, all our tests here pass as is). * wasm-split adds a cast when a function becomes an inexact import. * Fix GUFA to handle inexact function literals. * Update types in passes and fuzzer as needed.
1 parent 8a66d2a commit 0edd3cf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+704
-151
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ full changeset diff at the end of each section.
1515
Current Trunk
1616
-------------
1717

18+
- C and JS APIs now assume RefFuncs are created after imported functions (non-
19+
imported functions can still be created later). This is necessary because
20+
imported function types can vary (due to Custom Descriptors), and we need to
21+
look up that type at RefFunc creation time.
1822
- The --mod-asyncify-never-unwind and --mod-asyncify-always-and-only-unwind
1923
passed were deleted. They only existed to support the lazy code loading
2024
support in emscripten that was removed. (#7893)

scripts/fuzz_opt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2482,7 +2482,7 @@ def get_random_opts():
24822482
# disabled, its dependent features need to be disabled as well.
24832483
IMPLIED_FEATURE_OPTS = {
24842484
'--disable-reference-types': ['--disable-gc', '--disable-exception-handling', '--disable-strings'],
2485-
'--disable-gc': ['--disable-strings', '--disable-stack-switching'],
2485+
'--disable-gc': ['--disable-strings', '--disable-stack-switching', '--disable-custom-descriptors'],
24862486
}
24872487

24882488
print('''

src/abi/js.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ inline void ensureHelpers(Module* wasm, IString specific = IString()) {
5858
if (specific.is() && name != specific) {
5959
return;
6060
}
61-
auto func = Builder::makeFunction(name, Signature(params, results), {});
61+
auto func = Builder::makeFunction(
62+
name, Type(Signature(params, results), NonNullable, Inexact), {});
6263
func->module = ENV;
6364
func->base = name;
6465
wasm->addFunction(std::move(func));

src/binaryen-c.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ Literal fromBinaryenLiteral(BinaryenLiteral x) {
157157
}
158158
}
159159
if (heapType.isSignature()) {
160-
return Literal::makeFunc(Name(x.func), heapType);
160+
return Literal::makeFunc(Name(x.func), type);
161161
}
162162
assert(heapType.isData());
163163
WASM_UNREACHABLE("TODO: gc data");
@@ -1609,8 +1609,21 @@ BinaryenExpressionRef BinaryenRefAs(BinaryenModuleRef module,
16091609
BinaryenExpressionRef BinaryenRefFunc(BinaryenModuleRef module,
16101610
const char* func,
16111611
BinaryenHeapType type) {
1612-
return static_cast<Expression*>(
1613-
Builder(*(Module*)module).makeRefFunc(func, HeapType(type)));
1612+
// We can assume imports have been created at this point in time, but not
1613+
// other defined functions. See if the function exists already, and assume it
1614+
// is non-imported if not. TODO: If we want to allow creating imports later,
1615+
// we would need an API addition or change.
1616+
auto* wasm = (Module*)module;
1617+
if (wasm->getFunctionOrNull(func)) {
1618+
// Use the HeapType constructor, which will do a lookup on the module.
1619+
return static_cast<Expression*>(
1620+
Builder(*(Module*)module).makeRefFunc(func, HeapType(type)));
1621+
} else {
1622+
// Assume non-imported, and provide the full type for that.
1623+
Type full = Type(HeapType(type), NonNullable, Exact);
1624+
return static_cast<Expression*>(
1625+
Builder(*(Module*)module).makeRefFunc(func, full));
1626+
}
16141627
}
16151628

16161629
BinaryenExpressionRef BinaryenRefEq(BinaryenModuleRef module,
@@ -5096,9 +5109,9 @@ void BinaryenAddFunctionImport(BinaryenModuleRef module,
50965109
func->name = internalName;
50975110
func->module = externalModuleName;
50985111
func->base = externalBaseName;
5099-
// TODO: Take a HeapType rather than params and results.
5112+
// TODO: Take a Type rather than params and results.
51005113
func->type =
5101-
Type(Signature(Type(params), Type(results)), NonNullable, Exact);
5114+
Type(Signature(Type(params), Type(results)), NonNullable, Inexact);
51025115
((Module*)module)->addFunction(std::move(func));
51035116
} else {
51045117
// already exists so just set module and base

src/ir/ReFinalize.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,7 @@ void ReFinalize::visitMemoryGrow(MemoryGrow* curr) { curr->finalize(); }
116116
void ReFinalize::visitRefNull(RefNull* curr) { curr->finalize(); }
117117
void ReFinalize::visitRefIsNull(RefIsNull* curr) { curr->finalize(); }
118118
void ReFinalize::visitRefFunc(RefFunc* curr) {
119-
// TODO: should we look up the function and update the type from there? This
120-
// could handle a change to the function's type, but is also not really what
121-
// this class has been meant to do.
119+
curr->finalize(curr->type.getHeapType(), *getModule());
122120
}
123121
void ReFinalize::visitRefEq(RefEq* curr) { curr->finalize(); }
124122
void ReFinalize::visitTableGet(TableGet* curr) { curr->finalize(); }

src/ir/module-splitting.cpp

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
#include "ir/export-utils.h"
7474
#include "ir/module-utils.h"
7575
#include "ir/names.h"
76+
#include "ir/utils.h"
7677
#include "pass.h"
7778
#include "support/insert_ordered.h"
7879
#include "wasm-builder.h"
@@ -274,7 +275,8 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) {
274275
activeBase.index + Index(activeSegment->data.size())};
275276

276277
Builder builder(module);
277-
activeSegment->data.push_back(builder.makeRefFunc(func, type));
278+
auto funcType = Type(type, NonNullable, Inexact);
279+
activeSegment->data.push_back(builder.makeRefFunc(func, funcType));
278280

279281
addSlot(func, newSlot);
280282
if (activeTable->initial <= newSlot.index) {
@@ -339,6 +341,7 @@ struct ModuleSplitter {
339341
void setupTablePatching();
340342
void shareImportableItems();
341343
void removeUnusedSecondaryElements();
344+
void updateIR();
342345

343346
ModuleSplitter(Module& primary, const Config& config)
344347
: config(config), primary(primary), tableManager(primary),
@@ -355,6 +358,7 @@ struct ModuleSplitter {
355358
setupTablePatching();
356359
shareImportableItems();
357360
removeUnusedSecondaryElements();
361+
updateIR();
358362
}
359363
};
360364

@@ -372,7 +376,7 @@ void ModuleSplitter::setupJSPI() {
372376
// Add an imported function to load the secondary module.
373377
auto import = Builder::makeFunction(
374378
ModuleSplitting::LOAD_SECONDARY_MODULE,
375-
Type(Signature(Type::none, Type::none), NonNullable, Exact),
379+
Type(Signature(Type::none, Type::none), NonNullable, Inexact),
376380
{});
377381
import->module = ENV;
378382
import->base = ModuleSplitting::LOAD_SECONDARY_MODULE;
@@ -516,6 +520,7 @@ void ModuleSplitter::exportImportFunction(Name funcName,
516520
func->hasExplicitName = primaryFunc->hasExplicitName;
517521
func->module = config.importNamespace;
518522
func->base = exportName;
523+
func->type = func->type.with(Inexact);
519524
secondary->addFunction(std::move(func));
520525
}
521526
}
@@ -790,9 +795,8 @@ void ModuleSplitter::setupTablePatching() {
790795
placeholder->name = Names::getValidFunctionName(
791796
primary, std::string("placeholder_") + placeholder->base.toString());
792797
placeholder->hasExplicitName = true;
793-
placeholder->type = secondaryFunc->type;
794-
elem = Builder(primary).makeRefFunc(placeholder->name,
795-
placeholder->type.getHeapType());
798+
placeholder->type = secondaryFunc->type.with(Inexact);
799+
elem = Builder(primary).makeRefFunc(placeholder->name, placeholder->type);
796800
primary.addFunction(std::move(placeholder));
797801
});
798802

@@ -833,8 +837,7 @@ void ModuleSplitter::setupTablePatching() {
833837
// primarySeg->data[i] is a placeholder, so use the secondary
834838
// function.
835839
auto* func = replacement->second;
836-
auto* ref = Builder(secondary).makeRefFunc(func->name,
837-
func->type.getHeapType());
840+
auto* ref = Builder(secondary).makeRefFunc(func->name, func->type);
838841
secondaryElems.push_back(ref);
839842
++replacement;
840843
} else if (auto* get = primarySeg->data[i]->dynCast<RefFunc>()) {
@@ -876,7 +879,7 @@ void ModuleSplitter::setupTablePatching() {
876879
}
877880
auto* func = curr->second;
878881
currData.push_back(
879-
Builder(secondary).makeRefFunc(func->name, func->type.getHeapType()));
882+
Builder(secondary).makeRefFunc(func->name, func->type));
880883
}
881884
if (currData.size()) {
882885
finishSegment();
@@ -971,11 +974,38 @@ void ModuleSplitter::removeUnusedSecondaryElements() {
971974
// code size in the primary module as well.
972975
for (auto& secondaryPtr : secondaries) {
973976
PassRunner runner(secondaryPtr.get());
977+
// Do not validate here in the middle, as the IR still needs updating later.
978+
runner.options.validate = false;
974979
runner.add("remove-unused-module-elements");
975980
runner.run();
976981
}
977982
}
978983

984+
void ModuleSplitter::updateIR() {
985+
// Imported functions may need type updates.
986+
struct Fixer : public PostWalker<Fixer> {
987+
void visitRefFunc(RefFunc* curr) {
988+
auto& wasm = *getModule();
989+
auto* func = wasm.getFunction(curr->func);
990+
if (func->type != curr->type) {
991+
// This became an import, and lost exactness.
992+
assert(!func->type.isExact());
993+
assert(curr->type.isExact());
994+
if (wasm.features.hasCustomDescriptors()) {
995+
// Add a cast, as the parent may depend on the exactness to validate.
996+
// TODO: The cast may be needed even without CD enabled
997+
replaceCurrent(Builder(wasm).makeRefCast(curr, curr->type));
998+
}
999+
curr->type = curr->type.with(Inexact);
1000+
}
1001+
}
1002+
} fixer;
1003+
fixer.walkModule(&primary);
1004+
for (auto& secondaryPtr : secondaries) {
1005+
fixer.walkModule(secondaryPtr.get());
1006+
}
1007+
}
1008+
9791009
} // anonymous namespace
9801010

9811011
Results splitFunctions(Module& primary, const Config& config) {

src/ir/possible-contents.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "ir/module-utils.h"
2828
#include "ir/possible-contents.h"
2929
#include "support/insert_ordered.h"
30+
#include "wasm-type.h"
3031
#include "wasm.h"
3132

3233
namespace std {
@@ -643,9 +644,9 @@ struct InfoCollector
643644
void visitRefFunc(RefFunc* curr) {
644645
if (!getModule()->getFunction(curr->func)->imported()) {
645646
// This is not imported, so we know the exact function literal.
646-
addRoot(curr,
647-
PossibleContents::literal(
648-
Literal::makeFunc(curr->func, curr->type.getHeapType())));
647+
addRoot(
648+
curr,
649+
PossibleContents::literal(Literal::makeFunc(curr->func, *getModule())));
649650
} else {
650651
// This is imported, so it is effectively a global.
651652
addRoot(curr,
@@ -1869,8 +1870,7 @@ void TNHOracle::infer() {
18691870
// lot of other optimizations become possible anyhow.
18701871
auto target = possibleTargets[0]->name;
18711872
info.inferences[call->target] =
1872-
PossibleContents::literal(Literal::makeFunc(
1873-
target, wasm.getFunction(target)->type.getHeapType()));
1873+
PossibleContents::literal(Literal::makeFunc(target, wasm));
18741874
continue;
18751875
}
18761876

@@ -3142,8 +3142,6 @@ void Flower::dump(Location location) {
31423142
std::cout << " sigparamloc " << '\n';
31433143
} else if (auto* loc = std::get_if<SignatureResultLocation>(&location)) {
31443144
std::cout << " sigresultloc " << loc->type << " : " << loc->index << '\n';
3145-
} else if (auto* loc = std::get_if<RootLocation>(&location)) {
3146-
std::cout << " rootloc " << loc->type << '\n';
31473145
} else {
31483146
std::cout << " (other)\n";
31493147
}

src/ir/properties.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ inline Literal getLiteral(const Expression* curr) {
116116
} else if (auto* n = curr->dynCast<RefNull>()) {
117117
return Literal(n->type);
118118
} else if (auto* r = curr->dynCast<RefFunc>()) {
119-
return Literal::makeFunc(r->func, r->type.getHeapType());
119+
return Literal::makeFunc(r->func, r->type);
120120
} else if (auto* i = curr->dynCast<RefI31>()) {
121121
if (auto* c = i->value->dynCast<Const>()) {
122122
return Literal::makeI31(c->value.geti32(),

src/literal.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
namespace wasm {
3232

33+
class Module;
3334
class Literals;
3435
struct FuncData;
3536
struct GCData;
@@ -70,6 +71,9 @@ class Literal {
7071

7172
public:
7273
// Type of the literal. Immutable because the literal's payload depends on it.
74+
// For references to defined heap types, this is almost always an exact type.
75+
// The exception is references to imported functions, since the function
76+
// provided at instantiation time may have a subtype of the import type.
7377
const Type type;
7478

7579
Literal() : v128(), type(Type::none) {}
@@ -90,7 +94,7 @@ class Literal {
9094
explicit Literal(const std::array<Literal, 8>&);
9195
explicit Literal(const std::array<Literal, 4>&);
9296
explicit Literal(const std::array<Literal, 2>&);
93-
explicit Literal(std::shared_ptr<FuncData> funcData, HeapType type);
97+
explicit Literal(std::shared_ptr<FuncData> funcData, Type type);
9498
explicit Literal(std::shared_ptr<GCData> gcData, HeapType type);
9599
explicit Literal(std::shared_ptr<ExnData> exnData);
96100
explicit Literal(std::shared_ptr<ContData> contData);
@@ -252,7 +256,8 @@ class Literal {
252256
}
253257
// Simple way to create a function from the name and type, without a full
254258
// FuncData.
255-
static Literal makeFunc(Name func, HeapType type);
259+
static Literal makeFunc(Name func, Type type);
260+
static Literal makeFunc(Name func, Module& wasm);
256261
static Literal makeI31(int32_t value, Shareability share) {
257262
auto lit = Literal(Type(HeapTypes::i31.getBasic(share), NonNullable));
258263
lit.i32 = value | 0x80000000;

src/parser/contexts.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,9 @@ struct ParseModuleTypesCtx : TypeParserCtx<ParseModuleTypesCtx>,
14191419
return in.err(pos, "expected signature type");
14201420
}
14211421
f->type = f->type.with(type.type);
1422+
if (f->imported()) {
1423+
f->type = f->type.with(Inexact);
1424+
}
14221425
// If we are provided with too many names (more than the function has), we
14231426
// will error on that later when we check the signature matches the type.
14241427
// For now, avoid asserting in setLocalName.
@@ -1601,7 +1604,7 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, AnnotationParserCtx {
16011604
elems.push_back(expr);
16021605
}
16031606
void appendFuncElem(std::vector<Expression*>& elems, Name func) {
1604-
auto type = wasm.getFunction(func)->type.getHeapType();
1607+
auto type = wasm.getFunction(func)->type;
16051608
elems.push_back(builder.makeRefFunc(func, type));
16061609
}
16071610

0 commit comments

Comments
 (0)