diff options
author | Alon Zakai <azakai@google.com> | 2021-11-11 11:24:44 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-11 11:24:44 -0800 |
commit | 50e66800dc28d67ea1cc88172f459df1ca96507d (patch) | |
tree | d4a42612823b3ba0f986d349288bfa3fa1671a1f /test | |
parent | a113d39fe87e098c5b19ca75002b6995a3f69e3e (diff) | |
download | binaryen-50e66800dc28d67ea1cc88172f459df1ca96507d.tar.gz binaryen-50e66800dc28d67ea1cc88172f459df1ca96507d.tar.bz2 binaryen-50e66800dc28d67ea1cc88172f459df1ca96507d.zip |
DeadArgumentElimination argument subtyping: Add fixups if the param is used (#4319)
Before, if we saw a param is written, that prevented us from subtyping it:
function foo(x : oldType) {
..
x = someValue;
..
}
Even if all calls to foo send some specific struct type that we'd like to subtype
to, seeing that write stopped us. To handle such a write we need to do some
extra handling for the case in which it is written a less-specific type (that is,
if someValue is of type oldType, something like this:
function foo(x : newType) {
var x_old : oldType;
x_old = x; // copy the param to x_old, and use x_old everywhere
..
x_old = someValue;
..
}
That is, still refine the param type, but inside the function use a new local that
has the old type, and is guaranteed to validate. This PR implements that logic
so that we can optimize more cases.
To allow that, this PR avoids trying to both refine a type and remove a param as
being unused - that has annoying corner cases. If it is unused, we can simply
remove it anyhow.
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/passes/dae-gc-refine-params.wast | 157 |
1 files changed, 135 insertions, 22 deletions
diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast index 7ecbcc5c8..818c943b2 100644 --- a/test/lit/passes/dae-gc-refine-params.wast +++ b/test/lit/passes/dae-gc-refine-params.wast @@ -184,42 +184,76 @@ ;; This function is called in ways that *do* allow us to alter the types of ;; its parameters (see last function), however, we reuse the parameters by ;; writing to them, which causes problems in one case. - ;; CHECK: (func $various-params-set (param $x (ref null ${})) (param $y (ref null ${i32})) - ;; CHECK-NEXT: (drop + ;; CHECK: (func $various-params-set (param $x (ref null ${i32})) (param $y (ref null ${i32})) + ;; CHECK-NEXT: (local $2 (ref null ${})) + ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (local.get $y) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (ref.null ${}) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $y - ;; CHECK-NEXT: (ref.null ${i32_i64}) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.null ${}) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (ref.null ${i32_i64}) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; NOMNL: (func $various-params-set (type $ref?|${}|_ref?|${i32}|_=>_none) (param $x (ref null ${})) (param $y (ref null ${i32})) - ;; NOMNL-NEXT: (drop + ;; NOMNL: (func $various-params-set (type $ref?|${i32}|_ref?|${i32}|_=>_none) (param $x (ref null ${i32})) (param $y (ref null ${i32})) + ;; NOMNL-NEXT: (local $2 (ref null ${})) + ;; NOMNL-NEXT: (local.set $2 ;; NOMNL-NEXT: (local.get $x) ;; NOMNL-NEXT: ) - ;; NOMNL-NEXT: (drop - ;; NOMNL-NEXT: (local.get $y) - ;; NOMNL-NEXT: ) - ;; NOMNL-NEXT: (local.set $x - ;; NOMNL-NEXT: (ref.null ${}) - ;; NOMNL-NEXT: ) - ;; NOMNL-NEXT: (local.set $y - ;; NOMNL-NEXT: (ref.null ${i32_i64}) + ;; NOMNL-NEXT: (block + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (local.get $2) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (local.get $y) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (local.set $2 + ;; NOMNL-NEXT: (ref.null ${}) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (local.get $2) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (local.set $y + ;; NOMNL-NEXT: (ref.null ${i32_i64}) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (drop + ;; NOMNL-NEXT: (local.get $y) + ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) (func $various-params-set (param $x (ref null ${})) (param $y (ref null ${})) ;; "Use" the locals to avoid other optimizations kicking in. (drop (local.get $x)) (drop (local.get $y)) - ;; Write to $x in a way that prevents us making the type more specific. + ;; Write to $x a value that will not fit in the refined type, which will + ;; force us to do a fixup: the param will get the new type, and a new local + ;; will stay at the old type, and we will use that local throughout the + ;; function. (local.set $x (ref.null ${})) - ;; Write to $y in a way that still allows us to make the type more specific. + (drop + (local.get $x) + ) + ;; Write to $y in a way that does not cause any issue, and we should not do + ;; any fixup while we refine the type. (local.set $y (ref.null ${i32_i64})) + (drop + (local.get $y) + ) ) ;; CHECK: (func $call-various-params-tee @@ -397,4 +431,83 @@ ;; "Use" the local to avoid other optimizations kicking in. (drop (local.get $x)) ) + + ;; CHECK: (func $unused-and-refinable + ;; CHECK-NEXT: (local $0 (ref null data)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $unused-and-refinable (type $none_=>_none) + ;; NOMNL-NEXT: (local $0 (ref null data)) + ;; NOMNL-NEXT: (nop) + ;; NOMNL-NEXT: ) + (func $unused-and-refinable (param $0 dataref) + ;; This function does not use $0. It is called with ${}, so it is also + ;; a parameter whose type we can refine. Do not do both operations: instead, + ;; just remove it because it is ignored, without altering the type (handling + ;; both operations would introduce some corner cases, and it just isn't worth + ;; handling them if the param is completely unused anyhow). We should see in + ;; the test output that the local $0 (the unused param) becomes a local + ;; because it is unused, and that local does *not* have its type refined to + ;; ${} (it will however be changed to be nullable, which it must be as a + ;; local). + ) + + ;; CHECK: (func $call-unused-and-refinable + ;; CHECK-NEXT: (call $unused-and-refinable) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $call-unused-and-refinable (type $none_=>_none) + ;; NOMNL-NEXT: (call $unused-and-refinable) + ;; NOMNL-NEXT: ) + (func $call-unused-and-refinable + (call $unused-and-refinable + (struct.new_default ${}) + ) + ) + + ;; CHECK: (func $non-nullable-fixup (param $0 (ref ${})) + ;; CHECK-NEXT: (local $1 (ref null data)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $non-nullable-fixup (type $ref|${}|_=>_none) (param $0 (ref ${})) + ;; NOMNL-NEXT: (local $1 (ref null data)) + ;; NOMNL-NEXT: (local.set $1 + ;; NOMNL-NEXT: (local.get $0) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: (local.set $1 + ;; NOMNL-NEXT: (ref.as_non_null + ;; NOMNL-NEXT: (local.get $1) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $non-nullable-fixup (param $0 dataref) + ;; Use the param to avoid other opts removing it, and to force us to do a + ;; fixup when we refine the param's type. When doing so, we must handle the + ;; fact that the new local's type is non-nullable. + (local.set $0 + (local.get $0) + ) + ) + + ;; CHECK: (func $call-non-nullable-fixup + ;; CHECK-NEXT: (call $non-nullable-fixup + ;; CHECK-NEXT: (struct.new_default ${}) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; NOMNL: (func $call-non-nullable-fixup (type $none_=>_none) + ;; NOMNL-NEXT: (call $non-nullable-fixup + ;; NOMNL-NEXT: (struct.new_default ${}) + ;; NOMNL-NEXT: ) + ;; NOMNL-NEXT: ) + (func $call-non-nullable-fixup + (call $non-nullable-fixup + (struct.new_default ${}) + ) + ) ) |