summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/ConstantFieldPropagation.cpp21
-rw-r--r--test/lit/passes/cfp.wast116
2 files changed, 136 insertions, 1 deletions
diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp
index 26cc8316b..a3dd6aa6f 100644
--- a/src/passes/ConstantFieldPropagation.cpp
+++ b/src/passes/ConstantFieldPropagation.cpp
@@ -179,7 +179,26 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
auto* value = info.makeExpression(*getModule());
auto field = GCTypeUtils::getField(type, curr->index);
assert(field);
- return Bits::makePackedFieldGet(value, *field, curr->signed_, *getModule());
+ // Apply packing, if needed.
+ value =
+ Bits::makePackedFieldGet(value, *field, curr->signed_, *getModule());
+ // Check if the value makes sense. The analysis below flows values around
+ // without considering where they are placed, that is, when we see a parent
+ // type can contain a value in a field then we assume a child may as well
+ // (which in general it can, e.g., using a reference to the parent, we can
+ // write that value to it, but the reference might actually point to a
+ // child instance). If we tracked the types of fields then we might avoid
+ // flowing values into places they cannot reside, like when a child field is
+ // a subtype, and so we could ignore things not refined enough for it (GUFA
+ // does a better job at this). For here, just check we do not break
+ // validation, and if we do, then we've inferred the only possible value is
+ // an impossible one, making the code unreachable.
+ if (!Type::isSubType(value->type, field->type)) {
+ Builder builder(*getModule());
+ value = builder.makeSequence(builder.makeDrop(value),
+ builder.makeUnreachable());
+ }
+ return value;
}
void optimizeUsingRefTest(StructGet* curr) {
diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast
index d386e7f34..461baa373 100644
--- a/test/lit/passes/cfp.wast
+++ b/test/lit/passes/cfp.wast
@@ -2710,3 +2710,119 @@
)
)
)
+
+;; $C is created with two values for its field: a global.get, and a copy from
+;; another $C, which does not expand the set of possible values. We should not
+;; get confused about $B, its sibling, which is never created, and whose field
+;; has an incompatible type.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $X (sub (struct)))
+ (type $X (sub (struct)))
+ ;; CHECK: (type $Y (sub final $X (struct)))
+ (type $Y (sub final $X (struct)))
+ ;; CHECK: (type $Z (sub final $X (struct)))
+ (type $Z (sub final $X (struct)))
+
+ ;; CHECK: (type $A (sub (struct (field (ref null $X)))))
+ (type $A (sub (struct (field (ref null $X)))))
+ ;; CHECK: (type $B (sub final $A (struct (field (ref null $Y)))))
+ (type $B (sub final $A (struct (field (ref null $Y)))))
+ ;; CHECK: (type $C (sub final $A (struct (field (ref null $Z)))))
+ (type $C (sub final $A (struct (field (ref null $Z)))))
+ )
+
+ ;; CHECK: (type $6 (func))
+
+ ;; CHECK: (type $7 (func (param (ref null $C))))
+
+ ;; CHECK: (type $8 (func (param (ref null $A)) (result (ref null $X))))
+
+ ;; CHECK: (type $9 (func (param (ref null $B)) (result (ref null $Y))))
+
+ ;; CHECK: (global $global (ref null $Z) (struct.new_default $Z))
+ (global $global (ref null $Z) (struct.new_default $Z))
+
+ ;; CHECK: (func $new (type $6)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $C
+ ;; CHECK-NEXT: (global.get $global)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $new
+ (drop
+ (struct.new $C
+ (global.get $global)
+ )
+ )
+ )
+
+ ;; CHECK: (func $copy (type $7) (param $C (ref null $C))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $C
+ ;; CHECK-NEXT: (block (result (ref null $Z))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $C)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $copy (param $C (ref null $C))
+ ;; The struct.get here can be optimized to a global.get.
+ (drop
+ (struct.new $C
+ (struct.get $C 0
+ (local.get $C)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $get-A (type $8) (param $A (ref null $A)) (result (ref null $X))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global)
+ ;; CHECK-NEXT: )
+ (func $get-A (param $A (ref null $A)) (result (ref null $X))
+ ;; The struct.get here can be optimized to a global.get. While we never
+ ;; create an $A, a $C might be referred to by an $A reference, and the
+ ;; global.get is the only possible value.
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+
+ ;; CHECK: (func $get-B (type $9) (param $B (ref null $B)) (result (ref null $Y))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $global)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-B (param $B (ref null $B)) (result (ref null $Y))
+ ;; This should not be optimized to a global.get: no $B is created, and we
+ ;; cannot refer to anything that is actually created. If we mistakenly
+ ;; thought this field can contain the global.get (as we do for the parent
+ ;; $A) then we would error here: $B's field contains $Y, but the global is
+ ;; is of a sibling type $Z. Instead, we can add an unreachable here, as no
+ ;; valid value is possible.
+ (struct.get $B 0
+ (local.get $B)
+ )
+ )
+)