diff options
-rw-r--r-- | src/passes/PostEmscripten.cpp | 81 | ||||
-rw-r--r-- | test/passes/post-emscripten.txt | 89 | ||||
-rw-r--r-- | test/passes/post-emscripten.wast | 71 |
3 files changed, 238 insertions, 3 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); } }; diff --git a/test/passes/post-emscripten.txt b/test/passes/post-emscripten.txt index c6b53e43c..ee4eb4976 100644 --- a/test/passes/post-emscripten.txt +++ b/test/passes/post-emscripten.txt @@ -1,10 +1,15 @@ (module (type $0 (func (param i32))) (type $FUNCSIG$ddd (func (param f64 f64) (result f64))) + (type $FUNCSIG$viif (func (param i32 i32 f32))) (type $FUNCSIG$v (func)) + (type $FUNCSIG$vif (func (param i32 f32))) (import "global.Math" "pow" (func $Math_pow (param f64 f64) (result f64))) + (import "env" "invoke_vif" (func $invoke_vif (param i32 i32 f32))) (memory $0 256 256) - (func $pow2 (; 1 ;) (type $FUNCSIG$v) + (table $0 7 7 funcref) + (elem (i32.const 0) $pow2 $pow.2 $exc $other_safe $other_unsafe $deep_safe $deep_unsafe) + (func $pow2 (; 2 ;) (type $FUNCSIG$v) (local $x f64) (local $y f64) (local $2 f64) @@ -55,7 +60,7 @@ ) ) ) - (func $pow.2 (; 2 ;) (type $FUNCSIG$v) + (func $pow.2 (; 3 ;) (type $FUNCSIG$v) (drop (f64.sqrt (f64.const 1) @@ -68,4 +73,84 @@ ) ) ) + (func $exc (; 4 ;) (type $FUNCSIG$v) + (call $other_safe + (i32.const 42) + (f32.const 3.141590118408203) + ) + (call $invoke_vif + (i32.const 4) + (i32.const 55) + (f32.const 2.1828181743621826) + ) + (call $deep_safe + (i32.const 100) + (f32.const 1.1109999418258667) + ) + (call $invoke_vif + (i32.const 6) + (i32.const 999) + (f32.const 1.4140000343322754) + ) + (call $invoke_vif + (i32.add + (i32.const 1) + (i32.const 1) + ) + (i32.const 42) + (f32.const 3.141590118408203) + ) + ) + (func $other_safe (; 5 ;) (type $FUNCSIG$vif) (param $0 i32) (param $1 f32) + (nop) + ) + (func $other_unsafe (; 6 ;) (type $FUNCSIG$vif) (param $0 i32) (param $1 f32) + (drop + (call $Math_pow + (f64.const 1) + (f64.const 3) + ) + ) + ) + (func $deep_safe (; 7 ;) (type $FUNCSIG$vif) (param $0 i32) (param $1 f32) + (call $other_safe + (unreachable) + (unreachable) + ) + ) + (func $deep_unsafe (; 8 ;) (type $FUNCSIG$vif) (param $0 i32) (param $1 f32) + (call $other_unsafe + (unreachable) + (unreachable) + ) + ) +) +(module + (type $FUNCSIG$v (func)) + (func $call (; 0 ;) (type $FUNCSIG$v) + (call $call) + ) +) +(module + (type $0 (func (param i32))) + (type $FUNCSIG$ddd (func (param f64 f64) (result f64))) + (type $FUNCSIG$viif (func (param i32 i32 f32))) + (type $FUNCSIG$v (func)) + (type $FUNCSIG$vif (func (param i32 f32))) + (import "env" "glob" (global $glob i32)) + (import "global.Math" "pow" (func $Math_pow (param f64 f64) (result f64))) + (import "env" "invoke_vif" (func $invoke_vif (param i32 i32 f32))) + (memory $0 256 256) + (table $0 7 7 funcref) + (elem (global.get $glob) $other_safe) + (func $exc (; 2 ;) (type $FUNCSIG$v) + (call $invoke_vif + (i32.const 3) + (i32.const 42) + (f32.const 3.141590118408203) + ) + ) + (func $other_safe (; 3 ;) (type $FUNCSIG$vif) (param $0 i32) (param $1 f32) + (nop) + ) ) diff --git a/test/passes/post-emscripten.wast b/test/passes/post-emscripten.wast index f3656eebd..fe60858f7 100644 --- a/test/passes/post-emscripten.wast +++ b/test/passes/post-emscripten.wast @@ -1,7 +1,10 @@ (module - (memory 256 256) (type $0 (func (param i32))) (import "global.Math" "pow" (func $Math_pow (param f64 f64) (result f64))) + (import "env" "invoke_vif" (func $invoke_vif (param i32 i32 f32))) + (memory 256 256) + (table 7 7 funcref) + (elem (i32.const 0) $pow2 $pow.2 $exc $other_safe $other_unsafe $deep_safe $deep_unsafe) (func $pow2 (local $x f64) (local $y f64) @@ -57,4 +60,70 @@ ) ) ) + (func $exc + (call $invoke_vif + (i32.const 3) ;; other_safe() + (i32.const 42) + (f32.const 3.14159) + ) + (call $invoke_vif + (i32.const 4) ;; other_unsafe() + (i32.const 55) + (f32.const 2.18281828) + ) + (call $invoke_vif + (i32.const 5) ;; deep_safe() + (i32.const 100) + (f32.const 1.111) + ) + (call $invoke_vif + (i32.const 6) ;; deep_unsafe() + (i32.const 999) + (f32.const 1.414) + ) + (call $invoke_vif + (i32.add (i32.const 1) (i32.const 1)) ;; nonconstant + (i32.const 42) + (f32.const 3.14159) + ) + ) + (func $other_safe (param i32) (param f32) + ) + (func $other_unsafe (param i32) (param f32) + (drop + (call $Math_pow + (f64.const 1) + (f64.const 3) + ) + ) + ) + (func $deep_safe (param i32) (param f32) + (call $other_safe (unreachable) (unreachable)) + ) + (func $deep_unsafe (param i32) (param f32) + (call $other_unsafe (unreachable) (unreachable)) + ) +) +(module ;; no invokes + (func $call + (call $call) + ) +) +(module + (type $0 (func (param i32))) + (import "global.Math" "pow" (func $Math_pow (param f64 f64) (result f64))) + (import "env" "invoke_vif" (func $invoke_vif (param i32 i32 f32))) + (import "env" "glob" (global $glob i32)) ;; non-constant table offset + (memory 256 256) + (table 7 7 funcref) + (elem (global.get $glob) $other_safe) + (func $exc + (call $invoke_vif + (i32.const 3) ;; other_safe() + (i32.const 42) + (f32.const 3.14159) + ) + ) + (func $other_safe (param i32) (param f32) + ) ) |