Skip to content
Closed
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 @@ -31,9 +31,10 @@ public abstract class ValueType
return this.GetType().ToString();
}

private const int UseFastHelper = -1;
private const int GetNumFields = -1;

private const int UseSizeFromMethodTable = int.MinValue;

// An override of this method will be injected by the compiler into all valuetypes that cannot be compared
// using a simple memory comparison.
// This API is a bit awkward because we want to avoid burning more than one vtable slot on this.
Expand All @@ -44,7 +45,7 @@ internal virtual unsafe int __GetFieldHelper(int index, out MethodTable* mt)
// Value types that don't override this method will use the fast path that looks at bytes, not fields.
Debug.Assert(index == GetNumFields);
mt = default;
return UseFastHelper;
return UseSizeFromMethodTable;
}

public override unsafe bool Equals([NotNullWhen(true)] object? obj)
Expand All @@ -57,13 +58,14 @@ public override unsafe bool Equals([NotNullWhen(true)] object? obj)
ref byte thisRawData = ref this.GetRawData();
ref byte thatRawData = ref obj.GetRawData();

if (numFields == UseFastHelper)
if (numFields < 0)
{
// Sanity check - if there are GC references, we should not be comparing bytes
Debug.Assert(!this.GetMethodTable()->ContainsGCPointers);

// Compare the memory
int valueTypeSize = (int)this.GetMethodTable()->ValueTypeSize;
// The size of the memory to compare is the smaller of ValueTypeSize and -numFields
Copy link
Member

Choose a reason for hiding this comment

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

This comment does not match implementation. Delete it?

int valueTypeSize = numFields == UseSizeFromMethodTable ? (int)this.GetMethodTable()->ValueTypeSize : -numFields;
return SpanHelpers.SequenceEqual(ref thisRawData, ref thatRawData, valueTypeSize);
}
else
Expand Down Expand Up @@ -100,10 +102,14 @@ public override unsafe int GetHashCode()

int numFields = __GetFieldHelper(GetNumFields, out _);

if (numFields == UseFastHelper)
hashCode.AddBytes(GetSpanForField(this.GetMethodTable(), ref this.GetRawData()));
if (numFields < 0)
{
hashCode.AddBytes(new ReadOnlySpan<byte>(ref this.GetRawData(), numFields == UseSizeFromMethodTable ? (int)this.GetMethodTable()->ValueTypeSize : -numFields));
}
else
{
RegularGetValueTypeHashCode(ref hashCode, ref this.GetRawData(), numFields);
}

return hashCode.ToHashCode();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Collections.Generic;

Expand Down Expand Up @@ -240,9 +241,17 @@ private static TypeDesc[] GetPotentialComparersForTypeCommon(TypeDesc type, stri
}

public static bool CanCompareValueTypeBits(MetadataType type, MethodDesc objectEqualsMethod)
{
return CanCompareValueTypeBitsUntilOffset(type, objectEqualsMethod, out int lastFieldOffset)
&& lastFieldOffset == type.InstanceFieldSize.AsInt;
}

