-
Notifications
You must be signed in to change notification settings - Fork 758
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
Conversation
src/passes/Heap2Local.cpp
Outdated
if (auto* arrayNew = allocation->dynCast<ArrayNew>()) { | ||
numFields = getIndex(arrayNew->size); | ||
} else if (auto* arrayNewFixed = allocation->dynCast<ArrayNewFixed>()) { | ||
numFields = arrayNewFixed->values.size(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/passes/Heap2Local.cpp
Outdated
if (reached->type == nullArray) { | ||
reached->type = nullStruct; | ||
} else if (reached->type == nonNullArray) { | ||
reached->type = nonNullStruct; | ||
} | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
;; CHECK-NEXT: (drop | ||
;; CHECK-NEXT: (ref.null none) | ||
;; CHECK-NEXT: ) |
There was a problem hiding this comment.
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 🤔
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
test/lit/passes/heap2local.wast
Outdated
;; Arrays with reference values. | ||
(module | ||
;; CHECK: (type $array (sub (array (ref null $array)))) | ||
(type $array (sub (array (ref null $array)))) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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.