diff options
94 files changed, 1660 insertions, 616 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 50d018937..cafdae444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,32 @@ Current Trunk ------------- - `wasm-dis` now supports options to enable or disable Wasm features. +- Reference types support has been improved by allowing multiple tables in a + module. +- `call_indirect` and `return_call_indirect` now take an additional table name + parameter. This is necessary for reference types support. +- New getter/setter methods have been introduced for `call_indirect` table name: + - `BinaryenCallIndirectGetTable` + - `BinaryenCallIndirectSetTable` + - JS API `CallIndirect.table` +- New APIs have been added to add and manipulate multiple tables in a module: + - `BinaryenAddTable` + - `BinaryenRemoveTable` + - `BinaryenGetNumTables` + - `BinaryenGetTable` + - `BinaryenGetTableByIndex` + - `BinaryenTableGetName` + - `BinaryenTableGetInitial` + - `BinaryenTableHasMax` + - `BinaryenTableGetMax` + - `BinaryenTableImportGetModule` + - `BinaryenTableImportGetBase` + - `module.addTable` + - `module.removeTable` + - `module.getTable` + - `module.getTableByIndex` + - `module.getNumTables` + - `binaryen.getTableInfo` v99 --- diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 545c4ab94..1015e41c0 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -805,6 +805,7 @@ BinaryenExpressionRef BinaryenReturnCall(BinaryenModuleRef module, } static BinaryenExpressionRef makeBinaryenCallIndirect(BinaryenModuleRef module, + const char* table, BinaryenExpressionRef target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, @@ -812,6 +813,7 @@ makeBinaryenCallIndirect(BinaryenModuleRef module, BinaryenType results, bool isReturn) { auto* ret = ((Module*)module)->allocator.alloc<CallIndirect>(); + ret->table = table; ret->target = (Expression*)target; for (BinaryenIndex i = 0; i < numOperands; i++) { ret->operands.push_back((Expression*)operands[i]); @@ -823,23 +825,25 @@ makeBinaryenCallIndirect(BinaryenModuleRef module, return static_cast<Expression*>(ret); } BinaryenExpressionRef BinaryenCallIndirect(BinaryenModuleRef module, + const char* table, BinaryenExpressionRef target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, BinaryenType params, BinaryenType results) { return makeBinaryenCallIndirect( - module, target, operands, numOperands, params, results, false); + module, table, target, operands, numOperands, params, results, false); } BinaryenExpressionRef BinaryenReturnCallIndirect(BinaryenModuleRef module, + const char* table, BinaryenExpressionRef target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, BinaryenType params, BinaryenType results) { return makeBinaryenCallIndirect( - module, target, operands, numOperands, params, results, true); + module, table, target, operands, numOperands, params, results, true); } BinaryenExpressionRef BinaryenLocalGet(BinaryenModuleRef module, BinaryenIndex index, @@ -1610,6 +1614,19 @@ void BinaryenCallIndirectSetTarget(BinaryenExpressionRef expr, assert(targetExpr); static_cast<CallIndirect*>(expression)->target = (Expression*)targetExpr; } +const char* BinaryenCallIndirectGetTable(BinaryenExpressionRef expr) { + auto* expression = (Expression*)expr; + assert(expression->is<CallIndirect>()); + return static_cast<CallIndirect*>(expression)->table.c_str(); +} +void BinaryenCallIndirectSetTable(BinaryenExpressionRef expr, + const char* table) { + Name name(table); + auto* expression = (Expression*)expr; + + assert(expression->is<CallIndirect>()); + static_cast<CallIndirect*>(expression)->table = name; +} BinaryenIndex BinaryenCallIndirectGetNumOperands(BinaryenExpressionRef expr) { auto* expression = (Expression*)expr; assert(expression->is<CallIndirect>()); @@ -3154,9 +3171,11 @@ void BinaryenAddTableImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char* externalBaseName) { - auto& table = ((Module*)module)->table; - table.module = externalModuleName; - table.base = externalBaseName; + auto table = std::make_unique<Table>(); + table->name = internalName; + table->module = externalModuleName; + table->base = externalBaseName; + ((Module*)module)->addTable(std::move(table)); } void BinaryenAddMemoryImport(BinaryenModuleRef module, const char* internalName, @@ -3273,35 +3292,88 @@ BinaryenExportRef BinaryenGetExportByIndex(BinaryenModuleRef module, return exports[index].get(); } -// Function table. One per module - +// TODO(reference-types): maybe deprecate this function? void BinaryenSetFunctionTable(BinaryenModuleRef module, BinaryenIndex initial, BinaryenIndex maximum, const char** funcNames, BinaryenIndex numFuncNames, BinaryenExpressionRef offset) { + auto* wasm = (Module*)module; + if (wasm->tables.empty()) { + wasm->addTable(Builder::makeTable(Name::fromInt(0))); + } + + auto& table = wasm->tables.front(); + table->initial = initial; + table->max = maximum; + Table::Segment segment((Expression*)offset); for (BinaryenIndex i = 0; i < numFuncNames; i++) { segment.data.push_back(funcNames[i]); } - auto& table = ((Module*)module)->table; - table.initial = initial; - table.max = maximum; - table.exists = true; - table.segments.push_back(segment); + table->segments.push_back(segment); +} + +BinaryenTableRef BinaryenAddTable(BinaryenModuleRef module, + const char* name, + BinaryenIndex initial, + BinaryenIndex maximum, + const char** funcNames, + BinaryenIndex numFuncNames, + BinaryenExpressionRef offset) { + auto table = Builder::makeTable(name, initial, maximum); + table->hasExplicitName = true; + + Table::Segment segment((Expression*)offset); + for (BinaryenIndex i = 0; i < numFuncNames; i++) { + segment.data.push_back(funcNames[i]); + } + table->segments.push_back(segment); + ((Module*)module)->addTable(std::move(table)); + + return ((Module*)module)->getTable(name); +} +void BinaryenRemoveTable(BinaryenModuleRef module, const char* table) { + ((Module*)module)->removeTable(table); +} +BinaryenIndex BinaryenGetNumTables(BinaryenModuleRef module) { + return ((Module*)module)->tables.size(); +} +BinaryenTableRef BinaryenGetTable(BinaryenModuleRef module, const char* name) { + return ((Module*)module)->getTableOrNull(name); +} +BinaryenTableRef BinaryenGetTableByIndex(BinaryenModuleRef module, + BinaryenIndex index) { + const auto& tables = ((Module*)module)->tables; + if (tables.size() <= index) { + Fatal() << "invalid table index."; + } + return tables[index].get(); } int BinaryenIsFunctionTableImported(BinaryenModuleRef module) { - return ((Module*)module)->table.imported(); + if (((Module*)module)->tables.size() > 0) { + return ((Module*)module)->tables[0]->imported(); + } + + return false; } BinaryenIndex BinaryenGetNumFunctionTableSegments(BinaryenModuleRef module) { - return ((Module*)module)->table.segments.size(); + if (((Module*)module)->tables.size() > 0) { + return ((Module*)module)->tables[0]->segments.size(); + } + + return 0; } BinaryenExpressionRef BinaryenGetFunctionTableSegmentOffset(BinaryenModuleRef module, BinaryenIndex segmentId) { - const auto& segments = ((Module*)module)->table.segments; + if (((Module*)module)->tables.empty()) { + Fatal() << "module has no tables."; + } + + const auto& segments = ((Module*)module)->tables[0]->segments; if (segments.size() <= segmentId) { Fatal() << "invalid function table segment id."; } @@ -3309,7 +3381,11 @@ BinaryenGetFunctionTableSegmentOffset(BinaryenModuleRef module, } BinaryenIndex BinaryenGetFunctionTableSegmentLength(BinaryenModuleRef module, BinaryenIndex segmentId) { - const auto& segments = ((Module*)module)->table.segments; + if (((Module*)module)->tables.empty()) { + Fatal() << "module has no tables."; + } + + const auto& segments = ((Module*)module)->tables[0]->segments; if (segments.size() <= segmentId) { Fatal() << "invalid function table segment id."; } @@ -3318,7 +3394,11 @@ BinaryenIndex BinaryenGetFunctionTableSegmentLength(BinaryenModuleRef module, const char* BinaryenGetFunctionTableSegmentData(BinaryenModuleRef module, BinaryenIndex segmentId, BinaryenIndex dataId) { - const auto& segments = ((Module*)module)->table.segments; + if (((Module*)module)->tables.empty()) { + Fatal() << "module has no tables."; + } + + const auto& segments = ((Module*)module)->tables[0]->segments; if (segments.size() <= segmentId || segments[segmentId].data.size() <= dataId) { Fatal() << "invalid function table segment or data id."; @@ -3789,6 +3869,21 @@ void BinaryenFunctionSetDebugLocation(BinaryenFunctionRef func, } // +// =========== Table operations =========== +// + +const char* BinaryenTableGetName(BinaryenTableRef table) { + return ((Table*)table)->name.c_str(); +} +int BinaryenTableGetInitial(BinaryenTableRef table) { + return ((Table*)table)->initial; +} +int BinaryenTableHasMax(BinaryenTableRef table) { + return ((Table*)table)->hasMax(); +} +int BinaryenTableGetMax(BinaryenTableRef table) { return ((Table*)table)->max; } + +// // =========== Global operations =========== // @@ -3835,6 +3930,14 @@ const char* BinaryenFunctionImportGetModule(BinaryenFunctionRef import) { return ""; } } +const char* BinaryenTableImportGetModule(BinaryenTableRef import) { + auto* table = (Table*)import; + if (table->imported()) { + return table->module.c_str(); + } else { + return ""; + } +} const char* BinaryenGlobalImportGetModule(BinaryenGlobalRef import) { auto* global = (Global*)import; if (global->imported()) { @@ -3859,6 +3962,14 @@ const char* BinaryenFunctionImportGetBase(BinaryenFunctionRef import) { return ""; } } +const char* BinaryenTableImportGetBase(BinaryenTableRef import) { + auto* table = (Table*)import; + if (table->imported()) { + return table->base.c_str(); + } else { + return ""; + } +} const char* BinaryenGlobalImportGetBase(BinaryenGlobalRef import) { auto* global = (Global*)import; if (global->imported()) { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 83424ceb6..ba61db885 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -608,6 +608,7 @@ BINARYEN_API BinaryenExpressionRef BinaryenCall(BinaryenModuleRef module, BinaryenType returnType); BINARYEN_API BinaryenExpressionRef BinaryenCallIndirect(BinaryenModuleRef module, + const char* table, BinaryenExpressionRef target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, @@ -621,6 +622,7 @@ BinaryenReturnCall(BinaryenModuleRef module, BinaryenType returnType); BINARYEN_API BinaryenExpressionRef BinaryenReturnCallIndirect(BinaryenModuleRef module, + const char* table, BinaryenExpressionRef target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, @@ -1041,6 +1043,12 @@ BinaryenCallIndirectGetTarget(BinaryenExpressionRef expr); BINARYEN_API void BinaryenCallIndirectSetTarget(BinaryenExpressionRef expr, BinaryenExpressionRef targetExpr); +// Gets the table name of a `call_indirect` expression. +BINARYEN_API const char* +BinaryenCallIndirectGetTable(BinaryenExpressionRef expr); +// Sets the table name of a `call_indirect` expression. +BINARYEN_API void BinaryenCallIndirectSetTable(BinaryenExpressionRef expr, + const char* table); // Gets the number of operands of a `call_indirect` expression. BINARYEN_API BinaryenIndex BinaryenCallIndirectGetNumOperands(BinaryenExpressionRef expr); @@ -2039,6 +2047,25 @@ BINARYEN_API BinaryenIndex BinaryenGetFunctionTableSegmentLength( BINARYEN_API const char* BinaryenGetFunctionTableSegmentData( BinaryenModuleRef module, BinaryenIndex segmentId, BinaryenIndex dataId); +// Tables + +BINARYEN_REF(Table); + +BINARYEN_API BinaryenTableRef BinaryenAddTable(BinaryenModuleRef module, + const char* table, + BinaryenIndex initial, + BinaryenIndex maximum, + const char** funcNames, + BinaryenIndex numFuncNames, + BinaryenExpressionRef offset); +BINARYEN_API void BinaryenRemoveTable(BinaryenModuleRef module, + const char* table); +BINARYEN_API BinaryenIndex BinaryenGetNumTables(BinaryenModuleRef module); +BINARYEN_API BinaryenTableRef BinaryenGetTable(BinaryenModuleRef module, + const char* name); +BINARYEN_API BinaryenTableRef BinaryenGetTableByIndex(BinaryenModuleRef module, + BinaryenIndex index); + // Memory. One per module // Each segment has data in segments, a start offset in segmentOffsets, and a @@ -2330,6 +2357,15 @@ BINARYEN_API void BinaryenFunctionSetDebugLocation(BinaryenFunctionRef func, BinaryenIndex columnNumber); // +// ========== Table Operations ========== +// + +BINARYEN_API const char* BinaryenTableGetName(BinaryenTableRef table); +BINARYEN_API int BinaryenTableGetInitial(BinaryenTableRef table); +BINARYEN_API int BinaryenTableHasMax(BinaryenTableRef table); +BINARYEN_API int BinaryenTableGetMax(BinaryenTableRef table); + +// // ========== Global Operations ========== // @@ -2364,12 +2400,14 @@ BINARYEN_API BinaryenType BinaryenEventGetResults(BinaryenEventRef event); // Gets the external module name of the specified import. BINARYEN_API const char* BinaryenFunctionImportGetModule(BinaryenFunctionRef import); +BINARYEN_API const char* BinaryenTableImportGetModule(BinaryenTableRef import); BINARYEN_API const char* BinaryenGlobalImportGetModule(BinaryenGlobalRef import); BINARYEN_API const char* BinaryenEventImportGetModule(BinaryenEventRef import); // Gets the external base name of the specified import. BINARYEN_API const char* BinaryenFunctionImportGetBase(BinaryenFunctionRef import); +BINARYEN_API const char* BinaryenTableImportGetBase(BinaryenTableRef import); BINARYEN_API const char* BinaryenGlobalImportGetBase(BinaryenGlobalRef import); BINARYEN_API const char* BinaryenEventImportGetBase(BinaryenEventRef import); diff --git a/src/ir/import-utils.h b/src/ir/import-utils.h index 3f3d27f1b..e4a656379 100644 --- a/src/ir/import-utils.h +++ b/src/ir/import-utils.h @@ -29,6 +29,7 @@ struct ImportInfo { std::vector<Global*> importedGlobals; std::vector<Function*> importedFunctions; + std::vector<Table*> importedTables; std::vector<Event*> importedEvents; ImportInfo(Module& wasm) : wasm(wasm) { @@ -42,6 +43,11 @@ struct ImportInfo { importedFunctions.push_back(import.get()); } } + for (auto& import : wasm.tables) { + if (import->imported()) { + importedTables.push_back(import.get()); + } + } for (auto& import : wasm.events) { if (import->imported()) { importedEvents.push_back(import.get()); @@ -80,12 +86,14 @@ struct ImportInfo { Index getNumImportedFunctions() { return importedFunctions.size(); } + Index getNumImportedTables() { return importedTables.size(); } + Index getNumImportedEvents() { return importedEvents.size(); } Index getNumImports() { return getNumImportedGlobals() + getNumImportedFunctions() + getNumImportedEvents() + (wasm.memory.imported() ? 1 : 0) + - (wasm.table.imported() ? 1 : 0); + getNumImportedTables(); } Index getNumDefinedGlobals() { @@ -96,6 +104,10 @@ struct ImportInfo { return wasm.functions.size() - getNumImportedFunctions(); } + Index getNumDefinedTables() { + return wasm.tables.size() - getNumImportedTables(); + } + Index getNumDefinedEvents() { return wasm.events.size() - getNumImportedEvents(); } diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 1f33b44ac..111271063 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -99,23 +99,27 @@ namespace ModuleSplitting { namespace { -template<class F> void forEachElement(Table& table, F f) { - for (auto& segment : table.segments) { - Name base = ""; - Index offset = 0; - if (auto* c = segment.offset->dynCast<Const>()) { - offset = c->value.geti32(); - } else if (auto* g = segment.offset->dynCast<GlobalGet>()) { - base = g->name; - } - for (size_t i = 0; i < segment.data.size(); ++i) { - f(base, offset + i, segment.data[i]); +template<class F> void forEachElement(Module& module, F f) { + for (auto& table : module.tables) { + for (auto& segment : table->segments) { + Name base = ""; + Index offset = 0; + if (auto* c = segment.offset->dynCast<Const>()) { + offset = c->value.geti32(); + } else if (auto* g = segment.offset->dynCast<GlobalGet>()) { + base = g->name; + } + for (size_t i = 0; i < segment.data.size(); ++i) { + f(table->name, base, offset + i, segment.data[i]); + } } } } struct TableSlotManager { struct Slot { + Name tableName; + // If `global` is empty, then this slot is at a statically known index. Name global; Index index = 0; @@ -124,13 +128,15 @@ struct TableSlotManager { Expression* makeExpr(Module& module); }; Module& module; - Table& table; + Table* activeTable = nullptr; Table::Segment* activeSegment = nullptr; Slot activeBase; std::map<Name, Slot> funcIndices; TableSlotManager(Module& module); + Table* makeTable(); + // Returns the table index for `func`, allocating a new index if necessary. Slot getSlot(Name func); void addSlot(Name func, Slot slot); @@ -153,41 +159,54 @@ void TableSlotManager::addSlot(Name func, Slot slot) { assert(it.second && "Function already has multiple table slots"); } -TableSlotManager::TableSlotManager(Module& module) - : module(module), table(module.table) { +TableSlotManager::TableSlotManager(Module& module) : module(module) { + if (module.tables.empty()) { + return; + } + activeTable = module.tables.front().get(); // If there is exactly one table segment and that segment has a non-constant // offset, append new items to the end of that segment. In all other cases, // append new items at constant offsets after all existing items at constant // offsets. - if (table.segments.size() == 1 && !table.segments[0].offset->is<Const>()) { - assert(table.segments[0].offset->is<GlobalGet>() && + if (activeTable->segments.size() == 1 && + !activeTable->segments[0].offset->is<Const>()) { + assert(activeTable->segments[0].offset->is<GlobalGet>() && "Unexpected initializer instruction"); - activeSegment = &table.segments[0]; - activeBase = {table.segments[0].offset->cast<GlobalGet>()->name, 0}; + activeSegment = &activeTable->segments[0]; + activeBase = {activeTable->name, + activeTable->segments[0].offset->cast<GlobalGet>()->name, + 0}; } else { // Finds the segment with the highest occupied table slot so that new items // can be inserted contiguously at the end of it without accidentally // overwriting any other items. TODO: be more clever about filling gaps in // the table, if that is ever useful. Index maxIndex = 0; - for (auto& segment : table.segments) { + for (auto& segment : activeTable->segments) { assert(segment.offset->is<Const>() && "Unexpected non-const segment offset with multiple segments"); Index segmentBase = segment.offset->cast<Const>()->value.geti32(); if (segmentBase + segment.data.size() >= maxIndex) { maxIndex = segmentBase + segment.data.size(); activeSegment = &segment; - activeBase = {"", segmentBase}; + activeBase = {activeTable->name, "", segmentBase}; } } } + // Initialize funcIndices with the functions already in the table. - forEachElement(table, [&](Name base, Index offset, Name func) { - addSlot(func, {base, offset}); + forEachElement(module, [&](Name table, Name base, Index offset, Name func) { + addSlot(func, {table, base, offset}); }); } +Table* TableSlotManager::makeTable() { + module.addTable(Builder::makeTable(Name::fromInt(0))); + + return module.tables.front().get(); +} + TableSlotManager::Slot TableSlotManager::getSlot(Name func) { auto slotIt = funcIndices.find(func); if (slotIt != funcIndices.end()) { @@ -196,21 +215,26 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func) { // If there are no segments yet, allocate one. if (activeSegment == nullptr) { - table.exists = true; - assert(table.segments.size() == 0); - table.segments.emplace_back(Builder(module).makeConst(int32_t(0))); - activeSegment = &table.segments.back(); + if (activeTable == nullptr) { + activeTable = makeTable(); + activeBase = {activeTable->name, "", 0}; + } + + assert(activeTable->segments.size() == 0); + activeTable->segments.emplace_back(Builder(module).makeConst(int32_t(0))); + activeSegment = &activeTable->segments.back(); } - Slot newSlot = {activeBase.global, + Slot newSlot = {activeBase.tableName, + activeBase.global, activeBase.index + Index(activeSegment->data.size())}; activeSegment->data.push_back(func); addSlot(func, newSlot); - if (table.initial <= newSlot.index) { - table.initial = newSlot.index + 1; + if (activeTable->initial <= newSlot.index) { + activeTable->initial = newSlot.index + 1; } - if (table.max <= newSlot.index) { - table.max = newSlot.index + 1; + if (activeTable->max <= newSlot.index) { + activeTable->max = newSlot.index + 1; } return newSlot; } @@ -358,8 +382,8 @@ void ModuleSplitter::thunkExportedSecondaryFunctions() { for (size_t i = 0, size = func->sig.params.size(); i < size; ++i) { args.push_back(builder.makeLocalGet(i, func->sig.params[i])); } - func->body = - builder.makeCallIndirect(tableSlot.makeExpr(primary), args, func->sig); + func->body = builder.makeCallIndirect( + tableSlot.tableName, tableSlot.makeExpr(primary), args, func->sig); primary.addFunction(std::move(func)); } } @@ -376,8 +400,10 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { if (!parent.secondaryFuncs.count(curr->target)) { return; } + auto tableSlot = parent.tableManager.getSlot(curr->target); replaceCurrent(builder.makeCallIndirect( - parent.tableManager.getSlot(curr->target).makeExpr(parent.primary), + tableSlot.tableName, + tableSlot.makeExpr(parent.primary), curr->operands, parent.secondary.getFunction(curr->target)->sig, curr->isReturn)); @@ -425,11 +451,15 @@ void ModuleSplitter::exportImportCalledPrimaryFunctions() { } void ModuleSplitter::setupTablePatching() { + if (!tableManager.activeTable) { + return; + } + std::map<Index, Name> replacedElems; // Replace table references to secondary functions with an imported // placeholder that encodes the table index in its name: // `importNamespace`.`index`. - forEachElement(primary.table, [&](Name, Index index, Name& elem) { + forEachElement(primary, [&](Name, Name, Index index, Name& elem) { if (secondaryFuncs.count(elem)) { replacedElems[index] = elem; auto* secondaryFunc = secondary.getFunction(elem); @@ -451,10 +481,14 @@ void ModuleSplitter::setupTablePatching() { return; } + auto secondaryTable = + ModuleUtils::copyTableWithoutSegments(tableManager.activeTable, secondary); + if (tableManager.activeBase.global.size()) { - assert(primary.table.segments.size() == 1 && + assert(tableManager.activeTable->segments.size() == 1 && "Unexpected number of segments with non-const base"); - assert(secondary.table.segments.size() == 0); + assert(secondary.tables.size() == 1 && + secondary.tables.front()->segments.empty()); // Since addition is not currently allowed in initializer expressions, we // need to start the new secondary segment where the primary segment starts. // The secondary segment will contain the same primary functions as the @@ -463,7 +497,8 @@ void ModuleSplitter::setupTablePatching() { // to be imported into the second module. TODO: use better strategies here, // such as using ref.func in the start function or standardizing addition in // initializer expressions. - const Table::Segment& primarySeg = primary.table.segments.front(); + const Table::Segment& primarySeg = + tableManager.activeTable->segments.front(); std::vector<Name> secondaryElems; secondaryElems.reserve(primarySeg.data.size()); @@ -484,7 +519,7 @@ void ModuleSplitter::setupTablePatching() { } auto offset = ExpressionManipulator::copy(primarySeg.offset, secondary); - secondary.table.segments.emplace_back(offset, secondaryElems); + secondaryTable->segments.emplace_back(offset, secondaryElems); return; } @@ -494,7 +529,7 @@ void ModuleSplitter::setupTablePatching() { std::vector<Name> currData; auto finishSegment = [&]() { auto* offset = Builder(secondary).makeConst(int32_t(currBase)); - secondary.table.segments.emplace_back(offset, currData); + secondaryTable->segments.emplace_back(offset, currData); }; for (auto curr = replacedElems.begin(); curr != replacedElems.end(); ++curr) { if (curr->first != currBase + currData.size()) { @@ -551,12 +586,14 @@ void ModuleSplitter::shareImportableItems() { primary.memory, secondary.memory, "memory", ExternalKind::Memory); } - if (primary.table.exists) { - secondary.table.exists = true; - secondary.table.initial = primary.table.initial; - secondary.table.max = primary.table.max; - makeImportExport( - primary.table, secondary.table, "table", ExternalKind::Table); + for (auto& table : primary.tables) { + auto secondaryTable = secondary.getTableOrNull(table->name); + if (!secondaryTable) { + secondaryTable = + ModuleUtils::copyTableWithoutSegments(table.get(), secondary); + } + + makeImportExport(*table, *secondaryTable, "table", ExternalKind::Table); } for (auto& global : primary.globals) { diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index f33d41266..29ebcc2b5 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -70,6 +70,29 @@ inline Event* copyEvent(Event* event, Module& out) { return ret; } +inline Table* copyTableWithoutSegments(Table* table, Module& out) { + auto ret = std::make_unique<Table>(); + ret->name = table->name; + ret->module = table->module; + ret->base = table->base; + + ret->initial = table->initial; + ret->max = table->max; + + return out.addTable(std::move(ret)); +} + +inline Table* copyTable(Table* table, Module& out) { + auto ret = copyTableWithoutSegments(table, out); + + for (auto segment : table->segments) { + segment.offset = ExpressionManipulator::copy(segment.offset, out); + ret->segments.push_back(segment); + } + + return ret; +} + inline void copyModule(const Module& in, Module& out) { // we use names throughout, not raw pointers, so simple copying is fine // for everything *but* expressions @@ -85,9 +108,8 @@ inline void copyModule(const Module& in, Module& out) { for (auto& curr : in.events) { copyEvent(curr.get(), out); } - out.table = in.table; - for (auto& segment : out.table.segments) { - segment.offset = ExpressionManipulator::copy(segment.offset, out); + for (auto& curr : in.tables) { + copyTable(curr.get(), out); } out.memory = in.memory; for (auto& segment : out.memory.segments) { @@ -126,9 +148,11 @@ template<typename T> inline void renameFunctions(Module& wasm, T& map) { } }; maybeUpdate(wasm.start); - for (auto& segment : wasm.table.segments) { - for (auto& name : segment.data) { - maybeUpdate(name); + for (auto& table : wasm.tables) { + for (auto& segment : table->segments) { + for (auto& name : segment.data) { + maybeUpdate(name); + } } } for (auto& exp : wasm.exports) { @@ -169,14 +193,18 @@ template<typename T> inline void iterDefinedMemories(Module& wasm, T visitor) { } template<typename T> inline void iterImportedTables(Module& wasm, T visitor) { - if (wasm.table.exists && wasm.table.imported()) { - visitor(&wasm.table); + for (auto& import : wasm.tables) { + if (import->imported()) { + visitor(import.get()); + } } } template<typename T> inline void iterDefinedTables(Module& wasm, T visitor) { - if (wasm.table.exists && !wasm.table.imported()) { - visitor(&wasm.table); + for (auto& import : wasm.tables) { + if (!import->imported()) { + visitor(import.get()); + } } } diff --git a/src/ir/names.h b/src/ir/names.h index 99dd3cab7..6fbb987f5 100644 --- a/src/ir/names.h +++ b/src/ir/names.h @@ -76,6 +76,10 @@ inline Name getValidFunctionName(Module& module, Name root) { return getValidName( module, root, [&](Name test) { return !module.getFunctionOrNull(test); }); } +inline Name getValidTableName(Module& module, Name root) { + return getValidName( + module, root, [&](Name test) { return !module.getTableOrNull(test); }); +} inline Name getValidEventName(Module& module, Name root) { return getValidName( module, root, [&](Name test) { return !module.getEventOrNull(test); }); diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index fc64d8bbd..2ac2605fa 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -576,9 +576,9 @@ function wrapModule(module, self = {}) { // 'callIndirect', 'returnCall', 'returnCallIndirect' are deprecated and may // be removed in a future release. Please use the the snake_case names // instead. - self['callIndirect'] = self['call_indirect'] = function(target, operands, params, results) { + self['callIndirect'] = self['call_indirect'] = function(table, target, operands, params, results) { return preserveStack(() => - Module['_BinaryenCallIndirect'](module, target, i32sToStack(operands), operands.length, params, results) + Module['_BinaryenCallIndirect'](module, strToStack(table), target, i32sToStack(operands), operands.length, params, results) ); }; self['returnCall'] = self['return_call'] = function(name, operands, type) { @@ -586,9 +586,9 @@ function wrapModule(module, self = {}) { Module['_BinaryenReturnCall'](module, strToStack(name), i32sToStack(operands), operands.length, type) ); }; - self['returnCallIndirect'] = self['return_call_indirect'] = function(target, operands, params, results) { + self['returnCallIndirect'] = self['return_call_indirect'] = function(table, target, operands, params, results) { return preserveStack(() => - Module['_BinaryenReturnCallIndirect'](module, target, i32sToStack(operands), operands.length, params, results) + Module['_BinaryenReturnCallIndirect'](module, strToStack(table), target, i32sToStack(operands), operands.length, params, results) ); }; @@ -2197,9 +2197,23 @@ function wrapModule(module, self = {}) { self['getGlobal'] = function(name) { return preserveStack(() => Module['_BinaryenGetGlobal'](module, strToStack(name))); }; + self['addTable'] = function(table, initial, maximum, funcNames, offset = self['i32']['const'](0)) { + return preserveStack(() => Module['_BinaryenAddTable'](module, + strToStack(table), initial, maximum, + i32sToStack(funcNames.map(strToStack)), + funcNames.length, + offset) + ); + } + self['getTable'] = function(name) { + return preserveStack(() => Module['_BinaryenGetTable'](module, strToStack(name))); + }; self['removeGlobal'] = function(name) { return preserveStack(() => Module['_BinaryenRemoveGlobal'](module, strToStack(name))); } + self['removeTable'] = function(name) { + return preserveStack(() => Module['_BinaryenRemoveTable'](module, strToStack(name))); + } self['addEvent'] = function(name, attribute, params, results) { return preserveStack(() => Module['_BinaryenAddEvent'](module, strToStack(name), attribute, params, results)); }; @@ -2360,9 +2374,15 @@ function wrapModule(module, self = {}) { self['getNumGlobals'] = function() { return Module['_BinaryenGetNumGlobals'](module); }; + self['getNumTables'] = function() { + return Module['_BinaryenGetNumTables'](module); + }; self['getGlobalByIndex'] = function(index) { return Module['_BinaryenGetGlobalByIndex'](module, index); }; + self['getTableByIndex'] = function(index) { + return Module['_BinaryenGetTableByIndex'](module, index); + }; self['emitText'] = function() { const old = out; let ret = ''; @@ -2587,6 +2607,7 @@ Module['getExpressionInfo'] = function(expr) { 'type': type, 'isReturn': Boolean(Module['_BinaryenCallIndirectIsReturn'](expr)), 'target': Module['_BinaryenCallIndirectGetTarget'](expr), + 'table': Module['_BinaryenCallIndirectGetTable'](expr), 'operands': getAllNested(expr, Module['_BinaryenCallIndirectGetNumOperands'], Module['_BinaryenCallIndirectGetOperandAt']) }; case Module['LocalGetId']: @@ -2973,6 +2994,23 @@ Module['getGlobalInfo'] = function(global) { }; }; +// Obtains information about a 'Table' +Module['getTableInfo'] = function(table) { + var hasMax = Boolean(Module['_BinaryenTableHasMax'](table)); + var tableInfo = { + 'name': UTF8ToString(Module['_BinaryenTableGetName'](table)), + 'module': UTF8ToString(Module['_BinaryenTableImportGetModule'](table)), + 'base': UTF8ToString(Module['_BinaryenTableImportGetBase'](table)), + 'initial': Module['_BinaryenTableGetInitial'](table) + }; + + if (hasMax) { + tableInfo.max = Module['_BinaryenTableGetMax'](table); + } + + return tableInfo; +}; + // Obtains information about a 'Event' Module['getEventInfo'] = function(event_) { return { @@ -3421,6 +3459,12 @@ Module['CallIndirect'] = makeExpressionWrapper({ 'setTarget'(expr, targetExpr) { Module['_BinaryenCallIndirectSetTarget'](expr, targetExpr); }, + 'getTable'(expr) { + return UTF8ToString(Module['_BinaryenCallIndirectGetTable'](expr)); + }, + 'setTable'(expr, table) { + preserveStack(() => { Module['_BinaryenCallIndirectSetTable'](expr, strToStack(table)) }); + }, 'getNumOperands'(expr) { return Module['_BinaryenCallIndirectGetNumOperands'](expr); }, diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp index f483a2295..1a404dbcf 100644 --- a/src/passes/DeadArgumentElimination.cpp +++ b/src/passes/DeadArgumentElimination.cpp @@ -284,9 +284,11 @@ struct DAE : public Pass { infoMap[curr->value].hasUnseenCalls = true; } } - for (auto& segment : module->table.segments) { - for (auto name : segment.data) { - infoMap[name].hasUnseenCalls = true; + for (auto& table : module->tables) { + for (auto& segment : table->segments) { + for (auto name : segment.data) { + infoMap[name].hasUnseenCalls = true; + } } } // Scan all the functions. diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp index f966d1a5a..6ee976b39 100644 --- a/src/passes/Directize.cpp +++ b/src/passes/Directize.cpp @@ -36,25 +36,31 @@ namespace { struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> { bool isFunctionParallel() override { return true; } - Pass* create() override { return new FunctionDirectizer(flatTable); } + Pass* create() override { return new FunctionDirectizer(tables); } - FunctionDirectizer(TableUtils::FlatTable* flatTable) : flatTable(flatTable) {} + FunctionDirectizer( + const std::unordered_map<Name, TableUtils::FlatTable>& tables) + : tables(tables) {} void visitCallIndirect(CallIndirect* curr) { - if (!flatTable) { + auto it = tables.find(curr->table); + if (it == tables.end()) { return; } + + auto& flatTable = it->second; + if (auto* c = curr->target->dynCast<Const>()) { Index index = c->value.geti32(); // If the index is invalid, or the type is wrong, we can // emit an unreachable here, since in Binaryen it is ok to // reorder/replace traps when optimizing (but never to // remove them, at least not by default). - if (index >= flatTable->names.size()) { + if (index >= flatTable.names.size()) { replaceWithUnreachable(curr); return; } - auto name = flatTable->names[index]; + auto name = flatTable.names[index]; if (!name.is()) { replaceWithUnreachable(curr); return; @@ -88,8 +94,7 @@ struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> { } private: - // If null, then we cannot optimize call_indirects. - TableUtils::FlatTable* flatTable; + const std::unordered_map<Name, TableUtils::FlatTable> tables; bool changedTypes = false; @@ -106,31 +111,34 @@ private: struct Directize : public Pass { void run(PassRunner* runner, Module* module) override { - bool canOptimizeCallIndirect = true; - TableUtils::FlatTable flatTable(module->table); - if (!module->table.exists) { - canOptimizeCallIndirect = false; - } else if (module->table.imported()) { - canOptimizeCallIndirect = false; - } else { - for (auto& ex : module->exports) { - if (ex->kind == ExternalKind::Table) { - canOptimizeCallIndirect = false; + std::unordered_map<Name, TableUtils::FlatTable> validTables; + + for (auto& table : module->tables) { + if (!table->imported()) { + bool canOptimizeCallIndirect = true; + + for (auto& ex : module->exports) { + if (ex->kind == ExternalKind::Table && ex->value == table->name) { + canOptimizeCallIndirect = false; + } + } + + if (canOptimizeCallIndirect) { + TableUtils::FlatTable flatTable(*table); + if (flatTable.valid) { + validTables.emplace(table->name, flatTable); + } } - } - if (!flatTable.valid) { - canOptimizeCallIndirect = false; } } + // Without typed function references, all we can do is optimize table // accesses, so if we can't do that, stop. - if (!canOptimizeCallIndirect && - !module->features.hasTypedFunctionReferences()) { + if (validTables.empty() && !module->features.hasTypedFunctionReferences()) { return; } // The table exists and is constant, so this is possible. - FunctionDirectizer(canOptimizeCallIndirect ? &flatTable : nullptr) - .run(runner, module); + FunctionDirectizer(validTables).run(runner, module); } }; diff --git a/src/passes/ExtractFunction.cpp b/src/passes/ExtractFunction.cpp index 34fb449d4..ca05b6108 100644 --- a/src/passes/ExtractFunction.cpp +++ b/src/passes/ExtractFunction.cpp @@ -46,7 +46,9 @@ struct ExtractFunction : public Pass { } // clear data module->memory.segments.clear(); - module->table.segments.clear(); + // TODO: if the extracted function contains a call_indirect, the referenced + // table should not be removed. + module->tables.clear(); // leave just an export for the thing we want if (!module->getExportOrNull(name)) { module->exports.clear(); diff --git a/src/passes/FuncCastEmulation.cpp b/src/passes/FuncCastEmulation.cpp index a9302fb65..1ac32166e 100644 --- a/src/passes/FuncCastEmulation.cpp +++ b/src/passes/FuncCastEmulation.cpp @@ -172,18 +172,21 @@ struct FuncCastEmulation : public Pass { Signature ABIType(Type(std::vector<Type>(numParams, Type::i64)), Type::i64); // Add a thunk for each function in the table, and do the call through it. std::unordered_map<Name, Name> funcThunks; - for (auto& segment : module->table.segments) { - for (auto& name : segment.data) { - auto iter = funcThunks.find(name); - if (iter == funcThunks.end()) { - auto thunk = makeThunk(name, module, numParams); - funcThunks[name] = thunk; - name = thunk; - } else { - name = iter->second; + for (auto& table : module->tables) { + for (auto& segment : table->segments) { + for (auto& name : segment.data) { + auto iter = funcThunks.find(name); + if (iter == funcThunks.end()) { + auto thunk = makeThunk(name, module, numParams); + funcThunks[name] = thunk; + name = thunk; + } else { + name = iter->second; + } } } } + // update call_indirects ParallelFuncCastEmulation(ABIType, numParams).run(runner, module); } diff --git a/src/passes/GenerateDynCalls.cpp b/src/passes/GenerateDynCalls.cpp index df5145402..a53f1d0de 100644 --- a/src/passes/GenerateDynCalls.cpp +++ b/src/passes/GenerateDynCalls.cpp @@ -127,7 +127,10 @@ void GenerateDynCalls::generateDynCallThunk(Signature sig) { for (const auto& param : sig.params) { args.push_back(builder.makeLocalGet(++i, param)); } - Expression* call = builder.makeCallIndirect(fptr, args, sig); + // FIXME: Should the existence of a table be ensured here? i.e. create one if + // there is none? + Expression* call = + builder.makeCallIndirect(Name::fromInt(0), fptr, args, sig); f->body = call; wasm->addFunction(std::move(f)); diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index f14f1a7e2..36d0ce8f9 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -282,8 +282,11 @@ struct I64ToI32Lowering : public WalkerPass<PostWalker<I64ToI32Lowering>> { params.push_back(param); } } - return builder->makeCallIndirect( - curr->target, args, Signature(Type(params), results), curr->isReturn); + return builder->makeCallIndirect(curr->table, + curr->target, + args, + Signature(Type(params), results), + curr->isReturn); }); } diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index 5a2a7137a..44ebbd54d 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -335,11 +335,14 @@ struct Inlining : public Pass { infos[ex->value].usedGlobally = true; } } - for (auto& segment : module->table.segments) { - for (auto name : segment.data) { - infos[name].usedGlobally = true; + for (auto& table : module->tables) { + for (auto& segment : table->segments) { + for (auto name : segment.data) { + infos[name].usedGlobally = true; + } } } + for (auto& global : module->globals) { if (!global->imported()) { for (auto* ref : FindAll<RefFunc>(global->init).list) { diff --git a/src/passes/LegalizeJSInterface.cpp b/src/passes/LegalizeJSInterface.cpp index 0e810560e..e8a604326 100644 --- a/src/passes/LegalizeJSInterface.cpp +++ b/src/passes/LegalizeJSInterface.cpp @@ -24,10 +24,10 @@ // // We can also legalize in a "minimal" way, that is, only JS-specific // components, that only JS will care about, such as dynCall methods -// (wasm will never call them, as it can share the table directly). E.g. +// (wasm will never call them, as it can share the tables directly). E.g. // is dynamic linking, where we can avoid legalizing wasm=>wasm calls // across modules, we still want to legalize dynCalls so JS can call into the -// table even to a signature that is not legal. +// tables even to a signature that is not legal. // #include "asmjs/shared-constants.h" @@ -94,13 +94,15 @@ struct LegalizeJSInterface : public Pass { if (im->imported() && isIllegal(im) && shouldBeLegalized(im)) { auto funcName = makeLegalStubForCalledImport(im, module); illegalImportsToLegal[im->name] = funcName; - // we need to use the legalized version in the table, as the import from - // JS is legal for JS. Our stub makes it look like a native wasm + // we need to use the legalized version in the tables, as the import + // from JS is legal for JS. Our stub makes it look like a native wasm // function. - for (auto& segment : module->table.segments) { - for (auto& name : segment.data) { - if (name == im->name) { - name = funcName; + for (auto& table : module->tables) { + for (auto& segment : table->segments) { + for (auto& name : segment.data) { + if (name == im->name) { + name = funcName; + } } } } diff --git a/src/passes/Metrics.cpp b/src/passes/Metrics.cpp index 3dda70d9b..09dcc5445 100644 --- a/src/passes/Metrics.cpp +++ b/src/passes/Metrics.cpp @@ -55,15 +55,15 @@ struct Metrics } ModuleUtils::iterDefinedGlobals(*module, [&](Global* curr) { walkGlobal(curr); }); - walkTable(&module->table); walkMemory(&module->memory); - // add imports / funcs / globals/ exports + // add imports / funcs / globals / exports / tables counts["[imports]"] = imports.getNumImports(); counts["[funcs]"] = imports.getNumDefinedFunctions(); counts["[globals]"] = imports.getNumDefinedGlobals(); counts["[events]"] = imports.getNumDefinedEvents(); counts["[exports]"] = module->exports.size(); + counts["[tables]"] = imports.getNumDefinedTables(); // add memory and table if (module->memory.exists) { Index size = 0; @@ -72,11 +72,15 @@ struct Metrics } counts["[memory-data]"] = size; } - if (module->table.exists) { - Index size = 0; - for (auto& segment : module->table.segments) { + + Index size = 0; + for (auto& table : module->tables) { + walkTable(table.get()); + for (auto& segment : table->segments) { size += segment.data.size(); } + } + if (!module->tables.empty()) { counts["[table-data]"] = size; } diff --git a/src/passes/PostEmscripten.cpp b/src/passes/PostEmscripten.cpp index d57d849da..35e2b9a8b 100644 --- a/src/passes/PostEmscripten.cpp +++ b/src/passes/PostEmscripten.cpp @@ -61,13 +61,13 @@ struct PostEmscripten : public Pass { hasInvokes = true; } } - if (!hasInvokes) { + if (!hasInvokes || module->tables.empty()) { return; } // Next, see if the Table is flat, which we need in order to see where // invokes go statically. (In dynamic linking, the table is not flat, // and we can't do this.) - TableUtils::FlatTable flatTable(module->table); + TableUtils::FlatTable flatTable(*module->tables[0]); if (!flatTable.valid) { return; } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index b7b7e1320..e49735af0 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -303,10 +303,18 @@ struct PrintExpressionContents } void visitCallIndirect(CallIndirect* curr) { if (curr->isReturn) { - printMedium(o, "return_call_indirect (type "); + printMedium(o, "return_call_indirect "); } else { - printMedium(o, "call_indirect (type "); + printMedium(o, "call_indirect "); } + + if (features.hasReferenceTypes()) { + printName(curr->table, o); + o << ' '; + } + + o << '('; + printMinor(o, "type "); printHeapTypeName(o, curr->sig); o << ')'; } @@ -1936,8 +1944,8 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { const char* maybeSpace; const char* maybeNewLine; - bool full = false; // whether to not elide nodes in output when possible - // (like implicit blocks) and to emit types + bool full = false; // whether to not elide nodes in output when possible + // (like implicit blocks) and to emit types bool stackIR = false; // whether to print stack IR if it is present // (if false, and Stack IR is there, we just // note it exists) @@ -2962,14 +2970,11 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { o << " funcref)"; } void visitTable(Table* curr) { - if (!curr->exists) { - return; - } if (curr->imported()) { doIndent(o, indent); o << '('; emitImportHeader(curr); - printTableHeader(&currModule->table); + printTableHeader(curr); o << ')' << maybeNewLine; } else { doIndent(o, indent); @@ -2983,8 +2988,23 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> { } doIndent(o, indent); o << '('; - printMajor(o, "elem "); + printMedium(o, "elem "); + + // TODO(reference-types): check for old-style based on the complete spec + if (currModule->tables.size() > 1) { + // tableuse + o << '('; + printMedium(o, "table "); + printName(curr->name, o); + o << ") "; + } + visit(segment.offset); + + if (currModule->tables.size() > 1) { + o << " func"; + } + for (auto name : segment.data) { o << ' '; printName(name, o); @@ -3334,6 +3354,7 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) { if (inst->origin->is<Pop>()) { break; } + PrintExpressionContents(func, o).visit(inst->origin); break; } diff --git a/src/passes/PrintCallGraph.cpp b/src/passes/PrintCallGraph.cpp index 6d9224a21..49e056312 100644 --- a/src/passes/PrintCallGraph.cpp +++ b/src/passes/PrintCallGraph.cpp @@ -96,10 +96,12 @@ struct PrintCallGraph : public Pass { CallPrinter printer(module); // Indirect Targets - for (auto& segment : module->table.segments) { - for (auto& curr : segment.data) { - auto* func = module->getFunction(curr); - o << " \"" << func->name << "\" [style=\"filled, rounded\"];\n"; + for (auto& table : module->tables) { + for (auto& segment : table->segments) { + for (auto& curr : segment.data) { + auto* func = module->getFunction(curr); + o << " \"" << func->name << "\" [style=\"filled, rounded\"];\n"; + } } } diff --git a/src/passes/RemoveImports.cpp b/src/passes/RemoveImports.cpp index 0874eac34..10a885586 100644 --- a/src/passes/RemoveImports.cpp +++ b/src/passes/RemoveImports.cpp @@ -49,8 +49,8 @@ struct RemoveImports : public WalkerPass<PostWalker<RemoveImports>> { *curr, [&](Function* func) { names.push_back(func->name); }); // Do not remove names referenced in a table std::set<Name> indirectNames; - if (curr->table.exists) { - for (auto& segment : curr->table.segments) { + for (auto& table : curr->tables) { + for (auto& segment : table->segments) { for (auto& name : segment.data) { indirectNames.insert(name); } diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 70a326799..8401f5e1f 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -29,7 +29,7 @@ namespace wasm { -enum class ModuleElementKind { Function, Global, Event }; +enum class ModuleElementKind { Function, Global, Event, Table }; typedef std::pair<ModuleElementKind, Name> ModuleElement; @@ -41,7 +41,6 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { std::vector<ModuleElement> queue; std::set<ModuleElement> reachable; bool usesMemory = false; - bool usesTable = false; ReachabilityAnalyzer(Module* module, const std::vector<ModuleElement>& roots) : module(module) { @@ -52,9 +51,12 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { walk(segment.offset); } } - for (auto& segment : module->table.segments) { - walk(segment.offset); + for (auto& table : module->tables) { + for (auto& segment : table->segments) { + walk(segment.offset); + } } + // main loop while (queue.size()) { auto& curr = queue.back(); @@ -73,6 +75,11 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { if (!global->imported()) { walk(global->init); } + } else if (curr.first == ModuleElementKind::Table) { + auto* table = module->getTable(curr.second); + for (auto& segment : table->segments) { + walk(segment.offset); + } } } } @@ -84,7 +91,14 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> { queue.emplace_back(ModuleElementKind::Function, curr->target); } } - void visitCallIndirect(CallIndirect* curr) { usesTable = true; } + void visitCallIndirect(CallIndirect* curr) { + assert(!module->tables.empty() && "call-indirect to undefined table."); + + if (reachable.count(ModuleElement(ModuleElementKind::Table, curr->table)) == + 0) { + queue.emplace_back(ModuleElementKind::Table, curr->table); + } + } void visitGlobalGet(GlobalGet* curr) { if (reachable.count(ModuleElement(ModuleElementKind::Global, curr->name)) == @@ -152,7 +166,6 @@ struct RemoveUnusedModuleElements : public Pass { } // Exports are roots. bool exportsMemory = false; - bool exportsTable = false; for (auto& curr : module->exports) { if (curr->kind == ExternalKind::Function) { roots.emplace_back(ModuleElementKind::Function, curr->value); @@ -160,25 +173,24 @@ struct RemoveUnusedModuleElements : public Pass { roots.emplace_back(ModuleElementKind::Global, curr->value); } else if (curr->kind == ExternalKind::Event) { roots.emplace_back(ModuleElementKind::Event, curr->value); + } else if (curr->kind == ExternalKind::Table) { + roots.emplace_back(ModuleElementKind::Table, curr->value); } else if (curr->kind == ExternalKind::Memory) { exportsMemory = true; - } else if (curr->kind == ExternalKind::Table) { - exportsTable = true; } } // Check for special imports, which are roots. bool importsMemory = false; - bool importsTable = false; if (module->memory.imported()) { importsMemory = true; } - if (module->table.imported()) { - importsTable = true; - } // For now, all functions that can be called indirectly are marked as roots. - for (auto& segment : module->table.segments) { - for (auto& curr : segment.data) { - roots.emplace_back(ModuleElementKind::Function, curr); + for (auto& table : module->tables) { + // TODO(reference-types): Check whether table's datatype is funcref. + for (auto& segment : table->segments) { + for (auto& curr : segment.data) { + roots.emplace_back(ModuleElementKind::Function, curr); + } } } // Compute reachability starting from the root set. @@ -196,6 +208,19 @@ struct RemoveUnusedModuleElements : public Pass { return analyzer.reachable.count( ModuleElement(ModuleElementKind::Event, curr->name)) == 0; }); + + for (auto& table : module->tables) { + table->segments.erase( + std::remove_if(table->segments.begin(), + table->segments.end(), + [&](auto& seg) { return seg.data.empty(); }), + table->segments.end()); + } + module->removeTables([&](Table* curr) { + return (curr->segments.empty() || !curr->imported()) && + analyzer.reachable.count( + ModuleElement(ModuleElementKind::Table, curr->name)) == 0; + }); // Handle the memory and table if (!exportsMemory && !analyzer.usesMemory) { if (!importsMemory) { @@ -210,18 +235,6 @@ struct RemoveUnusedModuleElements : public Pass { module->memory.max = 0; } } - if (!exportsTable && !analyzer.usesTable) { - if (!importsTable) { - // The table is unobservable to the outside, we can remove the contents. - module->table.segments.clear(); - } - if (module->table.segments.empty()) { - module->table.exists = false; - module->table.module = module->table.base = Name(); - module->table.initial = 0; - module->table.max = 0; - } - } } }; diff --git a/src/passes/ReorderFunctions.cpp b/src/passes/ReorderFunctions.cpp index 05df8cbe7..4d02616f0 100644 --- a/src/passes/ReorderFunctions.cpp +++ b/src/passes/ReorderFunctions.cpp @@ -70,9 +70,11 @@ struct ReorderFunctions : public Pass { for (auto& curr : module->exports) { counts[curr->value]++; } - for (auto& segment : module->table.segments) { - for (auto& curr : segment.data) { - counts[curr]++; + for (auto& table : module->tables) { + for (auto& segment : table->segments) { + for (auto& curr : segment.data) { + counts[curr]++; + } } } // sort diff --git a/src/passes/opt-utils.h b/src/passes/opt-utils.h index cc0f40c54..0a67a7e1e 100644 --- a/src/passes/opt-utils.h +++ b/src/passes/opt-utils.h @@ -86,9 +86,11 @@ inline void replaceFunctions(PassRunner* runner, // replace direct calls FunctionRefReplacer(maybeReplace).run(runner, &module); // replace in table - for (auto& segment : module.table.segments) { - for (auto& name : segment.data) { - maybeReplace(name); + for (auto& table : module.tables) { + for (auto& segment : table->segments) { + for (auto& name : segment.data) { + maybeReplace(name); + } } } // replace in start diff --git a/src/shell-interface.h b/src/shell-interface.h index aeb9cc2a9..3c7f1832a 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -85,14 +85,19 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { } } memory; - std::vector<Name> table; + std::unordered_map<Name, std::vector<Name>> tables; ShellExternalInterface() : memory() {} virtual ~ShellExternalInterface() = default; void init(Module& wasm, ModuleInstance& instance) override { memory.resize(wasm.memory.initial * wasm::Memory::kPageSize); - table.resize(wasm.table.initial); + + if (wasm.tables.size() > 0) { + for (auto& table : wasm.tables) { + tables[table->name].resize(table->initial); + } + } } void importGlobals(std::map<Name, Literals>& globals, Module& wasm) override { @@ -154,11 +159,20 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { << import->name.str; } - Literals callTable(Index index, + Literals callTable(Name tableName, + Index index, Signature sig, LiteralList& arguments, Type results, ModuleInstance& instance) override { + + auto it = tables.find(tableName); + if (it == tables.end()) { + trap("callTable on non-existing table"); + } + + auto& table = it->second; + if (index >= table.size()) { trap("callTable overflow"); } @@ -216,7 +230,9 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { memory.set<std::array<uint8_t, 16>>(addr, value); } - void tableStore(Address addr, Name entry) override { table[addr] = entry; } + void tableStore(Name tableName, Address addr, Name entry) override { + tables[tableName][addr] = entry; + } bool growMemory(Address /*oldSize*/, Address newSize) override { // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 66f175414..714e2c84b 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -423,10 +423,19 @@ private: } } + // TODO(reference-types): allow the fuzzer to create multiple tables void setupTable() { - wasm.table.exists = true; - wasm.table.initial = wasm.table.max = 0; - wasm.table.segments.emplace_back(builder.makeConst(int32_t(0))); + if (wasm.tables.size() > 0) { + auto& table = wasm.tables[0]; + table->initial = table->max = 0; + table->segments.emplace_back(builder.makeConst(int32_t(0))); + } else { + auto table = builder.makeTable( + Names::getValidTableName(wasm, "fuzzing_table"), 0, 0); + table->hasExplicitName = true; + table->segments.emplace_back(builder.makeConst(int32_t(0))); + wasm.addTable(std::move(table)); + } } std::map<Type, std::vector<Name>> globalsByType; @@ -522,26 +531,27 @@ private: } void finalizeTable() { - for (auto& segment : wasm.table.segments) { - // If the offset is a global that was imported (which is ok) but no - // longer is (not ok) we need to change that. - if (auto* offset = segment.offset->dynCast<GlobalGet>()) { - if (!wasm.getGlobal(offset->name)->imported()) { - // TODO: the segments must not overlap... - segment.offset = - builder.makeConst(Literal::makeFromInt32(0, Type::i32)); + for (auto& table : wasm.tables) { + for (auto& segment : table->segments) { + // If the offset is a global that was imported (which is ok) but no + // longer is (not ok) we need to change that. + if (auto* offset = segment.offset->dynCast<GlobalGet>()) { + if (!wasm.getGlobal(offset->name)->imported()) { + // TODO: the segments must not overlap... + segment.offset = + builder.makeConst(Literal::makeFromInt32(0, Type::i32)); + } } + Address maxOffset = segment.data.size(); + if (auto* offset = segment.offset->dynCast<Const>()) { + maxOffset = maxOffset + offset->value.getInteger(); + } + table->initial = std::max(table->initial, maxOffset); } - Address maxOffset = segment.data.size(); - if (auto* offset = segment.offset->dynCast<Const>()) { - maxOffset = maxOffset + offset->value.getInteger(); - } - wasm.table.initial = std::max(wasm.table.initial, maxOffset); + table->max = oneIn(2) ? Address(Table::kUnlimitedSize) : table->initial; + // Avoid an imported table (which the fuzz harness would need to handle). + table->module = table->base = Name(); } - wasm.table.max = - oneIn(2) ? Address(Table::kUnlimitedSize) : wasm.table.initial; - // Avoid an imported table (which the fuzz harness would need to handle). - wasm.table.module = wasm.table.base = Name(); } Name HANG_LIMIT_GLOBAL; @@ -705,7 +715,7 @@ private: } // add some to the table while (oneIn(3) && !finishedInput) { - wasm.table.segments[0].data.push_back(func->name); + wasm.tables[0]->segments[0].data.push_back(func->name); } numAddedFunctions++; return func; @@ -1425,7 +1435,7 @@ private: } Expression* makeCallIndirect(Type type) { - auto& data = wasm.table.segments[0].data; + auto& data = wasm.tables[0]->segments[0].data; if (data.empty()) { return make(type); } @@ -1462,7 +1472,8 @@ private: for (const auto& type : targetFn->sig.params) { args.push_back(make(type)); } - return builder.makeCallIndirect(target, args, targetFn->sig, isReturn); + return builder.makeCallIndirect( + wasm.tables[0]->name, target, args, targetFn->sig, isReturn); } Expression* makeCallRef(Type type) { diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 4ad9979cd..9e3915073 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -215,14 +215,23 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface { extra); } - Literals callTable(Index index, + Literals callTable(Name tableName, + Index index, Signature sig, LiteralList& arguments, Type result, EvallingModuleInstance& instance) override { + + std::unordered_map<wasm::Name, std::vector<wasm::Name>>::iterator it; + + auto* table = wasm->getTableOrNull(tableName); + if (!table) { + throw FailToEvalException("callTable on non-existing table"); + } + // we assume the table is not modified (hmm) // look through the segments, try to find the function - for (auto& segment : wasm->table.segments) { + for (auto& segment : table->segments) { Index start; // look for the index in this segment. if it has a constant offset, we // look in the proper range. if it instead gets a global, we rely on the @@ -282,7 +291,7 @@ struct CtorEvalExternalInterface : EvallingModuleInstance::ExternalInterface { } // called during initialization, but we don't keep track of a table - void tableStore(Address addr, Name value) override {} + void tableStore(Name tableName, Address addr, Name value) override {} bool growMemory(Address /*oldSize*/, Address newSize) override { throw FailToEvalException("grow memory"); diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp index 87bb293b6..46b66c986 100644 --- a/src/tools/wasm-metadce.cpp +++ b/src/tools/wasm-metadce.cpp @@ -213,17 +213,19 @@ struct MetaDCEGraph { // we can't remove segments, so root what they need InitScanner rooter(this, Name()); rooter.setModule(&wasm); - for (auto& segment : wasm.table.segments) { - // TODO: currently, all functions in the table are roots, but we - // should add an option to refine that - for (auto& name : segment.data) { - if (!wasm.getFunction(name)->imported()) { - roots.insert(functionToDCENode[name]); - } else { - roots.insert(importIdToDCENode[getFunctionImportId(name)]); + for (auto& table : wasm.tables) { + for (auto& segment : table->segments) { + // TODO: currently, all functions in the table are roots, but we + // should add an option to refine that + for (auto& name : segment.data) { + if (!wasm.getFunction(name)->imported()) { + roots.insert(functionToDCENode[name]); + } else { + roots.insert(importIdToDCENode[getFunctionImportId(name)]); + } } + rooter.walk(segment.offset); } - rooter.walk(segment.offset); } for (auto& segment : wasm.memory.segments) { if (!segment.isPassive) { diff --git a/src/tools/wasm-reduce.cpp b/src/tools/wasm-reduce.cpp index 3860630af..4bb4b6f9a 100644 --- a/src/tools/wasm-reduce.cpp +++ b/src/tools/wasm-reduce.cpp @@ -898,8 +898,14 @@ struct Reducer } // If we are left with a single function that is not exported or used in // a table, that is useful as then we can change the return type. + bool allTablesEmpty = std::all_of( + module->tables.begin(), module->tables.end(), [&](auto& table) { + return std::all_of(table->segments.begin(), + table->segments.end(), + [&](auto& segment) { return segment.data.empty(); }); + }); if (module->functions.size() == 1 && module->exports.empty() && - module->table.segments.empty()) { + allTablesEmpty) { auto* func = module->functions[0].get(); // We can't remove something that might have breaks to it. if (!func->imported() && !Properties::isNamedControlFlow(func->body)) { diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 20d4b9344..b3b52d9a1 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -161,6 +161,7 @@ static void run_asserts(Name moduleName, invalid = true; }; ModuleUtils::iterImportedGlobals(wasm, reportUnknownImport); + ModuleUtils::iterImportedTables(wasm, reportUnknownImport); ModuleUtils::iterImportedFunctions(wasm, [&](Importable* import) { if (import->module == SPECTEST && import->base.startsWith(PRINT)) { // We can handle it. @@ -168,23 +169,22 @@ static void run_asserts(Name moduleName, reportUnknownImport(import); } }); - if (wasm.memory.imported()) { - reportUnknownImport(&wasm.memory); - } - if (wasm.table.imported()) { - reportUnknownImport(&wasm.table); - } - for (auto& segment : wasm.table.segments) { - for (auto name : segment.data) { - // spec tests consider it illegal to use spectest.print in a table - if (auto* import = wasm.getFunction(name)) { - if (import->imported() && import->module == SPECTEST && - import->base.startsWith(PRINT)) { - std::cerr << "cannot put spectest.print in table\n"; - invalid = true; + ModuleUtils::iterDefinedTables(wasm, [&](Table* table) { + for (auto& segment : table->segments) { + for (auto name : segment.data) { + // spec tests consider it illegal to use spectest.print in a table + if (auto* import = wasm.getFunction(name)) { + if (import->imported() && import->module == SPECTEST && + import->base.startsWith(PRINT)) { + std::cerr << "cannot put spectest.print in table\n"; + invalid = true; + } } } } + }); + if (wasm.memory.imported()) { + reportUnknownImport(&wasm.memory); } } if (!invalid) { diff --git a/src/tools/wasm-split.cpp b/src/tools/wasm-split.cpp index 5b44e5f0e..171774d82 100644 --- a/src/tools/wasm-split.cpp +++ b/src/tools/wasm-split.cpp @@ -472,18 +472,21 @@ void adjustTableSize(Module& wasm, int initialSize) { if (initialSize < 0) { return; } - if (!wasm.table.exists) { + if (wasm.tables.empty()) { Fatal() << "--initial-table used but there is no table"; } - if ((uint64_t)initialSize < wasm.table.initial) { + + auto& table = wasm.tables.front(); + + if ((uint64_t)initialSize < table->initial) { Fatal() << "Specified initial table size too small, should be at least " - << wasm.table.initial; + << table->initial; } - if ((uint64_t)initialSize > wasm.table.max) { + if ((uint64_t)initialSize > table->max) { Fatal() << "Specified initial table size larger than max table size " - << wasm.table.max; + << table->max; } - wasm.table.initial = initialSize; + table->initial = initialSize; } void instrumentModule(Module& wasm, const WasmSplitOptions& options) { diff --git a/src/tools/wasm2js.cpp b/src/tools/wasm2js.cpp index 1464f48f8..8659ef6e2 100644 --- a/src/tools/wasm2js.cpp +++ b/src/tools/wasm2js.cpp @@ -982,6 +982,11 @@ int main(int argc, const char* argv[]) { "request for silly amounts of memory)"; } + // TODO: Remove this restriction when wasm2js can handle multiple tables + if (wasm.tables.size() > 1) { + Fatal() << "error: modules with multiple tables are not supported yet."; + } + if (options.passOptions.validate) { if (!WasmValidator().validate(wasm)) { std::cout << wasm << '\n'; diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 6a409f7db..a4d41530e 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -329,7 +329,27 @@ enum Section { Event = 13 }; -enum SegmentFlag { IsPassive = 0x01, HasMemIndex = 0x02 }; +// A passive segment is a segment that will not be automatically copied into a +// memory or table on instantiation, and must instead be applied manually +// using the instructions memory.init or table.init. +// An active segment is equivalent to a passive segment, but with an implicit +// memory.init followed by a data.drop (or table.init followed by a elem.drop) +// that is prepended to the module's start function. +// A declarative element segment is not available at runtime but merely serves +// to forward-declare references that are formed in code with instructions +// like ref.func. +enum SegmentFlag { + // Bit 0: 0 = active, 1 = passive + IsPassive = 1 << 0, + // Bit 1 if passive: 0 = passive, 1 = declarative + IsDeclarative = 1 << 1, + // Bit 1 if active: 0 = index 0, 1 = index given + HasIndex = 1 << 1, + // Table element segments only: + // Bit 2: 0 = elemType is funcref and vector of func indexes given + // 1 = elemType is given and vector of ref expressions is given + UsesExpressions = 1 << 2 +}; enum EncodedType { // value_type @@ -1079,6 +1099,7 @@ class WasmBinaryWriter { std::unordered_map<Name, Index> functionIndexes; std::unordered_map<Name, Index> eventIndexes; std::unordered_map<Name, Index> globalIndexes; + std::unordered_map<Name, Index> tableIndexes; BinaryIndexes(Module& wasm) { auto addIndexes = [&](auto& source, auto& indexes) { @@ -1099,6 +1120,7 @@ class WasmBinaryWriter { }; addIndexes(wasm.functions, functionIndexes); addIndexes(wasm.events, eventIndexes); + addIndexes(wasm.tables, tableIndexes); // Globals may have tuple types in the IR, in which case they lower to // multiple globals, one for each tuple element, in the binary. Tuple @@ -1171,11 +1193,12 @@ public: void writeEvents(); uint32_t getFunctionIndex(Name name) const; + uint32_t getTableIndex(Name name) const; uint32_t getGlobalIndex(Name name) const; uint32_t getEventIndex(Name name) const; uint32_t getTypeIndex(HeapType type) const; - void writeFunctionTableDeclaration(); + void writeTableDeclarations(); void writeTableElements(); void writeNames(); void writeSourceMapUrl(); @@ -1319,6 +1342,7 @@ public: // gets a name in the combined import+defined space Name getFunctionName(Index index); + Name getTableName(Index index); Name getGlobalName(Index index); Name getEventName(Index index); @@ -1355,6 +1379,14 @@ public: // function to check Index endOfFunction = -1; + // we store tables here before wasm.addTable after we know their names + std::vector<std::unique_ptr<Table>> tables; + // we store table imports here before wasm.addTableImport after we know + // their names + std::vector<Table*> tableImports; + // at index i we have all references to the table i + std::map<Index, std::vector<Expression*>> tableRefs; + // we store globals here before wasm.addGlobal after we know their names std::vector<std::unique_ptr<Global>> globals; // we store global imports here before wasm.addGlobalImport after we know @@ -1459,7 +1491,8 @@ public: void readDataSegments(); void readDataCount(); - std::map<Index, std::vector<Index>> functionTable; + // A map from table indexes to the map of segment indexes to their elements + std::map<Index, std::map<Index, std::vector<Index>>> functionTable; void readFunctionTableDeclaration(); void readTableElements(); @@ -1580,4 +1613,4 @@ private: #undef DEBUG_TYPE -#endif // wasm_wasm_binary_h +#endif // wasm_wasm_binary_h
\ No newline at end of file diff --git a/src/wasm-builder.h b/src/wasm-builder.h index bb37ed340..6fc5c4dda 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -78,6 +78,16 @@ public: return func; } + static std::unique_ptr<Table> + makeTable(Name name, Address initial = 0, Address max = Table::kMaxSize) { + auto table = std::make_unique<Table>(); + table->name = name; + table->initial = initial; + table->max = max; + + return table; + } + static std::unique_ptr<Export> makeExport(Name name, Name value, ExternalKind kind) { auto export_ = std::make_unique<Export>(); @@ -244,11 +254,13 @@ public: return call; } template<typename T> - CallIndirect* makeCallIndirect(Expression* target, + CallIndirect* makeCallIndirect(const Name table, + Expression* target, const T& args, Signature sig, bool isReturn = false) { auto* call = wasm.allocator.alloc<CallIndirect>(); + call->table = table; call->sig = sig; call->type = sig.results; call->target = target; diff --git a/src/wasm-delegations-fields.h b/src/wasm-delegations-fields.h index b99ebef40..033da6314 100644 --- a/src/wasm-delegations-fields.h +++ b/src/wasm-delegations-fields.h @@ -225,6 +225,7 @@ switch (DELEGATE_ID) { case Expression::Id::CallIndirectId: { DELEGATE_START(CallIndirect); DELEGATE_FIELD_CHILD(CallIndirect, target); + DELEGATE_FIELD_NAME(CallIndirect, table); DELEGATE_FIELD_CHILD_VECTOR(CallIndirect, operands); DELEGATE_FIELD_SIGNATURE(CallIndirect, sig); DELEGATE_FIELD_INT(CallIndirect, isReturn); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index ba6f5d6ee..b216aa7ae 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2069,7 +2069,8 @@ public: virtual void init(Module& wasm, SubType& instance) {} virtual void importGlobals(GlobalManager& globals, Module& wasm) = 0; virtual Literals callImport(Function* import, LiteralList& arguments) = 0; - virtual Literals callTable(Index index, + virtual Literals callTable(Name tableName, + Index index, Signature sig, LiteralList& arguments, Type result, @@ -2220,7 +2221,7 @@ public: WASM_UNREACHABLE("unimp"); } - virtual void tableStore(Address addr, Name entry) { + virtual void tableStore(Name tableName, Address addr, Name entry) { WASM_UNREACHABLE("unimp"); } }; @@ -2307,17 +2308,20 @@ private: std::unordered_set<size_t> droppedSegments; void initializeTableContents() { - for (auto& segment : wasm.table.segments) { - Address offset = - (uint32_t)InitializerExpressionRunner<GlobalManager>(globals, maxDepth) - .visit(segment.offset) - .getSingleValue() - .geti32(); - if (offset + segment.data.size() > wasm.table.initial) { - externalInterface->trap("invalid offset when initializing table"); - } - for (size_t i = 0; i != segment.data.size(); ++i) { - externalInterface->tableStore(offset + i, segment.data[i]); + for (auto& table : wasm.tables) { + for (auto& segment : table->segments) { + Address offset = (uint32_t)InitializerExpressionRunner<GlobalManager>( + globals, maxDepth) + .visit(segment.offset) + .getSingleValue() + .geti32(); + if (offset + segment.data.size() > table->initial) { + externalInterface->trap("invalid offset when initializing table"); + } + for (size_t i = 0; i != segment.data.size(); ++i) { + externalInterface->tableStore( + table->name, offset + i, segment.data[i]); + } } } } @@ -2443,7 +2447,7 @@ private: Index index = target.getSingleValue().geti32(); Type type = curr->isReturn ? scope.function->sig.results : curr->type; Flow ret = instance.externalInterface->callTable( - index, curr->sig, arguments, type, *instance.self()); + curr->table, index, curr->sig, arguments, type, *instance.self()); // TODO: make this a proper tail call (return first) if (curr->isReturn) { ret.breakTo = RETURN_FLOW; diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 031589894..8a92c0935 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -123,6 +123,7 @@ class SExpressionWasmBuilder { std::unordered_map<std::string, size_t> typeIndices; std::vector<Name> functionNames; + std::vector<Name> tableNames; std::vector<Name> globalNames; std::vector<Name> eventNames; int functionCounter = 0; @@ -153,6 +154,7 @@ private: UniqueNameMapper nameMapper; Name getFunctionName(Element& s); + Name getTableName(Element& s); Name getGlobalName(Element& s); Name getEventName(Element& s); void parseStart(Element& s) { wasm.addStart(getFunctionName(*s[1])); } @@ -297,7 +299,10 @@ private: void parseGlobal(Element& s, bool preParseImport = false); void parseTable(Element& s, bool preParseImport = false); void parseElem(Element& s); - void parseInnerElem(Element& s, Index i = 1, Expression* offset = nullptr); + void parseInnerElem(Table* table, + Element& s, + Index i = 1, + Expression* offset = nullptr); // Parses something like (func ..), (array ..), (struct) HeapType parseHeapType(Element& s); diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index 4d69eaee3..803fa98aa 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -242,7 +242,9 @@ struct Walker : public VisitorType { self->walkEvent(curr.get()); } } - self->walkTable(&module->table); + for (auto& curr : module->tables) { + self->walkTable(curr.get()); + }; self->walkMemory(&module->memory); } diff --git a/src/wasm.h b/src/wasm.h index 5e4c4668b..7fd284756 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -836,6 +836,7 @@ public: Signature sig; ExpressionList operands; Expression* target; + Name table; bool isReturn = false; void finalize(); @@ -1732,18 +1733,12 @@ public: } }; - // Currently the wasm object always 'has' one Table. It 'exists' if it has - // been defined or imported. The table can exist but be empty and have no - // defined initial or max size. - bool exists = false; Address initial = 0; Address max = kMaxSize; std::vector<Segment> segments; - Table() { name = Name::fromInt(0); } bool hasMax() { return max != kUnlimitedSize; } void clear() { - exists = false; name = ""; initial = 0; max = kMaxSize; @@ -1850,7 +1845,8 @@ public: std::vector<std::unique_ptr<Global>> globals; std::vector<std::unique_ptr<Event>> events; - Table table; + std::vector<std::unique_ptr<Table>> tables; + Memory memory; Name start; @@ -1880,6 +1876,7 @@ private: // exports map is by the *exported* name, which is unique std::unordered_map<Name, Export*> exportsMap; std::unordered_map<Name, Function*> functionsMap; + std::unordered_map<Name, Table*> tablesMap; std::unordered_map<Name, Global*> globalsMap; std::unordered_map<Name, Event*> eventsMap; @@ -1888,10 +1885,12 @@ public: Export* getExport(Name name); Function* getFunction(Name name); + Table* getTable(Name name); Global* getGlobal(Name name); Event* getEvent(Name name); Export* getExportOrNull(Name name); + Table* getTableOrNull(Name name); Function* getFunctionOrNull(Name name); Global* getGlobalOrNull(Name name); Event* getEventOrNull(Name name); @@ -1903,6 +1902,7 @@ public: Export* addExport(std::unique_ptr<Export>&& curr); Function* addFunction(std::unique_ptr<Function>&& curr); + Table* addTable(std::unique_ptr<Table>&& curr); Global* addGlobal(std::unique_ptr<Global>&& curr); Event* addEvent(std::unique_ptr<Event>&& curr); @@ -1910,11 +1910,13 @@ public: void removeExport(Name name); void removeFunction(Name name); + void removeTable(Name name); void removeGlobal(Name name); void removeEvent(Name name); void removeExports(std::function<bool(Export*)> pred); void removeFunctions(std::function<bool(Function*)> pred); + void removeTables(std::function<bool(Table*)> pred); void removeGlobals(std::function<bool(Global*)> pred); void removeEvents(std::function<bool(Event*)> pred); @@ -1939,4 +1941,4 @@ std::ostream& operator<<(std::ostream& o, wasm::StackIR& ir); } // namespace std -#endif // wasm_wasm_h +#endif // wasm_wasm_h
\ No newline at end of file diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 75ca5a2b1..f3ec376d1 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -48,7 +48,7 @@ void WasmBinaryWriter::write() { writeTypes(); writeImports(); writeFunctionSignatures(); - writeFunctionTableDeclaration(); + writeTableDeclarations(); writeMemory(); writeEvents(); writeGlobals(); @@ -285,17 +285,17 @@ void WasmBinaryWriter::writeImports() { wasm->memory.shared, wasm->memory.is64()); } - if (wasm->table.imported()) { + ModuleUtils::iterImportedTables(*wasm, [&](Table* table) { BYN_TRACE("write one table\n"); - writeImportHeader(&wasm->table); + writeImportHeader(table); o << U32LEB(int32_t(ExternalKind::Table)); o << S32LEB(BinaryConsts::EncodedType::funcref); - writeResizableLimits(wasm->table.initial, - wasm->table.max, - wasm->table.hasMax(), + writeResizableLimits(table->initial, + table->max, + table->hasMax(), /*shared=*/false, /*is64*/ false); - } + }); finishSection(start); } @@ -493,6 +493,12 @@ uint32_t WasmBinaryWriter::getFunctionIndex(Name name) const { return it->second; } +uint32_t WasmBinaryWriter::getTableIndex(Name name) const { + auto it = indexes.tableIndexes.find(name); + assert(it != indexes.tableIndexes.end()); + return it->second; +} + uint32_t WasmBinaryWriter::getGlobalIndex(Name name) const { auto it = indexes.globalIndexes.find(name); assert(it != indexes.globalIndexes.end()); @@ -516,38 +522,70 @@ uint32_t WasmBinaryWriter::getTypeIndex(HeapType type) const { return it->second; } -void WasmBinaryWriter::writeFunctionTableDeclaration() { - if (!wasm->table.exists || wasm->table.imported()) { +void WasmBinaryWriter::writeTableDeclarations() { + if (importInfo->getNumDefinedTables() == 0) { + // std::cerr << std::endl << "(WasmBinaryWriter::writeTableDeclarations) No + // defined tables found. skipping" << std::endl; return; } - BYN_TRACE("== writeFunctionTableDeclaration\n"); + BYN_TRACE("== writeTableDeclarations\n"); auto start = startSection(BinaryConsts::Section::Table); - o << U32LEB(1); // Declare 1 table. - o << S32LEB(BinaryConsts::EncodedType::funcref); - writeResizableLimits(wasm->table.initial, - wasm->table.max, - wasm->table.hasMax(), - /*shared=*/false, - /*is64*/ false); + auto num = importInfo->getNumDefinedTables(); + o << U32LEB(num); + ModuleUtils::iterDefinedTables(*wasm, [&](Table* table) { + o << S32LEB(BinaryConsts::EncodedType::funcref); + writeResizableLimits(table->initial, + table->max, + table->hasMax(), + /*shared=*/false, + /*is64*/ false); + }); finishSection(start); } void WasmBinaryWriter::writeTableElements() { - if (!wasm->table.exists || wasm->table.segments.size() == 0) { + size_t elemCount = 0; + for (auto& table : wasm->tables) { + elemCount += table->segments.size(); + } + if (elemCount == 0) { return; } + BYN_TRACE("== writeTableElements\n"); auto start = startSection(BinaryConsts::Section::Element); - - o << U32LEB(wasm->table.segments.size()); - for (auto& segment : wasm->table.segments) { - // Table index; 0 in the MVP (and binaryen IR only has 1 table) - o << U32LEB(0); - writeExpression(segment.offset); - o << int8_t(BinaryConsts::End); - o << U32LEB(segment.data.size()); - for (auto name : segment.data) { - o << U32LEB(getFunctionIndex(name)); + o << U32LEB(elemCount); + + for (auto& table : wasm->tables) { + for (auto& segment : table->segments) { + Index tableIdx = getTableIndex(table->name); + // No support for passive element segments yet as they don't belong to a + // table. + bool isPassive = false; + bool isDeclarative = false; + bool hasTableIndex = tableIdx > 0; + bool usesExpressions = false; + + uint32_t flags = + (isPassive ? BinaryConsts::IsPassive | + (isDeclarative ? BinaryConsts::IsDeclarative : 0) + : (hasTableIndex ? BinaryConsts::HasIndex : 0)) | + (usesExpressions ? BinaryConsts::UsesExpressions : 0); + + o << U32LEB(flags); + if (hasTableIndex) { + o << U32LEB(tableIdx); + } + writeExpression(segment.offset); + o << int8_t(BinaryConsts::End); + if (!usesExpressions && (isPassive || hasTableIndex)) { + // elemKind funcref + o << U32LEB(0); + } + o << U32LEB(segment.data.size()); + for (auto name : segment.data) { + o << U32LEB(getFunctionIndex(name)); + } } } finishSection(start); @@ -648,12 +686,31 @@ void WasmBinaryWriter::writeNames() { } // table names - if (wasm->table.exists && wasm->table.hasExplicitName) { - auto substart = - startSubsection(BinaryConsts::UserSections::Subsection::NameTable); - o << U32LEB(1) << U32LEB(0); // currently exactly 1 table at index 0 - writeEscapedName(wasm->table.name.str); - finishSubsection(substart); + { + std::vector<std::pair<Index, Table*>> tablesWithNames; + Index checked = 0; + auto check = [&](Table* curr) { + if (curr->hasExplicitName) { + tablesWithNames.push_back({checked, curr}); + } + checked++; + }; + ModuleUtils::iterImportedTables(*wasm, check); + ModuleUtils::iterDefinedTables(*wasm, check); + assert(checked == indexes.tableIndexes.size()); + + if (tablesWithNames.size() > 0) { + auto substart = + startSubsection(BinaryConsts::UserSections::Subsection::NameTable); + o << U32LEB(tablesWithNames.size()); + + for (auto& indexedTable : tablesWithNames) { + o << U32LEB(indexedTable.first); + writeEscapedName(indexedTable.second->name.str); + } + + finishSubsection(substart); + } } // memory names @@ -1626,6 +1683,13 @@ Name WasmBinaryBuilder::getFunctionName(Index index) { return wasm.functions[index]->name; } +Name WasmBinaryBuilder::getTableName(Index index) { + if (index >= wasm.tables.size()) { + throwError("invalid table index"); + } + return wasm.tables[index]->name; +} + Name WasmBinaryBuilder::getGlobalName(Index index) { if (index >= wasm.globals.size()) { throwError("invalid global index"); @@ -1694,19 +1758,19 @@ void WasmBinaryBuilder::readImports() { } case ExternalKind::Table: { Name name(std::string("timport$") + std::to_string(tableCounter++)); - wasm.table.module = module; - wasm.table.base = base; - wasm.table.name = name; + auto table = builder.makeTable(name); + table->module = module; + table->base = base; auto elementType = getS32LEB(); WASM_UNUSED(elementType); if (elementType != BinaryConsts::EncodedType::funcref) { throwError("Imported table type is not funcref"); } - wasm.table.exists = true; + bool is_shared; Type indexType; - getResizableLimits(wasm.table.initial, - wasm.table.max, + getResizableLimits(table->initial, + table->max, is_shared, indexType, Table::kUnlimitedSize); @@ -1716,6 +1780,9 @@ void WasmBinaryBuilder::readImports() { if (indexType == Type::i64) { throwError("Tables may not be 64-bit"); } + + tableImports.push_back(table.get()); + wasm.addTable(std::move(table)); break; } case ExternalKind::Memory: { @@ -2305,6 +2372,9 @@ void WasmBinaryBuilder::processNames() { for (auto& global : globals) { wasm.addGlobal(std::move(global)); } + for (auto& table : tables) { + wasm.addTable(std::move(table)); + } // now that we have names, apply things @@ -2320,7 +2390,7 @@ void WasmBinaryBuilder::processNames() { break; } case ExternalKind::Table: - curr->value = wasm.table.name; + curr->value = getTableName(index); break; case ExternalKind::Memory: curr->value = wasm.memory.name; @@ -2351,11 +2421,26 @@ void WasmBinaryBuilder::processNames() { } } - for (auto& pair : functionTable) { - auto i = pair.first; - auto& indices = pair.second; - for (auto j : indices) { - wasm.table.segments[i].data.push_back(getFunctionName(j)); + for (auto& iter : tableRefs) { + size_t index = iter.first; + auto& refs = iter.second; + for (auto* ref : refs) { + if (auto* callIndirect = ref->dynCast<CallIndirect>()) { + callIndirect->table = getTableName(index); + } else { + WASM_UNREACHABLE("Invalid type in table references"); + } + } + } + + for (auto& table_pair : functionTable) { + for (auto& pair : table_pair.second) { + auto i = pair.first; + auto& indices = pair.second; + for (auto j : indices) { + wasm.tables[table_pair.first]->segments[i].data.push_back( + getFunctionName(j)); + } } } @@ -2395,7 +2480,7 @@ void WasmBinaryBuilder::readDataSegments() { std::to_string(flags)); } curr.isPassive = flags & BinaryConsts::IsPassive; - if (flags & BinaryConsts::HasMemIndex) { + if (flags & BinaryConsts::HasIndex) { auto memIndex = getU32LEB(); if (memIndex != 0) { throwError("nonzero memory index"); @@ -2414,29 +2499,25 @@ void WasmBinaryBuilder::readDataSegments() { void WasmBinaryBuilder::readFunctionTableDeclaration() { BYN_TRACE("== readFunctionTableDeclaration\n"); auto numTables = getU32LEB(); - if (numTables != 1) { - throwError("Only 1 table definition allowed in MVP"); - } - if (wasm.table.exists) { - throwError("Table cannot be both imported and defined"); - } - wasm.table.exists = true; - auto elemType = getS32LEB(); - if (elemType != BinaryConsts::EncodedType::funcref) { - throwError("ElementType must be funcref in MVP"); - } - bool is_shared; - Type indexType; - getResizableLimits(wasm.table.initial, - wasm.table.max, - is_shared, - indexType, - Table::kUnlimitedSize); - if (is_shared) { - throwError("Tables may not be shared"); - } - if (indexType == Type::i64) { - throwError("Tables may not be 64-bit"); + + for (size_t i = 0; i < numTables; i++) { + auto elemType = getS32LEB(); + if (elemType != BinaryConsts::EncodedType::funcref) { + throwError("Non-funcref tables not yet supported"); + } + auto table = Builder::makeTable(Name::fromInt(i)); + bool is_shared; + Type indexType; + getResizableLimits( + table->initial, table->max, is_shared, indexType, Table::kUnlimitedSize); + if (is_shared) { + throwError("Tables may not be shared"); + } + if (indexType == Type::i64) { + throwError("Tables may not be 64-bit"); + } + + tables.push_back(std::move(table)); } } @@ -2447,13 +2528,44 @@ void WasmBinaryBuilder::readTableElements() { throwError("Too many segments"); } for (size_t i = 0; i < numSegments; i++) { - auto tableIndex = getU32LEB(); - if (tableIndex != 0) { - throwError("Table elements must refer to table 0 in MVP"); + auto flags = getU32LEB(); + bool isPassive = (flags & BinaryConsts::IsPassive) != 0; + bool hasTableIdx = (flags & BinaryConsts::HasIndex) != 0; + bool usesExpressions = (flags & BinaryConsts::UsesExpressions) != 0; + + if (isPassive) { + throwError("Only active elem segments are supported."); + } + + if (usesExpressions) { + throwError("Only elem segments with function indexes are supported."); } - wasm.table.segments.emplace_back(readExpression()); - auto& indexSegment = functionTable[i]; + Index tableIdx = 0; + if (hasTableIdx) { + tableIdx = getU32LEB(); + } + + auto numTableImports = tableImports.size(); + if (tableIdx < numTableImports) { + auto table = tableImports[tableIdx]; + table->segments.emplace_back(readExpression()); + } else if (tableIdx - numTableImports < tables.size()) { + auto table = tables[tableIdx - numTableImports].get(); + table->segments.emplace_back(readExpression()); + } else { + throwError("Table index out of range."); + } + + if (hasTableIdx) { + auto elemKind = getU32LEB(); + if (elemKind != 0x0) { + throwError("Only funcref elem kinds are valid."); + } + } + + size_t segmentIndex = functionTable[tableIdx].size(); + auto& indexSegment = functionTable[tableIdx][segmentIndex]; auto size = getU32LEB(); for (Index j = 0; j < size; j++) { indexSegment.push_back(getU32LEB()); @@ -2590,10 +2702,21 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) { } else if (nameType == BinaryConsts::UserSections::Subsection::NameTable) { auto num = getU32LEB(); for (size_t i = 0; i < num; i++) { + std::unordered_set<Name> usedNames; auto index = getU32LEB(); auto rawName = getInlineString(); - if (index == 0) { - wasm.table.setExplicitName(escape(rawName)); + auto name = escape(rawName); + // De-duplicate names by appending .1, .2, etc. + for (int i = 1; !usedNames.insert(name).second; ++i) { + name = std::string(escape(rawName).str) + std::string(".") + + std::to_string(i); + } + + auto numTableImports = tableImports.size(); + if (index < numTableImports) { + tableImports[index]->setExplicitName(name); + } else if (index - numTableImports < tables.size()) { + tables[index - numTableImports]->setExplicitName(name); } else { std::cerr << "warning: table index out of bounds in name section, " "table subsection: " @@ -3377,16 +3500,14 @@ void WasmBinaryBuilder::visitCallIndirect(CallIndirect* curr) { BYN_TRACE("zz node: CallIndirect\n"); auto index = getU32LEB(); curr->sig = getSignatureByTypeIndex(index); - auto reserved = getU32LEB(); - if (reserved != 0) { - throwError("Invalid flags field in call_indirect"); - } + Index tableIdx = getU32LEB(); auto num = curr->sig.params.size(); curr->operands.resize(num); curr->target = popNonVoidExpression(); for (size_t i = 0; i < num; i++) { curr->operands[num - i - 1] = popNonVoidExpression(); } + tableRefs[tableIdx].push_back(curr); curr->finalize(); } diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp index 1fd35de29..0aabcc0b4 100644 --- a/src/wasm/wasm-emscripten.cpp +++ b/src/wasm/wasm-emscripten.cpp @@ -477,7 +477,11 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata() { meta << "\n },\n"; } - meta << " \"tableSize\": " << wasm.table.initial.addr << ",\n"; + if (!wasm.tables.empty()) { + meta << " \"tableSize\": " << wasm.tables[0]->initial.addr << ",\n"; + } else { + meta << " \"tableSize\": 0,\n"; + } // Avoid adding duplicate imports to `declares' or `invokeFuncs`. Even // though we might import the same function multiple times (i.e. with diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 0dc121f2c..bcce3993e 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -458,6 +458,19 @@ Name SExpressionWasmBuilder::getFunctionName(Element& s) { } } +Name SExpressionWasmBuilder::getTableName(Element& s) { + if (s.dollared()) { + return s.str(); + } else { + // index + size_t offset = atoi(s.str().c_str()); + if (offset >= tableNames.size()) { + throw ParseException("unknown table in getTableName", s.line, s.col); + } + return tableNames[offset]; + } +} + Name SExpressionWasmBuilder::getGlobalName(Element& s) { if (s.dollared()) { return s.str(); @@ -1828,11 +1841,16 @@ Expression* SExpressionWasmBuilder::makeCall(Element& s, bool isReturn) { Expression* SExpressionWasmBuilder::makeCallIndirect(Element& s, bool isReturn) { - if (!wasm.table.exists) { - throw ParseException("no table", s.line, s.col); + if (wasm.tables.empty()) { + throw ParseException("no tables", s.line, s.col); } Index i = 1; auto ret = allocator.alloc<CallIndirect>(); + if (s[i]->isStr()) { + ret->table = s[i++]->str(); + } else { + ret->table = wasm.tables.front()->name; + } i = parseTypeUse(s, i, ret->sig); parseCallOperands(s, i, s.size() - 1, ret); ret->target = parseExpression(s[s.size() - 1]); @@ -2464,17 +2482,21 @@ void SExpressionWasmBuilder::parseExport(Element& s) { ex->name = s[1]->str(); if (s[2]->isList()) { auto& inner = *s[2]; - ex->value = inner[1]->str(); if (elementStartsWith(inner, FUNC)) { ex->kind = ExternalKind::Function; + ex->value = getFunctionName(*inner[1]); } else if (elementStartsWith(inner, MEMORY)) { ex->kind = ExternalKind::Memory; + ex->value = inner[1]->str(); } else if (elementStartsWith(inner, TABLE)) { ex->kind = ExternalKind::Table; + ex->value = getTableName(*inner[1]); } else if (elementStartsWith(inner, GLOBAL)) { ex->kind = ExternalKind::Global; + ex->value = getGlobalName(*inner[1]); } else if (inner[0]->str() == EVENT) { ex->kind = ExternalKind::Event; + ex->value = getEventName(*inner[1]); } else { throw ParseException("invalid export", inner.line, inner.col); } @@ -2505,10 +2527,6 @@ void SExpressionWasmBuilder::parseImport(Element& s) { wasm.memory.exists = true; } else if (elementStartsWith(*s[3], TABLE)) { kind = ExternalKind::Table; - if (wasm.table.exists) { - throw ParseException("more than one table", s[3]->line, s[3]->col); - } - wasm.table.exists = true; } else if (elementStartsWith(*s[3], GLOBAL)) { kind = ExternalKind::Global; } else if ((*s[3])[0]->str() == EVENT) { @@ -2590,21 +2608,27 @@ void SExpressionWasmBuilder::parseImport(Element& s) { global->mutable_ = mutable_; wasm.addGlobal(global.release()); } else if (kind == ExternalKind::Table) { - wasm.table.setName(name, hasExplicitName); - wasm.table.module = module; - wasm.table.base = base; + auto table = make_unique<Table>(); + table->setName(name, hasExplicitName); + table->module = module; + table->base = base; + tableNames.push_back(name); + if (j < inner.size() - 1) { auto initElem = inner[j++]; - wasm.table.initial = getAddress(initElem); - checkAddress(wasm.table.initial, "excessive table init size", initElem); + table->initial = getAddress(initElem); + checkAddress(table->initial, "excessive table init size", initElem); } if (j < inner.size() - 1) { auto maxElem = inner[j++]; - wasm.table.max = getAddress(maxElem); - checkAddress(wasm.table.max, "excessive table max size", maxElem); + table->max = getAddress(maxElem); + checkAddress(table->max, "excessive table max size", maxElem); } else { - wasm.table.max = Table::kUnlimitedSize; + table->max = Table::kUnlimitedSize; } + + wasm.addTable(std::move(table)); + j++; // funcref // ends with the table element type } else if (kind == ExternalKind::Memory) { @@ -2728,18 +2752,20 @@ void SExpressionWasmBuilder::parseGlobal(Element& s, bool preParseImport) { } void SExpressionWasmBuilder::parseTable(Element& s, bool preParseImport) { - if (wasm.table.exists) { - throw ParseException("more than one table", s.line, s.col); - } - wasm.table.exists = true; + std::unique_ptr<Table> table = make_unique<Table>(); Index i = 1; if (i == s.size()) { return; // empty table in old notation } if (s[i]->dollared()) { - wasm.table.setExplicitName(s[i++]->str()); + table->setExplicitName(s[i++]->str()); + } else { + table->name = Name::fromInt(tableCounter++); } + tableNames.push_back(table->name); + if (i == s.size()) { + wasm.addTable(std::move(table)); return; } Name importModule, importBase; @@ -2748,7 +2774,7 @@ void SExpressionWasmBuilder::parseTable(Element& s, bool preParseImport) { if (elementStartsWith(inner, EXPORT)) { auto ex = make_unique<Export>(); ex->name = inner[1]->str(); - ex->value = wasm.table.name; + ex->value = table->name; ex->kind = ExternalKind::Table; if (wasm.getExportOrNull(ex->name)) { throw ParseException("duplicate export", inner.line, inner.col); @@ -2759,26 +2785,27 @@ void SExpressionWasmBuilder::parseTable(Element& s, bool preParseImport) { if (!preParseImport) { throw ParseException("!preParseImport in table", inner.line, inner.col); } - wasm.table.module = inner[1]->str(); - wasm.table.base = inner[2]->str(); + table->module = inner[1]->str(); + table->base = inner[2]->str(); i++; } else { throw ParseException("invalid table", inner.line, inner.col); } } if (i == s.size()) { + wasm.addTable(std::move(table)); return; } if (!s[i]->dollared()) { if (s[i]->str() == FUNCREF) { // (table type (elem ..)) - parseInnerElem(*s[i + 1]); - if (wasm.table.segments.size() > 0) { - wasm.table.initial = wasm.table.max = - wasm.table.segments[0].data.size(); + parseInnerElem(table.get(), *s[i + 1]); + if (table->segments.size() > 0) { + table->initial = table->max = table->segments[0].data.size(); } else { - wasm.table.initial = wasm.table.max = 0; + table->initial = table->max = 0; } + wasm.addTable(std::move(table)); return; } // first element isn't dollared, and isn't funcref. this could be old syntax @@ -2787,39 +2814,87 @@ void SExpressionWasmBuilder::parseTable(Element& s, bool preParseImport) { if (s[s.size() - 1]->str() == FUNCREF) { // (table initial max? type) if (i < s.size() - 1) { - wasm.table.initial = atoi(s[i++]->c_str()); + table->initial = atoi(s[i++]->c_str()); } if (i < s.size() - 1) { - wasm.table.max = atoi(s[i++]->c_str()); + table->max = atoi(s[i++]->c_str()); } + wasm.addTable(std::move(table)); return; } } // old notation (table func1 func2 ..) - parseInnerElem(s, i); - if (wasm.table.segments.size() > 0) { - wasm.table.initial = wasm.table.max = wasm.table.segments[0].data.size(); + parseInnerElem(table.get(), s, i); + if (table->segments.size() > 0) { + table->initial = table->max = table->segments[0].data.size(); } else { - wasm.table.initial = wasm.table.max = 0; + table->initial = table->max = 0; } + + wasm.addTable(std::move(table)); } +// parses an elem segment +// elem ::= (elem (expr) vec(funcidx)) +// | (elem (offset (expr)) func vec(funcidx)) +// | (elem (table tableidx) (offset (expr)) func vec(funcidx)) +// +// abbreviation: +// (offset (expr)) ≡ (expr) +// (elem (expr) vec(funcidx)) ≡ (elem (table 0) (offset (expr)) func +// vec(funcidx)) +// void SExpressionWasmBuilder::parseElem(Element& s) { Index i = 1; + Table* table = nullptr; + Expression* offset = nullptr; + if (!s[i]->isList()) { - // the table is named - i++; + // optional segment id OR 'declare' OR start of elemList + i += 1; + } + + // old style refers to the pre-reftypes form of (elem (expr) vec(funcidx)) + bool oldStyle = true; + + while (1) { + auto& inner = *s[i++]; + if (elementStartsWith(inner, TABLE)) { + oldStyle = false; + Name tableName = getTableName(*inner[1]); + table = wasm.getTable(tableName); + } else { + if (elementStartsWith(inner, "offset")) { + offset = parseExpression(inner[1]); + } else { + offset = parseExpression(inner); + } + break; + } } - auto* offset = parseExpression(s[i++]); - parseInnerElem(s, i, offset); + + if (!oldStyle) { + if (strcmp(s[i]->c_str(), "func") != 0) { + throw ParseException( + "only the abbreviated form of elemList is supported."); + } + // ignore elemType for now + i += 1; + } + + if (wasm.tables.empty()) { + throw ParseException("elem without table", s.line, s.col); + } else if (!table) { + table = wasm.tables[0].get(); + } + + parseInnerElem(table, s, i, offset); } -void SExpressionWasmBuilder::parseInnerElem(Element& s, +void SExpressionWasmBuilder::parseInnerElem(Table* table, + Element& s, Index i, Expression* offset) { - if (!wasm.table.exists) { - throw ParseException("elem without table", s.line, s.col); - } if (!offset) { offset = allocator.alloc<Const>()->set(Literal(int32_t(0))); } @@ -2827,7 +2902,7 @@ void SExpressionWasmBuilder::parseInnerElem(Element& s, for (; i < s.size(); i++) { segment.data.push_back(getFunctionName(*s[i])); } - wasm.table.segments.push_back(segment); + table->segments.push_back(segment); } HeapType SExpressionWasmBuilder::parseHeapType(Element& s) { diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 95cb4a44c..00b1e5368 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -80,10 +80,11 @@ void BinaryInstWriter::visitCall(Call* curr) { } void BinaryInstWriter::visitCallIndirect(CallIndirect* curr) { + Index tableIdx = parent.getTableIndex(curr->table); + int8_t op = curr->isReturn ? BinaryConsts::RetCallIndirect : BinaryConsts::CallIndirect; - o << op << U32LEB(parent.getTypeIndex(curr->sig)) - << U32LEB(0); // Reserved flags field + o << op << U32LEB(parent.getTypeIndex(curr->sig)) << U32LEB(tableIdx); } void BinaryInstWriter::visitLocalGet(LocalGet* curr) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index fad78cefd..7e05bd375 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -809,6 +809,12 @@ void FunctionValidator::visitCallIndirect(CallIndirect* curr) { Type(Type::i32), curr, "indirect call target must be an i32"); + + if (curr->target->type != Type::unreachable) { + auto* table = getModule()->getTableOrNull(curr->table); + shouldBeTrue(!!table, curr, "call-indirect table must exist"); + } + validateCallParamsAndResult(curr, curr->sig); } @@ -2667,7 +2673,7 @@ static void validateExports(Module& module, ValidationInfo& info) { name, "module global exports must be found"); } else if (exp->kind == ExternalKind::Table) { - info.shouldBeTrue(name == Name("0") || name == module.table.name, + info.shouldBeTrue(module.getTableOrNull(name), name, "module table exports must be found"); } else if (exp->kind == ExternalKind::Memory) { @@ -2776,22 +2782,28 @@ static void validateMemory(Module& module, ValidationInfo& info) { } } -static void validateTable(Module& module, ValidationInfo& info) { - auto& curr = module.table; - for (auto& segment : curr.segments) { - info.shouldBeEqual(segment.offset->type, - Type(Type::i32), - segment.offset, - "segment offset should be i32"); - info.shouldBeTrue( - checkSegmentOffset(segment.offset, - segment.data.size(), - module.table.initial * Table::kPageSize), - segment.offset, - "table segment offset should be reasonable"); - for (auto name : segment.data) { - info.shouldBeTrue( - module.getFunctionOrNull(name), name, "segment name should be valid"); +static void validateTables(Module& module, ValidationInfo& info) { + if (!module.features.hasReferenceTypes()) { + info.shouldBeTrue(module.tables.size() <= 1, + "table", + "Only 1 table definition allowed in MVP (requires " + "--enable-reference-types)"); + } + for (auto& curr : module.tables) { + for (auto& segment : curr->segments) { + info.shouldBeEqual(segment.offset->type, + Type(Type::i32), + segment.offset, + "segment offset should be i32"); + info.shouldBeTrue(checkSegmentOffset(segment.offset, + segment.data.size(), + curr->initial * Table::kPageSize), + segment.offset, + "table segment offset should be reasonable"); + for (auto name : segment.data) { + info.shouldBeTrue( + module.getFunctionOrNull(name), name, "segment name should be valid"); + } } } } @@ -2865,7 +2877,7 @@ bool WasmValidator::validate(Module& module, Flags flags) { validateExports(module, info); validateGlobals(module, info); validateMemory(module, info); - validateTable(module, info); + validateTables(module, info); validateEvents(module, info); validateModule(module, info); validateFeatures(module, info); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index e9d2bf116..7bf9b8604 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1164,6 +1164,10 @@ Function* Module::getFunction(Name name) { return getModuleElement(functionsMap, name, "getFunction"); } +Table* Module::getTable(Name name) { + return getModuleElement(tablesMap, name, "getTable"); +} + Global* Module::getGlobal(Name name) { return getModuleElement(globalsMap, name, "getGlobal"); } @@ -1189,6 +1193,10 @@ Function* Module::getFunctionOrNull(Name name) { return getModuleElementOrNull(functionsMap, name); } +Table* Module::getTableOrNull(Name name) { + return getModuleElementOrNull(tablesMap, name); +} + Global* Module::getGlobalOrNull(Name name) { return getModuleElementOrNull(globalsMap, name); } @@ -1254,6 +1262,10 @@ Function* Module::addFunction(std::unique_ptr<Function>&& curr) { functions, functionsMap, std::move(curr), "addFunction"); } +Table* Module::addTable(std::unique_ptr<Table>&& curr) { + return addModuleElement(tables, tablesMap, std::move(curr), "addTable"); +} + Global* Module::addGlobal(std::unique_ptr<Global>&& curr) { return addModuleElement(globals, globalsMap, std::move(curr), "addGlobal"); } @@ -1281,6 +1293,9 @@ void Module::removeExport(Name name) { void Module::removeFunction(Name name) { removeModuleElement(functions, functionsMap, name); } +void Module::removeTable(Name name) { + removeModuleElement(tables, tablesMap, name); +} void Module::removeGlobal(Name name) { removeModuleElement(globals, globalsMap, name); } @@ -1310,6 +1325,9 @@ void Module::removeExports(std::function<bool(Export*)> pred) { void Module::removeFunctions(std::function<bool(Function*)> pred) { removeModuleElements(functions, functionsMap, pred); } +void Module::removeTables(std::function<bool(Table*)> pred) { + removeModuleElements(tables, tablesMap, pred); +} void Module::removeGlobals(std::function<bool(Global*)> pred) { removeModuleElements(globals, globalsMap, pred); } @@ -1326,6 +1344,10 @@ void Module::updateMaps() { for (auto& curr : exports) { exportsMap[curr->name] = curr.get(); } + tablesMap.clear(); + for (auto& curr : tables) { + tablesMap[curr->name] = curr.get(); + } globalsMap.clear(); for (auto& curr : globals) { globalsMap[curr->name] = curr.get(); diff --git a/src/wasm2js.h b/src/wasm2js.h index db25fd472..de234b90b 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -81,11 +81,11 @@ void sequenceAppend(Ref& ast, Ref extra) { } bool isTableExported(Module& wasm) { - if (!wasm.table.exists || wasm.table.imported()) { + if (wasm.tables.empty() || wasm.tables[0]->imported()) { return false; } for (auto& ex : wasm.exports) { - if (ex->kind == ExternalKind::Table && ex->value == wasm.table.name) { + if (ex->kind == ExternalKind::Table && ex->value == wasm.tables[0]->name) { return true; } } @@ -325,9 +325,11 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { functionsCallableFromOutside.insert(exp->value); } } - for (auto& segment : wasm->table.segments) { - for (auto name : segment.data) { - functionsCallableFromOutside.insert(name); + for (auto& table : wasm->tables) { + for (auto& segment : table->segments) { + for (auto name : segment.data) { + functionsCallableFromOutside.insert(name); + } } } @@ -458,15 +460,15 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { } } - // add table import - if (wasm->table.exists && wasm->table.imported()) { + // add imported tables + ModuleUtils::iterImportedTables(*wasm, [&](Table* table) { Ref theVar = ValueBuilder::makeVar(); asmFunc[3]->push_back(theVar); ValueBuilder::appendToVar( theVar, FUNCTION_TABLE, - ValueBuilder::makeDot(ValueBuilder::makeName(ENV), wasm->table.base)); - } + ValueBuilder::makeDot(ValueBuilder::makeName(ENV), table->base)); + }); // create heaps, etc addBasics(asmFunc[3], wasm); @@ -622,7 +624,7 @@ void Wasm2JSBuilder::addGlobalImport(Ref ast, Global* import) { } void Wasm2JSBuilder::addTable(Ref ast, Module* wasm) { - if (!wasm->table.exists) { + if (wasm->tables.size() == 0) { return; } @@ -631,69 +633,73 @@ void Wasm2JSBuilder::addTable(Ref ast, Module* wasm) { // Emit a simple flat table as a JS array literal. Otherwise, // emit assignments separately for each index. Ref theArray = ValueBuilder::makeArray(); - if (!wasm->table.imported()) { - TableUtils::FlatTable flat(wasm->table); - if (flat.valid) { - Name null("null"); - for (auto& name : flat.names) { - if (name.is()) { - name = fromName(name, NameScope::Top); - } else { - name = null; + for (auto& table : wasm->tables) { + if (!table->imported()) { + TableUtils::FlatTable flat(*table); + if (flat.valid) { + Name null("null"); + for (auto& name : flat.names) { + if (name.is()) { + name = fromName(name, NameScope::Top); + } else { + name = null; + } + ValueBuilder::appendToArray(theArray, ValueBuilder::makeName(name)); } - ValueBuilder::appendToArray(theArray, ValueBuilder::makeName(name)); + } else { + perElementInit = true; + Ref initial = + ValueBuilder::makeInt(Address::address32_t(table->initial.addr)); + theArray = ValueBuilder::makeNew( + ValueBuilder::makeCall(IString("Array"), initial)); } } else { perElementInit = true; - Ref initial = - ValueBuilder::makeInt(Address::address32_t(wasm->table.initial.addr)); - theArray = ValueBuilder::makeNew( - ValueBuilder::makeCall(IString("Array"), initial)); } - } else { - perElementInit = true; - } - if (isTableExported(*wasm)) { - // If the table is exported use a fake WebAssembly.Table object - // We don't handle the case where a table is both imported and exported. - if (wasm->table.imported()) { - Fatal() << "wasm2js doesn't support a table that is both imported and " - "exported\n"; - } - Ref theVar = ValueBuilder::makeVar(); - ast->push_back(theVar); + if (isTableExported(*wasm)) { + // If the table is exported use a fake WebAssembly.Table object + // We don't handle the case where a table is both imported and exported. + if (table->imported()) { + Fatal() << "wasm2js doesn't support a table that is both imported and " + "exported\n"; + } + Ref theVar = ValueBuilder::makeVar(); + ast->push_back(theVar); - Ref table = ValueBuilder::makeCall(IString("Table"), theArray); - ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, table); - } else if (!wasm->table.imported()) { - // Otherwise if the table is internal (neither imported not exported). - // Just use a plain array in this case, avoiding the Table. - Ref theVar = ValueBuilder::makeVar(); - ast->push_back(theVar); - ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, theArray); - } - - if (perElementInit) { - // TODO: optimize for size - for (auto& segment : wasm->table.segments) { - auto offset = segment.offset; - for (Index i = 0; i < segment.data.size(); i++) { - Ref index; - if (auto* c = offset->dynCast<Const>()) { - index = ValueBuilder::makeInt(c->value.geti32() + i); - } else if (auto* get = offset->dynCast<GlobalGet>()) { - index = ValueBuilder::makeBinary( - ValueBuilder::makeName(stringToIString(asmangle(get->name.str))), - PLUS, - ValueBuilder::makeNum(i)); - } else { - WASM_UNREACHABLE("unexpected expr type"); + Ref table = ValueBuilder::makeCall(IString("Table"), theArray); + ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, table); + } else if (!table->imported()) { + // Otherwise if the table is internal (neither imported not exported). + // Just use a plain array in this case, avoiding the Table. + Ref theVar = ValueBuilder::makeVar(); + ast->push_back(theVar); + ValueBuilder::appendToVar(theVar, FUNCTION_TABLE, theArray); + } + + if (perElementInit) { + // TODO: optimize for size + for (auto& segment : table->segments) { + auto offset = segment.offset; + for (Index i = 0; i < segment.data.size(); i++) { + Ref index; + if (auto* c = offset->dynCast<Const>()) { + index = ValueBuilder::makeInt(c->value.geti32() + i); + } else if (auto* get = offset->dynCast<GlobalGet>()) { + index = ValueBuilder::makeBinary( + ValueBuilder::makeName(stringToIString(asmangle(get->name.str))), + PLUS, + ValueBuilder::makeNum(i)); + } else { + WASM_UNREACHABLE("unexpected expr type"); + } + ast->push_back(ValueBuilder::makeStatement(ValueBuilder::makeBinary( + ValueBuilder::makeSub(ValueBuilder::makeName(FUNCTION_TABLE), + index), + SET, + ValueBuilder::makeName( + fromName(segment.data[i], NameScope::Top))))); } - ast->push_back(ValueBuilder::makeStatement(ValueBuilder::makeBinary( - ValueBuilder::makeSub(ValueBuilder::makeName(FUNCTION_TABLE), index), - SET, - ValueBuilder::makeName(fromName(segment.data[i], NameScope::Top))))); } } } @@ -2448,7 +2454,7 @@ void Wasm2JSGlue::emitPre() { if (isTableExported(wasm)) { out << "function Table(ret) {\n"; - if (wasm.table.initial == wasm.table.max) { + if (wasm.tables[0]->initial == wasm.tables[0]->max) { out << " // grow method not included; table is not growable\n"; } else { out << " ret.grow = function(by) {\n" diff --git a/test/binaryen.js/expressions.js b/test/binaryen.js/expressions.js index 3d2a5d138..4c97cdb90 100644 --- a/test/binaryen.js/expressions.js +++ b/test/binaryen.js/expressions.js @@ -295,6 +295,7 @@ console.log("# CallIndirect"); (function testCallIndirect() { const module = new binaryen.Module(); + var table = "0"; var target = module.i32.const(42); var params = binaryen.none; var results = binaryen.none; @@ -302,9 +303,10 @@ console.log("# CallIndirect"); module.i32.const(1), module.i32.const(2) ]; - const theCallIndirect = binaryen.CallIndirect(module.call_indirect(target, operands, params, results)); + const theCallIndirect = binaryen.CallIndirect(module.call_indirect(table, target, operands, params, results)); assert(theCallIndirect instanceof binaryen.CallIndirect); assert(theCallIndirect instanceof binaryen.Expression); + assert(theCallIndirect.table === table); assert(theCallIndirect.target === target); assertDeepEqual(theCallIndirect.operands, operands); assert(theCallIndirect.params === params); @@ -346,7 +348,7 @@ console.log("# CallIndirect"); assert( theCallIndirect.toText() == - "(call_indirect (type $i32_i32_=>_i32)\n (i32.const 7)\n (i32.const 6)\n (i32.const 9000)\n)\n" + "(call_indirect $0 (type $i32_i32_=>_i32)\n (i32.const 7)\n (i32.const 6)\n (i32.const 9000)\n)\n" ); module.dispose(); diff --git a/test/binaryen.js/expressions.js.txt b/test/binaryen.js/expressions.js.txt index 5a0353c27..80a642af8 100644 --- a/test/binaryen.js/expressions.js.txt +++ b/test/binaryen.js/expressions.js.txt @@ -42,7 +42,7 @@ ) # CallIndirect -(call_indirect (type $i32_i32_=>_i32) +(call_indirect $0 (type $i32_i32_=>_i32) (i32.const 7) (i32.const 6) (i32.const 9000) diff --git a/test/binaryen.js/kitchen-sink.js b/test/binaryen.js/kitchen-sink.js index 39154ef7d..327cf0534 100644 --- a/test/binaryen.js/kitchen-sink.js +++ b/test/binaryen.js/kitchen-sink.js @@ -517,7 +517,7 @@ function test_core() { ) ), module.i32.eqz( // check the output type of the call node - module.call_indirect(makeInt32(2449), [ makeInt32(13), makeInt64(37, 0), makeFloat32(1.3), makeFloat64(3.7) ], iIfF, binaryen.i32) + module.call_indirect("0", makeInt32(2449), [ makeInt32(13), makeInt64(37, 0), makeFloat32(1.3), makeFloat64(3.7) ], iIfF, binaryen.i32) ), module.drop(module.local.get(0, binaryen.i32)), module.local.set(0, makeInt32(101)), @@ -532,7 +532,7 @@ function test_core() { module.return(makeInt32(1337)), // Tail Call module.return_call("kitchen()sinker", [ makeInt32(13), makeInt64(37, 0), makeFloat32(1.3), makeFloat64(3.7) ], binaryen.i32), - module.return_call_indirect(makeInt32(2449), [ makeInt32(13), makeInt64(37, 0), makeFloat32(1.3), makeFloat64(3.7) ], iIfF, binaryen.i32), + module.return_call_indirect("0", makeInt32(2449), [ makeInt32(13), makeInt64(37, 0), makeFloat32(1.3), makeFloat64(3.7) ], iIfF, binaryen.i32), // Reference types module.ref.is_null(module.ref.null(binaryen.externref)), @@ -661,9 +661,25 @@ function test_core() { module.addGlobalExport("a-global", "a-global-exp"); module.addEventExport("a-event", "a-event-exp"); - // Function table. One per module + // Tables + module.addTable("t1", 0, 2, []); + var tablePtr = module.getTable("t1"); + assert(tablePtr !== 0); + assert(tablePtr === module.getTableByIndex(0)); + var table = binaryen.getTableInfo(tablePtr); + assert(table.name === "t1"); + assert(table.module === ""); + assert(table.base === ""); + assert(table.initial === 0); + assert(table.max === 2); + + module.removeTable("t1"); + assert(module.getNumTables() === 0); + + // Legacy module.setFunctionTable(1, 0xffffffff, [ binaryen.getFunctionInfo(sinker).name ]); + assert(module.getNumTables() === 1); // Memory. One per module diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 9b45d9a01..4b9de93ab 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -1758,7 +1758,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} ) (drop (i32.eqz - (call_indirect (type $i32_i64_f32_f64_=>_i32) + (call_indirect $0 (type $i32_i64_f32_f64_=>_i32) (i32.const 13) (i64.const 37) (f32.const 1.2999999523162842) @@ -1822,7 +1822,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} (f32.const 1.2999999523162842) (f64.const 3.7) ) - (return_call_indirect (type $i32_i64_f32_f64_=>_i32) + (return_call_indirect $0 (type $i32_i64_f32_f64_=>_i32) (i32.const 13) (i64.const 37) (f32.const 1.2999999523162842) @@ -3620,7 +3620,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} ) (drop (i32.eqz - (call_indirect (type $i32_i64_f32_f64_=>_i32) + (call_indirect $0 (type $i32_i64_f32_f64_=>_i32) (i32.const 13) (i64.const 37) (f32.const 1.2999999523162842) @@ -3684,7 +3684,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} (f32.const 1.2999999523162842) (f64.const 3.7) ) - (return_call_indirect (type $i32_i64_f32_f64_=>_i32) + (return_call_indirect $0 (type $i32_i64_f32_f64_=>_i32) (i32.const 13) (i64.const 37) (f32.const 1.2999999523162842) diff --git a/test/binaryen.js/tail_calls.js b/test/binaryen.js/tail_calls.js index 1b993be91..61fe5d829 100644 --- a/test/binaryen.js/tail_calls.js +++ b/test/binaryen.js/tail_calls.js @@ -18,6 +18,7 @@ var bar = module.addFunction( binaryen.none, [], module.return_call_indirect( + "0", module.i32.const(0), [], binaryen.none, diff --git a/test/ctor-eval/bad-indirect-call.wast.out b/test/ctor-eval/bad-indirect-call.wast.out index 4c33e46ca..6629e6bbb 100644 --- a/test/ctor-eval/bad-indirect-call.wast.out +++ b/test/ctor-eval/bad-indirect-call.wast.out @@ -6,7 +6,7 @@ (elem (i32.const 0) $call-indirect) (export "test1" (func $test1)) (func $test1 - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 1) ) (i32.store8 diff --git a/test/ctor-eval/bad-indirect-call2.wast.out b/test/ctor-eval/bad-indirect-call2.wast.out index ed7546981..9a9186a78 100644 --- a/test/ctor-eval/bad-indirect-call2.wast.out +++ b/test/ctor-eval/bad-indirect-call2.wast.out @@ -7,7 +7,7 @@ (elem (i32.const 0) $_abort $call-indirect) (export "test1" (func $test1)) (func $test1 - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) (i32.store8 diff --git a/test/ctor-eval/bad-indirect-call3.wast.out b/test/ctor-eval/bad-indirect-call3.wast.out index 5edfb58b1..422360974 100644 --- a/test/ctor-eval/bad-indirect-call3.wast.out +++ b/test/ctor-eval/bad-indirect-call3.wast.out @@ -14,7 +14,7 @@ ) ) (func $sig_mismatch - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (ref.null func) (i32.const 0) ) diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index 907ab58c9..b45039c69 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -318,6 +318,8 @@ void test_core() { BinaryenAddEvent( module, "a-event", 0, BinaryenTypeInt32(), BinaryenTypeNone()); + BinaryenAddTable(module, "tab", 0, 100, NULL, 0, makeInt32(module, 0)); + // Exception handling // (try @@ -680,6 +682,7 @@ void test_core() { BinaryenUnary(module, BinaryenEqZInt32(), // check the output type of the call node BinaryenCallIndirect(module, + "tab", makeInt32(module, 2449), callOperands4b, 4, @@ -704,6 +707,7 @@ void test_core() { BinaryenReturnCall( module, "kitchen()sinker", callOperands4, 4, BinaryenTypeInt32()), BinaryenReturnCallIndirect(module, + "tab", makeInt32(module, 2449), callOperands4b, 4, @@ -840,6 +844,7 @@ void test_core() { void test_unreachable() { BinaryenModuleRef module = BinaryenModuleCreate(); BinaryenExpressionRef body = BinaryenCallIndirect(module, + "invalid-table", BinaryenUnreachable(module), NULL, 0, diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 8b06c1109..f03e4ec6c 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -38,7 +38,7 @@ BinaryenFeatureAll: 8191 (memory $0 (shared 1 256)) (data (i32.const 10) "hello, world") (data passive "I am passive") - (table $0 1 1 funcref) + (table $tab 1 1 funcref) (elem (i32.const 0) "$kitchen()sinker") (global $a-global i32 (i32.const 7)) (global $a-mutable-global (mut f32) (f32.const 7.5)) @@ -1666,7 +1666,7 @@ BinaryenFeatureAll: 8191 ) (drop (i32.eqz - (call_indirect (type $i32_i64_f32_f64_=>_i32) + (call_indirect $tab (type $i32_i64_f32_f64_=>_i32) (i32.const 13) (i64.const 37) (f32.const 1.2999999523162842) @@ -1730,7 +1730,7 @@ BinaryenFeatureAll: 8191 (f32.const 1.2999999523162842) (f64.const 3.7) ) - (return_call_indirect (type $i32_i64_f32_f64_=>_i32) + (return_call_indirect $tab (type $i32_i64_f32_f64_=>_i32) (i32.const 13) (i64.const 37) (f32.const 1.2999999523162842) diff --git a/test/example/c-api-multiple-tables.c b/test/example/c-api-multiple-tables.c new file mode 100644 index 000000000..4ba612658 --- /dev/null +++ b/test/example/c-api-multiple-tables.c @@ -0,0 +1,90 @@ +#include <assert.h> +#include <string.h> +#include <binaryen-c.h> + +// "hello world" type example: create a function that adds two i32s and returns +// the result + +int main() { + BinaryenModuleRef module = BinaryenModuleCreate(); + BinaryenModuleSetFeatures(module, BinaryenFeatureReferenceTypes()); + + // Create a function type for i32 (i32, i32) + BinaryenType ii[2] = {BinaryenTypeInt32(), BinaryenTypeInt32()}; + BinaryenType params = BinaryenTypeCreate(ii, 2); + BinaryenType results = BinaryenTypeInt32(); + + assert(BinaryenGetNumTables(module) == 0); + + { + // Get the 0 and 1 arguments, and add them + BinaryenExpressionRef x = BinaryenLocalGet(module, 0, BinaryenTypeInt32()), + y = BinaryenLocalGet(module, 1, BinaryenTypeInt32()); + BinaryenExpressionRef add = + BinaryenBinary(module, BinaryenAddInt32(), x, y); + + // Create the add function + // Note: no additional local variables + // Note: no basic blocks here, we are an AST. The function body is just an + // expression node. + BinaryenFunctionRef adder = + BinaryenAddFunction(module, "adder", params, results, NULL, 0, add); + + const char* funcNames[] = {"adder"}; + BinaryenAddTable(module, + "tab", + 1, + 1, + funcNames, + 1, + BinaryenConst(module, BinaryenLiteralInt32(0))); + assert(BinaryenGetTable(module, "tab") != NULL); + + BinaryenAddTable(module, + "t2", + 1, + 1, + funcNames, + 1, + BinaryenConst(module, BinaryenLiteralInt32(0))); + BinaryenTableRef t2 = BinaryenGetTableByIndex(module, 1); + assert(t2 != NULL); + + assert(strcmp(BinaryenTableGetName(t2), "t2") == 0); + assert(BinaryenTableGetInitial(t2) == 1); + assert(BinaryenTableHasMax(t2) == 1); + assert(BinaryenTableGetMax(t2) == 1); + assert(strcmp(BinaryenTableImportGetModule(t2), "") == 0); + assert(strcmp(BinaryenTableImportGetBase(t2), "") == 0); + + assert(BinaryenGetNumTables(module) == 2); + } + + { + // Get the 0 and 1 arguments, and add them + BinaryenExpressionRef operands[] = { + BinaryenLocalGet(module, 0, BinaryenTypeInt32()), + BinaryenLocalGet(module, 1, BinaryenTypeInt32())}; + + BinaryenExpressionRef add_indirect = + BinaryenCallIndirect(module, + "tab", + BinaryenConst(module, BinaryenLiteralInt32(0)), + operands, + 2, + params, + results); + BinaryenCallIndirectSetTable(add_indirect, "t2"); + + BinaryenFunctionRef call_adder_indirectly = BinaryenAddFunction( + module, "call_adder_indirect", params, results, NULL, 0, add_indirect); + } + + // Print it out + BinaryenModulePrint(module); + + // Clean up the module, which owns all the objects we created above + BinaryenModuleDispose(module); + + return 0; +} diff --git a/test/example/c-api-multiple-tables.txt b/test/example/c-api-multiple-tables.txt new file mode 100644 index 000000000..c0f2da53b --- /dev/null +++ b/test/example/c-api-multiple-tables.txt @@ -0,0 +1,20 @@ +(module + (type $i32_i32_=>_i32 (func (param i32 i32) (result i32))) + (table $tab 1 1 funcref) + (elem (table $tab) (i32.const 0) func $adder) + (table $t2 1 1 funcref) + (elem (table $t2) (i32.const 0) func $adder) + (func $adder (param $0 i32) (param $1 i32) (result i32) + (i32.add + (local.get $0) + (local.get $1) + ) + ) + (func $call_adder_indirect (param $0 i32) (param $1 i32) (result i32) + (call_indirect $t2 (type $i32_i32_=>_i32) + (local.get $0) + (local.get $1) + (i32.const 0) + ) + ) +) diff --git a/test/example/module-splitting.txt b/test/example/module-splitting.txt index 32d579c2c..93a766652 100644 --- a/test/example/module-splitting.txt +++ b/test/example/module-splitting.txt @@ -311,7 +311,7 @@ After: (export "foo" (func $foo)) (export "%table" (table $0)) (func $foo (param $0 i32) (result i32) - (call_indirect (type $i32_=>_i32) + (call_indirect $0 (type $i32_=>_i32) (local.get $0) (i32.const 0) ) @@ -377,7 +377,7 @@ After: (export "foo" (func $foo)) (export "%table" (table $table)) (func $foo (param $0 i32) (result i32) - (call_indirect (type $i32_=>_i32) + (call_indirect $table (type $i32_=>_i32) (local.get $0) (i32.const 42) ) @@ -417,7 +417,7 @@ After: (export "%table" (table $table)) (export "%global" (global $base)) (func $foo (param $0 i32) (result i32) - (call_indirect (type $i32_=>_i32) + (call_indirect $table (type $i32_=>_i32) (local.get $0) (global.get $base) ) @@ -467,7 +467,7 @@ After: (nop) ) (func $foo (param $0 i32) (result i32) - (call_indirect (type $i32_=>_i32) + (call_indirect $table (type $i32_=>_i32) (local.get $0) (i32.add (global.get $base) @@ -564,7 +564,7 @@ After: (elem (i32.const 0) $placeholder_0) (export "%table" (table $0)) (func $foo - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) ) @@ -631,7 +631,7 @@ After: (nop) ) (func $bar - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) ) @@ -923,7 +923,7 @@ After: (export "%foo" (func $foo)) (export "%table" (table $table)) (func $foo (param $0 i32) (result i32) - (call_indirect (type $i32_=>_i32) + (call_indirect $table (type $i32_=>_i32) (i32.const 0) (i32.const 1) ) @@ -963,7 +963,7 @@ After: (export "foo2" (func $foo)) (export "%table" (table $0)) (func $foo - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) ) diff --git a/test/multi-table.minified.txt b/test/multi-table.minified.txt new file mode 100644 index 000000000..6222ea303 --- /dev/null +++ b/test/multi-table.minified.txt @@ -0,0 +1 @@ +(module(type $none_=>_none (func))(import "a" "b" (table $t1 1 10 funcref))(elem (table $t1) (i32.const 0) func $f)(table $t2 3 3 funcref)(elem (table $t2) (i32.const 0) func $f)(elem (table $t2) (i32.const 1) func $f $g)(func $f(nop))(func $g(nop)))
\ No newline at end of file diff --git a/test/multi-table.wast b/test/multi-table.wast new file mode 100644 index 000000000..07c1e0077 --- /dev/null +++ b/test/multi-table.wast @@ -0,0 +1,14 @@ +(module + (import "a" "b" (table $t1 1 10 funcref)) + (table $t2 3 3 funcref) + + ;; add to $t1 + (elem (i32.const 0) $f) + + ;; add to $t2 + (elem (table $t2) (i32.const 0) func $f) + (elem (table $t2) (offset (i32.const 1)) func $f $g) + + (func $f) + (func $g) +)
\ No newline at end of file diff --git a/test/multi-table.wast.from-wast b/test/multi-table.wast.from-wast new file mode 100644 index 000000000..9d1842b70 --- /dev/null +++ b/test/multi-table.wast.from-wast @@ -0,0 +1,14 @@ +(module + (type $none_=>_none (func)) + (import "a" "b" (table $t1 1 10 funcref)) + (elem (table $t1) (i32.const 0) func $f) + (table $t2 3 3 funcref) + (elem (table $t2) (i32.const 0) func $f) + (elem (table $t2) (i32.const 1) func $f $g) + (func $f + (nop) + ) + (func $g + (nop) + ) +) diff --git a/test/multi-table.wast.fromBinary b/test/multi-table.wast.fromBinary new file mode 100644 index 000000000..1bfd568da --- /dev/null +++ b/test/multi-table.wast.fromBinary @@ -0,0 +1,15 @@ +(module + (type $none_=>_none (func)) + (import "a" "b" (table $t1 1 10 funcref)) + (elem (table $t1) (i32.const 0) func $f) + (table $t2 3 3 funcref) + (elem (table $t2) (i32.const 0) func $f) + (elem (table $t2) (i32.const 1) func $f $g) + (func $f + (nop) + ) + (func $g + (nop) + ) +) + diff --git a/test/multi-table.wast.fromBinary.noDebugInfo b/test/multi-table.wast.fromBinary.noDebugInfo new file mode 100644 index 000000000..00c3a64cb --- /dev/null +++ b/test/multi-table.wast.fromBinary.noDebugInfo @@ -0,0 +1,15 @@ +(module + (type $none_=>_none (func)) + (import "a" "b" (table $timport$0 1 10 funcref)) + (elem (table $timport$0) (i32.const 0) func $0) + (table $0 3 3 funcref) + (elem (table $0) (i32.const 0) func $0) + (elem (table $0) (i32.const 1) func $0 $1) + (func $0 + (nop) + ) + (func $1 + (nop) + ) +) + diff --git a/test/newsyntax.wast.from-wast b/test/newsyntax.wast.from-wast index edf54b5b9..7b8e2b2a6 100644 --- a/test/newsyntax.wast.from-wast +++ b/test/newsyntax.wast.from-wast @@ -5,13 +5,13 @@ (export "call_indirect" (func $0)) (func $0 (drop - (call_indirect (type $i32_f64_=>_i32) + (call_indirect $timport$0 (type $i32_f64_=>_i32) (i32.const 10) (f64.const 20) (i32.const 30) ) ) - (call_indirect (type $none_=>_none) + (call_indirect $timport$0 (type $none_=>_none) (i32.const 1) ) ) diff --git a/test/newsyntax.wast.fromBinary b/test/newsyntax.wast.fromBinary index ea3b1ac9a..5309fb3eb 100644 --- a/test/newsyntax.wast.fromBinary +++ b/test/newsyntax.wast.fromBinary @@ -5,13 +5,13 @@ (export "call_indirect" (func $0)) (func $0 (drop - (call_indirect (type $i32_f64_=>_i32) + (call_indirect $timport$0 (type $i32_f64_=>_i32) (i32.const 10) (f64.const 20) (i32.const 30) ) ) - (call_indirect (type $none_=>_none) + (call_indirect $timport$0 (type $none_=>_none) (i32.const 1) ) ) diff --git a/test/newsyntax.wast.fromBinary.noDebugInfo b/test/newsyntax.wast.fromBinary.noDebugInfo index ea3b1ac9a..5309fb3eb 100644 --- a/test/newsyntax.wast.fromBinary.noDebugInfo +++ b/test/newsyntax.wast.fromBinary.noDebugInfo @@ -5,13 +5,13 @@ (export "call_indirect" (func $0)) (func $0 (drop - (call_indirect (type $i32_f64_=>_i32) + (call_indirect $timport$0 (type $i32_f64_=>_i32) (i32.const 10) (f64.const 20) (i32.const 30) ) ) - (call_indirect (type $none_=>_none) + (call_indirect $timport$0 (type $none_=>_none) (i32.const 1) ) ) diff --git a/test/passes/O3_low-memory-unused_metrics.txt b/test/passes/O3_low-memory-unused_metrics.txt index 1ec56ef2c..db50f97a8 100644 --- a/test/passes/O3_low-memory-unused_metrics.txt +++ b/test/passes/O3_low-memory-unused_metrics.txt @@ -6,6 +6,7 @@ total [imports] : 10 [memory-data] : 0 [table-data] : 0 + [tables] : 0 [total] : 1964 [vars] : 9 Binary : 240 diff --git a/test/passes/converge_O3_metrics.bin.txt b/test/passes/converge_O3_metrics.bin.txt index 1764458f6..2df4a4b0b 100644 --- a/test/passes/converge_O3_metrics.bin.txt +++ b/test/passes/converge_O3_metrics.bin.txt @@ -6,6 +6,7 @@ total [imports] : 3 [memory-data] : 28 [table-data] : 429 + [tables] : 0 [total] : 129 [vars] : 4 Binary : 12 @@ -247,6 +248,7 @@ total [imports] : 3 [memory-data] : 28 [table-data] : 429 + [tables] : 0 [total] : 129 [vars] : 4 Binary : 12 diff --git a/test/passes/dae_all-features.txt b/test/passes/dae_all-features.txt index 9a1a4d89a..1c8c9f799 100644 --- a/test/passes/dae_all-features.txt +++ b/test/passes/dae_all-features.txt @@ -268,7 +268,7 @@ (i32.const 42) ) (drop - (return_call_indirect (type $none_=>_i32) + (return_call_indirect $0 (type $none_=>_i32) (i32.const 0) ) ) diff --git a/test/passes/directize_all-features.txt b/test/passes/directize_all-features.txt index 4b6934f06..6b3d10b53 100644 --- a/test/passes/directize_all-features.txt +++ b/test/passes/directize_all-features.txt @@ -14,8 +14,29 @@ ) (module (type $i32_i32_=>_none (func (param i32 i32))) + (type $i32_=>_i32 (func (param i32) (result i32))) (table $0 5 5 funcref) - (elem (i32.const 4) $foo) + (elem (table $0) (i32.const 1) func $dummy) + (table $1 5 5 funcref) + (elem (table $1) (i32.const 1) func $f) + (func $dummy (param $0 i32) (result i32) + (local.get $0) + ) + (func $f (param $0 i32) (param $1 i32) + (unreachable) + ) + (func $g (param $x i32) (param $y i32) + (call $f + (local.get $x) + (local.get $y) + ) + ) +) +(module + (type $i32_i32_=>_none (func (param i32 i32))) + (table $0 5 5 funcref) + (table $1 5 5 funcref) + (elem (table $1) (i32.const 4) func $foo) (func $foo (param $0 i32) (param $1 i32) (unreachable) ) @@ -43,7 +64,9 @@ (module (type $i32_i32_=>_none (func (param i32 i32))) (table $0 5 5 funcref) - (elem (i32.const 0) $foo $foo $foo $foo $foo) + (elem (table $0) (i32.const 0) func $foo $foo $foo $foo $foo) + (table $1 5 5 funcref) + (elem (table $1) (i32.const 0) func $foo $foo $foo $foo $foo) (func $foo (param $0 i32) (param $1 i32) (unreachable) ) @@ -62,7 +85,7 @@ (unreachable) ) (func $bar (param $x i32) (param $y i32) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $table (type $i32_i32_=>_none) (local.get $x) (local.get $y) (i32.const 1) @@ -78,7 +101,7 @@ (unreachable) ) (func $bar (param $x i32) (param $y i32) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (local.get $x) (local.get $y) (i32.const 1) @@ -94,7 +117,24 @@ (unreachable) ) (func $bar (param $x i32) (param $y i32) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) + (local.get $x) + (local.get $y) + (i32.const 1) + ) + ) +) +(module + (type $i32_i32_=>_none (func (param i32 i32))) + (table $0 5 5 funcref) + (table $1 5 5 funcref) + (elem (table $1) (global.get $g) func $foo) + (global $g (mut i32) (i32.const 1)) + (func $foo (param $0 i32) (param $1 i32) + (unreachable) + ) + (func $bar (param $x i32) (param $y i32) + (call_indirect $1 (type $i32_i32_=>_none) (local.get $x) (local.get $y) (i32.const 1) @@ -110,7 +150,7 @@ (unreachable) ) (func $bar (param $x i32) (param $y i32) (param $z i32) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (local.get $x) (local.get $y) (local.get $z) diff --git a/test/passes/directize_all-features.wast b/test/passes/directize_all-features.wast index b113af396..d07b2d191 100644 --- a/test/passes/directize_all-features.wast +++ b/test/passes/directize_all-features.wast @@ -13,16 +13,37 @@ ) ) ) +(module + (type $ii (func (param i32 i32))) + (table $0 5 5 funcref) + (table $1 5 5 funcref) + (elem (table $0) (i32.const 1) func $dummy) + (elem (table $1) (i32.const 1) func $f) + (func $dummy (param i32) (result i32) + (local.get 0) + ) + (func $f (param i32) (param i32) + (unreachable) + ) + (func $g (param $x i32) (param $y i32) + (call_indirect $1 (type $ii) + (local.get $x) + (local.get $y) + (i32.const 1) + ) + ) +) ;; at table edges (module (type $ii (func (param i32 i32))) (table $0 5 5 funcref) - (elem (i32.const 4) $foo) + (table $1 5 5 funcref) + (elem (table $1) (i32.const 4) func $foo) (func $foo (param i32) (param i32) (unreachable) ) (func $bar (param $x i32) (param $y i32) - (call_indirect (type $ii) + (call_indirect $1 (type $ii) (local.get $x) (local.get $y) (i32.const 4) @@ -47,12 +68,14 @@ (module (type $ii (func (param i32 i32))) (table $0 5 5 funcref) + (table $1 5 5 funcref) (elem (i32.const 0) $foo $foo $foo $foo $foo) + (elem (table $1) (i32.const 0) func $foo $foo $foo $foo $foo) (func $foo (param i32) (param i32) (unreachable) ) (func $bar (param $x i32) (param $y i32) - (call_indirect (type $ii) + (call_indirect $1 (type $ii) (local.get $x) (local.get $y) (i32.const 2) @@ -109,6 +132,23 @@ ) ) ) +(module + (type $ii (func (param i32 i32))) + (table $0 5 5 funcref) + (table $1 5 5 funcref) + (global $g (mut i32) (i32.const 1)) + (elem (table $1) (global.get $g) func $foo) + (func $foo (param i32) (param i32) + (unreachable) + ) + (func $bar (param $x i32) (param $y i32) + (call_indirect $1 (type $ii) + (local.get $x) + (local.get $y) + (i32.const 1) + ) + ) +) ;; non-constant call index (module (type $ii (func (param i32 i32))) diff --git a/test/passes/flatten_all-features.txt b/test/passes/flatten_all-features.txt index e3d2ca55e..192c9ad82 100644 --- a/test/passes/flatten_all-features.txt +++ b/test/passes/flatten_all-features.txt @@ -1050,7 +1050,7 @@ (i32.const -1) (block (unreachable) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (i32.const 123) (i32.const 456) (unreachable) @@ -1062,7 +1062,7 @@ (i32.const -2) (block (unreachable) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (i32.const 139) (unreachable) (i32.const 0) @@ -1075,7 +1075,7 @@ (block (unreachable) (unreachable) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (i32.const 246) (unreachable) (unreachable) @@ -1089,7 +1089,7 @@ (unreachable) (unreachable) (unreachable) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (unreachable) (unreachable) (unreachable) diff --git a/test/passes/func-metrics.txt b/test/passes/func-metrics.txt index 117d9baf7..75ff9dcf2 100644 --- a/test/passes/func-metrics.txt +++ b/test/passes/func-metrics.txt @@ -6,6 +6,7 @@ global [imports] : 0 [memory-data] : 9 [table-data] : 3 + [tables] : 1 [total] : 3 Const : 3 func: empty @@ -95,6 +96,7 @@ global [funcs] : 0 [globals] : 0 [imports] : 0 + [tables] : 0 [total] : 0 (module ) @@ -104,6 +106,7 @@ global [funcs] : 3 [globals] : 0 [imports] : 1 + [tables] : 0 [total] : 0 func: func_a [binary-bytes] : 16 @@ -179,6 +182,7 @@ global [funcs] : 1 [globals] : 0 [imports] : 1 + [tables] : 0 [total] : 0 func: func_a [binary-bytes] : 12 @@ -211,6 +215,7 @@ global [funcs] : 1 [globals] : 0 [imports] : 1 + [tables] : 0 [total] : 0 func: func_a [binary-bytes] : 12 @@ -239,6 +244,7 @@ global [funcs] : 1 [globals] : 1 [imports] : 1 + [tables] : 0 [total] : 1 GlobalGet : 1 func: 0 diff --git a/test/passes/fuzz_metrics_noprint.bin.txt b/test/passes/fuzz_metrics_noprint.bin.txt index fd335ce6f..d8c708f3c 100644 --- a/test/passes/fuzz_metrics_noprint.bin.txt +++ b/test/passes/fuzz_metrics_noprint.bin.txt @@ -6,6 +6,7 @@ total [imports] : 4 [memory-data] : 4 [table-data] : 16 + [tables] : 1 [total] : 9206 [vars] : 186 Binary : 715 diff --git a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_optimize-level=3.txt b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_optimize-level=3.txt index c3dfec2dc..9a0ec41ec 100644 --- a/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_optimize-level=3.txt +++ b/test/passes/generate-stack-ir_optimize-stack-ir_print-stack-ir_optimize-level=3.txt @@ -276,7 +276,7 @@ i32.and i32.const 8 i32.add - call_indirect (type $f32_=>_none) + call_indirect $0 (type $f32_=>_none) ) (func $cneg (param $x f32) local.get $x @@ -285,7 +285,7 @@ i32.and i32.const 8 i32.add - call_indirect (type $f32_=>_none) + call_indirect $0 (type $f32_=>_none) ) (func $___syscall_ret (local $$0 i32) diff --git a/test/passes/metrics_all-features.txt b/test/passes/metrics_all-features.txt index 79381a62d..0a22ac945 100644 --- a/test/passes/metrics_all-features.txt +++ b/test/passes/metrics_all-features.txt @@ -6,6 +6,7 @@ total [imports] : 0 [memory-data] : 9 [table-data] : 3 + [tables] : 1 [total] : 27 [vars] : 1 Binary : 1 @@ -69,6 +70,7 @@ total [funcs] : 0 [globals] : 0 [imports] : 0 + [tables] : 0 [total] : 0 [vars] : 0 (module diff --git a/test/passes/metrics_strip-debug_metrics.bin.txt b/test/passes/metrics_strip-debug_metrics.bin.txt index f873992bb..5df5c0772 100644 --- a/test/passes/metrics_strip-debug_metrics.bin.txt +++ b/test/passes/metrics_strip-debug_metrics.bin.txt @@ -4,6 +4,7 @@ total [funcs] : 1 [globals] : 0 [imports] : 0 + [tables] : 0 [total] : 1 [vars] : 0 Nop : 1 @@ -13,6 +14,7 @@ total [funcs] : 1 [globals] : 0 [imports] : 0 + [tables] : 0 [total] : 1 [vars] : 0 Nop : 1 diff --git a/test/passes/metrics_strip-producers_metrics.bin.txt b/test/passes/metrics_strip-producers_metrics.bin.txt index f20d10e0b..a7ae38f9a 100644 --- a/test/passes/metrics_strip-producers_metrics.bin.txt +++ b/test/passes/metrics_strip-producers_metrics.bin.txt @@ -4,6 +4,7 @@ total [funcs] : 1 [globals] : 0 [imports] : 0 + [tables] : 0 [total] : 1 [vars] : 0 Nop : 1 @@ -13,6 +14,7 @@ total [funcs] : 1 [globals] : 0 [imports] : 0 + [tables] : 0 [total] : 1 [vars] : 0 Nop : 1 diff --git a/test/passes/print_g_metrics.bin.txt b/test/passes/print_g_metrics.bin.txt index 57a1d2745..ca0eeaa58 100644 --- a/test/passes/print_g_metrics.bin.txt +++ b/test/passes/print_g_metrics.bin.txt @@ -70,6 +70,7 @@ total [funcs] : 3 [globals] : 1 [imports] : 0 + [tables] : 0 [total] : 37 [vars] : 0 Binary : 11 diff --git a/test/passes/remove-unused-module-elements_all-features.txt b/test/passes/remove-unused-module-elements_all-features.txt index 0873cfee4..22751f9f5 100644 --- a/test/passes/remove-unused-module-elements_all-features.txt +++ b/test/passes/remove-unused-module-elements_all-features.txt @@ -36,40 +36,40 @@ (call $called3) ) (func $other1 (param $0 i32) - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) - (call_indirect (type $i32_=>_none) + (call_indirect $0 (type $i32_=>_none) (i32.const 0) (i32.const 0) ) - (call_indirect (type $i32_=>_none) + (call_indirect $0 (type $i32_=>_none) (i32.const 0) (i32.const 0) ) (drop - (call_indirect (type $i32_=>_i32) + (call_indirect $0 (type $i32_=>_i32) (i32.const 0) (i32.const 0) ) ) (drop - (call_indirect (type $i32_=>_i32) + (call_indirect $0 (type $i32_=>_i32) (i32.const 0) (i32.const 0) ) ) (drop - (call_indirect (type $i32_=>_i32) + (call_indirect $0 (type $i32_=>_i32) (i32.const 0) (i32.const 0) ) @@ -84,10 +84,22 @@ (module ) (module + (type $none_=>_none (func)) + (import "env" "table2" (table $1 1 1 funcref)) + (elem (i32.const 0) $f) + (func $f + (nop) + ) +) +(module +) +(module +) +(module (import "env" "memory" (memory $0 256)) (import "env" "table" (table $timport$0 1 funcref)) (export "mem" (memory $0)) - (export "tab" (table $0)) + (export "tab" (table $timport$0)) ) (module (type $none_=>_none (func)) @@ -110,7 +122,7 @@ (i32.const 0) ) ) - (call_indirect (type $none_=>_none) + (call_indirect $timport$0 (type $none_=>_none) (i32.const 0) ) ) diff --git a/test/passes/remove-unused-module-elements_all-features.wast b/test/passes/remove-unused-module-elements_all-features.wast index c70dc1495..8837f91a9 100644 --- a/test/passes/remove-unused-module-elements_all-features.wast +++ b/test/passes/remove-unused-module-elements_all-features.wast @@ -73,10 +73,31 @@ (import "env" "memory" (memory $0 256)) (import "env" "table" (table 0 funcref)) ) +(module ;; remove all tables and the memory + (import "env" "memory" (memory $0 256)) + (import "env" "table" (table 0 funcref)) + (import "env" "table2" (table $1 1 2 funcref)) + (elem (table $1) (offset (i32.const 0)) func) + (elem (table $1) (offset (i32.const 1)) func) +) +(module ;; remove the first table and memory, but not the second one + (import "env" "memory" (memory $0 256)) + (import "env" "table" (table 0 funcref)) + (import "env" "table2" (table $1 1 1 funcref)) + (elem (table $1) (offset (i32.const 0)) func) + (elem (table $1) (offset (i32.const 0)) func $f) + (func $f) +) (module ;; also when not imported (memory 256) (table 1 funcref) ) +(module ;; also with multiple tables + (memory 256) + (table $0 1 funcref) + (table $1 1 funcref) + (elem (table $1) (i32.const 0) func) +) (module ;; but not when exported (import "env" "memory" (memory $0 256)) (import "env" "table" (table 1 funcref)) diff --git a/test/passes/remove-unused-names_merge-blocks_all-features.txt b/test/passes/remove-unused-names_merge-blocks_all-features.txt index 4efda13ca..774715e02 100644 --- a/test/passes/remove-unused-names_merge-blocks_all-features.txt +++ b/test/passes/remove-unused-names_merge-blocks_all-features.txt @@ -694,12 +694,12 @@ (drop (i32.const 50) ) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (i32.const 20) (i32.const 40) (i32.const 60) ) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (unreachable) (block (result i32) (drop @@ -717,7 +717,7 @@ (drop (i32.const 31) ) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (i32.const 41) (unreachable) (block (result i32) @@ -733,7 +733,7 @@ (drop (i32.const 52) ) - (call_indirect (type $i32_i32_=>_none) + (call_indirect $0 (type $i32_i32_=>_none) (i32.const 42) (i32.const 62) (unreachable) diff --git a/test/passes/remove-unused-nonfunction-module-elements_all-features.txt b/test/passes/remove-unused-nonfunction-module-elements_all-features.txt index 5a5406f78..76d388b23 100644 --- a/test/passes/remove-unused-nonfunction-module-elements_all-features.txt +++ b/test/passes/remove-unused-nonfunction-module-elements_all-features.txt @@ -51,40 +51,40 @@ (call $remove3) ) (func $other1 (param $0 i32) - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) - (call_indirect (type $none_=>_none) + (call_indirect $0 (type $none_=>_none) (i32.const 0) ) - (call_indirect (type $i32_=>_none) + (call_indirect $0 (type $i32_=>_none) (i32.const 0) (i32.const 0) ) - (call_indirect (type $i32_=>_none) + (call_indirect $0 (type $i32_=>_none) (i32.const 0) (i32.const 0) ) (drop - (call_indirect (type $i32_=>_i32) + (call_indirect $0 (type $i32_=>_i32) (i32.const 0) (i32.const 0) ) ) (drop - (call_indirect (type $i32_=>_i32) + (call_indirect $0 (type $i32_=>_i32) (i32.const 0) (i32.const 0) ) ) (drop - (call_indirect (type $i32_=>_i32) + (call_indirect $0 (type $i32_=>_i32) (i32.const 0) (i32.const 0) ) @@ -102,7 +102,7 @@ (import "env" "memory" (memory $0 256)) (import "env" "table" (table $timport$0 1 funcref)) (export "mem" (memory $0)) - (export "tab" (table $0)) + (export "tab" (table $timport$0)) ) (module (type $none_=>_none (func)) @@ -125,7 +125,7 @@ (i32.const 0) ) ) - (call_indirect (type $none_=>_none) + (call_indirect $timport$0 (type $none_=>_none) (i32.const 0) ) ) @@ -306,7 +306,7 @@ (f64.const 1) (f64.const 1) ) - (call_indirect (type $f64_=>_f64) + (call_indirect $0 (type $f64_=>_f64) (f64.const 1) (i32.const 0) ) diff --git a/test/passes/too_much_for_liveness.bin.txt b/test/passes/too_much_for_liveness.bin.txt index 1d0e2df2d..6a9afb9bd 100644 --- a/test/passes/too_much_for_liveness.bin.txt +++ b/test/passes/too_much_for_liveness.bin.txt @@ -4,6 +4,7 @@ total [funcs] : 1 [globals] : 0 [imports] : 0 + [tables] : 0 [total] : 4 [vars] : 65536 Block : 1 @@ -16,6 +17,7 @@ total [funcs] : 1 [globals] : 0 [imports] : 0 + [tables] : 0 [total] : 4 [vars] : 65536 Block : 1 diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt index 158c9b49a..36ed6b87e 100644 --- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt +++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt @@ -6,6 +6,7 @@ total [imports] : 5 [memory-data] : 22 [table-data] : 1 + [tables] : 1 [total] : 850 [vars] : 29 AtomicFence : 1 diff --git a/test/polymorphic_stack.wast.from-wast b/test/polymorphic_stack.wast.from-wast index 18b991df3..be9cb82bd 100644 --- a/test/polymorphic_stack.wast.from-wast +++ b/test/polymorphic_stack.wast.from-wast @@ -36,7 +36,7 @@ ) (drop (i64.eqz - (call_indirect (type $i32_=>_i32) + (call_indirect $timport$0 (type $i32_=>_i32) (unreachable) (unreachable) ) diff --git a/test/reference-types.wast.from-wast b/test/reference-types.wast.from-wast index 3d8e70c23..29aab9bea 100644 --- a/test/reference-types.wast.from-wast +++ b/test/reference-types.wast.from-wast @@ -191,71 +191,71 @@ (call $take_anyref (ref.func $foo) ) - (call_indirect (type $externref_=>_none) + (call_indirect $0 (type $externref_=>_none) (local.get $local_externref) (i32.const 0) ) - (call_indirect (type $externref_=>_none) + (call_indirect $0 (type $externref_=>_none) (global.get $global_externref) (i32.const 0) ) - (call_indirect (type $externref_=>_none) + (call_indirect $0 (type $externref_=>_none) (ref.null extern) (i32.const 0) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (local.get $local_funcref) (i32.const 1) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (global.get $global_funcref) (i32.const 1) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (ref.null func) (i32.const 1) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (ref.func $foo) (i32.const 1) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (local.get $local_anyref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (global.get $global_anyref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.null any) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (local.get $local_externref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (global.get $global_externref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.null extern) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (local.get $local_funcref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (global.get $global_funcref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.null func) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.func $foo) (i32.const 3) ) diff --git a/test/reference-types.wast.fromBinary b/test/reference-types.wast.fromBinary index ae54cfcd8..5e51309ec 100644 --- a/test/reference-types.wast.fromBinary +++ b/test/reference-types.wast.fromBinary @@ -191,71 +191,71 @@ (call $take_anyref (ref.func $foo) ) - (call_indirect (type $externref_=>_none) + (call_indirect $0 (type $externref_=>_none) (local.get $local_funcref) (i32.const 0) ) - (call_indirect (type $externref_=>_none) + (call_indirect $0 (type $externref_=>_none) (global.get $global_externref) (i32.const 0) ) - (call_indirect (type $externref_=>_none) + (call_indirect $0 (type $externref_=>_none) (ref.null extern) (i32.const 0) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (local.get $local_externref) (i32.const 1) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (global.get $global_funcref) (i32.const 1) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (ref.null func) (i32.const 1) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (ref.func $foo) (i32.const 1) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (local.get $local_anyref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (global.get $global_anyref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.null any) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (local.get $local_funcref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (global.get $global_externref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.null extern) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (local.get $local_externref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (global.get $global_funcref) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.null func) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.func $foo) (i32.const 3) ) diff --git a/test/reference-types.wast.fromBinary.noDebugInfo b/test/reference-types.wast.fromBinary.noDebugInfo index 08186f32b..23c2bb03b 100644 --- a/test/reference-types.wast.fromBinary.noDebugInfo +++ b/test/reference-types.wast.fromBinary.noDebugInfo @@ -191,71 +191,71 @@ (call $2 (ref.func $3) ) - (call_indirect (type $externref_=>_none) + (call_indirect $0 (type $externref_=>_none) (local.get $1) (i32.const 0) ) - (call_indirect (type $externref_=>_none) + (call_indirect $0 (type $externref_=>_none) (global.get $global$0) (i32.const 0) ) - (call_indirect (type $externref_=>_none) + (call_indirect $0 (type $externref_=>_none) (ref.null extern) (i32.const 0) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (local.get $0) (i32.const 1) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (global.get $global$1) (i32.const 1) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (ref.null func) (i32.const 1) ) - (call_indirect (type $funcref_=>_none) + (call_indirect $0 (type $funcref_=>_none) (ref.func $3) (i32.const 1) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (local.get $2) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (global.get $global$3) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.null any) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (local.get $1) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (global.get $global$0) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.null extern) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (local.get $0) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (global.get $global$1) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.null func) (i32.const 3) ) - (call_indirect (type $anyref_=>_none) + (call_indirect $0 (type $anyref_=>_none) (ref.func $3) (i32.const 3) ) diff --git a/test/spec/old_import.wast b/test/spec/old_import.wast index 36638a994..6c5e9dcdb 100644 --- a/test/spec/old_import.wast +++ b/test/spec/old_import.wast @@ -135,10 +135,6 @@ (module (import "" "" (table 10 funcref)) (table 10 funcref)) "multiple tables" ) -(assert_invalid - (module (table 10 funcref) (table 10 funcref)) - "multiple tables" -) (assert_unlinkable (module (import "spectest" "unknown" (table 10 funcref))) diff --git a/test/tail-call.wast.from-wast b/test/tail-call.wast.from-wast index 2a08be62e..480f47a4f 100644 --- a/test/tail-call.wast.from-wast +++ b/test/tail-call.wast.from-wast @@ -6,7 +6,7 @@ (return_call $bar) ) (func $bar - (return_call_indirect (type $none_=>_none) + (return_call_indirect $0 (type $none_=>_none) (i32.const 0) ) ) diff --git a/test/tail-call.wast.fromBinary b/test/tail-call.wast.fromBinary index 70438c907..58fad5d61 100644 --- a/test/tail-call.wast.fromBinary +++ b/test/tail-call.wast.fromBinary @@ -6,7 +6,7 @@ (return_call $bar) ) (func $bar - (return_call_indirect (type $none_=>_none) + (return_call_indirect $0 (type $none_=>_none) (i32.const 0) ) ) diff --git a/test/tail-call.wast.fromBinary.noDebugInfo b/test/tail-call.wast.fromBinary.noDebugInfo index e7a1ab752..919626330 100644 --- a/test/tail-call.wast.fromBinary.noDebugInfo +++ b/test/tail-call.wast.fromBinary.noDebugInfo @@ -6,7 +6,7 @@ (return_call $1) ) (func $1 - (return_call_indirect (type $none_=>_none) + (return_call_indirect $0 (type $none_=>_none) (i32.const 0) ) ) |