diff --git a/src/coreclr/inc/corhdr.h b/src/coreclr/inc/corhdr.h index c4b0ed61d77d6b..cee5db06c1104f 100644 --- a/src/coreclr/inc/corhdr.h +++ b/src/coreclr/inc/corhdr.h @@ -329,6 +329,7 @@ typedef enum CorTypeAttr enum class CorExtendedLayoutKind { CStruct = 0, // C-style struct + CUnion = 1, // C-style union }; // Macros for accessing the members of the CorTypeAttr. diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 883af8a3cd01e0..e8a9b7a0b8ff17 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -2118,6 +2118,14 @@ private uint getClassAttribsInternal(TypeDesc type) if (metadataType.IsInlineArray) result |= CorInfoFlag.CORINFO_FLG_INDEXABLE_FIELDS; + + if (metadataType.IsExtendedLayout) + { + if (metadataType.GetClassLayout().Kind == MetadataLayoutKind.CUnion) + { + result |= CorInfoFlag.CORINFO_FLG_OVERLAPPING_FIELDS; + } + } } if (type.IsCanonicalSubtype(CanonicalFormKind.Any)) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs index 52bfd8d2852cf0..fc541a267aaa7c 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs @@ -498,7 +498,7 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType protected ComputedInstanceFieldLayout ComputeCStructFieldLayout(MetadataType type, int numInstanceFields) { - if (type.ContainsGCPointers || !type.IsValueType) + if (type.ContainsGCPointers || type.IsByRefLike || !type.IsValueType) { // CStruct layout algorithm does not support GC pointers. ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadBadFormat, type); @@ -581,6 +581,91 @@ protected ComputedInstanceFieldLayout ComputeCStructFieldLayout(MetadataType typ return computedLayout; } + protected ComputedInstanceFieldLayout ComputeCUnionFieldLayout(MetadataType type, int numInstanceFields) + { + if (type.ContainsGCPointers || type.IsByRefLike || !type.IsValueType) + { + // CUnion layout algorithm does not support GC pointers. + ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadBadFormat, type); + } + + var offsets = new FieldAndOffset[numInstanceFields]; + + LayoutInt largestFieldSize = LayoutInt.Zero; + LayoutInt largestAlignmentRequirement = LayoutInt.One; + int fieldOrdinal = 0; + int packingSize = type.Context.Target.MaximumAlignment; + bool layoutAbiStable = true; + bool hasAutoLayoutField = false; + bool hasInt128Field = false; + bool hasVectorTField = false; + + foreach (var field in type.GetFields()) + { + if (field.IsStatic) + continue; + + var fieldSizeAndAlignment = ComputeFieldSizeAndAlignment(field.FieldType.UnderlyingType, hasLayout: true, packingSize, out ComputedFieldData fieldData); + if (!fieldData.LayoutAbiStable) + layoutAbiStable = false; + if (fieldData.HasAutoLayout) + hasAutoLayoutField = true; + if (fieldData.HasInt128Field) + hasInt128Field = true; + if (fieldData.HasVectorTField) + hasVectorTField = true; + + largestAlignmentRequirement = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequirement); + largestFieldSize = LayoutInt.Max(fieldSizeAndAlignment.Size, largestFieldSize); + + // All fields are placed at offset 0 in a union + offsets[fieldOrdinal] = new FieldAndOffset(field, LayoutInt.Zero); + + fieldOrdinal++; + } + + if (hasAutoLayoutField) + { + // CUnion does not support auto layout fields + ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadBadFormat, type); + } + + if (largestFieldSize == LayoutInt.Zero) + { + // CUnion cannot have zero size. + ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadBadFormat, type); + } + + if (type.IsInlineArray) + { + // CUnion types cannot be inline arrays + ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadBadFormat, type); + } + + SizeAndAlignment instanceByteSizeAndAlignment; + var instanceSizeAndAlignment = ComputeInstanceSize( + type, + largestFieldSize, + largestAlignmentRequirement, + classLayoutSize: 0, // CUnion does not use the size from metadata. + out instanceByteSizeAndAlignment); + + ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout + { + IsAutoLayoutOrHasAutoLayoutFields = false, + IsInt128OrHasInt128Fields = hasInt128Field, + IsVectorTOrHasVectorTFields = hasVectorTField, + FieldAlignment = instanceSizeAndAlignment.Alignment, + FieldSize = instanceSizeAndAlignment.Size, + ByteCountUnaligned = instanceByteSizeAndAlignment.Size, + ByteCountAlignment = instanceByteSizeAndAlignment.Alignment, + Offsets = offsets, + LayoutAbiStable = layoutAbiStable + }; + + return computedLayout; + } + private static void AdjustForInlineArray( MetadataType type, int instanceFieldCount, diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataType.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataType.cs index 44a93d53f10109..2e6d90b3d27c32 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataType.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataType.cs @@ -125,6 +125,7 @@ public enum MetadataLayoutKind Auto, Sequential, Explicit, - CStruct + CStruct, + CUnion } } diff --git a/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs index 2abdab970b83ac..b4491af580779b 100644 --- a/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs +++ b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs @@ -586,6 +586,9 @@ public override ClassLayoutMetadata GetClassLayout() case 0: layoutKind = MetadataLayoutKind.CStruct; break; + case 1: + layoutKind = MetadataLayoutKind.CUnion; + break; default: ThrowHelper.ThrowTypeLoadException(this); return default; // Invalid kind value diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalUtils.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalUtils.cs index 5abdfa1b65466a..8be81d29d3fcf0 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalUtils.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalUtils.cs @@ -31,7 +31,7 @@ public static bool IsBlittableType(TypeDesc type) if (mdType.IsExtendedLayout) { - return mdType.GetClassLayout().Kind is MetadataLayoutKind.CStruct; + return mdType.GetClassLayout().Kind is MetadataLayoutKind.CStruct or MetadataLayoutKind.CUnion; } if (mdType.IsAutoLayout) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerMetadataFieldLayoutAlgorithm.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerMetadataFieldLayoutAlgorithm.cs index 4e869b4a647a51..446c9b6a4a0f43 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerMetadataFieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerMetadataFieldLayoutAlgorithm.cs @@ -49,6 +49,7 @@ protected override ComputedInstanceFieldLayout ComputeInstanceFieldLayout(Metada // all types without GC references (ie C# unmanaged types). MetadataLayoutKind.Sequential when !type.ContainsGCPointers => ComputeSequentialFieldLayout(type, numInstanceFields, layoutMetadata), MetadataLayoutKind.CStruct => ComputeCStructFieldLayout(type, numInstanceFields), + MetadataLayoutKind.CUnion => ComputeCUnionFieldLayout(type, numInstanceFields), _ => ComputeAutoFieldLayout(type, numInstanceFields, layoutMetadata), }; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunMetadataFieldLayoutAlgorithm.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunMetadataFieldLayoutAlgorithm.cs index ba52e485554348..33d82c8782acd8 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunMetadataFieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunMetadataFieldLayoutAlgorithm.cs @@ -612,6 +612,8 @@ protected override ComputedInstanceFieldLayout ComputeInstanceFieldLayout(Metada { case MetadataLayoutKind.CStruct: return ComputeCStructFieldLayout(type, numInstanceFields); + case MetadataLayoutKind.CUnion: + return ComputeCUnionFieldLayout(type, numInstanceFields); case MetadataLayoutKind.Explicit: // Works around https://github.com/dotnet/runtime/issues/102868 if (type is { IsValueType: false, BaseType.IsSequentialLayout: true }) diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestMetadataFieldLayoutAlgorithm.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestMetadataFieldLayoutAlgorithm.cs index 737ff9ecdf2bdb..032e24eb5fece8 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestMetadataFieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestMetadataFieldLayoutAlgorithm.cs @@ -43,6 +43,10 @@ protected override ComputedInstanceFieldLayout ComputeInstanceFieldLayout(Metada { return ComputeCStructFieldLayout(type, numInstanceFields); } + else if (layoutMetadata.Kind == MetadataLayoutKind.CUnion) + { + return ComputeCUnionFieldLayout(type, numInstanceFields); + } else { return ComputeAutoFieldLayout(type, numInstanceFields, layoutMetadata); diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index d130506c408898..3ba48279bad49a 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -342,7 +342,8 @@ class EEClassLayoutInfo Auto = 0, // Make sure Auto is the default value as the default-constructed value represents the "auto layout" case Sequential, Explicit, - CStruct + CStruct, + CUnion }; private: enum { @@ -496,6 +497,12 @@ class EEClassLayoutInfo ULONG cFields ); + ULONG InitializeCUnionFieldLayout( + FieldDesc* pFields, + MethodTable** pByValueClassCache, + ULONG cFields + ); + private: void SetIsZeroSized(BOOL isZeroSized) { diff --git a/src/coreclr/vm/classlayoutinfo.cpp b/src/coreclr/vm/classlayoutinfo.cpp index c3678686cb3832..f1db07aedfbb48 100644 --- a/src/coreclr/vm/classlayoutinfo.cpp +++ b/src/coreclr/vm/classlayoutinfo.cpp @@ -636,6 +636,45 @@ ULONG EEClassLayoutInfo::InitializeCStructFieldLayout( return SetInstanceBytesSize(managedSize); } +ULONG EEClassLayoutInfo::InitializeCUnionFieldLayout( + FieldDesc* pFields, + MethodTable** pByValueClassCache, + ULONG cFields +) +{ + STANDARD_VM_CONTRACT; + + SetLayoutType(LayoutType::CUnion); + + NewArrayHolder pInfoArray = new LayoutRawFieldInfo[cFields + 1]; + UINT32 numInstanceFields; + BYTE fieldsAlignmentRequirement; + InitializeLayoutFieldInfoArray(pFields, cFields, pByValueClassCache, DEFAULT_PACKING_SIZE, pInfoArray, &numInstanceFields, &fieldsAlignmentRequirement); + + BYTE alignmentRequirement = max(1, fieldsAlignmentRequirement); + + SetAlignmentRequirement(alignmentRequirement); + SetPackingSize(DEFAULT_PACKING_SIZE); + + // For a union, all fields are placed at offset 0 + // and the size is the maximum of all field sizes + UINT32 maxFieldSize = 0; + for (UINT32 i = 0; i < numInstanceFields; i++) + { + pInfoArray[i].m_placement.m_offset = 0; + if (pInfoArray[i].m_placement.m_size > maxFieldSize) + { + maxFieldSize = pInfoArray[i].m_placement.m_size; + } + } + + SetFieldOffsets(pFields, cFields, pInfoArray, numInstanceFields); + + UINT32 managedSize = AlignSize(maxFieldSize, alignmentRequirement); + + return SetInstanceBytesSize(managedSize); +} + namespace { #ifdef UNIX_AMD64_ABI diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 84c5fe782bed2a..c2a22dfed7c9ab 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -3724,6 +3724,9 @@ uint32_t CEEInfo::getClassAttribsInternal (CORINFO_CLASS_HANDLE clsHnd) if (pClass->IsUnsafeValueClass()) ret |= CORINFO_FLG_UNSAFE_VALUECLASS; + + if (pClass->HasLayout() && pClass->GetLayoutInfo()->GetLayoutType() == EEClassLayoutInfo::LayoutType::CUnion) + ret |= CORINFO_FLG_OVERLAPPING_FIELDS; } if (pClass->HasExplicitFieldOffsetLayout() && pClass->HasOverlaidField()) ret |= CORINFO_FLG_OVERLAPPING_FIELDS; diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 25f730ac1e3607..68f76e97f3457e 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -8319,10 +8319,11 @@ VOID MethodTableBuilder::PlaceInstanceFields(MethodTable** pByValueClassCache) { if (!pParentMT->IsValueTypeClass() || hasGCFields + || bmtFP->fIsByRefLikeType || isAutoLayoutOrHasAutoLayoutField) { // CStruct layout types can't have a parent type, GC fields - // or auto layout fields. + // byreflike types, or auto layout fields. BuildMethodTableThrowException(IDS_CLASSLOAD_BADFORMAT); } @@ -8335,6 +8336,27 @@ VOID MethodTableBuilder::PlaceInstanceFields(MethodTable** pByValueClassCache) break; } + case EEClassLayoutInfo::LayoutType::CUnion: + { + if (!pParentMT->IsValueTypeClass() + || hasGCFields + || bmtFP->fIsByRefLikeType + || isAutoLayoutOrHasAutoLayoutField) + { + // CUnion layout types can't have a parent type, GC fields + // byreflike types, or auto layout fields. + BuildMethodTableThrowException(IDS_CLASSLOAD_BADFORMAT); + } + + // Explicit size is not used for CUnion layout. + pLayoutInfo->SetHasExplicitSize(FALSE); + // CUnion layouts are always blittable + pLayoutInfo->SetIsBlittable(TRUE); + + HandleCUnionLayout(pByValueClassCache); + break; + } + default: UNREACHABLE(); break; @@ -8824,6 +8846,33 @@ VOID MethodTableBuilder::HandleCStructLayout(MethodTable** pByValueClassCache) } } +VOID MethodTableBuilder::HandleCUnionLayout(MethodTable** pByValueClassCache) +{ + STANDARD_VM_CONTRACT; + + _ASSERTE(HasLayout()); + + EEClassLayoutInfo* pLayoutInfo = GetLayoutInfo(); + + CONSISTENCY_CHECK(pLayoutInfo != nullptr); + + bmtFP->NumInstanceFieldBytes = pLayoutInfo->InitializeCUnionFieldLayout( + GetHalfBakedClass()->GetFieldDescList(), + pByValueClassCache, + bmtEnumFields->dwNumDeclaredFields + ); + + if (bmtFP->NumInlineArrayElements != 0) + { + BuildMethodTableThrowException(IDS_CLASSLOAD_BADFORMAT); + } + + if (pLayoutInfo->IsZeroSized()) + { + BuildMethodTableThrowException(IDS_CLASSLOAD_BADFORMAT); + } +} + //******************************************************************************* // this accesses the field size which is temporarily stored in m_pMTOfEnclosingClass // during class loading. Don't use any other time @@ -12572,6 +12621,10 @@ BOOL HasLayoutMetadata(Assembly* pAssembly, IMDInternalImport* pInternalImport, { *pLayoutType = EEClassLayoutInfo::LayoutType::CStruct; } + else if (kind == CorExtendedLayoutKind::CUnion) + { + *pLayoutType = EEClassLayoutInfo::LayoutType::CUnion; + } else { pAssembly->ThrowTypeLoadException(pInternalImport, cl, IDS_CLASSLOAD_BADFORMAT); diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index 780d042e5e4ec0..7f6a2baf94f064 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -3000,6 +3000,9 @@ class MethodTableBuilder VOID HandleCStructLayout( MethodTable **); + VOID HandleCUnionLayout( + MethodTable **); + VOID CheckForHFA(MethodTable ** pByValueClassCache); VOID CheckForNativeHFA(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ExtendedLayoutKind.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ExtendedLayoutKind.cs index 3dc246f0c8427c..54aa65361dd945 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ExtendedLayoutKind.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ExtendedLayoutKind.cs @@ -12,5 +12,11 @@ public enum ExtendedLayoutKind /// The value type should have its fields laid out in accordance with the C language struct layout rules. /// CStruct = 0, + + /// + /// The value type should have its fields laid out like a C union, where all fields are placed at offset 0. + /// This layout only supports value types without GC pointers. + /// + CUnion = 1, } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 311fc1016aa725..2acb5759253fd7 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -14534,6 +14534,7 @@ public ExtendedLayoutAttribute(System.Runtime.InteropServices.ExtendedLayoutKind public enum ExtendedLayoutKind { CStruct = 0, + CUnion = 1, } public partial class ExternalException : System.SystemException { diff --git a/src/mono/mono/metadata/class-init.c b/src/mono/mono/metadata/class-init.c index 22da5d35d80c08..f1022310a938a8 100644 --- a/src/mono/mono/metadata/class-init.c +++ b/src/mono/mono/metadata/class-init.c @@ -2104,7 +2104,8 @@ validate_struct_fields_overlaps (guint8 *layout_check, int layout_size, MonoClas } typedef enum _ExtendedLayoutKind { - EXTENDED_LAYOUT_KIND_CSTRUCT = 0 + EXTENDED_LAYOUT_KIND_CSTRUCT = 0, + EXTENDED_LAYOUT_KIND_CUNION = 1 } ExtendedLayoutKind; /* @@ -2484,6 +2485,11 @@ mono_class_layout_fields (MonoClass *klass, int base_instance_size, int packing_ goto cleanup; } + if (m_class_is_byreflike(klass)) { + if (mono_class_set_type_load_failure (klass, "CStruct type cannot be ByRefLike.")) + goto cleanup; + } + mono_class_setup_fields (klass->parent); if (mono_class_set_type_load_failure_causedby_class (klass, klass->parent, "Cannot initialize parent class")) goto cleanup; @@ -2554,6 +2560,87 @@ mono_class_layout_fields (MonoClass *klass, int base_instance_size, int packing_ } break; } + else if (extended_layout_kind == EXTENDED_LAYOUT_KIND_CUNION) { + if (!m_class_is_valuetype(klass)) { + if (mono_class_set_type_load_failure (klass, "CUnion type must be value type.")) + goto cleanup; + } + + if (m_class_is_byreflike(klass)) { + if (mono_class_set_type_load_failure (klass, "CStruct type cannot be ByRefLike.")) + goto cleanup; + } + + mono_class_setup_fields (klass->parent); + if (mono_class_set_type_load_failure_causedby_class (klass, klass->parent, "Cannot initialize parent class")) + goto cleanup; + + real_size = klass->parent->instance_size; + + if (top == 0) { + /* Empty unions are not allowed */ + if (mono_class_set_type_load_failure (klass, "CUnion type cannot be empty.")) + goto cleanup; + } + + if (any_field_has_auto_layout) { + if (mono_class_set_type_load_failure (klass, "CUnion type cannot have AutoLayout fields.")) + goto cleanup; + } + + klass->blittable = TRUE; + + guint32 max_field_size = 0; + + for (i = 0; i < top; i++){ + gint32 align; + guint32 size; + MonoType *ftype; + + field = &klass->fields [i]; + + if (mono_field_is_deleted (field)) + continue; + if (field->type->attrs & FIELD_ATTRIBUTE_STATIC) + continue; + + ftype = mono_type_get_underlying_type (field->type); + ftype = mono_type_get_basic_type_from_generic (ftype); + + if ((top == 1) && (instance_size == MONO_ABI_SIZEOF (MonoObject)) && + (strcmp (mono_field_get_name (field), "$PRIVATE$") == 0)) { + /* This field is a hack inserted by MCS to empty structures */ + continue; + } + + size = mono_type_size (field->type, &align); + + if (type_has_references (klass, ftype)) + if (mono_class_set_type_load_failure (klass, "CUnion type must not have reference fields.")) + goto cleanup; + + min_align = MAX (align, min_align); + /* All fields are placed at offset 0 in a union */ + field_offsets [i] = real_size; + + /* Track maximum field size */ + if (size > max_field_size) + max_field_size = size; + } + + if (max_field_size == 0) { + /* No real instance fields were found for the union */ + if (mono_class_set_type_load_failure (klass, "CUnion type cannot be empty.")) + goto cleanup; + } + instance_size = real_size + max_field_size; + + if (instance_size & (min_align - 1)) { + instance_size += min_align - 1; + instance_size &= ~(min_align - 1); + } + break; + } else { mono_class_set_type_load_failure (klass, "Unknown extended layout kind '%d'.", extended_layout_kind); goto cleanup; diff --git a/src/tests/Loader/classloader/ExtendedLayout/CStruct.cs b/src/tests/Loader/classloader/ExtendedLayout/CStruct.cs index 8aa59110e4f0f7..11fd8801b14f06 100644 --- a/src/tests/Loader/classloader/ExtendedLayout/CStruct.cs +++ b/src/tests/Loader/classloader/ExtendedLayout/CStruct.cs @@ -102,4 +102,10 @@ public static void Pack_Ignored() Assert.Equal(4, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.b))); } + + [Fact] + public static void ByRefLike_ThrowTypeLoadException() + { + Assert.Throws(() => typeof(CStructByRefLike)); + } } diff --git a/src/tests/Loader/classloader/ExtendedLayout/CUnion.cs b/src/tests/Loader/classloader/ExtendedLayout/CUnion.cs new file mode 100644 index 00000000000000..1cc5583fca0dc0 --- /dev/null +++ b/src/tests/Loader/classloader/ExtendedLayout/CUnion.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xunit; + +namespace ExtendedLayoutTests; + +public static class CUnionTests +{ + [Fact] + public static void BlittablePrimitiveFieldsLayout() + { + CUnionBlittablePrimitiveFields c = default; + + // All fields should be at offset 0, size should be max field size (4 bytes for int/float) + Assert.Equal(4, Unsafe.SizeOf()); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.a))); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.b))); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref c.c)); + } + + [Fact] + public static void NonBlittableUnmanagedPrimitiveFields_TreatedAsBlittable() + { + CUnionNonBlittablePrimitiveFields c = default; + Assert.Equal(Unsafe.SizeOf(), Marshal.SizeOf()); + // Size should be 2 (char is 2 bytes, bool is 1 byte) + Assert.Equal(2, Unsafe.SizeOf()); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.b))); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.c))); + } + + [Fact] + public static void ReferenceFields_ThrowTypeLoadException() + { + Assert.Throws(() => typeof(CUnionWithReferenceFields)); + + Assert.Throws(() => typeof(CUnionWithMixedFields)); + } + + [Fact] + public static void NestedCUnion() + { + CUnionCustomCUnionField nested = default; + + // Size should be the size of NestedCUnionType (8 bytes for int64) + Assert.Equal(8, Unsafe.SizeOf()); + } + + [Fact] + public static void NestedNonCUnionNonAuto() + { + CUnionCustomSeqStructField nested = default; + + Assert.Equal(4, Unsafe.SizeOf()); + } + + [Fact] + public static void NestedAutoLayout_ThrowTypeLoadException() + { + Assert.Throws(() => typeof(CUnionCustomAutoStructField)); + } + + [Fact] + public static void EmptyUnion() + { + Assert.Throws(() => typeof(EmptyCUnion)); + } + + [Fact] + public static void ExplicitOffsets_Ignored() + { + CUnionWithOffsets c = default; + + // Offset should be 0 regardless of what's specified + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.a))); + } + + [Fact] + public static void ExplicitSize_Ignored() + { + // Size should be 4 (int size), not 12 + Assert.Equal(4, Unsafe.SizeOf()); + } + + [Fact] + public static void Pack_Ignored() + { + // Size should be 4 (max of int8=1 and int32=4), aligned to 4 + Assert.Equal(4, Unsafe.SizeOf()); + + CUnionWithPack c = default; + + // Both fields should be at offset 0 + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref c.a)); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.b))); + } + + [Fact] + public static void MixedSizes_SizeIsLargestField() + { + CUnionMixedSizes c = default; + + // Size should be 8 (int64 is the largest field) + Assert.Equal(8, Unsafe.SizeOf()); + + // All fields should be at offset 0 + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref c.byteField)); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.shortField))); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.intField))); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.longField))); + } + + [Fact] + public static void TwoInts_ShareSameMemory() + { + CUnionTwoInts c = default; + + // Size should be 4 (both fields are int32) + Assert.Equal(4, Unsafe.SizeOf()); + + // Both fields should be at offset 0 + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.first))); + Assert.Equal(0, Unsafe.ByteOffset(ref Unsafe.As(ref c), ref Unsafe.As(ref c.second))); + + // Writing to one field should affect the other + c.first = 0x55556666; + Assert.Equal(0x55556666, c.second); + + c.second = 0x77778888; + Assert.Equal(0x77778888, c.first); + } + + [Fact] + public static void ByRefLike_ThrowTypeLoadException() + { + Assert.Throws(() => typeof(CUnionByRefLike)); + } +} diff --git a/src/tests/Loader/classloader/ExtendedLayout/ExtendedLayout.csproj b/src/tests/Loader/classloader/ExtendedLayout/ExtendedLayout.csproj index 311ecf34abbbdf..b1c70f46fc7e37 100644 --- a/src/tests/Loader/classloader/ExtendedLayout/ExtendedLayout.csproj +++ b/src/tests/Loader/classloader/ExtendedLayout/ExtendedLayout.csproj @@ -9,6 +9,7 @@ + diff --git a/src/tests/Loader/classloader/ExtendedLayout/ExtendedLayoutTypes.il b/src/tests/Loader/classloader/ExtendedLayout/ExtendedLayoutTypes.il index 9e732db94a45c1..819c45c7f3393e 100644 --- a/src/tests/Loader/classloader/ExtendedLayout/ExtendedLayoutTypes.il +++ b/src/tests/Loader/classloader/ExtendedLayout/ExtendedLayoutTypes.il @@ -158,3 +158,166 @@ .pack 1 } + +// ByRefLike types with ExtendedLayout should fail to load +.class flags(0x18) public value beforefieldinit CStructByRefLike +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000000) // CStruct + } + + .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = { } + + .field public int32 a +} + +// CUnion types below (can be moved to C# once we have ExtendedLayout support in the C# compiler) + +.class flags(0x18) public value beforefieldinit CUnionBlittablePrimitiveFields +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public int32 a + .field public float32 b + .field public uint8 c +} + +.class flags(0x18) public value beforefieldinit CUnionNonBlittablePrimitiveFields +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public bool b + .field public char c +} + +.class flags(0x18) public value beforefieldinit CUnionWithReferenceFields +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public class [System.Runtime]System.String a +} + +.class flags(0x18) public value beforefieldinit CUnionWithMixedFields +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public int32 a + .field public class [System.Runtime]System.String b +} + +.class flags(0x18) public value beforefieldinit NestedCUnionType +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + .field public int32 x + .field public int64 y +} + +.class flags(0x18) public value beforefieldinit CUnionCustomCUnionField +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public valuetype NestedCUnionType x +} + +.class flags(0x18) public value beforefieldinit CUnionCustomSeqStructField +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public valuetype NestedSequentialType y +} + +.class flags(0x18) public value beforefieldinit CUnionCustomAutoStructField +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public valuetype NestedAutoLayoutType y +} + +.class flags(0x18) public value beforefieldinit EmptyCUnion +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } +} + +.class flags(0x18) public value beforefieldinit CUnionWithOffsets +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field [4] public int32 a +} + +.class flags(0x18) public value beforefieldinit CUnionWithSize +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public int32 a + + .size 12 +} + +.class flags(0x18) public value beforefieldinit CUnionWithPack +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public uint8 a + .field public int32 b + + .pack 1 +} + +// CUnion types with mixed field sizes for testing union semantics +.class flags(0x18) public value beforefieldinit CUnionMixedSizes +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public uint8 byteField + .field public int16 shortField + .field public int32 intField + .field public int64 longField +} + +.class flags(0x18) public value beforefieldinit CUnionTwoInts +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .field public int32 first + .field public int32 second +} + +.class flags(0x18) public value beforefieldinit CUnionByRefLike +{ + .custom instance void [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutAttribute::.ctor(valuetype [System.Runtime]System.Runtime.InteropServices.ExtendedLayoutKind) = { + int32(0x00000001) // CUnion + } + + .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = { } + + .field public int32 a +}