From 47697022cd049ce6818b15088f1492a229e13be6 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sat, 19 Apr 2025 14:24:32 +0100 Subject: [PATCH 01/28] allow non-nullable ref types + init exprs in tables --- src/binaryen-c.cpp | 4 +++- src/binaryen-c.h | 3 ++- src/js/binaryen.js-post.js | 4 ++-- src/passes/Print.cpp | 7 +++++- src/passes/RemoveUnusedModuleElements.cpp | 7 +++++- src/wasm-builder.h | 4 +++- src/wasm.h | 2 ++ src/wasm/wasm-binary.cpp | 28 ++++++++++++++++++++--- src/wasm/wasm-validator.cpp | 24 +++++++++++++++---- 9 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index a68e3b3f1dc..5498d542717 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -5333,8 +5333,10 @@ BinaryenTableRef BinaryenAddTable(BinaryenModuleRef module, const char* name, BinaryenIndex initial, BinaryenIndex maximum, - BinaryenType tableType) { + BinaryenType tableType, + BinaryenExpressionRef init) { auto table = Builder::makeTable(name, Type(tableType), initial, maximum); + table->init = init; table->hasExplicitName = true; return ((Module*)module)->addTable(std::move(table)); } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 6fc35cefc2c..3e4b433be0e 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -2931,7 +2931,8 @@ BINARYEN_API BinaryenTableRef BinaryenAddTable(BinaryenModuleRef module, const char* table, BinaryenIndex initial, BinaryenIndex maximum, - BinaryenType tableType); + BinaryenType tableType, + BinaryenExpressionRef init); BINARYEN_API void BinaryenRemoveTable(BinaryenModuleRef module, const char* table); BINARYEN_API BinaryenIndex BinaryenGetNumTables(BinaryenModuleRef module); diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 50688181a24..81d7c79e79e 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -2612,8 +2612,8 @@ function wrapModule(module, self = {}) { self['getGlobal'] = function(name) { return preserveStack(() => Module['_BinaryenGetGlobal'](module, strToStack(name))); }; - self['addTable'] = function(table, initial, maximum, type = Module['_BinaryenTypeFuncref']()) { - return preserveStack(() => Module['_BinaryenAddTable'](module, strToStack(table), initial, maximum, type)); + self['addTable'] = function(table, initial, maximum, type = Module['_BinaryenTypeFuncref'](), init = null) { + return preserveStack(() => Module['_BinaryenAddTable'](module, strToStack(table), initial, maximum, type, init)); } self['getTable'] = function(name) { return preserveStack(() => Module['_BinaryenGetTable'](module, strToStack(name))); diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index c34e5d0dac9..d1cc1b82e20 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3409,7 +3409,12 @@ void PrintSExpression::printTableHeader(Table* curr) { o << ' ' << curr->max; } o << ' '; - printType(curr->type) << ')'; + printType(curr->type); + if (curr->hasInit()) { + o << ' '; + visit(curr->init); + } + o << ')'; } void PrintSExpression::visitTable(Table* curr) { diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index c52f9872246..bf96b763316 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -538,14 +538,19 @@ struct Analyzer { } }); break; - case ModuleElementKind::Table: + case ModuleElementKind::Table: { ModuleUtils::iterTableSegments( *module, value, [&](ElementSegment* segment) { if (!segment->data.empty()) { use({ModuleElementKind::ElementSegment, segment->name}); } }); + auto* table = module->getTable(value); + if (table->hasInit()) { + use(table->init); + } break; + } case ModuleElementKind::DataSegment: { auto* segment = module->getDataSegment(value); if (segment->offset) { diff --git a/src/wasm-builder.h b/src/wasm-builder.h index e59336283bc..6c6cb37743e 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -107,13 +107,15 @@ class Builder { Nullable), Address initial = 0, Address max = Table::kMaxSize, - Type addressType = Type::i32) { + Type addressType = Type::i32, + Expression* init = nullptr) { auto table = std::make_unique(); table->name = name; table->type = type; table->addressType = addressType; table->initial = initial; table->max = max; + table->init = init; return table; } diff --git a/src/wasm.h b/src/wasm.h index ba359577eeb..7d7424a455b 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2514,9 +2514,11 @@ class Table : public Importable { Address max = kMaxSize; Type addressType = Type::i32; Type type = Type(HeapType::func, Nullable); + Expression* init = nullptr; bool hasMax() { return max != kUnlimitedSize; } bool is64() { return addressType == Type::i64; } + bool hasInit() { return init != nullptr; } void clear() { name = ""; initial = 0; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 3a242535bdc..94328c18861 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -773,12 +773,20 @@ void WasmBinaryWriter::writeTableDeclarations() { auto num = importInfo->getNumDefinedTables(); o << U32LEB(num); ModuleUtils::iterDefinedTables(*wasm, [&](Table* table) { + if (table->hasInit()) { + o << uint8_t(0x40); + o << uint8_t(0x00); + } writeType(table->type); writeResizableLimits(table->initial, table->max, table->hasMax(), /*shared=*/false, table->is64()); + if (table->hasInit()) { + writeExpression(table->init); + o << uint8_t(BinaryConsts::End); + } }); finishSection(start); } @@ -5000,9 +5008,19 @@ void WasmBinaryReader::readTableDeclarations() { } } for (size_t i = 0; i < num; i++) { - auto [name, isExplicit] = getOrMakeName( - tableNames, numImports + i, makeName("", i), usedTableNames); - auto elemType = getType(); + auto [name, isExplicit] = + getOrMakeName(tableNames, numImports + i, makeName("", i), usedTableNames); + auto type_code = getS32LEB(); + bool has_init = false; + if (type_code == BinaryConsts::EncodedType::Empty) { + auto nextInt = getInt8(); + if (nextInt != 0x00) { + throwError("Malformed table"); + } + has_init = true; + type_code = getS32LEB(); + } + auto elemType = getType(type_code); if (!elemType.isRef()) { throwError("Table type must be a reference type"); } @@ -5017,6 +5035,10 @@ void WasmBinaryReader::readTableDeclarations() { if (is_shared) { throwError("Tables may not be shared"); } + if (has_init) { + auto* init = readExpression(); + table->init = init; + } wasm.addTable(std::move(table)); } } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index ca550d7225a..950025f35d5 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4741,10 +4741,26 @@ void validateTables(Module& module, ValidationInfo& info) { info.shouldBeTrue(table->initial <= table->max, "table", "size minimum must not be greater than maximum"); - info.shouldBeTrue( - table->type.isNullable(), - "table", - "Non-nullable reference types are not yet supported for tables"); + if (!table->type.isNullable()) { + info.shouldBeTrue( + table->init != nullptr, + "table", + "tables with non-nullable types require an initializer expression"); + info.shouldBeSubType(table->init->type, + table->type, + table->init, + "init expression must be a subtype of the table type"); + info.shouldBeTrue(Properties::isValidConstantExpression(module, table->init), + "table", + "table initializer value must be constant"); + validator.validate(table->init); + } + if (!module.features.hasGC()) { + info.shouldBeFalse(table->hasInit(), + "table", + "tables cannot have an initializer expression in MVP " + "(requires --enable-gc)."); + } auto typeFeats = table->type.getFeatures(); if (!info.shouldBeTrue(table->type == funcref || typeFeats <= module.features, From 1855e6c9133b276f03befdfcf0fc43b6c508f104 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:08:33 +0000 Subject: [PATCH 02/28] add support for nullable tables & init expressions in parser + interpreter --- src/parser/context-decls.cpp | 1 + src/parser/contexts.h | 6 +++--- src/parser/parsers.h | 10 +++++++--- src/wasm-interpreter.h | 13 ++++++++----- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/parser/context-decls.cpp b/src/parser/context-decls.cpp index 888d6874842..345e01c5034 100644 --- a/src/parser/context-decls.cpp +++ b/src/parser/context-decls.cpp @@ -108,6 +108,7 @@ Result<> ParseDeclsCtx::addTable(Name name, const std::vector& exports, ImportNames* import, TableType type, + std::optional, Index pos) { CHECK_ERR(checkImport(pos, import)); auto t = addTableDecl(pos, name, import, type); diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 0e2f959c0e4..87b69f2fa07 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1118,7 +1118,7 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { ImportNames* importNames, TableType limits); Result<> - addTable(Name, const std::vector&, ImportNames*, TableType, Index); + addTable(Name, const std::vector&, ImportNames*, TableType, std::optional, Index); // TODO: Record index of implicit elem for use when parsing types and instrs. Result<> addImplicitElems(TypeT, ElemListT&& elems); @@ -1501,7 +1501,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, } Result<> addTable( - Name, const std::vector&, ImportNames*, Type ttype, Index pos) { + Name, const std::vector&, ImportNames*, Type ttype, std::optional, Index pos) { auto& t = wasm.tables[index]; if (!ttype.isRef()) { return in.err(pos, "expected reference type"); @@ -1866,7 +1866,7 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { } Result<> - addTable(Name, const std::vector&, ImportNames*, TableTypeT, Index) { + addTable(Name, const std::vector&, ImportNames*, TableTypeT, std::optional, Index) { return Ok{}; } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 2da13ee90c1..33a736fed40 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -3379,7 +3379,7 @@ template MaybeResult<> import_(Ctx& ctx) { auto name = ctx.in.takeID(); auto type = tabletype(ctx); CHECK_ERR(type); - CHECK_ERR(ctx.addTable(name ? *name : Name{}, {}, &names, *type, pos)); + CHECK_ERR(ctx.addTable(name ? *name : Name{}, {}, &names, *type, std::nullopt, pos)); } else if (ctx.in.takeSExprStart("memory"sv)) { auto name = ctx.in.takeID(); auto type = memtype(ctx); @@ -3472,7 +3472,7 @@ template MaybeResult<> func(Ctx& ctx) { } // table ::= '(' 'table' id? ('(' 'export' name ')')* -// '(' 'import' mod:name nm:name ')'? index_type? tabletype ')' +// '(' 'import' mod:name nm:name ')'? index_type? tabletype expr? ')' // | '(' 'table' id? ('(' 'export' name ')')* index_type? // reftype '(' 'elem' (elemexpr* | funcidx*) ')' ')' template MaybeResult<> table(Ctx& ctx) { @@ -3505,6 +3505,7 @@ template MaybeResult<> table(Ctx& ctx) { std::optional ttype; std::optional elems; + std::optional init; if (type) { // We should have inline elements. if (!ctx.in.takeSExprStart("elem"sv)) { @@ -3539,13 +3540,16 @@ template MaybeResult<> table(Ctx& ctx) { auto tabtype = tabletypeContinued(ctx, addressType); CHECK_ERR(tabtype); ttype = *tabtype; + auto e = expr(ctx); + CHECK_ERR(e); + init = *e; } if (!ctx.in.takeRParen()) { return ctx.in.err("expected end of table declaration"); } - CHECK_ERR(ctx.addTable(name, *exports, import.getPtr(), *ttype, pos)); + CHECK_ERR(ctx.addTable(name, *exports, import.getPtr(), *ttype, init, pos)); if (elems) { CHECK_ERR(ctx.addImplicitElems(*type, std::move(*elems))); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 48404207a60..e355910adb8 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3541,12 +3541,15 @@ class ModuleRunnerBase : public ExpressionRunner { // parsing/validation checked this already. assert(inserted && "Unexpected repeated table name"); } else { - assert(table->type.isNullable() && - "We only support nullable tables today"); - - auto null = Literal::makeNull(table->type.getHeapType()); + Literal initVal; + if (table->type.isNullable()) { + initVal = Literal::makeNull(table->type.getHeapType()); + } else { + assert(table->hasInit() && "Non-nullable table must have an init expressions"); + initVal = ExpressionRunner::visit(table->init).getSingleValue(); + } auto& runtimeTable = - definedTables.emplace_back(createTable(null, *table)); + definedTables.emplace_back(createTable(initVal, *table)); [[maybe_unused]] auto [_, inserted] = allTables.try_emplace(table->name, runtimeTable.get()); assert(inserted && "Unexpected repeated table name"); From 6a09536f8e6343d5d0dd2e1ecf231b388a7bbe73 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sat, 28 Feb 2026 23:25:27 +0000 Subject: [PATCH 03/28] properly initialise table init when parsing WAT --- src/parser/context-defs.cpp | 12 ++++++++++++ src/parser/contexts.h | 6 ++---- src/wasm-traversal.h | 1 + src/wasm/wasm-validator.cpp | 14 +++++++++----- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/parser/context-defs.cpp b/src/parser/context-defs.cpp index 629f94ac274..c69c428d5bc 100644 --- a/src/parser/context-defs.cpp +++ b/src/parser/context-defs.cpp @@ -62,6 +62,18 @@ Result<> ParseDefsCtx::addGlobal(Name, return Ok{}; } +Result<> ParseDefsCtx::addTable(Name, + const std::vector&, + ImportNames*, + TableTypeT, + std::optional init, + Index) { + if (init) { + wasm.tables[index]->init = *init; + } + return Ok{}; +} + Result<> ParseDefsCtx::addImplicitElems(Type, std::vector&& elems) { auto& e = wasm.elementSegments[implicitElemIndices.at(index)]; diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 87b69f2fa07..4400665d50a 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1501,7 +1501,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, } Result<> addTable( - Name, const std::vector&, ImportNames*, Type ttype, std::optional, Index pos) { + Name, const std::vector&, ImportNames*, Type ttype, std::optional init, Index pos) { auto& t = wasm.tables[index]; if (!ttype.isRef()) { return in.err(pos, "expected reference type"); @@ -1866,9 +1866,7 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { } Result<> - addTable(Name, const std::vector&, ImportNames*, TableTypeT, std::optional, Index) { - return Ok{}; - } + addTable(Name, const std::vector&, ImportNames*, TableTypeT, std::optional, Index); Result<> addMemory(Name, const std::vector&, ImportNames*, TableTypeT, Index) { diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index db9d1c1c4f9..b333b361d9f 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -179,6 +179,7 @@ struct Walker : public VisitorType { } void walkTable(Table* table) { + walk(table->init); static_cast(this)->visitTable(table); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 950025f35d5..af44b894468 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4746,14 +4746,18 @@ void validateTables(Module& module, ValidationInfo& info) { table->init != nullptr, "table", "tables with non-nullable types require an initializer expression"); - info.shouldBeSubType(table->init->type, + if (table->init != nullptr) { + info.shouldBeSubType( + table->init->type, table->type, table->init, "init expression must be a subtype of the table type"); - info.shouldBeTrue(Properties::isValidConstantExpression(module, table->init), - "table", - "table initializer value must be constant"); - validator.validate(table->init); + info.shouldBeTrue( + Properties::isValidConstantExpression(module, table->init), + "table", + "table initializer value must be constant"); + validator.validate(table->init); + } } if (!module.features.hasGC()) { info.shouldBeFalse(table->hasInit(), From 0cdd750aeadbeca05c7d57dd9f0b0549283b4c36 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sun, 1 Mar 2026 00:56:10 +0000 Subject: [PATCH 04/28] only parse table init if it exists --- src/parser/parsers.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 33a736fed40..9b62d99dcf4 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -3540,9 +3540,11 @@ template MaybeResult<> table(Ctx& ctx) { auto tabtype = tabletypeContinued(ctx, addressType); CHECK_ERR(tabtype); ttype = *tabtype; - auto e = expr(ctx); - CHECK_ERR(e); - init = *e; + if (ctx.in.peekLParen()) { + auto e = expr(ctx); + CHECK_ERR(e); + init = *e; + } } if (!ctx.in.takeRParen()) { From 7ef15026a895c09ce387e629853dec1a5df8d06f Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sun, 1 Mar 2026 15:30:04 +0000 Subject: [PATCH 05/28] consider that nullable tables might have an init expr --- src/wasm-interpreter.h | 8 ++++---- src/wasm/wasm-validator.cpp | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index e355910adb8..664d6bfc757 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3542,11 +3542,11 @@ class ModuleRunnerBase : public ExpressionRunner { assert(inserted && "Unexpected repeated table name"); } else { Literal initVal; - if (table->type.isNullable()) { - initVal = Literal::makeNull(table->type.getHeapType()); - } else { - assert(table->hasInit() && "Non-nullable table must have an init expressions"); + if (table->hasInit()) { initVal = ExpressionRunner::visit(table->init).getSingleValue(); + } else { + assert(table->type.isNullable() && "Non-nullable table must have an init expressions"); + initVal = Literal::makeNull(table->type.getHeapType()); } auto& runtimeTable = definedTables.emplace_back(createTable(initVal, *table)); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index af44b894468..21914992bcf 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4746,18 +4746,18 @@ void validateTables(Module& module, ValidationInfo& info) { table->init != nullptr, "table", "tables with non-nullable types require an initializer expression"); - if (table->init != nullptr) { - info.shouldBeSubType( - table->init->type, - table->type, - table->init, - "init expression must be a subtype of the table type"); - info.shouldBeTrue( - Properties::isValidConstantExpression(module, table->init), - "table", - "table initializer value must be constant"); - validator.validate(table->init); - } + } + if (table->init != nullptr) { + info.shouldBeSubType( + table->init->type, + table->type, + table->init, + "init expression must be a subtype of the table type"); + info.shouldBeTrue( + Properties::isValidConstantExpression(module, table->init), + "table", + "table initializer value must be constant"); + validator.validate(table->init); } if (!module.features.hasGC()) { info.shouldBeFalse(table->hasInit(), From bbd1d4e687a093bb4a83474b2d9a786138e4ee60 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sun, 1 Mar 2026 16:32:58 +0000 Subject: [PATCH 06/28] add missing argument to addTable in C api tests --- test/example/c-api-kitchen-sink.c | 10 +++++----- test/example/c-api-multiple-tables.c | 4 ++-- test/spec/testsuite | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index 2499ffff646..73721372089 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -383,8 +383,8 @@ void test_read_with_feature() { BinaryenModuleRef module = BinaryenModuleCreate(); // Having multiple tables makes this module inherently not MVP compatible // and requires the externref feature enabled to parse successfully. - BinaryenAddTable(module, "tab", 0, 100, BinaryenTypeFuncref()); - BinaryenAddTable(module, "tab2", 0, 100, BinaryenTypeFuncref()); + BinaryenAddTable(module, "tab", 0, 100, BinaryenTypeFuncref(), NULL); + BinaryenAddTable(module, "tab2", 0, 100, BinaryenTypeFuncref(), NULL); BinaryenFeatures features = BinaryenFeatureMVP() | BinaryenFeatureReferenceTypes(); @@ -489,7 +489,7 @@ void test_core() { // Tags BinaryenAddTag(module, "a-tag", BinaryenTypeInt32(), BinaryenTypeNone()); - BinaryenAddTable(module, "tab", 0, 100, BinaryenTypeFuncref()); + BinaryenAddTable(module, "tab", 0, 100, BinaryenTypeFuncref(), NULL); // Exception handling @@ -1369,7 +1369,7 @@ void test_core() { // Function table. One per module const char* funcNames[] = {BinaryenFunctionGetName(sinker)}; - BinaryenAddTable(module, "0", 1, 1, BinaryenTypeFuncref()); + BinaryenAddTable(module, "0", 1, 1, BinaryenTypeFuncref(), NULL); BinaryenAddActiveElementSegment( module, "0", @@ -2021,7 +2021,7 @@ void test_for_each() { BinaryenFunctionGetName(fns[2])}; BinaryenExpressionRef constExprRef = BinaryenConst(module, BinaryenLiteralInt32(0)); - BinaryenAddTable(module, "0", 1, 1, BinaryenTypeFuncref()); + BinaryenAddTable(module, "0", 1, 1, BinaryenTypeFuncref(), NULL); BinaryenAddActiveElementSegment( module, "0", "0", funcNames, 3, constExprRef); assert(1 == BinaryenGetNumElementSegments(module)); diff --git a/test/example/c-api-multiple-tables.c b/test/example/c-api-multiple-tables.c index 4d1eefcfcf5..14744845661 100644 --- a/test/example/c-api-multiple-tables.c +++ b/test/example/c-api-multiple-tables.c @@ -31,7 +31,7 @@ int main() { BinaryenAddFunction(module, "adder", params, results, NULL, 0, add); const char* funcNames[] = {"adder"}; - BinaryenAddTable(module, "tab", 1, 1, BinaryenTypeFuncref()); + BinaryenAddTable(module, "tab", 1, 1, BinaryenTypeFuncref(), NULL); assert(BinaryenGetTable(module, "tab") != NULL); BinaryenAddActiveElementSegment( module, @@ -41,7 +41,7 @@ int main() { 1, BinaryenConst(module, BinaryenLiteralInt32(0))); - BinaryenAddTable(module, "t2", 1, 1, BinaryenTypeFuncref()); + BinaryenAddTable(module, "t2", 1, 1, BinaryenTypeFuncref(), NULL); BinaryenAddActiveElementSegment( module, "t2", diff --git a/test/spec/testsuite b/test/spec/testsuite index 4b24564c844..c337f0da647 160000 --- a/test/spec/testsuite +++ b/test/spec/testsuite @@ -1 +1 @@ -Subproject commit 4b24564c844e3d34bf46dfcb3c774ee5163e31cc +Subproject commit c337f0da6477acd40fbcab98671a68f59106ad86 From 41fc39833e8df82e3a1587b1758b38cb22cb4ab6 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sun, 1 Mar 2026 19:45:42 +0000 Subject: [PATCH 07/28] allow non-nullable types in element segments --- src/wasm/wasm-validator.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 21914992bcf..336b6612acc 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4784,10 +4784,6 @@ void validateTables(Module& module, ValidationInfo& info) { info.shouldBeTrue(segment->type.isRef(), "elem", "element segment type must be of reference type."); - info.shouldBeTrue( - segment->type.isNullable(), - "elem", - "Non-nullable reference types are not yet supported for tables"); auto typeFeats = segment->type.getFeatures(); if (!info.shouldBeTrue( segment->type == funcref || typeFeats <= module.features, From a1f578185afaa37d0d58fd632b882f1cb70960e6 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Sun, 1 Mar 2026 19:56:31 +0000 Subject: [PATCH 08/28] unignore some spec tests that now pass --- scripts/test/shared.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 48f10b5a52b..404197a5f86 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -421,22 +421,18 @@ def get_tests(test_dir, extensions=[], recursive=False): 'linking.wast', # Missing function type validation on instantiation 'proposals/threads/memory.wast', # Missing memory type validation on instantiation 'annotations.wast', # String annotations IDs should be allowed - 'instance.wast', # Requires support for table default elements 'table64.wast', # Requires validations for table size 'tag.wast', # Non-empty tag results allowed by stack switching 'local_init.wast', # Requires local validation to respect unnamed blocks 'ref_func.wast', # Requires rejecting undeclared functions references - 'ref_is_null.wast', # Requires support for non-nullable reference types in tables 'return_call_indirect.wast', # Requires more precise unreachable validation 'select.wast', # Missing validation of type annotation on select - 'table.wast', # Requires support for table default elements 'unreached-invalid.wast', # Requires more precise unreachable validation - 'array.wast', # Requires support for table default elements + 'array.wast', # Failure to parse element segment item abbreviation 'br_if.wast', # Requires more precise branch validation 'br_on_cast.wast', # Requires host references to not be externalized i31refs 'br_on_cast_fail.wast', # Requires host references to not be externalized i31refs 'extern.wast', # Requires ref.host wast constants - 'i31.wast', # Requires support for table default elements 'ref_cast.wast', # Requires host references to not be externalized i31refs 'ref_test.wast', # Requires host references to not be externalized i31refs 'struct.wast', # Duplicate field names not properly rejected From dc47c17cc00cad8a3b3b26ed6397213b2243238e Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:10:17 +0000 Subject: [PATCH 09/28] revert testsuite submodule update --- test/spec/testsuite | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/testsuite b/test/spec/testsuite index c337f0da647..4b24564c844 160000 --- a/test/spec/testsuite +++ b/test/spec/testsuite @@ -1 +1 @@ -Subproject commit c337f0da6477acd40fbcab98671a68f59106ad86 +Subproject commit 4b24564c844e3d34bf46dfcb3c774ee5163e31cc From 8024e798d7b9af6c0554d5901be1196e67b4dac7 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:16:09 +0000 Subject: [PATCH 10/28] format code --- src/parser/contexts.h | 24 ++++++++++++++++++------ src/parser/parsers.h | 6 ++++-- src/wasm-interpreter.h | 6 ++++-- src/wasm/wasm-binary.cpp | 4 ++-- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 4400665d50a..bd4d9742b29 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1117,8 +1117,12 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { Name name, ImportNames* importNames, TableType limits); - Result<> - addTable(Name, const std::vector&, ImportNames*, TableType, std::optional, Index); + Result<> addTable(Name, + const std::vector&, + ImportNames*, + TableType, + std::optional, + Index); // TODO: Record index of implicit elem for use when parsing types and instrs. Result<> addImplicitElems(TypeT, ElemListT&& elems); @@ -1500,8 +1504,12 @@ struct ParseModuleTypesCtx : TypeParserCtx, return Ok{}; } - Result<> addTable( - Name, const std::vector&, ImportNames*, Type ttype, std::optional init, Index pos) { + Result<> addTable(Name, + const std::vector&, + ImportNames*, + Type ttype, + std::optional init, + Index pos) { auto& t = wasm.tables[index]; if (!ttype.isRef()) { return in.err(pos, "expected reference type"); @@ -1865,8 +1873,12 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return Ok{}; } - Result<> - addTable(Name, const std::vector&, ImportNames*, TableTypeT, std::optional, Index); + Result<> addTable(Name, + const std::vector&, + ImportNames*, + TableTypeT, + std::optional, + Index); Result<> addMemory(Name, const std::vector&, ImportNames*, TableTypeT, Index) { diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 9b62d99dcf4..37a866b6116 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -3379,7 +3379,8 @@ template MaybeResult<> import_(Ctx& ctx) { auto name = ctx.in.takeID(); auto type = tabletype(ctx); CHECK_ERR(type); - CHECK_ERR(ctx.addTable(name ? *name : Name{}, {}, &names, *type, std::nullopt, pos)); + CHECK_ERR(ctx.addTable( + name ? *name : Name{}, {}, &names, *type, std::nullopt, pos)); } else if (ctx.in.takeSExprStart("memory"sv)) { auto name = ctx.in.takeID(); auto type = memtype(ctx); @@ -3472,7 +3473,8 @@ template MaybeResult<> func(Ctx& ctx) { } // table ::= '(' 'table' id? ('(' 'export' name ')')* -// '(' 'import' mod:name nm:name ')'? index_type? tabletype expr? ')' +// '(' 'import' mod:name nm:name ')'? index_type? tabletype expr? +// ')' // | '(' 'table' id? ('(' 'export' name ')')* index_type? // reftype '(' 'elem' (elemexpr* | funcidx*) ')' ')' template MaybeResult<> table(Ctx& ctx) { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 664d6bfc757..6693a0042d9 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3543,9 +3543,11 @@ class ModuleRunnerBase : public ExpressionRunner { } else { Literal initVal; if (table->hasInit()) { - initVal = ExpressionRunner::visit(table->init).getSingleValue(); + initVal = + ExpressionRunner::visit(table->init).getSingleValue(); } else { - assert(table->type.isNullable() && "Non-nullable table must have an init expressions"); + assert(table->type.isNullable() && + "Non-nullable table must have an init expressions"); initVal = Literal::makeNull(table->type.getHeapType()); } auto& runtimeTable = diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 94328c18861..2d9c4978894 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -5008,8 +5008,8 @@ void WasmBinaryReader::readTableDeclarations() { } } for (size_t i = 0; i < num; i++) { - auto [name, isExplicit] = - getOrMakeName(tableNames, numImports + i, makeName("", i), usedTableNames); + auto [name, isExplicit] = getOrMakeName( + tableNames, numImports + i, makeName("", i), usedTableNames); auto type_code = getS32LEB(); bool has_init = false; if (type_code == BinaryConsts::EncodedType::Empty) { From 9264c1e10811b5e796ff90701f3a9da2566fbcde Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:23:55 +0000 Subject: [PATCH 11/28] check if table init exists before traversing it --- src/wasm-traversal.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index b333b361d9f..c9dc34b26cc 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -179,7 +179,9 @@ struct Walker : public VisitorType { } void walkTable(Table* table) { - walk(table->init); + if (table->init) { + walk(table->init); + } static_cast(this)->visitTable(table); } From 92ee42173c840014b96cb0966f91a8e5972f70f9 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:33:53 +0000 Subject: [PATCH 12/28] check for GC if there is a table init, rather than vice verse --- src/wasm/wasm-validator.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 336b6612acc..bc08ec07d66 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4748,6 +4748,10 @@ void validateTables(Module& module, ValidationInfo& info) { "tables with non-nullable types require an initializer expression"); } if (table->init != nullptr) { + info.shouldBeTrue(module.features.hasGC(), + "table", + "tables cannot have an initializer expression in MVP " + "(requires --enable-gc)."); info.shouldBeSubType( table->init->type, table->type, @@ -4759,12 +4763,6 @@ void validateTables(Module& module, ValidationInfo& info) { "table initializer value must be constant"); validator.validate(table->init); } - if (!module.features.hasGC()) { - info.shouldBeFalse(table->hasInit(), - "table", - "tables cannot have an initializer expression in MVP " - "(requires --enable-gc)."); - } auto typeFeats = table->type.getFeatures(); if (!info.shouldBeTrue(table->type == funcref || typeFeats <= module.features, From 6b053c081d32f8f4271cb16c362bf553e53c84a0 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:59:02 +0000 Subject: [PATCH 13/28] add changelog entry for non-nullable tables / table init exprs --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c507bc5b206..4a2e18a37ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,11 @@ Current Trunk and `setValueI64`, previously took a hi/low pair but now take a single value which can be bigint or a number. Passing two values to these APIs will now trigger an assertion. (#7984) + - Add support for non-nullable table types and initialisation expressions for + tables. This comes with a breaking change to the C and JS APIs: + `BinaryenAddTable` takes an additional `BinaryenExpressionRef` parameter to + provide an initialisation expression. This can be null for tables without an + initialiser. In JS this parameter is optional and so is not breaking. (#8405) v126 ---- From 8bffe076b7abd1cdaadb2d94d56543e86e166bdc Mon Sep 17 00:00:00 2001 From: Pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:40:48 +0000 Subject: [PATCH 14/28] Update CHANGELOG.md Uses American spelling and clarify that the breaking change is only in the C API Co-authored-by: Steven Fontanella --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a2e18a37ec..36c60ff631a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,11 +24,11 @@ Current Trunk and `setValueI64`, previously took a hi/low pair but now take a single value which can be bigint or a number. Passing two values to these APIs will now trigger an assertion. (#7984) - - Add support for non-nullable table types and initialisation expressions for - tables. This comes with a breaking change to the C and JS APIs: - `BinaryenAddTable` takes an additional `BinaryenExpressionRef` parameter to - provide an initialisation expression. This can be null for tables without an - initialiser. In JS this parameter is optional and so is not breaking. (#8405) + - Add support for non-nullable table types and initialization expressions for + tables. This comes with a breaking change to C API: `BinaryenAddTable` takes + an additional `BinaryenExpressionRef` parameter to provide an initialization + expression. This may be set to NULL for tables without an initializer. In JS + this parameter is optional and so is not breaking. (#8405) v126 ---- From a33eae8c44cd23d42de1059f975cfe4dcc6da355 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:30:09 +0000 Subject: [PATCH 15/28] introduce named constants for table encoding with init expr --- src/wasm-binary.h | 3 +++ src/wasm/wasm-binary.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index eb5ee36c0f4..8a92b5b4703 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -361,6 +361,9 @@ constexpr uint32_t ExactImport = 1 << 5; constexpr uint32_t HasMemoryOrderMask = 1 << 5; constexpr uint32_t HasMemoryIndexMask = 1 << 6; +constexpr uint32_t HasTableInitializer = 0x40; +constexpr uint32_t TableReservedByte = 0x00; + enum EncodedType { // value types i32 = -0x1, // 0x7f diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 2d9c4978894..ef7fe54aabf 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -774,8 +774,8 @@ void WasmBinaryWriter::writeTableDeclarations() { o << U32LEB(num); ModuleUtils::iterDefinedTables(*wasm, [&](Table* table) { if (table->hasInit()) { - o << uint8_t(0x40); - o << uint8_t(0x00); + o << uint8_t(BinaryConsts::HasTableInitializer); + o << uint8_t(BinaryConsts::TableReservedByte); } writeType(table->type); writeResizableLimits(table->initial, @@ -5012,9 +5012,9 @@ void WasmBinaryReader::readTableDeclarations() { tableNames, numImports + i, makeName("", i), usedTableNames); auto type_code = getS32LEB(); bool has_init = false; - if (type_code == BinaryConsts::EncodedType::Empty) { + if (type_code == BinaryConsts::HasTableInitializer) { auto nextInt = getInt8(); - if (nextInt != 0x00) { + if (nextInt != BinaryConsts::TableReservedByte) { throwError("Malformed table"); } has_init = true; From 9f2f70dc5d81b203c55828c1414835ee8655b0cb Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:31:22 +0000 Subject: [PATCH 16/28] Revert "introduce named constants for table encoding with init expr" This reverts commit a33eae8c44cd23d42de1059f975cfe4dcc6da355. Reason for revert: storing 0x40 as a uint32_t breaks comparison with the int32_t --- src/wasm-binary.h | 3 --- src/wasm/wasm-binary.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 8a92b5b4703..eb5ee36c0f4 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -361,9 +361,6 @@ constexpr uint32_t ExactImport = 1 << 5; constexpr uint32_t HasMemoryOrderMask = 1 << 5; constexpr uint32_t HasMemoryIndexMask = 1 << 6; -constexpr uint32_t HasTableInitializer = 0x40; -constexpr uint32_t TableReservedByte = 0x00; - enum EncodedType { // value types i32 = -0x1, // 0x7f diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index ef7fe54aabf..2d9c4978894 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -774,8 +774,8 @@ void WasmBinaryWriter::writeTableDeclarations() { o << U32LEB(num); ModuleUtils::iterDefinedTables(*wasm, [&](Table* table) { if (table->hasInit()) { - o << uint8_t(BinaryConsts::HasTableInitializer); - o << uint8_t(BinaryConsts::TableReservedByte); + o << uint8_t(0x40); + o << uint8_t(0x00); } writeType(table->type); writeResizableLimits(table->initial, @@ -5012,9 +5012,9 @@ void WasmBinaryReader::readTableDeclarations() { tableNames, numImports + i, makeName("", i), usedTableNames); auto type_code = getS32LEB(); bool has_init = false; - if (type_code == BinaryConsts::HasTableInitializer) { + if (type_code == BinaryConsts::EncodedType::Empty) { auto nextInt = getInt8(); - if (nextInt != BinaryConsts::TableReservedByte) { + if (nextInt != 0x00) { throwError("Malformed table"); } has_init = true; From fdab5fff6995d539e1c3bbc55ff53b60e4b686a2 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:41:44 +0000 Subject: [PATCH 17/28] use named constants for table initializer encoding --- src/wasm-binary.h | 3 +++ src/wasm/wasm-binary.cpp | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/wasm-binary.h b/src/wasm-binary.h index eb5ee36c0f4..af5366cf47f 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -361,6 +361,9 @@ constexpr uint32_t ExactImport = 1 << 5; constexpr uint32_t HasMemoryOrderMask = 1 << 5; constexpr uint32_t HasMemoryIndexMask = 1 << 6; +constexpr uint8_t HasTableInitializer = 0x40; +constexpr uint8_t TableReservedByte = 0x00; + enum EncodedType { // value types i32 = -0x1, // 0x7f diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 2d9c4978894..0ec8faccd3e 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -774,8 +774,8 @@ void WasmBinaryWriter::writeTableDeclarations() { o << U32LEB(num); ModuleUtils::iterDefinedTables(*wasm, [&](Table* table) { if (table->hasInit()) { - o << uint8_t(0x40); - o << uint8_t(0x00); + o << uint8_t(BinaryConsts::HasTableInitializer); + o << uint8_t(BinaryConsts::TableReservedByte); } writeType(table->type); writeResizableLimits(table->initial, @@ -5010,16 +5010,19 @@ void WasmBinaryReader::readTableDeclarations() { for (size_t i = 0; i < num; i++) { auto [name, isExplicit] = getOrMakeName( tableNames, numImports + i, makeName("", i), usedTableNames); - auto type_code = getS32LEB(); - bool has_init = false; - if (type_code == BinaryConsts::EncodedType::Empty) { - auto nextInt = getInt8(); - if (nextInt != 0x00) { + auto peekInt = getInt8(); + bool hasInit = false; + if (peekInt == BinaryConsts::HasTableInitializer) { + auto reservedByte = getInt8(); + if (reservedByte != BinaryConsts::TableReservedByte) { + // byte reserved for future extension, must be zero for now throwError("Malformed table"); } - has_init = true; - type_code = getS32LEB(); + hasInit = true; + } else { + pos--; } + auto type_code = getS32LEB(); auto elemType = getType(type_code); if (!elemType.isRef()) { throwError("Table type must be a reference type"); @@ -5035,7 +5038,7 @@ void WasmBinaryReader::readTableDeclarations() { if (is_shared) { throwError("Tables may not be shared"); } - if (has_init) { + if (hasInit) { auto* init = readExpression(); table->init = init; } From c12800bae30df620c4c3249e061f3cadfdd8cdc9 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:42:11 +0000 Subject: [PATCH 18/28] add C API kitchen sink test for table init expr --- test/example/c-api-kitchen-sink.c | 11 +++++++++++ test/example/c-api-kitchen-sink.txt | 1 + 2 files changed, 12 insertions(+) diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index 73721372089..11e00528174 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -522,6 +522,7 @@ void test_core() { BinaryenType i16Array; BinaryenType funcArray; BinaryenType i32Struct; + BinaryenType i32StructNonNull; { TypeBuilderRef tb = TypeBuilderCreate(4); TypeBuilderSetArrayType( @@ -543,6 +544,7 @@ void test_core() { i16Array = BinaryenTypeFromHeapType(builtHeapTypes[1], true); funcArray = BinaryenTypeFromHeapType(builtHeapTypes[2], true); i32Struct = BinaryenTypeFromHeapType(builtHeapTypes[3], true); + i32StructNonNull = BinaryenTypeFromHeapType(builtHeapTypes[3], false); } // Memory. Add it before creating any memory-using instructions. @@ -1381,6 +1383,15 @@ void test_core() { BinaryenAddPassiveElementSegment(module, "p2", funcNames, 1); BinaryenRemoveElementSegment(module, "p2"); + // Non-nullable table + BinaryenAddTable( + module, + "1", + 1, + 1, + i32StructNonNull, + BinaryenStructNew(module, NULL, 0, BinaryenTypeGetHeapType(i32StructNonNull))); + BinaryenExpressionRef funcrefExpr1 = BinaryenRefFunc(module, "kitchen()sinker", kitchenSinkerRefType); diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 4d68abcddd5..33aea888f09 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -95,6 +95,7 @@ BinaryenFeatureAll: 8388607 (data $seg (i32.const 0) "data segment 3") (table $tab 0 100 funcref) (table $0 1 1 funcref) + (table $1 1 1 (ref $1) (struct.new_default $1)) (elem $0 (table $0) (i32.const 0) func $"kitchen()sinker") (elem $passive func $"kitchen()sinker") (tag $a-tag (type $5) (param i32)) From 4ab3fb9bbef7ab0c5d64585648f9caa61911d4f5 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:51:56 +0000 Subject: [PATCH 19/28] Add JS kitchen sink test Might not work... pushing so CI can check --- test/binaryen.js/kitchen-sink.js | 2 ++ test/binaryen.js/kitchen-sink.js.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index cac76a36ab0..8f50e8a84e6 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -771,6 +771,8 @@ function test_core() { assert(module.getNumTables() === 1); assert(module.getNumElementSegments() === 1); + module.addTable("t2", 1, 1, binaryen.i31ref, binaryen.ref.i31(1)); + // Start function. One per module var starter = module.addFunction("starter", binaryen.none, binaryen.none, [], module.nop()); module.setStart(starter); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 3df38bcb411..bb10f2d8a89 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -139,6 +139,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} (data $x0 (i32.const 10) "hello, world") (data $y1 "I am passive") (table $t0 1 funcref) + (table $t2 1 i31ref (ref.i32 1)) (elem $e0 (i32.const 0) $"kitchen()sinker") (elem declare func $foobar) (tag $a-tag (type $1) (param i32)) From 1009d7b98f0adb870f11374da565c3476bb5402a Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:56:38 +0000 Subject: [PATCH 20/28] format code --- test/example/c-api-kitchen-sink.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index 11e00528174..37da054f81a 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -1390,7 +1390,8 @@ void test_core() { 1, 1, i32StructNonNull, - BinaryenStructNew(module, NULL, 0, BinaryenTypeGetHeapType(i32StructNonNull))); + BinaryenStructNew( + module, NULL, 0, BinaryenTypeGetHeapType(i32StructNonNull))); BinaryenExpressionRef funcrefExpr1 = BinaryenRefFunc(module, "kitchen()sinker", kitchenSinkerRefType); From 5dda1dd8f4446528b540d491519e494f9184dcc8 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Wed, 4 Mar 2026 15:42:55 +0000 Subject: [PATCH 21/28] Update JS kitchen sink test to use correct API Also update expected elem section output due to the module now having two tables. --- test/binaryen.js/kitchen-sink.js | 2 +- test/binaryen.js/kitchen-sink.js.txt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index 8f50e8a84e6..86e9648e2f2 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -771,7 +771,7 @@ function test_core() { assert(module.getNumTables() === 1); assert(module.getNumElementSegments() === 1); - module.addTable("t2", 1, 1, binaryen.i31ref, binaryen.ref.i31(1)); + module.addTable("t2", 1, 1, binaryen.i31ref, module.ref.i31(module.i32.const(1))); // Start function. One per module var starter = module.addFunction("starter", binaryen.none, binaryen.none, [], module.nop()); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index bb10f2d8a89..ec491d1fe50 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -139,8 +139,10 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} (data $x0 (i32.const 10) "hello, world") (data $y1 "I am passive") (table $t0 1 funcref) - (table $t2 1 i31ref (ref.i32 1)) - (elem $e0 (i32.const 0) $"kitchen()sinker") + (table $t2 1 1 i31ref (ref.i31 + (i32.const 1) + )) + (elem $e0 (table $t0) (i32.const 0) func $"kitchen()sinker") (elem declare func $foobar) (tag $a-tag (type $1) (param i32)) (export "mem" (memory $0)) From 4bafe31a7d6d5ba745b86d55ebc81cef7250a1d9 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:02:41 +0000 Subject: [PATCH 22/28] update reason for ignoring global.wast spec test --- scripts/test/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 404197a5f86..4f573813d63 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -414,7 +414,7 @@ def get_tests(test_dir, extensions=[], recursive=False): 'float_exprs.wast', # Adding 0 and NaN should give canonical NaN 'float_misc.wast', # Rounding wrong on f64.sqrt 'func.wast', # Duplicate parameter names not properly rejected - 'global.wast', # Fail to parse table + 'global.wast', # Table init values shouldn't be able to reference module-defined global 'if.wast', # Requires more precise unreachable validation 'imports.wast', # Requires fixing handling of mutation to imported globals 'proposals/threads/imports.wast', # Missing memory type validation on instantiation From 4c149b43486d7d5557cac3effd7e179b73e77ef1 Mon Sep 17 00:00:00 2001 From: Pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:04:06 +0000 Subject: [PATCH 23/28] Use `getType()` rather than getting LEB and then decoding Co-authored-by: Thomas Lively --- src/wasm/wasm-binary.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 0ec8faccd3e..ed88f13cf04 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -5022,8 +5022,7 @@ void WasmBinaryReader::readTableDeclarations() { } else { pos--; } - auto type_code = getS32LEB(); - auto elemType = getType(type_code); + auto elemType = getType(); if (!elemType.isRef()) { throwError("Table type must be a reference type"); } From eeea874483e670cc474a427fc5af25b7ddd546ca Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:29:58 +0000 Subject: [PATCH 24/28] remove `table->hasInit()` in preference of directly checking `init` --- src/passes/Print.cpp | 2 +- src/passes/RemoveUnusedModuleElements.cpp | 2 +- src/wasm-interpreter.h | 2 +- src/wasm.h | 1 - src/wasm/wasm-binary.cpp | 4 ++-- src/wasm/wasm-validator.cpp | 4 ++-- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index d1cc1b82e20..0708b63bd9c 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3410,7 +3410,7 @@ void PrintSExpression::printTableHeader(Table* curr) { } o << ' '; printType(curr->type); - if (curr->hasInit()) { + if (curr->init) { o << ' '; visit(curr->init); } diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index bf96b763316..d98ab24b0ed 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -546,7 +546,7 @@ struct Analyzer { } }); auto* table = module->getTable(value); - if (table->hasInit()) { + if (table->init) { use(table->init); } break; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6693a0042d9..07ea85131b8 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3542,7 +3542,7 @@ class ModuleRunnerBase : public ExpressionRunner { assert(inserted && "Unexpected repeated table name"); } else { Literal initVal; - if (table->hasInit()) { + if (table->init) { initVal = ExpressionRunner::visit(table->init).getSingleValue(); } else { diff --git a/src/wasm.h b/src/wasm.h index 7d7424a455b..fe9d25204b6 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2518,7 +2518,6 @@ class Table : public Importable { bool hasMax() { return max != kUnlimitedSize; } bool is64() { return addressType == Type::i64; } - bool hasInit() { return init != nullptr; } void clear() { name = ""; initial = 0; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index ed88f13cf04..862e4a091ba 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -773,7 +773,7 @@ void WasmBinaryWriter::writeTableDeclarations() { auto num = importInfo->getNumDefinedTables(); o << U32LEB(num); ModuleUtils::iterDefinedTables(*wasm, [&](Table* table) { - if (table->hasInit()) { + if (table->init) { o << uint8_t(BinaryConsts::HasTableInitializer); o << uint8_t(BinaryConsts::TableReservedByte); } @@ -783,7 +783,7 @@ void WasmBinaryWriter::writeTableDeclarations() { table->hasMax(), /*shared=*/false, table->is64()); - if (table->hasInit()) { + if (table->init) { writeExpression(table->init); o << uint8_t(BinaryConsts::End); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index bc08ec07d66..7f110b96dd4 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4743,11 +4743,11 @@ void validateTables(Module& module, ValidationInfo& info) { "size minimum must not be greater than maximum"); if (!table->type.isNullable()) { info.shouldBeTrue( - table->init != nullptr, + table->init, "table", "tables with non-nullable types require an initializer expression"); } - if (table->init != nullptr) { + if (table->init) { info.shouldBeTrue(module.features.hasGC(), "table", "tables cannot have an initializer expression in MVP " From 6c7dd7d15451a1bb48a0694b4cf2435c8cc7eeb5 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:34:44 +0000 Subject: [PATCH 25/28] walk table init expr in `walkModuleCode` --- src/wasm-traversal.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index c9dc34b26cc..a2b2372c71f 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -251,6 +251,11 @@ struct Walker : public VisitorType { setModule(module); // Dispatch statically through the SubType. SubType* self = static_cast(this); + for (auto& curr : module->globals) { + if (curr->init) { + self->walk(curr->init); + } + } for (auto& curr : module->globals) { if (!curr->imported()) { self->walk(curr->init); From d98caed5d1395abe801af77dc1da936a4345012a Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:09:34 +0000 Subject: [PATCH 26/28] fix typo... globals -> tables --- src/wasm-traversal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index a2b2372c71f..a3bdc5a3905 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -251,7 +251,7 @@ struct Walker : public VisitorType { setModule(module); // Dispatch statically through the SubType. SubType* self = static_cast(this); - for (auto& curr : module->globals) { + for (auto& curr : module->tables) { if (curr->init) { self->walk(curr->init); } From e8ed315e83a2002c791b38da907054c4b75e6c43 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:07:30 +0000 Subject: [PATCH 27/28] check that table init exprs only reference imported globals --- scripts/test/shared.py | 1 - src/wasm/wasm-validator.cpp | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 4f573813d63..1823ec24ed0 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -414,7 +414,6 @@ def get_tests(test_dir, extensions=[], recursive=False): 'float_exprs.wast', # Adding 0 and NaN should give canonical NaN 'float_misc.wast', # Rounding wrong on f64.sqrt 'func.wast', # Duplicate parameter names not properly rejected - 'global.wast', # Table init values shouldn't be able to reference module-defined global 'if.wast', # Requires more precise unreachable validation 'imports.wast', # Requires fixing handling of mutation to imported globals 'proposals/threads/imports.wast', # Missing memory type validation on instantiation diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 7f110b96dd4..3bfca4182b0 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4762,6 +4762,16 @@ void validateTables(Module& module, ValidationInfo& info) { "table", "table initializer value must be constant"); validator.validate(table->init); + // Check that no module-defined globals are references. + for (auto* get : FindAll(table->init).list) { + auto* global = module.getGlobalOrNull(get->name); + if (global) { + info.shouldBeTrue( + global->imported(), + table->init, + "table initializer may not refer to module-defined globals"); + } + } } auto typeFeats = table->type.getFeatures(); if (!info.shouldBeTrue(table->type == funcref || From 26b850ef1e1f91ad48e06f7a9c09989c1bbc98d3 Mon Sep 17 00:00:00 2001 From: pufferfish101007 <50246616+pufferfish101007@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:17:44 +0000 Subject: [PATCH 28/28] format code --- src/wasm/wasm-validator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 3bfca4182b0..c9e031162b6 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4766,10 +4766,10 @@ void validateTables(Module& module, ValidationInfo& info) { for (auto* get : FindAll(table->init).list) { auto* global = module.getGlobalOrNull(get->name); if (global) { - info.shouldBeTrue( - global->imported(), - table->init, - "table initializer may not refer to module-defined globals"); + info.shouldBeTrue( + global->imported(), + table->init, + "table initializer may not refer to module-defined globals"); } } }