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 | |
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')
-rw-r--r-- | test/lit/help/wasm-opt.test | 2 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 2 | ||||
-rw-r--r-- | test/lit/passes/optimize-casts.wast | 398 |
3 files changed, 402 insertions, 0 deletions
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 2af38f982..bc3d710ac 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -296,6 +296,8 @@ ;; CHECK-NEXT: load/store offsets, propagating ;; CHECK-NEXT: them across locals too ;; CHECK-NEXT: +;; CHECK-NEXT: --optimize-casts eliminate and reuse casts +;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-for-js early optimize of the ;; CHECK-NEXT: instruction combinations for js ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index e5669563d..ce2748343 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -255,6 +255,8 @@ ;; CHECK-NEXT: load/store offsets, propagating ;; CHECK-NEXT: them across locals too ;; CHECK-NEXT: +;; CHECK-NEXT: --optimize-casts eliminate and reuse casts +;; CHECK-NEXT: ;; CHECK-NEXT: --optimize-for-js early optimize of the ;; CHECK-NEXT: instruction combinations for js ;; CHECK-NEXT: 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) + ) +) |