Skip to content
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

Do not promote struct locals with holes #62645

Merged
merged 3 commits into from
Dec 13, 2021
Merged

Conversation

kunalspathak
Copy link
Member

In #43870 , when we added more support for multireg args, we added a check for struct's field count. We didn't check if the total size of struct matched the combined size of both the fields. That led us to generate code that partially copy the struct fields on stack when passing as arguments. Add an explicit check to verify the combined size matches.

Fixes: #62597

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Dec 10, 2021
@ghost
Copy link

ghost commented Dec 10, 2021

Tagging subscribers to this area: @JulieLeeMSFT
See info in area-owners.md if you want to be subscribed.

Issue Details

In #43870 , when we added more support for multireg args, we added a check for struct's field count. We didn't check if the total size of struct matched the combined size of both the fields. That led us to generate code that partially copy the struct fields on stack when passing as arguments. Add an explicit check to verify the combined size matches.

Fixes: #62597

Author: kunalspathak
Assignees: -
Labels:

area-CodeGen-coreclr

Milestone: -

@kunalspathak
Copy link
Member Author

/azp list

@azure-pipelines
Copy link

@kunalspathak
Copy link
Member Author

/azp run runtime-coreclr jitstress
/azp run runtime-coreclr jitstressregs
/azp run runtime-coreclr libraries-jitstress
/azp run Antigen
/azp run Fuzzlyn

@azure-pipelines
Copy link

No pipelines are associated with this pull request.

@kunalspathak
Copy link
Member Author

/azp run runtime-coreclr jitstress

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@kunalspathak
Copy link
Member Author

/azp run runtime-coreclr jitstressregs

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@kunalspathak
Copy link
Member Author

/azp run runtime-coreclr libraries-jitstress

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@kunalspathak
Copy link
Member Author

/azp run Antigen

@kunalspathak
Copy link
Member Author

/azp run Fuzzlyn

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@kunalspathak
Copy link
Member Author

@dotnet/jit-contrib

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

