Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,9 @@ public int MultiDimensionalArrayRank
return (int)((BaseSize - (uint)(3 * sizeof(IntPtr))) / (uint)(2 * sizeof(int)));
}
}

[MethodImpl(MethodImplOptions.InternalCall)]
public extern uint GetNumInstanceFieldBytes();
}

// Helper structs used for tail calls via helper.
Expand Down
55 changes: 39 additions & 16 deletions src/coreclr/System.Private.CoreLib/src/System/ValueType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
**
===========================================================*/

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;

namespace System
{
Expand All @@ -22,34 +24,34 @@ public abstract class ValueType
{
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
Justification = "Trimmed fields don't make a difference for equality")]
public override bool Equals([NotNullWhen(true)] object? obj)
public unsafe override bool Equals([NotNullWhen(true)] object? obj)
{
if (null == obj)
{
return false;
}
Type thisType = this.GetType();
Type thatType = obj.GetType();

if (thatType != thisType)
if (GetType() != obj.GetType())
{
return false;
}

object thisObj = (object)this;
object? thisResult, thatResult;

// if there are no GC references in this object we can avoid reflection
// and do a fast memcmp
if (CanCompareBits(this))
return FastEqualsCheck(thisObj, obj);
{
return SpanHelpers.SequenceEqual(
ref RuntimeHelpers.GetRawData(this),
ref RuntimeHelpers.GetRawData(obj),
RuntimeHelpers.GetMethodTable(this)->GetNumInstanceFieldBytes());
}

FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
FieldInfo[] thisFields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

for (int i = 0; i < thisFields.Length; i++)
{
thisResult = thisFields[i].GetValue(thisObj);
thatResult = thisFields[i].GetValue(obj);
object? thisResult = thisFields[i].GetValue(this);
object? thatResult = thisFields[i].GetValue(obj);

if (thisResult == null)
{
Expand All @@ -69,9 +71,6 @@ public override bool Equals([NotNullWhen(true)] object? obj)
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool CanCompareBits(object obj);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool FastEqualsCheck(object a, object b);

/*=================================GetHashCode==================================
**Action: Our algorithm for returning the hashcode is a little bit complex. We look
** for the first non-static field and get its hashcode. If the type has no
Expand All @@ -85,8 +84,32 @@ public override bool Equals([NotNullWhen(true)] object? obj)
[MethodImpl(MethodImplOptions.InternalCall)]
public extern override int GetHashCode();

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int GetHashCodeOfPtr(IntPtr ptr);
private static int s_seed;

internal static int GetHashCodeOfPtr(IntPtr ptr)
{
int hashCode = (int)ptr;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is dropping the upper bits of a 64 bit pointer. Is this wise? All pointers offset by 4GB will result in the same hash code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is for runtime handles which are pointers to unmanaged tables. I don't expect they would span across 4GB.


if (hashCode == 0)
{
return 0;
}

int seed = s_seed;

// Initialize s_seed lazily
if (seed == 0)
{
// We use the first non-0 pointer as the seed, all hashcodes will be based off that.
// This is to make sure that we only reveal relative memory addresses and never absolute ones.
seed = hashCode;
Interlocked.CompareExchange(ref s_seed, seed, 0);
seed = s_seed;
}

Debug.Assert(s_seed != 0);
return hashCode - seed;
}

public override string? ToString()
{
Expand Down
42 changes: 2 additions & 40 deletions src/coreclr/vm/comutilnative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1847,21 +1847,6 @@ FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
}
FCIMPLEND

FCIMPL2(FC_BOOL_RET, ValueTypeHelper::FastEqualsCheck, Object* obj1, Object* obj2)
{
FCALL_CONTRACT;

_ASSERTE(obj1 != NULL);
_ASSERTE(obj2 != NULL);
_ASSERTE(!obj1->GetMethodTable()->ContainsPointers());
_ASSERTE(obj1->GetSize() == obj2->GetSize());

TypeHandle pTh = obj1->GetTypeHandle();

FC_RETURN_BOOL(memcmp(obj1->GetData(),obj2->GetData(),pTh.GetSize()) == 0);
}
FCIMPLEND

static INT32 FastGetValueTypeHashCodeHelper(MethodTable *mt, void *pObjRef)
{
CONTRACTL
Expand Down Expand Up @@ -2044,33 +2029,10 @@ FCIMPL1(INT32, ValueTypeHelper::GetHashCode, Object* objUNSAFE)
}
FCIMPLEND

static LONG s_dwSeed;

FCIMPL1(INT32, ValueTypeHelper::GetHashCodeOfPtr, LPVOID ptr)
FCIMPL1(UINT32, MethodTableNative::GetNumInstanceFieldBytes, MethodTable* mt)
{
FCALL_CONTRACT;

INT32 hashCode = (INT32)((INT64)(ptr));

if (hashCode == 0)
{
return 0;
}

DWORD dwSeed = s_dwSeed;

// Initialize s_dwSeed lazily
if (dwSeed == 0)
{
// We use the first non-0 pointer as the seed, all hashcodes will be based off that.
// This is to make sure that we only reveal relative memory addresses and never absolute ones.
dwSeed = hashCode;
InterlockedCompareExchange(&s_dwSeed, dwSeed, 0);
dwSeed = s_dwSeed;
}
_ASSERTE(dwSeed != 0);

return hashCode - dwSeed;
return mt->GetNumInstanceFieldBytes();
}
FCIMPLEND

Expand Down
7 changes: 5 additions & 2 deletions src/coreclr/vm/comutilnative.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,12 @@ extern "C" void QCALLTYPE Interlocked_MemoryBarrierProcessWide();
class ValueTypeHelper {
public:
static FCDECL1(FC_BOOL_RET, CanCompareBits, Object* obj);
static FCDECL2(FC_BOOL_RET, FastEqualsCheck, Object* obj1, Object* obj2);
static FCDECL1(INT32, GetHashCode, Object* objRef);
static FCDECL1(INT32, GetHashCodeOfPtr, LPVOID ptr);
};

class MethodTableNative {
public:
static FCDECL1(UINT32, GetNumInstanceFieldBytes, MethodTable* mt);
};

class StreamNative {
Expand Down
7 changes: 5 additions & 2 deletions src/coreclr/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ FCFuncEnd()

FCFuncStart(gValueTypeFuncs)
FCFuncElement("CanCompareBits", ValueTypeHelper::CanCompareBits)
FCFuncElement("FastEqualsCheck", ValueTypeHelper::FastEqualsCheck)
FCFuncElement("GetHashCode", ValueTypeHelper::GetHashCode)
FCFuncElement("GetHashCodeOfPtr", ValueTypeHelper::GetHashCodeOfPtr)
FCFuncEnd()

FCFuncStart(gDiagnosticsDebugger)
Expand Down Expand Up @@ -605,6 +603,10 @@ FCFuncStart(gRuntimeHelpers)
FCFuncElement("UnregisterForGCReporting", GCReporting::Unregister)
FCFuncEnd()

FCFuncStart(gMethodTableFuncs)
FCFuncElement("GetNumInstanceFieldBytes", MethodTableNative::GetNumInstanceFieldBytes)
FCFuncEnd()

FCFuncStart(gMngdFixedArrayMarshalerFuncs)
FCFuncElement("CreateMarshaler", MngdFixedArrayMarshaler::CreateMarshaler)
FCFuncElement("ConvertSpaceToNative", MngdFixedArrayMarshaler::ConvertSpaceToNative)
Expand Down Expand Up @@ -783,6 +785,7 @@ FCClassElement("Marshal", "System.Runtime.InteropServices", gInteropMarshalFuncs
FCClassElement("Math", "System", gMathFuncs)
FCClassElement("MathF", "System", gMathFFuncs)
FCClassElement("MetadataImport", "System.Reflection", gMetaDataImport)
FCClassElement("MethodTable", "System.Runtime.CompilerServices", gMethodTableFuncs)
FCClassElement("MngdFixedArrayMarshaler", "System.StubHelpers", gMngdFixedArrayMarshalerFuncs)
FCClassElement("MngdNativeArrayMarshaler", "System.StubHelpers", gMngdNativeArrayMarshalerFuncs)
FCClassElement("MngdRefCustomMarshaler", "System.StubHelpers", gMngdRefCustomMarshalerFuncs)
Expand Down