diff --git a/src/coreclr/dlls/mscorrc/mscorrc.rc b/src/coreclr/dlls/mscorrc/mscorrc.rc index 4db50443879a4d..a317ff81159029 100644 --- a/src/coreclr/dlls/mscorrc/mscorrc.rc +++ b/src/coreclr/dlls/mscorrc/mscorrc.rc @@ -262,6 +262,7 @@ BEGIN IDS_EE_BADMARSHAL_CUSTOMMARSHALER "Custom marshalers are only allowed on classes, strings, arrays, and boxed value types." IDS_EE_BADMARSHAL_GENERICS_RESTRICTION "Non-blittable generic types cannot be marshaled." IDS_EE_BADMARSHAL_INT128_RESTRICTION "System.Int128 and System.UInt128 cannot be passed by value to unmanaged." + IDS_EE_BADMARSHAL_DECIMAL_RESTRICTION "System.Numerics.Decimal32, System.Numerics.Decimal64, and System.Numerics.Decimal128 cannot be passed by value to unmanaged." IDS_EE_BADMARSHAL_AUTOLAYOUT "Structures marked with [StructLayout(LayoutKind.Auto)] cannot be marshaled." IDS_EE_BADMARSHAL_STRING_OUT "Cannot marshal a string by-value with the [Out] attribute." IDS_EE_BADMARSHAL_MARSHAL_DISABLED "Cannot marshal managed types when the runtime marshalling system is disabled." diff --git a/src/coreclr/dlls/mscorrc/resource.h b/src/coreclr/dlls/mscorrc/resource.h index de761d74bd6315..42067318c97b60 100644 --- a/src/coreclr/dlls/mscorrc/resource.h +++ b/src/coreclr/dlls/mscorrc/resource.h @@ -249,6 +249,7 @@ #define IDS_EE_BADMARSHAL_ABSTRACTOUTCRITICALHANDLE 0x1a63 #define IDS_EE_BADMARSHAL_CRITICALHANDLE 0x1a65 #define IDS_EE_BADMARSHAL_INT128_RESTRICTION 0x1a66 +#define IDS_EE_BADMARSHAL_DECIMAL_RESTRICTION 0x1a67 #define IDS_EE_BADMARSHAL_ABSTRACTRETCRITICALHANDLE 0x1a6a diff --git a/src/coreclr/tools/Common/Compiler/DecimalFieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/Compiler/DecimalFieldLayoutAlgorithm.cs new file mode 100644 index 00000000000000..a7c1eb74ba0531 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/DecimalFieldLayoutAlgorithm.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; + +namespace ILCompiler +{ + /// + /// Represents an algorithm that computes field layout for the IEEE 754 decimal floating-point + /// types (Decimal32/Decimal64/Decimal128). Decimal128 shares the 16-byte packing requirement of + /// Int128/UInt128; Decimal32/Decimal64 keep the natural alignment of their underlying scalar. + /// + public class DecimalFieldLayoutAlgorithm : FieldLayoutAlgorithm + { + private readonly FieldLayoutAlgorithm _fallbackAlgorithm; + + public DecimalFieldLayoutAlgorithm(FieldLayoutAlgorithm fallbackAlgorithm) + { + _fallbackAlgorithm = fallbackAlgorithm; + } + + public override ComputedInstanceFieldLayout ComputeInstanceLayout(DefType defType, InstanceLayoutKind layoutKind) + { + Debug.Assert(IsDecimalFloatingPointType(defType)); + + ComputedInstanceFieldLayout layoutFromMetadata = _fallbackAlgorithm.ComputeInstanceLayout(defType, layoutKind); + + // Only Decimal128 corresponds to a 16-byte ABI primitive (_Decimal128) requiring the packing + // applied to Int128/UInt128; Decimal32/Decimal64 keep the natural alignment of uint/ulong. + // ARM32 has no such primitive in its PCS, so it uses the metadata layout as Int128 does; + // every other target is 16-byte aligned, matching the Int128/UInt128 treatment. + if (defType.Name != "Decimal128"u8 + || defType.Context.Target.Architecture == TargetArchitecture.ARM) + { + layoutFromMetadata.LayoutAbiStable = true; + layoutFromMetadata.IsDecimalFloatingPointOrHasDecimalFloatingPointFields = true; + return layoutFromMetadata; + } + + return new ComputedInstanceFieldLayout + { + ByteCountUnaligned = layoutFromMetadata.ByteCountUnaligned, + ByteCountAlignment = layoutFromMetadata.ByteCountAlignment, + FieldAlignment = new LayoutInt(16), + FieldSize = layoutFromMetadata.FieldSize, + Offsets = layoutFromMetadata.Offsets, + LayoutAbiStable = true, + IsDecimalFloatingPointOrHasDecimalFloatingPointFields = true + }; + } + + public override ComputedStaticFieldLayout ComputeStaticFieldLayout(DefType defType, StaticLayoutKind layoutKind) + { + return _fallbackAlgorithm.ComputeStaticFieldLayout(defType, layoutKind); + } + + public override bool ComputeContainsGCPointers(DefType type) + { + Debug.Assert(!_fallbackAlgorithm.ComputeContainsGCPointers(type)); + return false; + } + + public override bool ComputeContainsByRefs(DefType type) + { + Debug.Assert(!_fallbackAlgorithm.ComputeContainsByRefs(type)); + return false; + } + + public override bool ComputeIsUnsafeValueType(DefType type) + { + Debug.Assert(!_fallbackAlgorithm.ComputeIsUnsafeValueType(type)); + return false; + } + + public override ValueTypeShapeCharacteristics ComputeValueTypeShapeCharacteristics(DefType type) + { + Debug.Assert(_fallbackAlgorithm.ComputeValueTypeShapeCharacteristics(type) == ValueTypeShapeCharacteristics.None); + return ValueTypeShapeCharacteristics.None; + } + + public static bool IsDecimalFloatingPointType(DefType type) + { + return type.IsIntrinsic + && type.Namespace == "System.Numerics"u8 + && (type.Name == "Decimal32"u8 || type.Name == "Decimal64"u8 || type.Name == "Decimal128"u8); + } + } +} diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 329abbf03b7f78..525e1283855b6c 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -2680,8 +2680,10 @@ private GetTypeLayoutResult GetTypeLayoutHelper(MetadataType type, uint parentIn #endif // The intrinsic SIMD/HW SIMD types have a lot of fields that the JIT does - // not care about since they are considered primitives by the JIT. - if (type.IsIntrinsic) + // not care about since they are considered primitives by the JIT. The IEEE 754 + // decimal floating-point types are the System.Numerics intrinsics that are not + // SIMD, so the JIT lays them out like any other struct and needs their fields. + if (type.IsIntrinsic && !type.IsDecimalFloatingPointOrHasDecimalFloatingPointFields) { Utf8Span ns = type.Namespace; if (ns == "System.Runtime.Intrinsics"u8 || ns == "System.Numerics"u8) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs b/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs index 073b118f7e8947..94239d8e321c7b 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs @@ -88,6 +88,12 @@ private static class FieldLayoutFlags /// True if the type contains byrefs /// public const int ContainsByRefs = 0x4000; + + /// + /// True if the type transitively has a decimal floating-point type + /// (Decimal32/Decimal64/Decimal128) in it or is one. + /// + public const int IsDecimalFloatingPointOrHasDecimalFloatingPointFields = 0x8000; } private sealed class StaticBlockInfo @@ -198,6 +204,22 @@ public virtual bool IsVectorTOrHasVectorTFields } } + /// + /// Is a type a decimal floating-point type (Decimal32/Decimal64/Decimal128) or transitively + /// have any fields of such a type. + /// + public virtual bool IsDecimalFloatingPointOrHasDecimalFloatingPointFields + { + get + { + if (!_fieldLayoutFlags.HasFlags(FieldLayoutFlags.ComputedInstanceTypeLayout)) + { + ComputeInstanceLayout(InstanceLayoutKind.TypeAndFields); + } + return _fieldLayoutFlags.HasFlags(FieldLayoutFlags.IsDecimalFloatingPointOrHasDecimalFloatingPointFields); + } + } + /// /// The number of bytes required to hold a field of this type /// @@ -500,6 +522,10 @@ public void ComputeInstanceLayout(InstanceLayoutKind layoutKind) { _fieldLayoutFlags.AddFlags(FieldLayoutFlags.IsVectorTOrHasVectorTFields); } + if (computedLayout.IsDecimalFloatingPointOrHasDecimalFloatingPointFields) + { + _fieldLayoutFlags.AddFlags(FieldLayoutFlags.IsDecimalFloatingPointOrHasDecimalFloatingPointFields); + } if (computedLayout.Offsets != null) { diff --git a/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs index 38967539503687..7018caa8484077 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/FieldLayoutAlgorithm.cs @@ -105,6 +105,7 @@ public struct ComputedInstanceFieldLayout public bool IsAutoLayoutOrHasAutoLayoutFields; public bool IsInt128OrHasInt128Fields; public bool IsVectorTOrHasVectorTFields; + public bool IsDecimalFloatingPointOrHasDecimalFloatingPointFields; /// /// If Offsets is non-null, then all field based layout is complete. diff --git a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs index 885db56adb4b84..5a85030e78ee8b 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/MetadataFieldLayoutAlgorithm.cs @@ -113,6 +113,7 @@ out instanceByteSizeAndAlignment IsAutoLayoutOrHasAutoLayoutFields = false, IsInt128OrHasInt128Fields = false, IsVectorTOrHasVectorTFields = false, + IsDecimalFloatingPointOrHasDecimalFloatingPointFields = false, }; if (numInstanceFields > 0) @@ -325,11 +326,13 @@ protected ComputedInstanceFieldLayout ComputeExplicitFieldLayout(MetadataType ty bool hasAutoLayoutField = false; bool hasInt128Field = false; bool hasVectorTField = false; + bool hasDecimalField = false; if (type.BaseType is not null) { hasInt128Field = type.BaseType.IsInt128OrHasInt128Fields; hasVectorTField = type.BaseType.IsVectorTOrHasVectorTFields; + hasDecimalField = type.BaseType.IsDecimalFloatingPointOrHasDecimalFloatingPointFields; } foreach (FieldDesc field in type.GetFields()) @@ -347,6 +350,8 @@ protected ComputedInstanceFieldLayout ComputeExplicitFieldLayout(MetadataType ty hasInt128Field = true; if (fieldData.HasVectorTField) hasVectorTField = true; + if (fieldData.HasDecimalField) + hasDecimalField = true; largestAlignmentRequired = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequired); @@ -401,6 +406,7 @@ protected ComputedInstanceFieldLayout ComputeExplicitFieldLayout(MetadataType ty IsAutoLayoutOrHasAutoLayoutFields = hasAutoLayoutField, IsInt128OrHasInt128Fields = hasInt128Field, IsVectorTOrHasVectorTFields = hasVectorTField, + IsDecimalFloatingPointOrHasDecimalFloatingPointFields = hasDecimalField, }; computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment; computedLayout.FieldSize = instanceSizeAndAlignment.Size; @@ -436,11 +442,13 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType bool hasAutoLayoutField = false; bool hasInt128Field = false; bool hasVectorTField = false; + bool hasDecimalField = false; if (type.BaseType is not null) { hasInt128Field = type.BaseType.IsInt128OrHasInt128Fields; hasVectorTField = type.BaseType.IsVectorTOrHasVectorTFields; + hasDecimalField = type.BaseType.IsDecimalFloatingPointOrHasDecimalFloatingPointFields; } foreach (var field in type.GetFields()) @@ -457,6 +465,8 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType hasInt128Field = true; if (fieldData.HasVectorTField) hasVectorTField = true; + if (fieldData.HasDecimalField) + hasDecimalField = true; largestAlignmentRequirement = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequirement); @@ -485,6 +495,7 @@ protected ComputedInstanceFieldLayout ComputeSequentialFieldLayout(MetadataType IsAutoLayoutOrHasAutoLayoutFields = hasAutoLayoutField, IsInt128OrHasInt128Fields = hasInt128Field, IsVectorTOrHasVectorTFields = hasVectorTField, + IsDecimalFloatingPointOrHasDecimalFloatingPointFields = hasDecimalField, }; computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment; computedLayout.FieldSize = instanceSizeAndAlignment.Size; @@ -514,6 +525,7 @@ protected ComputedInstanceFieldLayout ComputeCStructFieldLayout(MetadataType typ bool hasAutoLayoutField = false; bool hasInt128Field = false; bool hasVectorTField = false; + bool hasDecimalField = false; foreach (var field in type.GetFields()) { @@ -529,6 +541,8 @@ protected ComputedInstanceFieldLayout ComputeCStructFieldLayout(MetadataType typ hasInt128Field = true; if (fieldData.HasVectorTField) hasVectorTField = true; + if (fieldData.HasDecimalField) + hasDecimalField = true; largestAlignmentRequirement = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequirement); @@ -570,6 +584,7 @@ protected ComputedInstanceFieldLayout ComputeCStructFieldLayout(MetadataType typ IsAutoLayoutOrHasAutoLayoutFields = false, IsInt128OrHasInt128Fields = hasInt128Field, IsVectorTOrHasVectorTFields = hasVectorTField, + IsDecimalFloatingPointOrHasDecimalFloatingPointFields = hasDecimalField, FieldAlignment = instanceSizeAndAlignment.Alignment, FieldSize = instanceSizeAndAlignment.Size, ByteCountUnaligned = instanceByteSizeAndAlignment.Size, @@ -599,6 +614,7 @@ protected ComputedInstanceFieldLayout ComputeCUnionFieldLayout(MetadataType type bool hasAutoLayoutField = false; bool hasInt128Field = false; bool hasVectorTField = false; + bool hasDecimalField = false; foreach (var field in type.GetFields()) { @@ -614,6 +630,8 @@ protected ComputedInstanceFieldLayout ComputeCUnionFieldLayout(MetadataType type hasInt128Field = true; if (fieldData.HasVectorTField) hasVectorTField = true; + if (fieldData.HasDecimalField) + hasDecimalField = true; largestAlignmentRequirement = LayoutInt.Max(fieldSizeAndAlignment.Alignment, largestAlignmentRequirement); largestFieldSize = LayoutInt.Max(fieldSizeAndAlignment.Size, largestFieldSize); @@ -655,6 +673,7 @@ protected ComputedInstanceFieldLayout ComputeCUnionFieldLayout(MetadataType type IsAutoLayoutOrHasAutoLayoutFields = false, IsInt128OrHasInt128Fields = hasInt128Field, IsVectorTOrHasVectorTFields = hasVectorTField, + IsDecimalFloatingPointOrHasDecimalFloatingPointFields = hasDecimalField, FieldAlignment = instanceSizeAndAlignment.Alignment, FieldSize = instanceSizeAndAlignment.Size, ByteCountUnaligned = instanceByteSizeAndAlignment.Size, @@ -736,6 +755,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, int[] instanceNonGCPointerFieldsCount = new int[maxLog2Size + 1]; bool hasInt128Field = false; bool hasVectorTField = false; + bool hasDecimalField = false; foreach (var field in type.GetFields()) { @@ -752,6 +772,8 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, hasInt128Field = true; if (((DefType)fieldType).IsVectorTOrHasVectorTFields) hasVectorTField = true; + if (((DefType)fieldType).IsDecimalFloatingPointOrHasDecimalFloatingPointFields) + hasDecimalField = true; } else if (fieldType.IsGCPointer) { @@ -1026,6 +1048,7 @@ protected ComputedInstanceFieldLayout ComputeAutoFieldLayout(MetadataType type, IsAutoLayoutOrHasAutoLayoutFields = true, IsInt128OrHasInt128Fields = hasInt128Field, IsVectorTOrHasVectorTFields = hasVectorTField, + IsDecimalFloatingPointOrHasDecimalFloatingPointFields = hasDecimalField, }; computedLayout.FieldAlignment = instanceSizeAndAlignment.Alignment; computedLayout.FieldSize = instanceSizeAndAlignment.Size; @@ -1105,6 +1128,7 @@ private struct ComputedFieldData public bool HasAutoLayout; public bool HasInt128Field; public bool HasVectorTField; + public bool HasDecimalField; } private static SizeAndAlignment ComputeFieldSizeAndAlignment(TypeDesc fieldType, bool hasLayout, int packingSize, out ComputedFieldData fieldData) @@ -1116,7 +1140,8 @@ private static SizeAndAlignment ComputeFieldSizeAndAlignment(TypeDesc fieldType, LayoutAbiStable = true, HasAutoLayout = true, HasInt128Field = false, - HasVectorTField = false + HasVectorTField = false, + HasDecimalField = false }; if (fieldType.IsDefType) @@ -1130,6 +1155,7 @@ private static SizeAndAlignment ComputeFieldSizeAndAlignment(TypeDesc fieldType, fieldData.HasAutoLayout = defType.IsAutoLayoutOrHasAutoLayoutFields; fieldData.HasInt128Field = defType.IsInt128OrHasInt128Fields; fieldData.HasVectorTField = defType.IsVectorTOrHasVectorTFields; + fieldData.HasDecimalField = defType.IsDecimalFloatingPointOrHasDecimalFloatingPointFields; } else { diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs index 1d251e816d5dee..19f150154841ea 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalHelpers.cs @@ -423,6 +423,12 @@ internal static MarshallerKind GetMarshallerKind( return MarshallerKind.Invalid; } + if (!isField && ((DefType)type).IsDecimalFloatingPointOrHasDecimalFloatingPointFields && !isByRef) + { + // Decimal32/64/128 types or structs that contain them cannot be passed by value + return MarshallerKind.Invalid; + } + if (!isField && ((DefType)type).IsVectorTOrHasVectorTFields) { // Vector types or structs that contain them cannot be passed by value @@ -922,6 +928,7 @@ internal static MarshallerKind GetDisabledMarshallerKind( if (!defType.ContainsGCPointers && !defType.IsAutoLayoutOrHasAutoLayoutFields && !defType.IsInt128OrHasInt128Fields + && !defType.IsDecimalFloatingPointOrHasDecimalFloatingPointFields && IsValidForGenericMarshalling(defType, isFieldScenario, builtInMarshallingEnabled: false)) { return MarshallerKind.BlittableValue; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs index cc3a45e3c9b334..6ae737aa39c41a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilerTypeSystemContext.Aot.cs @@ -37,6 +37,7 @@ public SharedGenericsConfiguration GenericsConfig private readonly VectorOfTFieldLayoutAlgorithm _vectorOfTFieldLayoutAlgorithm; private readonly VectorFieldLayoutAlgorithm _vectorFieldLayoutAlgorithm; private readonly Int128FieldLayoutAlgorithm _int128FieldLayoutAlgorithm; + private readonly DecimalFieldLayoutAlgorithm _decimalFieldLayoutAlgorithm; private readonly TypeWithRepeatedFieldsFieldLayoutAlgorithm _typeWithRepeatedFieldsFieldLayoutAlgorithm; private TypeDesc[] _arrayOfTInterfaces; @@ -58,6 +59,7 @@ public CompilerTypeSystemContext(TargetDetails details, SharedGenericsMode gener _vectorOfTFieldLayoutAlgorithm = new VectorOfTFieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm); _vectorFieldLayoutAlgorithm = new VectorFieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm); _int128FieldLayoutAlgorithm = new Int128FieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm); + _decimalFieldLayoutAlgorithm = new DecimalFieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm); _typeWithRepeatedFieldsFieldLayoutAlgorithm = new TypeWithRepeatedFieldsFieldLayoutAlgorithm(_metadataFieldLayoutAlgorithm); _delegateInfoHashtable = new DelegateInfoHashtable(delegateFeatures); @@ -116,6 +118,8 @@ public override FieldLayoutAlgorithm GetLayoutAlgorithmForType(DefType type) return _vectorFieldLayoutAlgorithm; else if (Int128FieldLayoutAlgorithm.IsIntegerType(type)) return _int128FieldLayoutAlgorithm; + else if (DecimalFieldLayoutAlgorithm.IsDecimalFloatingPointType(type)) + return _decimalFieldLayoutAlgorithm; else if (type is TypeWithRepeatedFields) return _typeWithRepeatedFieldsFieldLayoutAlgorithm; else diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index a1b2f8d86b6d66..e26214457ac8c3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -384,6 +384,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs index edf32f30df371a..eb85bccb5f31ba 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCompilerContext.cs @@ -51,6 +51,7 @@ public partial class ReadyToRunCompilerContext : CompilerTypeSystemContext private VectorOfTFieldLayoutAlgorithm _vectorOfTFieldLayoutAlgorithm; private VectorFieldLayoutAlgorithm _vectorFieldLayoutAlgorithm; private Int128FieldLayoutAlgorithm _int128FieldLayoutAlgorithm; + private DecimalFieldLayoutAlgorithm _decimalFieldLayoutAlgorithm; private TypeWithRepeatedFieldsFieldLayoutAlgorithm _typeWithRepeatedFieldsFieldLayoutAlgorithm; private RuntimeInterfacesAlgorithm _arrayOfTRuntimeInterfacesAlgorithm; @@ -86,6 +87,7 @@ public ReadyToRunCompilerContext( _vectorOfTFieldLayoutAlgorithm = new VectorOfTFieldLayoutAlgorithm(_r2rFieldLayoutAlgorithm, _vectorFieldLayoutAlgorithm, matchingVectorType); _int128FieldLayoutAlgorithm = new Int128FieldLayoutAlgorithm(_r2rFieldLayoutAlgorithm); + _decimalFieldLayoutAlgorithm = new DecimalFieldLayoutAlgorithm(_r2rFieldLayoutAlgorithm); _typeWithRepeatedFieldsFieldLayoutAlgorithm = new TypeWithRepeatedFieldsFieldLayoutAlgorithm(_r2rFieldLayoutAlgorithm); @@ -141,6 +143,10 @@ public override FieldLayoutAlgorithm GetLayoutAlgorithmForType(DefType type) { return _int128FieldLayoutAlgorithm; } + else if (DecimalFieldLayoutAlgorithm.IsDecimalFloatingPointType(type)) + { + return _decimalFieldLayoutAlgorithm; + } else if (type is TypeWithRepeatedFields) { return _typeWithRepeatedFieldsFieldLayoutAlgorithm; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 1607b954067eb7..99f71df813dcfd 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -146,6 +146,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs index 987d49ce274c66..1ffab5e6120f8c 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ArchitectureSpecificFieldLayoutTests.cs @@ -506,6 +506,21 @@ public void TestAlignmentBehavior_AutoAlignmentRules(string wrapperType, int[] a [InlineData("StructStructByte_UInt128StructAuto", "X86", 16, 32)] [InlineData("StructStructByte_UInt128StructAuto", "X64Linux", 16, 32)] [InlineData("StructStructByte_UInt128StructAuto", "X64Windows", 16, 32)] + [InlineData("StructStructByte_Decimal32StructAuto", "ARM64", 4, 8)] + [InlineData("StructStructByte_Decimal32StructAuto", "ARM", 4, 8)] + [InlineData("StructStructByte_Decimal32StructAuto", "X86", 4, 8)] + [InlineData("StructStructByte_Decimal32StructAuto", "X64Linux", 4, 8)] + [InlineData("StructStructByte_Decimal32StructAuto", "X64Windows", 4, 8)] + [InlineData("StructStructByte_Decimal64StructAuto", "ARM64", 8, 16)] + [InlineData("StructStructByte_Decimal64StructAuto", "ARM", 8, 16)] + [InlineData("StructStructByte_Decimal64StructAuto", "X86", 8, 16)] + [InlineData("StructStructByte_Decimal64StructAuto", "X64Linux", 8, 16)] + [InlineData("StructStructByte_Decimal64StructAuto", "X64Windows", 8, 16)] + [InlineData("StructStructByte_Decimal128StructAuto", "ARM64", 16, 32)] + [InlineData("StructStructByte_Decimal128StructAuto", "ARM", 8, 24)] + [InlineData("StructStructByte_Decimal128StructAuto", "X86", 16, 32)] + [InlineData("StructStructByte_Decimal128StructAuto", "X64Linux", 16, 32)] + [InlineData("StructStructByte_Decimal128StructAuto", "X64Windows", 16, 32)] // Variation of TestAlignmentBehavior_AutoAlignmentRules above that is able to deal with os specific behavior public void TestAlignmentBehavior_AutoAlignmentRulesWithOSDependence(string wrapperType, string osArch, int alignment, int size) { diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs index d385dec97fcfe8..6b6ec56a878677 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/InstanceFieldLayout.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Numerics; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -228,6 +229,24 @@ public struct StructStructByte_UInt128StructAuto public Auto.UInt128Struct fld2; } + public struct StructStructByte_Decimal32StructAuto + { + public StructByte fld1; + public Auto.Decimal32Struct fld2; + } + + public struct StructStructByte_Decimal64StructAuto + { + public StructByte fld1; + public Auto.Decimal64Struct fld2; + } + + public struct StructStructByte_Decimal128StructAuto + { + public StructByte fld1; + public Auto.Decimal128Struct fld2; + } + [StructLayout(LayoutKind.Sequential)] public class Class16Align { @@ -499,6 +518,24 @@ public struct Int128Struct Int128 fld1; } + [StructLayout(LayoutKind.Auto)] + public struct Decimal32Struct + { + Decimal32 fld1; + } + + [StructLayout(LayoutKind.Auto)] + public struct Decimal64Struct + { + Decimal64 fld1; + } + + [StructLayout(LayoutKind.Auto)] + public struct Decimal128Struct + { + Decimal128 fld1; + } + [StructLayout(LayoutKind.Sequential)] public class Class16Align { diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs index b2e8be2cf58f60..9f32a260f34f4f 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/Platform.cs @@ -293,3 +293,28 @@ public readonly struct Vector512 private readonly Vector256 _upper; } } + +namespace System.Numerics +{ + [Intrinsic] + [StructLayout(LayoutKind.Sequential)] + public readonly struct Decimal32 + { + private readonly uint _value; + } + + [Intrinsic] + [StructLayout(LayoutKind.Sequential)] + public readonly struct Decimal64 + { + private readonly ulong _value; + } + + [Intrinsic] + [StructLayout(LayoutKind.Sequential)] + public readonly struct Decimal128 + { + private readonly ulong _lower; + private readonly ulong _upper; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj index c28d4484b4b392..df5dd7c2aef63e 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ILCompiler.TypeSystem.Tests.csproj @@ -66,6 +66,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs index 191ad3aa3a4b31..2c42a82f498a83 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/TestTypeSystemContext.cs @@ -26,6 +26,7 @@ internal class TestTypeSystemContext : MetadataTypeSystemContext private VectorFieldLayoutAlgorithm _vectorFieldLayoutAlgorithm; private Int128FieldLayoutAlgorithm _int128FieldLayoutAlgorithm; + private DecimalFieldLayoutAlgorithm _decimalFieldLayoutAlgorithm; private MetadataFieldLayoutAlgorithm _metadataFieldLayout = new TestMetadataFieldLayoutAlgorithm(); private MetadataRuntimeInterfacesAlgorithm _metadataRuntimeInterfacesAlgorithm = new MetadataRuntimeInterfacesAlgorithm(); @@ -39,6 +40,7 @@ public TestTypeSystemContext(TargetArchitecture arch, TargetOS targetOS = Target { _vectorFieldLayoutAlgorithm = new VectorFieldLayoutAlgorithm(_metadataFieldLayout); _int128FieldLayoutAlgorithm = new Int128FieldLayoutAlgorithm(_metadataFieldLayout); + _decimalFieldLayoutAlgorithm = new DecimalFieldLayoutAlgorithm(_metadataFieldLayout); } public ModuleDesc GetModuleForSimpleName(string simpleName) @@ -79,6 +81,10 @@ public override FieldLayoutAlgorithm GetLayoutAlgorithmForType(DefType type) { return _int128FieldLayoutAlgorithm; } + else if (DecimalFieldLayoutAlgorithm.IsDecimalFloatingPointType(type)) + { + return _decimalFieldLayoutAlgorithm; + } return _metadataFieldLayout; } diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 8d86a61273abb9..165ccdaac5c289 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -365,6 +365,9 @@ class EEClassLayoutInfo e_HAS_AUTO_LAYOUT_FIELD_IN_LAYOUT = 0x10, // Type type recursively has a field which is an Int128 e_IS_OR_HAS_INT128_FIELD = 0x20, + // The type recursively has a field which is a decimal floating-point type + // (Decimal32/Decimal64/Decimal128). + e_IS_OR_HAS_DECIMAL_FIELD = 0x40, }; LayoutType m_LayoutType; @@ -419,6 +422,12 @@ class EEClassLayoutInfo return (m_bFlags & e_IS_OR_HAS_INT128_FIELD) == e_IS_OR_HAS_INT128_FIELD; } + BOOL IsDecimalFloatingPointOrHasDecimalFloatingPointFields() const + { + LIMITED_METHOD_CONTRACT; + return (m_bFlags & e_IS_OR_HAS_DECIMAL_FIELD) == e_IS_OR_HAS_DECIMAL_FIELD; + } + BYTE GetAlignmentRequirement() const { LIMITED_METHOD_CONTRACT; @@ -452,6 +461,13 @@ class EEClassLayoutInfo : (m_bFlags & ~e_IS_OR_HAS_INT128_FIELD); } + void SetIsDecimalFloatingPointOrHasDecimalFloatingPointFields(BOOL hasDecimalField) + { + LIMITED_METHOD_CONTRACT; + m_bFlags = hasDecimalField ? (m_bFlags | e_IS_OR_HAS_DECIMAL_FIELD) + : (m_bFlags & ~e_IS_OR_HAS_DECIMAL_FIELD); + } + void SetHasExplicitSize(BOOL hasExplicitSize) { LIMITED_METHOD_CONTRACT; @@ -534,6 +550,7 @@ class EEClassLayoutInfo Align8 = 0x4, AutoLayout = 0x8, Int128 = 0x10, + DecimalFloatingPoint = 0x20, }; static NestedFieldFlags GetNestedFieldFlags(Module* pModule, FieldDesc *pFD, ULONG cFields, CorNativeLinkType nlType, MethodTable** pByValueClassCache); @@ -1376,6 +1393,9 @@ class EEClass // DO NOT CREATE A NEW EEClass USING NEW! // Only accurate on non-auto layout types BOOL IsInt128OrHasInt128Fields(); + // Only accurate on non-auto layout types + BOOL IsDecimalFloatingPointOrHasDecimalFloatingPointFields(); + static void GetBestFitMapping(MethodTable * pMT, BOOL *pfBestFitMapping, BOOL *pfThrowOnUnmappableChar); /* @@ -1961,6 +1981,14 @@ inline BOOL EEClass::IsInt128OrHasInt128Fields() return HasLayout() && GetLayoutInfo()->IsInt128OrHasInt128Fields(); } +inline BOOL EEClass::IsDecimalFloatingPointOrHasDecimalFloatingPointFields() +{ + // As with IsInt128OrHasInt128Fields, this doesn't detect fields on auto layout types, + // but that's sufficient for the interop scenarios where it is used. + LIMITED_METHOD_CONTRACT; + return HasLayout() && GetLayoutInfo()->IsDecimalFloatingPointOrHasDecimalFloatingPointFields(); +} + //========================================================================== // These routines manage the prestub (a bootstrapping stub that all // FunctionDesc's are initialized with.) diff --git a/src/coreclr/vm/classlayoutinfo.cpp b/src/coreclr/vm/classlayoutinfo.cpp index f1db07aedfbb48..99f11a0299c9c3 100644 --- a/src/coreclr/vm/classlayoutinfo.cpp +++ b/src/coreclr/vm/classlayoutinfo.cpp @@ -397,6 +397,16 @@ namespace return FALSE; } + BOOL TypeHasDecimalFloatingPointField(CorElementType corElemType, TypeHandle pNestedType) + { + if (corElemType == ELEMENT_TYPE_VALUETYPE) + { + _ASSERTE(!pNestedType.IsNull()); + return pNestedType.GetMethodTable()->IsDecimalFloatingPointOrHasDecimalFloatingPointFields(); + } + return FALSE; + } + ParseNativeTypeFlags NlTypeToNativeTypeFlags(CorNativeLinkType nlType) { ParseNativeTypeFlags nativeTypeFlags = ParseNativeTypeFlags::None; @@ -488,6 +498,11 @@ auto EEClassLayoutInfo::GetNestedFieldFlags(Module* pModule, FieldDesc *pFields, { flags |= NestedFieldFlags::Int128; } + + if (TypeHasDecimalFloatingPointField(corElemType, typeHandleMaybe)) + { + flags |= NestedFieldFlags::DecimalFloatingPoint; + } } #ifdef FEATURE_DOUBLE_ALIGNMENT_HINT @@ -1069,6 +1084,7 @@ EEClassNativeLayoutInfo* EEClassNativeLayoutInfo::CollectNativeLayoutFieldMetada else if (pMT->HasSameTypeDefAs(CoreLibBinder::GetClass(CLASS__INT128)) || pMT->HasSameTypeDefAs(CoreLibBinder::GetClass(CLASS__UINT128)) || + pMT->HasSameTypeDefAs(CoreLibBinder::GetClass(CLASS__DECIMAL128)) || pMT->HasSameTypeDefAs(CoreLibBinder::GetClass(CLASS__VECTOR64T)) || pMT->HasSameTypeDefAs(CoreLibBinder::GetClass(CLASS__VECTOR128T)) || pMT->HasSameTypeDefAs(CoreLibBinder::GetClass(CLASS__VECTOR256T)) || diff --git a/src/coreclr/vm/classnames.h b/src/coreclr/vm/classnames.h index f56068039ef462..2e973e6b8c1888 100644 --- a/src/coreclr/vm/classnames.h +++ b/src/coreclr/vm/classnames.h @@ -32,6 +32,10 @@ #define g_UInt128ClassName "System.UInt128" #define g_UInt128Name "UInt128" +#define g_Decimal32Name "Decimal32" +#define g_Decimal64Name "Decimal64" +#define g_Decimal128Name "Decimal128" + #define g_Vector64ClassName "System.Runtime.Intrinsics.Vector64`1" #define g_Vector64Name "Vector64`1" diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 1584e25df9f8f1..84f6ff9bf88230 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -256,6 +256,8 @@ DEFINE_FIELD(DELEGATEWRAPPER, VALUE, Value) DEFINE_CLASS(INT128, System, Int128) DEFINE_CLASS(UINT128, System, UInt128) +DEFINE_CLASS(DECIMAL128, Numerics, Decimal128) + DEFINE_CLASS(MATH, System, Math) DEFINE_METHOD(MATH, CONVERT_TO_INT32_CHECKED, ConvertToInt32Checked, NoSig) DEFINE_METHOD(MATH, CONVERT_TO_UINT32_CHECKED, ConvertToUInt32Checked, NoSig) diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index fb110a4677b7bc..66c6028add33fb 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -3469,6 +3469,12 @@ BOOL PInvoke::MarshalingRequired( return TRUE; } + if (hndArgType.GetMethodTable()->IsDecimalFloatingPointOrHasDecimalFloatingPointFields()) + { + // Decimal32/Decimal64/Decimal128 cannot be marshalled by value at this time + return TRUE; + } + // When the runtime runtime marshalling system is disabled, we don't support // any types that contain gc pointers, but all "unmanaged" types are treated as blittable // as long as they aren't auto-layout and don't have any auto-layout fields. diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 5bb833b15d8527..9b35eb266bf302 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -2023,8 +2023,10 @@ static GetTypeLayoutResult GetTypeLayoutHelper( parNode.hasSignificantPadding = pClass->HasExplicitFieldOffsetLayout() || pClass->HasExplicitSize(); // The intrinsic SIMD/HW SIMD types have a lot of fields that the JIT does - // not care about since they are considered primitives by the JIT. - if (pMT->IsIntrinsicType()) + // not care about since they are considered primitives by the JIT. The IEEE 754 + // decimal floating-point types are the System.Numerics intrinsics that are not + // SIMD, so the JIT lays them out like any other struct and needs their fields. + if (pMT->IsIntrinsicType() && !pMT->IsDecimalFloatingPointOrHasDecimalFloatingPointFields()) { const char* nsName; pMT->GetFullyQualifiedNameInfo(&nsName); diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index ad90c8b130abb5..e567af576fe9fe 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -1874,6 +1874,9 @@ class MethodTable // Only accurate on types which are not auto layout inline BOOL IsInt128OrHasInt128Fields(); + // Only accurate on types which are not auto layout + inline BOOL IsDecimalFloatingPointOrHasDecimalFloatingPointFields(); + UINT32 GetNativeSize(); DWORD GetBaseSize() diff --git a/src/coreclr/vm/methodtable.inl b/src/coreclr/vm/methodtable.inl index e6b343e86a2e63..ea7d6a534314e5 100644 --- a/src/coreclr/vm/methodtable.inl +++ b/src/coreclr/vm/methodtable.inl @@ -999,6 +999,13 @@ inline BOOL MethodTable::IsInt128OrHasInt128Fields() return HasLayout() && GetClass()->IsInt128OrHasInt128Fields(); } +//========================================================================================== +inline BOOL MethodTable::IsDecimalFloatingPointOrHasDecimalFloatingPointFields() +{ + LIMITED_METHOD_CONTRACT; + return HasLayout() && GetClass()->IsDecimalFloatingPointOrHasDecimalFloatingPointFields(); +} + //========================================================================================== inline DWORD MethodTable::GetPerInstInfoSize() { diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index ca229d3e0056df..35f9fe4f685db8 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -8325,6 +8325,9 @@ VOID MethodTableBuilder::PlaceInstanceFields(MethodTable** pByValueClassCache) bool hasInt128Field = (pParentMT && pParentMT->IsInt128OrHasInt128Fields()) || ((nestedFieldFlags & EEClassLayoutInfo::NestedFieldFlags::Int128) == EEClassLayoutInfo::NestedFieldFlags::Int128); + bool hasDecimalField = (pParentMT && pParentMT->IsDecimalFloatingPointOrHasDecimalFloatingPointFields()) + || ((nestedFieldFlags & EEClassLayoutInfo::NestedFieldFlags::DecimalFloatingPoint) == EEClassLayoutInfo::NestedFieldFlags::DecimalFloatingPoint); + bool isAlign8 = ((nestedFieldFlags & EEClassLayoutInfo::NestedFieldFlags::Align8) == EEClassLayoutInfo::NestedFieldFlags::Align8) #if defined(FEATURE_64BIT_ALIGNMENT) || (pParentMT && pParentMT->RequiresAlign8()) @@ -8337,6 +8340,7 @@ VOID MethodTableBuilder::PlaceInstanceFields(MethodTable** pByValueClassCache) pLayoutInfo->SetIsBlittable(isBlittable ? TRUE : FALSE); pLayoutInfo->SetHasAutoLayoutField(isAutoLayoutOrHasAutoLayoutField ? TRUE : FALSE); pLayoutInfo->SetIsInt128OrHasInt128Fields(hasInt128Field ? TRUE : FALSE); + pLayoutInfo->SetIsDecimalFloatingPointOrHasDecimalFloatingPointFields(hasDecimalField ? TRUE : FALSE); pLayoutInfo->SetHasExplicitSize(bmtLayout->classSize); if (bmtLayout->layoutType == EEClassLayoutInfo::LayoutType::Sequential) @@ -10747,6 +10751,42 @@ void MethodTableBuilder::CheckForSystemTypes() // Value types // + // The IEEE 754 decimal floating-point types live in System.Numerics and require special ABI + // handling similar to Int128/UInt128 (Decimal128 shares __int128's 16-byte alignment). + if (strcmp(nameSpace, g_NumericsNS) == 0) + { + if ((strcmp(name, g_Decimal32Name) == 0) + || (strcmp(name, g_Decimal64Name) == 0) + || (strcmp(name, g_Decimal128Name) == 0)) + { + EEClassLayoutInfo* pLayout = pClass->GetLayoutInfo(); + pLayout->SetIsDecimalFloatingPointOrHasDecimalFloatingPointFields(TRUE); + + if (strcmp(name, g_Decimal128Name) == 0) + { + // Decimal32/Decimal64 map onto uint/ulong and keep their natural alignment. + // Decimal128 corresponds to the _Decimal128 ABI primitive, which mirrors the + // 16-byte alignment applied to Int128/UInt128. +#ifdef TARGET_ARM + // No _Decimal128 type exists for the Procedure Call Standard for ARM. We default + // to the same alignment as __m128, matching the Int128/UInt128 treatment. + pLayout->SetAlignmentRequirement(8); +#elif defined(TARGET_64BIT) || defined(TARGET_X86) + pLayout->SetAlignmentRequirement(16); // sizeof(_Decimal128) +#elif defined(TARGET_WASM) + // The Wasm Basic C ABI does not define a decimal type; it tracks what the + // clang/LLVM Wasm backend implements, and clang has no _Decimal128. Match + // __int128_t, the only other 16 byte scalar it does define, which is 16 byte + // aligned (including under Emscripten, which only reduces long double to 8). + pLayout->SetAlignmentRequirement(16); +#else +#error Unknown architecture +#endif // TARGET_ARM + } + } + return; + } + // All special value types are in the system namespace if (strcmp(nameSpace, g_SystemNS) != 0) return; diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index dfba06e9a1eba2..7014f82ba9e86e 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -690,6 +690,11 @@ namespace *errorResIDOut = IDS_EE_BADMARSHAL_INT128_RESTRICTION; return MarshalInfo::MARSHAL_TYPE_UNKNOWN; } + if (pMT->IsDecimalFloatingPointOrHasDecimalFloatingPointFields()) + { + *errorResIDOut = IDS_EE_BADMARSHAL_DECIMAL_RESTRICTION; + return MarshalInfo::MARSHAL_TYPE_UNKNOWN; + } *pMTOut = pMT; return MarshalInfo::MARSHAL_TYPE_BLITTABLEVALUECLASS; } @@ -1834,6 +1839,7 @@ MarshalInfo::MarshalInfo(Module* pModule, // * Int128: Represents the 128 bit integer ABI primitive type which requires currently unimplemented handling // * UInt128: Represents the 128 bit integer ABI primitive type which requires currently unimplemented handling + // * Decimal32/Decimal64/Decimal128: IEEE 754 decimal floating-point ABI primitives which require currently unimplemented handling // The field layout is correct, so field scenarios work, but these should not be passed by value as parameters if (!IsFieldScenario() && !m_byref) { @@ -1842,6 +1848,11 @@ MarshalInfo::MarshalInfo(Module* pModule, m_resID = IDS_EE_BADMARSHAL_INT128_RESTRICTION; IfFailGoto(E_FAIL, lFail); } + if (m_pMT->IsDecimalFloatingPointOrHasDecimalFloatingPointFields()) + { + m_resID = IDS_EE_BADMARSHAL_DECIMAL_RESTRICTION; + IfFailGoto(E_FAIL, lFail); + } } if (!m_pMT->HasLayout()) diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index 70fb67d41fa3d5..b91149c6148b97 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Numerics { @@ -13,6 +14,8 @@ namespace System.Numerics /// Represents a decimal floating-point number that uses the IEEE 754 decimal128 interchange format, providing 34 decimal digits of precision. /// /// The IEEE 754 standard defines two interchange encodings for decimal floating-point: binary integer decimal (BID) and densely packed decimal (DPD). Which encoding is used is determined by the underlying ABI for the platform and defaults to BID where the ABI does not otherwise specify. + [Intrinsic] + [StructLayout(LayoutKind.Sequential)] public readonly struct Decimal128 : IComparable, IComparable, diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index dd5c48c0e98814..b46866b0525ac3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -14,6 +14,7 @@ namespace System.Numerics /// Represents a decimal floating-point number that uses the IEEE 754 decimal32 interchange format, providing 7 decimal digits of precision. /// /// The IEEE 754 standard defines two interchange encodings for decimal floating-point: binary integer decimal (BID) and densely packed decimal (DPD). Which encoding is used is determined by the underlying ABI for the platform and defaults to BID where the ABI does not otherwise specify. + [Intrinsic] [StructLayout(LayoutKind.Sequential)] public readonly struct Decimal32 : IComparable, diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index 760dde42f84402..56fde15e2c7d9c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Numerics { @@ -13,6 +14,8 @@ namespace System.Numerics /// Represents a decimal floating-point number that uses the IEEE 754 decimal64 interchange format, providing 16 decimal digits of precision. /// /// The IEEE 754 standard defines two interchange encodings for decimal floating-point: binary integer decimal (BID) and densely packed decimal (DPD). Which encoding is used is determined by the underlying ABI for the platform and defaults to BID where the ABI does not otherwise specify. + [Intrinsic] + [StructLayout(LayoutKind.Sequential)] public readonly struct Decimal64 : IComparable, IComparable, diff --git a/src/tests/Interop/CMakeLists.txt b/src/tests/Interop/CMakeLists.txt index a6c5e76fd93b77..2dece1bea969a9 100644 --- a/src/tests/Interop/CMakeLists.txt +++ b/src/tests/Interop/CMakeLists.txt @@ -13,6 +13,7 @@ SET(CLR_INTEROP_TEST_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) include_directories(common) add_subdirectory(PInvoke/Decimal) +add_subdirectory(PInvoke/Decimal128) add_subdirectory(PInvoke/ArrayWithOffset) add_subdirectory(PInvoke/Delegate) add_subdirectory(PInvoke/Primitives/Int) diff --git a/src/tests/Interop/Interop.csproj b/src/tests/Interop/Interop.csproj index ee10f9f8954ee2..f8451487dfba58 100644 --- a/src/tests/Interop/Interop.csproj +++ b/src/tests/Interop/Interop.csproj @@ -34,6 +34,7 @@ + diff --git a/src/tests/Interop/PInvoke/Decimal128/CMakeLists.txt b/src/tests/Interop/PInvoke/Decimal128/CMakeLists.txt new file mode 100644 index 00000000000000..0ddf5e242b66ae --- /dev/null +++ b/src/tests/Interop/PInvoke/Decimal128/CMakeLists.txt @@ -0,0 +1,7 @@ +project (Decimal128Native) +include ("${CLR_INTEROP_TEST_ROOT}/Interop.cmake") +set(SOURCES + Decimal128Native.cpp +) +add_library (Decimal128Native SHARED ${SOURCES}) +install (TARGETS Decimal128Native DESTINATION bin) diff --git a/src/tests/Interop/PInvoke/Decimal128/Decimal128Native.cpp b/src/tests/Interop/PInvoke/Decimal128/Decimal128Native.cpp new file mode 100644 index 00000000000000..f384bf10c7b6cc --- /dev/null +++ b/src/tests/Interop/PInvoke/Decimal128/Decimal128Native.cpp @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include +#include +#include +#include + +// Decimal128 is treated here purely as a 16-byte, 16-byte-aligned blob of two uint64 halves. +// This mirrors the native _Decimal128 ABI packing without requiring decimal arithmetic support +// (which MSVC and Clang lack), and lets us validate that the managed 16-byte alignment matches +// native. ARM32 is excluded to match the managed rule, which keeps the natural 8-byte alignment +// there as no _Decimal128 exists in its Procedure Call Standard. +struct +#if !defined(TARGET_ARM) +alignas(16) +#endif +Decimal128 { +#if BIGENDIAN + uint64_t upper; + uint64_t lower; +#else + uint64_t lower; + uint64_t upper; +#endif +}; + +struct StructWithDecimal128 +{ + int8_t messUpPadding; + Decimal128 value; +}; + +struct StructJustDecimal128 +{ + Decimal128 value; +}; + +extern "C" DLL_EXPORT Decimal128 STDMETHODCALLTYPE GetDecimal128(uint64_t upper, uint64_t lower) +{ + Decimal128 result; + result.lower = lower; + result.upper = upper; + return result; +} + +extern "C" DLL_EXPORT void STDMETHODCALLTYPE GetDecimal128Out(uint64_t upper, uint64_t lower, char* pValue /* char*, as .NET does not currently guarantee that Decimal128 values are aligned */) +{ + Decimal128 value = GetDecimal128(upper, lower); + memcpy(pValue, &value, sizeof(value)); // Perform unaligned write +} + +extern "C" DLL_EXPORT uint64_t STDMETHODCALLTYPE GetDecimal128Lower(Decimal128 value) +{ + return value.lower; +} + +extern "C" DLL_EXPORT uint64_t STDMETHODCALLTYPE GetDecimal128Lower_S(StructJustDecimal128 value) +{ + return value.value.lower; +} + +// Test that struct alignment behavior matches with the standard OS compiler +extern "C" DLL_EXPORT void STDMETHODCALLTYPE AddStructWithDecimal128_ByRef(char* pLhs, char* pRhs) /* char*, as .NET does not currently guarantee that Decimal128 values are aligned */ +{ + StructWithDecimal128 result = {}; + StructWithDecimal128 lhs; + memcpy(&lhs, pLhs, sizeof(lhs)); // Perform unaligned read + StructWithDecimal128 rhs; + memcpy(&rhs, pRhs, sizeof(rhs)); // Perform unaligned read + + result.messUpPadding = lhs.messUpPadding; + + result.value.lower = lhs.value.lower + rhs.value.lower; + uint64_t carry = (result.value.lower < lhs.value.lower) ? 1 : 0; + result.value.upper = lhs.value.upper + rhs.value.upper + carry; + + memcpy(pLhs, &result, sizeof(result)); // Perform unaligned write +} + +// Present only so the by-value marshaling restriction has real symbols to resolve; these are never +// actually invoked because the marshaler throws MarshalDirectiveException before the call. +extern "C" DLL_EXPORT uint32_t STDMETHODCALLTYPE GetDecimal32Bits(uint32_t value) +{ + return value; +} + +extern "C" DLL_EXPORT uint64_t STDMETHODCALLTYPE GetDecimal64Bits(uint64_t value) +{ + return value; +} diff --git a/src/tests/Interop/PInvoke/Decimal128/Decimal128Test.cs b/src/tests/Interop/PInvoke/Decimal128/Decimal128Test.cs new file mode 100644 index 00000000000000..e3f7c2cd7e6038 --- /dev/null +++ b/src/tests/Interop/PInvoke/Decimal128/Decimal128Test.cs @@ -0,0 +1,110 @@ +// 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.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xunit; + +public struct StructJustDecimal128 +{ + public StructJustDecimal128(Decimal128 val) { value = val; } + public Decimal128 value; +} + +public struct StructWithDecimal128 +{ + public StructWithDecimal128(Decimal128 val) { value = val; messUpPadding = 0x10; } + public byte messUpPadding; + public Decimal128 value; +} + +unsafe partial class Decimal128Native +{ + [DllImport(nameof(Decimal128Native))] + public static extern Decimal128 GetDecimal128(ulong upper, ulong lower); + + [DllImport(nameof(Decimal128Native))] + public static extern void GetDecimal128Out(ulong upper, ulong lower, Decimal128* value); + + [DllImport(nameof(Decimal128Native))] + public static extern void GetDecimal128Out(ulong upper, ulong lower, out Decimal128 value); + + [DllImport(nameof(Decimal128Native))] + public static extern void GetDecimal128Out(ulong upper, ulong lower, out StructJustDecimal128 value); + + [DllImport(nameof(Decimal128Native))] + public static extern ulong GetDecimal128Lower_S(StructJustDecimal128 value); + + [DllImport(nameof(Decimal128Native))] + public static extern ulong GetDecimal128Lower(Decimal128 value); + + [DllImport(nameof(Decimal128Native))] + public static extern void AddStructWithDecimal128_ByRef(ref StructWithDecimal128 lhs, ref StructWithDecimal128 rhs); +} + +unsafe partial class Decimal32Native +{ + [DllImport("Decimal128Native")] + public static extern uint GetDecimal32Bits(Decimal32 value); +} + +unsafe partial class Decimal64Native +{ + [DllImport("Decimal128Native")] + public static extern ulong GetDecimal64Bits(Decimal64 value); +} + +[ActiveIssue("https://github.com/dotnet/runtime/issues/91388", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.PlatformDoesNotSupportNativeTestAssets))] +public unsafe partial class Decimal128Native +{ + // Decimal128 shares the raw 128-bit little/big-endian layout of UInt128, so we can build and + // inspect known bit patterns by reinterpreting a UInt128 without relying on decimal semantics. + private static Decimal128 FromBits(ulong upper, ulong lower) => Unsafe.BitCast(new UInt128(upper, lower)); + + private static UInt128 ToBits(Decimal128 value) => Unsafe.BitCast(value); + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/69399", TestRuntimes.Mono)] + public static void TestDecimal128FieldLayout() + { + // Validates that the ABI-required alignment of Decimal128 within a struct matches the native compiler (16-byte on most targets; 8-byte on ARM32). + StructWithDecimal128 lhs = new StructWithDecimal128(FromBits(11, 12)); + StructWithDecimal128 rhs = new StructWithDecimal128(FromBits(13, 14)); + + AddStructWithDecimal128_ByRef(ref lhs, ref rhs); + Assert.Equal(new UInt128(24, 26), ToBits(lhs.value)); + Assert.Equal((byte)0x10, lhs.messUpPadding); + + Decimal128 value2; + GetDecimal128Out(3, 4, &value2); + Assert.Equal(new UInt128(3, 4), ToBits(value2)); + + GetDecimal128Out(5, 6, out Decimal128 value3); + Assert.Equal(new UInt128(5, 6), ToBits(value3)); + + GetDecimal128Out(7, 8, out StructJustDecimal128 value4); + Assert.Equal(new UInt128(7, 8), ToBits(value4.value)); + + // Until the decimal by-value ABI is implemented, validate that we don't marshal by value. + + // Checking return value + Assert.Throws(() => GetDecimal128(0, 1)); + + // Checking input value as Decimal128 itself + Assert.Throws(() => GetDecimal128Lower(default(Decimal128))); + + // Checking input value as structure wrapping Decimal128 + Assert.Throws(() => GetDecimal128Lower_S(default(StructJustDecimal128))); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/69399", TestRuntimes.Mono)] + public static void TestDecimal32And64MarshalRestriction() + { + // Decimal32/Decimal64 have no native calling convention either, so by-value marshaling is blocked. + Assert.Throws(() => Decimal32Native.GetDecimal32Bits(default(Decimal32))); + Assert.Throws(() => Decimal64Native.GetDecimal64Bits(default(Decimal64))); + } +}