if (structPromotionInfo.fieldCnt == 2)
{
unsigned structSize = varDsc->GetLayout()->GetSize();
if (structPromotionInfo.fields[0].fldSize * 2 != structSize)
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't this be better and more general?

Suggested change
if (structPromotionInfo.fields[0].fldSize * 2 != structSize)
if (structPromotionInfo.containsHoles)

(e.g., why *2 instead of fields[0].fldSize + fields[1].fldSize?)

Seems like the next condition maybe should also check containsHoles in case there's an explicit layout struct of a single SIMD field where the explicit layout is larger than the SIMD field

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, I actually had fields[0].fldSize + fields[1].fldSize but somehow I didn't push that change. Let me do it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looking at the code around, containsHoles does exactly that, so I should just replace this check with containHoles. Perhaps I should also add a check for customLayout the way it is done here:

else if (varDsc->lvIsMultiRegRet && structPromotionInfo.containsHoles && structPromotionInfo.customLayout)
{
JITDUMP("Not promoting multi-reg returned struct local V%02u with holes.\n", lclNum);
shouldPromote = false;

Copy link
Member

Choose a reason for hiding this comment

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

Yes, probably should add && structPromotionInfo.customLayout as well. I think the logic here is: a default/automatic struct layout might end up with holes due to alignment padding, but users can't depend on values in that padding. However, if the user explicitly set a custom/explicit layout, then we can't depend on the padding/holes being uninteresting.

@ghost ghost added needs-author-action An issue or pull request that requires more info or actions from the author. and removed needs-author-action An issue or pull request that requires more info or actions from the author. labels Dec 10, 2021
@AndyAyersMS
Copy link
Member

Is there some easy way to repro this without generating IL at runtime?

Dynamic methods bypass tiering (and thus OSR/PGO) and so we won't get as much coverage as we would from a source example.

@kunalspathak
Copy link
Member Author

Is there some easy way to repro this without generating IL at runtime?

I will try to find out.

@kunalspathak kunalspathak changed the title Make sure the combined field size matches the struct size Do not promote struct locals with holes Dec 10, 2021
Copy link
Member

@BruceForstall BruceForstall left a comment

Choose a reason for hiding this comment

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

LGTM

Any asm diffs?

Presumably this is a back-port candidate

@kunalspathak
Copy link
Member Author

Any asm diffs?

I will post the result once the asmdiff pipeline completes.

Presumably this is a back-port candidate

That's right. I will start the process once I see the asmdiffs.

@kunalspathak
Copy link
Member Author

/azp run runtime-coreclr jitstress

@kunalspathak
Copy link
Member Author

/azp run runtime-coreclr jitstressregs

@kunalspathak
Copy link
Member Author

/azp run runtime-coreclr libraries-jitstress

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

2 similar comments
@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@kunalspathak
Copy link
Member Author

asmdiffs results: https://dev.azure.com/dnceng/public/_build/results?buildId=1507589&view=ms.vss-build-web.run-extensions-tab

The worst difference are around 228 bytes in windows/linux arm64. I also inspected diffs from other platforms, and almost all of them are coming because we stop promoting the DateTimeOffset struct. So the code changes are related to _offsetMinutes and _offsetMinutes as they are passed in memory now.

[StructLayout(LayoutKind.Auto)]
[Serializable]
[System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public readonly struct DateTimeOffset : IComparable, ISpanFormattable, IComparable<DateTimeOffset>, IEquatable<DateTimeOffset>, ISerializable, IDeserializationCallback
#if FEATURE_GENERIC_MATH
#pragma warning disable SA1001, CA2252 // SA1001: Comma positioning; CA2252: Preview Features
, IAdditionOperators<DateTimeOffset, TimeSpan, DateTimeOffset>,
IAdditiveIdentity<DateTimeOffset, TimeSpan>,
IComparisonOperators<DateTimeOffset, DateTimeOffset>,
IMinMaxValue<DateTimeOffset>,
ISpanParseable<DateTimeOffset>,
ISubtractionOperators<DateTimeOffset, TimeSpan, DateTimeOffset>,
ISubtractionOperators<DateTimeOffset, DateTimeOffset, TimeSpan>
#pragma warning restore SA1001, CA2252
#endif // FEATURE_GENERIC_MATH
{
// Constants
internal const long MaxOffset = TimeSpan.TicksPerHour * 14;
internal const long MinOffset = -MaxOffset;
private const long UnixEpochSeconds = DateTime.UnixEpochTicks / TimeSpan.TicksPerSecond; // 62,135,596,800
private const long UnixEpochMilliseconds = DateTime.UnixEpochTicks / TimeSpan.TicksPerMillisecond; // 62,135,596,800,000
internal const long UnixMinSeconds = DateTime.MinTicks / TimeSpan.TicksPerSecond - UnixEpochSeconds;
internal const long UnixMaxSeconds = DateTime.MaxTicks / TimeSpan.TicksPerSecond - UnixEpochSeconds;
// Static Fields
public static readonly DateTimeOffset MinValue = new DateTimeOffset(DateTime.MinTicks, TimeSpan.Zero);
public static readonly DateTimeOffset MaxValue = new DateTimeOffset(DateTime.MaxTicks, TimeSpan.Zero);
public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(DateTime.UnixEpochTicks, TimeSpan.Zero);
// Instance Fields
private readonly DateTime _dateTime;
private readonly short _offsetMinutes;

@ltrzesniewski
Copy link
Contributor

I took a quick glance at the code to better understand what's going on, and I noticed something: a comment in ShouldPromoteStructVar says: We can promote; should we promote?
Shouldn't that case (structPromotionInfo.containsHoles && structPromotionInfo.customLayout) always mean we can't promote? So shouldn't it be checked in CanPromoteStructVar or even in CanPromoteStructType instead?

@kunalspathak
Copy link
Member Author

Failures are related to #60648, #58704 and #52361.

@kunalspathak
Copy link
Member Author

I took a quick glance at the code to better understand what's going on, and I noticed something: a comment in ShouldPromoteStructVar says: We can promote; should we promote? Shouldn't that case (structPromotionInfo.containsHoles && structPromotionInfo.customLayout) always mean we can't promote? So shouldn't it be checked in CanPromoteStructVar or even in CanPromoteStructType instead?

Your observations are correct @ltrzesniewski. However, currently, the code that checks for "can" and "should" are intermixed and probably we need to separate out some day. I just wanted to have the fix close to the place where we do similar check for "returned struct":

else if (varDsc->lvIsMultiRegRet && structPromotionInfo.containsHoles && structPromotionInfo.customLayout)
{
JITDUMP("Not promoting multi-reg returned struct local V%02u with holes.\n", lclNum);
shouldPromote = false;

@kunalspathak kunalspathak merged commit bffa7cf into dotnet:main Dec 13, 2021
@kunalspathak
Copy link
Member Author

/backport to release/6.0

@github-actions
Copy link
Contributor

Started backporting to release/6.0: https://github.com/dotnet/runtime/actions/runs/1574362543

@ghost ghost locked as resolved and limited conversation to collaborators Jan 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Explicit struct size is not always taken into account in code emitted at runtime
4 participants