Skip to content

Commit

Permalink
Reimplement a few helpers with new RuntimeHelpers APIs (#100846)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkoritzinsky authored Apr 11, 2024
1 parent 53608c5 commit b9cd89a
Show file tree
Hide file tree
Showing 14 changed files with 59 additions and 233 deletions.
3 changes: 0 additions & 3 deletions src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ public abstract partial class Enum
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Enum_GetValuesAndNames")]
private static partial void GetEnumValuesAndNames(QCallTypeHandle enumType, ObjectHandleOnStack values, ObjectHandleOnStack names, Interop.BOOL getNames);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object InternalBoxEnum(RuntimeType enumType, long value);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern unsafe CorElementType InternalGetCorElementType(MethodTable* pMT);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ internal static class MdConstant
#endregion
}

return RuntimeType.CreateEnum(fieldType, defaultValue);
return Enum.ToObject(fieldType, defaultValue);
}
else if (fieldType == typeof(DateTime))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3664,9 +3664,6 @@ public override Type MakeArrayType(int rank)
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool CanValueSpecialCast(RuntimeType valueType, RuntimeType targetType);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object AllocateValueType(RuntimeType type, object? value);

private CheckValueStatus TryChangeTypeSpecial(ref object value)
{
Pointer? pointer = value as Pointer;
Expand Down Expand Up @@ -3973,14 +3970,6 @@ internal object GetUninitializedObject()

#region Legacy internal static

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object _CreateEnum(RuntimeType enumType, long value);

internal static object CreateEnum(RuntimeType enumType, long value)
{
return _CreateEnum(enumType, value);
}

#if FEATURE_COMINTEROP
[MethodImpl(MethodImplOptions.InternalCall)]
private extern object InvokeDispMethod(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,8 @@ internal static EnumInfo<TStorage> GetEnumInfo<TStorage>(RuntimeType enumType, b
}
#pragma warning restore

private static unsafe object InternalBoxEnum(Type enumType, long value)
{
return ToObject(enumType.TypeHandle.ToMethodTable(), value);
}
internal static unsafe object ToObject(MethodTable* mt, long value)
=> InternalBoxEnum(new RuntimeTypeHandle(mt), value);

private static unsafe CorElementType InternalGetCorElementType(RuntimeType rt)
{
Expand Down Expand Up @@ -151,51 +149,5 @@ internal static Type InternalGetUnderlyingType(RuntimeType enumType)

return GetEnumInfo(enumType).UnderlyingType;
}

[Conditional("BIGENDIAN")]
private static unsafe void AdjustForEndianness(ref byte* pValue, MethodTable* enumEEType)
{
// On Debug builds, include the big-endian code to help deter bitrot (the "Conditional("BIGENDIAN")" will prevent it from executing on little-endian).
// On Release builds, exclude code to deter IL bloat and toolchain work.
#if BIGENDIAN || DEBUG
EETypeElementType elementType = enumEEType->ElementType;
switch (elementType)
{
case EETypeElementType.SByte:
case EETypeElementType.Byte:
pValue += sizeof(long) - sizeof(byte);
break;

case EETypeElementType.Int16:
case EETypeElementType.UInt16:
pValue += sizeof(long) - sizeof(short);
break;

case EETypeElementType.Int32:
case EETypeElementType.UInt32:
pValue += sizeof(long) - sizeof(int);
break;

case EETypeElementType.Int64:
case EETypeElementType.UInt64:
break;

default:
throw new NotSupportedException();
}
#endif //BIGENDIAN || DEBUG
}

#region ToObject

internal static unsafe object ToObject(MethodTable* enumEEType, long value)
{
Debug.Assert(enumEEType->IsEnum);

byte* pValue = (byte*)&value;
AdjustForEndianness(ref pValue, enumEEType);
return RuntimeImports.RhBox(enumEEType, ref *pValue);
}
#endregion
}
}
3 changes: 0 additions & 3 deletions src/coreclr/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ FCFuncEnd()

FCFuncStart(gEnumFuncs)
FCFuncElement("InternalGetCorElementType", ReflectionEnum::InternalGetCorElementType)
FCFuncElement("InternalBoxEnum", ReflectionEnum::InternalBoxEnum)
FCFuncEnd()

