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 @@ -31,20 +31,30 @@ public abstract class ValueType
return this.GetType().ToString();
}

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

// An override of this method will be injected by the compiler into all valuetypes that cannot be compared
// using a simple memory comparison.
// using a simple memory comparison until the last byte as reported by sizeof.
// This API is a bit awkward because we want to avoid burning more than one vtable slot on this.
// The method returns the offset and type handle of the index-th field on this type.
// When index == GetNumFields, this method is expected to return the number of fields of this
// valuetype. Otherwise, it returns the offset and type handle of the index-th field on this type.
// valuetype or a negative value. If the value is negative, the struct can be memcompared until
// the byte specified by the negated return value.
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 -(int)this.GetMethodTable()->ValueTypeSize;
}

private unsafe int GetValueTypeSize(int numFields)
{
Debug.Assert(numFields < 0);
int valueTypeSize = -numFields;
Debug.Assert(valueTypeSize <= (int)this.GetMethodTable()->ValueTypeSize);

return valueTypeSize;
}

public override unsafe bool Equals([NotNullWhen(true)] object? obj)
Expand All @@ -57,14 +67,13 @@ 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;
return SpanHelpers.SequenceEqual(ref thisRawData, ref thatRawData, valueTypeSize);
return SpanHelpers.SequenceEqual(ref thisRawData, ref thatRawData, GetValueTypeSize(numFields));
}
else
{
Expand Down Expand Up @@ -100,10 +109,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(), GetValueTypeSize(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 @@ -72,23 +72,31 @@ public override MethodIL EmitIL(MethodDesc specializedMethod)

private MethodIL EmitILCommon(MethodDesc contextMethod)
{
var owningType = (MetadataType)_owningType.InstantiateAsOpen();

ILEmitter emitter = new ILEmitter();

// Types marked as InlineArray aren't supported by
// the built-in Equals() or GetHashCode().
if (owningType.IsInlineArray)
if (_owningType.IsInlineArray)
{
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");
MethodDesc methodTableOfMethod = methodTableType.GetKnownMethod("Of", null);

var owningType = (MetadataType)_owningType.InstantiateAsOpen();

ILToken rawDataToken = owningType.IsValueType ? default :
emitter.NewToken(Context.SystemModule.GetKnownType("System.Runtime.CompilerServices", "RawData").GetKnownField("Data"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using ILCompiler.DependencyAnalysis;

using Internal.IL.Stubs;
using Internal.TypeSystem;

using Debug = System.Diagnostics.Debug;
Expand All @@ -26,8 +27,16 @@ public ObjectDataInterner(bool genericsOnly)

public bool CanFold(MethodDesc method)
{
return this != Null
&& (!_genericsOnly || method.HasInstantiation || method.OwningType.HasInstantiation);
if (this == Null)
return false;

if (!_genericsOnly || method.HasInstantiation || method.OwningType.HasInstantiation)
return true;

if (method.GetTypicalMethodDefinition() is ValueTypeGetFieldHelperMethodOverride)
return true;

return false;
}

private void EnsureMap(NodeFactory factory)
Expand Down
Loading