Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
Update the JIT to track
Span.Length
andReadOnlySpan.Length
as "never negative" #81055Update the JIT to track
Span.Length
andReadOnlySpan.Length
as "never negative" #81055Changes from 6 commits
5dac74f
c88ae01
820a5d8
618226e
e5d4b13
d700615
60ffb5a
2f882d8
c224a3d
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
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 it make any difference in the PMI diffs if we add
CORINFO_FLG_SPAN
that is returned bygetClassAttribs
forSpan<T>
andReadOnlySpan<T>
, and then use it inCompiler::StructPromotionHelper::PromoteStructVar
to set this bit on the second local created?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.
I'll take a look independently, but I wouldn't expect significant diffs and I don't think we'd want this to be the exclusive way to determine that.
-- Posting a rough summary of what got discussed on Discord.
In order for user code to access the field, they must go through
get_Length
. They could reinterpret cast or use pointers or other things, but those are all rarer and potentially dangerous.For the block copy case, you don't really end up with interesting opts possible. The user could then access the copied field, but that would still go through
get_Length
and therefore the intrinsic path.We'd want to keep the flag we set as part of
import
to handle any early constant folding handling. Such optimizations generally have positive impact on TP due to the early dead code removal they allow (and therefore making the total IR to process significantly smaller from the start).There are other potential opts and tracking we could do, including loop cloning if we had some
CORINFO_FLG_SPAN
and there might be some cases like multi-reg arg passing where the intrinsic path wouldn't necessarily catch thingsThere 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.
I am still not a fan of this propagation of "never negative" from a
GT_FIELD
access to theLclVarDsc
. It makes an implicit assumption that the local will always satisfy this condition. That is ok for the uses ofIsNeverNegative()
introduced by this PR but is a very surprising precondition to require in JIT IR (and in contrast to things likeGTF_IND_NONNULL
). I think it makes the state fragile in the future.If we want to keep it this way I think the new accessors/information on
GT_FIELD
needs to be renamed into something likeIsNonNegativeLengthAccess()
that makes it more clear that the access is on a local that is expected not to break the invariant.My preference would still be to introduce
CORINFO_FLG_SPAN
and set it on span locals. We can teachGenTree:IsNeverNegative
/IntegralRange::ForNode
to recognize accesses of the length (for both promoted access and potential non-promoted access, before promotion happens). It would be more in line withARR_LENGTH
today and would not require adding new state to any nodes (only alvIsSpan
onLclVarDsc
that can be set as part oflvaSetStruct
-- we already callgetClassAttribs
).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.
I'm concerned about the additional cost that doing that would bring. What we have here is simple, effectively, extensible, and works without negatively impacting throughput (it's a small sub 0.01% gain on most platforms and a small sub 0.01% regression on a couple).
While this wouldn't add overhead to the promotion case since we already call
getClassAttribs
, doing pre-promotion recognition would require additional JIT/EE calls to be introduced which can negatively impact throughput. We would then need to either track that information somehow or continue doing lookups against the field handle where necessary (and we cannot trivially cache since we have a different handle perT
).I think this is reasonable for now. That being said, I think we want and need this to be more generally extensible long term. The concept of a exposing an
int
but the code requiring it to be "never negative" is fairly integrated throughout the entirety of .NET. In practice it is similar to most APIs not allowing references to benull
. There are a good deal of optimizations that are only valid on unsigned types and which will allow us to provide significantly better perf/codegen by recognizing. -- The main reason this diff "looks small" is because we are already explicitly casting to the equivalent unsigned type in much of our managed BCL code and so the remaining cases are places where we weren't or couldn't.Can you elaborate on the fragility and how you think future changes could impact this?
Are you referring to locals getting tagged and then reused for something else? Users assigning negative values (which should only be possible via unsafe code)? Or something else?
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.
The overhead would be in the pattern recognition inside
GenTree::IsNeverNegative
/IntegralRange::ForNode
, but we would need to measure it to make any conclusions.There would be no new EE calls since
lvaSetStruct
already callsgetClassAttribs
. It would makegetClassAttribs
more expensive on the EE side, but I expect we will want to do this anyway at some point in the (near) future to support loop cloning for spans.I am referring to a future JIT dev marking a
GT_FIELD
node as never negative because the field access is non-negative at that particular point in time, in the same way one would withGTF_IND_NONNULL
. That's not legal due to this propagation intoLclVarDsc
that actually requires the backing storage to always be non-negative globally in the function.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.
Talked a bit more on Discord to get clarification.
I've updated
GenTreeField::IsNeverNegative
to beGenTreeField::IsSpanLength
to help avoid any issues around the current propagation. There exists a comment in the method that goes into more detail around the "why".The plan after this PR is to look into also providing the
getClassAttribs
support since that should allow otherArray
like optimizations to kick in for span. However, the intent for the support being added in this PR is that it will stick around both to help catch pre-promotion dead code elimination and because the belief is that there are more general purpose areas where the information that a value is known to be non-negative will be profitable (much likeGTF_IND_NONNULL
).