diff options
author | Alon Zakai <azakai@google.com> | 2019-11-19 08:41:25 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-11-19 08:41:25 -0800 |
commit | 365e6f239926e3da640014237b5420895ec247b9 (patch) | |
tree | 91979baf53d7a41712cd8bc2a7a9d76fc99f5ef5 /src/passes/PostEmscripten.cpp | |
parent | 9d21a951dfc60a0fed861763f50f4130dd0a42b6 (diff) | |
download | binaryen-365e6f239926e3da640014237b5420895ec247b9.tar.gz binaryen-365e6f239926e3da640014237b5420895ec247b9.tar.bz2 binaryen-365e6f239926e3da640014237b5420895ec247b9.zip |
Optimize away invoke_ calls where possible (#2442)
When we see invoke_ calls in emscripten-generated code, we know
they call into JS just to do a try-catch for exceptions. If the target being
called cannot throw, which we check in a whole-program manner, then
we can simply skip the invoke.
I confirmed that this fixes the regression in emscripten-core/emscripten#9817 (comment)
(that is, with this optimization, upstream is around as fast as fastcomp).
When we have native wasm exception handling, this can be
extended to optimize that as well.
Diffstat (limited to 'src/passes/PostEmscripten.cpp')
-rw-r--r-- | src/passes/PostEmscripten.cpp | 81 |
1 files changed, 81 insertions, 0 deletions
diff --git a/src/passes/PostEmscripten.cpp b/src/passes/PostEmscripten.cpp index 55ffd6e0c..9d23a299c 100644 --- a/src/passes/PostEmscripten.cpp +++ b/src/passes/PostEmscripten.cpp @@ -23,6 +23,8 @@ #include <ir/import-utils.h> #include <ir/localize.h> #include <ir/memory-utils.h> +#include <ir/module-utils.h> +#include <ir/table-utils.h> #include <pass.h> #include <shared-constants.h> #include <wasm-builder.h> @@ -32,6 +34,8 @@ namespace wasm { namespace { +static bool isInvoke(Name name) { return name.startsWith("invoke_"); } + struct OptimizeCalls : public WalkerPass<PostWalker<OptimizeCalls>> { bool isFunctionParallel() override { return true; } @@ -106,6 +110,83 @@ struct PostEmscripten : public Pass { // Optimize calls OptimizeCalls().run(runner, module); + + // Optimize exceptions + optimizeExceptions(runner, module); + } + + // Optimize exceptions (and setjmp) by removing unnecessary invoke* calls. + // An invoke is a call to JS with a function pointer; JS does a try-catch + // and calls the pointer, catching and reporting any error. If we know no + // exception will be thrown, we can simply skip the invoke. + void optimizeExceptions(PassRunner* runner, Module* module) { + // First, check if this code even uses invokes. + bool hasInvokes = false; + for (auto& imp : module->functions) { + if (imp->imported() && imp->module == ENV && isInvoke(imp->base)) { + hasInvokes = true; + } + } + if (!hasInvokes) { + return; + } + // Next, see if the Table is flat, which we need in order to see where + // invokes go statically. (In dynamic linking, the table is not flat, + // and we can't do this.) + FlatTable flatTable(module->table); + if (!flatTable.valid) { + return; + } + // This code has exceptions. Find functions that definitely cannot throw, + // and remove invokes to them. + struct Info + : public ModuleUtils::CallGraphPropertyAnalysis<Info>::FunctionInfo { + bool canThrow = false; + }; + ModuleUtils::CallGraphPropertyAnalysis<Info> analyzer( + *module, [&](Function* func, Info& info) { + if (func->imported()) { + // Assume any import can throw. We may want to reduce this to just + // longjmp/cxa_throw/etc. + info.canThrow = true; + } + }); + + analyzer.propagateBack([](const Info& info) { return info.canThrow; }, + [](const Info& info) { return true; }, + [](Info& info) { info.canThrow = true; }); + + // Apply the information. + struct OptimizeInvokes : public WalkerPass<PostWalker<OptimizeInvokes>> { + bool isFunctionParallel() override { return true; } + + Pass* create() override { return new OptimizeInvokes(map, flatTable); } + + std::map<Function*, Info>& map; + FlatTable& flatTable; + + OptimizeInvokes(std::map<Function*, Info>& map, FlatTable& flatTable) + : map(map), flatTable(flatTable) {} + + void visitCall(Call* curr) { + if (isInvoke(curr->target)) { + // The first operand is the function pointer index, which must be + // constant if we are to optimize it statically. + if (auto* index = curr->operands[0]->dynCast<Const>()) { + auto actualTarget = flatTable.names.at(index->value.geti32()); + if (!map[getModule()->getFunction(actualTarget)].canThrow) { + // This invoke cannot throw! Make it a direct call. + curr->target = actualTarget; + for (Index i = 0; i < curr->operands.size() - 1; i++) { + curr->operands[i] = curr->operands[i + 1]; + } + curr->operands.resize(curr->operands.size() - 1); + } + } + } + } + }; + OptimizeInvokes(analyzer.map, flatTable).run(runner, module); } }; |