diff options
-rw-r--r-- | src/passes/Precompute.cpp | 33 | ||||
-rw-r--r-- | test/lit/passes/precompute-gc-immutable.wast | 820 |
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)) +) |