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

Fix VN incorrect optimizations with a new JitEEInterface function. #57282

Merged
merged 10 commits into from
Aug 24, 2021

Conversation

sandreenko
Copy link
Contributor

@sandreenko sandreenko commented Aug 12, 2021

This is an alternative for #57076 that gets VM help to avoid non-unique fieldSeq in VN maps.

The main advantage of this approach is that we keep ValueNumbering changes small (and I personally can't be confident in any VN changes) and we don't change other phases to carry any information that was not there. Instead, we ask JitEEInterface where we need to restore field->struct information.

Thanks to @davidwrighton for the VM part of this change.

No pmi diffs, can't do SPMI because the GUID has changed. Will post crossgen2 diffs later, we expect small regressions there.

Fixes #42517, fixes #49954, fixes #54102, fixes #56980.

#57076 (comment) has a detailed explanation of the issue, but it is probably faster to look at the test cases added in this PR.

@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 Aug 12, 2021
@sandreenko sandreenko force-pushed the tryToAddNewMethod branch 3 times, most recently from 0f34021 to 397825b Compare August 15, 2021 22:23
@sandreenko sandreenko changed the title test. Fix VN incorrect optimizations with a new JitEEInterface function. Aug 15, 2021
@sandreenko
Copy link
Contributor Author

/azp run runtime-coreclr libraries-jitstress, runtime-coreclr jitstress, runtime-coreclr outerloop

@sandreenko sandreenko marked this pull request as ready for review August 16, 2021 09:43
@dotnet dotnet deleted a comment from azure-pipelines bot Aug 16, 2021
@dotnet dotnet deleted a comment from azure-pipelines bot Aug 16, 2021
@dotnet dotnet deleted a comment from azure-pipelines bot Aug 16, 2021
@dotnet dotnet deleted a comment from azure-pipelines bot Aug 16, 2021
@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@dotnet dotnet deleted a comment from azure-pipelines bot Aug 16, 2021
@@ -8006,10 +8006,11 @@ void Compiler::fgValueNumberTree(GenTree* tree)
rhsVNPair.SetBoth(vnStore->VNForExpr(compCurBB, lclVarTree->TypeGet()));
}

lclDefSsaNum = GetSsaNumForLocalVarDef(lclVarTree);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For a tree like:

