Skip to content
This repository has been archived by the owner on Nov 1, 2020. It is now read-only.

Remove reflection in ValueType.Equals/GetHashCode #5436

Merged
merged 7 commits into from
Feb 27, 2018
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
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Internal.TypeSystem;

namespace Internal.IL.Stubs
{
partial class ValueTypeGetFieldHelperMethodOverride
{
protected internal override int ClassCode => 2036839816;

protected internal override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer)
{
var otherMethod = (ValueTypeGetFieldHelperMethodOverride)other;

return comparer.Compare(_owningType, otherMethod._owningType);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;

using Internal.TypeSystem;

namespace Internal.IL.Stubs
{
/// <summary>
/// Synthetic method override of "int ValueType.__GetFieldHelper(Int32, out EETypePtr)". This method is injected
/// into all value types that cannot have their Equals(object) and GetHashCode() methods operate on individual
/// bytes. The purpose of the override is to provide access to the value types' fields and their types.
/// </summary>
public sealed partial class ValueTypeGetFieldHelperMethodOverride : ILStubMethod
{
private DefType _owningType;
private MethodSignature _signature;

internal ValueTypeGetFieldHelperMethodOverride(DefType owningType)
{
_owningType = owningType;
}

public override TypeSystemContext Context
{
get
{
return _owningType.Context;
}
}

public override TypeDesc OwningType
{
get
{
return _owningType;
}
}

public override MethodSignature Signature
{
get
{
if (_signature == null)
{
TypeSystemContext context = _owningType.Context;
TypeDesc int32Type = context.GetWellKnownType(WellKnownType.Int32);
TypeDesc eeTypePtrType = context.SystemModule.GetKnownType("System", "EETypePtr");

_signature = new MethodSignature(0, 0, int32Type, new[] {
int32Type,
eeTypePtrType.MakeByRefType()
});
}

return _signature;
}
}

public override MethodIL EmitIL()
{
TypeDesc owningType = _owningType.InstantiateAsOpen();

ILEmitter emitter = new ILEmitter();

TypeDesc eeTypePtrType = Context.SystemModule.GetKnownType("System", "EETypePtr");
MethodDesc eeTypePtrOfMethod = eeTypePtrType.GetKnownMethod("EETypePtrOf", null);
ILToken eeTypePtrToken = emitter.NewToken(eeTypePtrType);

var switchStream = emitter.NewCodeStream();
var getFieldStream = emitter.NewCodeStream();

ArrayBuilder<ILCodeLabel> fieldGetters = new ArrayBuilder<ILCodeLabel>();
foreach (FieldDesc field in owningType.GetFields())
{
if (field.IsStatic)
continue;

ILCodeLabel label = emitter.NewCodeLabel();
fieldGetters.Add(label);

getFieldStream.EmitLabel(label);
getFieldStream.EmitLdArg(2);

// We need something we can instantiate EETypePtrOf over. Also, the classlib
// code doesn't handle pointers.
TypeDesc boxableFieldType = field.FieldType;
if (boxableFieldType.IsPointer || boxableFieldType.IsFunctionPointer)
boxableFieldType = Context.GetWellKnownType(WellKnownType.IntPtr);

MethodDesc ptrOfField = eeTypePtrOfMethod.MakeInstantiatedMethod(boxableFieldType);
getFieldStream.Emit(ILOpcode.call, emitter.NewToken(ptrOfField));

getFieldStream.Emit(ILOpcode.stobj, eeTypePtrToken);

getFieldStream.EmitLdArg(0);
getFieldStream.Emit(ILOpcode.ldflda, emitter.NewToken(field));

getFieldStream.EmitLdArg(0);

getFieldStream.Emit(ILOpcode.sub);

getFieldStream.Emit(ILOpcode.ret);
}

if (fieldGetters.Count > 0)
{
switchStream.EmitLdArg(1);
switchStream.EmitSwitch(fieldGetters.ToArray());
}

switchStream.EmitLdc(fieldGetters.Count);

switchStream.Emit(ILOpcode.ret);

return emitter.Link(this);
}

public override Instantiation Instantiation
{
get
{
return Instantiation.Empty;
}
}

public override bool IsVirtual
{
get
{
return true;
}
}

public override string Name
{
get
{
return "__GetFieldHelper";
}
}
}
}
189 changes: 189 additions & 0 deletions src/Common/src/TypeSystem/IL/TypeSystemContext.ValueTypeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;

using Internal.IL.Stubs;

using Debug = System.Diagnostics.Debug;

namespace Internal.TypeSystem
{
public abstract partial class TypeSystemContext
{
private MethodDesc _objectEqualsMethod;

private class ValueTypeMethodHashtable : LockFreeReaderHashtable<DefType, MethodDesc>
{
protected override int GetKeyHashCode(DefType 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 CompareValueToValue(MethodDesc v1, MethodDesc v2) => v1.OwningType == v2.OwningType;

protected override MethodDesc CreateValueFromKey(DefType key)
{
return new ValueTypeGetFieldHelperMethodOverride(key);
}
}

private ValueTypeMethodHashtable _valueTypeMethodHashtable = new ValueTypeMethodHashtable();

protected virtual IEnumerable<MethodDesc> GetAllMethodsForValueType(TypeDesc valueType)
{
TypeDesc valueTypeDefinition = valueType.GetTypeDefinition();

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

// Check that System.ValueType has the method we're overriding.
Debug.Assert(valueTypeDefinition.BaseType.GetMethod(getFieldHelperMethod.Name, null) != null);

if (valueType != valueTypeDefinition)
{
yield return GetMethodForInstantiatedType(getFieldHelperMethod, (InstantiatedType)valueType);
}
else
{
yield return getFieldHelperMethod;
}
}

foreach (MethodDesc method in valueType.GetMethods())
yield return method;
}

private bool RequiresGetFieldHelperMethod(MetadataType valueType)
{
if (_objectEqualsMethod == null)
_objectEqualsMethod = GetWellKnownType(WellKnownType.Object).GetMethod("Equals", null);

// If the classlib doesn't have Object.Equals, we don't need this.
if (_objectEqualsMethod == null)
return false;

// Byref-like valuetypes cannot be boxed.
if (valueType.IsByRefLike)
return false;

// Enums get their overrides from System.Enum.
if (valueType.IsEnum)
return false;

return !CanCompareValueTypeBits(valueType);
}

private bool CanCompareValueTypeBits(MetadataType type)
{
Debug.Assert(type.IsValueType);

if (type.ContainsGCPointers)
return false;

if (type.IsGenericDefinition)
return false;

OverlappingFieldTracker overlappingFieldTracker = new OverlappingFieldTracker(type);

bool result = true;
foreach (var field in type.GetFields())
{
if (field.IsStatic)
continue;

if (!overlappingFieldTracker.TrackField(field))
{
// This field overlaps with another field - can't compare memory
result = false;
break;
}

TypeDesc fieldType = field.FieldType;
if (fieldType.IsPrimitive || fieldType.IsEnum || fieldType.IsPointer || fieldType.IsFunctionPointer)
{
TypeFlags category = fieldType.UnderlyingType.Category;
if (category == TypeFlags.Single || category == TypeFlags.Double)
{
// Double/Single have weird behaviors around negative/positive zero
result = false;
break;
}
}
else
{
// Would be a suprise if this wasn't a valuetype. We checked ContainsGCPointers above.
Debug.Assert(fieldType.IsValueType);

// If the field overrides Equals, we can't use the fast helper because we need to call the method.
if (fieldType.FindVirtualFunctionTargetMethodOnObjectType(_objectEqualsMethod).OwningType == type)
{
result = false;
break;
}

if (!CanCompareValueTypeBits((MetadataType)fieldType))
{
result = false;
break;
}
}
}

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

return result;
}

private struct OverlappingFieldTracker
{
private bool[] _usedBytes;

public OverlappingFieldTracker(MetadataType type)
{
_usedBytes = new bool[type.InstanceFieldSize.AsInt];
}

public bool TrackField(FieldDesc field)
{
int fieldBegin = field.Offset.AsInt;

TypeDesc fieldType = field.FieldType;

int fieldEnd;
if (fieldType.IsPointer || fieldType.IsFunctionPointer)
{
fieldEnd = fieldBegin + field.Context.Target.PointerSize;
}
else
{
Debug.Assert(fieldType.IsValueType);
fieldEnd = fieldBegin + ((DefType)fieldType).InstanceFieldSize.AsInt;
}

for (int i = fieldBegin; i < fieldEnd; i++)
{
if (_usedBytes[i])
return false;
_usedBytes[i] = true;
}

return true;
}

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

return false;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ protected override IEnumerable<MethodDesc> GetAllMethods(TypeDesc type)
{
return GetAllMethodsForEnum(type);
}
else if (type.IsValueType)
{
return GetAllMethodsForValueType(type);
}

return type.GetMethods();
}
Expand Down
9 changes: 9 additions & 0 deletions src/ILCompiler.TypeSystem/src/ILCompiler.TypeSystem.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,12 @@
<Compile Include="..\..\Common\src\TypeSystem\IL\Stubs\StructMarshallingThunk.Sorting.cs">
<Link>IL\Stubs\StructMarshallingThunk.Sorting.cs</Link>
</Compile>
<Compile Include="..\..\Common\src\TypeSystem\IL\Stubs\ValueTypeGetFieldHelperMethodOverride.cs">
<Link>IL\Stubs\ValueTypeGetFieldHelperMethodOverride.cs</Link>
</Compile>
<Compile Include="..\..\Common\src\TypeSystem\IL\Stubs\ValueTypeGetFieldHelperMethodOverride.Sorting.cs">
<Link>IL\Stubs\ValueTypeGetFieldHelperMethodOverride.Sorting.cs</Link>
</Compile>
<Compile Include="..\..\Common\src\TypeSystem\IL\TypeSystemContext.DelegateInfo.cs">
<Link>IL\TypeSystemContext.DelegateInfo.cs</Link>
</Compile>
Expand Down Expand Up @@ -418,6 +424,9 @@
<Compile Include="..\..\Common\src\TypeSystem\IL\TypeSystemContext.EnumMethods.cs">
<Link>IL\TypeSystemContext.EnumMethods.cs</Link>
</Compile>
<Compile Include="..\..\Common\src\TypeSystem\IL\TypeSystemContext.ValueTypeMethods.cs">
<Link>IL\TypeSystemContext.ValueTypeMethods.cs</Link>
</Compile>
<Compile Include="..\..\Common\src\TypeSystem\Interop\IL\InlineArrayType.Sorting.cs">
<Link>TypeSystem\Interop\IL\InlineArrayType.Sorting.cs</Link>
</Compile>
Expand Down
Loading