Skip to content

Commit 5d14a8d

Browse files
authored
Convert HELPER_METHOD_FRAME in objectnative.cpp to QCall (#97641)
* Equals * AllocateUninitializedClone * Compare object content in native code * Do not compare padding in Equals * Add test coverage for Equals
1 parent 822533b commit 5d14a8d

File tree

7 files changed

+81
-62
lines changed

7 files changed

+81
-62
lines changed

src/coreclr/System.Private.CoreLib/src/System/Object.CoreCLR.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
45
using System.Runtime.CompilerServices;
56

67
namespace System
@@ -19,7 +20,9 @@ public partial class Object
1920
[Intrinsic]
2021
protected internal unsafe object MemberwiseClone()
2122
{
22-
object clone = RuntimeHelpers.AllocateUninitializedClone(this);
23+
object clone = this;
24+
RuntimeHelpers.AllocateUninitializedClone(ObjectHandleOnStack.Create(ref clone));
25+
Debug.Assert(clone != this);
2326

2427
// copy contents of "this" to the clone
2528

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,32 @@ public static unsafe void PrepareMethod(RuntimeMethodHandle method, RuntimeTypeH
139139
[MethodImpl(MethodImplOptions.InternalCall)]
140140
internal static extern int TryGetHashCode(object o);
141141

142+
public static new unsafe bool Equals(object? o1, object? o2)
143+
{
144+
// Compare by ref for normal classes, by value for value types.
145+
146+
if (ReferenceEquals(o1, o2))
147+
return true;
148+
149+
if (o1 is null || o2 is null)
150+
return false;
151+
152+
MethodTable* pMT = GetMethodTable(o1);
153+
154+
// If it's not a value class, don't compare by value
155+
if (!pMT->IsValueType)
156+
return false;
157+
158+
// Make sure they are the same type.
159+
if (pMT != GetMethodTable(o2))
160+
return false;
161+
162+
// Compare the contents
163+
return ContentEquals(o1, o2);
164+
}
165+
142166
[MethodImpl(MethodImplOptions.InternalCall)]
143-
public static extern new bool Equals(object? o1, object? o2);
167+
private static extern unsafe bool ContentEquals(object o1, object o2);
144168

145169
[Obsolete("OffsetToStringData has been deprecated. Use string.GetPinnableReference() instead.")]
146170
public static int OffsetToStringData
@@ -194,8 +218,8 @@ public static object GetUninitializedObject(
194218
return rt.GetUninitializedObject();
195219
}
196220

197-
[MethodImpl(MethodImplOptions.InternalCall)]
198-
internal static extern object AllocateUninitializedClone(object obj);
221+
[LibraryImport(QCall, EntryPoint = "ObjectNative_AllocateUninitializedClone")]
222+
internal static partial void AllocateUninitializedClone(ObjectHandleOnStack objHandle);
199223

200224
/// <returns>true if given type is reference type or value type that contains references</returns>
201225
[Intrinsic]

src/coreclr/classlibnative/bcltype/objectnative.cpp

Lines changed: 26 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -123,48 +123,22 @@ FCIMPL1(INT32, ObjectNative::TryGetHashCode, Object* obj) {
123123
}
124124
FCIMPLEND
125125

126-
//
127-
// Compare by ref for normal classes, by value for value types.
128-
//
129-
// <TODO>@todo: it would be nice to customize this method based on the
130-
// defining class rather than doing a runtime check whether it is
131-
// a value type.</TODO>
132-
//
133-
134-
FCIMPL2(FC_BOOL_RET, ObjectNative::Equals, Object *pThisRef, Object *pCompareRef)
126+
FCIMPL2(FC_BOOL_RET, ObjectNative::ContentEquals, Object *pThisRef, Object *pCompareRef)
135127
{
136-
CONTRACTL
137-
{
138-
FCALL_CHECK;
139-
INJECT_FAULT(FCThrow(kOutOfMemoryException););
140-
}
141-
CONTRACTL_END;
142-
143-
if (pThisRef == pCompareRef)
144-
FC_RETURN_BOOL(TRUE);
128+
FCALL_CONTRACT;
145129

146-
// Since we are in FCALL, we must handle NULL specially.
147-
if (pThisRef == NULL || pCompareRef == NULL)
148-
FC_RETURN_BOOL(FALSE);
130+
// Should be ensured by caller
131+
_ASSERTE(pThisRef != NULL);
132+
_ASSERTE(pCompareRef != NULL);
133+
_ASSERTE(pThisRef->GetMethodTable() == pCompareRef->GetMethodTable());
149134

150135
MethodTable *pThisMT = pThisRef->GetMethodTable();
151136

152-
// If it's not a value class, don't compare by value
153-
if (!pThisMT->IsValueType())
154-
FC_RETURN_BOOL(FALSE);
155-
156-
// Make sure they are the same type.
157-
if (pThisMT != pCompareRef->GetMethodTable())
158-
FC_RETURN_BOOL(FALSE);
159-
160-
// Compare the contents (size - vtable - sync block index).
161-
DWORD dwBaseSize = pThisMT->GetBaseSize();
162-
if(pThisMT == g_pStringClass)
163-
dwBaseSize -= sizeof(WCHAR);
137+
// Compare the contents
164138
BOOL ret = memcmp(
165-
(void *) (pThisRef+1),
166-
(void *) (pCompareRef+1),
167-
dwBaseSize - sizeof(Object) - sizeof(int)) == 0;
139+
pThisRef->GetData(),
140+
pCompareRef->GetData(),
141+
pThisMT->GetNumInstanceFieldBytes()) == 0;
168142

169143
FC_GC_POLL_RET();
170144

@@ -215,36 +189,34 @@ FCIMPL1(Object*, ObjectNative::GetClass, Object* pThis)
215189
}
216190
FCIMPLEND
217191

218-
FCIMPL1(Object*, ObjectNative::AllocateUninitializedClone, Object* pObjUNSAFE)
192+
extern "C" void QCALLTYPE ObjectNative_AllocateUninitializedClone(QCall::ObjectHandleOnStack objHandle)
219193
{
220-
FCALL_CONTRACT;
221-
222-
// Delegate error handling to managed side (it will throw NullReferenceException)
223-
if (pObjUNSAFE == NULL)
224-
return NULL;
194+
QCALL_CONTRACT;
225195

226-
OBJECTREF refClone = ObjectToOBJECTREF(pObjUNSAFE);
196+
BEGIN_QCALL;
227197

228-
HELPER_METHOD_FRAME_BEGIN_RET_1(refClone);
198+
GCX_COOP();
229199

200+
OBJECTREF refClone = objHandle.Get();
201+
_ASSERTE(refClone != NULL); // Should be handled at managed side
230202
MethodTable* pMT = refClone->GetMethodTable();
231-
203+
232204
// assert that String has overloaded the Clone() method
233205
_ASSERTE(pMT != g_pStringClass);
234-
235-
if (pMT->IsArray()) {
236-
refClone = DupArrayForCloning((BASEARRAYREF)refClone);
237-
} else {
206+
207+
if (pMT->IsArray())
208+
{
209+
objHandle.Set(DupArrayForCloning((BASEARRAYREF)refClone));
210+
}
211+
else
212+
{
238213
// We don't need to call the <cinit> because we know
239214
// that it has been called....(It was called before this was created)
240-
refClone = AllocateObject(pMT);
215+
objHandle.Set(AllocateObject(pMT));
241216
}
242217

243-
HELPER_METHOD_FRAME_END();
244-
245-
return OBJECTREFToObject(refClone);
218+
END_QCALL;
246219
}
247-
FCIMPLEND
248220

249221
extern "C" BOOL QCALLTYPE Monitor_Wait(QCall::ObjectHandleOnStack pThis, INT32 Timeout)
250222
{

src/coreclr/classlibnative/bcltype/objectnative.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ class ObjectNative
2727

2828
static FCDECL1(INT32, GetHashCode, Object* vThisRef);
2929
static FCDECL1(INT32, TryGetHashCode, Object* vThisRef);
30-
static FCDECL2(FC_BOOL_RET, Equals, Object *pThisRef, Object *pCompareRef);
31-
static FCDECL1(Object*, AllocateUninitializedClone, Object* pObjUNSAFE);
30+
static FCDECL2(FC_BOOL_RET, ContentEquals, Object *pThisRef, Object *pCompareRef);
3231
static FCDECL1(Object*, GetClass, Object* pThis);
3332
static FCDECL1(FC_BOOL_RET, IsLockHeld, Object* pThisUNSAFE);
3433
};
3534

35+
extern "C" void QCALLTYPE ObjectNative_AllocateUninitializedClone(QCall::ObjectHandleOnStack objHandle);
3636
extern "C" BOOL QCALLTYPE Monitor_Wait(QCall::ObjectHandleOnStack pThis, INT32 Timeout);
3737
extern "C" void QCALLTYPE Monitor_Pulse(QCall::ObjectHandleOnStack pThis);
3838
extern "C" void QCALLTYPE Monitor_PulseAll(QCall::ObjectHandleOnStack pThis);

src/coreclr/vm/ecalllist.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,8 +471,7 @@ FCFuncStart(gRuntimeHelpers)
471471
FCFuncElement("PrepareDelegate", ReflectionInvocation::PrepareDelegate)
472472
FCFuncElement("GetHashCode", ObjectNative::GetHashCode)
473473
FCFuncElement("TryGetHashCode", ObjectNative::TryGetHashCode)
474-
FCFuncElement("Equals", ObjectNative::Equals)
475-
FCFuncElement("AllocateUninitializedClone", ObjectNative::AllocateUninitializedClone)
474+
FCFuncElement("ContentEquals", ObjectNative::ContentEquals)
476475
FCFuncElement("EnsureSufficientExecutionStack", ReflectionInvocation::EnsureSufficientExecutionStack)
477476
FCFuncElement("TryEnsureSufficientExecutionStack", ReflectionInvocation::TryEnsureSufficientExecutionStack)
478477
FCFuncElement("AllocTailCallArgBuffer", TailCallHelp::AllocTailCallArgBuffer)

src/coreclr/vm/qcallentrypoints.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ static const Entry s_QCall[] =
324324
DllImportEntry(GetFileLoadExceptionMessage)
325325
DllImportEntry(FileLoadException_GetMessageForHR)
326326
DllImportEntry(Interlocked_MemoryBarrierProcessWide)
327+
DllImportEntry(ObjectNative_AllocateUninitializedClone)
327328
DllImportEntry(Monitor_Wait)
328329
DllImportEntry(Monitor_Pulse)
329330
DllImportEntry(Monitor_PulseAll)

src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,26 @@ public static unsafe void GetObjectValue()
6868
Assert.Equal(i, (int)iOV);
6969
}
7070

71+
[Fact]
72+
public static void EqualsTest()
73+
{
74+
// Boolean RuntimeHelpers.Equals(Object, Object)
75+
76+
Assert.True(RuntimeHelpers.Equals(Guid.Empty, Guid.Empty));
77+
Assert.False(RuntimeHelpers.Equals(Guid.Empty, Guid.NewGuid()));
78+
79+
// Reference equal
80+
object o = new object();
81+
Assert.True(RuntimeHelpers.Equals(o, o));
82+
83+
// Type mismatch
84+
Assert.False(RuntimeHelpers.Equals(Guid.Empty, string.Empty));
85+
86+
// Non value types
87+
Assert.False(RuntimeHelpers.Equals(new object(), new object()));
88+
Assert.False(RuntimeHelpers.Equals(new int[] { 1, 2, 3 }, new int[] { 1, 2, 3 }));
89+
}
90+
7191
[Fact]
7292
public static void InitializeArray()
7393
{

0 commit comments

Comments
 (0)