diff options
-rw-r--r-- | src/wasm/wasm-emscripten.cpp | 165 | ||||
-rw-r--r-- | test/lld/em_asm_table.wast | 8 | ||||
-rw-r--r-- | test/lld/em_asm_table.wast.out | 67 |
3 files changed, 187 insertions, 53 deletions
diff --git a/src/wasm/wasm-emscripten.cpp b/src/wasm/wasm-emscripten.cpp index caea83302..602889b39 100644 --- a/src/wasm/wasm-emscripten.cpp +++ b/src/wasm/wasm-emscripten.cpp @@ -16,6 +16,7 @@ #include "wasm-emscripten.h" +#include <functional> #include <sstream> #include "asm_v_wasm.h" @@ -32,6 +33,7 @@ namespace wasm { cashew::IString EMSCRIPTEN_ASM_CONST("emscripten_asm_const"); cashew::IString EM_JS_PREFIX("__em_js__"); +static const char* INVOKE_PREFIX = "invoke_"; static Name STACK_SAVE("stackSave"); static Name STACK_RESTORE("stackRestore"); @@ -672,6 +674,12 @@ struct AsmConstWalker : public LinearExecutionWalker<AsmConstWalker> { std::set<std::string> allSigs; // last sets in the current basic block, per index std::map<Index, LocalSet*> sets; + // table indices that are calls to emscripten_asm_const_* + std::map<Index, Name> asmTable; + // cache used by tableIndexForName + std::map<Name, Literal> tableIndices; + // first available index after the table segment for each segment + std::vector<int32_t> tableOffsets; AsmConstWalker(Module& _wasm) : wasm(_wasm), segmentOffsets(getSegmentOffsets(wasm)) {} @@ -692,7 +700,14 @@ private: void queueImport(Name importName, std::string baseSig); void addImports(); + Index resolveConstIndex(Expression* arg, + std::function<void(Expression*)> reportError); + Const* resolveConstAddr(Expression* arg, const Name& target); + void prepareAsmIndices(Table* table); + Literal tableIndexForName(Name name); + std::vector<std::unique_ptr<Function>> queuedImports; + std::vector<Name> queuedTableEntries; }; void AsmConstWalker::noteNonLinear(Expression* curr) { @@ -702,45 +717,115 @@ void AsmConstWalker::noteNonLinear(Expression* curr) { void AsmConstWalker::visitLocalSet(LocalSet* curr) { sets[curr->index] = curr; } +Const* AsmConstWalker::resolveConstAddr(Expression* arg, const Name& target) { + while (!arg->dynCast<Const>()) { + if (auto* get = arg->dynCast<LocalGet>()) { + // The argument may be a local.get, in which case, the last set in this + // basic block has the value. + auto* set = sets[get->index]; + if (set) { + assert(set->index == get->index); + arg = set->value; + } + } else if (auto* value = arg->dynCast<Binary>()) { + // In the dynamic linking case the address of the string constant + // is the result of adding its offset to __memory_base. + // In this case are only looking for the offset with the data segment so + // the RHS of the addition is just what we want. + assert(value->op == AddInt32); + arg = value->right; + } else { + Fatal() << "Unexpected arg0 type (" << getExpressionName(arg) + << ") in call to to: " << target; + } + } + return arg->cast<Const>(); +} + +Index AsmConstWalker::resolveConstIndex( + Expression* arg, std::function<void(Expression*)> reportError) { + while (!arg->is<Const>()) { + if (auto* get = arg->dynCast<LocalGet>()) { + // The argument may be a local.get, in which case, the last set in this + // basic block has the value. + auto* set = sets[get->index]; + if (set) { + assert(set->index == get->index); + arg = set->value; + } else { + reportError(arg); + return 0; + } + } else if (arg->is<GlobalGet>()) { + // In the dynamic linking case, indices start at __table_base. + // We want the value relative to __table_base. + // If we are doing a global.get, assume it's __table_base, then the + // offset relative to __table_base must be 0. + return 0; + } else { + reportError(arg); + return 0; + } + } + return Index(arg->cast<Const>()->value.geti32()); +} + void AsmConstWalker::visitCall(Call* curr) { auto* import = wasm.getFunction(curr->target); + if (!import->imported()) { + return; + } + // Find calls to emscripten_asm_const* functions whose first argument is // is always a string constant. - if (import->imported() && import->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) { + if (import->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) { auto baseSig = getSig(curr); auto sig = fixupNameWithSig(curr->target, baseSig); - auto* arg = curr->operands[0]; - while (!arg->dynCast<Const>()) { - if (auto* get = arg->dynCast<LocalGet>()) { - // The argument may be a local.get, in which case, the last set in this - // basic block has the value. - auto* set = sets[get->index]; - if (set) { - assert(set->index == get->index); - arg = set->value; - } - } else if (auto* value = arg->dynCast<Binary>()) { - // In the dynamic linking case the address of the string constant - // is the result of adding its offset to __memory_base. - // In this case are only looking for the offset with the data segment so - // the RHS of the addition is just what we want. - assert(value->op == AddInt32); - arg = value->right; - } else { - if (!value) { - Fatal() << "Unexpected arg0 type (" << getExpressionName(arg) - << ") in call to to: " << import->base; - } - } - } - - auto* value = arg->cast<Const>(); + auto* value = resolveConstAddr(curr->operands[0], import->base); auto code = codeForConstAddr(wasm, segmentOffsets, value); sigsForCode[code].insert(sig); // Replace the first argument to the call with a Const index Builder builder(wasm); curr->operands[0] = builder.makeConst(idLiteralForCode(code)); + } else if (import->base.startsWith(INVOKE_PREFIX)) { + // A call to emscripten_asm_const_* maybe done indirectly via one of the + // invoke_* functions, in case of setjmp/longjmp, for example. + // We attempt to modify the invoke_* call instead. + auto idx = resolveConstIndex(curr->operands[0], [&](Expression* arg) {}); + + // If the address of the invoke call is an emscripten_asm_const_* function: + if (asmTable.count(idx)) { + auto* value = resolveConstAddr(curr->operands[1], asmTable[idx]); + auto code = codeForConstAddr(wasm, segmentOffsets, value); + + // Extract the base signature from the invoke_* function name. + std::string baseSig(import->base.c_str() + sizeof(INVOKE_PREFIX) - 1); + Name name; + auto sig = fixupNameWithSig(name, baseSig); + sigsForCode[code].insert(sig); + + Builder builder(wasm); + curr->operands[0] = builder.makeConst(tableIndexForName(name)); + curr->operands[1] = builder.makeConst(idLiteralForCode(code)); + } + } +} + +void AsmConstWalker::prepareAsmIndices(Table* table) { + for (auto& segment : table->segments) { + auto idx = resolveConstIndex(segment.offset, [&](Expression* arg) { + Fatal() << "Unexpected table index type (" << getExpressionName(arg) + << ") table"; + }); + for (auto& name : segment.data) { + auto* func = wasm.getFunction(name); + if (func->imported() && func->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) { + asmTable[idx] = name; + } + ++idx; + } + tableOffsets.push_back(idx); } } @@ -757,6 +842,8 @@ void AsmConstWalker::visitTable(Table* curr) { } void AsmConstWalker::process() { + // Find table indices that point to emscripten_asm_const_* functions. + prepareAsmIndices(&wasm.table); // Find and queue necessary imports walkModule(&wasm); // Add them after the walk, to avoid iterator invalidation on @@ -812,10 +899,28 @@ void AsmConstWalker::queueImport(Name importName, std::string baseSig) { queuedImports.push_back(std::unique_ptr<Function>(import)); } +Literal AsmConstWalker::tableIndexForName(Name name) { + auto result = tableIndices.find(name); + if (result != tableIndices.end()) { + return result->second; + } + queuedTableEntries.push_back(name); + return tableIndices[name] = Literal(tableOffsets[0]++); +} + void AsmConstWalker::addImports() { for (auto& import : queuedImports) { wasm.addFunction(import.release()); } + + if (!queuedTableEntries.empty()) { + assert(wasm.table.segments.size() == 1); + std::vector<Name>& tableSegmentData = wasm.table.segments[0].data; + for (auto& entry : queuedTableEntries) { + tableSegmentData.push_back(entry); + } + wasm.table.initial.addr += queuedTableEntries.size(); + } } AsmConstWalker fixEmAsmConstsAndReturnWalker(Module& wasm) { @@ -951,7 +1056,7 @@ struct FixInvokeFunctionNamesWalker } std::string sigWoOrigFunc = sig.front() + sig.substr(2, sig.size() - 2); invokeSigs.insert(sigWoOrigFunc); - return Name("invoke_" + sigWoOrigFunc); + return Name(INVOKE_PREFIX + sigWoOrigFunc); } Name fixEmEHSjLjNames(const Name& name, const std::string& sig) { @@ -1091,7 +1196,7 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { if (emJsWalker.codeByName.count(import->base.str) == 0 && !import->base.startsWith(EMSCRIPTEN_ASM_CONST.str) && - !import->base.startsWith("invoke_")) { + !import->base.startsWith(INVOKE_PREFIX)) { if (declares.insert(import->base.str).second) { meta << nextElement() << '"' << import->base.str << '"'; } @@ -1145,7 +1250,7 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata( meta << " \"invokeFuncs\": ["; commaFirst = true; ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) { - if (import->base.startsWith("invoke_")) { + if (import->base.startsWith(INVOKE_PREFIX)) { if (invokeFuncs.insert(import->base.str).second) { meta << nextElement() << '"' << import->base.str << '"'; } diff --git a/test/lld/em_asm_table.wast b/test/lld/em_asm_table.wast index c35ebe577..673e27585 100644 --- a/test/lld/em_asm_table.wast +++ b/test/lld/em_asm_table.wast @@ -4,10 +4,18 @@ (import "env" "memory" (memory $2 8192)) (import "env" "emscripten_log" (func $fimport$0 (param i32 i32))) (import "env" "emscripten_asm_const_int" (func $fimport$1 (param i32 i32 i32) (result i32))) + (import "env" "__invoke_i32_i8*_i8*_..." (func $__invoke_i32_i8*_i8*_... (param i32 i32 i32 i32) (result i32))) + (data (i32.const 1024) "{ console.log(\"hello world\"); }\00") (table $0 159609 funcref) (elem (i32.const 1) $fimport$0 $fimport$1) (global $global$0 (mut i32) (i32.const 1024)) (global $global$1 i32 (i32.const 1048)) (export "__data_end" (global $global$1)) + (export "main" (func $main)) + (func $main + (drop + (call $__invoke_i32_i8*_i8*_... (i32.const 2) (i32.const 1024) (i32.const 13) (i32.const 27)) + ) + ) ) diff --git a/test/lld/em_asm_table.wast.out b/test/lld/em_asm_table.wast.out index 48d02d208..80f8ee4c8 100644 --- a/test/lld/em_asm_table.wast.out +++ b/test/lld/em_asm_table.wast.out @@ -1,26 +1,49 @@ (module (type $0 (func (param i32 i32))) (type $1 (func (param i32 i32 i32) (result i32))) - (type $FUNCSIG$vii (func (param i32 i32))) + (type $FUNCSIG$iiiii (func (param i32 i32 i32 i32) (result i32))) + (type $FUNCSIG$v (func)) (type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32))) + (type $FUNCSIG$vii (func (param i32 i32))) (import "env" "memory" (memory $0 8192)) + (data (i32.const 1024) "{ console.log(\"hello world\"); }\00") (import "env" "emscripten_log" (func $fimport$0 (param i32 i32))) + (import "env" "invoke_iiii" (func $invoke_iiii (param i32 i32 i32 i32) (result i32))) (import "env" "emscripten_asm_const_iii" (func $emscripten_asm_const_iii (param i32 i32 i32) (result i32))) - (table $0 159609 funcref) - (elem (i32.const 1) $fimport$0 $emscripten_asm_const_iii) + (table $0 159610 funcref) + (elem (i32.const 1) $fimport$0 $emscripten_asm_const_iii $emscripten_asm_const_iii) (global $global$0 (mut i32) (i32.const 1024)) (global $global$1 i32 (i32.const 1048)) (export "__data_end" (global $global$1)) + (export "main" (func $main)) + (export "dynCall_iiii" (func $dynCall_iiii)) (export "stackSave" (func $stackSave)) (export "stackAlloc" (func $stackAlloc)) (export "stackRestore" (func $stackRestore)) (export "__growWasmMemory" (func $__growWasmMemory)) (export "dynCall_vii" (func $dynCall_vii)) - (export "dynCall_iiii" (func $dynCall_iiii)) - (func $stackSave (; 2 ;) (result i32) + (func $main (; 3 ;) (type $FUNCSIG$v) + (drop + (call $invoke_iiii + (i32.const 3) + (i32.const 0) + (i32.const 13) + (i32.const 27) + ) + ) + ) + (func $dynCall_iiii (; 4 ;) (param $fptr i32) (param $0 i32) (param $1 i32) (param $2 i32) (result i32) + (call_indirect (type $FUNCSIG$iiii) + (local.get $0) + (local.get $1) + (local.get $2) + (local.get $fptr) + ) + ) + (func $stackSave (; 5 ;) (result i32) (global.get $global$0) ) - (func $stackAlloc (; 3 ;) (param $0 i32) (result i32) + (func $stackAlloc (; 6 ;) (param $0 i32) (result i32) (local $1 i32) (global.set $global$0 (local.tee $1 @@ -35,66 +58,64 @@ ) (local.get $1) ) - (func $stackRestore (; 4 ;) (param $0 i32) + (func $stackRestore (; 7 ;) (param $0 i32) (global.set $global$0 (local.get $0) ) ) - (func $__growWasmMemory (; 5 ;) (param $newSize i32) (result i32) + (func $__growWasmMemory (; 8 ;) (param $newSize i32) (result i32) (memory.grow (local.get $newSize) ) ) - (func $dynCall_vii (; 6 ;) (param $fptr i32) (param $0 i32) (param $1 i32) + (func $dynCall_vii (; 9 ;) (param $fptr i32) (param $0 i32) (param $1 i32) (call_indirect (type $FUNCSIG$vii) (local.get $0) (local.get $1) (local.get $fptr) ) ) - (func $dynCall_iiii (; 7 ;) (param $fptr i32) (param $0 i32) (param $1 i32) (param $2 i32) (result i32) - (call_indirect (type $FUNCSIG$iiii) - (local.get $0) - (local.get $1) - (local.get $2) - (local.get $fptr) - ) - ) ) (; --BEGIN METADATA -- { + "asmConsts": { + "0": ["{ console.log(\"hello world\"); }", ["iii"], [""]] + }, "staticBump": 480, - "tableSize": 159609, + "tableSize": 159610, "declares": [ "emscripten_log" ], "externs": [ ], "implementedFunctions": [ + "_main", + "_dynCall_iiii", "_stackSave", "_stackAlloc", "_stackRestore", "___growWasmMemory", - "_dynCall_vii", - "_dynCall_iiii" + "_dynCall_vii" ], "exports": [ + "main", + "dynCall_iiii", "stackSave", "stackAlloc", "stackRestore", "__growWasmMemory", - "dynCall_vii", - "dynCall_iiii" + "dynCall_vii" ], "namedGlobals": { "__data_end" : "1048" }, "invokeFuncs": [ + "invoke_iiii" ], "features": [ ], - "mainReadsParams": 0 + "mainReadsParams": 1 } -- END METADATA -- ;) |