summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/possible-contents.cpp65
-rw-r--r--src/passes/RemoveUnusedModuleElements.cpp4
-rw-r--r--test/lit/passes/gufa.wast146
3 files changed, 198 insertions, 17 deletions
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp
index 3677516b3..67891f45f 100644
--- a/src/ir/possible-contents.cpp
+++ b/src/ir/possible-contents.cpp
@@ -502,8 +502,9 @@ struct InfoCollector
// Calls send values to params in their possible targets, and receive
// results.
- void visitCall(Call* curr) {
- auto* target = getModule()->getFunction(curr->target);
+
+ template<typename T> void handleDirectCall(T* curr, Name targetName) {
+ auto* target = getModule()->getFunction(targetName);
handleCall(
curr,
[&](Index i) {
@@ -513,9 +514,7 @@ struct InfoCollector
return ResultLocation{target, i};
});
}
- void visitCallIndirect(CallIndirect* curr) {
- // TODO: the table identity could also be used here
- auto targetType = curr->heapType;
+ template<typename T> void handleIndirectCall(T* curr, HeapType targetType) {
handleCall(
curr,
[&](Index i) {
@@ -525,21 +524,55 @@ struct InfoCollector
return SignatureResultLocation{targetType, i};
});
}
- void visitCallRef(CallRef* curr) {
- auto targetType = curr->target->type;
+ template<typename T> void handleIndirectCall(T* curr, Type targetType) {
+ // If the type is unreachable, nothing can be called (and there is no heap
+ // type to get).
if (targetType != Type::unreachable) {
- auto heapType = targetType.getHeapType();
- handleCall(
- curr,
- [&](Index i) {
- return SignatureParamLocation{heapType, i};
- },
- [&](Index i) {
- return SignatureResultLocation{heapType, i};
- });
+ handleIndirectCall(curr, targetType.getHeapType());
}
}
+ void visitCall(Call* curr) {
+ Name targetName;
+ if (!Intrinsics(*getModule()).isCallWithoutEffects(curr)) {
+ // This is just a normal call.
+ handleDirectCall(curr, curr->target);
+ return;
+ }
+ // A call-without-effects receives a function reference and calls it, the
+ // same as a CallRef. When we have a flag for non-closed-world, we should
+ // handle this automatically by the reference flowing out to an import,
+ // which is what binaryen intrinsics look like. For now, to support use
+ // cases of a closed world but that also use this intrinsic, handle the
+ // intrinsic specifically here. (Without that, the closed world assumption
+ // makes us ignore the function ref that flows to an import, so we are not
+ // aware that it is actually called.)
+ auto* target = curr->operands.back();
+ if (auto* refFunc = target->dynCast<RefFunc>()) {
+ // We can see exactly where this goes.
+ handleDirectCall(curr, refFunc->func);
+ } else {
+ // We can't see where this goes. We must be pessimistic and assume it
+ // can call anything of the proper type, the same as a CallRef. (We could
+ // look at the possible contents of |target| during the flow, but that
+ // would require special logic like we have for RefCast etc., and the
+ // intrinsics will be lowered away anyhow, so just running after that is
+ // a workaround.)
+ handleIndirectCall(curr, target->type);
+ }
+ }
+ void visitCallIndirect(CallIndirect* curr) {
+ // TODO: the table identity could also be used here
+ // TODO: optimize the call target like CallRef
+ handleIndirectCall(curr, curr->heapType);
+ }
+ void visitCallRef(CallRef* curr) {
+ // TODO: Optimize like RefCast etc.: the values reaching us depend on the
+ // possible values of |target| (which might be nothing, or might be a
+ // constant function).
+ handleIndirectCall(curr, curr->target->type);
+ }
+
// Creates a location for a null of a particular type and adds a root for it.
// Such roots are where the default value of an i32 local comes from, or the
// value in a ref.null.
diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp
index 9268232b4..7116475cd 100644
--- a/src/passes/RemoveUnusedModuleElements.cpp
+++ b/src/passes/RemoveUnusedModuleElements.cpp
@@ -132,7 +132,9 @@ struct ReachabilityAnalyzer : public PostWalker<ReachabilityAnalyzer> {
// handle this automatically by the reference flowing out to an import,
// which is what binaryen intrinsics look like. For now, to support use
// cases of a closed world but that also use this intrinsic, handle the
- // intrinsic specifically here.
+ // intrinsic specifically here. (Without that, the closed world assumption
+ // makes us ignore the function ref that flows to an import, so we are not
+ // aware that it is actually called.)
auto* target = curr->operands.back();
if (auto* refFunc = target->dynCast<RefFunc>()) {
// We can see exactly where this goes.
diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast
index 01b83ce06..cf8e36097 100644
--- a/test/lit/passes/gufa.wast
+++ b/test/lit/passes/gufa.wast
@@ -920,3 +920,149 @@
)
)
)
+
+;; The call.without.effects intrinsic does a call to the reference given to it,
+;; but for now other imports do not (until we add a flag for closed-world).
+(module
+ ;; CHECK: (type $A (func (param i32)))
+ (type $A (func (param i32)))
+
+ ;; CHECK: (type $i32_funcref_=>_none (func (param i32 funcref)))
+
+ ;; CHECK: (type $funcref_=>_none (func (param funcref)))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param i32 funcref)))
+ (import "binaryen-intrinsics" "call.without.effects"
+ (func $call-without-effects (param i32 funcref)))
+
+ ;; CHECK: (import "other" "import" (func $other-import (param funcref)))
+ (import "other" "import"
+ (func $other-import (param funcref)))
+
+ ;; CHECK: (elem declare func $target-drop $target-keep)
+
+ ;; CHECK: (export "foo" (func $foo))
+
+ ;; CHECK: (func $foo
+ ;; CHECK-NEXT: (call $call-without-effects
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (ref.func $target-keep)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $other-import
+ ;; CHECK-NEXT: (ref.func $target-drop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (export "foo")
+ ;; Calling the intrinsic with a reference is considered a call of the
+ ;; reference, so $target-keep's code is reachable. We should leave it alone,
+ ;; but we can put an unreachable in $target-drop.
+ (call $call-without-effects
+ (i32.const 1)
+ (ref.func $target-keep)
+ )
+ ;; The other import is not enough to keep $target-drop alive.
+ (call $other-import
+ (ref.func $target-drop)
+ )
+ )
+
+ ;; CHECK: (func $target-keep (param $x i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target-keep (type $A) (param $x i32)
+ (drop
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $target-drop (param $x i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target-drop (type $A) (param $x i32)
+ (drop
+ (local.get $x)
+ )
+ )
+)
+
+;; As above, but now the call to the intrinsic does not let us see the exact
+;; function being called.
+(module
+ ;; CHECK: (type $A (func (param i32)))
+ (type $A (func (param i32)))
+
+ ;; CHECK: (type $i32_funcref_=>_none (func (param i32 funcref)))
+
+ ;; CHECK: (type $funcref_=>_none (func (param funcref)))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param i32 funcref)))
+ (import "binaryen-intrinsics" "call.without.effects"
+ (func $call-without-effects (param i32 funcref)))
+
+ ;; CHECK: (import "other" "import" (func $other-import (param funcref)))
+ (import "other" "import"
+ (func $other-import (param funcref)))
+
+ ;; CHECK: (elem declare func $target-keep $target-keep-2)
+
+ ;; CHECK: (export "foo" (func $foo))
+
+ ;; CHECK: (func $foo
+ ;; CHECK-NEXT: (call $call-without-effects
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (ref.null $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $target-keep)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $other-import
+ ;; CHECK-NEXT: (ref.func $target-keep-2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo (export "foo")
+ ;; Call the intrinsic without a RefFunc. All we infer here is the type,
+ ;; which means we must assume anything with type $A (and a reference) can be
+ ;; called, which will keep alive the bodies of both $target-keep and
+ ;; $target-keep-2 - no unreachables will be placed in either one.
+ (call $call-without-effects
+ (i32.const 1)
+ (ref.null $A)
+ )
+ (drop
+ (ref.func $target-keep)
+ )
+ (call $other-import
+ (ref.func $target-keep-2)
+ )
+ )
+
+ ;; CHECK: (func $target-keep (param $x i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target-keep (type $A) (param $x i32)
+ (drop
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $target-keep-2 (param $x i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target-keep-2 (type $A) (param $x i32)
+ (drop
+ (local.get $x)
+ )
+ )
+)