Skip to content

LUBFinder: Update nulls #4302

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft

LUBFinder: Update nulls #4302

wants to merge 27 commits into from

Conversation

kripken
Copy link
Member

@kripken kripken commented Nov 2, 2021

It is common in GC code to have stuff like this:

x = null;
..
x = Data();

Nulls in wasm have a type, and if that initial null has say anyref then
before this PR we would keep the type of x as anyref. However,
while nulls have types, all null values are identical, and so we can
in fact change x's type to a nullable reference of Data, by also
changing the null's type. That is, the LUB of

(ref.null any)  ,  (struct.new $Data)

can be (ref null $Data) if we update that null to

(ref.null $Data)

This PR adds that optimization to LUBFinder, and does the relevant
minor work to get that supported in the two passes using that
helper. The LUBFinder now tracks nulls and updates them at the
end if it makes sense to do so.

An annoyance is that test/lit/passes/dae-gc-refine-return.wast used
a lot of nulls in that test file. But now the pass can optimize those. This
PR therefore adds blocks to wrap around them, which avoids changing
anything in the test there, though it is not a small diff sadly.

@kripken kripken requested a review from tlively November 2, 2021 18:52
@tlively
Copy link
Member

tlively commented Nov 2, 2021

Should be (ref.null $Data) in the PR description where it describes the result of the transformation.

@kripken
Copy link
Member Author

kripken commented Nov 2, 2021

@tlively Do you mean to add a . there? But the sentence is:

That is, the LUB of [..two instructions..] can be (ref null $Data).

The LUB is a type (and not a ref.null instruction) so I think that is correct as it is?

@tlively
Copy link
Member

tlively commented Nov 2, 2021

Yeah sorry I mistyped that comment originally. See the edit.

can be (ref null $Data) if we update that null to

(ref.null any)

should be

can be (ref null $Data) if we update that null to

(ref.null $Data)
@kripken
Copy link
Member Author

kripken commented Nov 2, 2021

Oh, right, thanks! Fixed.

Copy link
Member

@tlively tlively left a comment

Choose a reason for hiding this comment

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

Nice!

Comment on lines +103 to +106
std::vector<Type> types;
for (auto* null : updatableNulls) {
types.push_back(null->type);
}
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to reuse note(Type) here rather than introducing a new vector?

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 guess that could be more efficient. It would leave noticeable traces, though, if you look at the internals, even though they should be hidden from the outside.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, fair enough. No reason to mess with longer-lived state here.

Comment on lines +711 to +712
if (lub.get() == originalType) {
return false;
Copy link
Member

Choose a reason for hiding this comment

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

This looks dangerous because get() could update nulls even if it returns the original type, in which case we would have to return true to refinalize. However, I think that if we get here, it must be that the lub is not originalType because we would have returned already after one of the calls to processReturnValue or processReturnType. So can we turn this into an assertion that the lub is not equal to originalType?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, good point, this does look dangerous. I'll try to figure out something.

We can't assert here, because while you are right that the LUB is not originalType, it might still become originalType during the final computation - when we take into account nulls. But, that case is not dangerous to us here. It's annoying that the invariants that makes this safe are in the other file, which makes it hard to reason about safety here, which I'll think on.

(i32.const 2)
(return (ref.null func))
)
(ref.null func)
Copy link
Member

Choose a reason for hiding this comment

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

What if the nulls are not all the same type?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants