Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4769702
allow non-nullable ref types + init exprs in tables
pufferfish101007 Apr 19, 2025
1855e6c
add support for nullable tables & init expressions in parser + interp…
pufferfish101007 Feb 28, 2026
6a09536
properly initialise table init when parsing WAT
pufferfish101007 Feb 28, 2026
0cdd750
only parse table init if it exists
pufferfish101007 Mar 1, 2026
7ef1502
consider that nullable tables might have an init expr
pufferfish101007 Mar 1, 2026
bbd1d4e
add missing argument to addTable in C api tests
pufferfish101007 Mar 1, 2026
41fc398
allow non-nullable types in element segments
pufferfish101007 Mar 1, 2026
a1f5781
unignore some spec tests that now pass
pufferfish101007 Mar 1, 2026
dc47c17
revert testsuite submodule update
pufferfish101007 Mar 2, 2026
8024e79
format code
pufferfish101007 Mar 2, 2026
9264c1e
check if table init exists before traversing it
pufferfish101007 Mar 3, 2026
92ee421
check for GC if there is a table init, rather than vice verse
pufferfish101007 Mar 3, 2026
6b053c0
add changelog entry for non-nullable tables / table init exprs
pufferfish101007 Mar 3, 2026
8bffe07
Update CHANGELOG.md
pufferfish101007 Mar 3, 2026
a33eae8
introduce named constants for table encoding with init expr
pufferfish101007 Mar 3, 2026
9f2f70d
Revert "introduce named constants for table encoding with init expr"
pufferfish101007 Mar 4, 2026
fdab5ff
use named constants for table initializer encoding
pufferfish101007 Mar 4, 2026
c12800b
add C API kitchen sink test for table init expr
pufferfish101007 Mar 4, 2026
4ab3fb9
Add JS kitchen sink test
pufferfish101007 Mar 4, 2026
1009d7b
format code
pufferfish101007 Mar 4, 2026
5dda1dd
Update JS kitchen sink test to use correct API
pufferfish101007 Mar 4, 2026
4bafe31
update reason for ignoring global.wast spec test
pufferfish101007 Mar 5, 2026
4c149b4
Use `getType()` rather than getting LEB and then decoding
pufferfish101007 Mar 5, 2026
eeea874
remove `table->hasInit()` in preference of directly checking `init`
pufferfish101007 Mar 5, 2026
6c7dd7d
walk table init expr in `walkModuleCode`
pufferfish101007 Mar 5, 2026
d98caed
fix typo... globals -> tables
pufferfish101007 Mar 5, 2026
e8ed315
check that table init exprs only reference imported globals
pufferfish101007 Mar 6, 2026
26b850e
format code
pufferfish101007 Mar 6, 2026
a333fd5
Merge branch 'main' of https://github.com/WebAssembly/binaryen into n…
pufferfish101007 Mar 8, 2026
07257fe
add roundtrip and validation tests
pufferfish101007 Mar 8, 2026
2b35b2d
visit table init expr in SubtypingDiscoverer
pufferfish101007 Mar 8, 2026
46c733f
add test that table init exprs are walked in unused module elements pass
pufferfish101007 Mar 8, 2026
d0aa3ab
visit tables in unsubtyping pass; add test for table init unsubtyping
pufferfish101007 Mar 13, 2026
808c978
check that imported tables do not have init expr
pufferfish101007 Mar 16, 2026
6507fa6
add rudimentary support for table init exprs in fuzzing
pufferfish101007 Mar 16, 2026
ab9e004
format
pufferfish101007 Mar 16, 2026
b0f8f3e
RemoveUnusedModuleElements: reference table init expr
pufferfish101007 Mar 17, 2026
4efbaae
Merge branch 'main' of https://github.com/WebAssembly/binaryen into n…
pufferfish101007 Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/binaryen-c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
3 changes: 2 additions & 1 deletion src/binaryen-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/js/binaryen.js-post.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down
1 change: 1 addition & 0 deletions src/parser/context-decls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ Result<> ParseDeclsCtx::addTable(Name name,
const std::vector<Name>& exports,
ImportNames* import,
TableType type,
std::optional<ExprT>,
Index pos) {
CHECK_ERR(checkImport(pos, import));
auto t = addTableDecl(pos, name, import, type);
Expand Down
12 changes: 12 additions & 0 deletions src/parser/context-defs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ Result<> ParseDefsCtx::addGlobal(Name,
return Ok{};
}

Result<> ParseDefsCtx::addTable(Name,
const std::vector<Name>&,
ImportNames*,
TableTypeT,
std::optional<ExprT> init,
Index) {
if (init) {
wasm.tables[index]->init = *init;
}
return Ok{};
}

Result<> ParseDefsCtx::addImplicitElems(Type,
std::vector<Expression*>&& elems) {
auto& e = wasm.elementSegments[implicitElemIndices.at(index)];
Expand Down
8 changes: 3 additions & 5 deletions src/parser/contexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx {
ImportNames* importNames,
TableType limits);
Result<>
addTable(Name, const std::vector<Name>&, ImportNames*, TableType, Index);
addTable(Name, const std::vector<Name>&, ImportNames*, TableType, std::optional<ExprT>, Index);

// TODO: Record index of implicit elem for use when parsing types and instrs.
Result<> addImplicitElems(TypeT, ElemListT&& elems);
Expand Down Expand Up @@ -1501,7 +1501,7 @@ struct ParseModuleTypesCtx : TypeParserCtx<ParseModuleTypesCtx>,
}

Result<> addTable(
Name, const std::vector<Name>&, ImportNames*, Type ttype, Index pos) {
Name, const std::vector<Name>&, ImportNames*, Type ttype, std::optional<ExprT> init, Index pos) {
auto& t = wasm.tables[index];
if (!ttype.isRef()) {
return in.err(pos, "expected reference type");
Expand Down Expand Up @@ -1866,9 +1866,7 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, AnnotationParserCtx {
}

Result<>
addTable(Name, const std::vector<Name>&, ImportNames*, TableTypeT, Index) {
return Ok{};
}
addTable(Name, const std::vector<Name>&, ImportNames*, TableTypeT, std::optional<ExprT>, Index);

Result<>
addMemory(Name, const std::vector<Name>&, ImportNames*, TableTypeT, Index) {
Expand Down
12 changes: 9 additions & 3 deletions src/parser/parsers.h
Original file line number Diff line number Diff line change
Expand Up @@ -3379,7 +3379,7 @@ template<typename Ctx> 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);
Expand Down Expand Up @@ -3472,7 +3472,7 @@ template<typename Ctx> 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<typename Ctx> MaybeResult<> table(Ctx& ctx) {
Expand Down Expand Up @@ -3505,6 +3505,7 @@ template<typename Ctx> MaybeResult<> table(Ctx& ctx) {

std::optional<typename Ctx::TableTypeT> ttype;
std::optional<typename Ctx::ElemListT> elems;
std::optional<typename Ctx::ExprT> init;
if (type) {
// We should have inline elements.
if (!ctx.in.takeSExprStart("elem"sv)) {
Expand Down Expand Up @@ -3539,13 +3540,18 @@ template<typename Ctx> MaybeResult<> table(Ctx& ctx) {
auto tabtype = tabletypeContinued(ctx, addressType);
CHECK_ERR(tabtype);
ttype = *tabtype;
if (ctx.in.peekLParen()) {
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)));
Expand Down
7 changes: 6 additions & 1 deletion src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 6 additions & 1 deletion src/passes/RemoveUnusedModuleElements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion src/wasm-builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
table->name = name;
table->type = type;
table->addressType = addressType;
table->initial = initial;
table->max = max;
table->init = init;
return table;
}

Expand Down
13 changes: 8 additions & 5 deletions src/wasm-interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -3541,12 +3541,15 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
// 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->hasInit()) {
initVal = ExpressionRunner<SubType>::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(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");
Expand Down
1 change: 1 addition & 0 deletions src/wasm-traversal.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ struct Walker : public VisitorType {
}

void walkTable(Table* table) {
walk(table->init);
static_cast<SubType*>(this)->visitTable(table);
}

Expand Down
2 changes: 2 additions & 0 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 25 additions & 3 deletions src/wasm/wasm-binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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");
}
Expand All @@ -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));
}
}
Expand Down
32 changes: 24 additions & 8 deletions src/wasm/wasm-validator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4741,10 +4741,30 @@ 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");
}
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(),
"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,
Expand All @@ -4764,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,
Expand Down
10 changes: 5 additions & 5 deletions test/example/c-api-kitchen-sink.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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));
Expand Down
4 changes: 2 additions & 2 deletions test/example/c-api-multiple-tables.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion test/spec/testsuite
Loading