FCFuncStart(gObjectFuncs)
Expand Down Expand Up @@ -114,9 +113,7 @@ FCFuncEnd()

FCFuncStart(gSystem_RuntimeType)
FCFuncElement("GetGUID", ReflectionInvocation::GetGUID)
FCFuncElement("_CreateEnum", ReflectionInvocation::CreateEnum)
FCFuncElement("CanValueSpecialCast", ReflectionInvocation::CanValueSpecialCast)
FCFuncElement("AllocateValueType", ReflectionInvocation::AllocateValueType)
#if defined(FEATURE_COMINTEROP)
FCFuncElement("InvokeDispMethod", ReflectionInvocation::InvokeDispMethod)
#endif // defined(FEATURE_COMINTEROP)
Expand Down
84 changes: 1 addition & 83 deletions src/coreclr/vm/reflectioninvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,54 +113,6 @@ FCIMPL2(FC_BOOL_RET, ReflectionInvocation::CanValueSpecialCast, ReflectClassBase
}
FCIMPLEND

/// <summary>
/// Allocate the value type and copy the optional value into it.
/// </summary>
FCIMPL2(Object*, ReflectionInvocation::AllocateValueType, ReflectClassBaseObject *pTargetTypeUNSAFE, Object *valueUNSAFE) {
CONTRACTL
{
FCALL_CHECK;
PRECONDITION(CheckPointer(pTargetTypeUNSAFE));
PRECONDITION(CheckPointer(valueUNSAFE, NULL_OK));
}
CONTRACTL_END;

struct _gc
{
REFLECTCLASSBASEREF refTargetType;
OBJECTREF value;
OBJECTREF obj;
}gc;

gc.value = ObjectToOBJECTREF(valueUNSAFE);
gc.obj = gc.value;
gc.refTargetType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTargetTypeUNSAFE);

HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);

TypeHandle targetType = gc.refTargetType->GetType();

// This method is only intended for value types; it is not called directly by any public APIs
// so we don't expect validation issues here.
_ASSERTE(targetType.IsValueType());

MethodTable* allocMT = targetType.AsMethodTable();
_ASSERTE(!allocMT->IsByRefLike());

gc.obj = allocMT->Allocate();
_ASSERTE(gc.obj != NULL);

if (gc.value != NULL) {
_ASSERTE(allocMT->IsEquivalentTo(gc.value->GetMethodTable()));
CopyValueClass(gc.obj->UnBox(), gc.value->UnBox(), allocMT);
}

HELPER_METHOD_FRAME_END();

return OBJECTREFToObject(gc.obj);
}
FCIMPLEND

