-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add explicit layout checks to Crossgen2 #27054
Conversation
This is rather difficult for me to follow with the I feel like this would be easier to follow (and more performant) if we do it like CoreCLR does it by allocating a byte array and setting bytes in it as we walk the fields in a single pass: coreclr/src/vm/methodtablebuilder.cpp Lines 8324 to 8328 in af55c1b
I'm concerned that there's bugs in this around handling of pointer-typed fields (seems like we don't consider an overlap an error, but CoreCLR does): [StructLayout(LayoutKind.Explicit)]
unsafe struct A
{
[FieldOffset(0)] object o;
[FieldOffset(0)] int* z;
}
} We also don't consider an overlap in padding an error, but CoreCLR does: [StructLayout(LayoutKind.Explicit)]
unsafe struct A
{
[FieldOffset(8)] object o;
}
[StructLayout(LayoutKind.Explicit)]
unsafe struct B
{
[FieldOffset(0)] A a;
[FieldOffset(0)] object o;
} Two more things:
Ideally, this would be accompanied by a unit test since it's a type system change and we need those to be well tested, but unfortunately we haven't ported unit tests to this repo :( |
9f9ae31
to
6fd02d1
Compare
@MichalStrehovsky - I believe I have addressed your PR feedback in the latest iteration; could you please take another look when you have a chance? At this point I haven't done anything about unit testing. The change fixes about 10~20 failures in existing CoreCLR tests under Loader\classloader\explicitlayout. Did you allude to porting some existing tests from the CoreRT repo? |
|
||
namespace Internal.TypeSystem | ||
{ | ||
public class ExplicitLayoutValidator |
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.
Can this be a struct?
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.
Of course, thanks for pointing that out!
|
||
using Internal.TypeSystem; | ||
using System; | ||
using System.Net; |
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 do we use System.Net for?
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.
No use, I just accidentally copied that over along with the copyright header. Deleted.
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using Internal.TypeSystem; |
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.
Nit: I think this is unnecessary
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.
Deleted.
} | ||
} | ||
} | ||
else if (fieldType.IsPointer || fieldType.IsByRef || fieldType.IsByRefLike) |
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.
- IsByRefLike is a ref struct - those will respond to
IsValueType
above. - We don't allow ByRef fields right now, so maybe we don't need to handle that.
- Pointer typed fields are okay to be misaligned. This works:
[StructLayout(LayoutKind.Explicit)]
unsafe class Program
{
[FieldOffset(2)] int* f;
static unsafe void Main()
{
}
}
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.
Removed IsByRefLike from the conditional check. Added a new clause for IsPointer setting the range to NonORef.
} | ||
else if (fieldType.IsValueType) | ||
{ | ||
MetadataType mdType = (MetadataType)fieldType; |
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.
If the field is a ByRef-like struct, it also needs to be aligned at pointer boundary. This should throw:
[StructLayout(LayoutKind.Explicit)]
ref struct Program
{
[FieldOffset(2)] Span<int> f;
static unsafe void Main()
{
}
}
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.
Added alignment check for IsByRefLike within the IsValueType conditional block.
} | ||
else | ||
{ | ||
throw new NotImplementedException(); |
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.
Nit: Instead of an else branch that throws NotImplemented, I tend to use asserts. It doesn't make sense to ship the throw to customers and if we ever add new type system entities, the assert will hit.
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.
Changed NotImplementedException throw to assertion failure.
I was just thinking how to do the testing for this (e.g. all the cases that I pointed out in this review would make good unit test cases). We could make this change in CoreRT repo with unit tests included and then commit just the product part here. But I'm not going to block on that because it's awkward in the short term (although having unit tests is better in long term). I do want to bring over the tests at some point. |
Based on Michal's PR feedback I have refactored the explicit layout checks to use a loose port of the CoreCLR algorithm i.e. creating an array representing the individual bytes of the type layout and gradually filling it in with GC reference / non-GC reference markers for the individual fields. Thanks Tomas
1) Remove superfluous using clauses; 2) Make ExplicitLayoutValidator a struct; 3) Fixing handling of IsByRefLike and IsPointer cases; 4) Changing the final throw NotImplementedException to an assert. Thanks Tomas
6fd02d1
to
a51c425
Compare
OK, that should be it (modulo the tests I haven't done anything about at this point), @MichalStrehovsky - could you please take another look? |
} | ||
SetFieldLayout(offset, _pointerSize, FieldLayoutTag.ORef); | ||
} | ||
else if (fieldType.IsPointer) |
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.
else if (fieldType.IsPointer) | |
else if (fieldType.IsPointer || fieldType.IsFunctionPointer) |
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.
Fixed in 3rd commit.
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.
Looks good modulo the function pointer case.
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.
LGTM, thank you!
Several tests under "Loader/classloader/explicitlayout" fail today
because Crossgen2 is missing GC consistency checks for types with
explicit layout - according to my understanding of the corresponding
CoreCLR algorithm we basically need to make sure that no GC fields
overlap non-GC fields. I have reimplemented the algorithm instead
of porting it as the CoreCLR version is heavily tied to the method
table builder and hard to separate out.
Thanks
Tomas