diff options
author | Alon Zakai <azakai@google.com> | 2022-11-17 15:00:28 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-17 23:00:28 +0000 |
commit | 977d653f9801b3eedc7dd667e4068573e73d2bb5 (patch) | |
tree | 8876e96e5f31d32b6a656a627b02e635db1314af /test/lit/passes/optimize-casts.wast | |
parent | 5f5c70255cfa917efee9855ce1f8340b017e0adb (diff) | |
download | binaryen-977d653f9801b3eedc7dd667e4068573e73d2bb5.tar.gz binaryen-977d653f9801b3eedc7dd667e4068573e73d2bb5.tar.bz2 binaryen-977d653f9801b3eedc7dd667e4068573e73d2bb5.zip |
[Wasm GC] Start an OptimizeCasts pass and reuse cast values there (#5263)
(some.operation
(ref.cast .. (local.get $ref))
(local.get $ref)
)
=>
(some.operation
(local.tee $temp
(ref.cast .. (local.get $ref))
)
(local.get $temp)
)
This can help cases where we cast for some reason but happen to not use the
cast value in all places. This occurs in j2wasm in itable calls sometimes: The
this pointer is is refined, but the itable may be done with an unrefined pointer,
which is less optimizable.
So far this is just inside basic blocks, but that is enough for the cast of itable
calls and other common patterns I see.
Diffstat (limited to 'test/lit/passes/optimize-casts.wast')
-rw-r--r-- | test/lit/passes/optimize-casts.wast | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/test/lit/passes/optimize-casts.wast b/test/lit/passes/optimize-casts.wast new file mode 100644 index 000000000..449416e42 --- /dev/null +++ b/test/lit/passes/optimize-casts.wast @@ -0,0 +1,398 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --optimize-casts -all --nominal -S -o - \ +;; RUN: | filecheck %s + +(module + ;; CHECK: (type $A (struct_subtype data)) + (type $A (struct_subtype data)) + + ;; CHECK: (type $B (struct_subtype $A)) + (type $B (struct_subtype $A)) + + ;; CHECK: (func $ref.as (type $ref?|$A|_=>_none) (param $x (ref null $A)) + ;; CHECK-NEXT: (local $1 (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref.as (param $x (ref null $A)) + ;; After the first ref.as, we can use the cast value in later gets, which is + ;; more refined. + (drop + (local.get $x) + ) + (drop + (ref.as_non_null + (local.get $x) + ) + ) + (drop + (local.get $x) + ) + ;; In this case we don't really need the last ref.as here, but we leave that + ;; for later opts. + (drop + (ref.as_non_null + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $ref.as-no (type $ref|$A|_=>_none) (param $x (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref.as-no (param $x (ref $A)) + ;; As above, but the param is now non-nullable anyhow, so we should do + ;; nothing. + (drop + (local.get $x) + ) + (drop + (ref.as_non_null + (local.get $x) + ) + ) + (drop + (local.get $x) + ) + (drop + (ref.as_non_null + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $ref.cast (type $ref|data|_=>_none) (param $x (ref data)) + ;; CHECK-NEXT: (local $1 (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (ref.cast_static $A + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref.cast (param $x (ref data)) + ;; As $ref.as but with ref.casts: we should use the cast value after it has + ;; been computed, in both gets. + (drop + (ref.cast_static $A + (local.get $x) + ) + ) + (drop + (local.get $x) + ) + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $not-past-set (type $ref|data|_=>_none) (param $x (ref data)) + ;; CHECK-NEXT: (local $1 (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (ref.cast_static $A + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (call $get) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $not-past-set (param $x (ref data)) + (drop + (ref.cast_static $A + (local.get $x) + ) + ) + (drop + (local.get $x) + ) + ;; The local.set in the middle stops us from helping the last get. + (local.set $x + (call $get) + ) + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $best (type $ref|data|_=>_none) (param $x (ref data)) + ;; CHECK-NEXT: (local $1 (ref $A)) + ;; CHECK-NEXT: (local $2 (ref $B)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (ref.cast_static $A + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (ref.cast_static $B + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $best (param $x (ref data)) + (drop + (ref.cast_static $A + (local.get $x) + ) + ) + ;; Here we should use $A. + (drop + (local.get $x) + ) + (drop + (ref.cast_static $B + (local.get $x) + ) + ) + ;; Here we should use $B, which is even better. + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $best-2 (type $ref|data|_=>_none) (param $x (ref data)) + ;; CHECK-NEXT: (local $1 (ref $B)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (ref.cast_static $B + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $A + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $best-2 (param $x (ref data)) + ;; As above, but with the casts reversed. Now we should use $B in both + ;; gets. + (drop + (ref.cast_static $B + (local.get $x) + ) + ) + (drop + (local.get $x) + ) + (drop + (ref.cast_static $A + (local.get $x) + ) + ) + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $fallthrough (type $ref|data|_=>_none) (param $x (ref data)) + ;; CHECK-NEXT: (local $1 (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (ref.cast_static $A + ;; CHECK-NEXT: (block (result (ref data)) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fallthrough (param $x (ref data)) + (drop + (ref.cast_static $A + ;; We look through the block, and optimize. + (block (result (ref data)) + (local.get $x) + ) + ) + ) + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $past-basic-block (type $ref|data|_=>_none) (param $x (ref data)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $A + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $past-basic-block (param $x (ref data)) + (drop + (ref.cast_static $A + (local.get $x) + ) + ) + ;; The if means the later get is in another basic block. We do not handle + ;; this atm. + (if + (i32.const 0) + (return) + ) + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $multiple (type $ref|data|_ref|data|_=>_none) (param $x (ref data)) (param $y (ref data)) + ;; CHECK-NEXT: (local $a (ref data)) + ;; CHECK-NEXT: (local $b (ref data)) + ;; CHECK-NEXT: (local $4 (ref $A)) + ;; CHECK-NEXT: (local $5 (ref $A)) + ;; CHECK-NEXT: (local.set $a + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $b + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $4 + ;; CHECK-NEXT: (ref.cast_static $A + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $5 + ;; CHECK-NEXT: (ref.cast_static $A + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $b + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multiple (param $x (ref data)) (param $y (ref data)) + (local $a (ref data)) + (local $b (ref data)) + ;; Two different locals, with overlapping lives. + (local.set $a + (local.get $x) + ) + (local.set $b + (local.get $y) + ) + (drop + (ref.cast_static $A + (local.get $a) + ) + ) + (drop + (ref.cast_static $A + (local.get $b) + ) + ) + ;; These two can be optimized. + (drop + (local.get $a) + ) + (drop + (local.get $b) + ) + (local.set $b + (local.get $x) + ) + ;; Now only the first can be, since $b changed. + (drop + (local.get $a) + ) + (drop + (local.get $b) + ) + ) + + ;; CHECK: (func $get (type $none_=>_ref|data|) (result (ref data)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $get (result (ref data)) + ;; Helper for the above. + (unreachable) + ) +) |