forked from dotnet/corert
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove reflection in ValueType.Equals/GetHashCode (dotnet#5436)
The implementation relies on a new virtual method on `System.ValueType` that provides information about fields on a type. An override of this method is injected by the compiler when needed. This implementation is different from what Project N does (where we inject actual overrides of both methods). The CoreRT implementation is a bit more space-saving because there is only one method that does fewer things. This ends up being a 0.9% regression in size of a hello world app, but we do get some correctness with it and a potential to get the reflection stack completely out of the base hello world image (that one will be a huge win). We can get some of the regression back by: * Getting RyuJIT to generate more efficient code for the offset calculation (dotnet/coreclr#16527) * Making fewer things reflectable. We're currently generating the data for e.g. OSVERSIONINFO because it's used as a parameter in a method that is reflectable, and therefore the parameter gets boxed, and therefore it needs this data. It also drags in a fixed buffer internal type because it's a valuetype field on OSVERSIONINFO. This alone costs us 100+ bytes.
- Loading branch information
1 parent
8bbdab5
commit b442bed
Showing
7 changed files
with
535 additions
and
0 deletions.
There are no files selected for viewing
20 changes: 20 additions & 0 deletions
20
src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.Sorting.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
145 changes: 145 additions & 0 deletions
145
src/Common/src/TypeSystem/IL/Stubs/ValueTypeGetFieldHelperMethodOverride.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
189
src/Common/src/TypeSystem/IL/TypeSystemContext.ValueTypeMethods.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.