Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/passes/Heap2Local.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,13 @@ struct Struct2Local : PostWalker<Struct2Local> {
return;
}

if (curr->type == Type::unreachable) {
// We must not modify unreachable code here, as we will replace it with a
// const, which has a concrete type (similar to the situation with
// local.get in other cases in this pass).
return;
}

// This test operates on the allocation, which means we can compute whether
// it will succeed statically. We do not even need
// GCTypeUtils::evaluateCastCheck because we know the allocation's type
Expand Down
68 changes: 68 additions & 0 deletions test/lit/passes/heap2local-desc.wast
Original file line number Diff line number Diff line change
Expand Up @@ -1360,3 +1360,71 @@
)
)
)

(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $struct (descriptor $desc) (struct))
(type $struct (descriptor $desc) (struct))
;; CHECK: (type $desc (sub (describes $struct) (struct)))
(type $desc (sub (describes $struct) (struct)))
)

;; CHECK: (type $2 (func))

;; CHECK: (func $test (type $2)
;; CHECK-NEXT: (local $temp (ref $desc))
;; CHECK-NEXT: (local $1 (ref none))
;; CHECK-NEXT: (local $2 (ref none))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.test (ref none)
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (local.set $2
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $1
;; CHECK-NEXT: (local.get $2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(local $temp (ref $desc))
(local.set $temp
(struct.new_default $desc)
)
;; The ref.test's input will become unreachable after we optimize. We should
;; not emit a const for the test result, even though we know it, as this is
;; unreachable code which would not validate.
Comment on lines +1413 to +1415
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you be more specific about where the validation failure would be? It looks like the constant would flow into the drop, but that would be perfectly valid.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the usual issue with local.get, like we do in a few other places,

if (curr->type == Type::unreachable) {
// We must not modify unreachable code here, as we will replace it with a
// local.get, which has a concrete type (another option could be to run
// DCE and not only ReFinalize - DCE will propagate an unreachable out of
// a concrete block, like we emit here - but we can just ignore such
// code).
return;
}

If we don't skip this code, we emit a block with an unreachable child followed by a concrete one (here, a const). So yes, there is a drop of the block that ends in the constant, but the problem is that the block has the wrong type.

(drop
(ref.test (ref none)
(ref.cast_desc_eq (ref $struct)
(struct.new_default_desc $struct
(ref.as_non_null
(ref.null none)
)
)
(local.get $temp)
)
)
)
)
)

Loading