diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/help/optimization-opts.test | 3 | ||||
-rw-r--r-- | test/lit/passes/global-subtyping.wast | 553 |
2 files changed, 556 insertions, 0 deletions
diff --git a/test/lit/help/optimization-opts.test b/test/lit/help/optimization-opts.test index 34559ab0a..380101a53 100644 --- a/test/lit/help/optimization-opts.test +++ b/test/lit/help/optimization-opts.test @@ -275,6 +275,9 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --generate-stack-ir generate Stack IR ;; CHECK-NEXT: +;; CHECK-NEXT: --global-subtyping apply more specific subtypes to +;; CHECK-NEXT: type fields where possible +;; CHECK-NEXT: ;; CHECK-NEXT: --gto globally optimize GC types ;; CHECK-NEXT: ;; CHECK-NEXT: --heap2local replace GC allocations with diff --git a/test/lit/passes/global-subtyping.wast b/test/lit/passes/global-subtyping.wast new file mode 100644 index 000000000..5c05ac753 --- /dev/null +++ b/test/lit/passes/global-subtyping.wast @@ -0,0 +1,553 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --nominal --global-subtyping -all -S -o - | filecheck %s + +(module + ;; A struct with three fields. The first will have no writes, the second one + ;; write of the same type, and the last a write of a subtype, which will allow + ;; us to specialize that one. + ;; CHECK: (type $struct (struct_subtype (field (mut anyref)) (field (mut anyref)) (field (mut (ref $ref|$struct|_=>_none))) data)) + (type $struct (struct_subtype (field (mut anyref)) (field (mut anyref)) (field (mut anyref)) data)) + + ;; CHECK: (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func)) + + ;; CHECK: (elem declare func $work) + + ;; CHECK: (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 1 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 2 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (ref.func $work) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 2 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) + (struct.set $struct 1 + (local.get $struct) + (ref.null any) + ) + (struct.set $struct 2 + (local.get $struct) + (ref.func $work) + ) + (drop + ;; The type of this struct.get must be updated after the field's type + ;; changes, or the validator will complain. + (struct.get $struct 2 + (local.get $struct) + ) + ) + ) +) + +(module + ;; A struct with a nullable field and a write of a non-nullable value. We + ;; must keep the type nullable, unlike in the previous module, due to the + ;; default value being null. + + ;; CHECK: (type $struct (struct_subtype (field (mut (ref null $ref|$struct|_=>_none))) data)) + (type $struct (struct_subtype (field (mut anyref)) data)) + + ;; CHECK: (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func)) + + ;; CHECK: (elem declare func $work) + + ;; CHECK: (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (ref.func $work) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) + (drop + (struct.new_default $struct) + ) + (struct.set $struct 0 + (local.get $struct) + (ref.func $work) + ) + ) +) + +(module + ;; Multiple writes to a field, with a LUB that is not equal to any of them. + ;; We can at least improve from dataref to a ref of $struct here. Note also + ;; that we do so in all three types, not just the parent to which we write + ;; (the children have no writes, but must still be updated). + + ;; CHECK: (type $struct (struct_subtype (field (mut (ref $struct))) data)) + (type $struct (struct_subtype (field (mut dataref)) data)) + + ;; CHECK: (type $ref|$struct|_ref|$child-A|_ref|$child-B|_=>_none (func_subtype (param (ref $struct) (ref $child-A) (ref $child-B)) func)) + + ;; CHECK: (type $child-A (struct_subtype (field (mut (ref $struct))) $struct)) + (type $child-A (struct_subtype (field (mut dataref)) $struct)) + + ;; CHECK: (type $child-B (struct_subtype (field (mut (ref $struct))) $struct)) + (type $child-B (struct_subtype (field (mut dataref)) $struct)) + + ;; CHECK: (func $work (type $ref|$struct|_ref|$child-A|_ref|$child-B|_=>_none) (param $struct (ref $struct)) (param $child-A (ref $child-A)) (param $child-B (ref $child-B)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (local.get $child-A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (local.get $child-B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) (param $child-A (ref $child-A)) (param $child-B (ref $child-B)) + (struct.set $struct 0 + (local.get $struct) + (local.get $child-A) + ) + (struct.set $struct 0 + (local.get $struct) + (local.get $child-B) + ) + ) +) + +(module + ;; As above, but all writes are of $child-A, which allows more optimization + ;; up to that type. + + ;; CHECK: (type $child-A (struct_subtype (field (mut (ref $child-A))) $struct)) + (type $child-A (struct_subtype (field (mut dataref)) $struct)) + + ;; CHECK: (type $struct (struct_subtype (field (mut (ref $child-A))) data)) + (type $struct (struct_subtype (field (mut dataref)) data)) + + ;; CHECK: (type $ref|$struct|_ref|$child-A|_=>_none (func_subtype (param (ref $struct) (ref $child-A)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $child-B (struct_subtype (field (mut (ref $child-A))) $struct)) + (type $child-B (struct_subtype (field (mut dataref)) $struct)) + + ;; CHECK: (func $work (type $ref|$struct|_ref|$child-A|_=>_none) (param $struct (ref $struct)) (param $child-A (ref $child-A)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (local.get $child-A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (local.get $child-A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) (param $child-A (ref $child-A)) + (struct.set $struct 0 + (local.get $struct) + (local.get $child-A) + ) + (struct.set $struct 0 + (local.get $struct) + (local.get $child-A) + ) + ) + + ;; CHECK: (func $keepalive (type $none_=>_none) + ;; CHECK-NEXT: (local $temp (ref null $child-B)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $keepalive + ;; Add a reference to $child-B just to keep it alive in the output for easier + ;; comparisons to the previous testcase. Note that $child-B's field will be + ;; refined, because its parent $struct forces it to be. + (local $temp (ref null $child-B)) + ) +) + +(module + ;; Write to the parent a child, and to the child a parent. The write to the + ;; child prevents specialization even in the parent and we only improve up to + ;; $struct but not to $child. + + ;; CHECK: (type $struct (struct_subtype (field (mut (ref $struct))) data)) + (type $struct (struct_subtype (field (mut dataref)) data)) + + ;; CHECK: (type $child (struct_subtype (field (mut (ref $struct))) $struct)) + (type $child (struct_subtype (field (mut dataref)) $struct)) + + ;; CHECK: (type $ref|$struct|_ref|$child|_=>_none (func_subtype (param (ref $struct) (ref $child)) func)) + + ;; CHECK: (func $work (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $child 0 + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) (param $child (ref $child)) + (struct.set $struct 0 + (local.get $struct) + (local.get $child) + ) + (struct.set $child 0 + (local.get $child) + (local.get $struct) + ) + ) +) + +(module + ;; As above, but both writes are of $child, so we can optimize. + + ;; CHECK: (type $child (struct_subtype (field (mut (ref $child))) $struct)) + (type $child (struct_subtype (field (mut dataref)) $struct)) + + ;; CHECK: (type $struct (struct_subtype (field (mut (ref $child))) data)) + (type $struct (struct_subtype (field (mut dataref)) data)) + + ;; CHECK: (type $ref|$struct|_ref|$child|_=>_none (func_subtype (param (ref $struct) (ref $child)) func)) + + ;; CHECK: (func $work (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $child 0 + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) (param $child (ref $child)) + (struct.set $struct 0 + (local.get $struct) + (local.get $child) + ) + (struct.set $child 0 + (local.get $child) + (local.get $child) + ) + ) +) + +(module + ;; As in 2 testcases ago, write to the parent a child, and to the child a + ;; parent, but now the writes happen in struct.new. Even with that precise + ;; info, however, we can't make the parent field more specific than the + ;; child's. + + ;; CHECK: (type $struct (struct_subtype (field (mut (ref $struct))) data)) + (type $struct (struct_subtype (field (mut dataref)) data)) + + ;; CHECK: (type $child (struct_subtype (field (mut (ref $struct))) $struct)) + (type $child (struct_subtype (field (mut dataref)) $struct)) + + ;; CHECK: (type $ref|$struct|_ref|$child|_=>_none (func_subtype (param (ref $struct) (ref $child)) func)) + + ;; CHECK: (func $work (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $child + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) (param $child (ref $child)) + (drop + (struct.new $struct + (local.get $child) + ) + ) + (drop + (struct.new $child + (local.get $struct) + ) + ) + ) +) + +(module + ;; Write a parent to the parent and a child to the child. We can specialize + ;; each of them to contain their own type. This tests that we are aware that + ;; a struct.new is of a precise type, which means that seeing a type written + ;; to a parent does not limit specialization in a child. + ;; + ;; (Note that we can't do a similar test with struct.set, as that would + ;; imply the fields are mutable, which limits optimization, see the next + ;; testcase after this.) + + ;; CHECK: (type $struct (struct_subtype (field (ref $struct)) data)) + (type $struct (struct_subtype (field dataref) data)) + + ;; CHECK: (type $child (struct_subtype (field (ref $child)) $struct)) + (type $child (struct_subtype (field dataref) $struct)) + + ;; CHECK: (type $ref|$struct|_ref|$child|_=>_none (func_subtype (param (ref $struct) (ref $child)) func)) + + ;; CHECK: (func $work (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $child + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) (param $child (ref $child)) + (drop + (struct.new $struct + (local.get $struct) + ) + ) + (drop + (struct.new $child + (local.get $child) + ) + ) + ) +) + +(module + ;; As above, but the fields are mutable. We cannot specialize them to + ;; different types in this case, and both will become $struct (still an + ;; improvement!) + + ;; CHECK: (type $struct (struct_subtype (field (mut (ref $struct))) data)) + (type $struct (struct_subtype (field (mut dataref)) data)) + + ;; CHECK: (type $child (struct_subtype (field (mut (ref $struct))) $struct)) + (type $child (struct_subtype (field (mut dataref)) $struct)) + + ;; CHECK: (type $ref|$struct|_ref|$child|_=>_none (func_subtype (param (ref $struct) (ref $child)) func)) + + ;; CHECK: (func $work (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $child + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) (param $child (ref $child)) + (drop + (struct.new $struct + (local.get $struct) + ) + ) + (drop + (struct.new $child + (local.get $child) + ) + ) + ) +) + +(module + ;; As above, but the child also has a new field that is not in the parent. In + ;; that case there is nothing stopping us from specializing that new field + ;; to $child. + + ;; CHECK: (type $struct (struct_subtype (field (mut (ref $struct))) data)) + (type $struct (struct_subtype (field (mut dataref)) data)) + + ;; CHECK: (type $child (struct_subtype (field (mut (ref $struct))) (field (mut (ref $child))) $struct)) + (type $child (struct_subtype (field (mut dataref)) (field (mut dataref)) $struct)) + + ;; CHECK: (type $ref|$struct|_ref|$child|_=>_none (func_subtype (param (ref $struct) (ref $child)) func)) + + ;; CHECK: (func $work (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $child + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) (param $child (ref $child)) + (drop + (struct.new $struct + (local.get $struct) + ) + ) + (drop + (struct.new $child + (local.get $child) + (local.get $child) + ) + ) + ) +) + +(module + ;; A copy of a field does not prevent optimization (even though it assigns + ;; the old type). + + ;; CHECK: (type $struct (struct_subtype (field (mut (ref $struct))) data)) + (type $struct (struct_subtype (field (mut dataref)) data)) + + ;; CHECK: (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func)) + + ;; CHECK: (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $work (param $struct (ref $struct)) + (struct.set $struct 0 + (local.get $struct) + (local.get $struct) + ) + (struct.set $struct 0 + (local.get $struct) + (struct.get $struct 0 + (local.get $struct) + ) + ) + ) +) + +(module + ;; CHECK: (type $Y (struct_subtype $X)) + (type $Y (struct_subtype $X)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $C (struct_subtype (field (ref $Y)) $A)) + (type $C (struct_subtype (field (ref $X)) $A)) + + ;; CHECK: (type $B (struct_subtype (field (ref $Y)) $A)) + (type $B (struct_subtype (field (ref $X)) $A)) + + ;; CHECK: (type $A (struct_subtype (field (ref $Y)) data)) + (type $A (struct_subtype (field (ref $X)) data)) + + ;; CHECK: (type $X (struct_subtype data)) + (type $X (struct_subtype data)) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (local $unused (ref null $C)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (struct.new_default $Y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo + ;; A use of type $C without ever creating an instance of it. We do still need + ;; to update the type if we update the parent type, and we will in fact update + ;; the parent $A's field from $X to $Y (see below), so we must do the same in + ;; $C. As a result, all the fields with $X in them in all of $A, $B, $C will + ;; be improved to contain $Y. + (local $unused (ref null $C)) + + (drop + (struct.new $B + (struct.new $Y) ;; This value is more specific than the field, which is an + ;; opportunity to subtype, which we do for $B. As $A, our + ;; parent, has no writes at all, we can propagate this + ;; info to there as well, which means we can perform the + ;; same optimization in $A as well. + ) + ) + ) +) + +(module + ;; As above, but remove the struct.new to $B, which means $A, $B, $C all have + ;; no writes to them. There are no optimizations to do here. + + ;; CHECK: (type $X (struct_subtype data)) + (type $X (struct_subtype data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $C (struct_subtype (field (ref $X)) $A)) + (type $C (struct_subtype (field (ref $X)) $A)) + + ;; CHECK: (type $B (struct_subtype (field (ref $X)) $A)) + (type $B (struct_subtype (field (ref $X)) $A)) + + ;; CHECK: (type $Y (struct_subtype $X)) + (type $Y (struct_subtype $X)) + + ;; CHECK: (type $A (struct_subtype (field (ref $X)) data)) + (type $A (struct_subtype (field (ref $X)) data)) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (local $unused1 (ref null $C)) + ;; CHECK-NEXT: (local $unused2 (ref null $B)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $Y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo + (local $unused1 (ref null $C)) + (local $unused2 (ref null $B)) + (drop (struct.new $Y)) + ) +) + +(module + ;; CHECK: (type $X (struct_subtype data)) + (type $X (struct_subtype data)) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $B (struct_subtype (field (ref $Y)) $A)) + (type $B (struct_subtype (field (ref $Y)) $A)) + + ;; CHECK: (type $A (struct_subtype (field (ref $X)) data)) + (type $A (struct_subtype (field (ref $X)) data)) + + ;; CHECK: (type $Y (struct_subtype $X)) + (type $Y (struct_subtype $X)) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (local $unused2 (ref null $B)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (struct.new_default $X) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo + ;; $B begins with its field of type $Y, which is more specific than the + ;; field is in the supertype $A. There are no writes to $B, and so we end + ;; up looking in the parent to see what to do; we should still emit a + ;; reasonable type for $B, and there is no reason to make it *less* + ;; specific, so leave things as they are. + (local $unused2 (ref null $B)) + (drop + (struct.new $A + (struct.new $X) + ) + ) + ) +) |