Skip to content

Commit 79e0e27

Browse files
authored
Implement System.Runtime.InteropServices.CLong/CULong/NFloat (#46401)
* Implement managed side of CLong/CULong/NFloat. * Make CLong, CULong, and NFloat intrinsically handled correctly by the JIT. * Add framework tests for CLong, CULong, and NFloat. * Add interop test of CLong to validate calling convention semantics. * Update CULong.cs * Fix implicit conversions. * Fix overflow and equality test failures. * Fix formatting. * Fix formatting and add function header. * Add doc comments. * Don't throw on float out of range. Rename tests. * Rewrite EqualsTest implementations more straightforward. * Fix NFloat tests. * Use .Equals instead of == * Use ToString directly instead of hard coding the expected value. * Update the emitted assembly stub's thiscall retbuf support for x86 to account for the new native exchange types. * Add sizeof tests. * Add test with struct containing CLong. * Disable ThisCallTest on Mono due to #46820 * validate type name.
1 parent 689bcb2 commit 79e0e27

File tree

21 files changed

+986
-224
lines changed

21 files changed

+986
-224
lines changed

src/coreclr/jit/compiler.cpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,35 @@ bool Compiler::isTrivialPointerSizedStruct(CORINFO_CLASS_HANDLE clsHnd) const
560560
}
561561
#endif // TARGET_X86
562562

563+
//---------------------------------------------------------------------------
564+
// isNativePrimitiveStructType:
565+
// Check if the given struct type is an intrinsic type that should be treated as though
566+
// it is not a struct at the unmanaged ABI boundary.
567+
//
568+
// Arguments:
569+
// clsHnd - the handle for the struct type.
570+
//
571+
// Return Value:
572+
// true if the given struct type should be treated as a primitive for unmanaged calls,
573+
// false otherwise.
574+
//
575+
bool Compiler::isNativePrimitiveStructType(CORINFO_CLASS_HANDLE clsHnd)
576+
{
577+
if (!isIntrinsicType(clsHnd))
578+
{
579+
return false;
580+
}
581+
const char* namespaceName = nullptr;
582+
const char* typeName = getClassNameFromMetadata(clsHnd, &namespaceName);
583+
584+
if (strcmp(namespaceName, "System.Runtime.InteropServices") != 0)
585+
{
586+
return false;
587+
}
588+
589+
return strcmp(typeName, "CLong") == 0 || strcmp(typeName, "CULong") == 0 || strcmp(typeName, "NFloat") == 0;
590+
}
591+
563592
//-----------------------------------------------------------------------------
564593
// getPrimitiveTypeForStruct:
565594
// Get the "primitive" type that is is used for a struct
@@ -1013,14 +1042,14 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
10131042
}
10141043
}
10151044
#elif UNIX_X86_ABI
1016-
if (callConv != CorInfoCallConvExtension::Managed)
1045+
if (callConv != CorInfoCallConvExtension::Managed && !isNativePrimitiveStructType(clsHnd))
10171046
{
10181047
canReturnInRegister = false;
10191048
howToReturnStruct = SPK_ByReference;
10201049
useType = TYP_UNKNOWN;
10211050
}
10221051
#elif defined(TARGET_WINDOWS) && !defined(TARGET_ARM)
1023-
if (callConvIsInstanceMethodCallConv(callConv))
1052+
if (callConvIsInstanceMethodCallConv(callConv) && !isNativePrimitiveStructType(clsHnd))
10241053
{
10251054
canReturnInRegister = false;
10261055
howToReturnStruct = SPK_ByReference;

src/coreclr/jit/compiler.h

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5046,6 +5046,10 @@ class Compiler
50465046
// Convert a BYTE which represents the VM's CorInfoGCtype to the JIT's var_types
50475047
var_types getJitGCType(BYTE gcType);
50485048

5049+
// Returns true if the provided type should be treated as a primitive type
5050+
// for the unmanaged calling conventions.
5051+
bool isNativePrimitiveStructType(CORINFO_CLASS_HANDLE clsHnd);
5052+
50495053
enum structPassingKind
50505054
{
50515055
SPK_Unknown, // Invalid value, never returned
@@ -8047,6 +8051,21 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
80478051
#endif
80488052
}
80498053

8054+
bool isIntrinsicType(CORINFO_CLASS_HANDLE clsHnd)
8055+
{
8056+
return info.compCompHnd->isIntrinsicType(clsHnd);
8057+
}
8058+
8059+
const char* getClassNameFromMetadata(CORINFO_CLASS_HANDLE cls, const char** namespaceName)
8060+
{
8061+
return info.compCompHnd->getClassNameFromMetadata(cls, namespaceName);
8062+
}
8063+
8064+
CORINFO_CLASS_HANDLE getTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index)
8065+
{
8066+
return info.compCompHnd->getTypeInstantiationArgument(cls, index);
8067+
}
8068+
80508069
#ifdef FEATURE_SIMD
80518070

