summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/struct-utils.h9
-rw-r--r--src/passes/ConstantFieldPropagation.cpp21
-rw-r--r--test/lit/passes/cfp.wast324
3 files changed, 345 insertions, 9 deletions
diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h
index 667c9ed55..4e24aacf4 100644
--- a/src/ir/struct-utils.h
+++ b/src/ir/struct-utils.h
@@ -119,7 +119,9 @@ struct FunctionStructValuesMap
// void noteDefault(Type fieldType, HeapType type, Index index, T& info);
//
// * Note a copied value (read from this field and written to the same, possibly
-// in another object).
+// in another object). Note that we require that the two types (the one read
+// from, and written to) are identical; allowing subtyping is possible, but
+// would add complexity amid diminishing returns.
//
// void noteCopy(HeapType type, Index index, T& info);
//
@@ -232,8 +234,13 @@ struct StructScanner
// if we changed something.
template<typename T> class TypeHierarchyPropagator {
public:
+ // Constructor that gets a module and computes subtypes.
TypeHierarchyPropagator(Module& wasm) : subTypes(wasm) {}
+ // Constructor that gets subtypes and uses them, avoiding a scan of a
+ // module. TODO: avoid a copy here?
+ TypeHierarchyPropagator(const SubTypes& subTypes) : subTypes(subTypes) {}
+
SubTypes subTypes;
void propagateToSuperTypes(StructValuesMap<T>& infos) {
diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp
index 61e06ea35..2b7dae147 100644
--- a/src/passes/ConstantFieldPropagation.cpp
+++ b/src/passes/ConstantFieldPropagation.cpp
@@ -56,7 +56,7 @@ struct Bool {
operator bool() const { return value; }
- void combine(bool other) { value = value || other; }
+ bool combine(bool other) { return value = value || other; }
};
using BoolStructValuesMap = StructUtils::StructValuesMap<Bool>;
@@ -185,10 +185,6 @@ struct PCVScanner
// Note copies, as they must be considered later. See the comment on the
// propagation of values below.
functionCopyInfos[getFunction()][type][index] = true;
-
- // TODO: This may be extensible to a copy from a subtype, and not just the
- // exact type (but this is already entering the realm of diminishing
- // returns).
}
void noteRead(HeapType type, Index index, PossibleConstantValues& info) {
@@ -223,6 +219,8 @@ struct ConstantFieldPropagation : public Pass {
BoolStructValuesMap combinedCopyInfos;
functionCopyInfos.combineInto(combinedCopyInfos);
+ SubTypes subTypes(*module);
+
// Handle subtyping. |combinedInfo| so far contains data that represents
// each struct.new and struct.set's operation on the struct type used in
// that instruction. That is, if we do a struct.set to type T, the value was
@@ -264,10 +262,17 @@ struct ConstantFieldPropagation : public Pass {
// ..
// A1->f0 = A2->f0; // Either of these might refer to an A, B, or C.
// ..
- // foo(C->f0); // This can contain 20, if the copy involved a C.
+ // foo(A->f0); // These can contain 20,
+ // foo(C->f0); // if the copy read from B.
//
// To handle that, copied fields are treated like struct.set ones (by
- // copying the struct.new data to struct.set).
+ // copying the struct.new data to struct.set). Note that we must propagate
+ // copying to subtypes first, as in the example above the struct.new values
+ // of subtypes must be taken into account (that is, A or a subtype is being
+ // copied, so we want to do the same thing for B and C as well as A, since
+ // a copy of A means it could be a copy of B or C).
+ StructUtils::TypeHierarchyPropagator<Bool> boolPropagator(subTypes);
+ boolPropagator.propagateToSubTypes(combinedCopyInfos);
for (auto& [type, copied] : combinedCopyInfos) {
for (Index i = 0; i < copied.size(); i++) {
if (copied[i]) {
@@ -277,7 +282,7 @@ struct ConstantFieldPropagation : public Pass {
}
StructUtils::TypeHierarchyPropagator<PossibleConstantValues> propagator(
- *module);
+ subTypes);
propagator.propagateToSuperTypes(combinedNewInfos);
propagator.propagateToSuperAndSubTypes(combinedSetInfos);
diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast
index e3bd4b049..38e17c578 100644
--- a/test/lit/passes/cfp.wast
+++ b/test/lit/passes/cfp.wast
@@ -2292,3 +2292,327 @@
)
)
)
+
+;; A type with two subtypes. A copy on the parent can affect either child.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32)))))
+ (type $A (sub (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B1 (sub $A (struct (field (mut i32)))))
+ (type $B1 (sub $A (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B2 (sub $A (struct (field (mut i32)))))
+ (type $B2 (sub $A (struct (field (mut i32)))))
+ )
+
+ ;; CHECK: (type $3 (func (param (ref null $A) (ref null $B1) (ref null $B2))))
+
+ ;; CHECK: (func $test (type $3) (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B1
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B2
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $A 0
+ ;; CHECK-NEXT: (local.get $A)
+ ;; CHECK-NEXT: (struct.get $A 0
+ ;; CHECK-NEXT: (local.get $A)
+ ;; 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: (struct.get $B1 0
+ ;; CHECK-NEXT: (local.get $B1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B2 0
+ ;; CHECK-NEXT: (local.get $B2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ ;; A and B1 agree on their value in their construction.
+ (drop
+ (struct.new $A
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B1
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B2
+ (i32.const 20) ;; this value is different from the others
+ )
+ )
+
+ ;; Copy on $A
+ (struct.set $A 0
+ (local.get $A)
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+
+ ;; $A might read either child, so we can't infer.
+ (drop
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+ ;; $B1 should be only able to read 10, but the copy opens the possibility
+ ;; of 20, so we can't optimize.
+ (drop
+ (struct.get $B1 0
+ (local.get $B1)
+ )
+ )
+ ;; As with $B1, the copy stops us.
+ (drop
+ (struct.get $B2 0
+ (local.get $B2)
+ )
+ )
+ )
+)
+
+;; As above, but now the copy is on B1.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32)))))
+ (type $A (sub (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B1 (sub $A (struct (field (mut i32)))))
+ (type $B1 (sub $A (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B2 (sub $A (struct (field (mut i32)))))
+ (type $B2 (sub $A (struct (field (mut i32)))))
+ )
+
+ ;; CHECK: (type $3 (func (param (ref null $A) (ref null $B1) (ref null $B2))))
+
+ ;; CHECK: (func $test (type $3) (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B1
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B2
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $B1 0
+ ;; CHECK-NEXT: (local.get $B1)
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; 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 $B1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B2 0
+ ;; CHECK-NEXT: (local.get $B2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ (drop
+ (struct.new $A
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B1
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B2
+ (i32.const 20)
+ )
+ )
+
+ ;; This changed from $A to $B1.
+ (struct.set $B1 0
+ (local.get $B1)
+ (struct.get $B1 0
+ (local.get $B1)
+ )
+ )
+
+ ;; This might still read $B1 or $B2, with different values, so we can't
+ ;; optimize.
+ (drop
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+ ;; The copy can only refer to $B1, so we can optimize here.
+ (drop
+ (struct.get $B1 0
+ (local.get $B1)
+ )
+ )
+ ;; The copy can't refer to a $B2, so we can optimize here. TODO
+ (drop
+ (struct.get $B2 0
+ (local.get $B2)
+ )
+ )
+ )
+)
+
+;; As above, but now the copy is on B2.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32)))))
+ (type $A (sub (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B1 (sub $A (struct (field (mut i32)))))
+ (type $B1 (sub $A (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B2 (sub $A (struct (field (mut i32)))))
+ (type $B2 (sub $A (struct (field (mut i32)))))
+ )
+
+ ;; CHECK: (type $3 (func (param (ref null $A) (ref null $B1) (ref null $B2))))
+
+ ;; CHECK: (func $test (type $3) (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B1
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B2
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $B2 0
+ ;; CHECK-NEXT: (local.get $B2)
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; 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: (struct.get $B1 0
+ ;; CHECK-NEXT: (local.get $B1)
+ ;; 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 $B2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ (drop
+ (struct.new $A
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B1
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B2
+ (i32.const 20)
+ )
+ )
+
+ ;; This changed from $A to $B2.
+ (struct.set $B2 0
+ (local.get $B2)
+ (struct.get $B2 0
+ (local.get $B2)
+ )
+ )
+
+ ;; This might still read $B1 or $B2, with different values, so we can't
+ ;; optimize.
+ (drop
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+ ;; The copy can't refer to a $B1, so we can optimize here. TODO
+ (drop
+ (struct.get $B1 0
+ (local.get $B1)
+ )
+ )
+ ;; $B2 is copied to itself, and nothing else, so we can optimize here.
+ (drop
+ (struct.get $B2 0
+ (local.get $B2)
+ )
+ )
+ )
+)