diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
index 4e75d7db895ce..02ecf96568927 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
@@ -462,7 +462,13 @@ internal unsafe struct MethodTable
// Additional conditional fields (see methodtable.h).
// m_pModule
- // m_pAuxiliaryData
+
+ ///
+ /// A pointer to auxiliary data that is cold for method table.
+ ///
+ [FieldOffset(AuxiliaryDataOffset)]
+ public MethodTableAuxiliaryData* AuxiliaryData;
+
// union {
// m_pEEClass (pointer to the EE class)
// m_pCanonMT (pointer to the canonical method table)
@@ -523,6 +529,12 @@ internal unsafe struct MethodTable
private const int ParentMethodTableOffset = 0x10 + DebugClassNamePtr;
+#if TARGET_64BIT
+ private const int AuxiliaryDataOffset = 0x20 + DebugClassNamePtr;
+#else
+ private const int AuxiliaryDataOffset = 0x18 + DebugClassNamePtr;
+#endif
+
#if TARGET_64BIT
private const int ElementTypeOffset = 0x30 + DebugClassNamePtr;
#else
@@ -610,6 +622,28 @@ public TypeHandle GetArrayElementTypeHandle()
public extern uint GetNumInstanceFieldBytes();
}
+ // Subset of src\vm\methodtable.h
+ [StructLayout(LayoutKind.Explicit)]
+ internal unsafe struct MethodTableAuxiliaryData
+ {
+ [FieldOffset(0)]
+ private uint Flags;
+
+ private const uint enum_flag_CanCompareBitsOrUseFastGetHashCode = 0x0001; // Is any field type or sub field type overrode Equals or GetHashCode
+ private const uint enum_flag_HasCheckedCanCompareBitsOrUseFastGetHashCode = 0x0002; // Whether we have checked the overridden Equals or GetHashCode
+
+ public bool HasCheckedCanCompareBitsOrUseFastGetHashCode => (Flags & enum_flag_HasCheckedCanCompareBitsOrUseFastGetHashCode) != 0;
+
+ public bool CanCompareBitsOrUseFastGetHashCode
+ {
+ get
+ {
+ Debug.Assert(HasCheckedCanCompareBitsOrUseFastGetHashCode);
+ return (Flags & enum_flag_CanCompareBitsOrUseFastGetHashCode) != 0;
+ }
+ }
+ }
+
///
/// A type handle, which can wrap either a pointer to a TypeDesc or to a .
///
diff --git a/src/coreclr/System.Private.CoreLib/src/System/ValueType.cs b/src/coreclr/System.Private.CoreLib/src/System/ValueType.cs
index cc13e37e083f0..78301866c36dc 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/ValueType.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/ValueType.cs
@@ -10,15 +10,17 @@
**
===========================================================*/
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace System
{
[Serializable]
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
- public abstract class ValueType
+ public abstract partial class ValueType
{
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
Justification = "Trimmed fields don't make a difference for equality")]
@@ -36,7 +38,7 @@ public override unsafe bool Equals([NotNullWhen(true)] object? obj)
// if there are no GC references in this object we can avoid reflection
// and do a fast memcmp
- if (CanCompareBits(this))
+ if (CanCompareBitsOrUseFastGetHashCode(RuntimeHelpers.GetMethodTable(obj))) // MethodTable kept alive by access to object below
{
return SpanHelpers.SequenceEqual(
ref RuntimeHelpers.GetRawData(this),
@@ -66,8 +68,23 @@ ref RuntimeHelpers.GetRawData(obj),
return true;
}
- [MethodImpl(MethodImplOptions.InternalCall)]
- private static extern bool CanCompareBits(object obj);
+ // Return true if the valuetype does not contain pointer, is tightly packed,
+ // does not have floating point number field and does not override Equals method.
+ private static unsafe bool CanCompareBitsOrUseFastGetHashCode(MethodTable* pMT)
+ {
+ MethodTableAuxiliaryData* pAuxData = pMT->AuxiliaryData;
+
+ if (pAuxData->HasCheckedCanCompareBitsOrUseFastGetHashCode)
+ {
+ return pAuxData->CanCompareBitsOrUseFastGetHashCode;
+ }
+
+ return CanCompareBitsOrUseFastGetHashCodeHelper(pMT);
+ }
+
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MethodTable_CanCompareBitsOrUseFastGetHashCode")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static unsafe partial bool CanCompareBitsOrUseFastGetHashCodeHelper(MethodTable* pMT);
/*=================================GetHashCode==================================
**Action: Our algorithm for returning the hashcode is a little bit complex. We look
@@ -79,8 +96,67 @@ ref RuntimeHelpers.GetRawData(obj),
**Arguments: None.
**Exceptions: None.
==============================================================================*/
- [MethodImpl(MethodImplOptions.InternalCall)]
- public extern override int GetHashCode();
+ public override unsafe int GetHashCode()
+ {
+ // The default implementation of GetHashCode() for all value types.
+ // Note that this implementation reveals the value of the fields.
+ // So if the value type contains any sensitive information it should
+ // implement its own GetHashCode().
+
+ MethodTable* pMT = RuntimeHelpers.GetMethodTable(this);
+ ref byte rawData = ref RuntimeHelpers.GetRawData(this);
+ HashCode hashCode = default;
+
+ // To get less colliding and more evenly distributed hash codes,
+ // we munge the class index into the hashcode
+ hashCode.Add((IntPtr)pMT);
+
+ if (CanCompareBitsOrUseFastGetHashCode(pMT))
+ {
+ // this is a struct with no refs and no "strange" offsets
+ uint size = pMT->GetNumInstanceFieldBytes();
+ hashCode.AddBytes(MemoryMarshal.CreateReadOnlySpan(ref rawData, (int)size));
+ }
+ else
+ {
+ object thisRef = this;
+ switch (GetHashCodeStrategy(pMT, ObjectHandleOnStack.Create(ref thisRef), out uint fieldOffset, out uint fieldSize))
+ {
+ case ValueTypeHashCodeStrategy.ReferenceField:
+ hashCode.Add(Unsafe.As(ref Unsafe.AddByteOffset(ref rawData, fieldOffset)).GetHashCode());
+ break;
+
+ case ValueTypeHashCodeStrategy.DoubleField:
+ hashCode.Add(Unsafe.As(ref Unsafe.AddByteOffset(ref rawData, fieldOffset)).GetHashCode());
+ break;
+
+ case ValueTypeHashCodeStrategy.SingleField:
+ hashCode.Add(Unsafe.As(ref Unsafe.AddByteOffset(ref rawData, fieldOffset)).GetHashCode());
+ break;
+
+ case ValueTypeHashCodeStrategy.FastGetHashCode:
+ Debug.Assert(fieldSize != 0);
+ hashCode.AddBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AddByteOffset(ref rawData, fieldOffset), (int)fieldSize));
+ break;
+ }
+ }
+
+ return hashCode.ToHashCode();
+ }
+
+ // Must match the definition in src\vm\comutilnative.cpp
+ private enum ValueTypeHashCodeStrategy
+ {
+ None,
+ ReferenceField,
+ DoubleField,
+ SingleField,
+ FastGetHashCode,
+ }
+
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ValueType_GetHashCodeStrategy")]
+ private static unsafe partial ValueTypeHashCodeStrategy GetHashCodeStrategy(
+ MethodTable* pMT, ObjectHandleOnStack objHandle, out uint fieldOffset, out uint fieldSize);
public override string? ToString()
{
diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp
index 027c4ae8903ae..612cb9d72dc0d 100644
--- a/src/coreclr/vm/comutilnative.cpp
+++ b/src/coreclr/vm/comutilnative.cpp
@@ -1681,226 +1681,127 @@ BOOL CanCompareBitsOrUseFastGetHashCode(MethodTable* mt)
return canCompareBitsOrUseFastGetHashCode;
}
-NOINLINE static FC_BOOL_RET CanCompareBitsHelper(MethodTable* mt, OBJECTREF objRef)
+extern "C" BOOL QCALLTYPE MethodTable_CanCompareBitsOrUseFastGetHashCode(MethodTable * mt)
{
- FC_INNER_PROLOG(ValueTypeHelper::CanCompareBits);
-
- _ASSERTE(mt != NULL);
- _ASSERTE(objRef != NULL);
+ QCALL_CONTRACT;
BOOL ret = FALSE;
- HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2, objRef);
+ BEGIN_QCALL;
ret = CanCompareBitsOrUseFastGetHashCode(mt);
- HELPER_METHOD_FRAME_END();
- FC_INNER_EPILOG();
+ END_QCALL;
- FC_RETURN_BOOL(ret);
+ return ret;
}
-// Return true if the valuetype does not contain pointer, is tightly packed,
-// does not have floating point number field and does not override Equals method.
-FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
+enum ValueTypeHashCodeStrategy
{
- FCALL_CONTRACT;
-
- _ASSERTE(obj != NULL);
- MethodTable* mt = obj->GetMethodTable();
+ None,
+ ReferenceField,
+ DoubleField,
+ SingleField,
+ FastGetHashCode,
+};
- if (mt->HasCheckedCanCompareBitsOrUseFastGetHashCode())
- {
- FC_RETURN_BOOL(mt->CanCompareBitsOrUseFastGetHashCode());
- }
-
- OBJECTREF objRef(obj);
-
- FC_INNER_RETURN(FC_BOOL_RET, CanCompareBitsHelper(mt, objRef));
-}
-FCIMPLEND
-
-static INT32 FastGetValueTypeHashCodeHelper(MethodTable *mt, void *pObjRef)
-{
- CONTRACTL
- {
- NOTHROW;
- GC_NOTRIGGER;
- MODE_COOPERATIVE;
- } CONTRACTL_END;
-
- INT32 hashCode = 0;
- INT32 *pObj = (INT32*)pObjRef;
-
- // this is a struct with no refs and no "strange" offsets, just go through the obj and xor the bits
- INT32 size = mt->GetNumInstanceFieldBytes();
- for (INT32 i = 0; i < (INT32)(size / sizeof(INT32)); i++)
- hashCode ^= *pObj++;
-
- return hashCode;
-}
-
-static INT32 RegularGetValueTypeHashCode(MethodTable *mt, void *pObjRef)
+static ValueTypeHashCodeStrategy GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
- MODE_COOPERATIVE;
+ MODE_PREEMPTIVE;
} CONTRACTL_END;
- INT32 hashCode = 0;
+ // Should be handled by caller
+ _ASSERTE(!mt->CanCompareBitsOrUseFastGetHashCode());
- GCPROTECT_BEGININTERIOR(pObjRef);
+ ValueTypeHashCodeStrategy ret = ValueTypeHashCodeStrategy::None;
- BOOL canUseFastGetHashCodeHelper = FALSE;
- if (mt->HasCheckedCanCompareBitsOrUseFastGetHashCode())
- {
- canUseFastGetHashCodeHelper = mt->CanCompareBitsOrUseFastGetHashCode();
- }
- else
- {
- canUseFastGetHashCodeHelper = CanCompareBitsOrUseFastGetHashCode(mt);
- }
+ // Grab the first non-null field and return its hash code or 'it' as hash code
+ ApproxFieldDescIterator fdIterator(mt, ApproxFieldDescIterator::INSTANCE_FIELDS);
- // While we should not get here directly from ValueTypeHelper::GetHashCode, if we recurse we need to
- // be able to handle getting the hashcode for an embedded structure whose hashcode is computed by the fast path.
- if (canUseFastGetHashCodeHelper)
+ FieldDesc *field;
+ while ((field = fdIterator.Next()) != NULL)
{
- hashCode = FastGetValueTypeHashCodeHelper(mt, pObjRef);
- }
- else
- {
- // it's looking ugly so we'll use the old behavior in managed code. Grab the first non-null
- // field and return its hash code or 'it' as hash code
- // Note that the old behavior has already been broken for value types
- // that is qualified for CanUseFastGetHashCodeHelper. So maybe we should
- // change the implementation here to use all fields instead of just the 1st one.
- //
- //
- // check this approximation - we may be losing exact type information
- ApproxFieldDescIterator fdIterator(mt, ApproxFieldDescIterator::INSTANCE_FIELDS);
-
- FieldDesc *field;
- while ((field = fdIterator.Next()) != NULL)
+ _ASSERTE(!field->IsRVA());
+ if (field->IsObjRef())
{
- _ASSERTE(!field->IsRVA());
- if (field->IsObjRef())
+ GCX_COOP();
+ // if we get an object reference we get the hash code out of that
+ if (*(Object**)((BYTE *)objHandle.Get()->UnBox() + *fieldOffset + field->GetOffsetUnsafe()) != NULL)
{
- // if we get an object reference we get the hash code out of that
- if (*(Object**)((BYTE *)pObjRef + field->GetOffsetUnsafe()) != NULL)
- {
- PREPARE_SIMPLE_VIRTUAL_CALLSITE(METHOD__OBJECT__GET_HASH_CODE, (*(Object**)((BYTE *)pObjRef + field->GetOffsetUnsafe())));
- DECLARE_ARGHOLDER_ARRAY(args, 1);
- args[ARGNUM_0] = PTR_TO_ARGHOLDER(*(Object**)((BYTE *)pObjRef + field->GetOffsetUnsafe()));
- CALL_MANAGED_METHOD(hashCode, INT32, args);
- }
- else
- {
- // null object reference, try next
- continue;
- }
+ *fieldOffset += field->GetOffsetUnsafe();
+ ret = ValueTypeHashCodeStrategy::ReferenceField;
}
else
{
- CorElementType fieldType = field->GetFieldType();
- if (fieldType == ELEMENT_TYPE_R8)
- {
- PREPARE_NONVIRTUAL_CALLSITE(METHOD__DOUBLE__GET_HASH_CODE);
- DECLARE_ARGHOLDER_ARRAY(args, 1);
- args[ARGNUM_0] = PTR_TO_ARGHOLDER(((BYTE *)pObjRef + field->GetOffsetUnsafe()));
- CALL_MANAGED_METHOD(hashCode, INT32, args);
- }
- else if (fieldType == ELEMENT_TYPE_R4)
- {
- PREPARE_NONVIRTUAL_CALLSITE(METHOD__SINGLE__GET_HASH_CODE);
- DECLARE_ARGHOLDER_ARRAY(args, 1);
- args[ARGNUM_0] = PTR_TO_ARGHOLDER(((BYTE *)pObjRef + field->GetOffsetUnsafe()));
- CALL_MANAGED_METHOD(hashCode, INT32, args);
- }
- else if (fieldType != ELEMENT_TYPE_VALUETYPE)
+ // null object reference, try next
+ continue;
+ }
+ }
+ else
+ {
+ CorElementType fieldType = field->GetFieldType();
+ if (fieldType == ELEMENT_TYPE_R8)
+ {
+ *fieldOffset += field->GetOffsetUnsafe();
+ ret = ValueTypeHashCodeStrategy::DoubleField;
+ }
+ else if (fieldType == ELEMENT_TYPE_R4)
+ {
+ *fieldOffset += field->GetOffsetUnsafe();
+ ret = ValueTypeHashCodeStrategy::SingleField;
+ }
+ else if (fieldType != ELEMENT_TYPE_VALUETYPE)
+ {
+ *fieldOffset += field->GetOffsetUnsafe();
+ *fieldSize = field->LoadSize();
+ ret = ValueTypeHashCodeStrategy::FastGetHashCode;
+ }
+ else
+ {
+ // got another value type. Get the type
+ TypeHandle fieldTH = field->GetFieldTypeHandleThrowing();
+ _ASSERTE(!fieldTH.IsNull());
+ MethodTable* fieldMT = fieldTH.GetMethodTable();
+ if (CanCompareBitsOrUseFastGetHashCode(fieldMT))
{
- UINT fieldSize = field->LoadSize();
- INT32 *pValue = (INT32*)((BYTE *)pObjRef + field->GetOffsetUnsafe());
- for (INT32 j = 0; j < (INT32)(fieldSize / sizeof(INT32)); j++)
- hashCode ^= *pValue++;
+ *fieldOffset += field->GetOffsetUnsafe();
+ *fieldSize = field->LoadSize();
+ ret = ValueTypeHashCodeStrategy::FastGetHashCode;
}
else
{
- // got another value type. Get the type
- TypeHandle fieldTH = field->GetFieldTypeHandleThrowing();
- _ASSERTE(!fieldTH.IsNull());
- hashCode = RegularGetValueTypeHashCode(fieldTH.GetMethodTable(), (BYTE *)pObjRef + field->GetOffsetUnsafe());
+ *fieldOffset += field->GetOffsetUnsafe();
+ ret = GetHashCodeStrategy(fieldMT, objHandle, fieldOffset, fieldSize);
}
}
- break;
}
+ break;
}
- GCPROTECT_END();
-
- return hashCode;
+ return ret;
}
-// The default implementation of GetHashCode() for all value types.
-// Note that this implementation reveals the value of the fields.
-// So if the value type contains any sensitive information it should
-// implement its own GetHashCode().
-FCIMPL1(INT32, ValueTypeHelper::GetHashCode, Object* objUNSAFE)
+extern "C" INT32 QCALLTYPE ValueType_GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize)
{
- FCALL_CONTRACT;
-
- if (objUNSAFE == NULL)
- FCThrow(kNullReferenceException);
-
- OBJECTREF obj = ObjectToOBJECTREF(objUNSAFE);
- VALIDATEOBJECTREF(obj);
+ QCALL_CONTRACT;
- INT32 hashCode = 0;
- MethodTable *pMT = objUNSAFE->GetMethodTable();
+ ValueTypeHashCodeStrategy ret = ValueTypeHashCodeStrategy::None;
+ *fieldOffset = 0;
+ *fieldSize = 0;
- // We don't want to expose the method table pointer in the hash code
- // Let's use the typeID instead.
- UINT32 typeID = pMT->LookupTypeID();
- if (typeID == TypeIDProvider::INVALID_TYPE_ID)
- {
- // If the typeID has yet to be generated, fall back to GetTypeID
- // This only needs to be done once per MethodTable
- HELPER_METHOD_FRAME_BEGIN_RET_1(obj);
- typeID = pMT->GetTypeID();
- HELPER_METHOD_FRAME_END();
- }
+ BEGIN_QCALL;
- // To get less colliding and more evenly distributed hash codes,
- // we munge the class index with two big prime numbers
- hashCode = typeID * 711650207 + 2506965631U;
- BOOL canUseFastGetHashCodeHelper = FALSE;
- if (pMT->HasCheckedCanCompareBitsOrUseFastGetHashCode())
- {
- canUseFastGetHashCodeHelper = pMT->CanCompareBitsOrUseFastGetHashCode();
- }
- else
- {
- HELPER_METHOD_FRAME_BEGIN_RET_1(obj);
- canUseFastGetHashCodeHelper = CanCompareBitsOrUseFastGetHashCode(pMT);
- HELPER_METHOD_FRAME_END();
- }
+ ret = GetHashCodeStrategy(mt, objHandle, fieldOffset, fieldSize);
- if (canUseFastGetHashCodeHelper)
- {
- hashCode ^= FastGetValueTypeHashCodeHelper(pMT, obj->UnBox());
- }
- else
- {
- HELPER_METHOD_FRAME_BEGIN_RET_1(obj);
- hashCode ^= RegularGetValueTypeHashCode(pMT, obj->UnBox());
- HELPER_METHOD_FRAME_END();
- }
+ END_QCALL;
- return hashCode;
+ return ret;
}
-FCIMPLEND
FCIMPL1(UINT32, MethodTableNative::GetNumInstanceFieldBytes, MethodTable* mt)
{
diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h
index 80d35da7b7214..a3c5ea65c3ca7 100644
--- a/src/coreclr/vm/comutilnative.h
+++ b/src/coreclr/vm/comutilnative.h
@@ -245,18 +245,14 @@ class COMInterlocked
extern "C" void QCALLTYPE Interlocked_MemoryBarrierProcessWide();
-class ValueTypeHelper {
-public:
- static FCDECL1(FC_BOOL_RET, CanCompareBits, Object* obj);
- static FCDECL1(INT32, GetHashCode, Object* objRef);
-};
-
class MethodTableNative {
public:
static FCDECL1(UINT32, GetNumInstanceFieldBytes, MethodTable* mt);
};
extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb);
+extern "C" BOOL QCALLTYPE MethodTable_CanCompareBitsOrUseFastGetHashCode(MethodTable* mt);
+extern "C" INT32 QCALLTYPE ValueType_GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize);
class StreamNative {
public:
diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h
index bd4a209016652..8e68900686a7e 100644
--- a/src/coreclr/vm/corelib.h
+++ b/src/coreclr/vm/corelib.h
@@ -569,12 +569,6 @@ DEFINE_METHOD(OBJECT, GET_TYPE, GetType,
DEFINE_METHOD(OBJECT, GET_HASH_CODE, GetHashCode, IM_RetInt)
DEFINE_METHOD(OBJECT, EQUALS, Equals, IM_Obj_RetBool)
-// DEFINE_CLASS(DOUBLE, System, Double)
-DEFINE_METHOD(DOUBLE, GET_HASH_CODE, GetHashCode, IM_RetInt)
-
-// DEFINE_CLASS(SINGLE, System, Single)
-DEFINE_METHOD(SINGLE, GET_HASH_CODE, GetHashCode, IM_RetInt)
-
DEFINE_CLASS(__CANON, System, __Canon)
BEGIN_ILLINK_FEATURE_SWITCH(System.Runtime.InteropServices.BuiltInComInterop.IsSupported, true, true)
diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h
index 1af6a5055e6c3..55b93f1f1ab68 100644
--- a/src/coreclr/vm/ecalllist.h
+++ b/src/coreclr/vm/ecalllist.h
@@ -76,11 +76,6 @@ FCFuncStart(gStringFuncs)
FCDynamicSig(COR_CTOR_METHOD_NAME, &gsig_IM_PtrSByt_Int_Int_Encoding_RetVoid, ECall::CtorSBytePtrStartLengthEncodingManaged)
FCFuncEnd()
-FCFuncStart(gValueTypeFuncs)
- FCFuncElement("CanCompareBits", ValueTypeHelper::CanCompareBits)
- FCFuncElement("GetHashCode", ValueTypeHelper::GetHashCode)
-FCFuncEnd()
-
FCFuncStart(gDiagnosticsDebugger)
FCFuncElement("BreakInternal", DebugDebugger::Break)
FCFuncElement("get_IsAttached", DebugDebugger::IsDebuggerAttached)
@@ -619,7 +614,6 @@ FCClassElement("Thread", "System.Threading", gThreadFuncs)
FCClassElement("ThreadPool", "System.Threading", gThreadPoolFuncs)
FCClassElement("Type", "System", gSystem_Type)
FCClassElement("TypedReference", "System", gTypedReferenceFuncs)
-FCClassElement("ValueType", "System", gValueTypeFuncs)
#ifdef FEATURE_COMINTEROP
FCClassElement("Variant", "System", gVariantFuncs)
#endif
diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp
index ec72d3112f5f8..8e6a670297943 100644
--- a/src/coreclr/vm/qcallentrypoints.cpp
+++ b/src/coreclr/vm/qcallentrypoints.cpp
@@ -100,6 +100,8 @@ static const Entry s_QCall[] =
DllImportEntry(QCall_GetGCHandleForTypeHandle)
DllImportEntry(QCall_FreeGCHandleForTypeHandle)
DllImportEntry(MethodTable_AreTypesEquivalent)
+ DllImportEntry(MethodTable_CanCompareBitsOrUseFastGetHashCode)
+ DllImportEntry(ValueType_GetHashCodeStrategy)
DllImportEntry(RuntimeTypeHandle_MakePointer)
DllImportEntry(RuntimeTypeHandle_MakeByRef)
DllImportEntry(RuntimeTypeHandle_MakeSZArray)
diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ValueTypeTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ValueTypeTests.cs
index 422f71e11c04f..92a2c006ce204 100644
--- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ValueTypeTests.cs
+++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ValueTypeTests.cs
@@ -299,6 +299,21 @@ public static void StructContainsPointerCompareTest()
Assert.True(obj1.Equals(obj2));
Assert.Equal(obj1.GetHashCode(), obj2.GetHashCode());
}
+
+ [Fact]
+ public static void StructContainsPointerNestedCompareTest()
+ {
+ StructContainsPointerNested obj1 = new StructContainsPointerNested();
+ obj1.o = null;
+ obj1.value.value = 1;
+
+ StructContainsPointerNested obj2 = new StructContainsPointerNested();
+ obj2.o = null;
+ obj2.value.value = 1;
+
+ Assert.True(obj1.Equals(obj2));
+ Assert.Equal(obj1.GetHashCode(), obj2.GetHashCode());
+ }
public struct S
{
@@ -392,5 +407,11 @@ public struct StructContainsPointer
public double value1;
public double value2;
}
+
+ public struct StructContainsPointerNested
+ {
+ public object o;
+ public StructNonOverriddenEqualsOrGetHasCode value;
+ }
}
}