Skip to content

Add --js-call-indirect option. #5864

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/asm_v_wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ std::string getSigFromStructs(Type result, const ListType& operands) {
std::string ret;
ret += getSig(result);
for (auto operand : operands) {
ret += getSig(operand.type);
ret += getSig(operand->type);
}
return ret;
}
Expand Down
1 change: 1 addition & 0 deletions src/passes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ set(passes_SOURCES
InstrumentLocals.cpp
InstrumentMemory.cpp
Intrinsics.cpp
JSCallIndirect.cpp
JSPI.cpp
LegalizeJSInterface.cpp
LimitSegments.cpp
Expand Down
101 changes: 101 additions & 0 deletions src/passes/JsCallIndirect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2017 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

//
// This pass replaces all call_indirect instructions with a call to JS side
// js_call_indirect_xxxx functions that can perform debug analysis of the
// call in question. Must run after FuncCastEmulation pass, if that is
// desired.
//

#include <string>
#include <unordered_map>
#include <unordered_set>

#include <ir/element-utils.h>
#include <ir/literal-utils.h>
#include <pass.h>
#include <wasm-builder.h>
#include <wasm.h>
#include <asm_v_wasm.h>

namespace wasm {

void setFunctionAsImported(std::unique_ptr<Function>& import, Module* m);

typedef std::unordered_map<std::string, std::unique_ptr<Function>>
JsCallIndirectMap;

struct ParallelJsCallIndirect
: public WalkerPass<PostWalker<ParallelJsCallIndirect>> {
bool isFunctionParallel() override { return true; }

std::unique_ptr<Pass> create() override {
return std::make_unique<ParallelJsCallIndirect>();
}

void visitCallIndirect(CallIndirect* curr) {
Module* module = getModule();
Builder builder(*module);
std::vector<Expression*> callArgs;
callArgs.push_back(curr->target);
callArgs.insert(
callArgs.end(), curr->operands.begin(), curr->operands.end());
std::string jsCallIndirect =
"js_call_indirect_" + getSigFromStructs(curr->type, curr->operands);
if (module->getFunctionOrNull(jsCallIndirect)) {
replaceCurrent(builder.makeCall(jsCallIndirect, callArgs, curr->type));
} else {
// The code is attempting a call_indirect via a signature that there does not exist
// any function in the whole program. We can abort here since that will not work at runtime.
// TODO: Maybe just create on the fly a "js_call_indirect_" function for this nonexisting
// signature?
replaceCurrent(builder.makeCall("abort", {}, Type::none));
}
}
};

struct JsCallIndirect : public Pass {
void run(Module* module) override {
std::unordered_set<Signature> seen_sigs;
for (std::unique_ptr<Function>& func : module->functions) {
Signature sig = func->getSig();
std::string type_str = getSig(sig.results, sig.params);
if (seen_sigs.find(sig) != seen_sigs.end())
continue;
seen_sigs.insert(sig);

std::vector<Type> params;
params.push_back(Type::i32);
for (auto t : sig.params)
params.push_back(t);
auto& import =
Builder::makeFunction(std::string("js_call_indirect_") + type_str,
Signature(Type(params), sig.results.getBasic()),
{});
setFunctionAsImported(import, module);
import->base = type_str;
module->addFunction(std::move(import));
}

// route all call_indirects to js_call_indirect_*() variants
ParallelJsCallIndirect().run(getPassRunner(), module);
}
};

Pass* createJsCallIndirectPass() { return new JsCallIndirect(); }

} // namespace wasm
42 changes: 23 additions & 19 deletions src/passes/LogExecution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,28 @@ namespace wasm {

Name LOGGER("log_execution");

void setFunctionAsImported(std::unique_ptr<Function>& import, Module* m)
{
// Import the log function from import "env" if the module
// imports other functions from that name.
for (auto& func : m->functions) {
if (func->imported() && func->module == ENV) {
import->module = func->module;
return;
}
}

// If not, then pick the import name of the first function we find.
if (!import->module) {
for (auto& func : m->functions) {
if (func->imported()) {
import->module = func->module;
return;
}
}
}
}

struct LogExecution : public WalkerPass<PostWalker<LogExecution>> {
void visitLoop(Loop* curr) { curr->body = makeLogCall(curr->body); }

Expand All @@ -59,25 +81,7 @@ struct LogExecution : public WalkerPass<PostWalker<LogExecution>> {
// Add the import
auto import =
Builder::makeFunction(LOGGER, Signature(Type::i32, Type::none), {});

// Import the log function from import "env" if the module
// imports other functions from that name.
for (auto& func : curr->functions) {
if (func->imported() && func->module == ENV) {
import->module = func->module;
break;
}
}

// If not, then pick the import name of the first function we find.
if (!import->module) {
for (auto& func : curr->functions) {
if (func->imported()) {
import->module = func->module;
break;
}
}
}
setFunctionAsImported(import, curr);

import->base = LOGGER;
curr->addFunction(std::move(import));
Expand Down
4 changes: 4 additions & 0 deletions src/passes/pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ void PassRegistry::registerPasses() {
"emulates function pointer casts, allowing incorrect indirect "
"calls to (sometimes) work",
createFuncCastEmulationPass);
registerPass("js-call-indirect",
"Redirects all call_indirects to occur from JS side, allowing "
"these to be audited e.g. for debugging purposes",
createJsCallIndirectPass);
registerPass(
"func-metrics", "reports function metrics", createFunctionMetricsPass);
registerPass("generate-dyncalls",
Expand Down
1 change: 1 addition & 0 deletions src/passes/passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Pass* createExtractFunctionPass();
Pass* createExtractFunctionIndexPass();
Pass* createFlattenPass();
Pass* createFuncCastEmulationPass();
Pass* createJsCallIndirectPass();
Pass* createFullPrinterPass();
Pass* createFunctionMetricsPass();
Pass* createGenerateDynCallsPass();
Expand Down