diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 9838bf636ff..11775cf32e0 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -114,8 +114,15 @@ 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 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]; } @@ -177,7 +184,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 @@ -499,12 +507,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-reftest-desc.wast b/test/lit/passes/cfp-reftest-desc.wast index 11c649260a4..8a5d9a3f66a 100644 --- a/test/lit/passes/cfp-reftest-desc.wast +++ b/test/lit/passes/cfp-reftest-desc.wast @@ -106,3 +106,83 @@ ) ) +(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)))) + (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)))) + (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_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 + ;; 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) + ) +) + 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) + ) + ) +) + diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 56d1a832d42..324178d331d 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -2389,6 +2389,116 @@ ) ) +(module + (rec + ;; CHECK: (rec + ;; 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))) + + ;; 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 @@ -2947,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) + ) + ) + ) +)