Skip to content
New issue

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

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

Already on GitHub? # to your account

Heap2Local: Optimize Arrays in addition to Structs #6478

Merged
merged 55 commits into from
Apr 9, 2024

Conversation

kripken
Copy link
Member

@kripken kripken commented Apr 8, 2024

To keep things simple, this adds a Array2Struct component to the pass. When we
find a non-escaping array, we run that to turn it into a struct, and then run the
existing Struct2Local to convert that to locals. This avoids refactoring Struct2Local
to handle both structs and arrays (with the downside of making the optimization of
arrays a little less efficient, but they are rarer, I suspect - that is certainly the case
in Java output I've seen).

The core EscapeAnalyzer logic is generalized to handle both arrays and structs,
but the changes there are thankfully quite minor.

@kripken kripken requested a review from tlively April 8, 2024 20:24
Comment on lines 820 to 823
if (auto* arrayNew = allocation->dynCast<ArrayNew>()) {
numFields = getIndex(arrayNew->size);
} else if (auto* arrayNewFixed = allocation->dynCast<ArrayNewFixed>()) {
numFields = arrayNewFixed->values.size();
Copy link
Member

Choose a reason for hiding this comment

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

Maybe this part can be factored into a getSize helper?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea, done.

} else if (auto* arrayNewFixed = allocation->dynCast<ArrayNewFixed>()) {
// Simply use the same values as the array.
structNew = builder.makeStructNew(structType, arrayNewFixed->values);
arrayNewReplacement = structNew;
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to noteIsReached in this case as well? If not, why? If it's because of the noteCurrentIsReached calls below, then why do we need the noteIsReached above?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, good point, this was not consistent. It happened to work but it's confusing. I moved the noteIsReached to a shared location and applied it to both structNew and arrayNewReplacement uniformly, with a better (hopefully) explanation.

Comment on lines 875 to 880
if (reached->type == nullArray) {
reached->type = nullStruct;
} else if (reached->type == nonNullArray) {
reached->type = nonNullStruct;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

What about supertypes of the array type?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, the fuzzer noticed this overnight as well 😄 Fixed.

noteCurrentIsReached();
}

void visitArrayGet(ArrayGet* curr) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to handle other array operations like ArrayCopy, ArrayFill, or the string allocation instructions?

Copy link
Member Author

Choose a reason for hiding this comment

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

We have a TODO for those. This is safe atm as EscapeAnalyzer will assume the worst for anything it does not recognize, like those.

Comment on lines +2330 to +2332
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
Copy link
Member

Choose a reason for hiding this comment

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

It might be worth checking side effects when preserving the reference just to reduce test verbosity 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, sorry about the verbosity. This pass has not make an effort to check effects so far - not sure it's worth changing that, as it would add more code complexity.

;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $array.flowing.type (result i32)
(local $temp (ref $array))
Copy link
Member

Choose a reason for hiding this comment

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

Does this still work properly when the local is a supertype of the allocated array type?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. We had a test for that with structs and I added one for arrays now, good idea.

;; Arrays with reference values.
(module
;; CHECK: (type $array (sub (array (ref null $array))))
(type $array (sub (array (ref null $array))))
Copy link
Member

Choose a reason for hiding this comment

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

Is it important that this type is sub? If not, perhaps we can elide that part.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good, I removed that part.

@kripken kripken merged commit fca1d3a into WebAssembly:main Apr 9, 2024
12 of 13 checks passed
@kripken kripken deleted the heap2local.nfc.2 branch April 9, 2024 20:31
@gkdn gkdn mentioned this pull request Aug 31, 2024
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants