diff options
author | Alon Zakai <azakai@google.com> | 2023-05-05 12:46:33 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-05 12:46:33 -0700 |
commit | 6086df072d07bc7cc63f65eafd9eca92ef8e3e89 (patch) | |
tree | af5b7486023e53455a4cc122cfa405906e995f82 /test/lit/ctor-eval | |
parent | 5e7dcebbeae8bfe1c418b28454fb95141c8a2e03 (diff) | |
download | binaryen-6086df072d07bc7cc63f65eafd9eca92ef8e3e89.tar.gz binaryen-6086df072d07bc7cc63f65eafd9eca92ef8e3e89.tar.bz2 binaryen-6086df072d07bc7cc63f65eafd9eca92ef8e3e89.zip |
[Wasm GC] wasm-ctor-eval: Handle cycles of data (#5685)
A cycle of data is something we can't just naively emit as wasm globals. If
at runtime we end up, for example, with an object A that refers to itself,
then we can't just emit
(global $A
(struct.new $A
(global.get $A)))
The struct.get is of this very global, and such a self-reference is invalid. So
we need to break such cycles as we emit them. The simple idea used here
is to find paths in the cycle that are nullable and mutable, and replace the
initial value with a null that is fixed up later in the start function:
(global $A
(struct.new $A
(ref.null $A)))
(func $start
(struct.set
(global.get $A)
(global.get $A)))
)
This is not optimal in terms of breaking cycles, but it is fast (linear time)
and simple, and does well in practice on j2wasm (where cycles in fact
occur).
Diffstat (limited to 'test/lit/ctor-eval')
-rw-r--r-- | test/lit/ctor-eval/gc-cycle-multi.wast | 144 | ||||
-rw-r--r-- | test/lit/ctor-eval/gc-cycle.wast | 1297 | ||||
-rw-r--r-- | test/lit/ctor-eval/partial-global.wat | 39 |
3 files changed, 1480 insertions, 0 deletions
diff --git a/test/lit/ctor-eval/gc-cycle-multi.wast b/test/lit/ctor-eval/gc-cycle-multi.wast new file mode 100644 index 000000000..62a888dbd --- /dev/null +++ b/test/lit/ctor-eval/gc-cycle-multi.wast @@ -0,0 +1,144 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-ctor-eval --ctors=test1,test2,test3 --kept-exports=test1,test2,test3 --quiet -all -S -o - | filecheck %s + +;; Similar to gc-cycle.wast, but now there are three exported ctors that we +;; call. + +(module + ;; Multiple independent cycles. + ;; CHECK: (type $A (struct (field (mut (ref null $A))) (field i32))) + (type $A (struct (field (mut (ref null $A))) (field i32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $c (mut (ref null $A)) (global.get $ctor-eval$global_8)) + + ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_7)) + (global $a (mut (ref null $A)) (ref.null $A)) + ;; CHECK: (global $ctor-eval$global_6 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $b (mut (ref null $A)) (global.get $ctor-eval$global_6)) + (global $b (mut (ref null $A)) (ref.null $A)) + (global $c (mut (ref null $A)) (ref.null $A)) + + (func $makeCycle (param $i i32) (result (ref $A)) + (local $x (ref $A)) + (local.set $x + (struct.new $A + (ref.null $A) + (local.get $i) + ) + ) + (struct.set $A 0 + (local.get $x) + (local.get $x) + ) + (local.get $x) + ) + + (func $test1 (export "test1") + (global.set $a + (call $makeCycle + (i32.const 10) + ) + ) + ) + + (func $test2 (export "test2") + (global.set $b + (call $makeCycle + (i32.const 20) + ) + ) + ) + + (func $test3 (export "test3") + (global.set $c + (call $makeCycle + (i32.const 30) + ) + ) + ) + + ;; CHECK: (export "test1" (func $test1_6)) + + ;; CHECK: (export "test2" (func $test2_7)) + + ;; CHECK: (export "test3" (func $test3_8)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (i32.add + (struct.get $A 1 + (global.get $a) + ) + (i32.add + (struct.get $A 1 + (global.get $b) + ) + (struct.get $A 1 + (global.get $c) + ) + ) + ) + ) +) +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_8) +;; CHECK-NEXT: (global.get $ctor-eval$global_8) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_6) +;; CHECK-NEXT: (global.get $ctor-eval$global_6) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test1_6 (type $none_=>_none) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) + +;; CHECK: (func $test2_7 (type $none_=>_none) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) + +;; CHECK: (func $test3_8 (type $none_=>_none) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) diff --git a/test/lit/ctor-eval/gc-cycle.wast b/test/lit/ctor-eval/gc-cycle.wast new file mode 100644 index 000000000..a6edc28aa --- /dev/null +++ b/test/lit/ctor-eval/gc-cycle.wast @@ -0,0 +1,1297 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-ctor-eval --ctors=test --kept-exports=test --quiet -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $A (struct (field (mut (ref null $A))) (field i32))) + (type $A (struct (field (mut (ref null $A))) (field i32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_3 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_3)) + (global $a (mut (ref null $A)) (ref.null $A)) + + (func $test (export "test") + (local $a (ref $A)) + ;; This generates a self-cycle where the global $a's ref field points to + ;; itself. To handle this, wasm-ctor-eval will emit a new global with a null + ;; in the ref field, and add a start function that adds the self-reference. + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $A) + (i32.const 42) + ) + ) + ) + (struct.set $A 0 + (local.get $a) + (local.get $a) + ) + ) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + ;; Getting $A.0.1 (reading from the reference in the global's first field) + ;; checks that we have a proper reference there. If we could do --fuzz-exec + ;; here we could validate that (but atm we can't use --output=fuzz-exec at the + ;; same time as --all-items in the update_lit_checks.py note). + (struct.get $A 1 + (struct.get $A 0 + (global.get $a) + ) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_3) +;; CHECK-NEXT: (global.get $ctor-eval$global_3) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; As above, but with $A's fields reversed. This verifies we use the right + ;; field index in the start function. + + ;; CHECK: (type $A (struct (field i32) (field (mut (ref null $A))))) + (type $A (struct (field i32) (field (mut (ref null $A))))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_3 (ref $A) (struct.new $A + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_3)) + (global $a (mut (ref null $A)) (ref.null $A)) + + (func $test (export "test") + (local $a (ref $A)) + (global.set $a + (local.tee $a + (struct.new $A + (i32.const 42) + (ref.null $A) + ) + ) + ) + (struct.set $A 1 + (local.get $a) + (local.get $a) + ) + ) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (struct.get $A 0 + (struct.get $A 1 + (global.get $a) + ) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 1 +;; CHECK-NEXT: (global.get $ctor-eval$global_3) +;; CHECK-NEXT: (global.get $ctor-eval$global_3) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; A cycle between two globals. + + ;; CHECK: (type $A (struct (field (mut (ref null $A))) (field i32))) + (type $A (struct (field (mut (ref null $A))) (field i32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) + (global $a (mut (ref null $A)) (ref.null $A)) + + ;; CHECK: (global $ctor-eval$global_7 (ref $A) (struct.new $A + ;; CHECK-NEXT: (global.get $ctor-eval$global_8) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $b (mut (ref null $A)) (global.get $ctor-eval$global_7)) + (global $b (mut (ref null $A)) (ref.null $A)) + + (func $test (export "test") + (local $a (ref $A)) + (local $b (ref $A)) + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $A) + (i32.const 42) + ) + ) + ) + (global.set $b + (local.tee $b + (struct.new $A + (global.get $a) ;; $b can refer to $a since we've created $a already. + (i32.const 1337) + ) + ) + ) + ;; $a needs a set to allow us to create the cycle. + (struct.set $A 0 + (local.get $a) + (local.get $b) + ) + ) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (i32.add + (struct.get $A 1 + (global.get $a) + ) + (struct.get $A 1 + (global.get $b) + ) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_8) +;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (local $b (ref $A)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; A cycle between two globals of different types. One of them has an + ;; immutable field in the cycle. + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut (ref null $B))) (field i32))) + (type $A (struct (field (mut (ref null $B))) (field i32))) + + ;; CHECK: (type $B (struct (field (ref null $A)) (field i32))) + (type $B (struct (field (ref null $A)) (field i32))) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) + (global $a (mut (ref null $A)) (ref.null $A)) + + ;; CHECK: (global $ctor-eval$global_7 (ref $B) (struct.new $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_8) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $b (mut (ref null $B)) (global.get $ctor-eval$global_7)) + (global $b (mut (ref null $B)) (ref.null $B)) + + (func $test (export "test") + (local $a (ref $A)) + (local $b (ref $B)) + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $A) + (i32.const 42) + ) + ) + ) + (global.set $b + (local.tee $b + (struct.new $B + (global.get $a) + (i32.const 1337) + ) + ) + ) + (struct.set $A 0 + (local.get $a) + (local.get $b) + ) + ) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $B 1 + ;; CHECK-NEXT: (global.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (i32.add + (struct.get $A 1 + (global.get $a) + ) + (struct.get $B 1 + (global.get $b) + ) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_8) +;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (local $b (ref $B)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; As above, but with the order of globals reversed. + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut (ref null $B))) (field i32))) + (type $A (struct (field (mut (ref null $B))) (field i32))) + + ;; CHECK: (type $B (struct (field (ref null $A)) (field i32))) + (type $B (struct (field (ref null $A)) (field i32))) + ) + + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) + + ;; CHECK: (global $ctor-eval$global_7 (ref $B) (struct.new $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_8) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $b (mut (ref null $B)) (global.get $ctor-eval$global_7)) + (global $b (mut (ref null $B)) (ref.null $B)) + + (global $a (mut (ref null $A)) (ref.null $A)) + + (func $test (export "test") + (local $a (ref $A)) + (local $b (ref $B)) + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $A) + (i32.const 42) + ) + ) + ) + (global.set $b + (local.tee $b + (struct.new $B + (global.get $a) + (i32.const 1337) + ) + ) + ) + (struct.set $A 0 + (local.get $a) + (local.get $b) + ) + ) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $B 1 + ;; CHECK-NEXT: (global.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (i32.add + (struct.get $A 1 + (global.get $a) + ) + (struct.get $B 1 + (global.get $b) + ) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_8) +;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (local $b (ref $B)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; A cycle as above, but with non-nullability rather than immutability. + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut (ref null $B))) (field i32))) + (type $A (struct (field (mut (ref null $B))) (field i32))) + + ;; CHECK: (type $B (struct (field (mut (ref $A))) (field i32))) + (type $B (struct (field (mut (ref $A))) (field i32))) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) + (global $a (mut (ref null $A)) (ref.null $A)) + + (global $b (mut (ref null $B)) (ref.null $B)) + + (func $test (export "test") + (local $a (ref $A)) + (local $b (ref $B)) + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $A) + (i32.const 42) + ) + ) + ) + (global.set $b + (local.tee $b + (struct.new $B + (local.get $a) + (i32.const 1337) + ) + ) + ) + (struct.set $A 0 + (local.get $a) + (local.get $b) + ) + ) + + ;; CHECK: (global $ctor-eval$global_7 (ref $B) (struct.new $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_8) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (struct.get $A 1 + (global.get $a) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_8) +;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (local $b (ref $B)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; A cycle as above, but with globals in reverse order and with both non- + ;; nullability and immutability. + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut (ref null $B))) (field i32))) + (type $A (struct (field (mut (ref null $B))) (field i32))) + + ;; CHECK: (type $B (struct (field (ref $A)) (field i32))) + (type $B (struct (field (ref $A)) (field i32))) + ) + + + (global $b (mut (ref null $B)) (ref.null $B)) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_8 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_8)) + (global $a (mut (ref null $A)) (ref.null $A)) + + (func $test (export "test") + (local $a (ref $A)) + (local $b (ref $B)) + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $A) + (i32.const 42) + ) + ) + ) + (global.set $b + (local.tee $b + (struct.new $B + (local.get $a) + (i32.const 1337) + ) + ) + ) + (struct.set $A 0 + (local.get $a) + (local.get $b) + ) + ) + + ;; CHECK: (global $ctor-eval$global_7 (ref $B) (struct.new $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_8) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (struct.get $A 1 + (global.get $a) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_8) +;; CHECK-NEXT: (global.get $ctor-eval$global_7) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (local $b (ref $B)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; A cycle between three globals. + + ;; CHECK: (type $A (struct (field (mut (ref null $A))) (field i32))) + (type $A (struct (field (mut (ref null $A))) (field i32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_13 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_14)) + (global $a (mut (ref null $A)) (ref.null $A)) + + (global $b (mut (ref null $A)) (ref.null $A)) + + (global $c (mut (ref null $A)) (ref.null $A)) + + (func $test (export "test") + (local $a (ref $A)) + (local $b (ref $A)) + (local $c (ref $A)) + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $A) + (i32.const 42) + ) + ) + ) + (global.set $b + (local.tee $b + (struct.new $A + (global.get $a) + (i32.const 1337) + ) + ) + ) + (global.set $c + (local.tee $c + (struct.new $A + (global.get $b) + (i32.const 99999) + ) + ) + ) + (struct.set $A 0 + (local.get $a) + (local.get $c) + ) + ) + + ;; CHECK: (global $ctor-eval$global_12 (ref $A) (struct.new $A + ;; CHECK-NEXT: (global.get $ctor-eval$global_13) + ;; CHECK-NEXT: (i32.const 99999) + ;; CHECK-NEXT: )) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (struct.get $A 1 + (global.get $a) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_13) +;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: (global.get $ctor-eval$global_12) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (local $b (ref $A)) +;; CHECK-NEXT: (local $c (ref $A)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; A cycle between three globals as above, but now using different types and + ;; also both structs and arrays. Also reverse the order of globals, make + ;; one array immutable and one non-nullable, and make one array refer to the + ;; other two. + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut (ref null $C))) (field i32))) + (type $A (struct (field (mut (ref null $C))) (field i32))) + ;; CHECK: (type $B (array (ref null $A))) + (type $B (array (ref null $A))) + ;; CHECK: (type $C (array (mut (ref any)))) + (type $C (array (mut (ref any)))) + ) + + (global $c (mut (ref null $C)) (ref.null $C)) + + (global $b (mut (ref null $B)) (ref.null $B)) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_13 (ref $B) (array.new_fixed $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_14)) + (global $a (mut (ref null $A)) (ref.null $A)) + + (func $test (export "test") + (local $a (ref $A)) + (local $b (ref $B)) + (local $c (ref $C)) + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $C) + (i32.const 42) + ) + ) + ) + (global.set $b + (local.tee $b + (array.new $B + (global.get $a) + (i32.const 10) + ) + ) + ) + (global.set $c + (local.tee $c + (array.new_fixed $C + (local.get $b) + (local.get $a) + ) + ) + ) + (struct.set $A 0 + (local.get $a) + (global.get $c) + ) + ) + + ;; CHECK: (global $ctor-eval$global_12 (ref $C) (array.new_fixed $C + ;; CHECK-NEXT: (global.get $ctor-eval$global_13) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: )) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (struct.get $A 1 + (global.get $a) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: (global.get $ctor-eval$global_12) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (local $b (ref $B)) +;; CHECK-NEXT: (local $c (ref $C)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; As above but with the order of globals reversed once more. + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut (ref null $C))) (field i32))) + (type $A (struct (field (mut (ref null $C))) (field i32))) + ;; CHECK: (type $B (array (ref null $A))) + (type $B (array (ref null $A))) + ;; CHECK: (type $C (array (mut (ref any)))) + (type $C (array (mut (ref any)))) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_13 (ref $B) (array.new_fixed $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_14)) + (global $a (mut (ref null $A)) (ref.null $A)) + + (global $b (mut (ref null $B)) (ref.null $B)) + + (global $c (mut (ref null $C)) (ref.null $C)) + + (func $test (export "test") + (local $a (ref $A)) + (local $b (ref $B)) + (local $c (ref $C)) + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $C) + (i32.const 42) + ) + ) + ) + (global.set $b + (local.tee $b + (array.new $B + (global.get $a) + (i32.const 10) + ) + ) + ) + (global.set $c + (local.tee $c + (array.new_fixed $C + (local.get $b) + (local.get $a) + ) + ) + ) + (struct.set $A 0 + (local.get $a) + (global.get $c) + ) + ) + + ;; CHECK: (global $ctor-eval$global_12 (ref $C) (array.new_fixed $C + ;; CHECK-NEXT: (global.get $ctor-eval$global_13) + ;; CHECK-NEXT: (global.get $ctor-eval$global_14) + ;; CHECK-NEXT: )) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (struct.get $A 1 + (global.get $a) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: (global.get $ctor-eval$global_12) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (local $b (ref $B)) +;; CHECK-NEXT: (local $c (ref $C)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; A cycle between two globals, where some of the fields participate in the + ;; cycle and some do not. + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut (ref null $B))) (field (mut (ref null $B))) (field (mut (ref null $B))))) + (type $A (struct (field (mut (ref null $B))) (field (mut (ref null $B))) (field (mut (ref null $B))))) + ;; CHECK: (type $B (array (ref null $A))) + (type $B (array (ref null $A))) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_anyref (func (result anyref))) + + ;; CHECK: (global $ctor-eval$global_16 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_16)) + (global $a (mut (ref null $A)) (ref.null $A)) + (global $b (mut (ref null $B)) (ref.null $B)) + + (func $test (export "test") + (local $a (ref $A)) + (global.set $a + (local.tee $a + (struct.new $A + (array.new_default $B + (i32.const 0) + ) + (ref.null $B) + (array.new_default $B + (i32.const 0) + ) + ) + ) + ) + (global.set $b + (array.new_fixed $B + (struct.new_default $A) + (global.get $a) + (struct.new_default $A) + ) + ) + (struct.set $A 1 + (local.get $a) + (global.get $b) + ) + ) + + ;; CHECK: (global $ctor-eval$global_19 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_15 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_14 (ref $B) (array.new_fixed $B + ;; CHECK-NEXT: (global.get $ctor-eval$global_15) + ;; CHECK-NEXT: (global.get $ctor-eval$global_16) + ;; CHECK-NEXT: (global.get $ctor-eval$global_19) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_17 (ref $B) (array.new_fixed $B)) + + ;; CHECK: (global $ctor-eval$global_18 (ref $B) (array.new_fixed $B)) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_anyref) (result anyref) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result (ref null any)) + (struct.get $A 1 + (global.get $a) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global_16) +;; CHECK-NEXT: (global.get $ctor-eval$global_17) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (struct.set $A 1 +;; CHECK-NEXT: (global.get $ctor-eval$global_16) +;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (struct.set $A 2 +;; CHECK-NEXT: (global.get $ctor-eval$global_16) +;; CHECK-NEXT: (global.get $ctor-eval$global_18) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; As above, with the cycle creation logic reversed. + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (ref null $B)) (field (ref null $B)) (field (ref null $B)))) + (type $A (struct (field (ref null $B)) (field (ref null $B)) (field (ref null $B)))) + ;; CHECK: (type $B (array (mut (ref null $A)))) + (type $B (array (mut (ref null $A)))) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_anyref (func (result anyref))) + + ;; CHECK: (global $ctor-eval$global_16 (ref $B) (array.new_fixed $B + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_19 (ref $B) (array.new_fixed $B)) + + ;; CHECK: (global $ctor-eval$global_15 (ref $B) (array.new_fixed $B)) + + ;; CHECK: (global $ctor-eval$global_14 (ref $A) (struct.new $A + ;; CHECK-NEXT: (global.get $ctor-eval$global_15) + ;; CHECK-NEXT: (global.get $ctor-eval$global_16) + ;; CHECK-NEXT: (global.get $ctor-eval$global_19) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_14)) + (global $a (mut (ref null $A)) (ref.null $A)) + (global $b (mut (ref null $B)) (ref.null $B)) + + (func $test (export "test") + (local $b (ref $B)) + (global.set $b + (local.tee $b + (array.new_fixed $B + (struct.new_default $A) + (ref.null $A) + (struct.new_default $A) + ) + ) + ) + (global.set $a + (struct.new $A + (array.new_default $B + (i32.const 0) + ) + (global.get $b) + (array.new_default $B + (i32.const 0) + ) + ) + ) + (array.set $B + (local.get $b) + (i32.const 1) + (global.get $a) + ) + ) + + ;; CHECK: (global $ctor-eval$global_17 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_18 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + + ;; CHECK: (func $keepalive (type $none_=>_anyref) (result anyref) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result (ref null any)) + (struct.get $A 1 + (global.get $a) + ) + ) +) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (array.set $B +;; CHECK-NEXT: (global.get $ctor-eval$global_16) +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: (global.get $ctor-eval$global_17) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (array.set $B +;; CHECK-NEXT: (global.get $ctor-eval$global_16) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: (global.get $ctor-eval$global_14) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (array.set $B +;; CHECK-NEXT: (global.get $ctor-eval$global_16) +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: (global.get $ctor-eval$global_18) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $b (ref $B)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; The start function already exists here. We must prepend to it. + + ;; CHECK: (type $A (struct (field (mut (ref null $A))) (field i32))) + (type $A (struct (field (mut (ref null $A))) (field i32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (global $ctor-eval$global_4 (ref $A) (struct.new $A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $a (mut (ref null $A)) (global.get $ctor-eval$global_4)) + (global $a (mut (ref null $A)) (ref.null $A)) + + ;; CHECK: (global $b (mut (ref null $A)) (ref.null none)) + (global $b (mut (ref null $A)) (ref.null $A)) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (export "keepalive" (func $keepalive)) + + ;; CHECK: (start $start) + (start $start) + + (func $test (export "test") + (local $a (ref $A)) + (global.set $a + (local.tee $a + (struct.new $A + (ref.null $A) + (i32.const 42) + ) + ) + ) + (struct.set $A 0 + (local.get $a) + (local.get $a) + ) + ) + + ;; CHECK: (func $keepalive (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $A 1 + ;; CHECK-NEXT: (global.get $b) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $keepalive (export "keepalive") (result i32) + (i32.add + (struct.get $A 1 + (global.get $a) + ) + (struct.get $A 1 + (global.get $b) + ) + ) + ) + + ;; CHECK: (func $start (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (global.get $ctor-eval$global_4) + ;; CHECK-NEXT: (global.get $ctor-eval$global_4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $b + ;; CHECK-NEXT: (global.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $start + (global.set $b + (global.get $a) + ) + ) +) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (local $a (ref $A)) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) +(module + ;; CHECK: (type $A (struct (field (mut (ref null $A))))) + (type $A (struct (field (mut (ref null $A))))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $anyref_=>_none (func (param anyref))) + + ;; CHECK: (import "a" "b" (func $import (param anyref))) + (import "a" "b" (func $import (param anyref))) + + (func $test (export "test") + (local $a (ref $A)) + (struct.set $A 0 + (local.tee $a + (struct.new_default $A) + ) + (local.get $a) + ) + ;; The previous instructions created a cycle, which we now send to an import. + ;; The import will block us from evalling the entire function, and we will + ;; only partially eval it, removing the statements before the call. Note that + ;; the cycle only exists in local state - there is no global it is copied to - + ;; and so this test verifies that we handle cycles in local state. + (call $import + (local.get $a) + ) + ) +) +;; CHECK: (global $ctor-eval$global (ref $A) (struct.new $A +;; CHECK-NEXT: (ref.null none) +;; CHECK-NEXT: )) + +;; CHECK: (export "test" (func $test_3)) + +;; CHECK: (start $start) + +;; CHECK: (func $start (type $none_=>_none) +;; CHECK-NEXT: (struct.set $A 0 +;; CHECK-NEXT: (global.get $ctor-eval$global) +;; CHECK-NEXT: (global.get $ctor-eval$global) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $test_3 (type $none_=>_none) +;; CHECK-NEXT: (call $import +;; CHECK-NEXT: (global.get $ctor-eval$global) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) diff --git a/test/lit/ctor-eval/partial-global.wat b/test/lit/ctor-eval/partial-global.wat new file mode 100644 index 000000000..fdd196d02 --- /dev/null +++ b/test/lit/ctor-eval/partial-global.wat @@ -0,0 +1,39 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-ctor-eval %s --ctors=test --kept-exports=test --quiet -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (global $global (mut i32) (i32.const 0)) + (global $global (mut i32) (i32.const 0)) + + (func $test (export "test") + ;; The nop can be evalled away, but not the loop. We should not apply any + ;; partial results from the loop - in particular, the global must remain at + ;; 0. That is, the global.set of 999 below must not be applied to the global. + ;; + ;; (It is true that in this simple module it would be ok to set 999 to the + ;; global, but if the global were exported for example then that would not + ;; be the case, nor would it be the case if the code did $global = $global + 1 + ;; or such. That is, since the global.set is not evalled away, its effects + ;; must not be applied; we do both atomically or neither, so that the + ;; global.set's execution only happens once.) + + (nop) + (loop + (global.set $global + (i32.const 999) + ) + (unreachable) + ) + ) +) + +;; CHECK: (export "test" (func $test_1)) + +;; CHECK: (func $test_1 (type $none_=>_none) +;; CHECK-NEXT: (global.set $global +;; CHECK-NEXT: (i32.const 999) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) |