public static bool CanCompareValueTypeBitsUntilOffset(MetadataType type, MethodDesc objectEqualsMethod, out int lastFieldEndOffset)
{
Debug.Assert(type.IsValueType);

lastFieldEndOffset = 0;

if (type.ContainsGCPointers)
return false;

Expand All @@ -260,6 +269,8 @@ public static bool CanCompareValueTypeBits(MetadataType type, MethodDesc objectE
if (field.IsStatic)
continue;

lastFieldEndOffset = Math.Max(lastFieldEndOffset, field.Offset.AsInt + field.FieldType.GetElementSize().AsInt);

if (!overlappingFieldTracker.TrackField(field))
{
// This field overlaps with another field - can't compare memory
Expand Down Expand Up @@ -299,7 +310,7 @@ public static bool CanCompareValueTypeBits(MetadataType type, MethodDesc objectE
}

// If there are gaps, we can't memcompare
if (result && overlappingFieldTracker.HasGaps)
if (result && overlappingFieldTracker.HasGapsBeforeOffset(lastFieldEndOffset))
result = false;

return result;
Expand Down Expand Up @@ -341,16 +352,13 @@ public bool TrackField(FieldDesc field)
return true;
}

public bool HasGaps
public bool HasGapsBeforeOffset(int offset)
{
get
{
for (int i = 0; i < _usedBytes.Length; i++)
if (!_usedBytes[i])
return true;
for (int i = 0; i < offset; i++)
if (!_usedBytes[i])
return true;

return false;
}
return false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ namespace Internal.IL.Stubs
/// </summary>
public sealed partial class ValueTypeGetFieldHelperMethodOverride : ILStubMethod
{
private DefType _owningType;
private MetadataType _owningType;
private MethodSignature _signature;

internal ValueTypeGetFieldHelperMethodOverride(DefType owningType)
internal ValueTypeGetFieldHelperMethodOverride(MetadataType owningType)
{
_owningType = owningType;
}
Expand Down Expand Up @@ -61,17 +61,22 @@ public override MethodIL EmitIL()

ILEmitter emitter = new ILEmitter();

if (_owningType is MetadataType mdType)
// Types marked as InlineArray aren't supported by
// the built-in Equals() or GetHashCode().
if (_owningType.IsInlineArray)
{
// Types marked as InlineArray aren't supported by
// the built-in Equals() or GetHashCode().
if (mdType.IsInlineArray)
{
var stream = emitter.NewCodeStream();
MethodDesc thrower = Context.GetHelperEntryPoint("ThrowHelpers", "ThrowNotSupportedInlineArrayEqualsGetHashCode");
stream.EmitCallThrowHelper(emitter, thrower);
return emitter.Link(this);
}
var stream = emitter.NewCodeStream();
MethodDesc thrower = Context.GetHelperEntryPoint("ThrowHelpers", "ThrowNotSupportedInlineArrayEqualsGetHashCode");
stream.EmitCallThrowHelper(emitter, thrower);
return emitter.Link(this);
}

if (_owningType.IsValueType && ComparerIntrinsics.CanCompareValueTypeBitsUntilOffset(_owningType, Context.GetWellKnownType(WellKnownType.Object).GetMethod("Equals", null), out int lastFieldEndOffset))
{
var stream = emitter.NewCodeStream();
stream.EmitLdc(-lastFieldEndOffset);
stream.Emit(ILOpcode.ret);
return emitter.Link(this);
}

TypeDesc methodTableType = Context.SystemModule.GetKnownType("Internal.Runtime", "MethodTable");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ public partial class CompilerTypeSystemContext
private MethodDesc _objectEqualsMethod;
private MetadataType _iAsyncStateMachineType;

private sealed class ValueTypeMethodHashtable : LockFreeReaderHashtable<DefType, MethodDesc>
private sealed class ValueTypeMethodHashtable : LockFreeReaderHashtable<MetadataType, MethodDesc>
{
protected override int GetKeyHashCode(DefType key) => key.GetHashCode();
protected override int GetKeyHashCode(MetadataType key) => key.GetHashCode();
protected override int GetValueHashCode(MethodDesc value) => value.OwningType.GetHashCode();
protected override bool CompareKeyToValue(DefType key, MethodDesc value) => key == value.OwningType;
protected override bool CompareKeyToValue(MetadataType key, MethodDesc value) => key == value.OwningType;
protected override bool CompareValueToValue(MethodDesc v1, MethodDesc v2) => v1.OwningType == v2.OwningType;

protected override MethodDesc CreateValueFromKey(DefType key)
protected override MethodDesc CreateValueFromKey(MetadataType key)
{
return new ValueTypeGetFieldHelperMethodOverride(key);
}
Expand All @@ -37,7 +37,7 @@ protected virtual IEnumerable<MethodDesc> GetAllMethodsForValueType(TypeDesc val

if (RequiresValueTypeGetFieldHelperMethod((MetadataType)valueTypeDefinition))
{
MethodDesc getFieldHelperMethod = _valueTypeMethodHashtable.GetOrCreateValue((DefType)valueTypeDefinition);
MethodDesc getFieldHelperMethod = _valueTypeMethodHashtable.GetOrCreateValue((MetadataType)valueTypeDefinition);

if (valueType != valueTypeDefinition)
{
Expand All @@ -60,7 +60,7 @@ protected virtual IEnumerable<MethodDesc> GetAllMethodsForAttribute(TypeDesc att

if (RequiresAttributeGetFieldHelperMethod(attributeTypeDefinition))
{
MethodDesc getFieldHelperMethod = _valueTypeMethodHashtable.GetOrCreateValue((DefType)attributeTypeDefinition);
MethodDesc getFieldHelperMethod = _valueTypeMethodHashtable.GetOrCreateValue((MetadataType)attributeTypeDefinition);

if (attributeType != attributeTypeDefinition)
{
Expand Down
Loading