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");
}
}
}