Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 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
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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
----
Expand Down
7 changes: 1 addition & 6 deletions scripts/test/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,29 +414,24 @@ 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
'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
'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
Expand Down
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
26 changes: 18 additions & 8 deletions src/parser/contexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1117,8 +1117,12 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx {
Name name,
ImportNames* importNames,
TableType limits);
Result<>
addTable(Name, const std::vector<Name>&, ImportNames*, TableType, Index);
Result<> 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 @@ -1500,8 +1504,12 @@ struct ParseModuleTypesCtx : TypeParserCtx<ParseModuleTypesCtx>,
return Ok{};
}

Result<> addTable(
Name, const std::vector<Name>&, ImportNames*, Type ttype, Index pos) {
Result<> addTable(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 @@ -1865,10 +1873,12 @@ struct ParseDefsCtx : TypeParserCtx<ParseDefsCtx>, AnnotationParserCtx {
return Ok{};
}

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

Result<>
addMemory(Name, const std::vector<Name>&, ImportNames*, TableTypeT, Index) {
Expand Down
14 changes: 11 additions & 3 deletions src/parser/parsers.h
Original file line number Diff line number Diff line change
Expand Up @@ -3379,7 +3379,8 @@ 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 +3473,8 @@ 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 +3507,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 +3542,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->init) {
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->init) {
use(table->init);
}
break;
}
case ModuleElementKind::DataSegment: {
auto* segment = module->getDataSegment(value);
if (segment->offset) {
Expand Down
3 changes: 3 additions & 0 deletions src/wasm-binary.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
15 changes: 10 additions & 5 deletions src/wasm-interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -3541,12 +3541,17 @@ 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->init) {
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
8 changes: 8 additions & 0 deletions src/wasm-traversal.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ struct Walker : public VisitorType {
}

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

Expand Down Expand Up @@ -248,6 +251,11 @@ struct Walker : public VisitorType {
setModule(module);
// Dispatch statically through the SubType.
SubType* self = static_cast<SubType*>(this);
for (auto& curr : module->tables) {
if (curr->init) {
self->walk(curr->init);
}
}
for (auto& curr : module->globals) {
if (!curr->imported()) {
self->walk(curr->init);
Expand Down
1 change: 1 addition & 0 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2514,6 +2514,7 @@ 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; }
Expand Down
24 changes: 24 additions & 0 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->init) {
o << uint8_t(BinaryConsts::HasTableInitializer);
o << uint8_t(BinaryConsts::TableReservedByte);
}
writeType(table->type);
writeResizableLimits(table->initial,
table->max,
table->hasMax(),
/*shared=*/false,
table->is64());
if (table->init) {
writeExpression(table->init);
o << uint8_t(BinaryConsts::End);
}
});
finishSection(start);
}
Expand Down Expand Up @@ -5002,6 +5010,18 @@ void WasmBinaryReader::readTableDeclarations() {
for (size_t i = 0; i < num; i++) {
auto [name, isExplicit] = getOrMakeName(
tableNames, numImports + i, makeName("", i), usedTableNames);
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");
}
hasInit = true;
} else {
pos--;
}
auto elemType = getType();
if (!elemType.isRef()) {
throwError("Table type must be a reference type");
Expand All @@ -5017,6 +5037,10 @@ void WasmBinaryReader::readTableDeclarations() {
if (is_shared) {
throwError("Tables may not be shared");
}
if (hasInit) {
auto* init = readExpression();
table->init = init;
}
wasm.addTable(std::move(table));
}
}
Expand Down
Loading