Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Add explicit layout checks to Crossgen2 #27054

Merged
merged 3 commits into from
Oct 31, 2019

Conversation

trylek
Copy link
Member

@trylek trylek commented Oct 6, 2019

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

@MichalStrehovsky
Copy link
Member

This is rather difficult for me to follow with the accessPath and all. Do I understand it correctly that this goes over each field and for each field we check whether it overlaps any other field (by doing another foreach), basically doing n * n comparisons in the end?

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:

BYTE *pFieldLayout = (BYTE*) qb.AllocThrows(instanceSliceSize * sizeof(BYTE));
for (i=0; i < instanceSliceSize; i++)
{
pFieldLayout[i] = empty;
}

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:

  • We should do this check before ComputeExplicitFieldLayout returns - this is a core type system invariant that we should check for and not just an R2R addon.
  • The exception should match what CoreCLR does. ExceptionStringID.ClassLoadExplicitLayout is the right ID to use (it matches IDS_CLASSLOAD_EXPLICIT_LAYOUT), but the last parameter is an offset. We do this for compat - we want the exception messages to match what CoreCLR would throw. (The compat aspect is more pronounced in full AOT mode, but it's also here.)

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 :(

@trylek
Copy link
Member Author

trylek commented Oct 30, 2019

@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
Copy link
Member

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?

Copy link
Member Author

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;
Copy link
Member

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?

Copy link
Member Author

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;
Copy link
Member

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

Copy link
Member Author

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)
Copy link
Member

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()
    {
    }
}

Copy link
Member Author

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;
Copy link
Member

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()
    {
    }
}

Copy link
Member Author

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();
Copy link
Member

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.

Copy link
Member Author

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.

@MichalStrehovsky
Copy link
Member

Did you allude to porting some existing tests from the CoreRT repo?

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
@trylek
Copy link
Member Author

trylek commented Oct 31, 2019

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)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
else if (fieldType.IsPointer)
else if (fieldType.IsPointer || fieldType.IsFunctionPointer)

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in 3rd commit.

Copy link
Member

@MichalStrehovsky MichalStrehovsky left a 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.

Copy link
Member

@janvorli janvorli left a comment

Choose a reason for hiding this comment

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

LGTM, thank you!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants