Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
11 changes: 9 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 MethodTable* fieldMT))
{
case ValueTypeHashCodeStrategy.ReferenceField:
hashCode.Add(Unsafe.As<byte, object>(ref Unsafe.AddByteOffset(ref rawData, fieldOffset)).GetHashCode());
Expand All @@ -138,6 +138,12 @@ 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(fieldMT != null);
// Box the field to handle complicated cases like mutable method and shared generic
hashCode.Add(RuntimeHelpers.Box(fieldMT, ref Unsafe.AddByteOffset(ref rawData, fieldOffset))?.GetHashCode() ?? 0);
break;
}
}

Expand All @@ -152,11 +158,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 MethodTable* fieldMT);

public override string? ToString()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,17 @@ public override unsafe bool Equals([NotNullWhen(true)] object? obj)

public override unsafe int GetHashCode()
{
int hashCode = (int)this.GetMethodTable()->HashCode;
HashCode hashCode = default;
hashCode.Add(this.GetMethodTable()->HashCode);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
hashCode.Add(this.GetMethodTable()->HashCode);
hashCode.Add((IntPtr)this.GetMethodTable());

to match CoreCLR


hashCode ^= GetHashCodeImpl();

return hashCode;
}

private unsafe int GetHashCodeImpl()
{
int numFields = __GetFieldHelper(GetNumFields, out _);

if (numFields == UseFastHelper)
return FastGetValueTypeHashCodeHelper(this.GetMethodTable(), ref this.GetRawData());
hashCode.Add(FastGetValueTypeHashCodeHelper(this.GetMethodTable(), ref this.GetRawData()));

hashCode.Add(RegularGetValueTypeHashCode(ref this.GetRawData(), numFields));

return RegularGetValueTypeHashCode(ref this.GetRawData(), numFields);
return hashCode.ToHashCode();
}

private static unsafe int FastGetValueTypeHashCodeHelper(MethodTable* type, ref byte data)
Expand Down Expand Up @@ -164,7 +160,7 @@ private unsafe int RegularGetValueTypeHashCode(ref byte data, int numFields)
var fieldValue = (ValueType)RuntimeImports.RhBox(fieldType, ref fieldData);
if (fieldValue != null)
{
hashCode = fieldValue.GetHashCodeImpl();
hashCode = fieldValue.GetHashCode();
Copy link
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
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
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
19 changes: 14 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, MethodTable** fieldMTOut)
{
CONTRACTL
{
Expand Down Expand Up @@ -1772,10 +1773,18 @@ static ValueTypeHashCodeStrategy GetHashCodeStrategy(MethodTable* mt, QCall::Obj
*fieldSize = field->LoadSize();
ret = ValueTypeHashCodeStrategy::FastGetHashCode;
}
else if (HasOverriddenMethod(fieldMT,
CoreLibBinder::GetClass(CLASS__VALUE_TYPE),
CoreLibBinder::GetMethod(METHOD__VALUE_TYPE__GET_HASH_CODE)->GetSlot()))
{
*fieldOffset += field->GetOffsetUnsafe();
*fieldMTOut = fieldMT;
ret = ValueTypeHashCodeStrategy::ValueTypeOverride;
}
else
{
*fieldOffset += field->GetOffsetUnsafe();
ret = GetHashCodeStrategy(fieldMT, objHandle, fieldOffset, fieldSize);
ret = GetHashCodeStrategy(fieldMT, objHandle, fieldOffset, fieldSize, fieldMTOut);
}
}
}
Expand All @@ -1785,18 +1794,18 @@ 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, MethodTable** fieldMT)
{
QCALL_CONTRACT;

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

BEGIN_QCALL;


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

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, MethodTable** fieldMT);

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 StructWithNestedOverriddenNotBitwiseComparableTest()
{
StructWithNestedOverriddenNotBitwiseComparable obj1 = new StructWithNestedOverriddenNotBitwiseComparable();
obj1.value1.value = 0.0;
obj1.value2.value = 1.0;

StructWithNestedOverriddenNotBitwiseComparable obj2 = new StructWithNestedOverriddenNotBitwiseComparable();
obj2.value1.value = -0.0;
obj2.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;
}
}
}