From 138f06858daea8f7314c4fbd9961c922f840fca8 Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Wed, 27 Nov 2019 16:24:58 -0800 Subject: [PATCH] Improve performance of RuntimeHelpers.GetMethodTable (#275) --- .../CompilerServices/RuntimeHelpers.CoreCLR.cs | 15 ++------- .../IL/Stubs/RuntimeHelpersIntrinsics.cs | 13 ++++++++ src/coreclr/src/vm/jitinterface.cpp | 37 ++++++++++++++++++++++ src/coreclr/src/vm/mscorlib.h | 1 + 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 5753585..19cf845 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -260,22 +260,13 @@ namespace System.Runtime.CompilerServices // GC.KeepAlive(o); // [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Intrinsic] internal static unsafe MethodTable* GetMethodTable(object obj) { - Debug.Assert(obj != null); - - // We know that the first data field in any managed object is immediately after the - // method table pointer, so just back up one pointer and immediately deref. - // This is not ideal in terms of minimizing instruction count but is the best we can do at the moment. + // The body of this function will be replaced by the EE with unsafe code + // See getILIntrinsicImplementationForRuntimeHelpers for how this happens. return (MethodTable *)Unsafe.Add(ref Unsafe.As(ref obj.GetRawData()), -1); - - // The JIT currently implements this as: - // lea tmp, [rax + 8h] ; assume rax contains the object reference, tmp is type IntPtr& - // mov tmp, qword ptr [tmp - 8h] ; tmp now contains the MethodTable* pointer - // - // Ideally this would just be a single dereference: - // mov tmp, qword ptr [rax] ; rax = obj ref, tmp = MethodTable* pointer } } diff --git a/src/coreclr/src/tools/Common/TypeSystem/IL/Stubs/RuntimeHelpersIntrinsics.cs b/src/coreclr/src/tools/Common/TypeSystem/IL/Stubs/RuntimeHelpersIntrinsics.cs index d45a6c9..ba723ca 100644 --- a/src/coreclr/src/tools/Common/TypeSystem/IL/Stubs/RuntimeHelpersIntrinsics.cs +++ b/src/coreclr/src/tools/Common/TypeSystem/IL/Stubs/RuntimeHelpersIntrinsics.cs @@ -30,6 +30,19 @@ namespace Internal.IL.Stubs return emit.Link(method); } + if (methodName == "GetMethodTable") + { + ILEmitter emit = new ILEmitter(); + ILCodeStream codeStream = emit.NewCodeStream(); + codeStream.EmitLdArg(0); + codeStream.Emit(ILOpcode.ldflda, emit.NewToken(method.Context.SystemModule.GetKnownType("System.Runtime.CompilerServices", "RawData").GetField("Data"))); + codeStream.EmitLdc(-method.Context.Target.PointerSize); + codeStream.Emit(ILOpcode.add); + codeStream.Emit(ILOpcode.ldind_i); + codeStream.Emit(ILOpcode.ret); + return emit.Link(method); + } + // All the methods handled below are per-instantiation generic methods if (method.Instantiation.Length != 1 || method.IsTypicalMethodDefinition) return null; diff --git a/src/coreclr/src/vm/jitinterface.cpp b/src/coreclr/src/vm/jitinterface.cpp index 505f4cb..d146ca9 100644 --- a/src/coreclr/src/vm/jitinterface.cpp +++ b/src/coreclr/src/vm/jitinterface.cpp @@ -7485,6 +7485,43 @@ bool getILIntrinsicImplementationForRuntimeHelpers(MethodDesc * ftn, return true; } + if (tk == MscorlibBinder::GetMethod(METHOD__RUNTIME_HELPERS__GET_METHOD_TABLE)->GetMemberDef()) + { + mdToken tokRawData = MscorlibBinder::GetField(FIELD__RAW_DATA__DATA)->GetMemberDef(); + + // In the CLR, an object is laid out as follows. + // [ object_header || MethodTable* (64-bit pointer) || instance_data ] + // ^ ^-- ref .firstField points here + // `-- reference (type O) points here + // + // So essentially what we want to do is to turn an object reference (type O) into a + // native int&, then dereference it to get the MethodTable*. (Essentially, an object + // reference is a MethodTable**.) Per ECMA-335, Sec. III.1.5, we can add + // (but not subtract) a & and an int32 to produce a &. So we'll get a reference to + // .firstField (type &), then back up one pointer length to get a value of + // essentially type (MethodTable*)&. Both of these are legal GC-trackable references + // to , regardless of 's actual length. + + static BYTE ilcode[] = { CEE_LDARG_0, // stack contains [ O ] = + CEE_LDFLDA,0,0,0,0, // stack contains [ & ] = ref .firstField + CEE_LDC_I4_S,(BYTE)(-TARGET_POINTER_SIZE), // stack contains [ &, int32 ] = -IntPtr.Size + CEE_ADD, // stack contains [ & ] = ref .methodTablePtr + CEE_LDIND_I, // stack contains [ native int ] = .methodTablePtr + CEE_RET }; + + ilcode[2] = (BYTE)(tokRawData); + ilcode[3] = (BYTE)(tokRawData >> 8); + ilcode[4] = (BYTE)(tokRawData >> 16); + ilcode[5] = (BYTE)(tokRawData >> 24); + + methInfo->ILCode = const_cast(ilcode); + methInfo->ILCodeSize = sizeof(ilcode); + methInfo->maxStack = 2; + methInfo->EHcount = 0; + methInfo->options = (CorInfoOptions)0; + return true; + } + if (tk == MscorlibBinder::GetMethod(METHOD__RUNTIME_HELPERS__ENUM_EQUALS)->GetMemberDef()) { // Normally we would follow the above pattern and unconditionally replace the IL, diff --git a/src/coreclr/src/vm/mscorlib.h b/src/coreclr/src/vm/mscorlib.h index ce78a3f..5f03cac 100644 --- a/src/coreclr/src/vm/mscorlib.h +++ b/src/coreclr/src/vm/mscorlib.h @@ -711,6 +711,7 @@ DEFINE_METHOD(RTFIELD, GET_FIELDHANDLE, GetFieldHandle, DEFINE_CLASS(RUNTIME_HELPERS, CompilerServices, RuntimeHelpers) DEFINE_METHOD(RUNTIME_HELPERS, IS_REFERENCE_OR_CONTAINS_REFERENCES, IsReferenceOrContainsReferences, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, IS_BITWISE_EQUATABLE, IsBitwiseEquatable, NoSig) +DEFINE_METHOD(RUNTIME_HELPERS, GET_METHOD_TABLE, GetMethodTable, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_DATA, GetRawData, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_SZ_ARRAY_DATA, GetRawSzArrayData, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_ARRAY_DATA, GetRawArrayData, NoSig) -- 2.7.4