diff options
-rw-r--r-- | src/passes/TypeRefining.cpp | 28 | ||||
-rw-r--r-- | test/lit/passes/type-refining.wast | 49 |
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) + ) + ) + ) + ) +) |