summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/TypeRefining.cpp28
-rw-r--r--test/lit/passes/type-refining.wast49
2 files changed, 76 insertions, 1 deletions
diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp
index 0938f6d8e..87366e148 100644
--- a/src/passes/TypeRefining.cpp
+++ b/src/passes/TypeRefining.cpp
@@ -256,7 +256,33 @@ struct TypeRefining : public Pass {
auto oldType = curr->ref->type.getHeapType();
auto newFieldType = parent.finalInfos[oldType][curr->index].getLUB();
- if (!Type::isSubType(newFieldType, curr->type)) {
+ if (Type::isSubType(newFieldType, curr->type)) {
+ // This is the normal situation, where the new type is a refinement of
+ // the old type. Apply that type so that the type of the struct.get
+ // matches what is in the refined field. ReFinalize will later
+ // propagate this to parents.
+ //
+ // Note that ReFinalize will also apply the type of the field itself
+ // to a struct.get, so our doing it here in this pass is usually
+ // redundant. But ReFinalize also updates other types while doing so,
+ // which can cause a problem:
+ //
+ // (struct.get $A
+ // (block (result (ref null $A))
+ // (ref.null any)
+ // )
+ // )
+ //
+ // Here ReFinalize will turn the block's result into a bottom type,
+ // which means it won't know a type for the struct.get at that point.
+ // Doing it in this pass avoids that issue, as we have all the
+ // necessary information. (ReFinalize will still get into the
+ // situation where it doesn't know how to update the type of the
+ // struct.get, but it will just leave the existing type - it assumes
+ // no update is needed - which will be correct, since we've updated it
+ // ourselves here, before.)
+ curr->type = newFieldType;
+ } else {
// This instruction is invalid, so it must be the result of the
// situation described above: we ignored the read during our
// inference, and optimized accordingly, and so now we must remove it
diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast
index 5356d7a0f..870e3d7cb 100644
--- a/test/lit/passes/type-refining.wast
+++ b/test/lit/passes/type-refining.wast
@@ -1044,3 +1044,52 @@
)
)
)
+
+(module
+ ;; CHECK: (type $struct (struct (field (mut (ref $struct)))))
+ (type $struct (struct (field (mut (ref null struct)))))
+
+ ;; CHECK: (type $ref|$struct|_=>_none (func (param (ref $struct))))
+
+ ;; CHECK: (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct))
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result nullref)
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $work (param $struct (ref $struct))
+ ;; The only set to the field is (ref $struct), so we can refine to that.
+ (struct.set $struct 0
+ (local.get $struct)
+ (local.get $struct)
+ )
+ ;; After refining, we must update the get's type properly. ReFinalize by
+ ;; itself would hit a problem here, as it first turns the block's result to
+ ;; a bottom type, after which it can't figure out how to update the
+ ;; struct.get. TypeRefining should handle that internally by updating all
+ ;; struct.gets itself based on the changes it is making.
+ ;;
+ ;; Note that this problem depends on the recursion of a struct.get feeding
+ ;; into a struct.set: after the refining, the struct.set will only
+ ;; validate if we provide it the *refined* type for the field.
+ (struct.set $struct 0
+ (local.get $struct)
+ (struct.get $struct 0
+ (block (result (ref null $struct))
+ (ref.null none)
+ )
+ )
+ )
+ )
+)