Skip to content

Commit

Permalink
Use MethodTable.Of in generated valuetype methods (#83325)
Browse files Browse the repository at this point in the history
I thought it might improve codegen, but the improvements are marginal. Still, contributes to dotnet/runtimelab#232.
  • Loading branch information
MichalStrehovsky authored Mar 13, 2023
1 parent e247ef3 commit 1c8d37a
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ internal static unsafe object IsInstanceOfInterface(EETypePtr pTargetType, objec
//
[MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhBoxAny")]
private static extern unsafe object RhBoxAny(ref byte pData, MethodTable* pEEType);
internal static extern unsafe object RhBoxAny(ref byte pData, MethodTable* pEEType);

internal static unsafe object RhBoxAny(ref byte pData, EETypePtr pEEType)
=> RhBoxAny(ref pData, pEEType.ToPointer());
Expand Down Expand Up @@ -372,7 +372,7 @@ internal static unsafe string RhNewString(EETypePtr pEEType, int length)

[MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhBox")]
private static extern unsafe object RhBox(MethodTable* pEEType, ref byte data);
internal static extern unsafe object RhBox(MethodTable* pEEType, ref byte data);

internal static unsafe object RhBox(EETypePtr pEEType, ref byte data)
=> RhBox(pEEType.ToPointer(), ref data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
using System.Runtime;
using System.Runtime.CompilerServices;

using Internal.Runtime.Augments;
using Internal.Runtime;

using Debug = System.Diagnostics.Debug;

Expand All @@ -39,15 +39,15 @@ public abstract class ValueType
// This API is a bit awkward because we want to avoid burning more than one vtable slot on this.
// 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.
internal virtual int __GetFieldHelper(int index, out EETypePtr eeType)
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);
eeType = default;
mt = default;
return UseFastHelper;
}

public override bool Equals([NotNullWhen(true)] object? obj)
public override unsafe bool Equals([NotNullWhen(true)] object? obj)
{
if (obj == null || obj.GetEETypePtr() != this.GetEETypePtr())
return false;
Expand All @@ -71,7 +71,7 @@ public override bool Equals([NotNullWhen(true)] object? obj)
// Foreach field, box and call the Equals method.
for (int i = 0; i < numFields; i++)
{
int fieldOffset = __GetFieldHelper(i, out EETypePtr fieldType);
int fieldOffset = __GetFieldHelper(i, out MethodTable* fieldType);

// Fetch the value of the field on both types
object thisField = RuntimeImports.RhBoxAny(ref Unsafe.Add(ref thisRawData, fieldOffset), fieldType);
Expand Down Expand Up @@ -102,22 +102,22 @@ public override int GetHashCode()
return hashCode;
}

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

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

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

private static int FastGetValueTypeHashCodeHelper(EETypePtr type, ref byte data)
private static unsafe int FastGetValueTypeHashCodeHelper(MethodTable* type, ref byte data)
{
// Sanity check - if there are GC references, we should not be hashing bytes
Debug.Assert(!type.HasPointers);
Debug.Assert(!type->HasGCPointers);

int size = (int)type.ValueTypeSize;
int size = (int)type->ValueTypeSize;
int hashCode = 0;

for (int i = 0; i < size / 4; i++)
Expand All @@ -128,31 +128,31 @@ private static int FastGetValueTypeHashCodeHelper(EETypePtr type, ref byte data)
return hashCode;
}

private int RegularGetValueTypeHashCode(ref byte data, int numFields)
private unsafe int RegularGetValueTypeHashCode(ref byte data, int numFields)
{
int hashCode = 0;

// We only take the hashcode for the first non-null field. That's what the CLR does.
for (int i = 0; i < numFields; i++)
{
int fieldOffset = __GetFieldHelper(i, out EETypePtr fieldType);
int fieldOffset = __GetFieldHelper(i, out MethodTable* fieldType);
ref byte fieldData = ref Unsafe.Add(ref data, fieldOffset);

Debug.Assert(!fieldType.IsPointer);
Debug.Assert(!fieldType->IsPointerType);

if (fieldType.ElementType == Internal.Runtime.EETypeElementType.Single)
if (fieldType->ElementType == EETypeElementType.Single)
{
hashCode = Unsafe.As<byte, float>(ref fieldData).GetHashCode();
}
else if (fieldType.ElementType == Internal.Runtime.EETypeElementType.Double)
else if (fieldType->ElementType == EETypeElementType.Double)
{
hashCode = Unsafe.As<byte, double>(ref fieldData).GetHashCode();
}
else if (fieldType.IsPrimitive)
else if (fieldType->IsPrimitive)
{
hashCode = FastGetValueTypeHashCodeHelper(fieldType, ref fieldData);
}
else if (fieldType.IsValueType)
else if (fieldType->IsValueType)
{
// We have no option but to box since this value type could have
// GC pointers (we could find out if we want though), or fields of type Double/Single (we can't
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
namespace Internal.IL.Stubs
{
/// <summary>
/// Synthetic method override of "int ValueType.__GetFieldHelper(Int32, out EETypePtr)". This method is injected
/// Synthetic method override of "int ValueType.__GetFieldHelper(Int32, out MethodTable*)". 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>
Expand Down Expand Up @@ -46,7 +46,7 @@ public override MethodSignature Signature
{
TypeSystemContext context = _owningType.Context;
TypeDesc int32Type = context.GetWellKnownType(WellKnownType.Int32);
TypeDesc eeTypePtrType = context.SystemModule.GetKnownType("System", "EETypePtr");
TypeDesc eeTypePtrType = context.SystemModule.GetKnownType("Internal.Runtime", "MethodTable").MakePointerType();

_signature = new MethodSignature(0, 0, int32Type, new[] {
int32Type,
Expand All @@ -64,9 +64,8 @@ public override MethodIL EmitIL()

ILEmitter emitter = new ILEmitter();

TypeDesc eeTypePtrType = Context.SystemModule.GetKnownType("System", "EETypePtr");
MethodDesc eeTypePtrOfMethod = eeTypePtrType.GetKnownMethod("EETypePtrOf", null);
ILToken eeTypePtrToken = emitter.NewToken(eeTypePtrType);
TypeDesc methodTableType = Context.SystemModule.GetKnownType("Internal.Runtime", "MethodTable");
MethodDesc methodTableOfMethod = methodTableType.GetKnownMethod("Of", null);

var switchStream = emitter.NewCodeStream();
var getFieldStream = emitter.NewCodeStream();
Expand Down Expand Up @@ -98,10 +97,10 @@ public override MethodIL EmitIL()
// Don't unnecessarily create an MethodTable for the enum.
boxableFieldType = boxableFieldType.UnderlyingType;

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

getFieldStream.Emit(ILOpcode.stobj, eeTypePtrToken);
getFieldStream.Emit(ILOpcode.stind_i);

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

0 comments on commit 1c8d37a

Please sign in to comment.