dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.3k stars 4.74k forks source link

How to get the size of a jitted method in memory? #36751

Closed Symbai closed 4 years ago

Symbai commented 4 years ago

Using System.Reflection.MethodInfo.MethodHandle.GetFunctionPointer() we're able to obtain the memory address of the beginning of a managed method when its jitted. Now how do I get the size of this method (i.e. the end address)?

AndyAyersMS commented 4 years ago

I don't know of a direct way to get this.

You can subscribe to the jit events stream and find that information in the relevant event. Note in principle a method's code may be split into two parts (though this does not happen in core, currently).

This isn't a codegen issue so will reclassify.

Symbai commented 4 years ago

Do you know of an indirect way to get this? I wouldn't mind about an unsafe way as I'm already using unsafe code to read the method table. I'm fine with something that works most of the time, hasn't to be deal with all situations like code split into two parts.

From what I've seen most code on github dealing with this problem are using clrmd to obtain the end address(es) which I cannot use in my scenario. Clrmd is also calling native code, so I have no idea how it's getting this information but it somehow can.

AndyAyersMS commented 4 years ago

Here's an example of using eventing. I don't know of an unsafe way.

If you run the above, you get data like:

Address            Size   Info                 Name
0x00007FFC0C080A50 000179 jitted tier0        System.Runtime.Intrinsics.Vector128.CreateScalarUnsafe
0x00007FFC0C080B20 000037 jitted tier0        System.Runtime.Intrinsics.Vector128.AsInt64
0x00007FFC0C080F70 000093 jitted fullopts     dynamicClass.IL_STUB_ReversePInvoke
0x00007FFC0C081650 000011 jitted tier0        System.Runtime.Intrinsics.X86.Popcnt.get_IsSupported
0x00007FFC0C082F00 000192 jitted tier1        System.SpanHelpers.SequenceCompareTo
0x00007FFC0C083D30 000377 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C083EF0 000166 jitted fullopts     dynamicClass.IL_STUB_WinRTtoCLR
0x00007FFC0C083FC0 000237 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C0840E0 000445 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C0846F0 000057 jitted minopts      JitEventSample.F
danmoseley commented 4 years ago

Copying here in case that useful gist gets lost 😄

code ```c# using System; using System.Diagnostics.Tracing; using System.Runtime.CompilerServices; using System.Threading; class JitEventListener : EventListener { const int JitKeyword = 0x00000010; const int VerboseMethodLoadId = 143; static bool first = true; protected override void OnEventSourceCreated(EventSource eventSource) { if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime")) { EnableEvents( eventSource, EventLevel.Verbose, (EventKeywords)(JitKeyword) ); } } protected override void OnEventWritten(EventWrittenEventArgs eventData) { if (eventData.EventId == VerboseMethodLoadId) { ulong startAddress = 0; uint size = 0; string namespaceName = "?"; string methodName = "?"; uint flags = 0; // see https://docs.microsoft.com/en-us/dotnet/framework/performance/method-etw-events for (int i = 0; i < eventData.Payload.Count; i++) { object payload = eventData.Payload[i]; string payloadName = eventData.PayloadNames[i]; if (payloadName == "MethodStartAddress") { startAddress = (ulong)payload; } else if (payloadName == "MethodSize") { size = (uint)payload; } else if (payloadName == "MethodNamespace") { namespaceName = (string)payload; } else if (payloadName == "MethodName") { methodName = (string)payload; } else if (payloadName == "MethodFlags") { flags = (uint)payload; } } string flagString = ""; if ((flags & 0x8) != 0) { flagString += " jitted"; } else { flagString += " prejitted"; } uint optLevel = ((flags >> 7) & 0x7); switch (optLevel) { case 1: flagString += " minopts"; break; case 2: flagString += " fullopts"; break; case 3: flagString += " tier0"; break; case 4: flagString += " tier1"; break; case 5: flagString += " tier1-OSR"; break; default: flagString += " unknown codegen"; break; } if (first) { Console.WriteLine("{0,-18} {1,-6} {2,-20} {3}", "Address", "Size", "Info", "Name"); first = false; } Console.WriteLine($"0x{startAddress:X16} {size:D6}{flagString,-20} {namespaceName}.{methodName}"); } } } class JitEventSample { [MethodImpl(MethodImplOptions.NoInlining)] static int F(int x) { return x * x; } static void Main(string[] args) { _ = new JitEventListener(); var f = F(33); Console.WriteLine($"F(33) = {f}"); // give event stream a chance to catch up Thread.Sleep(5_000); } } ```
AndyAyersMS commented 4 years ago

Note with this you can play around with the various config settings and build options and start to see the impact they have on the code the jit generates.

For "minopts" and "Tier0" codegen will be the same, and "fullopts" and "Tier1" codegen will be similar (though not always the same).

Note some methods are never optimized when jitting (like .cctors) because they are only ever called once.

