From ad60ad65914e69e8f0a5f8094ce277cd10143ff3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 23 Mar 2026 10:25:45 -0700 Subject: [PATCH 1/4] work --- src/passes/Precompute.cpp | 1 + test/lit/passes/precompute-gc-immutable.wast | 931 +------------------ 2 files changed, 9 insertions(+), 923 deletions(-) diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 274689c0167..f8d9164747f 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -215,6 +215,7 @@ class PrecomputingExpressionRunner } // ArrayLen is not disallowed here as it is an immutable property. Flow visitArrayCopy(ArrayCopy* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitArrayStore(ArrayStore* curr) { return Flow(NONCONSTANT_FLOW); } // Generates heap info for a heap-allocating expression. Flow getGCAllocation(Expression* curr, std::function visitFunc) { diff --git a/test/lit/passes/precompute-gc-immutable.wast b/test/lit/passes/precompute-gc-immutable.wast index 7520c8f1266..370ed42402c 100644 --- a/test/lit/passes/precompute-gc-immutable.wast +++ b/test/lit/passes/precompute-gc-immutable.wast @@ -2,930 +2,15 @@ ;; RUN: foreach %s %t wasm-opt --precompute-propagate -all -S -o - | filecheck %s -(module - ;; CHECK: (type $struct-imm (struct (field i32))) - (type $struct-imm (struct i32)) +(module + (type $array (array (mut i8))) - ;; CHECK: (type $struct-mut (struct (field (mut i32)))) - (type $struct-mut (struct (mut i32))) - - ;; CHECK: (type $array-mut (array (mut i32))) - - ;; CHECK: (type $array-imm (array i32)) - (type $array-imm (array i32)) - - (type $array-mut (array (mut i32))) - - ;; CHECK: (func $propagate-struct (type $2) - ;; 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-struct - (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 $propagate-array (type $2) - ;; CHECK-NEXT: (local $ref-imm (ref null $array-imm)) - ;; CHECK-NEXT: (local $ref-mut (ref null $array-mut)) - ;; CHECK-NEXT: (local.set $ref-imm - ;; CHECK-NEXT: (array.new_fixed $array-imm 3 - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: (i32.const 20) - ;; CHECK-NEXT: (i32.const 30) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $helper - ;; CHECK-NEXT: (i32.const 30) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $helper - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (local.set $ref-mut - ;; CHECK-NEXT: (array.new_fixed $array-mut 3 - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: (i32.const 20) - ;; CHECK-NEXT: (i32.const 30) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $helper - ;; CHECK-NEXT: (array.get $array-mut - ;; CHECK-NEXT: (local.get $ref-mut) - ;; CHECK-NEXT: (i32.const 2) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $helper - ;; CHECK-NEXT: (i32.const 3) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $propagate-array - (local $ref-imm (ref null $array-imm)) - (local $ref-mut (ref null $array-mut)) - ;; We can propagate from a slot in an immutable array created in this - ;; function, and also get the length. - (local.set $ref-imm - (array.new_fixed $array-imm 3 - (i32.const 10) - (i32.const 20) - (i32.const 30) - ) - ) - (call $helper - (array.get $array-imm ;; this returns 30 - (local.get $ref-imm) - (i32.const 2) - ) - ) - (call $helper - (array.len ;; this returns 3 - (local.get $ref-imm) - ) - ) - ;; But the same thing on a mutable array fails. - (local.set $ref-mut - (array.new_fixed $array-mut 3 - (i32.const 10) - (i32.const 20) - (i32.const 30) - ) - ) - (call $helper - (array.get $array-mut - (local.get $ref-mut) - (i32.const 2) - ) - ) - ;; We can, however, optimize array.len in both cases as that is an - ;; immutable property. - (call $helper - (array.len ;; this returns 3 - (local.get $ref-mut) - ) - ) - ) - - ;; CHECK: (func $non-constant (type $1) (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 (type $2) - ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) - ;; CHECK-NEXT: (local.tee $ref-imm - ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: ) - ;; 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 (type $6) (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 (type $2) - ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) - ;; CHECK-NEXT: (call $helper - ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (unreachable) - ;; 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 (type $1) (param $x i32) - ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; 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: (else - ;; 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: ) - ;; 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) - (then - (local.set $ref-imm - (struct.new $struct-imm - (i32.const 1) - ) - ) - ) - (else - (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 (type $1) (param $x i32) - ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; 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: (else - ;; 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: ) - ;; 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) - (then - (local.set $ref-imm - (struct.new $struct-imm - (i32.const 1) - ) - ) - ) - (else - (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 (type $1) (param $x i32) - ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) - ;; CHECK-NEXT: (if - ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (then - ;; 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: (else - ;; 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) - (then - (block - (local.set $ref-imm - (struct.new $struct-imm - (i32.const 1) - ) - ) - (call $helper - (struct.get $struct-imm 0 - (local.get $ref-imm) - ) - ) - ) - ) - (else - (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 (type $1) (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 (type $1) (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 (field (mut i32)) (field i32))) - (type $struct (struct (mut i32) i32)) - - ;; CHECK: (func $propagate (type $1) - ;; 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 (type $2) (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 $vtable (struct (field funcref))) - (type $vtable (struct funcref)) - ;; CHECK: (type $object (struct (field (ref $vtable)))) - (type $object (struct (ref $vtable))) - - ;; CHECK: (func $nested-creations (type $2) - ;; 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 (type $3) (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 $vtable (struct (field (mut funcref)))) - (type $vtable (struct (mut funcref))) - ;; CHECK: (type $object (struct (field (ref $vtable)))) - (type $object (struct (ref $vtable))) - - ;; CHECK: (func $nested-creations (type $2) - ;; 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) - ) - ) + (func $array.store (param $x (ref $array)) + ;; ArrayStore operations cannot be removed. + (i64.store32 (type $array) + (local.get $array) + (i32.const 0) + (i64.const 1) ) ) - - ;; CHECK: (func $helper (type $3) (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 $vtable (struct (field funcref))) - (type $vtable (struct funcref)) - ;; CHECK: (type $object (struct (field (mut (ref $vtable))))) - (type $object (struct (mut (ref $vtable)))) - - ;; CHECK: (func $nested-creations (type $2) - ;; 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 (type $3) (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 (field funcref))) - (type $vtable (struct funcref)) - ;; CHECK: (type $object (struct (field (ref $vtable)))) - (type $object (struct (ref $vtable))) - - ;; 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 (type $2) - ;; 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 (type $3) (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 (field funcref))) - (type $vtable (struct funcref)) - ;; CHECK: (type $object (struct (field (ref $vtable)))) - (type $object (struct (ref $vtable))) - - ;; 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 (type $2) - ;; 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 (type $3) (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 $vtable (array funcref)) - (type $vtable (array funcref)) - ;; CHECK: (type $object (struct (field (ref $vtable)))) - (type $object (struct (ref $vtable))) - - ;; CHECK: (global $vtable (ref $vtable) (array.new_fixed $vtable 1 - ;; CHECK-NEXT: (ref.func $nested-creations) - ;; CHECK-NEXT: )) - (global $vtable (ref $vtable) - (array.new_fixed $vtable 1 - (ref.func $nested-creations) - ) - ) - - ;; CHECK: (func $nested-creations (type $2) (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 (type $3) (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. - - (rec - ;; CHECK: (rec - ;; CHECK-NEXT: (type $itable (array structref)) - (type $itable (array (ref null struct))) - - ;; CHECK: (type $object (struct (field (ref $itable)))) - (type $object (struct (ref $itable))) - - ;; CHECK: (type $vtable-0 (struct (field funcref))) - (type $vtable-0 (struct funcref)) - - ;; CHECK: (type $vtable-1 (struct (field funcref))) - (type $vtable-1 (struct funcref)) - ) - - ;; CHECK: (global $itable (ref $itable) (array.new_fixed $itable 2 - ;; 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.new_fixed $itable 2 - (struct.new $vtable-0 - (ref.func $nested-creations) - ) - (struct.new $vtable-1 - (ref.func $helper) - ) - ) - ) - - ;; CHECK: (func $nested-creations (type $4) - ;; 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 (ref null $vtable-0) - (array.get $itable - (struct.get $object 0 - (local.get $ref) - ) - (i32.const 0) - ) - ) - ) - ) - (call $helper - (struct.get $vtable-1 0 - (ref.cast (ref null $vtable-1) - (array.get $itable - (struct.get $object 0 - (local.get $ref) - ) - (i32.const 1) - ) - ) - ) - ) - ) - - ;; CHECK: (func $helper (type $5) (param $0 funcref) - ;; CHECK-NEXT: (nop) - ;; CHECK-NEXT: ) - (func $helper (param funcref)) ) From 86be51a05d7581d136c91a341a4053bc5e88337f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 23 Mar 2026 10:26:06 -0700 Subject: [PATCH 2/4] fix --- test/lit/passes/precompute-gc-immutable.wast | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/lit/passes/precompute-gc-immutable.wast b/test/lit/passes/precompute-gc-immutable.wast index 370ed42402c..d45a284da59 100644 --- a/test/lit/passes/precompute-gc-immutable.wast +++ b/test/lit/passes/precompute-gc-immutable.wast @@ -2,13 +2,21 @@ ;; RUN: foreach %s %t wasm-opt --precompute-propagate -all -S -o - | filecheck %s -(module - (type $array (array (mut i8))) +(module + ;; CHECK: (type $array (array (mut i8))) + (type $array (array (mut i8))) + ;; CHECK: (func $array.store (type $1) (param $x (ref $array)) + ;; CHECK-NEXT: (i64.store32 (type $array) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) (func $array.store (param $x (ref $array)) ;; ArrayStore operations cannot be removed. (i64.store32 (type $array) - (local.get $array) + (local.get $x) (i32.const 0) (i64.const 1) ) From 09fc2cfb11d06f892ac40f2156f01d41c3843836 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 23 Mar 2026 11:24:32 -0700 Subject: [PATCH 3/4] fix --- test/lit/passes/precompute-gc-immutable.wast | 929 ++++++++++++++++++- 1 file changed, 918 insertions(+), 11 deletions(-) diff --git a/test/lit/passes/precompute-gc-immutable.wast b/test/lit/passes/precompute-gc-immutable.wast index d45a284da59..7520c8f1266 100644 --- a/test/lit/passes/precompute-gc-immutable.wast +++ b/test/lit/passes/precompute-gc-immutable.wast @@ -3,22 +3,929 @@ ;; RUN: foreach %s %t wasm-opt --precompute-propagate -all -S -o - | filecheck %s (module - ;; CHECK: (type $array (array (mut i8))) - (type $array (array (mut i8))) + ;; CHECK: (type $struct-imm (struct (field i32))) + (type $struct-imm (struct i32)) - ;; CHECK: (func $array.store (type $1) (param $x (ref $array)) - ;; CHECK-NEXT: (i64.store32 (type $array) + ;; CHECK: (type $struct-mut (struct (field (mut i32)))) + (type $struct-mut (struct (mut i32))) + + ;; CHECK: (type $array-mut (array (mut i32))) + + ;; CHECK: (type $array-imm (array i32)) + (type $array-imm (array i32)) + + (type $array-mut (array (mut i32))) + + ;; CHECK: (func $propagate-struct (type $2) + ;; 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-struct + (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 $propagate-array (type $2) + ;; CHECK-NEXT: (local $ref-imm (ref null $array-imm)) + ;; CHECK-NEXT: (local $ref-mut (ref null $array-mut)) + ;; CHECK-NEXT: (local.set $ref-imm + ;; CHECK-NEXT: (array.new_fixed $array-imm 3 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref-mut + ;; CHECK-NEXT: (array.new_fixed $array-mut 3 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (array.get $array-mut + ;; CHECK-NEXT: (local.get $ref-mut) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $propagate-array + (local $ref-imm (ref null $array-imm)) + (local $ref-mut (ref null $array-mut)) + ;; We can propagate from a slot in an immutable array created in this + ;; function, and also get the length. + (local.set $ref-imm + (array.new_fixed $array-imm 3 + (i32.const 10) + (i32.const 20) + (i32.const 30) + ) + ) + (call $helper + (array.get $array-imm ;; this returns 30 + (local.get $ref-imm) + (i32.const 2) + ) + ) + (call $helper + (array.len ;; this returns 3 + (local.get $ref-imm) + ) + ) + ;; But the same thing on a mutable array fails. + (local.set $ref-mut + (array.new_fixed $array-mut 3 + (i32.const 10) + (i32.const 20) + (i32.const 30) + ) + ) + (call $helper + (array.get $array-mut + (local.get $ref-mut) + (i32.const 2) + ) + ) + ;; We can, however, optimize array.len in both cases as that is an + ;; immutable property. + (call $helper + (array.len ;; this returns 3 + (local.get $ref-mut) + ) + ) + ) + + ;; CHECK: (func $non-constant (type $1) (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 (type $2) + ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) + ;; CHECK-NEXT: (local.tee $ref-imm + ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; 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 (type $6) (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 (type $2) + ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; 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 (type $1) (param $x i32) + ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) + ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) - ;; CHECK-NEXT: (i32.const 0) - ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: (then + ;; 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: (else + ;; 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: ) + ;; 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) + (then + (local.set $ref-imm + (struct.new $struct-imm + (i32.const 1) + ) + ) + ) + (else + (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 (type $1) (param $x i32) + ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; 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: (else + ;; 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: ) + ;; CHECK-NEXT: (call $helper + ;; CHECK-NEXT: (struct.get $struct-imm 0 + ;; CHECK-NEXT: (local.get $ref-imm) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $array.store (param $x (ref $array)) - ;; ArrayStore operations cannot be removed. - (i64.store32 (type $array) + (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) - (i32.const 0) - (i64.const 1) + (then + (local.set $ref-imm + (struct.new $struct-imm + (i32.const 1) + ) + ) + ) + (else + (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 (type $1) (param $x i32) + ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; 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: (else + ;; 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) + (then + (block + (local.set $ref-imm + (struct.new $struct-imm + (i32.const 1) + ) + ) + (call $helper + (struct.get $struct-imm 0 + (local.get $ref-imm) + ) + ) + ) + ) + (else + (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 (type $1) (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 (type $1) (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 (field (mut i32)) (field i32))) + (type $struct (struct (mut i32) i32)) + + ;; CHECK: (func $propagate (type $1) + ;; 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 (type $2) (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 $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + ;; CHECK: (type $object (struct (field (ref $vtable)))) + (type $object (struct (ref $vtable))) + + ;; CHECK: (func $nested-creations (type $2) + ;; 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 (type $3) (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 $vtable (struct (field (mut funcref)))) + (type $vtable (struct (mut funcref))) + ;; CHECK: (type $object (struct (field (ref $vtable)))) + (type $object (struct (ref $vtable))) + + ;; CHECK: (func $nested-creations (type $2) + ;; 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 (type $3) (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 $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + ;; CHECK: (type $object (struct (field (mut (ref $vtable))))) + (type $object (struct (mut (ref $vtable)))) + + ;; CHECK: (func $nested-creations (type $2) + ;; 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 (type $3) (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 (field funcref))) + (type $vtable (struct funcref)) + ;; CHECK: (type $object (struct (field (ref $vtable)))) + (type $object (struct (ref $vtable))) + + ;; 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 (type $2) + ;; 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 (type $3) (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 (field funcref))) + (type $vtable (struct funcref)) + ;; CHECK: (type $object (struct (field (ref $vtable)))) + (type $object (struct (ref $vtable))) + + ;; 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 (type $2) + ;; 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 (type $3) (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 $vtable (array funcref)) + (type $vtable (array funcref)) + ;; CHECK: (type $object (struct (field (ref $vtable)))) + (type $object (struct (ref $vtable))) + + ;; CHECK: (global $vtable (ref $vtable) (array.new_fixed $vtable 1 + ;; CHECK-NEXT: (ref.func $nested-creations) + ;; CHECK-NEXT: )) + (global $vtable (ref $vtable) + (array.new_fixed $vtable 1 + (ref.func $nested-creations) + ) + ) + + ;; CHECK: (func $nested-creations (type $2) (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 (type $3) (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. + + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $itable (array structref)) + (type $itable (array (ref null struct))) + + ;; CHECK: (type $object (struct (field (ref $itable)))) + (type $object (struct (ref $itable))) + + ;; CHECK: (type $vtable-0 (struct (field funcref))) + (type $vtable-0 (struct funcref)) + + ;; CHECK: (type $vtable-1 (struct (field funcref))) + (type $vtable-1 (struct funcref)) + ) + + ;; CHECK: (global $itable (ref $itable) (array.new_fixed $itable 2 + ;; 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.new_fixed $itable 2 + (struct.new $vtable-0 + (ref.func $nested-creations) + ) + (struct.new $vtable-1 + (ref.func $helper) + ) + ) + ) + + ;; CHECK: (func $nested-creations (type $4) + ;; 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 (ref null $vtable-0) + (array.get $itable + (struct.get $object 0 + (local.get $ref) + ) + (i32.const 0) + ) + ) + ) + ) + (call $helper + (struct.get $vtable-1 0 + (ref.cast (ref null $vtable-1) + (array.get $itable + (struct.get $object 0 + (local.get $ref) + ) + (i32.const 1) + ) + ) + ) + ) + ) + + ;; CHECK: (func $helper (type $5) (param $0 funcref) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $helper (param funcref)) ) From 665d0d6380bfcdeeda5df61923f5a9f71035a680 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 23 Mar 2026 11:24:40 -0700 Subject: [PATCH 4/4] fix --- test/lit/passes/precompute-gc-multibyte.wast | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 test/lit/passes/precompute-gc-multibyte.wast diff --git a/test/lit/passes/precompute-gc-multibyte.wast b/test/lit/passes/precompute-gc-multibyte.wast new file mode 100644 index 00000000000..d45a284da59 --- /dev/null +++ b/test/lit/passes/precompute-gc-multibyte.wast @@ -0,0 +1,24 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: foreach %s %t wasm-opt --precompute-propagate -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $array (array (mut i8))) + (type $array (array (mut i8))) + + ;; CHECK: (func $array.store (type $1) (param $x (ref $array)) + ;; CHECK-NEXT: (i64.store32 (type $array) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $array.store (param $x (ref $array)) + ;; ArrayStore operations cannot be removed. + (i64.store32 (type $array) + (local.get $x) + (i32.const 0) + (i64.const 1) + ) + ) +)