Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
10 changes: 8 additions & 2 deletions src/coreclr/System.Private.CoreLib/src/System/ValueType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public override unsafe int GetHashCode()
else
{
object thisRef = this;
switch (GetHashCodeStrategy(pMT, ObjectHandleOnStack.Create(ref thisRef), out uint fieldOffset, out uint fieldSize))
switch (GetHashCodeStrategy(pMT, ObjectHandleOnStack.Create(ref thisRef), out uint fieldOffset, out uint fieldSize, out delegate* managed<ref byte, int> getHashCodeMethod))
{
case ValueTypeHashCodeStrategy.ReferenceField:
hashCode.Add(Unsafe.As<byte, object>(ref Unsafe.AddByteOffset(ref rawData, fieldOffset)).GetHashCode());
Expand All @@ -138,6 +138,11 @@ public override unsafe int GetHashCode()
Debug.Assert(fieldSize != 0);
hashCode.AddBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AddByteOffset(ref rawData, fieldOffset), (int)fieldSize));
break;

case ValueTypeHashCodeStrategy.ValueTypeOverride:
Debug.Assert(getHashCodeMethod != null);
hashCode.Add(getHashCodeMethod(ref Unsafe.AddByteOffset(ref rawData, fieldOffset)));
break;
}
}

Expand All @@ -152,11 +157,12 @@ private enum ValueTypeHashCodeStrategy
DoubleField,
SingleField,
FastGetHashCode,
ValueTypeOverride,
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ValueType_GetHashCodeStrategy")]
private static unsafe partial ValueTypeHashCodeStrategy GetHashCodeStrategy(
MethodTable* pMT, ObjectHandleOnStack objHandle, out uint fieldOffset, out uint fieldSize);
MethodTable* pMT, ObjectHandleOnStack objHandle, out uint fieldOffset, out uint fieldSize, out delegate* managed<ref byte, int> getHashCodeMethod);

public override string? ToString()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ private unsafe int RegularGetValueTypeHashCode(ref byte data, int numFields)
var fieldValue = (ValueType)RuntimeImports.RhBox(fieldType, ref fieldData);
if (fieldValue != null)
{
hashCode = fieldValue.GetHashCodeImpl();
// call virtual method to handle overriden case
hashCode = fieldValue.GetHashCode();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think GetHashCodeImpl can now be inlined into GetHashCode.

The only reason we had GetHashCodeImpl was because it was emulating CoreCLR behavior that did not call the GetHashCode on the field type (even if it had one) but instead used the fallback algorithm.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Is it feasible to use System.HashCode for NativeAOT here? Will it bring unintended dependency or complexity?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it would be fine to use System.HashCode. This was mirroring the CoreCLR-JIT logic but now it diverged because that's how forks usually end up.

If we really wanted to, we could probably unify this to the point of sharing a part of this file, maybe with an ifdef or two. From a quick look GetHashCodeStrategy could be implemented using NativeAOT's __GetFieldHelper.

}
else
{
Expand Down
25 changes: 20 additions & 5 deletions src/coreclr/vm/comutilnative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1703,9 +1703,10 @@ enum ValueTypeHashCodeStrategy
DoubleField,
SingleField,
FastGetHashCode,
ValueTypeOverride,
};

static ValueTypeHashCodeStrategy GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize)
static ValueTypeHashCodeStrategy GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize, PCODE* method)
{
CONTRACTL
{
Expand Down Expand Up @@ -1774,8 +1775,21 @@ static ValueTypeHashCodeStrategy GetHashCodeStrategy(MethodTable* mt, QCall::Obj
}
else
{
*fieldOffset += field->GetOffsetUnsafe();
ret = GetHashCodeStrategy(fieldMT, objHandle, fieldOffset, fieldSize);
MethodTable* valueTypeMT = CoreLibBinder::GetClass(CLASS__VALUE_TYPE);
WORD slotGetHashCode = CoreLibBinder::GetMethod(METHOD__VALUE_TYPE__GET_HASH_CODE)->GetSlot();

if (HasOverriddenMethod(mt, valueTypeMT, slotGetHashCode))
{
*fieldOffset += field->GetOffsetUnsafe();
*method = mt->GetRestoredSlot(slotGetHashCode);
ret = ValueTypeHashCodeStrategy::ValueTypeOverride;
}
Comment thread
huoyaoyuan marked this conversation as resolved.
Outdated
else
{
*fieldOffset += field->GetOffsetUnsafe();
// fieldOffset is required since first field may not be at offset 0 with explicit layout
ret = GetHashCodeStrategy(fieldMT, objHandle, fieldOffset, fieldSize, method);
}
}
}
}
Expand All @@ -1785,18 +1799,19 @@ static ValueTypeHashCodeStrategy GetHashCodeStrategy(MethodTable* mt, QCall::Obj
return ret;
}

extern "C" INT32 QCALLTYPE ValueType_GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize)
extern "C" INT32 QCALLTYPE ValueType_GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize, PCODE * method)
{
QCALL_CONTRACT;

ValueTypeHashCodeStrategy ret = ValueTypeHashCodeStrategy::None;
*fieldOffset = 0;
*fieldSize = 0;
*method = NULL;

BEGIN_QCALL;


ret = GetHashCodeStrategy(mt, objHandle, fieldOffset, fieldSize);
ret = GetHashCodeStrategy(mt, objHandle, fieldOffset, fieldSize, method);

END_QCALL;

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/comutilnative.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ class MethodTableNative {

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);
extern "C" INT32 QCALLTYPE ValueType_GetHashCodeStrategy(MethodTable* mt, QCall::ObjectHandleOnStack objHandle, UINT32* fieldOffset, UINT32* fieldSize, PCODE * method);

class StreamNative {
public:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,21 @@ public static void StructContainsPointerNestedCompareTest()
Assert.Equal(obj1.GetHashCode(), obj2.GetHashCode());
}

[Fact]
public static void StructWithNestedOverriddenNotBitwiseComparableNext()
{
StructWithNestedOverriddenNotBitwiseComparable obj1 = new StructWithNestedOverriddenNotBitwiseComparable();
obj1.value1.value = 0.0;
obj1.value2.value = 1.0;

StructWithNestedOverriddenNotBitwiseComparable obj2 = new StructWithNestedOverriddenNotBitwiseComparable();
obj1.value1.value = -0.0;
obj1.value2.value = 1.0;

Assert.True(obj1.Equals(obj2));
Assert.Equal(obj1.GetHashCode(), obj2.GetHashCode());
}

public struct S
{
public int x;
Expand Down Expand Up @@ -413,5 +428,20 @@ public struct StructContainsPointerNested
public object o;
public StructNonOverriddenEqualsOrGetHasCode value;
}

public struct StructOverriddenNotBitwiseComparable
{
public double value;

public override bool Equals(object obj) => obj is StructOverriddenNotBitwiseComparable other && value.Equals(other.value);

public override int GetHashCode() => value.GetHashCode();
}

public struct StructWithNestedOverriddenNotBitwiseComparable
{
public StructOverriddenNotBitwiseComparable value1;
public StructOverriddenNotBitwiseComparable value2;
}
}
}