C:\bugs\r36751>dotnet run
F(33) = 1089
Address            Size   Info                 Name
0x00007FFC0C070630 000179 jitted tier0        System.Runtime.Intrinsics.Vector128.CreateScalarUnsafe
0x00007FFC0C070700 000037 jitted tier0        System.Runtime.Intrinsics.Vector128.AsInt64
0x00007FFC0C070F50 000377 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C071110 000166 jitted fullopts     dynamicClass.IL_STUB_WinRTtoCLR
0x00007FFC0C0711E0 000237 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C071300 000055 jitted minopts      JitEventSample.F
0x00007FFC0C071B20 001769 jitted minopts      JitEventListener.OnEventWritten
0x00007FFC0C073490 000038 jitted minopts      JitEventListener..cctor
0x00007FFC0C073B00 000221 jitted fullopts     dynamicClass.IL_STUB_PInvoke
0x00007FFC0C073C10 000169 jitted fullopts     dynamicClass.IL_STUB_PInvoke

C:\bugs\r36751>dotnet run -c Release
F(33) = 1089
Address            Size   Info                 Name
0x00007FFC0C0605D0 000179 jitted tier0        System.Runtime.Intrinsics.Vector128.CreateScalarUnsafe
0x00007FFC0C0606A0 000037 jitted tier0        System.Runtime.Intrinsics.Vector128.AsInt64
0x00007FFC0C060EF0 000377 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C0610B0 000166 jitted fullopts     dynamicClass.IL_STUB_WinRTtoCLR
0x00007FFC0C061180 000237 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C0612A0 000016 jitted tier0        JitEventSample.F
0x00007FFC0C062CE0 001130 jitted fullopts     JitEventListener.OnEventWritten
0x00007FFC0C063190 000013 jitted tier0        JitEventListener..cctor
0x00007FFC0C0637F0 000221 jitted fullopts     dynamicClass.IL_STUB_PInvoke
0x00007FFC0C063900 000169 jitted fullopts     dynamicClass.IL_STUB_PInvoke

C:\bugs\r36751>set complus_tc_quickjitforloops=1

C:\bugs\r36751>dotnet run -c Release
F(33) = 1089
Address            Size   Info                 Name
0x00007FFC0C051660 000179 jitted tier0        System.Runtime.Intrinsics.Vector128.CreateScalarUnsafe
0x00007FFC0C051730 000037 jitted tier0        System.Runtime.Intrinsics.Vector128.AsInt64
0x00007FFC0C051F80 000377 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C052140 000166 jitted fullopts     dynamicClass.IL_STUB_WinRTtoCLR
0x00007FFC0C052210 000237 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C052330 000016 jitted tier0        JitEventSample.F
0x00007FFC0C052E20 001256 jitted tier0        JitEventListener.OnEventWritten
0x00007FFC0C053750 000013 jitted tier0        JitEventListener..cctor
0x00007FFC0C0545E0 000221 jitted fullopts     dynamicClass.IL_STUB_PInvoke
0x00007FFC0C0546F0 000169 jitted fullopts     dynamicClass.IL_STUB_PInvoke

C:\bugs\r36751>set complus_tieredcompilation=0

C:\bugs\r36751>dotnet run -c Release
F(33) = 1089
Address            Size   Info                 Name
0x00007FFC0C0803F0 000069 jitted fullopts     System.Runtime.Intrinsics.Vector128.CreateScalarUnsafe
0x00007FFC0C080450 000015 jitted fullopts     System.Runtime.Intrinsics.Vector128.AsInt64
0x00007FFC0C080880 000377 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C080A40 000166 jitted fullopts     dynamicClass.IL_STUB_WinRTtoCLR
0x00007FFC0C080B10 000237 jitted fullopts     dynamicClass.IL_STUB_CLRtoWinRT
0x00007FFC0C080C30 000006 jitted fullopts     JitEventSample.F
0x00007FFC0C082350 001130 jitted fullopts     JitEventListener.OnEventWritten
0x00007FFC0C082C00 000013 jitted minopts      JitEventListener..cctor
0x00007FFC0C082E50 000221 jitted fullopts     dynamicClass.IL_STUB_PInvoke
0x00007FFC0C082F60 000169 jitted fullopts     dynamicClass.IL_STUB_PInvoke
AndyAyersMS commented 4 years ago

I hooked up iced to disassemble the jitted code and was amused to see it disassembling itself:

;; 0x00007FFC01C07B30 000241 jitted tier0        Iced.Intel.DecoderInternal.OpCodeHandler_Gb_Eb.Decode
00007FFC01C07B30 55                   push      rbp
00007FFC01C07B31 4883EC30             sub       rsp,30h
00007FFC01C07B35 488D6C2430           lea       rbp,[rsp+30h]
00007FFC01C07B3A 33C0                 xor       eax,eax
00007FFC01C07B3C 488945F8             mov       [rbp-8],rax
00007FFC01C07B40 8945F4               mov       [rbp-0Ch],eax
00007FFC01C07B43 48894D10             mov       [rbp+10h],rcx
joperezr commented 4 years ago

Seems like question has been answered so I'll go ahead and close this for now. @Symbai feel free to reopen if you that was not the case.