diff options
-rw-r--r-- | src/passes/ConstantFieldPropagation.cpp | 21 | ||||
-rw-r--r-- | test/lit/passes/cfp.wast | 116 |
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) + ) + ) +) |