80528071
// Should we support SIMD intrinsics?
@@ -8265,21 +8284,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
82658284
return false;
82668285
}
82678286

8268-
bool isIntrinsicType(CORINFO_CLASS_HANDLE clsHnd)
8269-
{
8270-
return info.compCompHnd->isIntrinsicType(clsHnd);
8271-
}
8272-
8273-
const char* getClassNameFromMetadata(CORINFO_CLASS_HANDLE cls, const char** namespaceName)
8274-
{
8275-
return info.compCompHnd->getClassNameFromMetadata(cls, namespaceName);
8276-
}
8277-
8278-
CORINFO_CLASS_HANDLE getTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index)
8279-
{
8280-
return info.compCompHnd->getTypeInstantiationArgument(cls, index);
8281-
}
8282-
82838287
bool isSIMDClass(typeInfo* pTypeInfo)
82848288
{
82858289
return pTypeInfo->IsStruct() && isSIMDClass(pTypeInfo->GetClassHandleForValueClass());

src/coreclr/vm/dllimportcallback.cpp

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ VOID UMEntryThunk::CompileUMThunkWorker(UMThunkStubInfo *pInfo,
178178
// push edx - repush the return address
179179
pcpusl->X86EmitPushReg(kEDX);
180180
}
181-
181+
182182
// The native signature doesn't have a return buffer
183183
// but the managed signature does.
184184
// Set up the return buffer address here.
@@ -188,12 +188,12 @@ VOID UMEntryThunk::CompileUMThunkWorker(UMThunkStubInfo *pInfo,
188188
// Calculate the offset to the return buffer we establish for EAX:EDX below.
189189
// lea edx [esp - offset to EAX:EDX return buffer]
190190
pcpusl->X86EmitEspOffset(0x8d, kEDX, -0xc /* skip return addr, EBP, EBX */ -0x8 /* point to start of EAX:EDX return buffer */ );
191-
191+
192192
// exchange edx (which has the return buffer address)
193193
// with the return address
194194
// xchg edx, [esp]
195-
pcpusl->X86EmitOp(0x87, kEDX, (X86Reg)kESP_Unsafe);
196-
195+
pcpusl->X86EmitOp(0x87, kEDX, (X86Reg)kESP_Unsafe);
196+
197197
// push edx
198198
pcpusl->X86EmitPushReg(kEDX);
199199
}
@@ -497,7 +497,7 @@ VOID UMEntryThunk::CompileUMThunkWorker(UMThunkStubInfo *pInfo,
497497
pcpusl->X86EmitIndexRegStore(kEBX, -0x8 /* to outer EBP */ -0x8 /* skip saved EBP, EBX */, kEDX);
498498
}
499499
// In the umtmlBufRetValToEnreg case,
500-
// we set up the return buffer to output
500+
// we set up the return buffer to output
501501
// into the EDX:EAX buffer we set up for the register return case.
502502
// So we don't need to do more work here.
503503
else if ((pInfo->m_wFlags & umtmlBufRetValToEnreg) == 0)
@@ -684,7 +684,7 @@ Stub *UMThunkMarshInfo::CompileNExportThunk(LoaderHeap *pLoaderHeap, PInvokeStat
684684
UINT nOffset = 0;
685685
int numRegistersUsed = 0;
686686
int numStackSlotsIndex = nStackBytes / STACK_ELEM_SIZE;
687-
687+
688688
// This could have been set in the UnmanagedCallersOnly scenario.
689689
if (m_callConv == UINT16_MAX)
690690
m_callConv = static_cast<UINT16>(pSigInfo->GetCallConv());
@@ -699,8 +699,30 @@ Stub *UMThunkMarshInfo::CompileNExportThunk(LoaderHeap *pLoaderHeap, PInvokeStat
699699
numRegistersUsed++;
700700
}
701701

702+
bool hasReturnBuffer = argit.HasRetBuffArg() || (m_callConv == pmCallConvThiscall && argit.HasValueTypeReturn());
703+
bool hasNativeExchangeTypeReturn = false;
704+
705+
if (hasReturnBuffer)
706+
{
707+
// If think we have a return buffer, lets make sure that we aren't returning one of the intrinsic native exchange types.
708+
TypeHandle returnType = pMetaSig->GetRetTypeHandleThrowing();
709+
if (returnType.GetMethodTable()->IsIntrinsicType())
710+
{
711+
LPCUTF8 pszNamespace;
712+
LPCUTF8 pszTypeName = returnType.GetMethodTable()->GetFullyQualifiedNameInfo(&pszNamespace);
713+
if ((strcmp(pszNamespace, g_InteropServicesNS) == 0)
714+
&& (strcmp(pszTypeName, "CLong") == 0 || strcmp(pszTypeName, "CULong") == 0 || strcmp(pszTypeName, "NFloat") == 0))
715+
{
716+
// We have one of the intrinsic native exchange types.
717+
// As a result, we don't have a return buffer.
718+
hasReturnBuffer = false;
719+
hasNativeExchangeTypeReturn = true;
720+
}
721+
}
722+
}
723+
702724
// process the return buffer parameter
703-
if (argit.HasRetBuffArg() || (m_callConv == pmCallConvThiscall && argit.HasValueTypeReturn()))
725+
if (hasReturnBuffer)
704726
{
705727
// Only copy the retbuf arg from the src call when both the managed call and native call
706728
// have a return buffer.
@@ -808,7 +830,7 @@ Stub *UMThunkMarshInfo::CompileNExportThunk(LoaderHeap *pLoaderHeap, PInvokeStat
808830
{
809831
stubInfo.m_wFlags |= umtmlThisCallHiddenArg;
810832
}
811-
else if (argit.HasValueTypeReturn())
833+
else if (argit.HasValueTypeReturn() && !hasNativeExchangeTypeReturn)
812834
{
813835
stubInfo.m_wFlags |= umtmlThisCallHiddenArg | umtmlEnregRetValToBuf;
814836
// When the native signature has a return buffer but the

src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public static partial class PlatformDetection
4343
public static bool IsArgIteratorSupported => IsMonoRuntime || (IsWindows && IsNotArmProcess);
4444
public static bool IsArgIteratorNotSupported => !IsArgIteratorSupported;
4545
public static bool Is32BitProcess => IntPtr.Size == 4;
46+
public static bool Is64BitProcess => IntPtr.Size == 8;
4647
public static bool IsNotWindows => !IsWindows;
4748

4849
public static bool IsThreadingSupported => !IsBrowser;

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,7 @@
708708
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CharSet.cs" />
709709
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ClassInterfaceAttribute.cs" />
710710
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ClassInterfaceType.cs" />
711+
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CLong.cs" />
711712
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CoClassAttribute.cs" />
712713
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CollectionsMarshal.cs" />
713714
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ComDefaultInterfaceAttribute.cs" />
@@ -736,6 +737,7 @@
736737
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ComTypes\ITypeLib2.cs" />
737738
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\ComVisibleAttribute.cs" />
738739
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CriticalHandle.cs" />
740+
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CULong.cs" />
739741
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CurrencyWrapper.cs" />
740742
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CustomQueryInterfaceMode.cs" />
741743
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\CustomQueryInterfaceResult.cs" />
@@ -770,6 +772,7 @@
770772
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\MemoryMarshal.cs" />
771773
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\UnmanagedCallersOnlyAttribute.cs" />
772774
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\NativeLibrary.cs" />
775+
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\NFloat.cs" />
773776
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\OptionalAttribute.cs" />
774777
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\OutAttribute.cs" />
775778
<Compile Include="$(MSBuildThisFileDirectory)System\Runtime\InteropServices\PreserveSigAttribute.cs" />
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
#pragma warning disable SA1121 // We use our own aliases since they differ per platform
7+
#if TARGET_WINDOWS
8+
using NativeType = System.Int32;
9+
#else
10+
using NativeType = System.IntPtr;
11+
#endif
12+
13+
namespace System.Runtime.InteropServices
14+
{
15+
/// <summary>
16+
/// <see cref="CLong"/> is an immutable value type that represents the <c>long</c> type in C and C++.
17+
/// It is meant to be used as an exchange type at the managed/unmanaged boundary to accurately represent
18+
/// in managed code unmanaged APIs that use the <c>long</c> type.
19+
/// This type has 32-bits of storage on all Windows platforms and 32-bit Unix-based platforms.
20+
/// It has 64-bits of storage on 64-bit Unix platforms.
21+
/// </summary>
22+
[CLSCompliant(false)]
23+
[Intrinsic]
24+
public readonly struct CLong : IEquatable<CLong>
25+
{
26+
private readonly NativeType _value;
27+
28+
/// <summary>
29+
/// Constructs an instance from a 32-bit integer.
30+
/// </summary>
31+
/// <param name="value">The integer vaule.</param>
32+
public CLong(int value)
33+
{
34+
_value = (NativeType)value;
35+
}
36+
37+
/// <summary>
38+
/// Constructs an instance from a native sized integer.
39+
/// </summary>
40+
/// <param name="value">The integer vaule.</param>
41+
/// <exception cref="OverflowException"><paramref name="value"/> is outside the range of the underlying storage type.</exception>
42+
public CLong(nint value)
43+
{
44+
_value = checked((NativeType)value);
45+
}
46+
47+
/// <summary>
48+
/// The underlying integer value of this instance.
49+
/// </summary>
50+
public nint Value => _value;
51+
52+
/// <summary>
53+
/// Returns a value indicating whether this instance is equal to a specified object.
54+
/// </summary>
55+
/// <param name="o">An object to compare with this instance.</param>
56+
/// <returns><c>true</c> if <paramref name="o"/> is an instance of <see cref="CLong"/> and equals the value of this instance; otherwise, <c>false</c>.</returns>
57+
public override bool Equals(object? o) => o is CLong other && Equals(other);
58+
59+
/// <summary>
60+
/// Returns a value indicating whether this instance is equal to a specified <see cref="CLong"/> value.
61+
/// </summary>
62+
/// <param name="other">A <see cref="CLong"/> value to compare to this instance.</param>
63+
/// <returns><c>true</c> if <paramref name="other"/> has the same value as this instance; otherwise, <c>false</c>.</returns>
64+
public bool Equals(CLong other) => _value == other._value;
65+
66+
/// <summary>
67+
/// Returns the hash code for this instance.
68+
/// </summary>
69+
/// <returns>A 32-bit signed integer hash code.</returns>
70+
public override int GetHashCode() => _value.GetHashCode();
71+
72+
/// <summary>
73+
/// Converts the numeric value of this instance to its equivalent string representation.
74+
/// </summary>
75+
/// <returns>The string representation of the value of this instance, consisting of a negative sign if the value is negative, and a sequence of digits ranging from 0 to 9 with no leading zeroes.</returns>
76+
public override string ToString() => _value.ToString();
77+
}
78+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
#pragma warning disable SA1121 // We use our own aliases since they differ per platform
7+
#if TARGET_WINDOWS
8+
using NativeType = System.UInt32;
9+
#else
10+
using NativeType = System.UIntPtr;
11+
#endif
12+
13+
namespace System.Runtime.InteropServices
14+
{
15+
/// <summary>
16+
/// <see cref="CULong"/> is an immutable value type that represents the <c>unsigned long</c> type in C and C++.
17+
/// It is meant to be used as an exchange type at the managed/unmanaged boundary to accurately represent
18+
/// in managed code unmanaged APIs that use the <c>unsigned long</c> type.
19+
/// This type has 32-bits of storage on all Windows platforms and 32-bit Unix-based platforms.
20+
/// It has 64-bits of storage on 64-bit Unix platforms.
21+
/// </summary>
22+
[CLSCompliant(false)]
23+
[Intrinsic]
24+
public readonly struct CULong : IEquatable<CULong>
25+
{
26+
private readonly NativeType _value;
27+
28+
/// <summary>
29+
/// Constructs an instance from a 32-bit unsigned integer.
30+
/// </summary>
31+
/// <param name="value">The integer vaule.</param>
32+
public CULong(uint value)
33+
{
34+
_value = (NativeType)value;
35+
}
36+
37+
/// <summary>
38+
/// Constructs an instance from a native sized unsigned integer.
39+
/// </summary>
40+
/// <param name="value">The integer vaule.</param>
41+
/// <exception cref="OverflowException"><paramref name="value"/> is outside the range of the underlying storage type.</exception>
42+
public CULong(nuint value)
43+
{
44+
_value = checked((NativeType)value);
45+
}
46+
47+
/// <summary>
48+
/// The underlying integer value of this instance.
49+
/// </summary>
50+
public nuint Value => _value;
51+
52+
/// <summary>
53+
/// Returns a value indicating whether this instance is equal to a specified object.
54+
/// </summary>
55+
/// <param name="o">An object to compare with this instance.</param>
56+
/// <returns><c>true</c> if <paramref name="o"/> is an instance of <see cref="CULong"/> and equals the value of this instance; otherwise, <c>false</c>.</returns>
57+
public override bool Equals(object? o) => o is CULong other && Equals(other);
58+
59+
/// <summary>
60+
/// Returns a value indicating whether this instance is equal to a specified <see cref="CLong"/> value.
61+
/// </summary>
62+
/// <param name="other">A <see cref="CULong"/> value to compare to this instance.</param>
63+
/// <returns><c>true</c> if <paramref name="other"/> has the same value as this instance; otherwise, <c>false</c>.</returns>
64+
public bool Equals(CULong other) => _value == other._value;
65+
66+
/// <summary>
67+
/// Returns the hash code for this instance.
68+
/// </summary>
69+
/// <returns>A 32-bit signed integer hash code.</returns>
70+
public override int GetHashCode() => _value.GetHashCode();
71+
72+
/// <summary>
73+
/// Converts the numeric value of this instance to its equivalent string representation.
74+
/// </summary>
75+
/// <returns>The string representation of the value of this instance, consisting of a sequence of digits ranging from 0 to 9 with no leading zeroes.</returns>
76+
public override string ToString() => _value.ToString();
77+
}
78+
}

0 commit comments

Comments
 (0)