-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Pull struct type info out of GenTreeObj #21705
Conversation
835f679
to
c6f9bf4
Compare
7a9b46a
to
11f70ea
Compare
3952d51
to
7955d32
Compare
e4c6b97
to
0087864
Compare
8fd34c3
to
df25389
Compare
5a4fc3f
to
d62b071
Compare
@CarolEidt When you have some time it would be useful to have a discussion about this change. Originally it was primarily aimed at supporting struct typed Now, if the node size was the only concern perhaps making
I don't think there's any doubt that this information needs to be better handled in the JIT. That's the simple part. In summary: class ClassLayout {
CORINFO_CLASS_HANDLE m_classHandle;
unsigned m_size;
unsigned m_gcPtrCount;
BYTE* m_gcPtrs;
// Other technical details left out for simplicity.
// Class is immutable and can be shared by, say, multiple `GT_OBJ` nodes.
// A bunch of convenience functions are available HasGCPtr(), IsGCPtr(slot),
// GetSlotCount() etc.
};
class ClassLayoutTable {
// Basically a JitHashTable<CORINFO_CLASS_HANDLE, ClassLayout*>
// but also allows access by index, similar to the LclVarDsc table
// Users of `ClassLayout` can store a pointer or an index, depending
// on the available space.
}; With this, we could probably replace pretty much every use of
The impact this can have on
It seems to me that we may want to change how this works in the future:
That said, in order to keep this PR manageable we'll probably want to keep the current |
@mikedn - it appears (famous last words) that things have lightened up a bit, and I'm hoping to spend some time in the coming weeks to sort out and prioritize work items particularly in the area of structs and register allocation (union not intersection of those), and this change is one that I'd really like to see prioritized. So this would be a great time to discuss this. What form of discussion did you have in mind?
Yes, I think it would be cleaner and more efficient. I think it's a good start to use this in all the places where we currently have class handles and/or GC layout, and then subsequently we can add it to other nodes (e.g.
That seems like a minor additional complication - am I missing something that makes this more costly than I'm thinking?
They already do:
I'd propose this as a possible subsequent round of change, as I believe there are still places where
Agreed. Thinking further out, I'd also like to see if we can improve and simplify the handling of structs with mismatched ABI requirements (e.g. multiple fields that are passed in a single register, or vice-versa), possibly by creating a "fake" ClassLayout that reflects the way the struct is broken up for ABI purposes. Supporting assignment between the "real" and "fake" layout seems like a cleaner approach to dealing with these arguments. |
Adding layout to I'm not convinced this is a good idea. In general I see "overloading" opers based on types with vastly different behavior to be dubious. It requires lots of
The main implementation complication is in
Yes but what I'm saying is that they should directly inherit from struct GenTreeObj : public GenTreeIndir {
ClassLayout* m_layout;
Kind gtBlkOpKind;
};
struct GenTreeBlk : public GenTreeIndir {
unsigned m_size;
Kind gtBlkOpKind;
};
struct GenTreeDynBlk : public GenTreeIndir {
GenTree* gtDynamicSize;
}; For case GT_STORE_OBJ:
if (node->AsObj()->GetLayout()->HasGCPtr())
LowerGCBlockStore(node);
else
node->AsObj()->gtBlkOpKind = LowerBlockStore(node, node->AsObj()->GetLayout()->GetSize());
break;
case GT_STORE_BLK:
node->AsBlk()->gtBlkOpKind = LowerBlockStore(node, node->AsBlk()->GetSize());
break; But yes, such a change should be separate. Attempting to do it now would make this PR unmanageable. So for the time being we'll need to live this this
I'm not sure about creating "fake" layouts, it may complicate things rather than help. I already have to do such a thing to deal with something named "PPP Quirk". I'm still not comfortable enough with all the call handling code but my current impression is that too many low level ABI details leak into the HIR when at least some should be confined to LIR and/or codegen. Something like - have a struct that has to be passed into an integer register? Leave the HIR alone (no LCL_FLD, no OBJ etc.) and deal with it in lowering or codegen. But we'll see. I have another bone to pick with calls - their use of |
bc48bce
to
78b7847
Compare
81ee3ee
to
c784647
Compare
b2b41be
to
37b67a9
Compare
3bf1e9c
to
ea04b68
Compare
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 really like this; indeed I've added a reference to this PR in my updated first-class-structs doc.
I'm coming around to your way of thinking that we should only use GT_BLK
for truly opaque block nodes (i.e. from cpblk
or initblk
nodes). That said, it seems like it still makes the backend simpler if GenTreeObj
still inherits from GenTreeBlk
.
My comments are mostly minor, and I think the timing is right for getting this in and building on it for further improvements to struct codegen.
void SetLayout(ClassLayout* layout) | ||
{ | ||
assert(varTypeIsStruct(lvType)); | ||
m_layout = layout; |
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.
Eventually I'd like to see use remove the use of the lvVerTypeInfo
to get struct handles (I'm not even certain whether there's any need for lvVerTypeInfo
to separately maintain the class handle after this change), but at least for now we should assert that they are the same.
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.
Heh, I was just working on finishing up this PR and this was something I was intending to put in comments. Yes, we should definitely avoid the lvVerTypeInfo
handle as it's duplicate and duplicate stuff is always at risk of getting out of sync and cause problems.
I actually did this but decided to keep it our of this PR to avoid some weirdness that goes on in lvaSetStruct
- it has a setTypeInfo
parameter that controls the setting of the handle in lvVerTypeInfo
independently of layout. It would seem that when verification is enabled the handle in lvVerTypeInfo
can perhaps be somehow different. I need to double check what's going on, though verification is AFAIK not a thing in .NET Core.
I also run into an annoyance caused by the implicit byref transformation - I would prefer to keep the assert(varTypeIsStruct(lvType));
asserts in Set/GetLayout
but during that transformation the LclVarDsc
is temporarily in a weird state where it no longer has struct type but the handle from layout is needed. For this and other reasons I suspect that I may have to clean up the implicit byref transformation first.
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, that's unfortunate. Do you have thoughts on what that cleanup might look like?
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.
Only high level - we likely need to move this transform out of global morph, in front of it. The temporary weird state of the LclVarDsc
might be unavoidable but I'd fell more comfortable if it's dealt with before global morph.
This would also tie in with #20957. Overall I think it should work like this:
LocalAddressVisitor
deals with all indirect lclvar access and producesGT_LCL_VAR
/GT_LCL_FLD
whenever possible. When not possible the lclvar is left address exposed. It should also leave some breadcrumbs to drive promotion (e.g. collect reference counts).- Promotion of
LclVarDsc
s - Transform IR according to promotion decisions above. Implicit byref transform should happen at the same time (since it's related to promotion).
- Global morph - at this point most, if not all, lclvar related transforms have already been applied. Morph has less work to do and we avoid some chicken & egg situations (e.g. having to kill assertions due to promotion).
- No more demotion of implicit by ref args
This does require an extra traversal of the IR so it would probably have a small throughput hit. But I keep finding ways to improve throughput in other parts of the JIT so I'm not worried about that.
src/jit/gentree.h
Outdated
@@ -300,6 +300,153 @@ class FieldSeqStore | |||
} | |||
}; | |||
|
|||
class ClassLayout |
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 seems odd for this to be in GenTree
, though I guess it's used there exclusively.
It might almost merit its own separate (header and cpp) files, including moving the cache there from compiler.cpp.
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 confess that I simply dumped this class in the first place where it worked :). I'd be OK with adding new files, though I remember a discussion about the JIT source directory already having lots of files and no subdirectory structure to make it more manageable. Up to the team.
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.
Moved all new code to layout.h
/layout.cpp
, seems reasonable. Especially if we're going to add more info/logic to ClassLayout
:
- HFA (and maybe other ABI related info that now gets duplicated in
fgArgTabEntry
) info - SIMD info
- maybe field info - when promoting we query EE for fields and other info for every promotable variable, even if it has the same type as a previously promoted variable. We could try to add the necessary info to
ClassLayout
the first time we get it. - maybe it would be even worthwhile to have an abstraction for fields too, instead of using
CORINFO_FIELD_HANDLE
directly
Some other notes:
|
lvaTable[shadowVar].lvVerTypeInfo = varDsc->lvVerTypeInfo; | ||
if (varTypeIsStruct(type)) | ||
{ | ||
lvaTable[shadowVar].SetLayout(varDsc->GetLayout()); |
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 looks like this should not be needed, gsParamsToShadows
calls lvaSetStruct
so both lvVerTypeInfo
and the layout would be set anyway. Question is why isn't lvaSetStruct
simply called here, makes no sense to me to delay it.
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 a bit confused by your comment. You said that it calls lvaSetStruct
, but it doesn't seem to. Did you mean that it should do so?
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 does call it but in a different place:
- first it creates the variables, without calling
lvaSetStruct
- then it changes the IR to use the new variables (ugly because the IR will now temporarily be in an invalid state - the newly created variables may have not been fully setup without
lvaSetStruct
) - then it generates copies from the old variables to the new variables and here it also calls
lvaSetStruct
-Lines 523 to 527 in 9479f67
CORINFO_CLASS_HANDLE clsHnd = varDsc->lvVerTypeInfo.GetClassHandle(); // We don't need unsafe value cls check here since we are copying the params and this flag // would have been set on the original param before reaching here. lvaSetStruct(shadowVar, clsHnd, false);
I don't see any reason not to calllvaSetStruct
when the variables are created.
But this code is ancient. There are still traces of old struct handling - it wraps the lclvar nodes in ADDR
nodes and then calls gtNewCpObjNode
. Which will of course remove the ADDR
nodes as they're not necessary.
A bunch of extra complication basically.
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.
Thanks - I didn't look down far enough. It should probably be cleaned up. It's not clear how much it matters, but I agree that it's unwise to have the inconsistent state, even temporarily.
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've added a few more comments and made some small tweaks to |
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 have one question, but I'm going to go ahead and merge this.
lvaTable[shadowVar].lvVerTypeInfo = varDsc->lvVerTypeInfo; | ||
if (varTypeIsStruct(type)) | ||
{ | ||
lvaTable[shadowVar].SetLayout(varDsc->GetLayout()); |
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 a bit confused by your comment. You said that it calls lvaSetStruct
, but it doesn't seem to. Did you mean that it should do so?
* Introduce ClassLayout * Delete unused getStructGcPtrsFromOp * Use ClassLayout in GenTreeObj/Blk * Use ClassLayout in LclVarDsc * Remove layout info from GenTreePutArgStk * Always initialze ClassLayout GC layout * Make ClassLayout::GetGCPtrs private * Restore genAdjustStackForPutArgStk asserts * Comments * Put layout related code in new files * More comments and small tweaks Commit migrated from dotnet/coreclr@9479f67
Contributes to #19875
Contributes to #19412