Skip to content

Commit 12add6f

Browse files
authored
Fix EM_ASM not working with setjmp/longjmp (#2283)
1 parent ab3a1f6 commit 12add6f

File tree

3 files changed

+187
-53
lines changed

3 files changed

+187
-53
lines changed

src/wasm/wasm-emscripten.cpp

Lines changed: 135 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "wasm-emscripten.h"
1818

19+
#include <functional>
1920
#include <sstream>
2021

2122
#include "asm_v_wasm.h"
@@ -32,6 +33,7 @@ namespace wasm {
3233

3334
cashew::IString EMSCRIPTEN_ASM_CONST("emscripten_asm_const");
3435
cashew::IString EM_JS_PREFIX("__em_js__");
36+
static const char* INVOKE_PREFIX = "invoke_";
3537

3638
static Name STACK_SAVE("stackSave");
3739
static Name STACK_RESTORE("stackRestore");
@@ -672,6 +674,12 @@ struct AsmConstWalker : public LinearExecutionWalker<AsmConstWalker> {
672674
std::set<std::string> allSigs;
673675
// last sets in the current basic block, per index
674676
std::map<Index, LocalSet*> sets;
677+
// table indices that are calls to emscripten_asm_const_*
678+
std::map<Index, Name> asmTable;
679+
// cache used by tableIndexForName
680+
std::map<Name, Literal> tableIndices;
681+
// first available index after the table segment for each segment
682+
std::vector<int32_t> tableOffsets;
675683

676684
AsmConstWalker(Module& _wasm)
677685
: wasm(_wasm), segmentOffsets(getSegmentOffsets(wasm)) {}
@@ -692,7 +700,14 @@ struct AsmConstWalker : public LinearExecutionWalker<AsmConstWalker> {
692700
void queueImport(Name importName, std::string baseSig);
693701
void addImports();
694702

703+
Index resolveConstIndex(Expression* arg,
704+
std::function<void(Expression*)> reportError);
705+
Const* resolveConstAddr(Expression* arg, const Name& target);
706+
void prepareAsmIndices(Table* table);
707+
Literal tableIndexForName(Name name);
708+
695709
std::vector<std::unique_ptr<Function>> queuedImports;
710+
std::vector<Name> queuedTableEntries;
696711
};
697712

698713
void AsmConstWalker::noteNonLinear(Expression* curr) {
@@ -702,45 +717,115 @@ void AsmConstWalker::noteNonLinear(Expression* curr) {
702717

703718
void AsmConstWalker::visitLocalSet(LocalSet* curr) { sets[curr->index] = curr; }
704719

720+
Const* AsmConstWalker::resolveConstAddr(Expression* arg, const Name& target) {
721+
while (!arg->dynCast<Const>()) {
722+
if (auto* get = arg->dynCast<LocalGet>()) {
723+
// The argument may be a local.get, in which case, the last set in this
724+
// basic block has the value.
725+
auto* set = sets[get->index];
726+
if (set) {
727+
assert(set->index == get->index);
728+
arg = set->value;
729+
}
730+
} else if (auto* value = arg->dynCast<Binary>()) {
731+
// In the dynamic linking case the address of the string constant
732+
// is the result of adding its offset to __memory_base.
733+
// In this case are only looking for the offset with the data segment so
734+
// the RHS of the addition is just what we want.
735+
assert(value->op == AddInt32);
736+
arg = value->right;
737+
} else {
738+
Fatal() << "Unexpected arg0 type (" << getExpressionName(arg)
739+
<< ") in call to to: " << target;
740+
}
741+
}
742+
return arg->cast<Const>();
743+
}
744+
745+
Index AsmConstWalker::resolveConstIndex(
746+
Expression* arg, std::function<void(Expression*)> reportError) {
747+
while (!arg->is<Const>()) {
748+
if (auto* get = arg->dynCast<LocalGet>()) {
749+
// The argument may be a local.get, in which case, the last set in this
750+
// basic block has the value.
751+
auto* set = sets[get->index];
752+
if (set) {
753+
assert(set->index == get->index);
754+
arg = set->value;
755+
} else {
756+
reportError(arg);
757+
return 0;
758+
}
759+
} else if (arg->is<GlobalGet>()) {
760+
// In the dynamic linking case, indices start at __table_base.
761+
// We want the value relative to __table_base.
762+
// If we are doing a global.get, assume it's __table_base, then the
763+
// offset relative to __table_base must be 0.
764+
return 0;
765+
} else {
766+
reportError(arg);
767+
return 0;
768+
}
769+
}
770+
return Index(arg->cast<Const>()->value.geti32());
771+
}
772+
705773
void AsmConstWalker::visitCall(Call* curr) {
706774
auto* import = wasm.getFunction(curr->target);
775+
if (!import->imported()) {
776+
return;
777+
}
778+
707779
// Find calls to emscripten_asm_const* functions whose first argument is
708780
// is always a string constant.
709-
if (import->imported() && import->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) {
781+
if (import->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) {
710782
auto baseSig = getSig(curr);
711783
auto sig = fixupNameWithSig(curr->target, baseSig);
712-
auto* arg = curr->operands[0];
713-
while (!arg->dynCast<Const>()) {
714-
if (auto* get = arg->dynCast<LocalGet>()) {
715-
// The argument may be a local.get, in which case, the last set in this
716-
// basic block has the value.
717-
auto* set = sets[get->index];
718-
if (set) {
719-
assert(set->index == get->index);
720-
arg = set->value;
721-
}
722-
} else if (auto* value = arg->dynCast<Binary>()) {
723-
// In the dynamic linking case the address of the string constant
724-
// is the result of adding its offset to __memory_base.
725-
// In this case are only looking for the offset with the data segment so
726-
// the RHS of the addition is just what we want.
727-
assert(value->op == AddInt32);
728-
arg = value->right;
729-
} else {
730-
if (!value) {
731-
Fatal() << "Unexpected arg0 type (" << getExpressionName(arg)
732-
<< ") in call to to: " << import->base;
733-
}
734-
}
735-
}
736-
737-
auto* value = arg->cast<Const>();
784+
auto* value = resolveConstAddr(curr->operands[0], import->base);
738785
auto code = codeForConstAddr(wasm, segmentOffsets, value);
739786
sigsForCode[code].insert(sig);
740787

741788
// Replace the first argument to the call with a Const index
742789
Builder builder(wasm);
743790
curr->operands[0] = builder.makeConst(idLiteralForCode(code));
791+
} else if (import->base.startsWith(INVOKE_PREFIX)) {
792+
// A call to emscripten_asm_const_* maybe done indirectly via one of the
793+
// invoke_* functions, in case of setjmp/longjmp, for example.
794+
// We attempt to modify the invoke_* call instead.
795+
auto idx = resolveConstIndex(curr->operands[0], [&](Expression* arg) {});
796+
797+
// If the address of the invoke call is an emscripten_asm_const_* function:
798+
if (asmTable.count(idx)) {
799+
auto* value = resolveConstAddr(curr->operands[1], asmTable[idx]);
800+
auto code = codeForConstAddr(wasm, segmentOffsets, value);
801+
802+
// Extract the base signature from the invoke_* function name.
803+
std::string baseSig(import->base.c_str() + sizeof(INVOKE_PREFIX) - 1);
804+
Name name;
805+
auto sig = fixupNameWithSig(name, baseSig);
806+
sigsForCode[code].insert(sig);
807+
808+
Builder builder(wasm);
809+
curr->operands[0] = builder.makeConst(tableIndexForName(name));
810+
curr->operands[1] = builder.makeConst(idLiteralForCode(code));
811+
}
812+
}
813+
}
814+
815+
void AsmConstWalker::prepareAsmIndices(Table* table) {
816+
for (auto& segment : table->segments) {
817+
auto idx = resolveConstIndex(segment.offset, [&](Expression* arg) {
818+
Fatal() << "Unexpected table index type (" << getExpressionName(arg)
819+
<< ") table";
820+
});
821+
for (auto& name : segment.data) {
822+
auto* func = wasm.getFunction(name);
823+
if (func->imported() && func->base.hasSubstring(EMSCRIPTEN_ASM_CONST)) {
824+
asmTable[idx] = name;
825+
}
826+
++idx;
827+
}
828+
tableOffsets.push_back(idx);
744829
}
745830
}
746831

@@ -757,6 +842,8 @@ void AsmConstWalker::visitTable(Table* curr) {
757842
}
758843

759844
void AsmConstWalker::process() {
845+
// Find table indices that point to emscripten_asm_const_* functions.
846+
prepareAsmIndices(&wasm.table);
760847
// Find and queue necessary imports
761848
walkModule(&wasm);
762849
// Add them after the walk, to avoid iterator invalidation on
@@ -812,10 +899,28 @@ void AsmConstWalker::queueImport(Name importName, std::string baseSig) {
812899
queuedImports.push_back(std::unique_ptr<Function>(import));
813900
}
814901

902+
Literal AsmConstWalker::tableIndexForName(Name name) {
903+
auto result = tableIndices.find(name);
904+
if (result != tableIndices.end()) {
905+
return result->second;
906+
}
907+
queuedTableEntries.push_back(name);
908+
return tableIndices[name] = Literal(tableOffsets[0]++);
909+
}
910+
815911
void AsmConstWalker::addImports() {
816912
for (auto& import : queuedImports) {
817913
wasm.addFunction(import.release());
818914
}
915+
916+
if (!queuedTableEntries.empty()) {
917+
assert(wasm.table.segments.size() == 1);
918+
std::vector<Name>& tableSegmentData = wasm.table.segments[0].data;
919+
for (auto& entry : queuedTableEntries) {
920+
tableSegmentData.push_back(entry);
921+
}
922+
wasm.table.initial.addr += queuedTableEntries.size();
923+
}
819924
}
820925

821926
AsmConstWalker fixEmAsmConstsAndReturnWalker(Module& wasm) {
@@ -951,7 +1056,7 @@ struct FixInvokeFunctionNamesWalker
9511056
}
9521057
std::string sigWoOrigFunc = sig.front() + sig.substr(2, sig.size() - 2);
9531058
invokeSigs.insert(sigWoOrigFunc);
954-
return Name("invoke_" + sigWoOrigFunc);
1059+
return Name(INVOKE_PREFIX + sigWoOrigFunc);
9551060
}
9561061

9571062
Name fixEmEHSjLjNames(const Name& name, const std::string& sig) {
@@ -1091,7 +1196,7 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata(
10911196
ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
10921197
if (emJsWalker.codeByName.count(import->base.str) == 0 &&
10931198
!import->base.startsWith(EMSCRIPTEN_ASM_CONST.str) &&
1094-
!import->base.startsWith("invoke_")) {
1199+
!import->base.startsWith(INVOKE_PREFIX)) {
10951200
if (declares.insert(import->base.str).second) {
10961201
meta << nextElement() << '"' << import->base.str << '"';
10971202
}
@@ -1145,7 +1250,7 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata(
11451250
meta << " \"invokeFuncs\": [";
11461251
commaFirst = true;
11471252
ModuleUtils::iterImportedFunctions(wasm, [&](Function* import) {
1148-
if (import->base.startsWith("invoke_")) {
1253+
if (import->base.startsWith(INVOKE_PREFIX)) {
11491254
if (invokeFuncs.insert(import->base.str).second) {
11501255
meta << nextElement() << '"' << import->base.str << '"';
11511256
}

test/lld/em_asm_table.wast

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,18 @@
44
(import "env" "memory" (memory $2 8192))
55
(import "env" "emscripten_log" (func $fimport$0 (param i32 i32)))
66
(import "env" "emscripten_asm_const_int" (func $fimport$1 (param i32 i32 i32) (result i32)))
7+
(import "env" "__invoke_i32_i8*_i8*_..." (func $__invoke_i32_i8*_i8*_... (param i32 i32 i32 i32) (result i32)))
8+
(data (i32.const 1024) "{ console.log(\"hello world\"); }\00")
79
(table $0 159609 funcref)
810
(elem (i32.const 1) $fimport$0 $fimport$1)
911
(global $global$0 (mut i32) (i32.const 1024))
1012
(global $global$1 i32 (i32.const 1048))
1113
(export "__data_end" (global $global$1))
14+
(export "main" (func $main))
15+
(func $main
16+
(drop
17+
(call $__invoke_i32_i8*_i8*_... (i32.const 2) (i32.const 1024) (i32.const 13) (i32.const 27))
18+
)
19+
)
1220
)
1321

0 commit comments

Comments
 (0)