Skip to content

Commit 77000ef

Browse files
committed
Add --js-call-indirect option.
1 parent 465ebbf commit 77000ef

File tree

6 files changed

+131
-20
lines changed

6 files changed

+131
-20
lines changed

src/asm_v_wasm.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ std::string getSigFromStructs(Type result, const ListType& operands) {
4242
std::string ret;
4343
ret += getSig(result);
4444
for (auto operand : operands) {
45-
ret += getSig(operand.type);
45+
ret += getSig(operand->type);
4646
}
4747
return ret;
4848
}

src/passes/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ set(passes_SOURCES
5050
InstrumentLocals.cpp
5151
InstrumentMemory.cpp
5252
Intrinsics.cpp
53+
JSCallIndirect.cpp
5354
JSPI.cpp
5455
LegalizeJSInterface.cpp
5556
LimitSegments.cpp

src/passes/JsCallIndirect.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2017 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// This pass replaces all call_indirect instructions with a call to JS side
19+
// js_call_indirect_xxxx functions that can perform debug analysis of the
20+
// call in question. Must run after FuncCastEmulation pass, if that is
21+
// desired.
22+
//
23+
24+
#include <string>
25+
#include <unordered_map>
26+
#include <unordered_set>
27+
28+
#include <ir/element-utils.h>
29+
#include <ir/literal-utils.h>
30+
#include <pass.h>
31+
#include <wasm-builder.h>
32+
#include <wasm.h>
33+
#include <asm_v_wasm.h>
34+
35+
namespace wasm {
36+
37+
void setFunctionAsImported(std::unique_ptr<Function>& import, Module* m);
38+
39+
typedef std::unordered_map<std::string, std::unique_ptr<Function>>
40+
JsCallIndirectMap;
41+
42+
struct ParallelJsCallIndirect
43+
: public WalkerPass<PostWalker<ParallelJsCallIndirect>> {
44+
bool isFunctionParallel() override { return true; }
45+
46+
std::unique_ptr<Pass> create() override {
47+
return std::make_unique<ParallelJsCallIndirect>();
48+
}
49+
50+
void visitCallIndirect(CallIndirect* curr) {
51+
Module* module = getModule();
52+
Builder builder(*module);
53+
std::vector<Expression*> callArgs;
54+
callArgs.push_back(curr->target);
55+
callArgs.insert(
56+
callArgs.end(), curr->operands.begin(), curr->operands.end());
57+
std::string jsCallIndirect =
58+
"js_call_indirect_" + getSigFromStructs(curr->type, curr->operands);
59+
if (module->getFunctionOrNull(jsCallIndirect)) {
60+
replaceCurrent(builder.makeCall(jsCallIndirect, callArgs, curr->type));
61+
} else {
62+
// The code is attempting a call_indirect via a signature that there does not exist
63+
// any function in the whole program. We can abort here since that will not work at runtime.
64+
// TODO: Maybe just create on the fly a "js_call_indirect_" function for this nonexisting
65+
// signature?
66+
replaceCurrent(builder.makeCall("abort", {}, Type::none));
67+
}
68+
}
69+
};
70+
71+
struct JsCallIndirect : public Pass {
72+
void run(Module* module) override {
73+
std::unordered_set<Signature> seen_sigs;
74+
for (std::unique_ptr<Function>& func : module->functions) {
75+
Signature sig = func->getSig();
76+
std::string type_str = getSig(sig.results, sig.params);
77+
if (seen_sigs.find(sig) != seen_sigs.end())
78+
continue;
79+
seen_sigs.insert(sig);
80+
81+
std::vector<Type> params;
82+
params.push_back(Type::i32);
83+
for (auto t : sig.params)
84+
params.push_back(t);
85+
auto& import =
86+
Builder::makeFunction(std::string("js_call_indirect_") + type_str,
87+
Signature(Type(params), sig.results.getBasic()),
88+
{});
89+
setFunctionAsImported(import, module);
90+
import->base = type_str;
91+
module->addFunction(std::move(import));
92+
}
93+
94+
// route all call_indirects to js_call_indirect_*() variants
95+
ParallelJsCallIndirect().run(getPassRunner(), module);
96+
}
97+
};
98+
99+
Pass* createJsCallIndirectPass() { return new JsCallIndirect(); }
100+
101+
} // namespace wasm

src/passes/LogExecution.cpp

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,28 @@ namespace wasm {
3838

3939
Name LOGGER("log_execution");
4040

41+
void setFunctionAsImported(std::unique_ptr<Function>& import, Module* m)
42+
{
43+
// Import the log function from import "env" if the module
44+
// imports other functions from that name.
45+
for (auto& func : m->functions) {
46+
if (func->imported() && func->module == ENV) {
47+
import->module = func->module;
48+
return;
49+
}
50+
}
51+
52+
// If not, then pick the import name of the first function we find.
53+
if (!import->module) {
54+
for (auto& func : m->functions) {
55+
if (func->imported()) {
56+
import->module = func->module;
57+
return;
58+
}
59+
}
60+
}
61+
}
62+
4163
struct LogExecution : public WalkerPass<PostWalker<LogExecution>> {
4264
void visitLoop(Loop* curr) { curr->body = makeLogCall(curr->body); }
4365

@@ -59,25 +81,7 @@ struct LogExecution : public WalkerPass<PostWalker<LogExecution>> {
5981
// Add the import
6082
auto import =
6183
Builder::makeFunction(LOGGER, Signature(Type::i32, Type::none), {});
62-
63-
// Import the log function from import "env" if the module
64-
// imports other functions from that name.
65-
for (auto& func : curr->functions) {
66-
if (func->imported() && func->module == ENV) {
67-
import->module = func->module;
68-
break;
69-
}
70-
}
71-
72-
// If not, then pick the import name of the first function we find.
73-
if (!import->module) {
74-
for (auto& func : curr->functions) {
75-
if (func->imported()) {
76-
import->module = func->module;
77-
break;
78-
}
79-
}
80-
}
84+
setFunctionAsImported(import, curr);
8185

8286
import->base = LOGGER;
8387
curr->addFunction(std::move(import));

src/passes/pass.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ void PassRegistry::registerPasses() {
160160
"emulates function pointer casts, allowing incorrect indirect "
161161
"calls to (sometimes) work",
162162
createFuncCastEmulationPass);
163+
registerPass("js-call-indirect",
164+
"Redirects all call_indirects to occur from JS side, allowing "
165+
"these to be audited e.g. for debugging purposes",
166+
createJsCallIndirectPass);
163167
registerPass(
164168
"func-metrics", "reports function metrics", createFunctionMetricsPass);
165169
registerPass("generate-dyncalls",

src/passes/passes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Pass* createExtractFunctionPass();
4848
Pass* createExtractFunctionIndexPass();
4949
Pass* createFlattenPass();
5050
Pass* createFuncCastEmulationPass();
51+
Pass* createJsCallIndirectPass();
5152
Pass* createFullPrinterPass();
5253
Pass* createFunctionMetricsPass();
5354
Pass* createGenerateDynCallsPass();

0 commit comments

Comments
 (0)