summaryrefslogtreecommitdiff
path: root/src/passes/PostEmscripten.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/passes/PostEmscripten.cpp')
-rw-r--r--src/passes/PostEmscripten.cpp81
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);
}
};