N005 ( 11, 10) [001400] -A------R----             *  ASG       long
N004 (  6,  5) [001401] *------N-----             +--*  IND       long
N003 (  3,  3) [001402] -------------             |  \--*  ADDR      byref  Zero Fseq[_00]
N002 (  3,  2) [001403] U------N-----             |     \--*  LCL_VAR   struct<System.Runtime.Intrinsics.Vector128`1[Byte], 16> V45 tmp38        ud:3->4
N001 (  1,  1) [001404] -------------             \--*  LCL_VAR   long   V69 tmp62        u:2 (last use)

SSA was working correctly and printing that 001403 is using SSA-3 and defining SSA-4. However, this code, for some reason, was writing the result as a define for SSA-3.

So when at the next tree we were asking for GetPerSsaData(4) we were not getting an initialized value.

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 comes up now because we no longer set lvOverlappingFields in the importer?

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this could be moved to come before line 8051, as that is the first use of this value,

@@ -7273,6 +7269,29 @@ void Compiler::fgValueNumberBlockAssignment(GenTree* tree)
isNewUniq = true;
}

if (!isNewUniq && (rhsVarDsc != nullptr) && (lhsVarDsc != nullptr))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

the check isNewUniq is weak here because it is possible that we have already generated a unique VN for rhs but did not report it here.
Because of that we can't narrow the cases that go inside this block to assert(ClassLayout::AreCompatible(rhsVarDsc->GetLayout(), lhsVarDsc->GetLayout())).
However, all cases that pass all these "if" should have unique VN, we just ask for it twice for some trees.

@sandreenko
Copy link
Contributor Author

PTAL @AndyAyersMS @briansull @dotnet/jit-contrib

@jakobbotsch

This comment has been minimized.

@jakobbotsch

This comment has been minimized.

Copy link
Member

@AndyAyersMS AndyAyersMS left a comment

Choose a reason for hiding this comment

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

Overall this looks to be in good shape.

Left you a few notes.

@@ -8006,10 +8006,11 @@ void Compiler::fgValueNumberTree(GenTree* tree)
rhsVNPair.SetBoth(vnStore->VNForExpr(compCurBB, lclVarTree->TypeGet()));
}

lclDefSsaNum = GetSsaNumForLocalVarDef(lclVarTree);
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 comes up now because we no longer set lvOverlappingFields in the importer?

}
else
{
CORINFO_FIELD_HANDLE fldHnd = rhsFldSeq->GetTail()->GetFieldHandle();
Copy link
Member

Choose a reason for hiding this comment

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

Presumably we've weeded out the NotAField cases above?

Maybe assert for it here, as calling getFieldClass on a null handle will cause an AV.

}
else
{
CORINFO_FIELD_HANDLE fldHnd = lhsFldSeq->GetTail()->GetFieldHandle();
Copy link
Member

Choose a reason for hiding this comment

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

ditto here

else
{
CORINFO_FIELD_HANDLE fldHnd = rhsFldSeq->GetTail()->GetFieldHandle();
rhsStructHnd = info.compCompHnd->getFieldClass(fldHnd);
Copy link
Member

Choose a reason for hiding this comment

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

There's a bit of a divergence between doesFieldBelongToClass and getFieldClass -- otherwise you would not have needed this new interface method. Can that cause problems here?

// and logical field pair then return true. This is needed as the field handle here
// is used as a key into a hashtable mapping writes to fields to value numbers.
//
// In this implmentation this is made more complex as the JIT is exposed to CORINFO_FIELD_STRUCT
Copy link
Member

Choose a reason for hiding this comment

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

Maybe make it clearer that the crossgen2 behavior is different than the runtime behavior?

Is this something we should fix down the road? Seems like it would make us more conservative in places when doing R2R codegen.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe make it clearer that the crossgen2 behavior is different than the runtime behavior?

I will add a comment.

Is this something we should fix down the road? Seems like it would make us more conservative in places when doing R2R codegen.

I hope we will target to get rid of FldSeq and its dependency on FIELD_HANDLE and STRUCT_HANDLE.

If not we can do:

if (crossgen2)
{
 canonFieldHandle = JitEEInterface::getCanonField(fieldHandle);
  bool doesBelong = JitEEInterface::doesFieldBelongToClass(canonFieldHandle , clsHnd);
}

but I don't like such a solution, it hammers JIT to VM behavior, we can do it always, not only for crossgen2, but then it is an unnecessary VM call.


haveCorrectFieldForVN =
compiler->info.compCompHnd->doesFieldBelongToClass(field->gtFldHnd, clsHnd);
assert(haveCorrectFieldForVN);
Copy link
Member

Choose a reason for hiding this comment

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

If we're going to assert like this (as opposed to say noway which might be appropriate here, if we think the only ways we can get confused about fields is when optimizing) then you might as well only call the interface method under DEBUG.

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought that when using UnsafeAs to change the struct type to a different struct type, we would get a false back from doesFieldBelongToClass

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to a noway assert.

I thought that when using UnsafeAs to change the struct type to a different struct type, we would get a false back from doesFieldBelongToClass

yes, but they have to stay in the same tree without spilling, so something like:

struct1 s1 = new S1();
Unsafe.As<s1, s2>(ref s1).fieldOfS2 = 10;

if this is imported as one field:

FIELD fieldOfS2 
\-- OBJ(struct2)
   \-- ADDR byref
     \--LCL_VAR struct1 

here we should catch it and transform as

LCL_FLD struct1 (NotAField)

instead of

LCL_FLD struct1 (fieldOfS2)

but I could not create a test where we keep the source as 1 tree so I added an assert that it is unreachable for now.


haveCorrectFieldForVN =
compiler->info.compCompHnd->doesFieldBelongToClass(field->gtFldHnd, clsHnd);
assert(haveCorrectFieldForVN);
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought that when using UnsafeAs to change the struct type to a different struct type, we would get a false back from doesFieldBelongToClass

@@ -8006,10 +8006,11 @@ void Compiler::fgValueNumberTree(GenTree* tree)
rhsVNPair.SetBoth(vnStore->VNForExpr(compCurBB, lclVarTree->TypeGet()));
}

lclDefSsaNum = GetSsaNumForLocalVarDef(lclVarTree);
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this could be moved to come before line 8051, as that is the first use of this value,

@sandreenko
Copy link
Contributor Author

/azp run runtime-coreclr libraries-jitstress, runtime-coreclr jitstress, runtime-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@sandreenko
Copy link
Contributor Author

/azp run runtime-coreclr libraries-jitstress, runtime-coreclr jitstress, runtime-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

Sergey and others added 10 commits August 23, 2021 20:45
with a naive implementation so far.
It appeared that we have many trees where we generate unique VN for rhs and don't inform `fgValueNumberBlockAssignment` about it.
For example, for
```
N005 ( 10,  8) [000033] -A--G---R---              *  ASG       struct (copy)
N004 (  3,  2) [000031] D------N----              +--*  LCL_VAR   struct<eightByteStruct, 8> V01 loc1         d:2
N003 (  6,  5) [000030] n---G-------              \--*  IND       struct
N002 (  3,  3) [000043] ------------                 \--*  ADDR      byref
N001 (  3,  2) [000044] -------N----                    \--*  LCL_VAR   struct<largeStruct, 32> V00 loc0         u:6 (last use)
```
we will generate unique rhs but then `fgValueNumberBlockAssignment` will still try to work it as with a non-unique one.
For a tree like:
```
N005 ( 11, 10) [001400] -A------R----             *  ASG       long
N004 (  6,  5) [001401] *------N-----             +--*  IND       long
N003 (  3,  3) [001402] -------------             |  \--*  ADDR      byref  Zero Fseq[_00]
N002 (  3,  2) [001403] U------N-----             |     \--*  LCL_VAR   struct<System.Runtime.Intrinsics.Vector128`1[Byte], 16> V45 tmp38        ud:3->4
N001 (  1,  1) [001404] -------------             \--*  LCL_VAR   long   V69 tmp62        u:2 (last use)
```
SSA was working correctly and printing that `001403` is using SSA-3 and defining SSA-4. However, this code, for some reason, was writing result as a define for SSA-3.
@sandreenko
Copy link
Contributor Author

/azp run runtime-coreclr libraries-jitstress, runtime-coreclr jitstress, runtime-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@sandreenko
Copy link
Contributor Author

The only failure is test45929 but it was failing like this in other test runs (checked with kusto), for example, in https://dev.azure.com/dnceng/public/_build/results?buildId=1277844&view=ms.vss-test-web.build-test-results-tab&runId=37787400&resultId=110575&paneView=debug.

@sandreenko sandreenko merged commit bc7081e into dotnet:main Aug 24, 2021
@sandreenko
Copy link
Contributor Author

/backport to release/6.0-rc1

@github-actions
Copy link
Contributor

Started backporting to release/6.0-rc1: https://github.com/dotnet/runtime/actions/runs/1162100281

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
6 participants