diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/help/optimization-opts.test | 3 | ||||
-rw-r--r-- | test/lit/passes/signature-refining.wast | 492 |
2 files changed, 495 insertions, 0 deletions
diff --git a/test/lit/help/optimization-opts.test b/test/lit/help/optimization-opts.test index 6667c45bf..53c74605f 100644 --- a/test/lit/help/optimization-opts.test +++ b/test/lit/help/optimization-opts.test @@ -458,6 +458,9 @@ ;; CHECK-NEXT: --set-globals sets specified globals to ;; CHECK-NEXT: specified values ;; CHECK-NEXT: +;; CHECK-NEXT: --signature-refining apply more specific subtypes to +;; CHECK-NEXT: signature types where possible +;; CHECK-NEXT: ;; CHECK-NEXT: --simplify-globals miscellaneous globals-related ;; CHECK-NEXT: optimizations ;; CHECK-NEXT: diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast new file mode 100644 index 000000000..71eec54b2 --- /dev/null +++ b/test/lit/passes/signature-refining.wast @@ -0,0 +1,492 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --nominal --signature-refining -all -S -o - | filecheck %s + +(module + ;; $func is defined with an anyref parameter but always called with a $struct, + ;; and we can specialize the heap type to that. That will both update the + ;; heap type's definition as well as the types of the parameters as printed + ;; on the function (which are derived from the heap type). + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct) + ) + ) +) + +(module + ;; As above, but the call is via call_ref. + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call_ref + (struct.new $struct) + (ref.func $func) + ) + ) +) + +(module + ;; A combination of call types, and the LUB is affected by all of them: one + ;; call uses a nullable $struct, the other a non-nullable dataref, so the LUB + ;; is a nullable dataref. + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref null data)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x (ref null data)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (local $struct (ref null $struct)) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (ref.as_data + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (local $struct (ref null $struct)) + (call $func + ;; Use a local to avoid a ref.null being updated. + (local.get $struct) + ) + (call_ref + (ref.as_data + (struct.new $struct) + ) + (ref.func $func) + ) + ) +) + +(module + ;; Multiple functions with the same heap type. Again, the LUB is in the + ;; middle, this time the parent $struct and not a subtype. + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct-sub1 (struct_subtype $struct)) + (type $struct-sub1 (struct_subtype $struct)) + + ;; CHECK: (type $struct-sub2 (struct_subtype $struct)) + (type $struct-sub2 (struct_subtype $struct)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (func $func-1 (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-1 (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $func-2 (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-2 (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func-1 + ;; CHECK-NEXT: (struct.new_default $struct-sub1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $func-2 + ;; CHECK-NEXT: (struct.new_default $struct-sub2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func-1 + (struct.new $struct-sub1) + ) + (call $func-2 + (struct.new $struct-sub2) + ) + ) +) + +(module + ;; As above, but now only one of the functions is called. The other is still + ;; updated, though, as they share a heap type. + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func-1 (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-1 (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $func-2 (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-2 (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func-1 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func-1 + (struct.new $struct) + ) + ) +) + +(module + ;; Define a field in the struct of the signature type that will be updated, + ;; to check for proper validation after the update. + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $struct (struct_subtype (field (ref $sig)) data)) + (type $struct (struct_subtype (field (ref $sig)) data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (local $temp (ref null $sig)) + ;; CHECK-NEXT: (local $2 anyref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ;; Define a local of the signature type that is updated. + (local $temp (ref null $sig)) + ;; Do a local.get of the param, to verify its type is valid. + (drop + (local.get $x) + ) + ;; Copy between the param and the local, to verify their types are still + ;; compatible after the update. Note that we will need to add a fixup local + ;; here, as $x's new type becomes too specific to be assigned the value + ;; here. + (local.set $x + (local.get $temp) + ) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct + (ref.func $func) + ) + ) + ) +) + +(module + ;; An unreachable value does not prevent optimization: we will update the + ;; param to be $struct. + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct) + ) + (call_ref + (unreachable) + (ref.func $func) + ) + ) +) + +(module + ;; When we have only unreachable values, there is nothing to optimize, and we + ;; should not crash. + + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param anyref) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func) + + ;; CHECK: (func $func (type $sig) (param $x anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call_ref + (unreachable) + (ref.func $func) + ) + ) +) + +(module + ;; When we have no calls, there is nothing to optimize, and we should not + ;; crash. + + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param anyref) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (func $func (type $sig) (param $x anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) +) + +(module + ;; Test multiple fields in multiple types. + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig-1 (func_subtype (param (ref null data) anyref) func)) + (type $sig-1 (func_subtype (param anyref) (param anyref) func)) + ;; CHECK: (type $sig-2 (func_subtype (param anyref (ref $struct)) func)) + (type $sig-2 (func_subtype (param anyref) (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $func-2) + + ;; CHECK: (func $func-1 (type $sig-1) (param $x (ref null data)) (param $y anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-1 (type $sig-1) (param $x anyref) (param $y anyref) + ) + + ;; CHECK: (func $func-2 (type $sig-2) (param $x anyref) (param $y (ref $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func-2 (type $sig-2) (param $x anyref) (param $y anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (local $any anyref) + ;; CHECK-NEXT: (local $data (ref null data)) + ;; CHECK-NEXT: (local $func funcref) + ;; CHECK-NEXT: (call $func-1 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (local.get $data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $func-1 + ;; CHECK-NEXT: (local.get $data) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $func-2 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (local.get $func) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (ref.func $func-2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (local $any (ref null any)) + (local $data (ref null data)) + (local $func (ref null func)) + + (call $func-1 + (struct.new $struct) + (local.get $data) + ) + (call $func-1 + (local.get $data) + (local.get $any) + ) + (call $func-2 + (struct.new $struct) + (struct.new $struct) + ) + (call_ref + (local.get $func) + (struct.new $struct) + (ref.func $func-2) + ) + ) +) + +(module + ;; The presence of a table prevents us from doing any optimizations. + + ;; CHECK: (type $sig (func_subtype (param anyref) func)) + (type $sig (func_subtype (param anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + (table 1 1 anyref) + + ;; CHECK: (table $0 1 1 anyref) + + ;; CHECK: (func $func (type $sig) (param $x anyref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct) + ) + ) +) + +(module + ;; Pass a null in one call to the function. The null can be updated which + ;; allows us to refine (but the new type must be nullable). + + ;; CHECK: (type $struct (struct_subtype data)) + + ;; CHECK: (type $sig (func_subtype (param (ref null $struct)) func)) + (type $sig (func_subtype (param anyref) func)) + + (type $struct (struct_subtype data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $sig) (param $x (ref null $struct)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func (type $sig) (param $x anyref) + ) + + ;; CHECK: (func $caller (type $none_=>_none) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $func + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + (call $func + (struct.new $struct) + ) + (call $func + (ref.null data) + ) + ) +) |