summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2021-10-15 13:03:33 -0700
committerGitHub <noreply@github.com>2021-10-15 20:03:33 +0000
commit980bb397fa713385d7e0a7f1ddc0318a3da7234c (patch)
tree6ee0f879e6c63a533e52ca1161148df177a5ff3b
parent0a1ac51b1e7e88f0ce89793c71333bb8bc7335d0 (diff)
downloadbinaryen-980bb397fa713385d7e0a7f1ddc0318a3da7234c.tar.gz
binaryen-980bb397fa713385d7e0a7f1ddc0318a3da7234c.tar.bz2
binaryen-980bb397fa713385d7e0a7f1ddc0318a3da7234c.zip
[Wasm GC] Propagate immutable fields (#4251)
Very simple with the work so far, just add StructGet/ArrayGet code to check if the field is immutable, and allow the get to go through in that case.
-rw-r--r--src/passes/Precompute.cpp33
-rw-r--r--test/lit/passes/precompute-gc-immutable.wast820
2 files changed, 851 insertions, 2 deletions
diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp
index 876b09acd..591245ab5 100644
--- a/src/passes/Precompute.cpp
+++ b/src/passes/Precompute.cpp
@@ -129,7 +129,25 @@ public:
return getHeapCreationFlow(flow, curr);
}
Flow visitStructSet(StructSet* curr) { return Flow(NONCONSTANT_FLOW); }
- Flow visitStructGet(StructGet* curr) { return Flow(NONCONSTANT_FLOW); }
+ Flow visitStructGet(StructGet* curr) {
+ if (curr->ref->type != Type::unreachable) {
+ // If this field is immutable then we may be able to precompute this, as
+ // if we also created the data in this function (or it was created in an
+ // immutable global) then we know the value in the field. If it is
+ // immutable, call the super method which will do the rest here. That
+ // includes checking for the data being properly created, as if it was
+ // not then we will not have a constant value for it, which means the
+ // local.get of that value will stop us.
+ auto& field =
+ curr->ref->type.getHeapType().getStruct().fields[curr->index];
+ if (field.mutable_ == Immutable) {
+ return Super::visitStructGet(curr);
+ }
+ }
+
+ // Otherwise, we've failed to precompute.
+ return Flow(NONCONSTANT_FLOW);
+ }
Flow visitArrayNew(ArrayNew* curr) {
auto flow = Super::visitArrayNew(curr);
if (flow.breaking()) {
@@ -145,7 +163,18 @@ public:
return getHeapCreationFlow(flow, curr);
}
Flow visitArraySet(ArraySet* curr) { return Flow(NONCONSTANT_FLOW); }
- Flow visitArrayGet(ArrayGet* curr) { return Flow(NONCONSTANT_FLOW); }
+ Flow visitArrayGet(ArrayGet* curr) {
+ if (curr->ref->type != Type::unreachable) {
+ // See above with struct.get
+ auto element = curr->ref->type.getHeapType().getArray().element;
+ if (element.mutable_ == Immutable) {
+ return Super::visitArrayGet(curr);
+ }
+ }
+
+ // Otherwise, we've failed to precompute.
+ return Flow(NONCONSTANT_FLOW);
+ }
Flow visitArrayLen(ArrayLen* curr) { return Flow(NONCONSTANT_FLOW); }
Flow visitArrayCopy(ArrayCopy* curr) { return Flow(NONCONSTANT_FLOW); }
diff --git a/test/lit/passes/precompute-gc-immutable.wast b/test/lit/passes/precompute-gc-immutable.wast
new file mode 100644
index 000000000..50dbef8f1
--- /dev/null
+++ b/test/lit/passes/precompute-gc-immutable.wast
@@ -0,0 +1,820 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+
+;; RUN: foreach %s %t wasm-opt --precompute-propagate -all --nominal -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $struct-imm (struct_subtype (field i32) data))
+ (type $struct-imm (struct_subtype i32 data))
+
+ ;; CHECK: (type $struct-mut (struct_subtype (field (mut i32)) data))
+ (type $struct-mut (struct_subtype (mut i32) data))
+
+ ;; CHECK: (func $propagate
+ ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm))
+ ;; CHECK-NEXT: (local $ref-mut (ref null $struct-mut))
+ ;; CHECK-NEXT: (local.set $ref-imm
+ ;; CHECK-NEXT: (struct.new $struct-imm
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $ref-mut
+ ;; CHECK-NEXT: (struct.new $struct-mut
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $struct-mut 0
+ ;; CHECK-NEXT: (local.get $ref-mut)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $propagate
+ (local $ref-imm (ref null $struct-imm))
+ (local $ref-mut (ref null $struct-mut))
+ ;; We can propagate from an immutable field of a struct created in this
+ ;; function.
+ (local.set $ref-imm
+ (struct.new $struct-imm
+ (i32.const 1)
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ ;; But the same thing on a mutable field fails.
+ (local.set $ref-mut
+ (struct.new $struct-mut
+ (i32.const 1)
+ )
+ )
+ (call $helper
+ (struct.get $struct-mut 0
+ (local.get $ref-mut)
+ )
+ )
+ )
+
+ ;; CHECK: (func $non-constant (param $param i32)
+ ;; CHECK-NEXT: (local $ref (ref null $struct-imm))
+ ;; CHECK-NEXT: (local.set $ref
+ ;; CHECK-NEXT: (struct.new $struct-imm
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $struct-imm 0
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $non-constant (param $param i32)
+ (local $ref (ref null $struct-imm))
+ (local.set $ref
+ (struct.new $struct-imm
+ ;; This value is not constant, so we have nothing to propagate.
+ (local.get $param)
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref)
+ )
+ )
+ )
+
+ ;; CHECK: (func $unreachable
+ ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm))
+ ;; CHECK-NEXT: (local.tee $ref-imm
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $struct-imm 0
+ ;; CHECK-NEXT: (local.get $ref-imm)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $unreachable
+ (local $ref-imm (ref null $struct-imm))
+ ;; Test we do not error on an unreachable value.
+ (local.set $ref-imm
+ (struct.new $struct-imm
+ (unreachable)
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ )
+
+ ;; CHECK: (func $param (param $ref-imm (ref null $struct-imm))
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $struct-imm 0
+ ;; CHECK-NEXT: (local.get $ref-imm)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $param (param $ref-imm (ref null $struct-imm))
+ ;; Test we ignore a param value, whose data we do not know.
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ )
+
+ ;; CHECK: (func $local-null
+ ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm))
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $struct-imm 0
+ ;; CHECK-NEXT: (ref.null $struct-imm)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $local-null
+ (local $ref-imm (ref null $struct-imm))
+ ;; Test we ignore a local value that is null.
+ ;; TODO: this could be precomputed to an unreachable
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ )
+
+ ;; CHECK: (func $local-unknown (param $x i32)
+ ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm))
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (local.set $ref-imm
+ ;; CHECK-NEXT: (struct.new $struct-imm
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $ref-imm
+ ;; CHECK-NEXT: (struct.new $struct-imm
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $struct-imm 0
+ ;; CHECK-NEXT: (local.get $ref-imm)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $local-unknown (param $x i32)
+ (local $ref-imm (ref null $struct-imm))
+ ;; Do not propagate if a local has more than one possible struct.new with
+ ;; different values.
+ (if
+ (local.get $x)
+ (local.set $ref-imm
+ (struct.new $struct-imm
+ (i32.const 1)
+ )
+ )
+ (local.set $ref-imm
+ (struct.new $struct-imm
+ (i32.const 2)
+ )
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ )
+
+ ;; CHECK: (func $local-unknown-ref-same-value (param $x i32)
+ ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm))
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (local.set $ref-imm
+ ;; CHECK-NEXT: (struct.new $struct-imm
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $ref-imm
+ ;; CHECK-NEXT: (struct.new $struct-imm
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $struct-imm 0
+ ;; CHECK-NEXT: (local.get $ref-imm)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $local-unknown-ref-same-value (param $x i32)
+ (local $ref-imm (ref null $struct-imm))
+ ;; As above, but the two different refs have the same value, so we can in
+ ;; theory optimize. However, atm we do nothing here, as the analysis stops
+ ;; when it sees it cannot propagate the local value (the ref, which has two
+ ;; possible values).
+ (if
+ (local.get $x)
+ (local.set $ref-imm
+ (struct.new $struct-imm
+ (i32.const 1)
+ )
+ )
+ (local.set $ref-imm
+ (struct.new $struct-imm
+ (i32.const 1)
+ )
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ )
+
+ ;; CHECK: (func $propagate-multi-refs (param $x i32)
+ ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm))
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (local.set $ref-imm
+ ;; CHECK-NEXT: (struct.new $struct-imm
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $block0
+ ;; CHECK-NEXT: (local.set $ref-imm
+ ;; CHECK-NEXT: (struct.new $struct-imm
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $propagate-multi-refs (param $x i32)
+ (local $ref-imm (ref null $struct-imm))
+ ;; Propagate more than once in a function, using the same local that is
+ ;; reused.
+ (if
+ (local.get $x)
+ (block
+ (local.set $ref-imm
+ (struct.new $struct-imm
+ (i32.const 1)
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ )
+ (block
+ (local.set $ref-imm
+ (struct.new $struct-imm
+ (i32.const 2)
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $propagate-multi-values (param $x i32)
+ ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm))
+ ;; CHECK-NEXT: (local.set $ref-imm
+ ;; CHECK-NEXT: (struct.new $struct-imm
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $propagate-multi-values (param $x i32)
+ (local $ref-imm (ref null $struct-imm))
+ ;; Propagate a ref's value more than once
+ (local.set $ref-imm
+ (struct.new $struct-imm
+ (i32.const 1)
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ (call $helper
+ (struct.get $struct-imm 0
+ (local.get $ref-imm)
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper (param $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $helper (param i32))
+)
+
+(module
+ ;; One field is immutable, the other is not, so we can only propagate the
+ ;; former.
+ ;; CHECK: (type $struct (struct_subtype (field (mut i32)) (field i32) data))
+ (type $struct (struct_subtype (mut i32) i32 data))
+
+ ;; CHECK: (func $propagate
+ ;; CHECK-NEXT: (local $ref (ref null $struct))
+ ;; CHECK-NEXT: (local.set $ref
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $propagate
+ (local $ref (ref null $struct))
+ ;; We can propagate from an immutable field of a struct created in this
+ ;; function.
+ (local.set $ref
+ (struct.new $struct
+ (i32.const 1)
+ (i32.const 2)
+ )
+ )
+ (call $helper
+ (struct.get $struct 0
+ (local.get $ref)
+ )
+ )
+ (call $helper
+ (struct.get $struct 1
+ (local.get $ref)
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper (param $0 i32)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $helper (param i32))
+)
+
+(module
+ ;; Create an immutable vtable in an immutable field, which lets us read from
+ ;; it.
+
+ ;; CHECK: (type $object (struct_subtype (field (ref $vtable)) data))
+
+ ;; CHECK: (type $vtable (struct_subtype (field funcref) data))
+ (type $vtable (struct_subtype funcref data))
+ (type $object (struct_subtype (ref $vtable) data))
+
+ ;; CHECK: (func $nested-creations
+ ;; CHECK-NEXT: (local $ref (ref null $object))
+ ;; CHECK-NEXT: (local.set $ref
+ ;; CHECK-NEXT: (struct.new $object
+ ;; CHECK-NEXT: (struct.new $vtable
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nested-creations
+ (local $ref (ref null $object))
+ ;; Create an object with a reference to another object, and propagate
+ ;; through both of them to a constant value, which saves two struct.gets.
+ (local.set $ref
+ (struct.new $object
+ (struct.new $vtable
+ (ref.func $nested-creations)
+ )
+ )
+ )
+ (call $helper
+ (struct.get $vtable 0
+ (struct.get $object 0
+ (local.get $ref)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper (param $0 funcref)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $helper (param funcref))
+)
+
+(module
+ ;; As above, but make $vtable not immutable, which prevents optimization.
+
+ ;; CHECK: (type $object (struct_subtype (field (ref $vtable)) data))
+
+ ;; CHECK: (type $vtable (struct_subtype (field (mut funcref)) data))
+ (type $vtable (struct_subtype (mut funcref) data))
+ (type $object (struct_subtype (ref $vtable) data))
+
+ ;; CHECK: (func $nested-creations
+ ;; CHECK-NEXT: (local $ref (ref null $object))
+ ;; CHECK-NEXT: (local.set $ref
+ ;; CHECK-NEXT: (struct.new $object
+ ;; CHECK-NEXT: (struct.new $vtable
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $vtable 0
+ ;; CHECK-NEXT: (struct.get $object 0
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nested-creations
+ (local $ref (ref null $object))
+ (local.set $ref
+ (struct.new $object
+ (struct.new $vtable
+ (ref.func $nested-creations)
+ )
+ )
+ )
+ (call $helper
+ (struct.get $vtable 0
+ ;; Note that we *can* precompute the first struct.get here, but there
+ ;; is no constant expression we can emit for it, so we do nothing.
+ (struct.get $object 0
+ (local.get $ref)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper (param $0 funcref)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $helper (param funcref))
+)
+
+
+(module
+ ;; As above, but make $object not immutable, which prevents optimization.
+
+ ;; CHECK: (type $object (struct_subtype (field (mut (ref $vtable))) data))
+
+ ;; CHECK: (type $vtable (struct_subtype (field funcref) data))
+ (type $vtable (struct_subtype funcref data))
+ (type $object (struct_subtype (mut (ref $vtable)) data))
+
+ ;; CHECK: (func $nested-creations
+ ;; CHECK-NEXT: (local $ref (ref null $object))
+ ;; CHECK-NEXT: (local.set $ref
+ ;; CHECK-NEXT: (struct.new $object
+ ;; CHECK-NEXT: (struct.new $vtable
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $vtable 0
+ ;; CHECK-NEXT: (struct.get $object 0
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nested-creations
+ (local $ref (ref null $object))
+ (local.set $ref
+ (struct.new $object
+ (struct.new $vtable
+ (ref.func $nested-creations)
+ )
+ )
+ )
+ (call $helper
+ (struct.get $vtable 0
+ (struct.get $object 0
+ (local.get $ref)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper (param $0 funcref)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $helper (param funcref))
+)
+
+(module
+ ;; Create an immutable vtable in an immutable global, which we can optimize
+ ;; with.
+
+ ;; CHECK: (type $vtable (struct_subtype (field funcref) data))
+ (type $vtable (struct_subtype funcref data))
+ ;; CHECK: (type $object (struct_subtype (field (ref $vtable)) data))
+ (type $object (struct_subtype (ref $vtable) data))
+
+ ;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: ))
+ (global $vtable (ref $vtable)
+ (struct.new $vtable
+ (ref.func $nested-creations)
+ )
+ )
+
+ ;; CHECK: (func $nested-creations
+ ;; CHECK-NEXT: (local $ref (ref null $object))
+ ;; CHECK-NEXT: (local.set $ref
+ ;; CHECK-NEXT: (struct.new $object
+ ;; CHECK-NEXT: (global.get $vtable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nested-creations
+ (local $ref (ref null $object))
+ (local.set $ref
+ (struct.new $object
+ (global.get $vtable)
+ )
+ )
+ (call $helper
+ (struct.get $vtable 0
+ (struct.get $object 0
+ (local.get $ref)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper (param $0 funcref)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $helper (param funcref))
+)
+
+(module
+ ;; Create an immutable vtable in an mutable global, whose mutability prevents
+ ;; optimization.
+
+ ;; CHECK: (type $vtable (struct_subtype (field funcref) data))
+ (type $vtable (struct_subtype funcref data))
+ ;; CHECK: (type $object (struct_subtype (field (ref $vtable)) data))
+ (type $object (struct_subtype (ref $vtable) data))
+
+ ;; CHECK: (global $vtable (mut (ref $vtable)) (struct.new $vtable
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: ))
+ (global $vtable (mut (ref $vtable))
+ (struct.new $vtable
+ (ref.func $nested-creations)
+ )
+ )
+
+ ;; CHECK: (func $nested-creations
+ ;; CHECK-NEXT: (local $ref (ref null $object))
+ ;; CHECK-NEXT: (local.set $ref
+ ;; CHECK-NEXT: (struct.new $object
+ ;; CHECK-NEXT: (global.get $vtable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (struct.get $vtable 0
+ ;; CHECK-NEXT: (struct.get $object 0
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nested-creations
+ (local $ref (ref null $object))
+ (local.set $ref
+ (struct.new $object
+ (global.get $vtable)
+ )
+ )
+ (call $helper
+ (struct.get $vtable 0
+ (struct.get $object 0
+ (local.get $ref)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper (param $0 funcref)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $helper (param funcref))
+)
+
+(module
+ ;; Create an immutable vtable in an immutable global, but using an array
+ ;; instead of a struct.
+
+ ;; CHECK: (type $object (struct_subtype (field (ref $vtable)) data))
+
+ ;; CHECK: (type $vtable (array_subtype funcref data))
+ (type $vtable (array_subtype funcref data))
+ (type $object (struct_subtype (ref $vtable) data))
+
+ ;; CHECK: (global $vtable (ref $vtable) (array.init_static $vtable
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: ))
+ (global $vtable (ref $vtable)
+ (array.init_static $vtable
+ (ref.func $nested-creations)
+ )
+ )
+
+ ;; CHECK: (func $nested-creations (param $param i32)
+ ;; CHECK-NEXT: (local $ref (ref null $object))
+ ;; CHECK-NEXT: (local.set $ref
+ ;; CHECK-NEXT: (struct.new $object
+ ;; CHECK-NEXT: (global.get $vtable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (array.get $vtable
+ ;; CHECK-NEXT: (struct.get $object 0
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $param)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nested-creations (param $param i32)
+ (local $ref (ref null $object))
+ (local.set $ref
+ (struct.new $object
+ (global.get $vtable)
+ )
+ )
+ (call $helper
+ (array.get $vtable
+ (struct.get $object 0
+ (local.get $ref)
+ )
+ (i32.const 0)
+ )
+ )
+ ;; The second operation here uses a param for the array index, which is not
+ ;; constant.
+ (call $helper
+ (array.get $vtable
+ (struct.get $object 0
+ (local.get $ref)
+ )
+ (local.get $param)
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper (param $0 funcref)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $helper (param funcref))
+)
+
+(module
+ ;; A j2wasm-like itable pattern: An itable is an array of (possibly-null)
+ ;; data that is filled with vtables of different types. On usage, we do a
+ ;; cast of the vtable type.
+
+ ;; CHECK: (type $itable (array_subtype (ref null data) data))
+ (type $itable (array_subtype (ref null data) data))
+
+ ;; CHECK: (type $object (struct_subtype (field (ref $itable)) data))
+ (type $object (struct_subtype (ref $itable) data))
+
+ ;; CHECK: (type $vtable-0 (struct_subtype (field funcref) data))
+ (type $vtable-0 (struct_subtype funcref data))
+
+ ;; CHECK: (type $vtable-1 (struct_subtype (field funcref) data))
+ (type $vtable-1 (struct_subtype funcref data))
+
+ ;; CHECK: (global $itable (ref $itable) (array.init_static $itable
+ ;; CHECK-NEXT: (struct.new $vtable-0
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.new $vtable-1
+ ;; CHECK-NEXT: (ref.func $helper)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $itable (ref $itable)
+ (array.init_static $itable
+ (struct.new $vtable-0
+ (ref.func $nested-creations)
+ )
+ (struct.new $vtable-1
+ (ref.func $helper)
+ )
+ )
+ )
+
+ ;; CHECK: (func $nested-creations
+ ;; CHECK-NEXT: (local $ref (ref null $object))
+ ;; CHECK-NEXT: (local.set $ref
+ ;; CHECK-NEXT: (struct.new $object
+ ;; CHECK-NEXT: (global.get $itable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (ref.func $nested-creations)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (ref.func $helper)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nested-creations
+ (local $ref (ref null $object))
+ (local.set $ref
+ (struct.new $object
+ (global.get $itable)
+ )
+ )
+ ;; We can precompute all these operations away into the final constants.
+ (call $helper
+ (struct.get $vtable-0 0
+ (ref.cast_static $vtable-0
+ (array.get $itable
+ (struct.get $object 0
+ (local.get $ref)
+ )
+ (i32.const 0)
+ )
+ )
+ )
+ )
+ (call $helper
+ (struct.get $vtable-1 0
+ (ref.cast_static $vtable-1
+ (array.get $itable
+ (struct.get $object 0
+ (local.get $ref)
+ )
+ (i32.const 1)
+ )
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $helper (param $0 funcref)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $helper (param funcref))
+)