diff options
-rw-r--r-- | src/passes/TypeRefining.cpp | 24 | ||||
-rw-r--r-- | test/lit/passes/type-refining.wast | 55 |
2 files changed, 78 insertions, 1 deletions
diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index 87366e148..7fd039411 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -250,7 +250,29 @@ struct TypeRefining : public Pass { } void visitStructGet(StructGet* curr) { - if (curr->ref->type == Type::unreachable || curr->ref->type.isNull()) { + if (curr->ref->type == Type::unreachable) { + return; + } + + if (curr->ref->type.isNull()) { + // This get will trap. In theory we could leave this for later + // optimizations to do, but we must actually handle it here, because + // of the situation where this get's type is refined, and the input + // type is the result of a refining: + // + // (struct.get $A ;; should be refined to something + // (struct.get $B ;; just refined to nullref + // + // If the input has become a nullref then we can't just return out of + // this function, as we'd be leaving a struct.get of $A with the + // wrong type. But we can't find the right type since in Binaryen IR + // we use the ref's type to see what is being read, and that just + // turned into nullref. To avoid that corner case, just turn this code + // into unreachable code now, and the later refinalize will turn all + // the parents unreachable, avoiding any type-checking problems. + Builder builder(*getModule()); + replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), + builder.makeUnreachable())); return; } diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 8258a3a54..824ad43a8 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1167,3 +1167,58 @@ ) ) ) + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (struct (field (mut nullref)))) + (type $A (struct (field (mut anyref)))) + ;; CHECK: (type $B (struct (field (mut nullref)))) + (type $B (struct (field (mut (ref null $A))))) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (func $0 (type $none_=>_none) + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (struct.new_default $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $0 + (struct.set $A 0 + (struct.new $A + ;; These two struct.gets will both be refined. That of $B will be + ;; refined to a get of a null, at which point the get of $A could get + ;; confused and not know what type it is reading from (since in the IR, + ;; we depend on the ref for that). The pass should not error here while + ;; it refines both the struct type's fields to nullref, after which the + ;; code here will be unreachable (since we do a struct.get from a + ;; nullref). + (struct.get $A 0 + (struct.get $B 0 + (struct.new_default $B) + ) + ) + ) + (ref.null none) + ) + ) +) |