diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs index 2e4c5d034933e8..dc764971c2604d 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs @@ -150,8 +150,8 @@ Signature LazyCreateSignature() { Debug.Assert(parameters != null); StackAllocedArguments argStorage = default; - Span copyOfParameters = new(ref argStorage._arg0, argCount); - Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); + Span copyOfParameters = argStorage._args.AsSpan(argCount); + Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(argCount); StackAllocatedByRefs byrefStorage = default; #pragma warning disable CS8500 diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs index 3ed1028e564c06..0d25286564e343 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.CoreCLR.cs @@ -311,9 +311,9 @@ public override MethodImplAttributes GetMethodImplementationFlags() unsafe { StackAllocedArguments argStorage = default; - Span copyOfParameters = new(ref argStorage._arg0, 1); + Span copyOfParameters = argStorage._args.AsSpan(1); ReadOnlySpan parameters = new(in parameter); - Span shouldCopyBackParameters = new(ref argStorage._copyBack0, 1); + Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(1); StackAllocatedByRefs byrefStorage = default; #pragma warning disable 8500 diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index 62c867c3565403..21784fce2e8cf1 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -310,6 +310,10 @@ BEGIN IDS_CLASSLOAD_GENERICTYPE_RECURSIVE "Could not load type '%1' from assembly '%2' because it has recursive generic definition." IDS_CLASSLOAD_TOOMANYGENERICARGS "Could not load type '%1' from assembly '%2'. Internal limitation: Too many generic arguments." + IDS_CLASSLOAD_INLINE_ARRAY_FIELD_COUNT "InlineArrayAttribute requires that the target type has a single instance field. Type: '%1'. Assembly: '%2'." + IDS_CLASSLOAD_INLINE_ARRAY_LENGTH "InlineArrayAttribute requires that the length argument is greater than 0. Type: '%1'. Assembly: '%2'." + IDS_CLASSLOAD_INLINE_ARRAY_EXPLICIT "InlineArrayAttribute cannot be applied to a type with explicit layout. Type: '%1'. Assembly: '%2'." + #if FEATURE_COMINTEROP IDS_EE_CANNOTCAST_NOMARSHAL "The Windows Runtime Object can only be used in the threading context where it was created, because it implements INoMarshal or has MarshalingBehaviorAttribute(MarshalingType.None) set." #endif diff --git a/src/coreclr/dlls/mscorrc/resource.h b/src/coreclr/dlls/mscorrc/resource.h index 1903a00a7fac98..b65318ca02ad08 100644 --- a/src/coreclr/dlls/mscorrc/resource.h +++ b/src/coreclr/dlls/mscorrc/resource.h @@ -168,6 +168,10 @@ #define IDS_CLASSLOAD_TOOMANYGENERICARGS 0x17ab +#define IDS_CLASSLOAD_INLINE_ARRAY_FIELD_COUNT 0x17ac +#define IDS_CLASSLOAD_INLINE_ARRAY_LENGTH 0x17ad +#define IDS_CLASSLOAD_INLINE_ARRAY_EXPLICIT 0x17ae + #define IDS_DEBUG_USERBREAKPOINT 0x17b6 #define IDS_PERFORMANCEMON_FUNCNOTFOUND 0x17bb diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 28531e00bee5f5..58fed256c87245 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -852,11 +852,11 @@ enum CorInfoFlag CORINFO_FLG_ARRAY = 0x00080000, // class is an array class (initialized differently) CORINFO_FLG_OVERLAPPING_FIELDS = 0x00100000, // struct or class has fields that overlap (aka union) CORINFO_FLG_INTERFACE = 0x00200000, // it is an interface - CORINFO_FLG_DONT_DIG_FIELDS = 0x00400000, // don't ask field info, AOT can't rely on it (used for types outside of AOT compilation version bubble) + CORINFO_FLG_DONT_DIG_FIELDS = 0x00400000, // don't ask field info (used for types outside of AOT compilation version bubble) CORINFO_FLG_CUSTOMLAYOUT = 0x00800000, // does this struct have custom layout? CORINFO_FLG_CONTAINS_GC_PTR = 0x01000000, // does the class contain a gc ptr ? CORINFO_FLG_DELEGATE = 0x02000000, // is this a subclass of delegate or multicast delegate ? - // CORINFO_FLG_UNUSED = 0x04000000, + CORINFO_FLG_INDEXABLE_FIELDS = 0x04000000, // struct fields may be accessed via indexing (used for inline arrays) CORINFO_FLG_BYREF_LIKE = 0x08000000, // it is byref-like value type CORINFO_FLG_VARIANCE = 0x10000000, // MethodTable::HasVariance (sealed does *not* mean uncast-able) CORINFO_FLG_BEFOREFIELDINIT = 0x20000000, // Additional flexibility for when to run .cctor (see code:#ClassConstructionFlags) diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 94dcf70963a413..711675ee7038db 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4369,6 +4369,11 @@ inline static bool StructHasDontDigFieldsFlagSet(DWORD attribs) return ((attribs & CORINFO_FLG_DONT_DIG_FIELDS) != 0); } +inline static bool StructHasIndexableFields(DWORD attribs) +{ + return ((attribs & CORINFO_FLG_INDEXABLE_FIELDS) != 0); +} + //------------------------------------------------------------------------------ // DEBUG_DESTROY_NODE: sets value of tree to garbage to catch extra references // diff --git a/src/coreclr/jit/importercalls.cpp b/src/coreclr/jit/importercalls.cpp index f9354f947865ff..9b18d14a12f333 100644 --- a/src/coreclr/jit/importercalls.cpp +++ b/src/coreclr/jit/importercalls.cpp @@ -2893,7 +2893,7 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis, GenTree* indexClone = nullptr; GenTree* ptrToSpanClone = nullptr; assert(genActualType(index) == TYP_INT); - assert(ptrToSpan->TypeGet() == TYP_BYREF); + assert(ptrToSpan->TypeGet() == TYP_BYREF || ptrToSpan->TypeGet() == TYP_I_IMPL); #if defined(DEBUG) if (verbose) diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 7bca65eda06b80..bb97eaf8d988a5 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1772,6 +1772,11 @@ bool Compiler::StructPromotionHelper::CanPromoteStructType(CORINFO_CLASS_HANDLE return false; } + if (StructHasIndexableFields(typeFlags)) + { + return false; + } + // Don't struct promote if we have an CUSTOMLAYOUT flag on an HFA type if (StructHasCustomLayout(typeFlags) && compiler->IsHfa(typeHnd)) { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/TypeLoaderExceptionHelper.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/TypeLoaderExceptionHelper.cs index adedfbf710e2fe..76b714c11a3946 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/TypeLoaderExceptionHelper.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/TypeLoaderExceptionHelper.cs @@ -80,6 +80,12 @@ private static string GetFormatString(ExceptionStringID id) return SR.ClassLoad_ExplicitLayout; case ExceptionStringID.ClassLoadRankTooLarge: return SR.ClassLoad_RankTooLarge; + case ExceptionStringID.ClassLoadInlineArrayFieldCount: + return SR.ClassLoad_InlineArrayFieldCount; + case ExceptionStringID.ClassLoadInlineArrayLength: + return SR.ClassLoad_InlineArrayLength; + case ExceptionStringID.ClassLoadInlineArrayExplicit: + return SR.ClassLoad_InlineArrayExplicit; case ExceptionStringID.InvalidProgramDefault: return SR.InvalidProgram_Default; case ExceptionStringID.InvalidProgramSpecific: diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs index 4f0106980837d4..bd260497b6e585 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/DynamicInvokeInfo.cs @@ -482,27 +482,17 @@ private unsafe object ReturnTransform(ref byte byref, bool wrapInTargetInvocatio // and pass it to CheckArguments(). // For argument count > MaxStackAllocArgCount, do a stackalloc of void* pointers along with // GCReportingRegistration to safely track references. - [StructLayout(LayoutKind.Sequential)] + [InlineArray(MaxStackAllocArgCount)] private ref struct StackAllocedArguments { internal object? _arg0; -#pragma warning disable CA1823, CS0169, IDE0051 // accessed via 'CheckArguments' ref arithmetic - private object? _arg1; - private object? _arg2; - private object? _arg3; -#pragma warning restore CA1823, CS0169, IDE0051 } // Helper struct to avoid intermediate IntPtr[] allocation and RegisterForGCReporting in calls to the native reflection stack. - [StructLayout(LayoutKind.Sequential)] + [InlineArray(MaxStackAllocArgCount)] private ref struct StackAllocatedByRefs { internal ref byte _arg0; -#pragma warning disable CA1823, CS0169, IDE0051 // accessed via 'CheckArguments' ref arithmetic - private ref byte _arg1; - private ref byte _arg2; - private ref byte _arg3; -#pragma warning restore CA1823, CS0169, IDE0051 } } } diff --git a/src/coreclr/tools/Common/Internal/Runtime/GCDescEncoder.cs b/src/coreclr/tools/Common/Internal/Runtime/GCDescEncoder.cs index 207f67e1522cbf..ae148d0f2298ea 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/GCDescEncoder.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/GCDescEncoder.cs @@ -28,7 +28,7 @@ public static int GetGCDescSize(TypeDesc type) } else if (elementType.IsDefType) { - var defType = (DefType)elementType; + var defType = (MetadataType)elementType; if (defType.ContainsGCPointers) { GCPointerMap pointerMap = GCPointerMap.FromInstanceLayout(defType); @@ -48,7 +48,7 @@ public static int GetGCDescSize(TypeDesc type) } else { - var defType = (DefType)type; + var defType = (MetadataType)type; if (defType.ContainsGCPointers) { int numSeries = GCPointerMap.FromInstanceLayout(defType).NumSeries; @@ -84,7 +84,7 @@ public static void EncodeGCDesc(ref T builder, TypeDesc type) } else if (elementType.IsDefType) { - var elementDefType = (DefType)elementType; + var elementDefType = (MetadataType)elementType; if (elementDefType.ContainsGCPointers) { GCPointerMap pointerMap = GCPointerMap.FromInstanceLayout(elementDefType); @@ -101,7 +101,7 @@ public static void EncodeGCDesc(ref T builder, TypeDesc type) } else { - var defType = (DefType)type; + var defType = (MetadataType)type; if (defType.ContainsGCPointers) { // Computing the layout for the boxed version if this is a value type. diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index fbb17f280b5e56..49468f41facbef 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -2010,6 +2010,9 @@ private uint getClassAttribsInternal(TypeDesc type) if (metadataType.IsUnsafeValueType) result |= CorInfoFlag.CORINFO_FLG_UNSAFE_VALUECLASS; + + if (metadataType.IsInlineArray) + result |= CorInfoFlag.CORINFO_FLG_INDEXABLE_FIELDS; } if (type.IsCanonicalSubtype(CanonicalFormKind.Any)) @@ -2240,9 +2243,10 @@ private int MarkGcField(byte* gcPtrs, CorInfoGCType gcType) } } - private int GatherClassGCLayout(TypeDesc type, byte* gcPtrs) + private int GatherClassGCLayout(MetadataType type, byte* gcPtrs) { int result = 0; + bool isInlineArray = type.IsInlineArray; foreach (var field in type.GetFields()) { @@ -2278,12 +2282,31 @@ private int GatherClassGCLayout(TypeDesc type, byte* gcPtrs) if (gcType == CorInfoGCType.TYPE_GC_OTHER) { - result += GatherClassGCLayout(fieldType, fieldGcPtrs); + result += GatherClassGCLayout((MetadataType)fieldType, fieldGcPtrs); } else { result += MarkGcField(fieldGcPtrs, gcType); } + + if (isInlineArray) + { + if (result > 0) + { + Debug.Assert(field.Offset.AsInt == 0); + int totalLayoutSize = type.GetElementSize().AsInt / PointerSize; + int elementLayoutSize = fieldType.GetElementSize().AsInt / PointerSize; + int gcPointersInElement = result; + for (int offset = elementLayoutSize; offset < totalLayoutSize; offset += elementLayoutSize) + { + Buffer.MemoryCopy(gcPtrs, gcPtrs + offset, elementLayoutSize, elementLayoutSize); + result += gcPointersInElement; + } + } + + // inline array has only one element field + break; + } } return result; } @@ -2292,7 +2315,7 @@ private uint getClassGClayout(CORINFO_CLASS_STRUCT_* cls, byte* gcPtrs) { uint result = 0; - DefType type = (DefType)HandleToObject(cls); + MetadataType type = (MetadataType)HandleToObject(cls); int pointerSize = PointerSize; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index a3d920666ed494..b2b57e7970a5ed 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -606,7 +606,7 @@ public enum CorInfoFlag : uint CORINFO_FLG_CUSTOMLAYOUT = 0x00800000, // does this struct have custom layout? CORINFO_FLG_CONTAINS_GC_PTR = 0x01000000, // does the class contain a gc ptr ? CORINFO_FLG_DELEGATE = 0x02000000, // is this a subclass of delegate or multicast delegate ? - // CORINFO_FLG_UNUSED = 0x04000000, + CORINFO_FLG_INDEXABLE_FIELDS = 0x04000000, // struct fields may be accessed via indexing (used for inline arrays) CORINFO_FLG_BYREF_LIKE = 0x08000000, // it is byref-like value type CORINFO_FLG_VARIANCE = 0x10000000, // MethodTable::HasVariance (sealed does *not* mean uncast-able) CORINFO_FLG_BEFOREFIELDINIT = 0x20000000, // Additional flexibility for when to run .cctor (see code:#ClassConstructionFlags) diff --git a/src/coreclr/tools/Common/TypeSystem/Canon/CanonTypes.Metadata.cs b/src/coreclr/tools/Common/TypeSystem/Canon/CanonTypes.Metadata.cs index 9e657c1e6546f4..320491b70319aa 100644 --- a/src/coreclr/tools/Common/TypeSystem/Canon/CanonTypes.Metadata.cs +++ b/src/coreclr/tools/Common/TypeSystem/Canon/CanonTypes.Metadata.cs @@ -57,6 +57,12 @@ public override bool HasCustomAttribute(string attributeNamespace, string attrib { return false; } + + public override int GetInlineArrayLength() + { + Debug.Fail("if this can be an inline array, implement GetInlineArrayLength"); + throw new InvalidOperationException(); + } } internal sealed partial class CanonType diff --git a/src/coreclr/tools/Common/TypeSystem/CodeGen/TypeDesc.CodeGen.cs b/src/coreclr/tools/Common/TypeSystem/CodeGen/TypeDesc.CodeGen.cs index 860285c10f2341..c14150dc6a28e9 100644 --- a/src/coreclr/tools/Common/TypeSystem/CodeGen/TypeDesc.CodeGen.cs +++ b/src/coreclr/tools/Common/TypeSystem/CodeGen/TypeDesc.CodeGen.cs @@ -25,5 +25,11 @@ partial void AddComputedIntrinsicFlag(ref TypeFlags flags) if (_typeDef.IsIntrinsic) flags |= TypeFlags.IsIntrinsic; } + + partial void AddComputedInlineArrayFlag(ref TypeFlags flags) + { + if (((MetadataType)_typeDef).IsInlineArray) + flags |= TypeFlags.IsInlineArray; + } } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs index d5824a3d2a58f0..4c55874d7999fb 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/ExceptionStringID.cs @@ -16,6 +16,10 @@ public enum ExceptionStringID ClassLoadValueClassTooLarge, ClassLoadRankTooLarge, + ClassLoadInlineArrayFieldCount, + ClassLoadInlineArrayLength, + ClassLoadInlineArrayExplicit, + // MissingMethodException MissingMethod, diff --git a/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedType.Metadata.cs b/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedType.Metadata.cs index d917b342e56a4b..1c642b46ebfa7c 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedType.Metadata.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedType.Metadata.cs @@ -86,6 +86,11 @@ public override bool HasCustomAttribute(string attributeNamespace, string attrib return _typeDef.HasCustomAttribute(attributeNamespace, attributeName); } + public override int GetInlineArrayLength() + { + return _typeDef.GetInlineArrayLength(); + } + public override MetadataType GetNestedType(string name) { // Return the result from the typical type definition. diff --git a/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedType.cs b/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedType.cs index 0df6089d42320d..d6fb23db52449b 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedType.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/InstantiatedType.cs @@ -74,6 +74,10 @@ public override DefType BaseType // will provide an implementation that adds the flag if necessary. partial void AddComputedIntrinsicFlag(ref TypeFlags flags); + // Type system implementations that support the notion of inline arrays + // will provide an implementation that adds the flag if necessary. + partial void AddComputedInlineArrayFlag(ref TypeFlags flags); + protected override TypeFlags ComputeTypeFlags(TypeFlags mask) { TypeFlags flags = 0; @@ -106,6 +110,8 @@ protected override TypeFlags ComputeTypeFlags(TypeFlags mask) if (_typeDef.IsByRefLike) flags |= TypeFlags.IsByRefLike; + AddComputedInlineArrayFlag(ref flags); + AddComputedIntrinsicFlag(ref flags); } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs index f3b7b9442f5bbe..0fc9064fb00a81 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; - using Debug = System.Diagnostics.Debug; namespace Internal.TypeSystem @@ -358,6 +357,12 @@ protected ComputedInstanceFieldLayout ComputeExplicitFieldLayout(MetadataType ty layoutMetadata.Size, out instanceByteSizeAndAlignment); + // inline array cannot have explicit layout + if (type.IsInlineArray) + { + ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadInlineArrayExplicit, type); + } + ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout { IsAutoLayoutOrHasAutoLayoutFields = hasAutoLayoutField, @@ -429,6 +434,11 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType layoutMetadata.Size, out instanceByteSizeAndAlignment); + if (type.IsInlineArray) + { + AdjustForInlineArray(type, numInstanceFields, ref instanceByteSizeAndAlignment, ref instanceSizeAndAlignment); + } + ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout { IsAutoLayoutOrHasAutoLayoutFields = hasAutoLayoutField, @@ -444,6 +454,45 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType return computedLayout; } + private static void AdjustForInlineArray( + MetadataType type, + int instanceFieldCount, + ref SizeAndAlignment instanceByteSizeAndAlignment, + ref SizeAndAlignment instanceSizeAndAlignment) + { + int repeat = type.GetInlineArrayLength(); + + if (repeat <= 0) + { + ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadInlineArrayLength, type); + } + + if (instanceFieldCount != 1) + { + ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadInlineArrayFieldCount, type); + } + + if (!instanceByteSizeAndAlignment.Size.IsIndeterminate) + { + long size = instanceByteSizeAndAlignment.Size.AsInt; + size *= repeat; + + // limit the max size of array instance to 1MiB + const int maxSize = 1024 * 1024; + if (size > maxSize) + { + ThrowHelper.ThrowTypeLoadException(ExceptionStringID.ClassLoadValueClassTooLarge, type); + } + + instanceByteSizeAndAlignment.Size = new LayoutInt((int)size); + } + + if (!instanceSizeAndAlignment.Size.IsIndeterminate) + { + instanceSizeAndAlignment.Size = new LayoutInt(instanceSizeAndAlignment.Size.AsInt * repeat); + } + } + protected virtual void AlignBaseOffsetIfNecessary(MetadataType type, ref LayoutInt baseOffset, bool requiresAlign8, bool requiresAlignedBase) { } @@ -746,6 +795,11 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, classLayoutSize: 0, byteCount: out instanceByteSizeAndAlignment); + if (type.IsInlineArray) + { + AdjustForInlineArray(type, numInstanceFields, ref instanceByteSizeAndAlignment, ref instanceSizeAndAlignment); + } + ComputedInstanceFieldLayout computedLayout = new ComputedInstanceFieldLayout { IsAutoLayoutOrHasAutoLayoutFields = true, diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataType.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataType.cs index 72b0aeadba86c4..722aa66a878f01 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataType.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataType.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; namespace Internal.TypeSystem { @@ -90,6 +91,19 @@ public virtual bool IsModuleType /// doesn't exist. /// public abstract MetadataType GetNestedType(string name); + + /// + /// Gets a value indicating whether this is an inline array type + /// + public bool IsInlineArray + { + get + { + return (GetTypeFlags(TypeFlags.IsInlineArray | TypeFlags.AttributeCacheComputed) & TypeFlags.IsInlineArray) != 0; + } + } + + public abstract int GetInlineArrayLength(); } public struct ClassLayoutMetadata diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx index 6abeabd7b0ea1d..6a70612fc391bf 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx +++ b/src/coreclr/tools/Common/TypeSystem/Common/Properties/Resources.resx @@ -129,6 +129,15 @@ Failed to load type '{0}' from assembly '{1}' because of field offset '{2}' + + InlineArrayAttribute requires that the target type has a single instance field. Type: '{0}'. Assembly: '{1}'. + + + InlineArrayAttribute requires that the length argument is greater than 0. Type: '{0}'. Assembly: '{1}'. + + + InlineArrayAttribute cannot be applied to a type with explicit layout. Type: '{0}'. Assembly: '{1}'.' + Array of type '{0}' from assembly '{1}' cannot be created because base value type is too large diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeFlags.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeFlags.cs index 6331f50a891b28..2f5ced2caf73f0 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeFlags.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeFlags.cs @@ -57,10 +57,11 @@ public enum TypeFlags HasFinalizer = 0x2000, IsByRefLike = 0x04000, - AttributeCacheComputed = 0x08000, + IsInlineArray = 0x08000, IsIntrinsic = 0x10000, + AttributeCacheComputed = 0x20000, - IsIDynamicInterfaceCastable = 0x20000, - IsIDynamicInterfaceCastableComputed = 0x40000, + IsIDynamicInterfaceCastable = 0x40000, + IsIDynamicInterfaceCastableComputed = 0x80000, } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/GCPointerMap.Algorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/GCPointerMap.Algorithm.cs index eca01c4b3a4587..a03addd6632b3a 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/GCPointerMap.Algorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/GCPointerMap.Algorithm.cs @@ -10,7 +10,7 @@ public partial struct GCPointerMap /// /// Computes the GC pointer map for the instance fields of . /// - public static GCPointerMap FromInstanceLayout(DefType type) + public static GCPointerMap FromInstanceLayout(MetadataType type) { Debug.Assert(type.ContainsGCPointers); @@ -20,15 +20,21 @@ public static GCPointerMap FromInstanceLayout(DefType type) return builder.ToGCMap(); } - private static void FromInstanceLayoutHelper(ref GCPointerMapBuilder builder, DefType type) + private static void FromInstanceLayoutHelper(ref GCPointerMapBuilder builder, MetadataType type) { if (!type.IsValueType && type.HasBaseType) { - DefType baseType = type.BaseType; + MetadataType baseType = (MetadataType)type.BaseType; GCPointerMapBuilder baseLayoutBuilder = builder.GetInnerBuilder(0, baseType.InstanceByteCount.AsInt); FromInstanceLayoutHelper(ref baseLayoutBuilder, baseType); } + int repeat = 1; + if (type.IsInlineArray) + { + repeat = ((MetadataType)type).GetInlineArrayLength(); + } + foreach (FieldDesc field in type.GetFields()) { if (field.IsStatic) @@ -37,16 +43,23 @@ private static void FromInstanceLayoutHelper(ref GCPointerMapBuilder builder, De TypeDesc fieldType = field.FieldType; if (fieldType.IsGCPointer) { - builder.MarkGCPointer(field.Offset.AsInt); + for (int i = 0; i < repeat; i++) + { + builder.MarkGCPointer(field.Offset.AsInt + type.Context.Target.PointerSize * i); + } } else if (fieldType.IsValueType) { - var fieldDefType = (DefType)fieldType; + var fieldDefType = (MetadataType)fieldType; if (fieldDefType.ContainsGCPointers) { - GCPointerMapBuilder innerBuilder = - builder.GetInnerBuilder(field.Offset.AsInt, fieldDefType.InstanceByteCount.AsInt); - FromInstanceLayoutHelper(ref innerBuilder, fieldDefType); + for (int i = 0; i < repeat; i++) + { + int fieldSize = fieldDefType.InstanceByteCount.AsInt; + GCPointerMapBuilder innerBuilder = + builder.GetInnerBuilder(field.Offset.AsInt + fieldSize * i, fieldSize); + FromInstanceLayoutHelper(ref innerBuilder, fieldDefType); + } } } } @@ -55,7 +68,7 @@ private static void FromInstanceLayoutHelper(ref GCPointerMapBuilder builder, De /// /// Computes the GC pointer map of the GC static region of the type. /// - public static GCPointerMap FromStaticLayout(DefType type) + public static GCPointerMap FromStaticLayout(MetadataType type) { GCPointerMapBuilder builder = new GCPointerMapBuilder(type.GCStaticFieldSize.AsInt, type.Context.Target.PointerSize); @@ -73,7 +86,7 @@ public static GCPointerMap FromStaticLayout(DefType type) else { Debug.Assert(fieldType.IsValueType); - var fieldDefType = (DefType)fieldType; + var fieldDefType = (MetadataType)fieldType; if (fieldDefType.ContainsGCPointers) { GCPointerMapBuilder innerBuilder = @@ -90,7 +103,7 @@ public static GCPointerMap FromStaticLayout(DefType type) /// /// Computes the GC pointer map of the thread static region of the type. /// - public static GCPointerMap FromThreadStaticLayout(DefType type) + public static GCPointerMap FromThreadStaticLayout(MetadataType type) { GCPointerMapBuilder builder = new GCPointerMapBuilder(type.ThreadGcStaticFieldSize.AsInt, type.Context.Target.PointerSize); @@ -106,7 +119,7 @@ public static GCPointerMap FromThreadStaticLayout(DefType type) } else if (fieldType.IsValueType) { - var fieldDefType = (DefType)fieldType; + var fieldDefType = (MetadataType)fieldType; if (fieldDefType.ContainsGCPointers) { GCPointerMapBuilder innerBuilder = diff --git a/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs index 348b2f6e79c5a8..36944af4eab8f6 100644 --- a/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs +++ b/src/coreclr/tools/Common/TypeSystem/Ecma/EcmaType.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -260,6 +261,13 @@ protected override TypeFlags ComputeTypeFlags(TypeFlags mask) if (stringComparer.Equals(nameHandle, "IntrinsicAttribute") && stringComparer.Equals(namespaceHandle, "System.Runtime.CompilerServices")) flags |= TypeFlags.IsIntrinsic; + + if (isValueType && + stringComparer.Equals(nameHandle, "InlineArrayAttribute") && + stringComparer.Equals(namespaceHandle, "System.Runtime.CompilerServices")) + { + flags |= TypeFlags.IsInlineArray; + } } } } @@ -525,6 +533,18 @@ public override bool HasCustomAttribute(string attributeNamespace, string attrib attributeNamespace, attributeName).IsNil; } + public override int GetInlineArrayLength() + { + Debug.Assert(this.IsInlineArray); + + var attr = MetadataReader.GetCustomAttribute(MetadataReader.GetCustomAttributeHandle(_typeDefinition.GetCustomAttributes(), + "System.Runtime.CompilerServices", "InlineArrayAttribute")); + + var value = attr.DecodeValue(new CustomAttributeTypeProvider(_module)).FixedArguments[0].Value; + + return value is int intValue ? intValue : 0; + } + public override ClassLayoutMetadata GetClassLayout() { TypeLayout layout = _typeDefinition.GetLayout(); diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/InlineArrayType.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/InlineArrayType.cs index 512cad4be34eff..78fd7bc25a4d2c 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/InlineArrayType.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/InlineArrayType.cs @@ -248,6 +248,12 @@ protected override TypeFlags ComputeTypeFlags(TypeFlags mask) return flags; } + public override int GetInlineArrayLength() + { + Debug.Fail("when this is backed by an actual inline array, implement GetInlineArrayLength"); + throw new InvalidOperationException(); + } + private void InitializeMethods() { MethodDesc[] methods = new MethodDesc[] { diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/NativeStructType.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/NativeStructType.cs index 9f5ee9edcb96fd..fcfee2dbab9981 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/NativeStructType.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/NativeStructType.cs @@ -68,6 +68,12 @@ public override bool IsExplicitLayout } } + public override int GetInlineArrayLength() + { + Debug.Fail("if this can be an inline array, implement GetInlineArrayLength"); + throw new InvalidOperationException(); + } + public override bool IsSequentialLayout { get diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/PInvokeDelegateWrapper.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/PInvokeDelegateWrapper.cs index c7577641566001..3708dff16c7d17 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/PInvokeDelegateWrapper.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/PInvokeDelegateWrapper.cs @@ -231,6 +231,12 @@ protected override TypeFlags ComputeTypeFlags(TypeFlags mask) return flags; } + public override int GetInlineArrayLength() + { + Debug.Fail("if this can be an inline array, implement GetInlineArrayLength"); + throw new InvalidOperationException(); + } + private MethodDesc[] _methods; private void InitializeMethods() diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.BoxedTypes.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.BoxedTypes.cs index afc348ef9c89d6..9236e10d2c35a5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.BoxedTypes.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.BoxedTypes.cs @@ -273,6 +273,12 @@ private sealed partial class BoxedValueType : MetadataType, INonEmittableType public override DefType[] ExplicitlyImplementedInterfaces => Array.Empty(); public override TypeSystemContext Context => ValueTypeRepresented.Context; + public override int GetInlineArrayLength() + { + Debug.Fail("if this can be an inline array, implement GetInlineArrayLength"); + throw new InvalidOperationException(); + } + public BoxedValueType(ModuleDesc owningModule, MetadataType valuetype) { // BoxedValueType has the same genericness as the valuetype it's wrapping. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.GeneratedAssembly.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.GeneratedAssembly.cs index d18c44ec8706cc..aa2da8a6cd17b6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.GeneratedAssembly.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.GeneratedAssembly.cs @@ -213,6 +213,12 @@ public override bool IsSequentialLayout } } + public override int GetInlineArrayLength() + { + Debug.Fail("if this can be an inline array, implement GetInlineArrayLength"); + throw new InvalidOperationException(); + } + public override bool IsBeforeFieldInit { get diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs index 262467b79c6ade..62480431a8b375 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NativeLayoutVertexNode.cs @@ -961,7 +961,7 @@ private static TypeDesc GetActualTemplateTypeForType(NodeFactory factory, TypeDe private ISymbolNode GetStaticsNode(NodeFactory context, out BagElementKind staticsBagKind) { - DefType closestCanonDefType = (DefType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific); + MetadataType closestCanonDefType = (MetadataType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific); ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromStaticLayout(closestCanonDefType)); staticsBagKind = BagElementKind.GcStaticDesc; @@ -970,7 +970,7 @@ private ISymbolNode GetStaticsNode(NodeFactory context, out BagElementKind stati private ISymbolNode GetThreadStaticsNode(NodeFactory context, out BagElementKind staticsBagKind) { - DefType closestCanonDefType = (DefType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific); + MetadataType closestCanonDefType = (MetadataType)_type.GetClosestDefType().ConvertToCanonForm(CanonicalFormKind.Specific); ISymbolNode symbol = context.GCStaticEEType(GCPointerMap.FromThreadStaticLayout(closestCanonDefType)); staticsBagKind = BagElementKind.ThreadStaticDesc; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs index 8f9478897a7464..ba8468c37d737c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs @@ -4,7 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; - +using System.Linq; +using System.Xml.Linq; using Internal.TypeSystem; // The GCRef map is used to encode GC type of arguments for callsites. Logically, it is sequence where pos is @@ -278,14 +279,32 @@ private void GcScanValueType(TypeDesc type, in ArgDestination argDest, int delta } } - Debug.Assert(type is DefType); - DefType defType = (DefType)type; - foreach (FieldDesc field in defType.GetFields()) + Debug.Assert(type is MetadataType); + MetadataType structType = (MetadataType)type; + bool isInlineArray = structType.IsInlineArray; + foreach (FieldDesc field in structType.GetFields()) { if (field.IsStatic) continue; - GcScanRoots(field.FieldType, in argDest, delta + field.Offset.AsInt, frame, topLevel: false); + if (isInlineArray) + { + var elementSize = field.FieldType.GetElementSize().AsInt; + var totalSize = structType.InstanceFieldSize.AsInt; + + for (int offset = 0; offset < totalSize; offset += elementSize) + { + GcScanRoots(field.FieldType, in argDest, delta + offset, frame, topLevel: false); + } + + // there is only one formal instance field in an inline array + Debug.Assert(field.Offset.AsInt == 0); + break; + } + else + { + GcScanRoots(field.FieldType, in argDest, delta + field.Offset.AsInt, frame, topLevel: false); + } } } diff --git a/src/coreclr/tools/dotnet-pgo/TypeRefTypeSystem/TypeRefTypeSystemType.cs b/src/coreclr/tools/dotnet-pgo/TypeRefTypeSystem/TypeRefTypeSystemType.cs index 40fff461bdfaa9..e1929cf06b4e80 100644 --- a/src/coreclr/tools/dotnet-pgo/TypeRefTypeSystem/TypeRefTypeSystemType.cs +++ b/src/coreclr/tools/dotnet-pgo/TypeRefTypeSystem/TypeRefTypeSystemType.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Collections.Generic; using System.Linq; using System.Text; @@ -250,5 +251,11 @@ protected override TypeFlags ComputeTypeFlags(TypeFlags mask) } protected override MethodImplRecord[] ComputeVirtualMethodImplsForType() => throw new NotImplementedException(); + + public override int GetInlineArrayLength() + { + Debug.Fail("if this can be an inline array, implement GetInlineArrayLength"); + throw new InvalidOperationException(); + } } } diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 22ef2b9919cc19..827212f0ac391f 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1384,9 +1384,15 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! LIMITED_METHOD_CONTRACT; m_VMFlags |= (DWORD)VMFLAG_HAS_FIELDS_WHICH_MUST_BE_INITED; } - void SetCannotBeBlittedByObjectCloner() + DWORD IsInlineArray() { - /* no op */ + LIMITED_METHOD_CONTRACT; + return (m_VMFlags & VMFLAG_INLINE_ARRAY); + } + void SetIsInlineArray() + { + LIMITED_METHOD_CONTRACT; + m_VMFlags |= (DWORD)VMFLAG_INLINE_ARRAY; } DWORD HasNonPublicFields() { @@ -1723,7 +1729,7 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! VMFLAG_BESTFITMAPPING = 0x00004000, // BestFitMappingAttribute.Value VMFLAG_THROWONUNMAPPABLECHAR = 0x00008000, // BestFitMappingAttribute.ThrowOnUnmappableChar - // unused = 0x00010000, + VMFLAG_INLINE_ARRAY = 0x00010000, VMFLAG_NO_GUID = 0x00020000, VMFLAG_HASNONPUBLICFIELDS = 0x00040000, VMFLAG_HAS_CUSTOM_FIELD_ALIGNMENT = 0x00080000, diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 8e888542fecfdc..3e60391f4ddc0d 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -2162,14 +2162,14 @@ static unsigned MarkGCField(BYTE* gcPtrs, CorInfoGCType type) return 0; } -/*********************************************************************/ -static unsigned ComputeGCLayout(MethodTable * pMT, BYTE* gcPtrs) +static unsigned ComputeGCLayout(MethodTable* pMT, BYTE* gcPtrs) { STANDARD_VM_CONTRACT; _ASSERTE(pMT->IsValueType()); unsigned result = 0; + bool isInlineArray = pMT->GetClass()->IsInlineArray(); ApproxFieldDescIterator fieldIterator(pMT, ApproxFieldDescIterator::INSTANCE_FIELDS); for (FieldDesc *pFD = fieldIterator.Next(); pFD != NULL; pFD = fieldIterator.Next()) { @@ -2177,7 +2177,7 @@ static unsigned ComputeGCLayout(MethodTable * pMT, BYTE* gcPtrs) if (pFD->GetFieldType() == ELEMENT_TYPE_VALUETYPE) { - MethodTable * pFieldMT = pFD->GetApproxFieldTypeHandleThrowing().AsMethodTable(); + MethodTable* pFieldMT = pFD->GetApproxFieldTypeHandleThrowing().AsMethodTable(); result += ComputeGCLayout(pFieldMT, gcPtrs + fieldStartIndex); } else if (pFD->IsObjRef()) @@ -2188,6 +2188,25 @@ static unsigned ComputeGCLayout(MethodTable * pMT, BYTE* gcPtrs) { result += MarkGCField(gcPtrs + fieldStartIndex, TYPE_GC_BYREF); } + + if (isInlineArray) + { + if (result > 0) + { + _ASSERTE(pFD->GetOffset() == 0); + DWORD totalLayoutSize = pMT->GetNumInstanceFieldBytes() / TARGET_POINTER_SIZE; + DWORD elementLayoutSize = pFD->GetSize() / TARGET_POINTER_SIZE; + DWORD gcPointersInElement = result; + for (DWORD offset = elementLayoutSize; offset < totalLayoutSize; offset += elementLayoutSize) + { + memcpy(gcPtrs + offset, gcPtrs, elementLayoutSize); + result += gcPointersInElement; + } + } + + // inline array has only one element field + break; + } } return result; } @@ -2229,7 +2248,7 @@ unsigned CEEInfo::getClassGClayoutStatic(TypeHandle VMClsHnd, BYTE* gcPtrs) { memset(gcPtrs, TYPE_GC_NONE, (VMClsHnd.GetSize() + TARGET_POINTER_SIZE - 1) / TARGET_POINTER_SIZE); - // ByRefLike structs can be included as fields in other value types. + // ByRefLike structs can contain byref fields or other ByRefLike structs result = ComputeGCLayout(VMClsHnd.AsMethodTable(), gcPtrs); } else @@ -3552,6 +3571,10 @@ uint32_t CEEInfo::getClassAttribsInternal (CORINFO_CLASS_HANDLE clsHnd) } if (pClass->HasExplicitFieldOffsetLayout() && pClass->HasOverlaidField()) ret |= CORINFO_FLG_OVERLAPPING_FIELDS; + + if (pClass->IsInlineArray()) + ret |= CORINFO_FLG_INDEXABLE_FIELDS; + if (VMClsHnd.IsCanonicalSubtype()) ret |= CORINFO_FLG_SHAREDINST; diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 35a7ddb0360a6d..1fbb50eb825cdd 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -1694,6 +1694,42 @@ MethodTableBuilder::BuildMethodTableThrowing( &pByValueClassCache, bmtMFDescs, bmtFP, &totalDeclaredFieldSize); + if (IsValueClass()) + { + const void* pVal; // The custom value. + ULONG cbVal; // Size of the custom value. + HRESULT hr = GetCustomAttribute(bmtInternal->pType->GetTypeDefToken(), + WellKnownAttribute::InlineArrayAttribute, + &pVal, &cbVal); + + if (hr != S_FALSE) + { + if (bmtEnumFields->dwNumInstanceFields != 1) + { + BuildMethodTableThrowException(IDS_CLASSLOAD_INLINE_ARRAY_FIELD_COUNT); + } + + if (cbVal >= (sizeof(INT32) + 2)) + { + INT32 repeat = GET_UNALIGNED_VAL32((byte*)pVal + 2); + if (repeat > 0) + { + bmtFP->NumInlineArrayElements = repeat; + GetHalfBakedClass()->SetIsInlineArray(); + } + else + { + BuildMethodTableThrowException(IDS_CLASSLOAD_INLINE_ARRAY_LENGTH); + } + + if (HasExplicitFieldOffsetLayout()) + { + BuildMethodTableThrowException(IDS_CLASSLOAD_INLINE_ARRAY_EXPLICIT); + } + } + } + } + // Place regular static fields PlaceRegularStaticFields(); @@ -1713,6 +1749,11 @@ MethodTableBuilder::BuildMethodTableThrowing( _ASSERTE(HasLayout()); + if (bmtFP->NumInlineArrayElements != 0) + { + GetLayoutInfo()->m_cbManagedSize *= bmtFP->NumInlineArrayElements; + } + bmtFP->NumInstanceFieldBytes = GetLayoutInfo()->m_cbManagedSize; // For simple Blittable types we still need to check if they have any overlapping @@ -3764,9 +3805,6 @@ VOID MethodTableBuilder::InitializeFieldDescs(FieldDesc *pFieldDescList, if (!IsFdPublic(dwMemberAttrs)) SetHasNonPublicFields(); - if (IsFdNotSerialized(dwMemberAttrs)) - SetCannotBeBlittedByObjectCloner(); - IfFailThrow(pInternalImport->GetSigOfFieldDef(bmtMetaData->pFields[i], &cMemberSignature, &pMemberSignature)); // Signature validation IfFailThrow(validateTokenSig(bmtMetaData->pFields[i],pMemberSignature,cMemberSignature,dwMemberAttrs,pInternalImport)); @@ -3956,8 +3994,6 @@ VOID MethodTableBuilder::InitializeFieldDescs(FieldDesc *pFieldDescList, if (!fIsStatic) { SetHasFieldsWhichMustBeInited(); - if (ElementType != ELEMENT_TYPE_STRING) - SetCannotBeBlittedByObjectCloner(); } else { // EnumerateFieldDescs already counted the total number of static vs. instance @@ -8318,6 +8354,24 @@ VOID MethodTableBuilder::PlaceInstanceFields(MethodTable ** pByValueClassCach BuildMethodTableThrowException(IDS_CLASSLOAD_FIELDTOOLARGE); } + if (bmtFP->NumInlineArrayElements > 1) + { + INT64 extendedSize = (INT64)dwNumInstanceFieldBytes * (INT64)bmtFP->NumInlineArrayElements; + // limit the max size of array instance to 1MiB + const INT64 maxSize = 1024 * 1024; + if (extendedSize > maxSize) + { + BuildMethodTableThrowException(IDS_CLASSLOAD_FIELDTOOLARGE); + } + + dwNumInstanceFieldBytes = (DWORD)extendedSize; + + if (pFieldDescList[0].IsByValue()) + { + dwNumGCPointerSeries *= bmtFP->NumInlineArrayElements; + } + } + bmtFP->NumInstanceFieldBytes = dwNumInstanceFieldBytes; bmtFP->NumGCPointerSeries = dwNumGCPointerSeries; @@ -11494,12 +11548,18 @@ VOID MethodTableBuilder::HandleGCForValueClasses(MethodTable ** pByValueClassCac } + DWORD repeat = 1; + if (bmtFP->NumInlineArrayElements > 1) + { + repeat = bmtFP->NumInlineArrayElements; + } + // Build the pointer series map for this pointers in this instance pSeries = ((CGCDesc*)pMT)->GetLowestSeries(); if (bmtFP->NumInstanceGCPointerFields) { // See gcdesc.h for an explanation of why we adjust by subtracting BaseSize - pSeries->SetSeriesSize( (size_t) (bmtFP->NumInstanceGCPointerFields * TARGET_POINTER_SIZE) - (size_t) pMT->GetBaseSize()); + pSeries->SetSeriesSize((size_t)(bmtFP->NumInstanceGCPointerFields * repeat * TARGET_POINTER_SIZE) - (size_t)pMT->GetBaseSize()); pSeries->SetSeriesOffset(bmtFP->GCPointerFieldStart + OBJECT_SIZE); pSeries++; } @@ -11509,44 +11569,54 @@ VOID MethodTableBuilder::HandleGCForValueClasses(MethodTable ** pByValueClassCac { if (pFieldDescList[i].IsByValue()) { - MethodTable *pByValueMT = pByValueClassCache[i]; + MethodTable* pByValueMT = pByValueClassCache[i]; if (pByValueMT->ContainsPointers()) { // Offset of the by value class in the class we are building, does NOT include Object DWORD dwCurrentOffset = pFieldDescList[i].GetOffset_NoLogging(); + DWORD dwElementSize = pByValueMT->GetBaseSize() - OBJECT_BASESIZE; - // The by value class may have more than one pointer series - CGCDescSeries * pByValueSeries = CGCDesc::GetCGCDescFromMT(pByValueMT)->GetLowestSeries(); - SIZE_T dwNumByValueSeries = CGCDesc::GetCGCDescFromMT(pByValueMT)->GetNumSeries(); - - for (SIZE_T j = 0; j < dwNumByValueSeries; j++) + // if we have an inline array, we will have only one formal instance field, + // but will have to replicate the layout "repeat" times. + // otherwise every field will be matched with 1 serie. + for (DWORD r = 0; r < repeat; r++) { - size_t cbSeriesSize; - size_t cbSeriesOffset; + // The by value class may have more than one pointer series + CGCDescSeries* pByValueSeries = CGCDesc::GetCGCDescFromMT(pByValueMT)->GetLowestSeries(); + SIZE_T dwNumByValueSeries = CGCDesc::GetCGCDescFromMT(pByValueMT)->GetNumSeries(); + + for (SIZE_T j = 0; j < dwNumByValueSeries; j++) + { + size_t cbSeriesSize; + size_t cbSeriesOffset; - _ASSERTE(pSeries <= CGCDesc::GetCGCDescFromMT(pMT)->GetHighestSeries()); + _ASSERTE(pSeries <= CGCDesc::GetCGCDescFromMT(pMT)->GetHighestSeries()); - cbSeriesSize = pByValueSeries->GetSeriesSize(); + cbSeriesSize = pByValueSeries->GetSeriesSize(); - // Add back the base size of the by value class, since it's being transplanted to this class - cbSeriesSize += pByValueMT->GetBaseSize(); + // Add back the base size of the by value class, since it's being transplanted to this class + cbSeriesSize += pByValueMT->GetBaseSize(); - // Subtract the base size of the class we're building - cbSeriesSize -= pMT->GetBaseSize(); + // Subtract the base size of the class we're building + cbSeriesSize -= pMT->GetBaseSize(); - // Set current series we're building - pSeries->SetSeriesSize(cbSeriesSize); + // Set current series we're building + pSeries->SetSeriesSize(cbSeriesSize); - // Get offset into the value class of the first pointer field (includes a +Object) - cbSeriesOffset = pByValueSeries->GetSeriesOffset(); + // Get offset into the value class of the first pointer field (includes a +Object) + cbSeriesOffset = pByValueSeries->GetSeriesOffset(); - // Add it to the offset of the by value class in our class - cbSeriesOffset += dwCurrentOffset; + // Add element N offset + cbSeriesOffset += r * dwElementSize; - pSeries->SetSeriesOffset(cbSeriesOffset); // Offset of field - pSeries++; - pByValueSeries++; + // Add it to the offset of the by value class in our class + cbSeriesOffset += dwCurrentOffset; + + pSeries->SetSeriesOffset(cbSeriesOffset); // Offset of field + pSeries++; + pByValueSeries++; + } } } } diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index 739a8e530f1320..f8cc5b86aab529 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -218,7 +218,6 @@ class MethodTableBuilder // we create the EEClass object, and thus set the flags immediately at the point // we create that object. void SetUnsafeValueClass() { WRAPPER_NO_CONTRACT; GetHalfBakedClass()->SetUnsafeValueClass(); } - void SetCannotBeBlittedByObjectCloner() { WRAPPER_NO_CONTRACT; GetHalfBakedClass()->SetCannotBeBlittedByObjectCloner(); } void SetHasFieldsWhichMustBeInited() { WRAPPER_NO_CONTRACT; GetHalfBakedClass()->SetHasFieldsWhichMustBeInited(); } void SetHasNonPublicFields() { WRAPPER_NO_CONTRACT; GetHalfBakedClass()->SetHasNonPublicFields(); } void SetModuleDynamicID(DWORD x) { WRAPPER_NO_CONTRACT; GetHalfBakedClass()->SetModuleDynamicID(x); } @@ -2011,6 +2010,7 @@ class MethodTableBuilder DWORD NumInstanceGCPointerFields; // does not include inherited pointer fields DWORD NumGCPointerSeries; DWORD NumInstanceFieldBytes; + DWORD NumInlineArrayElements; bool fIsByRefLikeType; bool fHasFixedAddressValueTypes; diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 0a15fbb91488fe..2efb208d1bc8bc 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -4840,26 +4840,45 @@ class ByRefPointerOffsetsReporter WRAPPER_NO_CONTRACT; } + void Find(FieldDesc* pFD, SIZE_T baseOffset) + { + if (pFD->GetFieldType() == ELEMENT_TYPE_VALUETYPE) + { + PTR_MethodTable pFieldMT = pFD->GetApproxFieldTypeHandleThrowing().AsMethodTable(); + if (pFieldMT->IsByRefLike()) + { + Find(pFieldMT, baseOffset + pFD->GetOffset()); + } + } + else if (pFD->IsByRef()) + { + Report(baseOffset + pFD->GetOffset()); + } + } + void Find(PTR_MethodTable pMT, SIZE_T baseOffset) { WRAPPER_NO_CONTRACT; _ASSERTE(pMT != nullptr); _ASSERTE(pMT->IsByRefLike()); + bool isValArray = pMT->GetClass()->IsInlineArray(); ApproxFieldDescIterator fieldIterator(pMT, ApproxFieldDescIterator::INSTANCE_FIELDS); for (FieldDesc* pFD = fieldIterator.Next(); pFD != NULL; pFD = fieldIterator.Next()) { - if (pFD->GetFieldType() == ELEMENT_TYPE_VALUETYPE) + if (isValArray) { - PTR_MethodTable pFieldMT = pFD->GetApproxFieldTypeHandleThrowing().AsMethodTable(); - if (pFieldMT->IsByRefLike()) + _ASSERTE(pFD->GetOffset() == 0); + DWORD elementSize = pFD->GetSize(); + DWORD totalSize = pMT->GetNumInstanceFieldBytes(); + for (DWORD offset = 0; offset < totalSize; offset += elementSize) { - Find(pFieldMT, baseOffset + pFD->GetOffset()); + Find(pFD, baseOffset + offset); } } - else if (pFD->IsByRef()) + else { - Report(baseOffset + pFD->GetOffset()); + Find(pFD, baseOffset); } } } diff --git a/src/coreclr/vm/wellknownattributes.h b/src/coreclr/vm/wellknownattributes.h index f1ac8a69767505..837f75c2ac083b 100644 --- a/src/coreclr/vm/wellknownattributes.h +++ b/src/coreclr/vm/wellknownattributes.h @@ -36,6 +36,7 @@ enum class WellKnownAttribute : DWORD WinRTMarshalingBehaviorAttribute, PreserveBaseOverridesAttribute, ObjectiveCTrackedTypeAttribute, + InlineArrayAttribute, CountOfWellKnownAttributes }; @@ -104,6 +105,8 @@ inline const char *GetWellKnownAttributeName(WellKnownAttribute attribute) return "System.Runtime.CompilerServices.PreserveBaseOverridesAttribute"; case WellKnownAttribute::ObjectiveCTrackedTypeAttribute: return "System.Runtime.InteropServices.ObjectiveC.ObjectiveCTrackedTypeAttribute"; + case WellKnownAttribute::InlineArrayAttribute: + return "System.Runtime.CompilerServices.InlineArrayAttribute"; case WellKnownAttribute::CountOfWellKnownAttributes: default: break; // Silence compiler warnings diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 4d2a3582ddd773..e7dc54f16d1940 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3893,6 +3893,15 @@ '{0}' from assembly '{1}' has too many dimensions. + + InlineArrayAttribute requires that the target type has a single instance field. Type: '{0}'. Assembly: '{1}'. + + + InlineArrayAttribute requires that the length argument is greater than 0. Type: '{0}'. Assembly: '{1}'. + + + InlineArrayAttribute cannot be applied to a type with explicit layout. Type: '{0}'. Assembly: '{1}'. + Could not load type '{0}' from assembly '{1}' because generic types cannot have explicit layout. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 6f2d52f360447d..35dbe01116bbcd 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -808,6 +808,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/ParamsArray.cs b/src/libraries/System.Private.CoreLib/src/System/ParamsArray.cs index 8e9d35f6b0076b..a4b5fd9bcdff2b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ParamsArray.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ParamsArray.cs @@ -7,8 +7,48 @@ // Suppress warnings for unused private fields #pragma warning disable CS0169, CA1823, IDE0051, IDE0044 +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + namespace System { +#if CORECLR || NATIVEAOT + + [InlineArray(Length)] + internal struct TwoObjects + { + private const int Length = 2; + internal object? Arg0; + + [UnscopedRef] + private ref object? this[int i] => ref Unsafe.Add(ref Arg0, i); + + public TwoObjects(object? arg0, object? arg1) + { + this[0] = arg0; + this[1] = arg1; + } + } + + [InlineArray(Length)] + internal struct ThreeObjects + { + private const int Length = 3; + internal object? Arg0; + + [UnscopedRef] + private ref object? this[int i] => ref Unsafe.Add(ref Arg0, i); + + public ThreeObjects(object? arg0, object? arg1, object? arg2) + { + this[0] = arg0; + this[1] = arg1; + this[2] = arg2; + } + } + +#else + internal struct TwoObjects { internal object? Arg0; @@ -34,4 +74,5 @@ public ThreeObjects(object? arg0, object? arg1, object? arg2) _arg2 = arg2; } } +#endif } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs index bd25e409799d90..a43181e65b59fb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -274,6 +274,27 @@ BindingFlags invokeAttr internal const int MaxStackAllocArgCount = 4; +#if CORECLR + [InlineArray(MaxStackAllocArgCount)] +#endif + private protected struct ArgumentData + { + private T _arg0; +#if !CORECLR +#pragma warning disable CA1823, CS0169, IDE0051, IDE0044 // accessed via 'CheckArguments' ref arithmetic + private T _arg1; + private T _arg2; + private T _arg3; +#pragma warning restore CA1823, CS0169, IDE0051, IDE0044 +#endif + [UnscopedRef] + public Span AsSpan(int length) + { + Debug.Assert((uint)length <= (uint) MaxStackAllocArgCount); + return new Span(ref _arg0, length); + } + } + // Helper struct to avoid intermediate object[] allocation in calls to the native reflection stack. // When argument count <= MaxStackAllocArgCount, define a local of type default(StackAllocatedByRefs) // and pass it to CheckArguments(). @@ -282,31 +303,25 @@ BindingFlags invokeAttr [StructLayout(LayoutKind.Sequential)] private protected ref struct StackAllocedArguments { - internal object? _arg0; -#pragma warning disable CA1823, CS0169, IDE0051, IDE0044 // accessed via 'CheckArguments' ref arithmetic - private object? _arg1; - private object? _arg2; - private object? _arg3; -#pragma warning restore CA1823, CS0169, IDE0051, IDE0044 - internal ParameterCopyBackAction _copyBack0; -#pragma warning disable CA1823, CS0169, IDE0051, IDE0044 // accessed via 'CheckArguments' ref arithmetic - private ParameterCopyBackAction _copyBack1; - private ParameterCopyBackAction _copyBack2; - private ParameterCopyBackAction _copyBack3; -#pragma warning restore CA1823, CS0169, IDE0051, IDE0044 + internal ArgumentData _args; + internal ArgumentData _copyBacks; } // Helper struct to avoid intermediate IntPtr[] allocation and RegisterForGCReporting in calls to the native reflection stack. - [StructLayout(LayoutKind.Sequential)] +#if CORECLR + [InlineArray(MaxStackAllocArgCount)] +#endif private protected ref struct StackAllocatedByRefs { internal ref byte _arg0; +#if !CORECLR #pragma warning disable CA1823, CS0169, IDE0051 // accessed via 'CheckArguments' ref arithmetic private ref byte _arg1; private ref byte _arg2; private ref byte _arg3; #pragma warning restore CA1823, CS0169, IDE0051 +#endif } #endif - } + } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs index a16f6f48531ae9..d9b5ae564bafe4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeConstructorInfo.cs @@ -142,8 +142,8 @@ internal void ThrowNoInvokeException() { Debug.Assert(parameters != null); StackAllocedArguments argStorage = default; - Span copyOfParameters = new(ref argStorage._arg0, argCount); - Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); + Span copyOfParameters = argStorage._args.AsSpan(argCount); + Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(argCount); StackAllocatedByRefs byrefStorage = default; #pragma warning disable 8500 @@ -289,8 +289,8 @@ public override object Invoke(BindingFlags invokeAttr, Binder? binder, object?[] { Debug.Assert(parameters != null); StackAllocedArguments argStorage = default; - Span copyOfParameters = new(ref argStorage._arg0, argCount); - Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); + Span copyOfParameters = argStorage._args.AsSpan(argCount); + Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(argCount); StackAllocatedByRefs byrefStorage = default; #pragma warning disable 8500 diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs index 28d530bcfc6c20..62e62b1550c9ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs @@ -139,8 +139,8 @@ private void ThrowNoInvokeException() { Debug.Assert(parameters != null); StackAllocedArguments argStorage = default; - Span copyOfParameters = new(ref argStorage._arg0, argCount); - Span shouldCopyBackParameters = new(ref argStorage._copyBack0, argCount); + Span copyOfParameters = argStorage._args.AsSpan(argCount); + Span shouldCopyBackParameters = argStorage._copyBacks.AsSpan(argCount); StackAllocatedByRefs byrefStorage = default; #pragma warning disable 8500 diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/InlineArrayAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/InlineArrayAttribute.cs new file mode 100644 index 00000000000000..9872c61e191ffa --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/InlineArrayAttribute.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + /// + /// Indicates that the instance's storage is sequentially replicated "length" times. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] + public sealed class InlineArrayAttribute : Attribute + { + public InlineArrayAttribute(int length) + { + Length = length; + } + + public int Length { get; } + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 53f5e047b232a0..6a18583fdb46da 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -12498,6 +12498,13 @@ public sealed partial class InterpolatedStringHandlerAttribute : System.Attribut { public InterpolatedStringHandlerAttribute() { } } + [AttributeUsage(System.AttributeTargets.Struct, AllowMultiple = false)] + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public sealed partial class InlineArrayAttribute : System.Attribute + { + public InlineArrayAttribute(int length) { } + public int Length { get { throw null; } } + } [System.AttributeUsageAttribute(System.AttributeTargets.Struct)] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public sealed partial class IsByRefLikeAttribute : System.Attribute diff --git a/src/tests/Loader/classloader/InlineArray/InlineArrayInvalid.cs b/src/tests/Loader/classloader/InlineArray/InlineArrayInvalid.cs new file mode 100644 index 00000000000000..6704199bee115a --- /dev/null +++ b/src/tests/Loader/classloader/InlineArray/InlineArrayInvalid.cs @@ -0,0 +1,136 @@ +// 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.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Xunit; + +unsafe class Validate +{ + [InlineArray(1)] + [StructLayout(LayoutKind.Explicit)] + private struct Explicit + { + [FieldOffset(0)] + public Guid Guid; + } + + [Fact] + public static void Explicit_Fails() + { + Console.WriteLine($"{nameof(Explicit_Fails)}..."); + Assert.Throws(() => { var t = typeof(Explicit); }); + + Assert.Throws(() => + { + return sizeof(Explicit); + }); + } + + [InlineArray(0)] + private struct ZeroLength + { + public int field; + } + + [Fact] + public static void ZeroLength_Fails() + { + Console.WriteLine($"{nameof(ZeroLength_Fails)}..."); + Assert.Throws(() => { var t = typeof(ZeroLength); }); + + Assert.Throws(() => + { + var t = new ZeroLength() + { + field = 1 + }; + return t; + }); + } + + [InlineArray(0x20000000)] + private struct TooLarge + { + public long field; + } + + [Fact] + public static void TooLarge_Fails() + { + Console.WriteLine($"{nameof(TooLarge_Fails)}..."); + Assert.Throws(() => { var t = typeof(TooLarge); }); + + Assert.Throws(() => + { + var t = new TooLarge() + { + field = 1 + }; + return t; + }); + } + + [InlineArray(-1)] + private struct NegativeLength + { + public long field; + } + + [Fact] + public static void NegativeLength_Fails() + { + Console.WriteLine($"{nameof(NegativeLength_Fails)}..."); + Assert.Throws(() => { var t = typeof(NegativeLength); }); + + Assert.Throws(() => + { + var t = new NegativeLength() + { + field = 1 + }; + return t; + }); + } + + + [InlineArray(123)] + private struct NoFields + { + public static int x; + } + + [Fact] + public static void NoFields_Fails() + { + Console.WriteLine($"{nameof(NoFields_Fails)}..."); + Assert.Throws(() => { var t = typeof(NoFields); }); + + Assert.Throws(() => + { + return (new NoFields()).ToString(); + }); + } + + [InlineArray(1)] + private struct TwoFields + { + int a; + int b; + } + + [Fact] + public static void TwoFields_Fails() + { + Console.WriteLine($"{nameof(TwoFields_Fails)}..."); + Assert.Throws(() => { var t = typeof(TwoFields); }); + + Assert.Throws(() => + { + return new TwoFields[12]; + }); + } +} diff --git a/src/tests/Loader/classloader/InlineArray/InlineArrayInvalid.csproj b/src/tests/Loader/classloader/InlineArray/InlineArrayInvalid.csproj new file mode 100644 index 00000000000000..148e8a4fadff02 --- /dev/null +++ b/src/tests/Loader/classloader/InlineArray/InlineArrayInvalid.csproj @@ -0,0 +1,13 @@ + + + true + Exe + true + + + + + + + + diff --git a/src/tests/Loader/classloader/InlineArray/InlineArrayValid.cs b/src/tests/Loader/classloader/InlineArray/InlineArrayValid.cs new file mode 100644 index 00000000000000..ca8a36ed4a9dd4 --- /dev/null +++ b/src/tests/Loader/classloader/InlineArray/InlineArrayValid.cs @@ -0,0 +1,367 @@ +// 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.IO; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Xunit; + +// we will be doing "sizeof" with arrays containing managed references. +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + +[InlineArray(LengthConst)] +struct MyArray : IEnumerable +{ + private const int LengthConst = 42; + private T _element; + + public int Length => LengthConst; + + [UnscopedRef] + public ref T this[int i] + { + get + { + if ((uint)i >= (uint)Length) + throw new IndexOutOfRangeException(nameof(i)); + + return ref Unsafe.Add(ref _element, i); + } + } + + [UnscopedRef] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Length); + + IEnumerator IEnumerable.GetEnumerator() => (IEnumerator)this.GetEnumerator(); + + public IEnumerator GetEnumerator() + { + for (int i =0; i < Length; i++) + { + yield return this[i]; + } + } +} + +unsafe class Validate +{ + // ====================== SizeOf ============================================================== + [InlineArray(42)] + struct FourtyTwoBytes + { + byte b; + } + + [Fact] + public static void Sizeof() + { + Console.WriteLine($"{nameof(Sizeof)}..."); + Assert.Equal(42, sizeof(FourtyTwoBytes)); + Assert.Equal(84, sizeof(MyArray)); + } + + // ====================== OneElement ========================================================== + [InlineArray(1)] + struct OneObj + { + public object obj; + } + + [Fact] + public static void OneElement() + { + Console.WriteLine($"{nameof(OneElement)}..."); + Assert.Equal(sizeof(nint), sizeof(OneObj)); + } + + // ====================== UseOnStack ========================================================== + class One { } + class Two { } + class Three { } + class Four { } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static unsafe Arr1 Initialize(Arr1 s) + { + s[0].o = new One(); + s[1].o = new Two(); + s[2].o = new Three(); + s[3].o = new Four(); + return s; + } + + struct E + { + public int x; + public int y; + public object o; + } + + [InlineArray(Length)] + struct Arr1 + { + public const int Length = 42; + public E e; + + [UnscopedRef] + public ref E this[int i] => ref Unsafe.Add(ref e, i); + } + + static object s; + private static unsafe void MakeGarbage() + { + // make garbage + for (int i = 0; i < 10000; i++) + { + s = new int[i]; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void PassByValueDoGcAndValidate(Arr1 s1) + { + MakeGarbage(); + + GC.Collect(2, GCCollectionMode.Forced, true, true); + GC.Collect(2, GCCollectionMode.Forced, true, true); + + Assert.Equal("One", s1[0].o.GetType().Name); + Assert.Equal("Two", s1[1].o.GetType().Name); + Assert.Equal("Three", s1[2].o.GetType().Name); + Assert.Equal("Four", s1[3].o.GetType().Name); + } + + [Fact] + public static void UseOnStack() + { + Console.WriteLine($"{nameof(UseOnStack)}..."); + + Arr1 s = default; + MakeGarbage(); + + s = Initialize(s); + + // use as a byval argument + PassByValueDoGcAndValidate(s); + + // refs must be separate and alive + Assert.Equal("One", s[0].o.GetType().Name); + Assert.Equal("Two", s[1].o.GetType().Name); + Assert.Equal("Three", s[2].o.GetType().Name); + Assert.Equal("Four", s[3].o.GetType().Name); + + // should copy by value + Arr1 s1 = s; + Assert.Equal("One", s1[0].o.GetType().Name); + Assert.Equal("Two", s1[1].o.GetType().Name); + Assert.Equal("Three", s1[2].o.GetType().Name); + Assert.Equal("Four", s1[3].o.GetType().Name); + } + + // ====================== MixObjectsAndValuetypes ============================================= + [InlineArray(Length)] + struct ObjShortArr + { + public const int Length = 100; + public (object, short) element; + + [UnscopedRef] + public ref (object o, short s) this[int i] => ref Unsafe.Add(ref element, i); + } + + [Fact] + public static void MixObjectsAndValuetypes() + { + Console.WriteLine($"{nameof(MixObjectsAndValuetypes)}..."); + Assert.Equal(ObjShortArr.Length * sizeof(nint) * 2, sizeof(ObjShortArr)); + + var arr = new ObjShortArr(); + for (short i = 0; i < ObjShortArr.Length; i++) + { + arr[i].o = i; + arr[i].s = (short)(i + 1); + } + + GC.Collect(2, GCCollectionMode.Forced, true, true); + + for (short i = 0; i < ObjShortArr.Length; i++) + { + Assert.Equal(i, arr[i].o); + Assert.Equal(i + 1, arr[i].s); + } + } + + // ====================== RefLikeOuter ======================================================== + [InlineArray(Length)] + ref struct ObjShortArrRef + { + public const int Length = 100; + public (object, short) element; + + [UnscopedRef] + public ref (object o, short s) this[int i] => ref Unsafe.Add(ref element, i); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void TestRefLikeOuterMethodArg(ObjShortArrRef arr) + { + GC.Collect(2, GCCollectionMode.Forced, true, true); + + for (short i = 0; i < ObjShortArrRef.Length; i++) + { + Assert.Equal(i * 2, arr[i].o); + Assert.Equal(i * 2 + 1, arr[i].s); + } + } + + [Fact] + public static void RefLikeOuter() + { + Console.WriteLine($"{nameof(RefLikeOuter)}..."); + + var arr = new ObjShortArrRef(); + for (short i = 0; i < ObjShortArrRef.Length; i++) + { + arr[i].o = i; + arr[i].s = (short)(i + 1); + } + + GC.Collect(2, GCCollectionMode.Forced, true, true); + + for (short i = 0; i < ObjShortArrRef.Length; i++) + { + Assert.Equal(i, arr[i].o); + Assert.Equal(i + 1, arr[i].s); + } + + for (short i = 0; i < ObjShortArrRef.Length; i++) + { + arr[i].o = i * 2; + arr[i].s = (short)(i * 2 + 1); + } + + TestRefLikeOuterMethodArg(arr); + } + + // ====================== RefLikeInner ======================================================== + [InlineArray(LengthConst)] + ref struct SpanArr + { + private const int LengthConst = 100; + public Span element; + + public Span* this[int i] + { + get + { + fixed (Span* p = &element) + { + return p + i; + } + } + } + + public int Length => LengthConst; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void TestRefLikeInnerMethodArg(SpanArr arr) + { + GC.Collect(2, GCCollectionMode.Forced, true, true); + + for (int i = 1; i < arr.Length; i++) + { + Assert.Equal(i, arr[i]->Length); + Assert.Equal(i, (*arr[i])[0]); + } + } + + [Fact] + public static void RefLikeInner() + { + Console.WriteLine($"{nameof(RefLikeInner)}..."); + + SpanArr arr = default; + for (int i = 1; i < arr.Length; i++) + { + var objArr = new object[i]; + objArr[0] = i; + *arr[i] = objArr; + } + + TestRefLikeInnerMethodArg(arr); + } + + // ====================== Nested ============================================================== + + struct IntObj + { + public int i; + public object o; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void NestedMethodArg(ref MyArray> nestedArray) + { + GC.Collect(2, GCCollectionMode.Forced, true, true); + + for (int i = 0; i < nestedArray.Length; i++) + { + for (int j = 0; j < nestedArray[i].Length; j++) + { + Assert.Equal(i + j, nestedArray[i][j].o); + Assert.Equal(i * j, nestedArray[i][j].i); + } + } + } + + [Fact] + public static void Nested() + { + Console.WriteLine($"{nameof(Nested)}..."); + + MyArray> nestedArray = default; + + for(int i = 0; i < nestedArray.Length; i++) + { + for (int j = 0; j < nestedArray[i].Length; j++) + { + nestedArray[i][j].o = i + j; + nestedArray[i][j].i = i * j; + } + } + } + + // ====================== Boxed =============================================================== + + [MethodImpl(MethodImplOptions.NoInlining)] + static void BoxedMethodArg(IEnumerable arr) + { + GC.Collect(2, GCCollectionMode.Forced, true, true); + + int i = 0; + foreach(object obj in arr) + { + Assert.Equal(i++, obj); + } + } + + [Fact] + public static void Boxed() + { + Console.WriteLine($"{nameof(Boxed)}..."); + + MyArray arr = default; + for (int i = 0; i < arr.Length; i++) + { + arr[i] = i; + } + + BoxedMethodArg(arr); + } +} diff --git a/src/tests/Loader/classloader/InlineArray/InlineArrayValid.csproj b/src/tests/Loader/classloader/InlineArray/InlineArrayValid.csproj new file mode 100644 index 00000000000000..96c6ab6ea953ac --- /dev/null +++ b/src/tests/Loader/classloader/InlineArray/InlineArrayValid.csproj @@ -0,0 +1,13 @@ + + + true + Exe + true + + + + + + + + diff --git a/src/tests/issues.targets b/src/tests/issues.targets index d508f5c2122199..5a18053d5ce17f 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -1529,6 +1529,10 @@ Static virtual methods are not yet implemented in the Mono runtime. + + https://github.com/dotnet/runtime/issues/80798 + + needs triage