diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/asm2wasm.h | 9 | ||||
-rw-r--r-- | src/binaryen-c.cpp | 13 | ||||
-rw-r--r-- | src/binaryen-c.h | 2 | ||||
-rw-r--r-- | src/passes/DuplicateFunctionElimination.cpp | 10 | ||||
-rw-r--r-- | src/passes/Print.cpp | 30 | ||||
-rw-r--r-- | src/passes/RemoveUnusedFunctions.cpp | 6 | ||||
-rw-r--r-- | src/passes/ReorderFunctions.cpp | 6 | ||||
-rw-r--r-- | src/shared-constants.h | 3 | ||||
-rw-r--r-- | src/shell-interface.h | 29 | ||||
-rw-r--r-- | src/wasm-binary.h | 56 | ||||
-rw-r--r-- | src/wasm-interpreter.h | 27 | ||||
-rw-r--r-- | src/wasm-js.cpp | 70 | ||||
-rw-r--r-- | src/wasm-linker.cpp | 34 | ||||
-rw-r--r-- | src/wasm-linker.h | 8 | ||||
-rw-r--r-- | src/wasm-s-parser.h | 69 | ||||
-rw-r--r-- | src/wasm-validator.h | 6 | ||||
-rw-r--r-- | src/wasm.cpp | 3 | ||||
-rw-r--r-- | src/wasm.h | 25 |
18 files changed, 306 insertions, 100 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index c0f13ff8f..a5754f725 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -660,12 +660,17 @@ void Asm2WasmBuilder::processAsm(Ref ast) { // TODO: when not using aliasing function pointers, we could merge them by noticing that // index 0 in each table is the null func, and each other index should only have one // non-null func. However, that breaks down when function pointer casts are emulated. - functionTableStarts[name] = wasm.table.names.size(); // this table starts here + if (wasm.table.segments.size() == 0) { + wasm.table.segments.emplace_back(wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0)))); + } + auto& segment = wasm.table.segments[0]; + functionTableStarts[name] = segment.data.size(); // this table starts here Ref contents = value[1]; for (unsigned k = 0; k < contents->size(); k++) { IString curr = contents[k][1]->getIString(); - wasm.table.names.push_back(curr); + segment.data.push_back(curr); } + wasm.table.initial = wasm.table.max = segment.data.size(); } else { abort_on("invalid var element", pair); } diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index b7fc4347a..83c626eb1 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -729,14 +729,17 @@ void BinaryenSetFunctionTable(BinaryenModuleRef module, BinaryenFunctionRef* fun } auto* wasm = (Module*)module; + Table::Segment segment(wasm->allocator.alloc<Const>()->set(Literal(int32_t(0)))); for (BinaryenIndex i = 0; i < numFuncs; i++) { - wasm->table.names.push_back(((Function*)funcs[i])->name); + segment.data.push_back(((Function*)funcs[i])->name); } + wasm->table.segments.push_back(segment); + wasm->table.initial = wasm->table.max = numFuncs; } // Memory. One per module -void BinaryenSetMemory(BinaryenModuleRef module, BinaryenIndex initial, BinaryenIndex maximum, const char* exportName, const char **segments, BinaryenIndex* segmentOffsets, BinaryenIndex* segmentSizes, BinaryenIndex numSegments) { +void BinaryenSetMemory(BinaryenModuleRef module, BinaryenIndex initial, BinaryenIndex maximum, const char* exportName, const char **segments, BinaryenExpressionRef* segmentOffsets, BinaryenIndex* segmentSizes, BinaryenIndex numSegments) { if (tracing) { std::cout << " {\n"; for (BinaryenIndex i = 0; i < numSegments; i++) { @@ -754,10 +757,10 @@ void BinaryenSetMemory(BinaryenModuleRef module, BinaryenIndex initial, Binaryen } if (numSegments == 0) std::cout << "0"; // ensure the array is not empty, otherwise a compiler error on VS std::cout << " };\n"; - std::cout << " BinaryenIndex segmentOffsets[] = { "; + std::cout << " BinaryenExpressionRef segmentOffsets[] = { "; for (BinaryenIndex i = 0; i < numSegments; i++) { if (i > 0) std::cout << ", "; - std::cout << segmentOffsets[i]; + std::cout << "expressions[" << expressions[segmentOffsets[i]] << "]"; } if (numSegments == 0) std::cout << "0"; // ensure the array is not empty, otherwise a compiler error on VS std::cout << " };\n"; @@ -779,7 +782,7 @@ void BinaryenSetMemory(BinaryenModuleRef module, BinaryenIndex initial, Binaryen wasm->memory.max = maximum; if (exportName) wasm->memory.exportName = exportName; for (BinaryenIndex i = 0; i < numSegments; i++) { - wasm->memory.segments.emplace_back(segmentOffsets[i], segments[i], segmentSizes[i]); + wasm->memory.segments.emplace_back((Expression*)segmentOffsets[i], segments[i], segmentSizes[i]); } } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index b32fa566a..e2086072b 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -346,7 +346,7 @@ void BinaryenSetFunctionTable(BinaryenModuleRef module, BinaryenFunctionRef* fun // Each segment has data in segments, a start offset in segmentOffsets, and a size in segmentSizes. // exportName can be NULL -void BinaryenSetMemory(BinaryenModuleRef module, BinaryenIndex initial, BinaryenIndex maximum, const char* exportName, const char **segments, BinaryenIndex* segmentOffsets, BinaryenIndex* segmentSizes, BinaryenIndex numSegments); +void BinaryenSetMemory(BinaryenModuleRef module, BinaryenIndex initial, BinaryenIndex maximum, const char* exportName, const char **segments, BinaryenExpressionRef* segmentOffsets, BinaryenIndex* segmentSizes, BinaryenIndex numSegments); // Start function. One per module diff --git a/src/passes/DuplicateFunctionElimination.cpp b/src/passes/DuplicateFunctionElimination.cpp index 961d26ba5..2b8e69b54 100644 --- a/src/passes/DuplicateFunctionElimination.cpp +++ b/src/passes/DuplicateFunctionElimination.cpp @@ -123,10 +123,12 @@ struct DuplicateFunctionElimination : public Pass { replacerRunner.add<FunctionReplacer>(&replacements); replacerRunner.run(); // replace in table - for (auto& name : module->table.names) { - auto iter = replacements.find(name); - if (iter != replacements.end()) { - name = iter->second; + for (auto& segment : module->table.segments) { + for (auto& name : segment.data) { + auto iter = replacements.find(name); + if (iter != replacements.end()) { + name = iter->second; + } } } // replace in start diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 47e503fa4..5eea38bdc 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -575,12 +575,19 @@ struct PrintSExpression : public Visitor<PrintSExpression> { decIndent(); } void visitTable(Table *curr) { - printOpening(o, "table"); - for (auto name : curr->names) { - o << ' '; - printName(name); + printOpening(o, "table") << ' ' << curr->initial; + if (curr->max && curr->max != Table::kMaxSize) o << ' ' << curr->max; + o << " anyfunc)\n"; + doIndent(o, indent); + for (auto& segment : curr->segments) { + printOpening(o, "elem ", true); + visit(segment.offset); + for (auto name : segment.data) { + o << ' '; + printName(name); + } + o << ')'; } - o << ')'; } void visitModule(Module *curr) { currModule = curr; @@ -589,9 +596,12 @@ struct PrintSExpression : public Visitor<PrintSExpression> { doIndent(o, indent); printOpening(o, "memory") << ' ' << curr->memory.initial; if (curr->memory.max && curr->memory.max != Memory::kMaxSize) o << ' ' << curr->memory.max; + o << ")\n"; for (auto segment : curr->memory.segments) { - o << maybeNewLine; - o << (minify ? "" : " ") << "(segment " << segment.offset << " \""; + doIndent(o, indent); + printOpening(o, "data ", true); + visit(segment.offset); + o << " \""; for (size_t i = 0; i < segment.data.size(); i++) { unsigned char c = segment.data[i]; switch (c) { @@ -612,10 +622,8 @@ struct PrintSExpression : public Visitor<PrintSExpression> { } } } - o << "\")"; + o << "\")\n"; } - o << ((curr->memory.segments.size() > 0 && !minify) ? "\n " : "") << ')'; - o << maybeNewLine; if (curr->memory.exportName.is()) { doIndent(o, indent); printOpening(o, "export "); @@ -647,7 +655,7 @@ struct PrintSExpression : public Visitor<PrintSExpression> { visitGlobal(child.get()); o << maybeNewLine; } - if (curr->table.names.size() > 0) { + if (curr->table.segments.size() > 0 || curr->table.initial > 0 || curr->table.max != Table::kMaxSize) { doIndent(o, indent); visitTable(&curr->table); o << maybeNewLine; diff --git a/src/passes/RemoveUnusedFunctions.cpp b/src/passes/RemoveUnusedFunctions.cpp index a2941aff6..78b0f0ffc 100644 --- a/src/passes/RemoveUnusedFunctions.cpp +++ b/src/passes/RemoveUnusedFunctions.cpp @@ -39,8 +39,10 @@ struct RemoveUnusedFunctions : public Pass { root.push_back(module->getFunction(curr->value)); } // For now, all functions that can be called indirectly are marked as roots. - for (auto& curr : module->table.names) { - root.push_back(module->getFunction(curr)); + for (auto& segment : module->table.segments) { + for (auto& curr : segment.data) { + root.push_back(module->getFunction(curr)); + } } // Compute function reachability starting from the root set. DirectCallGraphAnalyzer analyzer(module, root); diff --git a/src/passes/ReorderFunctions.cpp b/src/passes/ReorderFunctions.cpp index 38ef98afb..679fedb61 100644 --- a/src/passes/ReorderFunctions.cpp +++ b/src/passes/ReorderFunctions.cpp @@ -38,8 +38,10 @@ struct ReorderFunctions : public WalkerPass<PostWalker<ReorderFunctions, Visitor for (auto& curr : module->exports) { counts[curr->value]++; } - for (auto& curr : module->table.names) { - counts[curr]++; + for (auto& segment : module->table.segments) { + for (auto& curr : segment.data) { + counts[curr]++; + } } std::sort(module->functions.begin(), module->functions.end(), [this]( const std::unique_ptr<Function>& a, diff --git a/src/shared-constants.h b/src/shared-constants.h index fb94ff11b..f0148cb67 100644 --- a/src/shared-constants.h +++ b/src/shared-constants.h @@ -24,10 +24,12 @@ extern Name GROW_WASM_MEMORY, PARAM, RESULT, MEMORY, + DATA, SEGMENT, EXPORT, IMPORT, TABLE, + ELEM, LOCAL, TYPE, CALL, @@ -43,6 +45,7 @@ extern Name GROW_WASM_MEMORY, NEG_NAN, CASE, BR, + ANYFUNC, FAKE_RETURN, ASSERT_RETURN, ASSERT_TRAP, diff --git a/src/shell-interface.h b/src/shell-interface.h index f6c12e8ca..f332307ad 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -86,15 +86,27 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { } } memory; + std::vector<Name> table; + ShellExternalInterface() : memory() {} void init(Module& wasm) override { memory.resize(wasm.memory.initial * wasm::Memory::kPageSize); // apply memory segments for (auto& segment : wasm.memory.segments) { - assert(segment.offset + segment.data.size() <= wasm.memory.initial * wasm::Memory::kPageSize); + Address offset = ConstantExpressionRunner().visit(segment.offset).value.geti32(); + assert(offset + segment.data.size() <= wasm.memory.initial * wasm::Memory::kPageSize); + for (size_t i = 0; i != segment.data.size(); ++i) { + memory.set(offset + i, segment.data[i]); + } + } + + table.resize(wasm.table.initial); + for (auto& segment : wasm.table.segments) { + Address offset = ConstantExpressionRunner().visit(segment.offset).value.geti32(); + assert(offset + segment.data.size() <= wasm.table.initial); for (size_t i = 0; i != segment.data.size(); ++i) { - memory.set(segment.offset + i, segment.data[i]); + table[offset + i] = segment.data[i]; } } } @@ -114,6 +126,19 @@ struct ShellExternalInterface : ModuleInstance::ExternalInterface { abort(); } + Literal callTable(Index index, Name type, LiteralList& arguments, ModuleInstance& instance) override { + if (index >= table.size()) trap("callTable overflow"); + auto* func = instance.wasm.getFunction(table[index]); + if (func->type.is() && func->type != type) trap("callIndirect: bad type"); + if (func->params.size() != arguments.size()) trap("callIndirect: bad # of arguments"); + for (size_t i = 0; i < func->params.size(); i++) { + if (func->params[i] != arguments[i].type) { + trap("callIndirect: bad argument type"); + } + } + return instance.callFunctionInternal(func->name, arguments); + } + Literal load(Load* load, Address addr) override { switch (load->type) { case i32: { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index aad142d44..04bebddc5 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -706,7 +706,8 @@ public: o << U32LEB(num); for (auto& segment : wasm->memory.segments) { if (segment.data.size() == 0) continue; - o << U32LEB(segment.offset); + writeExpression(segment.offset); + o << int8_t(BinaryConsts::End); writeInlineBuffer(&segment.data[0], segment.data.size()); } finishSection(start); @@ -739,12 +740,19 @@ public: } void writeFunctionTable() { - if (wasm->table.names.size() == 0) return; + if (wasm->table.segments.size() == 0) return; if (debug) std::cerr << "== writeFunctionTable" << std::endl; auto start = startSection(BinaryConsts::Section::FunctionTable); - o << U32LEB(wasm->table.names.size()); - for (auto name : wasm->table.names) { - o << U32LEB(getFunctionIndex(name)); + o << U32LEB(wasm->table.initial); + o << U32LEB(wasm->table.max); + o << U32LEB(wasm->table.segments.size()); + for (auto& segment : wasm->table.segments) { + writeExpression(segment.offset); + o << int8_t(BinaryConsts::End); + o << U32LEB(segment.data.size()); + for (auto name : segment.data) { + o << U32LEB(getFunctionIndex(name)); + } } finishSection(start); } @@ -1572,6 +1580,15 @@ public: } } + Expression* readExpression() { + assert(depth == 0); + processExpressions(); + assert(expressionStack.size() == 1); + auto* ret = popExpression(); + assert(depth == 0); + return ret; + } + void readGlobals() { if (debug) std::cerr << "== readGlobals" << std::endl; size_t num = getU32LEB(); @@ -1580,11 +1597,7 @@ public: if (debug) std::cerr << "read one" << std::endl; auto curr = new Global; curr->type = getWasmType(); - assert(depth == 0); - processExpressions(); - assert(expressionStack.size() == 1); - curr->init = popExpression(); - assert(depth == 0); + curr->init = readExpression(); wasm.addGlobal(curr); } } @@ -1638,9 +1651,12 @@ public: } } - for (size_t index : functionTable) { - assert(index < wasm.functions.size()); - wasm.table.names.push_back(wasm.functions[index]->name); + for (auto& pair : functionTable) { + auto i = pair.first; + auto& indexes = pair.second; + for (auto j : indexes) { + wasm.table.segments[i].data.push_back(wasm.functions[j]->name); + } } } @@ -1649,7 +1665,7 @@ public: auto num = getU32LEB(); for (size_t i = 0; i < num; i++) { Memory::Segment curr; - auto offset = getU32LEB(); + auto offset = readExpression(); auto size = getU32LEB(); std::vector<char> buffer; buffer.resize(size); @@ -1660,14 +1676,20 @@ public: } } - std::vector<size_t> functionTable; + std::map<Index, std::vector<Index>> functionTable; void readFunctionTable() { if (debug) std::cerr << "== readFunctionTable" << std::endl; + wasm.table.initial = getU32LEB(); + wasm.table.max = getU32LEB(); auto num = getU32LEB(); for (size_t i = 0; i < num; i++) { - auto index = getU32LEB(); - functionTable.push_back(index); + wasm.table.segments.emplace_back(readExpression()); + auto& temporary = functionTable[i]; + auto size = getU32LEB(); + for (Index j = 0; j < size; j++) { + temporary.push_back(getU32LEB()); + } } } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 846dd896e..28a3e8e6d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -501,8 +501,8 @@ public: } }; -// Execute an expression in global init -class GlobalInitRunner : public ExpressionRunner<GlobalInitRunner> { +// Execute an constant expression in a global init or memory offset +class ConstantExpressionRunner : public ExpressionRunner<ConstantExpressionRunner> { public: Flow visitLoop(Loop* curr) { WASM_UNREACHABLE(); } Flow visitCall(Call* curr) { WASM_UNREACHABLE(); } @@ -537,6 +537,7 @@ public: struct ExternalInterface { virtual void init(Module& wasm) {} virtual Literal callImport(Import* import, LiteralList& arguments) = 0; + virtual Literal callTable(Index index, Name type, LiteralList& arguments, ModuleInstance& instance) = 0; virtual Literal load(Load* load, Address addr) = 0; virtual void store(Store* store, Address addr, Literal value) = 0; virtual void growMemory(Address oldSize, Address newSize) = 0; @@ -551,7 +552,7 @@ public: ModuleInstance(Module& wasm, ExternalInterface* externalInterface) : wasm(wasm), externalInterface(externalInterface) { memorySize = wasm.memory.initial; for (Index i = 0; i < wasm.globals.size(); i++) { - globals.push_back(GlobalInitRunner().visit(wasm.globals[i]->init).value); + globals.push_back(ConstantExpressionRunner().visit(wasm.globals[i]->init).value); } externalInterface->init(wasm); if (wasm.start.is()) { @@ -591,8 +592,8 @@ private: return callFunctionInternal(name, arguments); } -private: - // Internal function call. +public: + // Internal function call. Must be public so that callTable implementations can use it (refactor?) Literal callFunctionInternal(IString name, LiteralList& arguments) { class FunctionScope { @@ -674,18 +675,8 @@ private: LiteralList arguments; Flow flow = generateArguments(curr->operands, arguments); if (flow.breaking()) return flow; - size_t index = target.value.geti32(); - if (index >= instance.wasm.table.names.size()) trap("callIndirect: overflow"); - Name name = instance.wasm.table.names[index]; - Function *func = instance.wasm.getFunction(name); - if (func->type.is() && func->type != curr->fullType) trap("callIndirect: bad type"); - if (func->params.size() != arguments.size()) trap("callIndirect: bad # of arguments"); - for (size_t i = 0; i < func->params.size(); i++) { - if (func->params[i] != arguments[i].type) { - trap("callIndirect: bad argument type"); - } - } - return instance.callFunctionInternal(name, arguments); + Index index = target.value.geti32(); + return instance.externalInterface->callTable(index, curr->fullType, arguments, instance); } Flow visitGetLocal(GetLocal *curr) { @@ -804,6 +795,8 @@ private: return ret; } +private: + Address memorySize; // in pages template <class LS> diff --git a/src/wasm-js.cpp b/src/wasm-js.cpp index 33b0faa55..83956e47e 100644 --- a/src/wasm-js.cpp +++ b/src/wasm-js.cpp @@ -193,12 +193,25 @@ extern "C" void EMSCRIPTEN_KEEPALIVE instantiate() { var source = Module['HEAP8'].subarray($1, $1 + $2); var target = new Int8Array(Module['outside']['newBuffer']); target.set(source, $0); - }, (uint32_t)segment.offset, &segment.data[0], segment.data.size()); + }, ConstantExpressionRunner().visit(segment.offset).value.geti32(), &segment.data[0], segment.data.size()); + } + // Table support is in a JS array. If the entry is a number, it's a function pointer. If not, it's a JS method to be called directly + // TODO: make them all JS methods, wrapping a dynCall where necessary? + EM_ASM_({ + Module['outside']['wasmTable'] = new Array($0); + }, wasm.table.initial); + for (auto segment : wasm.table.segments) { + Address offset = ConstantExpressionRunner().visit(segment.offset).value.geti32(); + assert(offset + segment.data.size() <= wasm.table.initial); + for (size_t i = 0; i != segment.data.size(); ++i) { + EM_ASM_({ + Module['outside']['wasmTable'][$0] = $1; + }, offset + i, wasm.getFunction(segment.data[i])); + } } } - Literal callImport(Import *import, LiteralList& arguments) override { - if (wasmJSDebug) std::cout << "calling import " << import->name.str << '\n'; + void prepareTempArgments(LiteralList& arguments) { EM_ASM({ Module['tempArguments'] = []; }); @@ -213,6 +226,21 @@ extern "C" void EMSCRIPTEN_KEEPALIVE instantiate() { abort(); } } + } + + Literal getResultFromJS(double ret, WasmType type) { + switch (type) { + case none: return Literal(0); + case i32: return Literal((int32_t)ret); + case f32: return Literal((float)ret); + case f64: return Literal((double)ret); + default: abort(); + } + } + + Literal callImport(Import *import, LiteralList& arguments) override { + if (wasmJSDebug) std::cout << "calling import " << import->name.str << '\n'; + prepareTempArgments(arguments); double ret = EM_ASM_DOUBLE({ var mod = Pointer_stringify($0); var base = Pointer_stringify($1); @@ -224,12 +252,36 @@ extern "C" void EMSCRIPTEN_KEEPALIVE instantiate() { if (wasmJSDebug) std::cout << "calling import returning " << ret << '\n'; - switch (import->type->result) { - case none: return Literal(0); - case i32: return Literal((int32_t)ret); - case f32: return Literal((float)ret); - case f64: return Literal((double)ret); - default: abort(); + return getResultFromJS(ret, import->type->result); + } + + Literal callTable(Index index, Name type, LiteralList& arguments, ModuleInstance& instance) override { + void* ptr = (void*)EM_ASM_INT({ + var value = Module['outside']['wasmTable'][$0]; + return typeof value === "number" ? value : -1; + }, index); + if (ptr == nullptr) trap("callTable overflow"); + if (ptr != (void*)-1) { + // a Function we can call + Function* func = (Function*)ptr; + if (func->type.is() && func->type != type) trap("callIndirect: bad type"); + if (func->params.size() != arguments.size()) trap("callIndirect: bad # of arguments"); + for (size_t i = 0; i < func->params.size(); i++) { + if (func->params[i] != arguments[i].type) { + trap("callIndirect: bad argument type"); + } + } + return instance.callFunctionInternal(func->name, arguments); + } else { + // A JS function JS can call + prepareTempArgments(arguments); + double ret = EM_ASM_DOUBLE({ + var func = Module['outside']['wasmTable'][$0]; + var tempArguments = Module['tempArguments']; + Module['tempArguments'] = null; + return func.apply(null, tempArguments); + }, index); + return getResultFromJS(ret, instance.wasm.getFunctionType(type)->result); } } diff --git a/src/wasm-linker.cpp b/src/wasm-linker.cpp index d8bae7558..2c9a4cd4d 100644 --- a/src/wasm-linker.cpp +++ b/src/wasm-linker.cpp @@ -83,7 +83,7 @@ void Linker::layout() { // Update the segments with their addresses now that they have been allocated. for (const auto& seg : out.segments) { Address address = staticAddresses[seg.first]; - out.wasm.memory.segments[seg.second].offset = address; + out.wasm.memory.segments[seg.second].offset = out.wasm.allocator.alloc<Const>()->set(Literal(uint32_t(address))); segmentsByAddress[address] = seg.second; } @@ -132,7 +132,7 @@ void Linker::layout() { // Emit the pre-assigned function names in sorted order for (const auto& P : functionNames) { - out.wasm.table.names.push_back(P.second); + getTableSegment().data.push_back(P.second); } for (auto& relocation : out.relocations) { @@ -206,9 +206,11 @@ void Linker::layout() { } // ensure an explicit function type for indirect call targets - for (auto& name : out.wasm.table.names) { - auto* func = out.wasm.getFunction(name); - func->type = ensureFunctionType(getSig(func), &out.wasm)->name; + for (auto& segment : out.wasm.table.segments) { + for (auto& name : segment.data) { + auto* func = out.wasm.getFunction(name); + func->type = ensureFunctionType(getSig(func), &out.wasm)->name; + } } // Export malloc and free whenever availble. JavsScript version of malloc has @@ -223,6 +225,11 @@ void Linker::layout() { if (out.symbolInfo.implementedFunctions.count("free")) { exportFunction("free", true); } + + // finalize function table + if (out.wasm.table.segments.size() > 0) { + out.wasm.table.initial = out.wasm.table.max = getTableSegment().data.size(); + } } bool Linker::linkObject(S2WasmBuilder& builder) { @@ -382,10 +389,19 @@ void Linker::emscriptenGlue(std::ostream& o) { o << " }\n"; } +Table::Segment& Linker::getTableSegment() { + if (out.wasm.table.segments.size() == 0) { + out.wasm.table.segments.emplace_back(out.wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0)))); + } else { + assert(out.wasm.table.segments.size() == 1); + } + return out.wasm.table.segments[0]; +} + Index Linker::getFunctionIndex(Name name) { if (!functionIndexes.count(name)) { - functionIndexes[name] = out.wasm.table.names.size(); - out.wasm.table.names.push_back(name); + functionIndexes[name] = getTableSegment().data.size(); + getTableSegment().data.push_back(name); if (debug) { std::cerr << "function index: " << name << ": " << functionIndexes[name] << '\n'; @@ -403,7 +419,6 @@ bool hasI64ResultOrParam(FunctionType* ft) { } void Linker::makeDummyFunction() { - assert(out.wasm.table.names.empty()); bool create = false; // Check if there are address-taken functions for (auto& relocation : out.relocations) { @@ -421,9 +436,10 @@ void Linker::makeDummyFunction() { } void Linker::makeDynCallThunks() { + if (out.wasm.table.segments.size() == 0) return; std::unordered_set<std::string> sigs; wasm::Builder wasmBuilder(out.wasm); - for (const auto& indirectFunc : out.wasm.table.names) { + for (const auto& indirectFunc : getTableSegment().data) { // Skip generating thunks for the dummy function if (indirectFunc == dummyFunction) continue; std::string sig(getSig(out.wasm.getFunction(indirectFunc))); diff --git a/src/wasm-linker.h b/src/wasm-linker.h index 3f1e8c7ac..a6f5d319a 100644 --- a/src/wasm-linker.h +++ b/src/wasm-linker.h @@ -114,12 +114,12 @@ class LinkerObject { // Add an initializer segment for the named static variable. void addSegment(Name name, const char* data, Address size) { segments[name] = wasm.memory.segments.size(); - wasm.memory.segments.emplace_back(0, data, size); + wasm.memory.segments.emplace_back(wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0))), data, size); } void addSegment(Name name, std::vector<char>& data) { segments[name] = wasm.memory.segments.size(); - wasm.memory.segments.emplace_back(0, data); + wasm.memory.segments.emplace_back(wasm.allocator.alloc<Const>()->set(Literal(uint32_t(0))), data); } void addInitializerFunction(Name name) { @@ -282,6 +282,10 @@ class Linker { void ensureImport(Name target, std::string signature); + // Makes sure the table has a single segment, with offset 0, + // to which we can add content. + Table::Segment& getTableSegment(); + // Retrieves (and assigns) an entry index in the indirect function table for // a given function. Index getFunctionIndex(Name name); diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 750d485fb..b70f70b4e 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -367,13 +367,15 @@ private: if (id == START) return parseStart(curr); if (id == FUNC) return parseFunction(curr); if (id == MEMORY) return parseMemory(curr); + if (id == DATA) return parseData(curr); if (id == EXPORT) return parseExport(curr); if (id == IMPORT) return; // already done if (id == GLOBAL) return parseGlobal(curr); if (id == TABLE) return parseTable(curr); + if (id == ELEM) return parseElem(curr); if (id == TYPE) return; // already done std::cerr << "bad module element " << id.str << '\n'; - throw ParseException("unknown module element"); + throw ParseException("unknown module element", curr.line, curr.col); } // function parsing state @@ -1321,19 +1323,40 @@ private: } while (i < s.size()) { Element& curr = *s[i]; - assert(curr[0]->str() == SEGMENT); - const char *input = curr[2]->c_str(); + size_t j = 1; + Address offsetValue; + if (curr[0]->str() == DATA) { + offsetValue = 0; + } else { + offsetValue = atoi(curr[j++]->c_str()); + } + const char *input = curr[j]->c_str(); + auto* offset = allocator.alloc<Const>(); + offset->type = i32; + offset->value = Literal(int32_t(offsetValue)); if (auto size = strlen(input)) { std::vector<char> data; stringToBinary(input, size, data); - wasm.memory.segments.emplace_back(atoi(curr[1]->c_str()), data.data(), data.size()); + wasm.memory.segments.emplace_back(offset, data.data(), data.size()); } else { - wasm.memory.segments.emplace_back(atoi(curr[1]->c_str()), "", 0); + wasm.memory.segments.emplace_back(offset, "", 0); } i++; } } + void parseData(Element& s) { + auto* offset = parseExpression(s[1]); + const char *input = s[2]->c_str(); + if (auto size = strlen(input)) { + std::vector<char> data; + stringToBinary(input, size, data); + wasm.memory.segments.emplace_back(offset, data.data(), data.size()); + } else { + wasm.memory.segments.emplace_back(offset, "", 0); + } + } + void parseExport(Element& s) { if (!s[2]->dollared() && !std::isdigit(s[2]->str()[0])) { assert(s[2]->str() == MEMORY); @@ -1402,9 +1425,41 @@ private: } void parseTable(Element& s) { - for (size_t i = 1; i < s.size(); i++) { - wasm.table.names.push_back(getFunctionName(*s[i])); + if (s.size() == 1) return; // empty table in old notation + if (!s[1]->dollared()) { + if (s[1]->str() == ANYFUNC) { + // (table type (elem ..)) + parseElem(*s[2]); + wasm.table.initial = wasm.table.max = wasm.table.segments[0].data.size(); + return; + } + // first element isn't dollared, and isn't anyfunc. this could be old syntax for (table 0 1) which means function 0 and 1, or it could be (table initial max? type), look for type + if (s[s.size() - 1]->str() == ANYFUNC) { + // (table initial max? type) + wasm.table.initial = atoi(s[1]->c_str()); + wasm.table.max = atoi(s[2]->c_str()); + return; + } + } + // old notation (table func1 func2 ..) + parseElem(s); + wasm.table.initial = wasm.table.max = wasm.table.segments[0].data.size(); + } + + void parseElem(Element& s) { + Index i = 1; + Expression* offset; + if (s[i]->isList()) { + // there is an init expression + offset = parseExpression(s[i++]); + } else { + offset = allocator.alloc<Const>()->set(Literal(int32_t(0))); + } + Table::Segment segment(offset); + for (; i < s.size(); i++) { + segment.data.push_back(getFunctionName(*s[i])); } + wasm.table.segments.push_back(segment); } void parseType(Element& s) { diff --git a/src/wasm-validator.h b/src/wasm-validator.h index 90930d8f4..3d9a0e1c3 100644 --- a/src/wasm-validator.h +++ b/src/wasm-validator.h @@ -281,12 +281,6 @@ public: void visitMemory(Memory *curr) { shouldBeFalse(curr->initial > curr->max, "memory", "memory max >= initial"); shouldBeTrue(curr->max <= Memory::kMaxSize, "memory", "max memory must be <= 4GB"); - size_t top = 0; - for (auto& segment : curr->segments) { - shouldBeFalse(segment.offset < top, "memory", "segment offset is small enough"); - top = segment.offset + segment.data.size(); - } - shouldBeFalse(top > curr->initial * Memory::kPageSize, "memory", "total segments must be small enough"); } void visitModule(Module *curr) { // exports diff --git a/src/wasm.cpp b/src/wasm.cpp index dbbd209b4..8cfdee759 100644 --- a/src/wasm.cpp +++ b/src/wasm.cpp @@ -49,10 +49,12 @@ Name GROW_WASM_MEMORY("__growWasmMemory"), PARAM("param"), RESULT("result"), MEMORY("memory"), + DATA("data"), SEGMENT("segment"), EXPORT("export"), IMPORT("import"), TABLE("table"), + ELEM("elem"), LOCAL("local"), TYPE("type"), CALL("call"), @@ -68,6 +70,7 @@ Name GROW_WASM_MEMORY("__growWasmMemory"), NEG_NAN("-nan"), CASE("case"), BR("br"), + ANYFUNC("anyfunc"), FAKE_RETURN("fake_return_waka123"), ASSERT_RETURN("assert_return"), ASSERT_TRAP("assert_trap"), diff --git a/src/wasm.h b/src/wasm.h index 38a030e1a..68558033d 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1432,7 +1432,23 @@ public: class Table { public: - std::vector<Name> names; + static const Index kMaxSize = Index(-1); + + struct Segment { + Expression* offset; + std::vector<Name> data; + Segment() {} + Segment(Expression* offset) : offset(offset) { + } + Segment(Expression* offset, std::vector<Name>& init) : offset(offset) { + data.swap(init); + } + }; + + Address initial, max; + std::vector<Segment> segments; + + Table() : initial(0), max(kMaxSize) {} }; class Memory { @@ -1440,15 +1456,16 @@ public: static const Address::address_t kPageSize = 64 * 1024; static const Address::address_t kMaxSize = ~Address::address_t(0) / kPageSize; static const Address::address_t kPageMask = ~(kPageSize - 1); + struct Segment { - Address offset; + Expression* offset; std::vector<char> data; // TODO: optimize Segment() {} - Segment(Address offset, const char *init, Address size) : offset(offset) { + Segment(Expression* offset, const char *init, Address size) : offset(offset) { data.resize(size); std::copy_n(init, size, data.begin()); } - Segment(Address offset, std::vector<char>& init) : offset(offset) { + Segment(Expression* offset, std::vector<char>& init) : offset(offset) { data.swap(init); } }; |