FCIMPL6(void, RuntimeFieldHandle::SetValue, ReflectFieldObject *pFieldUNSAFE, Object *targetUNSAFE, Object *valueUNSAFE, ReflectClassBaseObject *pFieldTypeUNSAFE, ReflectClassBaseObject *pDeclaringTypeUNSAFE, CLR_BOOL *pIsClassInitialized) {
CONTRACTL
{
Expand Down Expand Up @@ -1535,24 +1487,6 @@ FCIMPL4(void, ReflectionInvocation::MakeTypedReference, TypedByRef * value, Obje
}
FCIMPLEND

FCIMPL2_IV(Object*, ReflectionInvocation::CreateEnum, ReflectClassBaseObject *pTypeUNSAFE, INT64 value) {
FCALL_CONTRACT;

REFLECTCLASSBASEREF refType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pTypeUNSAFE);

TypeHandle typeHandle = refType->GetType();
_ASSERTE(typeHandle.IsEnum());
OBJECTREF obj = NULL;
HELPER_METHOD_FRAME_BEGIN_RET_1(refType);
MethodTable *pEnumMT = typeHandle.AsMethodTable();
obj = pEnumMT->Box(ArgSlotEndiannessFixup ((ARG_SLOT*)&value,
pEnumMT->GetNumInstanceFieldBytes()));

HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(obj);
}
FCIMPLEND

#ifdef FEATURE_COMINTEROP
FCIMPL8(Object*, ReflectionInvocation::InvokeDispMethod, ReflectClassBaseObject* refThisUNSAFE,
StringObject* nameUNSAFE,
Expand Down Expand Up @@ -1965,7 +1899,7 @@ extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectIn
bool fHasSideEffectsUnused;
*ppfnAllocator = CEEJitInfo::getHelperFtnStatic(CEEInfo::getNewHelperStatic(pMT, &fHasSideEffectsUnused));
*pvAllocatorFirstArg = pMT;

pMT->EnsureInstanceActive();

if (pMT->HasPreciseInitCctors())
Expand Down Expand Up @@ -2105,22 +2039,6 @@ extern "C" void QCALLTYPE Enum_GetValuesAndNames(QCall::TypeHandle pEnumType, QC
END_QCALL;
}

FCIMPL2_IV(Object*, ReflectionEnum::InternalBoxEnum, ReflectClassBaseObject* target, INT64 value) {
FCALL_CONTRACT;

VALIDATEOBJECT(target);
OBJECTREF ret = NULL;

MethodTable* pMT = target->GetType().AsMethodTable();
HELPER_METHOD_FRAME_BEGIN_RET_0();

ret = pMT->Box(ArgSlotEndiannessFixup((ARG_SLOT*)&value, pMT->GetNumInstanceFieldBytes()));

HELPER_METHOD_FRAME_END();
return OBJECTREFToObject(ret);
}
FCIMPLEND

extern "C" int32_t QCALLTYPE ReflectionInvocation_SizeOf(QCall::TypeHandle pType)
{
QCALL_CONTRACT_NO_GC_TRANSITION;
Expand Down
3 changes: 0 additions & 3 deletions src/coreclr/vm/reflectioninvocation.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,9 @@ class ReflectionInvocation {
static FCDECL8(Object*, InvokeDispMethod, ReflectClassBaseObject* refThisUNSAFE, StringObject* nameUNSAFE, INT32 invokeAttr, Object* targetUNSAFE, PTRArray* argsUNSAFE, PTRArray* byrefModifiersUNSAFE, LCID lcid, PTRArray* namedParametersUNSAFE);
#endif // FEATURE_COMINTEROP
static FCDECL2(void, GetGUID, ReflectClassBaseObject* refThisUNSAFE, GUID * result);
static FCDECL2_IV(Object*, CreateEnum, ReflectClassBaseObject *pTypeUNSAFE, INT64 value);

// helper fcalls for invocation
static FCDECL2(FC_BOOL_RET, CanValueSpecialCast, ReflectClassBaseObject *valueType, ReflectClassBaseObject *targetType);
static FCDECL2(Object*, AllocateValueType, ReflectClassBaseObject *targetType, Object *valueUNSAFE);
};

extern "C" void QCALLTYPE ReflectionInvocation_CompileMethod(MethodDesc * pMD);
Expand All @@ -77,7 +75,6 @@ extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectIn
class ReflectionEnum {
public:
static FCDECL1(INT32, InternalGetCorElementType, MethodTable* pMT);
static FCDECL2_IV(Object*, InternalBoxEnum, ReflectClassBaseObject* pEnumType, INT64 value);
};

extern "C" void QCALLTYPE Enum_GetValuesAndNames(QCall::TypeHandle pEnumType, QCall::ObjectHandleOnStack pReturnValues, QCall::ObjectHandleOnStack pReturnNames, BOOL fGetNames);
Expand Down
35 changes: 26 additions & 9 deletions src/libraries/System.Private.CoreLib/src/System/Enum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

#pragma warning disable 8500 // Allow taking address of managed types

Expand Down Expand Up @@ -768,7 +769,7 @@ private static unsafe bool TryParse(Type enumType, ReadOnlySpan<char> value, boo
break;
}

result = parsed ? InternalBoxEnum(rt, longScratch) : null;
result = parsed ? InternalBoxEnum(rt.TypeHandle, longScratch) : null;
return parsed;

[MethodImpl(MethodImplOptions.NoInlining)]
Expand Down Expand Up @@ -2225,31 +2226,47 @@ public static object ToObject(Type enumType, object value)

[CLSCompliant(false)]
public static object ToObject(Type enumType, sbyte value) =>
InternalBoxEnum(ValidateRuntimeType(enumType), value);
InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value);

public static object ToObject(Type enumType, short value) =>
InternalBoxEnum(ValidateRuntimeType(enumType), value);
InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value);

public static object ToObject(Type enumType, int value) =>
InternalBoxEnum(ValidateRuntimeType(enumType), value);
InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value);

