diff --git a/src/ir/effects.h b/src/ir/effects.h index af866b9e536..6be8f348da7 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -770,6 +770,12 @@ class EffectAnalyzer { } } void visitCallIndirect(CallIndirect* curr) { + if (auto it = parent.module.typeEffects.find(curr->heapType); + it != parent.module.typeEffects.end()) { + parent.mergeIn(*it->second); + return; + } + parent.calls = true; if (curr->isReturn) { parent.branchesOut = true; @@ -1040,6 +1046,14 @@ class EffectAnalyzer { if (trapOnNull(curr->target)) { return; } + + if (auto it = + parent.module.typeEffects.find(curr->target->type.getHeapType()); + it != parent.module.typeEffects.end()) { + parent.mergeIn(*it->second); + return; + } + if (curr->isReturn) { parent.branchesOut = true; if (parent.features.hasExceptionHandling()) { diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp index 927129a733d..5935e5187b8 100644 --- a/src/passes/GlobalEffects.cpp +++ b/src/passes/GlobalEffects.cpp @@ -25,6 +25,7 @@ #include "ir/module-utils.h" #include "pass.h" #include "support/strongly_connected_components.h" +#include "support/utilities.h" #include "wasm.h" namespace wasm { @@ -210,10 +211,13 @@ void mergeMaybeEffects(std::optional& dest, // - Merge all of the effects of functions within the CC // - Also merge the (already computed) effects of each callee CC // - Add trap effects for potentially recursive call chains -void propagateEffects(const Module& module, - const PassOptions& passOptions, - std::map& funcInfos, - const CallGraph& callGraph) { +void propagateEffects( + const Module& module, + const PassOptions& passOptions, + std::map& funcInfos, + std::unordered_map>& + typeEffects, + const CallGraph& callGraph) { // We only care about Functions that are roots, not types. // A type would be a root if a function exists with that type, but no-one // indirect calls the type. @@ -302,12 +306,21 @@ void propagateEffects(const Module& module, } // Assign each function's effects to its CC effects. - for (Function* f : ccFuncs) { - if (!ccEffects) { - funcInfos.at(f).effects = UnknownEffects; - } else { - funcInfos.at(f).effects.emplace(*ccEffects); - } + for (auto node : cc) { + std::visit(overloaded{[&](HeapType type) { + if (ccEffects != UnknownEffects) { + typeEffects[type] = + std::make_shared(*ccEffects); + } + }, + [&](Function* f) { + if (!ccEffects) { + funcInfos.at(f).effects = UnknownEffects; + } else { + funcInfos.at(f).effects.emplace(*ccEffects); + } + }}, + node); } } } @@ -331,7 +344,8 @@ struct GenerateGlobalEffects : public Pass { auto callGraph = buildCallGraph(*module, funcInfos, getPassOptions().closedWorld); - propagateEffects(*module, getPassOptions(), funcInfos, callGraph); + propagateEffects( + *module, getPassOptions(), funcInfos, module->typeEffects, callGraph); copyEffectsToFunctions(funcInfos); } diff --git a/src/support/utilities.h b/src/support/utilities.h index 3f40111c451..99d548904db 100644 --- a/src/support/utilities.h +++ b/src/support/utilities.h @@ -94,6 +94,10 @@ class Fatal { #define WASM_UNREACHABLE(msg) wasm::handle_unreachable() #endif +template struct overloaded : Ts... { + using Ts::operator()...; +}; + } // namespace wasm #endif // wasm_support_utilities_h diff --git a/src/wasm.h b/src/wasm.h index a6a32bbeb98..fb4a92c10b8 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2682,6 +2682,11 @@ class Module { std::unordered_map typeNames; std::unordered_map typeIndices; + // Potential effects for bodies of indirect calls to this type. + // TODO: make this into Type + std::unordered_map> + typeEffects; + MixedArena allocator; private: diff --git a/test/lit/passes/global-effects-closed-world-tnh.wast b/test/lit/passes/global-effects-closed-world-tnh.wast index d71fde032ea..eb91423f1db 100644 --- a/test/lit/passes/global-effects-closed-world-tnh.wast +++ b/test/lit/passes/global-effects-closed-world-tnh.wast @@ -13,22 +13,9 @@ ) ;; CHECK: (func $calls-nop-via-nullable-ref (type $1) (param $ref (ref null $nopType)) - ;; CHECK-NEXT: (call_ref $nopType - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $calls-nop-via-nullable-ref (param $ref (ref null $nopType)) (call_ref $nopType (i32.const 1) (local.get $ref)) ) - - ;; CHECK: (func $f (type $1) (param $ref (ref null $nopType)) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - (func $f (param $ref (ref null $nopType)) - ;; The only possible implementation of $nopType has no effects. - ;; $calls-nop-via-nullable-ref may trap from a null reference, but - ;; --traps-never-happen is enabled, so we're free to optimize this out. - (call $calls-nop-via-nullable-ref (local.get $ref)) - ) ) diff --git a/test/lit/passes/global-effects-closed-world.wast b/test/lit/passes/global-effects-closed-world.wast index 51858cb1f2f..72b0bae415f 100644 --- a/test/lit/passes/global-effects-closed-world.wast +++ b/test/lit/passes/global-effects-closed-world.wast @@ -13,18 +13,10 @@ ) ;; CHECK: (func $calls-nop-via-ref (type $1) (param $ref (ref $nopType)) - ;; CHECK-NEXT: (call_ref $nopType - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $calls-nop-via-ref (param $ref (ref $nopType)) ;; This can only possibly be a nop in closed-world. - ;; Ideally vacuum could optimize this out but we don't have a way to share - ;; this information with other passes today. - ;; For now, we can at least annotate that the call to this function in $f - ;; has no effects. - ;; TODO: This call_ref could be marked as having no effects, like the call below. (call_ref $nopType (i32.const 1) (local.get $ref)) ) @@ -37,27 +29,6 @@ (func $calls-nop-via-nullable-ref (param $ref (ref null $nopType)) (call_ref $nopType (i32.const 1) (local.get $ref)) ) - - - ;; CHECK: (func $f (type $1) (param $ref (ref $nopType)) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - (func $f (param $ref (ref $nopType)) - ;; $calls-nop-via-ref has no effects because we determined that it can only - ;; call $nop. We can optimize this call out. - (call $calls-nop-via-ref (local.get $ref)) - ) - - ;; CHECK: (func $g (type $2) (param $ref (ref null $nopType)) - ;; CHECK-NEXT: (call $calls-nop-via-nullable-ref - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $g (param $ref (ref null $nopType)) - ;; Similar to $f, but we may still trap here because the ref is null, so we - ;; don't optimize. - (call $calls-nop-via-nullable-ref (local.get $ref)) - ) ) ;; Same as the above but with call_indirect @@ -75,29 +46,11 @@ ) ;; CHECK: (func $calls-nop-via-ref (type $1) - ;; CHECK-NEXT: (call_indirect $0 (type $nopType) - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $calls-nop-via-ref - ;; This can only possibly be a nop in closed-world. - ;; Ideally vacuum could optimize this out but we don't have a way to share - ;; this information with other passes today. - ;; For now, we can at least annotate that the call to this function in $f - ;; has no effects. - ;; TODO: This call_ref could be marked as having no effects, like the call below. (call_indirect (type $nopType) (i32.const 1) (i32.const 0)) ) - - ;; CHECK: (func $f (type $1) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - (func $f - ;; $calls-nop-via-ref has no effects because we determined that it can only - ;; call $nop. We can optimize this call out. - (call $calls-nop-via-ref) - ) ) (module @@ -125,18 +78,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $calls-effectful-function-via-ref (param $ref (ref $maybe-has-effects)) - (call_ref $maybe-has-effects (i32.const 1) (local.get $ref)) - ) - - ;; CHECK: (func $f (type $1) (param $ref (ref $maybe-has-effects)) - ;; CHECK-NEXT: (call $calls-effectful-function-via-ref - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $f (param $ref (ref $maybe-has-effects)) - ;; This may be a nop or it may trap depending on the ref. + ;; This may be a nop or it may trap depending on the ref ;; We don't know so don't optimize it out. - (call $calls-effectful-function-via-ref (local.get $ref)) + (call_ref $maybe-has-effects (i32.const 1) (local.get $ref)) ) ) @@ -168,16 +112,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $calls-effectful-function-via-ref - (call_indirect (type $maybe-has-effects) (i32.const 1) (i32.const 1)) - ) - - ;; CHECK: (func $f (type $1) - ;; CHECK-NEXT: (call $calls-effectful-function-via-ref) - ;; CHECK-NEXT: ) - (func $f ;; This may be a nop or it may trap depending on the ref. ;; We don't know so don't optimize it out. - (call $calls-effectful-function-via-ref) + (call_indirect (type $maybe-has-effects) (i32.const 1) (i32.const 1)) ) ) @@ -186,12 +123,12 @@ (type $uninhabited (func (param i32))) ;; CHECK: (func $calls-uninhabited (type $1) (param $ref (ref $uninhabited)) - ;; CHECK-NEXT: (call_ref $uninhabited - ;; CHECK-NEXT: (i32.const 1) - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $calls-uninhabited (param $ref (ref $uninhabited)) + ;; There's no function with this type, so it's impossible to create a ref to + ;; call this function with and there are no effects to aggregate. + ;; Remove this call. (call_ref $uninhabited (i32.const 1) (local.get $ref)) ) @@ -206,28 +143,6 @@ ;; TODO: try to optimize this to (unreachable) (call_ref $uninhabited (i32.const 1) (local.get $ref)) ) - - - ;; CHECK: (func $f (type $1) (param $ref (ref $uninhabited)) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - (func $f (param $ref (ref $uninhabited)) - ;; There's no function with this type, so it's impossible to create a ref to - ;; call this function with and there are no effects to aggregate. - ;; Remove this call. - (call $calls-uninhabited (local.get $ref)) - ) - - ;; CHECK: (func $g (type $2) (param $ref (ref null $uninhabited)) - ;; CHECK-NEXT: (call $calls-nullable-uninhabited - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $g (param $ref (ref null $uninhabited)) - ;; Similar to above but we have a nullable reference, so we may trap and - ;; can't optimize the call out. - (call $calls-nullable-uninhabited (local.get $ref)) - ) ) (module @@ -250,27 +165,19 @@ (unreachable) ) - ;; CHECK: (func $calls-ref-with-supertype (type $1) (param $func (ref $super)) + ;; CHECK: (func $calls-ref-with-supertype (type $2) (param $func (ref $super)) ;; CHECK-NEXT: (call_ref $super ;; CHECK-NEXT: (local.get $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $calls-ref-with-supertype (param $func (ref $super)) - (call_ref $super (local.get $func)) - ) - - ;; CHECK: (func $f (type $1) (param $func (ref $super)) - ;; CHECK-NEXT: (call $calls-ref-with-supertype - ;; CHECK-NEXT: (local.get $func) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $f (param $func (ref $super)) ;; Check that we account for subtyping correctly. - ;; $super has no effects (i.e. the union of all effects of functions with - ;; this type is empty). However, $sub does have effects, and we can call_ref - ;; with that subtype, so we need to include the unreachable effect and we - ;; can't optimize out this call. - (call $calls-ref-with-supertype (local.get $func)) + ;; The type $func-with-sub-param (the supertype) has no effects (i.e. the + ;; union of all effects of functions with this type is empty). + ;; However, a subtype of $func-with-sub-param ($func-with-super-param) does + ;; have effects, and we can call_ref with that subtype, so we need to + ;; include the unreachable effect and we can't optimize out this call. + (call_ref $super (local.get $func)) ) ) @@ -297,7 +204,7 @@ (unreachable) ) - ;; CHECK: (func $calls-ref-with-supertype (type $1) (param $func (ref (exact $super))) + ;; CHECK: (func $calls-ref-with-supertype (type $2) (param $func (ref (exact $super))) ;; CHECK-NEXT: (call_ref $super ;; CHECK-NEXT: (local.get $func) ;; CHECK-NEXT: ) @@ -305,15 +212,6 @@ (func $calls-ref-with-supertype (param $func (ref (exact $super))) (call_ref $super (local.get $func)) ) - - ;; CHECK: (func $f (type $1) (param $func (ref (exact $super))) - ;; CHECK-NEXT: (call $calls-ref-with-supertype - ;; CHECK-NEXT: (local.get $func) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $f (param $func (ref (exact $super))) - (call $calls-ref-with-supertype (local.get $func)) - ) ) (module @@ -340,20 +238,11 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $calls-type-with-effects-but-not-addressable (param $ref (ref $only-has-effects-in-not-addressable-function)) - (call_ref $only-has-effects-in-not-addressable-function (i32.const 1) (local.get $ref)) - ) - - ;; CHECK: (func $f (type $1) (param $ref (ref $only-has-effects-in-not-addressable-function)) - ;; CHECK-NEXT: (call $calls-type-with-effects-but-not-addressable - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $f (param $ref (ref $only-has-effects-in-not-addressable-function)) ;; The type $has-effects-but-not-exported doesn't have an address because ;; it's not exported and it's never the target of a ref.func. ;; We should be able to determine that $ref can only point to $nop. ;; TODO: Only aggregate effects from functions that are addressed. - (call $calls-type-with-effects-but-not-addressable (local.get $ref)) + (call_ref $only-has-effects-in-not-addressable-function (i32.const 1) (local.get $ref)) ) ) @@ -421,18 +310,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $indirect-calls (param $ref (ref $t)) - (call_ref $t (i32.const 1) (local.get $ref)) - ) - - ;; CHECK: (func $f (type $1) (param $ref (ref $t)) - ;; CHECK-NEXT: (call $indirect-calls - ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $f (param $ref (ref $t)) ;; $indirect-calls might end up calling an imported function, ;; so we don't know anything about effects here - (call $indirect-calls (local.get $ref)) + (call_ref $t (i32.const 1) (local.get $ref)) ) ) @@ -450,15 +330,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $calls-unreachable (export "calls-unreachable") - (call_ref $t (unreachable)) - ) - - ;; CHECK: (func $f (type $0) - ;; CHECK-NEXT: (call $calls-unreachable) - ;; CHECK-NEXT: ) - (func $f ;; $t looks like it has no effects, but unreachable is passed in, ;; so preserve the trap. - (call $calls-unreachable) + (call_ref $t (unreachable)) ) )