diff options
author | Alon Zakai <azakai@google.com> | 2021-09-10 15:35:29 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-10 22:35:29 +0000 |
commit | fc310a6dd6145d5d1470d8bf4cbb57c93f8785f1 (patch) | |
tree | bd611524aea65aab2652eb4e122438166d871ce9 /test | |
parent | ea74b4f8649dfa6c8775fcef829d115083e5acc9 (diff) | |
download | binaryen-fc310a6dd6145d5d1470d8bf4cbb57c93f8785f1.tar.gz binaryen-fc310a6dd6145d5d1470d8bf4cbb57c93f8785f1.tar.bz2 binaryen-fc310a6dd6145d5d1470d8bf4cbb57c93f8785f1.zip |
Add an Intrinsics mechanism, and a call.without.effects intrinsic (#4126)
An "intrinsic" is modeled as a call to an import. We could also add new
IR things for them, but that would take more work and lead to less
clear errors in other tools if they try to read a binary using such a
nonstandard extension.
A first intrinsic is added here, call.without.effects This is basically the same
as call_ref except that the optimizer is free to assume the call has no
side effects. Consequently, if the result is not used then it can be optimized
out (as even if it is not used then side effects could have kept it around).
Likewise, the lack of side effects allows more reordering and other
things.
A lowering pass for intrinsics is provided. Rather than automatically
lower them to normal wasm at the end of optimizations, the user must
call that pass explicitly. A typical workflow might be
-O --intrinsic-lowering -O
That optimizes with the intrinsic present - perhaps removing calls
thanks to it - then lowers it into normal wasm - it turns into a call_ref -
and then optimizes further, which would turns the call_ref into a
direct call, potentially inline, etc.
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/help/optimization-opts.test | 2 | ||||
-rw-r--r-- | test/lit/passes/intrinsic-lowering.wast | 53 | ||||
-rw-r--r-- | test/lit/passes/vacuum-intrinsics.wast | 233 |
3 files changed, 288 insertions, 0 deletions
diff --git a/test/lit/help/optimization-opts.test b/test/lit/help/optimization-opts.test index 18f8970c2..177a1b620 100644 --- a/test/lit/help/optimization-opts.test +++ b/test/lit/help/optimization-opts.test @@ -284,6 +284,8 @@ ;; CHECK-NEXT: to intercept all loads and ;; CHECK-NEXT: stores ;; CHECK-NEXT: +;; CHECK-NEXT: --intrinsic-lowering lower away binaryen intrinsics +;; CHECK-NEXT: ;; CHECK-NEXT: --legalize-js-interface legalizes i64 types on the ;; CHECK-NEXT: import/export boundary ;; CHECK-NEXT: diff --git a/test/lit/passes/intrinsic-lowering.wast b/test/lit/passes/intrinsic-lowering.wast new file mode 100644 index 000000000..01bafbf86 --- /dev/null +++ b/test/lit/passes/intrinsic-lowering.wast @@ -0,0 +1,53 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --intrinsic-lowering -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $none (func)) + (type $none (func)) + + ;; call.without.effects with no params. + ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $cwe-v (param funcref) (result i32))) + (import "binaryen-intrinsics" "call.without.effects" (func $cwe-v (param funcref) (result i32))) + + ;; call.without.effects with some params. + ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $cwe-dif (param f64 i32 funcref) (result f32))) + (import "binaryen-intrinsics" "call.without.effects" (func $cwe-dif (param f64) (param i32) (param funcref) (result f32))) + + ;; call.without.effects with no result. + ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $cwe-n (param funcref))) + (import "binaryen-intrinsics" "call.without.effects" (func $cwe-n (param funcref))) + + ;; CHECK: (func $test (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $dif + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (ref.null $none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $test (result i32) + ;; These will be lowered into calls. + (drop (call $cwe-v (ref.func $test))) + (drop (call $cwe-dif (f64.const 3.14159) (i32.const 42) (ref.func $dif))) + ;; The last must be a call_ref, as we don't see a constant ref.func + (call $cwe-n + (ref.null $none) + ) + (i32.const 1) + ) + + ;; CHECK: (func $dif (param $0 f64) (param $1 i32) (result f32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $dif (param f64) (param i32) (result f32) + ;; Helper function for the above. + (unreachable) + ) +) diff --git a/test/lit/passes/vacuum-intrinsics.wast b/test/lit/passes/vacuum-intrinsics.wast new file mode 100644 index 000000000..bd0f8ed60 --- /dev/null +++ b/test/lit/passes/vacuum-intrinsics.wast @@ -0,0 +1,233 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --vacuum -all -S -o - | filecheck %s + +(module + ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32))) + (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32))) + + ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-fj (param f32 funcref) (result i64))) + (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-fj (param f32) (param funcref) (result i64))) + + ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-ref (param funcref) (result (ref any)))) + (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects-ref (param funcref) (result (ref any)))) + + ;; CHECK: (func $used + ;; CHECK-NEXT: (local $i32 i32) + ;; CHECK-NEXT: (local.set $i32 + ;; CHECK-NEXT: (call $call.without.effects + ;; CHECK-NEXT: (ref.func $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $used + (local $i32 i32) + ;; The result is used (by the local.set), so we cannot do anything here. + (local.set $i32 + (call $call.without.effects (ref.func $i)) + ) + ) + + ;; CHECK: (func $unused + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $unused + ;; The result is unused, so we can remove the call. + (drop + (call $call.without.effects (ref.func $i)) + ) + ) + + ;; CHECK: (func $unused-fj + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $unused-fj + ;; As above, but with an extra float param and a different result type. + (drop + (call $call.without.effects-fj (f32.const 2.71828) (ref.func $fj)) + ) + ) + + ;; CHECK: (func $unused-fj-side-effects + ;; CHECK-NEXT: (local $f32 f32) + ;; CHECK-NEXT: (local.set $f32 + ;; CHECK-NEXT: (f32.const 2.718280076980591) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unused-fj-side-effects + (local $f32 f32) + ;; As above, but side effects in the param. We must keep the params around + ;; and drop them. + (drop + (call $call.without.effects-fj + (local.tee $f32 + (f32.const 2.71828) + ) + (ref.func $fj) + ) + ) + ) + + ;; CHECK: (func $unused-unreachable + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $call.without.effects-fj + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.func $fj) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unused-unreachable + ;; An unused result, but the call is unreachable and so we ignore it (and + ;; leave it for DCE). + (drop + (call $call.without.effects-fj (unreachable) (ref.func $fj)) + ) + ) + + ;; CHECK: (func $used-fallthrough + ;; CHECK-NEXT: (local $i32 i32) + ;; CHECK-NEXT: (local.set $i32 + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (block $condition (result i32) + ;; CHECK-NEXT: (call $nop) + ;; CHECK-NEXT: (call $call.without.effects + ;; CHECK-NEXT: (ref.func $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $ifTrue (result i32) + ;; CHECK-NEXT: (call $nop) + ;; CHECK-NEXT: (call $call.without.effects + ;; CHECK-NEXT: (ref.func $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $ifFalse (result i32) + ;; CHECK-NEXT: (call $nop) + ;; CHECK-NEXT: (call $call.without.effects + ;; CHECK-NEXT: (ref.func $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $used-fallthrough + (local $i32 i32) + (local.set $i32 + (if (result i32) + ;; The block falls through a value that is used as the if condition. + (block $condition (result i32) + ;; Add a call to $nop so that the blocks are not optimized away. + (call $nop) + (call $call.without.effects (ref.func $i)) + ) + ;; The arms fall through their blocks and also through the if, and end + ;; up used by the set. + (block $ifTrue (result i32) + (call $nop) + (call $call.without.effects (ref.func $i)) + ) + (block $ifFalse (result i32) + (call $nop) + (call $call.without.effects (ref.func $i)) + ) + ) + ) + ) + + ;; CHECK: (func $unused-fallthrough + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (block $condition (result i32) + ;; CHECK-NEXT: (call $nop) + ;; CHECK-NEXT: (call $call.without.effects + ;; CHECK-NEXT: (ref.func $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $ifTrue (result i32) + ;; CHECK-NEXT: (call $nop) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $ifFalse (result i32) + ;; CHECK-NEXT: (call $nop) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unused-fallthrough + (drop + (if (result i32) + (block $condition (result i32) + (call $nop) + (call $call.without.effects (ref.func $i)) + ) + ;; As above, but now there is a drop outside the if, so the arms are + ;; unused and we can optimize them. + (block $ifTrue (result i32) + (call $nop) + (call $call.without.effects (ref.func $i)) + ) + (block $ifFalse (result i32) + (call $nop) + (call $call.without.effects (ref.func $i)) + ) + ) + ) + ) + + ;; CHECK: (func $unused-fallthrough-bad-type + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result (ref any)) + ;; CHECK-NEXT: (call $i) + ;; CHECK-NEXT: (call $call.without.effects-ref + ;; CHECK-NEXT: (ref.func $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $call.without.effects-ref + ;; CHECK-NEXT: (ref.func $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unused-fallthrough-bad-type + (drop + (if (result (ref any)) + (call $i) + ;; As above, but the type of these unused values prevents us from + ;; optimizing as we cannot create a "zero" for them. + (call $call.without.effects-ref (ref.func $ref)) + (call $call.without.effects-ref (ref.func $ref)) + ) + ) + ) + + ;; CHECK: (func $i (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $i (result i32) + ;; Helper function for the above. + (unreachable) + ) + + ;; CHECK: (func $fj (param $0 f32) (result i64) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $fj (param f32) (result i64) + ;; Helper function for the above. + (unreachable) + ) + + ;; CHECK: (func $ref (result (ref any)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $ref (result (ref any)) + ;; Helper function for the above. + (unreachable) + ) + + + ;; CHECK: (func $nop + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $nop + ;; Helper function for the above. + (nop) + ) +) |