public static object ToObject(Type enumType, byte value) =>
InternalBoxEnum(ValidateRuntimeType(enumType), value);
InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value);

[CLSCompliant(false)]
public static object ToObject(Type enumType, ushort value) =>
InternalBoxEnum(ValidateRuntimeType(enumType), value);
InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value);

[CLSCompliant(false)]
public static object ToObject(Type enumType, uint value) =>
InternalBoxEnum(ValidateRuntimeType(enumType), value);
InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value);

public static object ToObject(Type enumType, long value) =>
InternalBoxEnum(ValidateRuntimeType(enumType), value);
InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, value);

[CLSCompliant(false)]
public static object ToObject(Type enumType, ulong value) =>
InternalBoxEnum(ValidateRuntimeType(enumType), unchecked((long)value));
InternalBoxEnum(ValidateRuntimeType(enumType).TypeHandle, unchecked((long)value));

private static object InternalBoxEnum(RuntimeTypeHandle type, long value)
{
ReadOnlySpan<byte> rawData = MemoryMarshal.AsBytes(new ReadOnlySpan<long>(ref value));
// On little-endian systems, we can always use the pointer to the start of the scratch space
// as memory layout since the least-significant bit is at the lowest address.
// For big-endian systems, the least-significant bit is at the highest address, so we need to adjust
// our starting ref to the correct offset from the end of the scratch space to get the value to box.
if (!BitConverter.IsLittleEndian)
{
int size = RuntimeHelpers.SizeOf(type);
rawData = rawData.Slice(sizeof(long) - size);
}

return RuntimeHelpers.Box(ref MemoryMarshal.GetReference(rawData), type)!;
}

internal static bool AreSequentialFromZero<TStorage>(TStorage[] values) where TStorage : struct, INumber<TStorage>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ private static bool TryByRefFastPath(RuntimeType type, ref object arg)
{
if (sigElementType.IsValueType)
{
Debug.Assert(!sigElementType.IsNullableOfT, "A true boxed Nullable<T> should never be here.");
// Make a copy to prevent the boxed instance from being directly modified by the method.
arg = RuntimeType.AllocateValueType(sigElementType, arg);
}
Expand Down
25 changes: 25 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,31 @@ internal bool CheckValue(
return false;
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
Justification = "AllocateValueType is only called on a ValueType. You can always create an instance of a ValueType.")]
[return: NotNullIfNotNull(nameof(value))]
internal static object? AllocateValueType(RuntimeType type, object? value)
{
Debug.Assert(type.IsValueType);
Debug.Assert(!type.IsByRefLike);

if (value is not null)
{
// Make a copy of the provided value by re-boxing the existing value's underlying data.
Debug.Assert(type.IsEquivalentTo(value.GetType()));
return RuntimeHelpers.Box(ref RuntimeHelpers.GetRawData(value), type.TypeHandle)!;
}

if (type.IsNullableOfT)
{
// If the type is Nullable<T>, then create a true boxed Nullable<T> of the default Nullable<T> value.
return RuntimeMethodHandle.ReboxToNullable(null, type);
}

// Otherwise, just create a default instance of the type.
return RuntimeHelpers.GetUninitializedObject(type);
}

private CheckValueStatus TryChangeType(ref object? value, ref bool copyBack)
{
RuntimeType? sigElementType;
Expand Down
10 changes: 0 additions & 10 deletions src/mono/System.Private.CoreLib/src/System/Enum.Mono.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,12 @@ public partial class Enum
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void GetEnumValuesAndNames(QCallTypeHandle enumType, out ulong[] values, out string[] names);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void InternalBoxEnum(QCallTypeHandle enumType, ObjectHandleOnStack res, long value);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern CorElementType InternalGetCorElementType(QCallTypeHandle enumType);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void InternalGetUnderlyingType(QCallTypeHandle enumType, ObjectHandleOnStack res);

private static object InternalBoxEnum(RuntimeType enumType, long value)
{
object? res = null;
InternalBoxEnum(new QCallTypeHandle(ref enumType), ObjectHandleOnStack.Create(ref res), value);
return res!;
}

private static unsafe CorElementType InternalGetCorElementType(RuntimeType rt)
{
Debug.Assert(rt.IsActualEnum);
Expand Down
Loading

0 comments on commit b9cd89a

Please sign in to comment.