From 4ffe0f757ddc4899d70af9ca4a4a9e8fc9f86e6f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Sep 2025 16:19:09 -0700 Subject: [PATCH 1/7] fix --- src/passes/ConstantFieldPropagation.cpp | 25 ++++++- test/lit/passes/cfp-reftest-desc.wast | 88 +++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 9838bf636ff..f42c645309c 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -219,7 +219,15 @@ struct FunctionOptimizer : public WalkerPass> { // ref.as_non_null (we need to trap as the get would have done so), plus the // constant value. (Leave it to further optimizations to get rid of the // ref.) - auto* value = makeExpression(info, heapType, curr); + optimizeSingleValue(info, heapType, curr, ref); + } + + void optimizeSingleValue(const PossibleConstantValues& info, + HeapType type, + Expression* curr, + Expression* ref) { + auto* value = makeExpression(info, type, curr); + Builder builder(*getModule()); auto* replacement = builder.blockify(builder.makeDrop(builder.makeRefAs(RefAsNonNull, ref))); replacement->list.push_back(value); @@ -282,6 +290,11 @@ struct FunctionOptimizer : public WalkerPass> { return; } + if (refType.isExact() && depth > 0) { + // We do not need to handle subtypes, and this is a subtype. + return; + } + auto iter = rawNewInfos.find(type); if (iter == rawNewInfos.end()) { // This type has no struct.news, so we can ignore it: it is abstract. @@ -331,8 +344,14 @@ struct FunctionOptimizer : public WalkerPass> { assert(values[0].used() || !values[1].used()); if (!values[1].used()) { - // We did not see two constant values (we might have seen just one, or - // even no constant values at all). + // We did not see two constant values, so this is a simple case that does + // not need a ref.test. + if (values[0].used()) { + // We found exactly one value. This can happen because we consider + // subtyping more carefully than the non-reftest logic (specifically, we + // notice exact types). Optimize to the single possible value. + optimizeSingleValue(values[0].constant, refHeapType, curr, ref); + } return; } diff --git a/test/lit/passes/cfp-reftest-desc.wast b/test/lit/passes/cfp-reftest-desc.wast index 11c649260a4..552f445c202 100644 --- a/test/lit/passes/cfp-reftest-desc.wast +++ b/test/lit/passes/cfp-reftest-desc.wast @@ -106,3 +106,91 @@ ) ) +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super.desc (struct)))) + (type $super (sub (descriptor $super.desc (struct)))) + ;; CHECK: (type $super.desc (sub (describes $super (struct (field (ref (exact $func))))))) + (type $super.desc (sub (describes $super (struct (field (ref (exact $func))))))) + + ;; CHECK: (type $func (func (param i32) (result i32))) + (type $func (func (param i32) (result i32))) + + ;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub (sub $super (descriptor $sub.desc (struct)))) + ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct (field (ref (exact $func))))))) + (type $sub.desc (sub $super.desc (describes $sub (struct (field (ref (exact $func))))))) + ) + + ;; CHECK: (type $5 (func (result (ref (exact $super.desc))))) + + ;; CHECK: (global $A (ref (exact $super.desc)) (struct.new $super.desc + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: )) + (global $A (ref (exact $super.desc)) (struct.new $super.desc + (ref.func $func) + )) + + ;; CHECK: (global $B (ref (exact $sub.desc)) (struct.new $sub.desc + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: )) + (global $B (ref (exact $sub.desc)) (struct.new $sub.desc + (ref.func $func) + )) + + ;; CHECK: (func $test (type $5) (result (ref (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $super + ;; CHECK-NEXT: (global.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_default $sub + ;; CHECK-NEXT: (global.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result (ref (exact $super.desc))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result (ref (exact $super.desc))) + (drop + (struct.new_default $super + (global.get $A) + ) + ) + (drop + (struct.new_default $sub + (global.get $B) + ) + ) + ;; We read from an exact $super here, so the type of the ref.get_desc is + ;; exact as well. If we ignore that in the optimization, we might think that + ;; the two struct.news before us are two possible values, one from $super and + ;; one from $sub, and if we emitted a ref.test between those values, we'd get + ;; a non-exact value that does not validate. + ;; + ;; Instead, we should look only at $super itself, and optimize to $A. + (ref.get_desc $super + (block (result (ref null (exact $super))) + (ref.null $super) + ) + ) + ) + + ;; CHECK: (func $func (type $func) (param $0 i32) (result i32) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $func (type $func) (param $0 i32) (result i32) + (i32.const 42) + ) +) + From eca60f93f300c9ca6b0bfdc2def90c6bc0c00c5f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Sep 2025 16:26:11 -0700 Subject: [PATCH 2/7] works on struct.get too, not just ref.get_desc --- test/lit/passes/cfp-reftest.wast | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/lit/passes/cfp-reftest.wast b/test/lit/passes/cfp-reftest.wast index 3f903e8b451..c3c9766b650 100644 --- a/test/lit/passes/cfp-reftest.wast +++ b/test/lit/passes/cfp-reftest.wast @@ -1456,3 +1456,58 @@ ) ) ) + +(module + ;; CHECK: (type $struct (sub (struct (field i32)))) + (type $struct (sub (struct i32))) + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64)))) + (type $substruct (sub $struct (struct i32 f64))) + + ;; CHECK: (type $3 (func (param (ref null (exact $struct))) (result i32))) + + ;; CHECK: (func $create (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $substruct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create + ;; Used below. + (drop + (struct.new $struct + (i32.const 10) + ) + ) + (drop + (struct.new $substruct + (i32.const 20) + (f64.const 3.14159) + ) + ) + ) + ;; CHECK: (func $get (type $3) (param $struct (ref null (exact $struct))) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + (func $get (param $struct (ref null (exact $struct))) (result i32) + ;; The type here is exact, so we do not even need to do a select: only the + ;; super's value is possible, 10. + (struct.get $struct 0 + (local.get $struct) + ) + ) +) + From 590f43e9d601d9b319c4a53e6cd5f91f07dc29cc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Sep 2025 16:39:38 -0700 Subject: [PATCH 3/7] remove func field --- test/lit/passes/cfp-reftest-desc.wast | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/test/lit/passes/cfp-reftest-desc.wast b/test/lit/passes/cfp-reftest-desc.wast index 552f445c202..8a5d9a3f66a 100644 --- a/test/lit/passes/cfp-reftest-desc.wast +++ b/test/lit/passes/cfp-reftest-desc.wast @@ -111,33 +111,25 @@ ;; CHECK: (rec ;; CHECK-NEXT: (type $super (sub (descriptor $super.desc (struct)))) (type $super (sub (descriptor $super.desc (struct)))) - ;; CHECK: (type $super.desc (sub (describes $super (struct (field (ref (exact $func))))))) - (type $super.desc (sub (describes $super (struct (field (ref (exact $func))))))) + ;; CHECK: (type $super.desc (sub (describes $super (struct)))) + (type $super.desc (sub (describes $super (struct)))) ;; CHECK: (type $func (func (param i32) (result i32))) (type $func (func (param i32) (result i32))) ;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct)))) (type $sub (sub $super (descriptor $sub.desc (struct)))) - ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct (field (ref (exact $func))))))) - (type $sub.desc (sub $super.desc (describes $sub (struct (field (ref (exact $func))))))) + ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) ) ;; CHECK: (type $5 (func (result (ref (exact $super.desc))))) - ;; CHECK: (global $A (ref (exact $super.desc)) (struct.new $super.desc - ;; CHECK-NEXT: (ref.func $func) - ;; CHECK-NEXT: )) - (global $A (ref (exact $super.desc)) (struct.new $super.desc - (ref.func $func) - )) - - ;; CHECK: (global $B (ref (exact $sub.desc)) (struct.new $sub.desc - ;; CHECK-NEXT: (ref.func $func) - ;; CHECK-NEXT: )) - (global $B (ref (exact $sub.desc)) (struct.new $sub.desc - (ref.func $func) - )) + ;; CHECK: (global $A (ref (exact $super.desc)) (struct.new_default $super.desc)) + (global $A (ref (exact $super.desc)) (struct.new $super.desc)) + + ;; CHECK: (global $B (ref (exact $sub.desc)) (struct.new_default $sub.desc)) + (global $B (ref (exact $sub.desc)) (struct.new $sub.desc)) ;; CHECK: (func $test (type $5) (result (ref (exact $super.desc))) ;; CHECK-NEXT: (drop From e42cbb0ea9b49cb92c490cca69ede9ca69323890 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 5 Sep 2025 20:22:49 -0700 Subject: [PATCH 4/7] use exactness more generally --- src/passes/ConstantFieldPropagation.cpp | 44 ++++++++----------------- test/lit/passes/cfp.wast | 17 ++++++---- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index f42c645309c..1947e5d4288 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -114,8 +114,11 @@ struct FunctionOptimizer : public WalkerPass> { return heapType; } - PossibleConstantValues getInfo(HeapType type, Index index) { - if (auto it = propagatedInfos.find(type); it != propagatedInfos.end()) { + PossibleConstantValues getInfo(HeapType type, Index index, Exactness exact) { + // If the reference is inexact, we must consider subtypes, who we + // propagated for that purpose. + auto& infos = exact == Inexact ? propagatedInfos : rawNewInfos; + if (auto it = infos.find(type); it != infos.end()) { // There is information on this type, fetch it. return it->second[index]; } @@ -177,7 +180,8 @@ struct FunctionOptimizer : public WalkerPass> { // Find the info for this field, and see if we can optimize. First, see if // there is any information for this heap type at all. If there isn't, it is // as if nothing was ever noted for that field. - PossibleConstantValues info = getInfo(heapType, index); + PossibleConstantValues info = + getInfo(heapType, index, ref->type.getExactness()); if (!info.hasNoted()) { // This field is never written at all. That means that we do not even // construct any data of this type, and so it is a logic error to reach @@ -219,15 +223,7 @@ struct FunctionOptimizer : public WalkerPass> { // ref.as_non_null (we need to trap as the get would have done so), plus the // constant value. (Leave it to further optimizations to get rid of the // ref.) - optimizeSingleValue(info, heapType, curr, ref); - } - - void optimizeSingleValue(const PossibleConstantValues& info, - HeapType type, - Expression* curr, - Expression* ref) { - auto* value = makeExpression(info, type, curr); - Builder builder(*getModule()); + auto* value = makeExpression(info, heapType, curr); auto* replacement = builder.blockify(builder.makeDrop(builder.makeRefAs(RefAsNonNull, ref))); replacement->list.push_back(value); @@ -290,11 +286,6 @@ struct FunctionOptimizer : public WalkerPass> { return; } - if (refType.isExact() && depth > 0) { - // We do not need to handle subtypes, and this is a subtype. - return; - } - auto iter = rawNewInfos.find(type); if (iter == rawNewInfos.end()) { // This type has no struct.news, so we can ignore it: it is abstract. @@ -344,14 +335,8 @@ struct FunctionOptimizer : public WalkerPass> { assert(values[0].used() || !values[1].used()); if (!values[1].used()) { - // We did not see two constant values, so this is a simple case that does - // not need a ref.test. - if (values[0].used()) { - // We found exactly one value. This can happen because we consider - // subtyping more carefully than the non-reftest logic (specifically, we - // notice exact types). Optimize to the single possible value. - optimizeSingleValue(values[0].constant, refHeapType, curr, ref); - } + // We did not see two constant values (we might have seen just one, or + // even no constant values at all). return; } @@ -518,12 +503,9 @@ struct ConstantFieldPropagation : public Pass { // Prepare data we will need later. SubTypes subTypes(*module); - PCVStructValuesMap rawNewInfos; - if (refTest) { - // The refTest optimizations require the raw new infos (see above), but we - // can skip copying here if we'll never read this. - rawNewInfos = combinedNewInfos; - } + // Copy the unpropagated data before we propagate. We use this in precise + // lookups. + auto rawNewInfos = combinedNewInfos; // Handle subtyping. |combinedInfo| so far contains data that represents // each struct.new and struct.set's operation on the struct type used in diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 56d1a832d42..e51efed6518 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2329,10 +2329,10 @@ ;; CHECK: (func $test (type $2) (param $0 i32) (result i32) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (struct.set $A 0 - ;; CHECK-NEXT: (select (result (ref null $A)) + ;; CHECK-NEXT: (struct.set $B 0 + ;; CHECK-NEXT: (select (result (ref null $B)) ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (block (result (ref null $A)) + ;; CHECK-NEXT: (block (result (ref $B)) ;; CHECK-NEXT: (local.tee $B ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 20) @@ -2341,10 +2341,15 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.get $B 0 From c8ad97174c2e355338fb8c7b3b4988fce3a1e194 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 6 Sep 2025 10:44:06 -0700 Subject: [PATCH 5/7] Avoid doing more in existing test --- test/lit/passes/cfp.wast | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index e51efed6518..a96af6ec5bd 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2329,10 +2329,10 @@ ;; CHECK: (func $test (type $2) (param $0 i32) (result i32) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) - ;; CHECK-NEXT: (struct.set $B 0 - ;; CHECK-NEXT: (select (result (ref null $B)) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (select (result (ref null $A)) ;; CHECK-NEXT: (ref.null none) - ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (block (result (ref null $A)) ;; CHECK-NEXT: (local.tee $B ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 20) @@ -2341,15 +2341,12 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result i32) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (block (result (ref null $A)) + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.get $B 0 @@ -2379,8 +2376,12 @@ (i32.const 0) ) (struct.get $A 0 - (struct.new $A - (i32.const 10) + ;; This block avoids us having an exact reference, which would let us + ;; optimize this get. + (block (result (ref null $A)) + (struct.new $A + (i32.const 10) + ) ) ) ) From 255d0f9bc833f4c09fef23b44cf63dae4fa0d4c2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 6 Sep 2025 10:48:22 -0700 Subject: [PATCH 6/7] show exact opts right after that one --- test/lit/passes/cfp.wast | 110 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index a96af6ec5bd..361fda953f6 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2395,6 +2395,116 @@ ) ) +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) + (type $A (sub (struct (field (mut i32))))) + ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) + (type $B (sub $A (struct (field (mut i32))))) + ) + + ;; CHECK: (type $2 (func (param i32))) + + ;; CHECK: (func $test (type $2) (param $0 i32) + ;; CHECK-NEXT: (local $A (ref $A)) + ;; CHECK-NEXT: (local $B (ref $B)) + ;; CHECK-NEXT: (local $A-exact (ref (exact $A))) + ;; CHECK-NEXT: (local $B-exact (ref (exact $B))) + ;; CHECK-NEXT: (local.set $A + ;; CHECK-NEXT: (local.tee $A-exact + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $B + ;; CHECK-NEXT: (local.tee $B-exact + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $A-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $B-exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $0 i32) + (local $A (ref $A)) + (local $B (ref $B)) + (local $A-exact (ref (exact $A))) + (local $B-exact (ref (exact $B))) + (local.set $A + (local.tee $A-exact + (struct.new $A + (i32.const 10) + ) + ) + ) + (local.set $B + (local.tee $B-exact + (struct.new $B + (i32.const 20) + ) + ) + ) + ;; We can optimize an inexact $B, but not $A. + (drop + (struct.get $A 0 + (local.get $A) + ) + ) + (drop + (struct.get $B 0 + (local.get $B) + ) + ) + ;; We can optimize both exact references. + (drop + (struct.get $A 0 + (local.get $A-exact) + ) + ) + (drop + (struct.get $B 0 + (local.get $B-exact) + ) + ) + ) +) + ;; A type with two subtypes. A copy on the parent can affect either child. (module (rec From 1b0d4d409c0b84126f5d5d1c06ddd0601af8d58b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 8 Sep 2025 08:24:22 -0700 Subject: [PATCH 7/7] fix --- src/passes/ConstantFieldPropagation.cpp | 10 ++-- test/lit/passes/cfp.wast | 66 +++++++++++++++++++------ 2 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 1947e5d4288..11775cf32e0 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -115,9 +115,13 @@ struct FunctionOptimizer : public WalkerPass> { } PossibleConstantValues getInfo(HeapType type, Index index, Exactness exact) { - // If the reference is inexact, we must consider subtypes, who we - // propagated for that purpose. - auto& infos = exact == Inexact ? propagatedInfos : rawNewInfos; + // If the reference is exact and the field immutable, then we are reading + // exactly what was written to struct.news and nothing else. + auto mutable_ = index == StructUtils::DescriptorIndex + ? Immutable + : GCTypeUtils::getField(type, index)->mutable_; + auto& infos = + (exact == Inexact || mutable_ == Mutable) ? propagatedInfos : rawNewInfos; if (auto it = infos.find(type); it != infos.end()) { // There is information on this type, fetch it. return it->second[index]; diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 361fda953f6..324178d331d 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2342,10 +2342,8 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (block (result (ref null $A)) - ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (i32.const 10) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -2376,12 +2374,8 @@ (i32.const 0) ) (struct.get $A 0 - ;; This block avoids us having an exact reference, which would let us - ;; optimize this get. - (block (result (ref null $A)) - (struct.new $A - (i32.const 10) - ) + (struct.new $A + (i32.const 10) ) ) ) @@ -2398,10 +2392,10 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) - (type $A (sub (struct (field (mut i32))))) - ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) - (type $B (sub $A (struct (field (mut i32))))) + ;; CHECK-NEXT: (type $A (sub (struct (field i32)))) + (type $A (sub (struct (field i32)))) + ;; CHECK: (type $B (sub $A (struct (field i32)))) + (type $B (sub $A (struct (field i32)))) ) ;; CHECK: (type $2 (func (param i32))) @@ -3063,3 +3057,47 @@ ) ) ) + +(module + ;; CHECK: (type $A (struct (field (mut i32)))) + (type $A (struct (field (mut i32)))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (local $A.exact (ref (exact $A))) + ;; CHECK-NEXT: (local.set $A.exact + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (local.get $A.exact) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $A.exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (local $A.exact (ref (exact $A))) + (local.set $A.exact + (struct.new $A + (i32.const 10) + ) + ) + ;; This set prevents us from optimizing. We should not be confused by the + ;; exactness of the ref. + (struct.set $A 0 + (local.get $A.exact) + (i32.const 20) + ) + (drop + (struct.get $A 0 + (local.get $A.exact) + ) + ) + ) +)