Skip to content

Commit efa8a80

Browse files
authored
[Custom Descriptors] Exact function imports (#8033)
Binary and text parsing a printing as well as validation of exact function imports. Importing a function exactly allows it to be referenced exactly, just like a defined function can be. Follow-up PRs will use exact function imports in various passes and tools.
1 parent b2908ce commit efa8a80

File tree

12 files changed

+447
-32
lines changed

12 files changed

+447
-32
lines changed

src/parser/context-decls.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Result<> ParseDeclsCtx::addFunc(Name name,
6767
const std::vector<Name>& exports,
6868
ImportNames* import,
6969
TypeUseT type,
70+
Exactness exact,
7071
std::optional<LocalsT>,
7172
std::vector<Annotation>&& annotations,
7273
Index pos) {

src/parser/contexts.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,7 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx {
10781078
const std::vector<Name>& exports,
10791079
ImportNames* import,
10801080
TypeUseT type,
1081+
Exactness exact,
10811082
std::optional<LocalsT>,
10821083
std::vector<Annotation>&&,
10831084
Index pos);
@@ -1411,17 +1412,15 @@ struct ParseModuleTypesCtx : TypeParserCtx<ParseModuleTypesCtx>,
14111412
const std::vector<Name>&,
14121413
ImportNames*,
14131414
TypeUse type,
1415+
Exactness exact,
14141416
std::optional<LocalsT> locals,
14151417
std::vector<Annotation>&&,
14161418
Index pos) {
14171419
auto& f = wasm.functions[index];
14181420
if (!type.type.isSignature()) {
14191421
return in.err(pos, "expected signature type");
14201422
}
1421-
f->type = f->type.with(type.type);
1422-
if (f->imported()) {
1423-
f->type = f->type.with(Inexact);
1424-
}
1423+
f->type = Type(type.type, NonNullable, exact);
14251424
// If we are provided with too many names (more than the function has), we
14261425
// will error on that later when we check the signature matches the type.
14271426
// For now, avoid asserting in setLocalName.
@@ -1803,6 +1802,7 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, AnnotationParserCtx {
18031802
const std::vector<Name>&,
18041803
ImportNames*,
18051804
TypeUseT,
1805+
Exactness,
18061806
std::optional<LocalsT>,
18071807
std::vector<Annotation>&&,
18081808
Index) {

src/parser/parsers.h

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ Result<typename Ctx::LabelIdxT> labelidx(Ctx&, bool inDelegate = false);
370370
template<typename Ctx> Result<typename Ctx::TagIdxT> tagidx(Ctx&);
371371
template<typename Ctx>
372372
Result<typename Ctx::TypeUseT> typeuse(Ctx&, bool allowNames = true);
373+
template<typename Ctx>
374+
Result<std::pair<typename Ctx::TypeUse, Exactness>> exacttypeuse(Ctx&);
373375
MaybeResult<ImportNames> inlineImport(Lexer&);
374376
Result<std::vector<Name>> inlineExports(Lexer&);
375377
template<typename Ctx> Result<> comptype(Ctx&);
@@ -3007,6 +3009,24 @@ Result<typename Ctx::TypeUseT> typeuse(Ctx& ctx, bool allowNames) {
30073009
return ctx.makeTypeUse(pos, type, namedParams.getPtr(), resultTypes.getPtr());
30083010
}
30093011

3012+
// exacttypeuse ::= typeuse |
3013+
// '(' 'exact' typeuse ')'
3014+
template<typename Ctx>
3015+
Result<std::pair<typename Ctx::TypeUseT, Exactness>> exacttypeuse(Ctx& ctx) {
3016+
auto exact = Inexact;
3017+
if (ctx.in.takeSExprStart("exact"sv)) {
3018+
exact = Exact;
3019+
}
3020+
auto type = typeuse(ctx, true);
3021+
CHECK_ERR(type);
3022+
if (exact == Exact) {
3023+
if (!ctx.in.takeRParen()) {
3024+
return ctx.in.err("expected end of exact type use");
3025+
}
3026+
}
3027+
return std::make_pair(*type, exact);
3028+
}
3029+
30103030
// ('(' 'import' mod:name nm:name ')')?
30113031
inline MaybeResult<ImportNames> inlineImport(Lexer& in) {
30123032
if (!in.takeSExprStart("import"sv)) {
@@ -3221,7 +3241,7 @@ template<typename Ctx> MaybeResult<typename Ctx::LocalsT> locals(Ctx& ctx) {
32213241
}
32223242

32233243
// import ::= '(' 'import' mod:name nm:name importdesc ')'
3224-
// importdesc ::= '(' 'func' id? typeuse ')'
3244+
// importdesc ::= '(' 'func' id? exacttypeuse ')'
32253245
// | '(' 'table' id? tabletype ')'
32263246
// | '(' 'memory' id? memtype ')'
32273247
// | '(' 'global' id? globaltype ')'
@@ -3246,11 +3266,12 @@ template<typename Ctx> MaybeResult<> import_(Ctx& ctx) {
32463266

32473267
if (ctx.in.takeSExprStart("func"sv)) {
32483268
auto name = ctx.in.takeID();
3249-
auto type = typeuse(ctx);
3250-
CHECK_ERR(type);
3269+
auto use = exacttypeuse(ctx);
3270+
CHECK_ERR(use);
3271+
auto [type, exact] = *use;
32513272
// TODO: function import annotations
32523273
CHECK_ERR(ctx.addFunc(
3253-
name ? *name : Name{}, {}, &names, *type, std::nullopt, {}, pos));
3274+
name ? *name : Name{}, {}, &names, type, exact, std::nullopt, {}, pos));
32543275
} else if (ctx.in.takeSExprStart("table"sv)) {
32553276
auto name = ctx.in.takeID();
32563277
auto type = tabletype(ctx);
@@ -3289,7 +3310,7 @@ template<typename Ctx> MaybeResult<> import_(Ctx& ctx) {
32893310
// func ::= '(' 'func' id? ('(' 'export' name ')')*
32903311
// x,I:typeuse t*:vec(local) (in:instr)* ')'
32913312
// | '(' 'func' id? ('(' 'export' name ')')*
3292-
// '(' 'import' mod:name nm:name ')' typeuse ')'
3313+
// '(' 'import' mod:name nm:name ')' exacttypeuse ')'
32933314
template<typename Ctx> MaybeResult<> func(Ctx& ctx) {
32943315
auto pos = ctx.in.getPos();
32953316
auto annotations = ctx.in.getAnnotations();
@@ -3309,11 +3330,19 @@ template<typename Ctx> MaybeResult<> func(Ctx& ctx) {
33093330
auto import = inlineImport(ctx.in);
33103331
CHECK_ERR(import);
33113332

3312-
auto type = typeuse(ctx);
3313-
CHECK_ERR(type);
3314-
3333+
typename Ctx::TypeUseT type;
3334+
Exactness exact = Exact;
33153335
std::optional<typename Ctx::LocalsT> localVars;
3316-
if (!import) {
3336+
3337+
if (import) {
3338+
auto use = exacttypeuse(ctx);
3339+
CHECK_ERR(use);
3340+
type = use->first;
3341+
exact = use->second;
3342+
} else {
3343+
auto use = typeuse(ctx);
3344+
CHECK_ERR(use);
3345+
type = *use;
33173346
if (auto l = locals(ctx)) {
33183347
CHECK_ERR(l);
33193348
localVars = *l;
@@ -3331,7 +3360,8 @@ template<typename Ctx> MaybeResult<> func(Ctx& ctx) {
33313360
CHECK_ERR(ctx.addFunc(name,
33323361
*exports,
33333362
import.getPtr(),
3334-
*type,
3363+
type,
3364+
exact,
33353365
localVars,
33363366
std::move(annotations),
33373367
pos));

src/passes/Print.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3078,6 +3078,9 @@ void PrintSExpression::handleSignature(Function* curr,
30783078
o << '(';
30793079
printMajor(o, "func ");
30803080
curr->name.print(o);
3081+
if (curr->imported() && curr->type.isExact()) {
3082+
o << " (exact";
3083+
}
30813084
if ((currModule && currModule->features.hasGC()) ||
30823085
requiresExplicitFuncType(curr->type.getHeapType())) {
30833086
o << " (type ";
@@ -3124,6 +3127,9 @@ void PrintSExpression::handleSignature(Function* curr,
31243127
o << maybeSpace;
31253128
printResultType(curr->getResults());
31263129
}
3130+
if (curr->imported() && curr->type.isExact()) {
3131+
o << ')';
3132+
}
31273133
}
31283134

31293135
void PrintSExpression::visitExport(Export* curr) {

src/passes/StackCheck.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ importStackOverflowHandler(Module& module, Name name, Signature sig) {
4141
ImportInfo info(module);
4242

4343
if (!info.getImportedFunction(ENV, name)) {
44-
auto import = Builder::makeFunction(name, sig, {});
44+
auto import =
45+
Builder::makeFunction(name, Type(sig, NonNullable, Inexact), {});
4546
import->module = ENV;
4647
import->base = name;
4748
module.addFunction(std::move(import));

src/wasm-binary.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,8 @@ enum BrOnCastFlag {
356356
OutputNullable = 1 << 1,
357357
};
358358

359+
constexpr uint32_t ExactImport = 1 << 5;
360+
359361
enum EncodedType {
360362
// value types
361363
i32 = -0x1, // 0x7f

src/wasm-builder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Builder {
4747
Type type,
4848
std::vector<Type>&& vars,
4949
Expression* body = nullptr) {
50-
assert(type.isSignature());
50+
assert(type.isSignature() && type.isNonNullable());
5151
auto func = std::make_unique<Function>();
5252
func->name = name;
5353
func->type = type;

src/wasm.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2342,15 +2342,20 @@ class Function : public Importable {
23422342
void clearDebugInfo();
23432343
};
23442344

2345-
// The kind of an import or export.
2346-
enum class ExternalKind {
2345+
// The kind of an import or export. Use a namespace to avoid polluting the wasm
2346+
// namespace while maintaining implicit conversion to int, which an enum class
2347+
// would not have.
2348+
namespace ExternalKindImpl {
2349+
enum Kind : uint32_t {
23472350
Function = 0,
23482351
Table = 1,
23492352
Memory = 2,
23502353
Global = 3,
23512354
Tag = 4,
2352-
Invalid = -1
2355+
Invalid = uint32_t(-1)
23532356
};
2357+
} // namespace ExternalKindImpl
2358+
using ExternalKind = ExternalKindImpl::Kind;
23542359

23552360
// The kind of a top-level module item. (This overlaps with ExternalKind, but
23562361
// C++ has no good way to extend an enum.) All such items are referred to by

src/wasm/wasm-binary.cpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,11 @@ void WasmBinaryWriter::writeImports() {
332332
};
333333
ModuleUtils::iterImportedFunctions(*wasm, [&](Function* func) {
334334
writeImportHeader(func);
335-
o << U32LEB(int32_t(ExternalKind::Function));
336-
o << U32LEB(getTypeIndex(func->type.getHeapType()));
335+
uint32_t kind = ExternalKind::Function;
336+
if (func->type.isExact()) {
337+
kind |= BinaryConsts::ExactImport;
338+
}
339+
o << U32LEB(kind) << U32LEB(getTypeIndex(func->type.getHeapType()));
337340
});
338341
ModuleUtils::iterImportedGlobals(*wasm, [&](Global* global) {
339342
writeImportHeader(global);
@@ -2875,12 +2878,13 @@ void WasmBinaryReader::readImports() {
28752878
for (size_t i = 0; i < num; i++) {
28762879
auto module = getInlineString();
28772880
auto base = getInlineString();
2878-
auto kind = (ExternalKind)getU32LEB();
2881+
auto kind = getU32LEB();
28792882
// We set a unique prefix for the name based on the kind. This ensures no
28802883
// collisions between them, which can't occur here (due to the index i) but
28812884
// could occur later due to the names section.
28822885
switch (kind) {
2883-
case ExternalKind::Function: {
2886+
case ExternalKind::Function:
2887+
case ExternalKind::Function | BinaryConsts::ExactImport: {
28842888
auto [name, isExplicit] =
28852889
getOrMakeName(functionNames,
28862890
wasm.functions.size(),
@@ -2894,8 +2898,9 @@ void WasmBinaryReader::readImports() {
28942898
'.' + base.toString() +
28952899
"'s type must be a signature. Given: " + type.toString());
28962900
}
2901+
auto exact = (kind & BinaryConsts::ExactImport) ? Exact : Inexact;
28972902
auto curr =
2898-
builder.makeFunction(name, Type(type, NonNullable, Inexact), {});
2903+
builder.makeFunction(name, Type(type, NonNullable, exact), {});
28992904
curr->hasExplicitName = isExplicit;
29002905
curr->module = module;
29012906
curr->base = base;
@@ -4691,8 +4696,8 @@ void WasmBinaryReader::readExports() {
46914696
if (!names.emplace(name).second) {
46924697
throwError("duplicate export name");
46934698
}
4694-
ExternalKind kind = (ExternalKind)getU32LEB();
4695-
std::variant<Name, HeapType> value;
4699+
auto kind = getU32LEB();
4700+
std::optional<std::variant<Name, HeapType>> value;
46964701
auto index = getU32LEB();
46974702
switch (kind) {
46984703
case ExternalKind::Function:
@@ -4711,9 +4716,12 @@ void WasmBinaryReader::readExports() {
47114716
value = getTagName(index);
47124717
break;
47134718
case ExternalKind::Invalid:
4714-
throwError("invalid export kind");
4719+
break;
4720+
}
4721+
if (!value) {
4722+
throwError("invalid export kind");
47154723
}
4716-
wasm.addExport(new Export(name, kind, value));
4724+
wasm.addExport(new Export(name, ExternalKind(kind), *value));
47174725
}
47184726
}
47194727

src/wasm/wasm-validator.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4159,10 +4159,7 @@ void FunctionValidator::visitFunction(Function* curr) {
41594159
curr->name,
41604160
"all used types should be allowed");
41614161

4162-
if (curr->imported()) {
4163-
shouldBeTrue(
4164-
!curr->type.isExact(), curr->name, "imported function should be inexact");
4165-
} else {
4162+
if (!curr->imported()) {
41664163
shouldBeTrue(
41674164
curr->type.isExact(), curr->name, "defined function should be exact");
41684165
}

0 commit comments

Comments
 (0)