From 7bdfbf6f7764ff896f5c8efa2729052316a6eab0 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Nov 2025 18:11:06 -0800 Subject: [PATCH 1/7] [NFC] Remove HeapType from RefFunc::finalize Since finalization already looks up the function on the module to determine whether it is imported, go further and just take the type directly from the function. --- src/binaryen-c.cpp | 11 +++++------ src/ir/ReFinalize.cpp | 4 +--- src/ir/possible-contents.h | 3 +-- src/ir/table-utils.h | 3 +-- src/passes/FuncCastEmulation.cpp | 2 +- src/passes/JSPI.cpp | 4 +--- src/passes/LegalizeJSInterface.cpp | 2 +- src/passes/MergeSimilarFunctions.cpp | 4 +--- src/tools/fuzzing/fuzzing.cpp | 12 ++++-------- src/tools/wasm-merge.cpp | 5 +---- src/wasm-builder.h | 6 +++--- src/wasm.h | 2 +- src/wasm/wasm-binary.cpp | 3 +-- src/wasm/wasm-ir-builder.cpp | 2 +- src/wasm/wasm.cpp | 6 +----- 15 files changed, 24 insertions(+), 45 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 1d4077145ec..efd388d3a21 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1614,10 +1614,11 @@ BinaryenExpressionRef BinaryenRefFunc(BinaryenModuleRef module, // 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)) { + if ([[maybe_unused]] auto* f = wasm->getFunctionOrNull(func)) { + assert(f->type.getHeapType() == HeapType(type)); // Use the HeapType constructor, which will do a lookup on the module. return static_cast( - Builder(*(Module*)module).makeRefFunc(func, HeapType(type))); + Builder(*(Module*)module).makeRefFunc(func)); } else { // Assume non-imported, and provide the full type for that. Type full = Type(HeapType(type), NonNullable, Exact); @@ -5299,8 +5300,7 @@ BinaryenAddActiveElementSegment(BinaryenModuleRef module, Fatal() << "invalid function '" << funcNames[i] << "'."; } segment->data.push_back( - Builder(*(Module*)module) - .makeRefFunc(funcNames[i], func->type.getHeapType())); + Builder(*(Module*)module).makeRefFunc(funcNames[i])); } return ((Module*)module)->addElementSegment(std::move(segment)); } @@ -5317,8 +5317,7 @@ BinaryenAddPassiveElementSegment(BinaryenModuleRef module, Fatal() << "invalid function '" << funcNames[i] << "'."; } segment->data.push_back( - Builder(*(Module*)module) - .makeRefFunc(funcNames[i], func->type.getHeapType())); + Builder(*(Module*)module).makeRefFunc(funcNames[i])); } return ((Module*)module)->addElementSegment(std::move(segment)); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index ced96c1c357..f7544e3cf51 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -115,9 +115,7 @@ void ReFinalize::visitMemorySize(MemorySize* curr) { curr->finalize(); } 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) { - curr->finalize(curr->type.getHeapType(), *getModule()); -} +void ReFinalize::visitRefFunc(RefFunc* curr) { curr->finalize(*getModule()); } void ReFinalize::visitRefEq(RefEq* curr) { curr->finalize(); } void ReFinalize::visitTableGet(TableGet* curr) { curr->finalize(); } void ReFinalize::visitTableSet(TableSet* curr) { curr->finalize(); } diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 6e0f110adb8..a5fdb51edc0 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -327,8 +327,7 @@ class PossibleContents { 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); } } } diff --git a/src/ir/table-utils.h b/src/ir/table-utils.h index 6b9f6b5a8d0..6af41d28517 100644 --- a/src/ir/table-utils.h +++ b/src/ir/table-utils.h @@ -92,8 +92,7 @@ inline Index append(Table& table, Name name, Module& wasm) { auto* func = wasm.getFunctionOrNull(name); assert(func != nullptr && "Cannot append non-existing function to a table."); - segment->data.push_back( - Builder(wasm).makeRefFunc(name, func->type.getHeapType())); + segment->data.push_back(Builder(wasm).makeRefFunc(name)); table.initial++; return tableIndex; } diff --git a/src/passes/FuncCastEmulation.cpp b/src/passes/FuncCastEmulation.cpp index d4f2de4009a..4f85c1ddc13 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(), *module); + ref->finalize(*module); } } diff --git a/src/passes/JSPI.cpp b/src/passes/JSPI.cpp index 65cf944b47c..7d42aad118c 100644 --- a/src/passes/JSPI.cpp +++ b/src/passes/JSPI.cpp @@ -151,9 +151,7 @@ struct JSPI : public Pass { if (iter == wrappedExports.end()) { continue; } - auto* replacementRef = builder.makeRefFunc( - iter->second, - module->getFunction(iter->second)->type.getHeapType()); + auto* replacementRef = builder.makeRefFunc(iter->second); segment->data[i] = replacementRef; } } diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 5cc34680fb5..e3b58f6f8b9 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(), *getModule()); + curr->finalize(*getModule()); } }; diff --git a/src/passes/MergeSimilarFunctions.cpp b/src/passes/MergeSimilarFunctions.cpp index 1e908f49abb..6b6aeca09d1 100644 --- a/src/passes/MergeSimilarFunctions.cpp +++ b/src/passes/MergeSimilarFunctions.cpp @@ -131,9 +131,7 @@ struct ParamInfo { if (const auto literals = std::get_if(&values)) { return builder.makeConst((*literals)[index]); } else if (auto callees = std::get_if>(&values)) { - auto fnName = (*callees)[index]; - auto heapType = module->getFunction(fnName)->type.getHeapType(); - return builder.makeRefFunc(fnName, heapType); + return builder.makeRefFunc((*callees)[index]); } else { WASM_UNREACHABLE("unexpected const value type"); } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 02dba6d7962..21d4f62c1ea 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1694,8 +1694,7 @@ Function* TranslateToFuzzReader::addFunction() { } }); auto& randomElem = compatibleSegments[upTo(compatibleSegments.size())]; - randomElem->data.push_back( - builder.makeRefFunc(func->name, func->type.getHeapType())); + randomElem->data.push_back(builder.makeRefFunc(func->name)); } numAddedFunctions++; return func; @@ -2988,10 +2987,7 @@ Expression* TranslateToFuzzReader::makeCallRef(Type type) { } // TODO: half the time make a completely random item with that type. return builder.makeCallRef( - builder.makeRefFunc(target->name, target->type.getHeapType()), - args, - type, - isReturn); + builder.makeRefFunc(target->name), args, type, isReturn); } Expression* TranslateToFuzzReader::makeLocalGet(Type type) { @@ -3627,7 +3623,7 @@ Expression* TranslateToFuzzReader::makeRefFuncConst(Type type) { do { auto& func = wasm.functions[i]; if (Type::isSubType(func->type, type)) { - return builder.makeRefFunc(func->name, func->type.getHeapType()); + return builder.makeRefFunc(func->name); } i = (i + 1) % wasm.functions.size(); } while (i != start); @@ -3666,7 +3662,7 @@ Expression* TranslateToFuzzReader::makeRefFuncConst(Type type) { Type(heapType, NonNullable, Exact), {}, body)); - return builder.makeRefFunc(func->name, heapType); + return builder.makeRefFunc(func->name); } Expression* TranslateToFuzzReader::makeConst(Type type) { diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index 51af318e7a0..d9a991a58cc 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -584,10 +584,7 @@ void updateTypes(Module& wasm) { } } - void visitRefFunc(RefFunc* curr) { - curr->finalize(getModule()->getFunction(curr->func)->type.getHeapType(), - *getModule()); - } + void visitRefFunc(RefFunc* curr) { curr->finalize(*getModule()); } void visitFunction(Function* curr) { ReFinalize().walkFunctionInModule(curr, getModule()); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index b1c10146dac..fef417fc1a5 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -709,10 +709,10 @@ class Builder { ret->type = type; return ret; } - RefFunc* makeRefFunc(Name func, HeapType heapType) { + RefFunc* makeRefFunc(Name func) { auto* ret = wasm.allocator.alloc(); ret->func = func; - ret->finalize(heapType, wasm); + ret->finalize(wasm); return ret; } RefEq* makeRefEq(Expression* left, Expression* right) { @@ -1368,7 +1368,7 @@ class Builder { return makeRefNull(type.getHeapType()); } if (type.isFunction()) { - return makeRefFunc(value.getFunc(), type.getHeapType()); + return makeRefFunc(value.getFunc()); } if (type.isRef() && type.getHeapType().isMaybeShared(HeapType::i31)) { return makeRefI31(makeConst(value.geti31()), diff --git a/src/wasm.h b/src/wasm.h index 3f3ac1d7434..757a0001501 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, Module& wasm); + void finalize(Module& wasm); }; class RefEq : public SpecificExpression { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 2e85bfb5e0c..57d034b3c62 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4962,8 +4962,7 @@ void WasmBinaryReader::readElementSegments() { } else { for (Index j = 0; j < size; j++) { Index index = getU32LEB(); - auto sig = getTypeByFunctionIndex(index); - auto* refFunc = Builder(wasm).makeRefFunc(getFunctionName(index), sig); + auto* refFunc = Builder(wasm).makeRefFunc(getFunctionName(index)); segmentData.push_back(refFunc); } } diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 512551b1eb7..e63bb9d488d 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -1736,7 +1736,7 @@ Result<> IRBuilder::makeRefIsNull() { } Result<> IRBuilder::makeRefFunc(Name func) { - push(builder.makeRefFunc(func, wasm.getFunction(func)->type.getHeapType())); + push(builder.makeRefFunc(func)); return Ok{}; } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 17a3fe1156c..44c1daea622 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -827,11 +827,7 @@ void RefFunc::finalize() { assert(type.isSignature()); } -void RefFunc::finalize(HeapType heapType, Module& wasm) { - type = Type(heapType, - NonNullable, - wasm.getFunction(func)->imported() ? Inexact : Exact); -} +void RefFunc::finalize(Module& wasm) { type = wasm.getFunction(func)->type; } void RefEq::finalize() { if (left->type == Type::unreachable || right->type == Type::unreachable) { From e6c3067dc1b8821a36aac4ffb142982066feffb7 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 7 Nov 2025 18:26:04 -0800 Subject: [PATCH 2/7] [Custom Descriptors] Exact function imports 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. --- src/parser/context-decls.cpp | 1 + src/parser/contexts.h | 8 +- src/parser/parsers.h | 50 ++++-- src/passes/Print.cpp | 6 + src/passes/StackCheck.cpp | 3 +- src/wasm-binary.h | 2 + src/wasm-builder.h | 2 +- src/wasm.h | 4 +- src/wasm/wasm-binary.cpp | 32 ++-- src/wasm/wasm-validator.cpp | 5 +- test/lit/basic/exact-imports.wast | 97 +++++++++++ test/spec/exact-func-import.wast | 268 ++++++++++++++++++++++++++++++ 12 files changed, 444 insertions(+), 34 deletions(-) create mode 100644 test/lit/basic/exact-imports.wast create mode 100644 test/spec/exact-func-import.wast diff --git a/src/parser/context-decls.cpp b/src/parser/context-decls.cpp index c124689d334..f02ec30dd66 100644 --- a/src/parser/context-decls.cpp +++ b/src/parser/context-decls.cpp @@ -67,6 +67,7 @@ Result<> ParseDeclsCtx::addFunc(Name name, const std::vector& exports, ImportNames* import, TypeUseT type, + Exactness exact, std::optional, std::vector&& annotations, Index pos) { diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 500c79ba315..d43967bffaa 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1078,6 +1078,7 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { const std::vector& exports, ImportNames* import, TypeUseT type, + Exactness exact, std::optional, std::vector&&, Index pos); @@ -1411,6 +1412,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, const std::vector&, ImportNames*, TypeUse type, + Exactness exact, std::optional locals, std::vector&&, Index pos) { @@ -1418,10 +1420,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, if (!type.type.isSignature()) { return in.err(pos, "expected signature type"); } - f->type = f->type.with(type.type); - if (f->imported()) { - f->type = f->type.with(Inexact); - } + f->type = Type(type.type, NonNullable, exact); // 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. @@ -1803,6 +1802,7 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { const std::vector&, ImportNames*, TypeUseT, + Exactness, std::optional, std::vector&&, Index) { diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 65753942864..7188d8aad20 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -370,6 +370,8 @@ Result labelidx(Ctx&, bool inDelegate = false); template Result tagidx(Ctx&); template Result typeuse(Ctx&, bool allowNames = true); +template +Result> exacttypeuse(Ctx&); MaybeResult inlineImport(Lexer&); Result> inlineExports(Lexer&); template Result<> comptype(Ctx&); @@ -3007,6 +3009,24 @@ Result typeuse(Ctx& ctx, bool allowNames) { return ctx.makeTypeUse(pos, type, namedParams.getPtr(), resultTypes.getPtr()); } +// exacttypeuse ::= typeuse | +// '(' 'exact' typeuse ')' +template +Result> exacttypeuse(Ctx& ctx) { + auto exact = Inexact; + if (ctx.in.takeSExprStart("exact"sv)) { + exact = Exact; + } + auto type = typeuse(ctx, true); + CHECK_ERR(type); + if (exact == Exact) { + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of exact type use"); + } + } + return std::make_pair(*type, exact); +} + // ('(' 'import' mod:name nm:name ')')? inline MaybeResult inlineImport(Lexer& in) { if (!in.takeSExprStart("import"sv)) { @@ -3221,7 +3241,7 @@ template MaybeResult locals(Ctx& ctx) { } // import ::= '(' 'import' mod:name nm:name importdesc ')' -// importdesc ::= '(' 'func' id? typeuse ')' +// importdesc ::= '(' 'func' id? exacttypeuse ')' // | '(' 'table' id? tabletype ')' // | '(' 'memory' id? memtype ')' // | '(' 'global' id? globaltype ')' @@ -3246,11 +3266,12 @@ template MaybeResult<> import_(Ctx& ctx) { if (ctx.in.takeSExprStart("func"sv)) { auto name = ctx.in.takeID(); - auto type = typeuse(ctx); - CHECK_ERR(type); + auto use = exacttypeuse(ctx); + CHECK_ERR(use); + auto [type, exact] = *use; // TODO: function import annotations CHECK_ERR(ctx.addFunc( - name ? *name : Name{}, {}, &names, *type, std::nullopt, {}, pos)); + name ? *name : Name{}, {}, &names, type, exact, std::nullopt, {}, pos)); } else if (ctx.in.takeSExprStart("table"sv)) { auto name = ctx.in.takeID(); auto type = tabletype(ctx); @@ -3289,7 +3310,7 @@ template MaybeResult<> import_(Ctx& ctx) { // func ::= '(' 'func' id? ('(' 'export' name ')')* // x,I:typeuse t*:vec(local) (in:instr)* ')' // | '(' 'func' id? ('(' 'export' name ')')* -// '(' 'import' mod:name nm:name ')' typeuse ')' +// '(' 'import' mod:name nm:name ')' exacttypeuse ')' template MaybeResult<> func(Ctx& ctx) { auto pos = ctx.in.getPos(); auto annotations = ctx.in.getAnnotations(); @@ -3309,11 +3330,19 @@ template MaybeResult<> func(Ctx& ctx) { auto import = inlineImport(ctx.in); CHECK_ERR(import); - auto type = typeuse(ctx); - CHECK_ERR(type); - + typename Ctx::TypeUseT type; + Exactness exact = Exact; std::optional localVars; - if (!import) { + + if (import) { + auto use = exacttypeuse(ctx); + CHECK_ERR(use); + type = use->first; + exact = use->second; + } else { + auto use = typeuse(ctx); + CHECK_ERR(use); + type = *use; if (auto l = locals(ctx)) { CHECK_ERR(l); localVars = *l; @@ -3331,7 +3360,8 @@ template MaybeResult<> func(Ctx& ctx) { CHECK_ERR(ctx.addFunc(name, *exports, import.getPtr(), - *type, + type, + exact, localVars, std::move(annotations), pos)); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 705722f498b..02c094c358d 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3078,6 +3078,9 @@ void PrintSExpression::handleSignature(Function* curr, o << '('; printMajor(o, "func "); curr->name.print(o); + if (curr->imported() && curr->type.isExact()) { + o << " (exact"; + } if ((currModule && currModule->features.hasGC()) || requiresExplicitFuncType(curr->type.getHeapType())) { o << " (type "; @@ -3124,6 +3127,9 @@ void PrintSExpression::handleSignature(Function* curr, o << maybeSpace; printResultType(curr->getResults()); } + if (curr->imported() && curr->type.isExact()) { + o << ')'; + } } void PrintSExpression::visitExport(Export* curr) { diff --git a/src/passes/StackCheck.cpp b/src/passes/StackCheck.cpp index 31bf791fd80..eee088054a9 100644 --- a/src/passes/StackCheck.cpp +++ b/src/passes/StackCheck.cpp @@ -41,7 +41,8 @@ importStackOverflowHandler(Module& module, Name name, Signature sig) { ImportInfo info(module); if (!info.getImportedFunction(ENV, name)) { - auto import = Builder::makeFunction(name, sig, {}); + auto import = + Builder::makeFunction(name, Type(sig, NonNullable, Inexact), {}); import->module = ENV; import->base = name; module.addFunction(std::move(import)); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index ee055dfbfbd..3117ca4c97f 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -356,6 +356,8 @@ enum BrOnCastFlag { OutputNullable = 1 << 1, }; +constexpr uint32_t ExactImport = 1 << 5; + enum EncodedType { // value types i32 = -0x1, // 0x7f diff --git a/src/wasm-builder.h b/src/wasm-builder.h index fef417fc1a5..d14e716f12b 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -47,7 +47,7 @@ class Builder { Type type, std::vector&& vars, Expression* body = nullptr) { - assert(type.isSignature()); + assert(type.isSignature() && type.isNonNullable()); auto func = std::make_unique(); func->name = name; func->type = type; diff --git a/src/wasm.h b/src/wasm.h index 757a0001501..6f7af64dcf6 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2343,13 +2343,13 @@ class Function : public Importable { }; // The kind of an import or export. -enum class ExternalKind { +enum class ExternalKind : uint32_t { Function = 0, Table = 1, Memory = 2, Global = 3, Tag = 4, - Invalid = -1 + Invalid = uint32_t(-1) }; // The kind of a top-level module item. (This overlaps with ExternalKind, but diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 57d034b3c62..d0eabf51f4d 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -332,8 +332,11 @@ void WasmBinaryWriter::writeImports() { }; ModuleUtils::iterImportedFunctions(*wasm, [&](Function* func) { writeImportHeader(func); - o << U32LEB(int32_t(ExternalKind::Function)); - o << U32LEB(getTypeIndex(func->type.getHeapType())); + auto kind = (uint32_t)ExternalKind::Function; + if (func->type.isExact()) { + kind |= BinaryConsts::ExactImport; + } + o << U32LEB(kind) << U32LEB(getTypeIndex(func->type.getHeapType())); }); ModuleUtils::iterImportedGlobals(*wasm, [&](Global* global) { writeImportHeader(global); @@ -2875,12 +2878,13 @@ void WasmBinaryReader::readImports() { for (size_t i = 0; i < num; i++) { auto module = getInlineString(); auto base = getInlineString(); - auto kind = (ExternalKind)getU32LEB(); + auto kind = getU32LEB(); // We set a unique prefix for the name based on the kind. This ensures no // collisions between them, which can't occur here (due to the index i) but // could occur later due to the names section. switch (kind) { - case ExternalKind::Function: { + case (uint32_t)ExternalKind::Function: + case (uint32_t)ExternalKind::Function | BinaryConsts::ExactImport: { auto [name, isExplicit] = getOrMakeName(functionNames, wasm.functions.size(), @@ -2894,8 +2898,9 @@ void WasmBinaryReader::readImports() { '.' + base.toString() + "'s type must be a signature. Given: " + type.toString()); } + auto exact = (kind & BinaryConsts::ExactImport) ? Exact : Inexact; auto curr = - builder.makeFunction(name, Type(type, NonNullable, Inexact), {}); + builder.makeFunction(name, Type(type, NonNullable, exact), {}); curr->hasExplicitName = isExplicit; curr->module = module; curr->base = base; @@ -2903,7 +2908,7 @@ void WasmBinaryReader::readImports() { wasm.addFunction(std::move(curr)); break; } - case ExternalKind::Table: { + case (uint32_t)ExternalKind::Table: { auto [name, isExplicit] = getOrMakeName(tableNames, wasm.tables.size(), @@ -2927,7 +2932,7 @@ void WasmBinaryReader::readImports() { wasm.addTable(std::move(table)); break; } - case ExternalKind::Memory: { + case (uint32_t)ExternalKind::Memory: { auto [name, isExplicit] = getOrMakeName(memoryNames, wasm.memories.size(), @@ -2945,7 +2950,7 @@ void WasmBinaryReader::readImports() { wasm.addMemory(std::move(memory)); break; } - case ExternalKind::Global: { + case (uint32_t)ExternalKind::Global: { auto [name, isExplicit] = getOrMakeName(globalNames, wasm.globals.size(), @@ -2967,7 +2972,7 @@ void WasmBinaryReader::readImports() { wasm.addGlobal(std::move(curr)); break; } - case ExternalKind::Tag: { + case (uint32_t)ExternalKind::Tag: { auto [name, isExplicit] = getOrMakeName(tagNames, wasm.tags.size(), @@ -4692,7 +4697,7 @@ void WasmBinaryReader::readExports() { throwError("duplicate export name"); } ExternalKind kind = (ExternalKind)getU32LEB(); - std::variant value; + std::optional> value; auto index = getU32LEB(); switch (kind) { case ExternalKind::Function: @@ -4711,9 +4716,12 @@ void WasmBinaryReader::readExports() { value = getTagName(index); break; case ExternalKind::Invalid: - throwError("invalid export kind"); + break; + } + if (!value) { + throwError("invalid export kind"); } - wasm.addExport(new Export(name, kind, value)); + wasm.addExport(new Export(name, kind, *value)); } } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index bd64dc4cead..94d3b137699 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4159,10 +4159,7 @@ void FunctionValidator::visitFunction(Function* curr) { curr->name, "all used types should be allowed"); - if (curr->imported()) { - shouldBeTrue( - !curr->type.isExact(), curr->name, "imported function should be inexact"); - } else { + if (!curr->imported()) { shouldBeTrue( curr->type.isExact(), curr->name, "defined function should be exact"); } diff --git a/test/lit/basic/exact-imports.wast b/test/lit/basic/exact-imports.wast new file mode 100644 index 00000000000..7ad6e87f0b3 --- /dev/null +++ b/test/lit/basic/exact-imports.wast @@ -0,0 +1,97 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + ;; CHECK-TEXT: (type $f (func (param i32) (result i64))) + ;; CHECK-BIN: (type $f (func (param i32) (result i64))) + (type $f (func (param i32) (result i64))) + ;; CHECK-TEXT: (import "" "" (func $1 (exact (type $f) (param i32) (result i64)))) + ;; CHECK-BIN: (import "" "" (func $1 (exact (type $f) (param i32) (result i64)))) + (import "" "" (func $1 (exact (type $f)))) + ;; CHECK-TEXT: (import "" "" (func $2 (exact (type $f) (param i32) (result i64)))) + ;; CHECK-BIN: (import "" "" (func $2 (exact (type $f) (param i32) (result i64)))) + (import "" "" (func $2 (exact (type $f) (param i32) (result i64)))) + ;; CHECK-TEXT: (import "" "" (func $3 (exact (type $f) (param i32) (result i64)))) + ;; CHECK-BIN: (import "" "" (func $3 (exact (type $f) (param i32) (result i64)))) + (import "" "" (func $3 (exact (param i32) (result i64)))) + + (func $4 (import "" "") (exact (type $f))) + (func $5 (import "" "") (exact (type $f) (param i32) (result i64))) + (func $6 (import "" "") (exact (param i32) (result i64))) + + (global (ref (exact $f)) (ref.func $1)) + (global (ref (exact $f)) (ref.func $2)) + (global (ref (exact $f)) (ref.func $3)) + (global (ref (exact $f)) (ref.func $4)) + (global (ref (exact $f)) (ref.func $5)) + (global (ref (exact $f)) (ref.func $6)) +) +;; CHECK-TEXT: (import "" "" (func $4 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-TEXT: (import "" "" (func $5 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-TEXT: (import "" "" (func $6 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-TEXT: (global $global$0 (ref (exact $f)) (ref.func $1)) + +;; CHECK-TEXT: (global $global$1 (ref (exact $f)) (ref.func $2)) + +;; CHECK-TEXT: (global $global$2 (ref (exact $f)) (ref.func $3)) + +;; CHECK-TEXT: (global $global$3 (ref (exact $f)) (ref.func $4)) + +;; CHECK-TEXT: (global $global$4 (ref (exact $f)) (ref.func $5)) + +;; CHECK-TEXT: (global $global$5 (ref (exact $f)) (ref.func $6)) + +;; CHECK-BIN: (import "" "" (func $4 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-BIN: (import "" "" (func $5 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-BIN: (import "" "" (func $6 (exact (type $f) (param i32) (result i64)))) + +;; CHECK-BIN: (global $global$0 (ref (exact $f)) (ref.func $1)) + +;; CHECK-BIN: (global $global$1 (ref (exact $f)) (ref.func $2)) + +;; CHECK-BIN: (global $global$2 (ref (exact $f)) (ref.func $3)) + +;; CHECK-BIN: (global $global$3 (ref (exact $f)) (ref.func $4)) + +;; CHECK-BIN: (global $global$4 (ref (exact $f)) (ref.func $5)) + +;; CHECK-BIN: (global $global$5 (ref (exact $f)) (ref.func $6)) + +;; CHECK-BIN-NODEBUG: (type $0 (func (param i32) (result i64))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$0 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$1 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$2 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$3 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$4 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (import "" "" (func $fimport$5 (exact (type $0) (param i32) (result i64)))) + +;; CHECK-BIN-NODEBUG: (global $global$0 (ref (exact $0)) (ref.func $fimport$0)) + +;; CHECK-BIN-NODEBUG: (global $global$1 (ref (exact $0)) (ref.func $fimport$1)) + +;; CHECK-BIN-NODEBUG: (global $global$2 (ref (exact $0)) (ref.func $fimport$2)) + +;; CHECK-BIN-NODEBUG: (global $global$3 (ref (exact $0)) (ref.func $fimport$3)) + +;; CHECK-BIN-NODEBUG: (global $global$4 (ref (exact $0)) (ref.func $fimport$4)) + +;; CHECK-BIN-NODEBUG: (global $global$5 (ref (exact $0)) (ref.func $fimport$5)) diff --git a/test/spec/exact-func-import.wast b/test/spec/exact-func-import.wast new file mode 100644 index 00000000000..49330fc7b84 --- /dev/null +++ b/test/spec/exact-func-import.wast @@ -0,0 +1,268 @@ +;; TODO: Use the upstream version from the custom descriptors proposal. + +(module + (type $f (func)) + (import "" "" (func $1 (exact (type 0)))) + (import "" "" (func $2 (exact (type $f) (param) (result)))) + (import "" "" (func $3 (exact (type $f)))) + (import "" "" (func $4 (exact (type 1)))) ;; Implicitly defined next + (import "" "" (func $5 (exact (param i32) (result i64)))) + + (func $6 (import "" "") (exact (type 0))) + (func $7 (import "" "") (exact (type $f) (param) (result))) + (func $8 (import "" "") (exact (type $f))) + (func $9 (import "" "") (exact (type 2))) ;; Implicitly defined next + (func $10 (import "" "") (exact (param i64) (result i32))) + + (global (ref (exact $f)) (ref.func $1)) + (global (ref (exact $f)) (ref.func $2)) + (global (ref (exact $f)) (ref.func $3)) + (global (ref (exact 1)) (ref.func $4)) + (global (ref (exact 1)) (ref.func $5)) + (global (ref (exact $f)) (ref.func $6)) + (global (ref (exact $f)) (ref.func $7)) + (global (ref (exact $f)) (ref.func $8)) + (global (ref (exact 2)) (ref.func $9)) + (global (ref (exact 2)) (ref.func $10)) +) + +;; References to inexact imports are not exact. + +(assert_invalid + (module + (type $f (func)) + (import "" "" (func $1 (type $f))) + (global (ref (exact $f)) (ref.func $1)) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $f (func)) + (import "" "" (func $1 (type $f))) + (elem declare func $1) + (func (result (ref (exact $f))) + (ref.func $1) + ) + ) + "type mismatch" +) + +;; Inexact imports can still be referenced inexactly, though. + +(module + (type $f (func)) + (import "" "" (func $1 (type $f))) + (global (ref $f) (ref.func $1)) + (func (result (ref $f)) + (ref.func $1) + ) +) + +;; Define a function and export it exactly. +(module $A + (type $f (func)) + (func (export "f") (type $f)) +) +(register "A") + +;; Import and re-export inexactly. +(module $B + (type $f (func)) + (func (export "f") (import "A" "f") (type $f)) +) +(register "B") + +;; The export from A is exact. +(module + (type $f (func)) + (import "A" "f" (func (exact (type $f)))) +) + +;; The export from B is _statically_ inexact, but instantiation checks +;; the dynamic types of imports, so this link still succeeds. +(module + (type $f (func)) + (import "B" "f" (func (exact (type $f)))) +) + +;; Even when the function is imported inexactly, it can still be cast to its +;; exact type. +(module + (type $f (func)) + (import "A" "f" (func $1 (type $f))) + (elem declare func $1) + (func (export "exact-test") (result i32) + (ref.test (ref (exact $f)) (ref.func $1)) + ) + (func (export "exact-cast") (result (ref (exact $f))) + (ref.cast (ref (exact $f)) (ref.func $1)) + ) + (func (export "exact-br-on-cast") (result funcref) + (br_on_cast 0 funcref (ref (exact $f)) (ref.func $1)) + (unreachable) + ) + (func (export "exact-br-on-cast-fail") (result funcref) + (block (result funcref) + (br_on_cast_fail 0 funcref (ref (exact $f)) (ref.func $1)) + (return) + ) + (unreachable) + ) +) + +(assert_return (invoke "exact-test") (i32.const 1)) +(assert_return (invoke "exact-cast") (ref.func)) +(assert_return (invoke "exact-br-on-cast") (ref.func)) +(assert_return (invoke "exact-br-on-cast-fail") (ref.func)) + +;; Define a function with a type that has a supertype. +(module $C + (type $super (sub (func))) + (type $sub (sub $super (func))) + (func (export "f") (type $sub)) + (func (export "g") (type $super)) +) +(register "C") + +;; TODO: Fix function type checking on linking. + +;; ;; We should not be able to import the function with the exact supertype. +;; (assert_unlinkable +;; (module +;; (type $super (sub (func))) +;; (type $sub (sub $super (func))) +;; (import "C" "f" (func (exact (type $super)))) +;; ) +;; "incompatible import type" +;; ) + +;; But we can still import it and re-export it inexactly with the supertype. +(module $D + (type $super (sub (func))) + (type $sub (sub $super (func))) + (import "C" "f" (func (type $super))) + (export "f" (func 0)) +) +(register "D") + +;; As before, we can still import the function with its real dynamic type. +(module + (type $super (sub (func))) + (type $sub (sub $super (func))) + (import "D" "f" (func (exact (type $sub)))) +) + +;; As before, even when the function is imported inexactly (this time with its +;; supertype), it can still be cast to its exact type. +(module + (type $super (sub (func))) + (type $sub (sub $super (func))) + (import "C" "f" (func $1 (type $super))) + (elem declare func $1) + (func (export "exact-test") (result i32) + (ref.test (ref (exact $sub)) (ref.func $1)) + ) + (func (export "exact-cast") (result (ref (exact $sub))) + (ref.cast (ref (exact $sub)) (ref.func $1)) + ) + (func (export "exact-br-on-cast") (result funcref) + (br_on_cast 0 funcref (ref (exact $sub)) (ref.func $1)) + (unreachable) + ) + (func (export "exact-br-on-cast-fail") (result funcref) + (block (result funcref) + (br_on_cast_fail 0 funcref (ref (exact $sub)) (ref.func $1)) + (return) + ) + (unreachable) + ) +) + +(assert_return (invoke "exact-test") (i32.const 1)) +(assert_return (invoke "exact-cast") (ref.func)) +(assert_return (invoke "exact-br-on-cast") (ref.func)) +(assert_return (invoke "exact-br-on-cast-fail") (ref.func)) + +;; But if we import a function whose dynamic type is the supertype, the same +;; casts will fail. +(module + (type $super (sub (func))) + (type $sub (sub $super (func))) + (import "C" "g" (func $1 (type $super))) + (elem declare func $1) + (func (export "exact-test") (result i32) + (ref.test (ref (exact $sub)) (ref.func $1)) + ) + (func (export "exact-cast") (result (ref (exact $sub))) + (ref.cast (ref (exact $sub)) (ref.func $1)) + ) + (func (export "exact-br-on-cast") (result funcref) + (br_on_cast 0 funcref (ref (exact $sub)) (ref.func $1)) + (unreachable) + ) + (func (export "exact-br-on-cast-fail") (result funcref) + (block (result funcref) + (br_on_cast_fail 0 funcref (ref (exact $sub)) (ref.func $1)) + (return) + ) + (unreachable) + ) +) + +(assert_return (invoke "exact-test") (i32.const 0)) +(assert_trap (invoke "exact-cast") "cast failure") +(assert_trap (invoke "exact-br-on-cast") "unreachable") +(assert_trap (invoke "exact-br-on-cast-fail") "unreachable") + + +;; Test the binary format + +;; Exact function imports use 0x20. +(module binary + "\00asm" "\01\00\00\00" + "\01" ;; Type section id + "\04" ;; Type section length + "\01" ;; Types vector length + "\60" ;; Function + "\00" ;; Number of params + "\00" ;; Number of results + "\02" ;; Import section id + "\05" ;; Import section length + "\01" ;; Import vector length + "\00" ;; Module name length + "\00" ;; Base name length + "\20" ;; Exact function + "\00" ;; Type index +) + +;; 0x20 is malformed in exports. +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\01" ;; Type section id + "\04" ;; Type section length + "\01" ;; Types vector length + "\60" ;; Function + "\00" ;; Number of params + "\00" ;; Number of results + "\03" ;; Function section id + "\02" ;; Function section length + "\01" ;; Function vector length + "\00" ;; Type index + "\07" ;; Export section id + "\04" ;; Export section length + "\01" ;; Export vector length + "\00" ;; Name length + "\20" ;; Exact func (malformed) + "\00" ;; Function index + "\0a" ;; Code section + "\04" ;; Code section length + "\01" ;; Code vector length + "\02" ;; Function length + "\00" ;; Type index + "\0b" ;; End + ) + "malformed export kind" +) From 02f2a07e140239040693d2c8cd6ee8e94e384987 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 10 Nov 2025 18:58:56 -0800 Subject: [PATCH 3/7] new scheme for int conversion --- src/wasm.h | 11 ++++++++--- src/wasm/wasm-binary.cpp | 14 +++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/wasm.h b/src/wasm.h index 6f7af64dcf6..3b229177269 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2342,15 +2342,20 @@ class Function : public Importable { void clearDebugInfo(); }; -// The kind of an import or export. -enum class ExternalKind : uint32_t { +// The kind of an import or export. Use a namespace to avoid polluting the wasm +// namespace while maintaining implicit conversion to int, which an enum class +// would not have. +namespace ExternalKindImpl { +enum Kind { Function = 0, Table = 1, Memory = 2, Global = 3, Tag = 4, - Invalid = uint32_t(-1) + Invalid = -1 }; +} // namespace ExternalKindImpl +using ExternalKind = ExternalKindImpl::Kind; // The kind of a top-level module item. (This overlaps with ExternalKind, but // C++ has no good way to extend an enum.) All such items are referred to by diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index d0eabf51f4d..f9085fe2578 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -332,7 +332,7 @@ void WasmBinaryWriter::writeImports() { }; ModuleUtils::iterImportedFunctions(*wasm, [&](Function* func) { writeImportHeader(func); - auto kind = (uint32_t)ExternalKind::Function; + uint32_t kind = ExternalKind::Function; if (func->type.isExact()) { kind |= BinaryConsts::ExactImport; } @@ -2883,8 +2883,8 @@ void WasmBinaryReader::readImports() { // collisions between them, which can't occur here (due to the index i) but // could occur later due to the names section. switch (kind) { - case (uint32_t)ExternalKind::Function: - case (uint32_t)ExternalKind::Function | BinaryConsts::ExactImport: { + case ExternalKind::Function: + case ExternalKind::Function | BinaryConsts::ExactImport: { auto [name, isExplicit] = getOrMakeName(functionNames, wasm.functions.size(), @@ -2908,7 +2908,7 @@ void WasmBinaryReader::readImports() { wasm.addFunction(std::move(curr)); break; } - case (uint32_t)ExternalKind::Table: { + case ExternalKind::Table: { auto [name, isExplicit] = getOrMakeName(tableNames, wasm.tables.size(), @@ -2932,7 +2932,7 @@ void WasmBinaryReader::readImports() { wasm.addTable(std::move(table)); break; } - case (uint32_t)ExternalKind::Memory: { + case ExternalKind::Memory: { auto [name, isExplicit] = getOrMakeName(memoryNames, wasm.memories.size(), @@ -2950,7 +2950,7 @@ void WasmBinaryReader::readImports() { wasm.addMemory(std::move(memory)); break; } - case (uint32_t)ExternalKind::Global: { + case ExternalKind::Global: { auto [name, isExplicit] = getOrMakeName(globalNames, wasm.globals.size(), @@ -2972,7 +2972,7 @@ void WasmBinaryReader::readImports() { wasm.addGlobal(std::move(curr)); break; } - case (uint32_t)ExternalKind::Tag: { + case ExternalKind::Tag: { auto [name, isExplicit] = getOrMakeName(tagNames, wasm.tags.size(), From eac76a331c984f7cc7e495414e8adbefada22d10 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 11 Nov 2025 18:23:15 -0800 Subject: [PATCH 4/7] consistency and ubsan fix --- src/wasm.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wasm.h b/src/wasm.h index 3b229177269..85dd3d10403 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2361,7 +2361,8 @@ using ExternalKind = ExternalKindImpl::Kind; // C++ has no good way to extend an enum.) All such items are referred to by // name in the IR (that is, the IR is relocatable), and so they are subclasses // of the Named class. -enum class ModuleItemKind { +namespace ModuleItemKindImpl { +enum Kind { Function = 0, Table = 1, Memory = 2, @@ -2371,6 +2372,9 @@ enum class ModuleItemKind { ElementSegment = 6, Invalid = -1 }; +} // namespace ModuleItemKindImpl + +using ModuleItemKind = ModuleItemKindImpl::Kind; class Export { public: From 43a5482899848d5d9edba2d1832918fce413c834 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 11 Nov 2025 18:26:00 -0800 Subject: [PATCH 5/7] oops --- src/wasm.h | 8 ++++---- src/wasm/wasm-binary.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wasm.h b/src/wasm.h index 85dd3d10403..3350df5fa7f 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2346,13 +2346,13 @@ class Function : public Importable { // namespace while maintaining implicit conversion to int, which an enum class // would not have. namespace ExternalKindImpl { -enum Kind { +enum Kind : uint32_t { Function = 0, Table = 1, Memory = 2, Global = 3, Tag = 4, - Invalid = -1 + Invalid = uint32_t(-1) }; } // namespace ExternalKindImpl using ExternalKind = ExternalKindImpl::Kind; @@ -2362,7 +2362,7 @@ using ExternalKind = ExternalKindImpl::Kind; // name in the IR (that is, the IR is relocatable), and so they are subclasses // of the Named class. namespace ModuleItemKindImpl { -enum Kind { +enum Kind : uint32_t { Function = 0, Table = 1, Memory = 2, @@ -2370,7 +2370,7 @@ enum Kind { Tag = 4, DataSegment = 5, ElementSegment = 6, - Invalid = -1 + Invalid = uint32_t(-1) }; } // namespace ModuleItemKindImpl diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f9085fe2578..21d9732d398 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4696,7 +4696,7 @@ void WasmBinaryReader::readExports() { if (!names.emplace(name).second) { throwError("duplicate export name"); } - ExternalKind kind = (ExternalKind)getU32LEB(); + auto kind = getU32LEB(); std::optional> value; auto index = getU32LEB(); switch (kind) { @@ -4721,7 +4721,7 @@ void WasmBinaryReader::readExports() { if (!value) { throwError("invalid export kind"); } - wasm.addExport(new Export(name, kind, *value)); + wasm.addExport(new Export(name, ExternalKind(kind), *value)); } } From e37a0dfa4dd7887eeffacc973be250763a412272 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 11 Nov 2025 18:26:30 -0800 Subject: [PATCH 6/7] whitespace --- src/wasm.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wasm.h b/src/wasm.h index 3350df5fa7f..99e947cd7eb 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2373,7 +2373,6 @@ enum Kind : uint32_t { Invalid = uint32_t(-1) }; } // namespace ModuleItemKindImpl - using ModuleItemKind = ModuleItemKindImpl::Kind; class Export { From 69b0b0153f9d2927032aebbebfa053446371490b Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 11 Nov 2025 22:39:47 -0800 Subject: [PATCH 7/7] undo unnecessary change --- src/wasm.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/wasm.h b/src/wasm.h index 99e947cd7eb..605c7025395 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2361,8 +2361,7 @@ using ExternalKind = ExternalKindImpl::Kind; // C++ has no good way to extend an enum.) All such items are referred to by // name in the IR (that is, the IR is relocatable), and so they are subclasses // of the Named class. -namespace ModuleItemKindImpl { -enum Kind : uint32_t { +enum class ModuleItemKind { Function = 0, Table = 1, Memory = 2, @@ -2370,10 +2369,8 @@ enum Kind : uint32_t { Tag = 4, DataSegment = 5, ElementSegment = 6, - Invalid = uint32_t(-1) + Invalid = -1 }; -} // namespace ModuleItemKindImpl -using ModuleItemKind = ModuleItemKindImpl::Kind; class Export { public: