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
25 changes: 19 additions & 6 deletions src/passes/AbstractTypeRefining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,19 +395,32 @@ struct AbstractTypeRefining : public Pass {
}

// If we optimize away a descriptor type, then we must fix up any
// ref.get_desc of it, as ReFinalize would fix us up to return it. This
// can only happen when no such descriptor is created, which means the
// instruction will never be reached (no struct with such a descriptor was
// created).
// ref.get_desc of it, as ReFinalize would fix us up to return it.
void visitRefGetDesc(RefGetDesc* curr) {
auto optimized = getOptimized(curr->type);
if (!optimized) {
return;
}

// We are optimizing the descriptor to a subtype.
auto subDescriptor = *optimized;

Builder builder(*getModule());
replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
builder.makeUnreachable()));
if (curr->type.isExact() || subDescriptor == HeapTypes::none) {
// This is exact, so we can ignore subtypes, or there is no subtype to
// optimize to. In this case it must trap.
replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
builder.makeUnreachable()));
} else {
// The descriptor is abstract, but it has a subtype that is not
// (which we want to optimize to). We know this will only succeed if
// we receive the subtype of the described type, so that that
// descriptor is fetched. Add a cast so that we validate.
Copy link
Member

Choose a reason for hiding this comment

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

As a follow-up, we can get rid of this cast (and if I'm not mistaken, this entire visitor) by inferring that a described type must not be instantiated if its descriptor type is uninstantiated.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was thinking that, yeah, we can infer abstractness from descriptors to described...

Copy link
Member Author

Choose a reason for hiding this comment

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

I suspect though that other optimizations handle this (if the descriptor is not instantiated, eventually we infer all creations of the described receive null descriptors and hence trap, get removed, and the type becomes abstract).

Copy link
Member

Choose a reason for hiding this comment

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

Yep, but I think cutting down on the iterations required to reach a good state is still useful, especially when it can make our code simpler as well.

auto subDescribed = subDescriptor.getDescribedType();
assert(subDescribed);
curr->ref =
builder.makeRefCast(curr->ref, curr->ref->type.with(*subDescribed));
}
}

void visitBrOn(BrOn* curr) {
Expand Down
124 changes: 124 additions & 0 deletions test/lit/passes/abstract-type-refining-desc.wast
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,8 @@

;; YESTNH: (type $2 (func (result (ref none))))

;; YESTNH: (type $3 (func (param (ref $A)) (result (ref none))))

;; YESTNH: (func $test (type $2) (result (ref none))
;; YESTNH-NEXT: (drop
;; YESTNH-NEXT: (struct.new_default $A
Expand All @@ -1048,6 +1050,8 @@
;; YESTNH-NEXT: )
;; NO_TNH: (type $2 (func (result (ref none))))

;; NO_TNH: (type $3 (func (param (ref $A)) (result (ref none))))

;; NO_TNH: (func $test (type $2) (result (ref none))
;; NO_TNH-NEXT: (drop
;; NO_TNH-NEXT: (struct.new_default $A
Expand All @@ -1063,5 +1067,125 @@
)
)
)

;; YESTNH: (func $inexact (type $3) (param $inexact (ref $A)) (result (ref none))
;; YESTNH-NEXT: (drop
;; YESTNH-NEXT: (local.get $inexact)
;; YESTNH-NEXT: )
;; YESTNH-NEXT: (unreachable)
;; YESTNH-NEXT: )
;; NO_TNH: (func $inexact (type $3) (param $inexact (ref $A)) (result (ref none))
;; NO_TNH-NEXT: (drop
;; NO_TNH-NEXT: (local.get $inexact)
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: (unreachable)
;; NO_TNH-NEXT: )
(func $inexact (param $inexact (ref $A)) (result (ref $B))
;; As above, but now the input type is inexact. There are no subtypes, so
;; we still optimize.
(ref.get_desc $A
(local.get $inexact)
)
)
)

(module
;; As above, but now there are subtypes $A.sub, $B.sub.
(rec
;; YESTNH: (rec
;; YESTNH-NEXT: (type $A (sub (descriptor $B (struct))))
;; NO_TNH: (rec
;; NO_TNH-NEXT: (type $A (sub (descriptor $B (struct))))
(type $A (sub (descriptor $B (struct))))
;; YESTNH: (type $B (sub (describes $A (struct))))
;; NO_TNH: (type $B (sub (describes $A (struct))))
(type $B (sub (describes $A (struct))))

;; YESTNH: (type $A.sub (sub $A (descriptor $B.sub (struct))))
;; NO_TNH: (type $A.sub (sub $A (descriptor $B.sub (struct))))
(type $A.sub (sub $A (descriptor $B.sub (struct))))
;; YESTNH: (type $B.sub (sub $B (describes $A.sub (struct))))
;; NO_TNH: (type $B.sub (sub $B (describes $A.sub (struct))))
(type $B.sub (sub $B (describes $A.sub (struct))))
)

;; YESTNH: (type $4 (func))

;; YESTNH: (type $5 (func (result (ref (exact $B.sub)))))

;; YESTNH: (type $6 (func (param (ref $A)) (result (ref $B.sub))))

;; YESTNH: (func $create (type $4)
;; YESTNH-NEXT: (drop
;; YESTNH-NEXT: (struct.new_default $A.sub
;; YESTNH-NEXT: (struct.new_default $B.sub)
;; YESTNH-NEXT: )
;; YESTNH-NEXT: )
;; YESTNH-NEXT: )
;; NO_TNH: (type $4 (func))

;; NO_TNH: (type $5 (func (result (ref (exact $B)))))

;; NO_TNH: (type $6 (func (param (ref $A)) (result (ref $B))))

;; NO_TNH: (func $create (type $4)
;; NO_TNH-NEXT: (drop
;; NO_TNH-NEXT: (struct.new_default $A.sub
;; NO_TNH-NEXT: (struct.new_default $B.sub)
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: )
(func $create
;; Make the subtypes not abstract.
(drop
(struct.new_default $A.sub
(struct.new_default $B.sub)
)
)
)

;; YESTNH: (func $test (type $5) (result (ref (exact $B.sub)))
;; YESTNH-NEXT: (drop
;; YESTNH-NEXT: (struct.new_default $A
;; YESTNH-NEXT: (ref.null none)
;; YESTNH-NEXT: )
;; YESTNH-NEXT: )
;; YESTNH-NEXT: (unreachable)
;; YESTNH-NEXT: )
;; NO_TNH: (func $test (type $5) (result (ref (exact $B)))
;; NO_TNH-NEXT: (ref.get_desc $A
;; NO_TNH-NEXT: (struct.new_default $A
;; NO_TNH-NEXT: (ref.null none)
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: )
(func $test (result (ref (exact $B)))
;; We can still optimize here, thanks to exactness (when TNH).
(ref.get_desc $A
(struct.new_default $A
(ref.null none)
)
)
)

;; YESTNH: (func $inexact (type $6) (param $inexact (ref $A)) (result (ref $B.sub))
;; YESTNH-NEXT: (ref.get_desc $A.sub
;; YESTNH-NEXT: (ref.cast (ref $A.sub)
;; YESTNH-NEXT: (local.get $inexact)
;; YESTNH-NEXT: )
;; YESTNH-NEXT: )
;; YESTNH-NEXT: )
;; NO_TNH: (func $inexact (type $6) (param $inexact (ref $A)) (result (ref $B))
;; NO_TNH-NEXT: (ref.get_desc $A
;; NO_TNH-NEXT: (local.get $inexact)
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: )
(func $inexact (param $inexact (ref $A)) (result (ref $B))
;; We do not optimize to an unreachable here, as the inexact reference may
;; contain an $A.sub. We optimize to that, using a cast (in TNH).
(ref.get_desc $A
(local.get $inexact)
)
)
)

Loading