I'm trying to craft a short C# snippet that would illustrate change of Assembly.GetCallingAssembly()
behavior because of JIT-inlining outlined in MSDN. Here's my code so far:
class Program
{
static void Main(string[] args)
{
Console.WriteLine( GetAssembly().FullName );
Console.ReadLine();
}
static Assembly GetAssembly()
{
return System.Reflection.Assembly.GetCallingAssembly();
}
}
which I build in "Release" and start using "Start Without Debugging" - this setup made code from this answer incur inlining. The result I see is
ConsoleApplication2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
so clearly GetAssembly()
is not inlined into Main()
, otherwise I'd see mscorlib
as the calling assembly.
I've reviewed all the inlining criteria and I don't see why GetAssembly()
would not be inlined.
Is it somehow possible to know why exactly JIT compiler decided to not inline a call?
This is the declaration for Assembly.GetCallingAssembly() in .NET 3.5:
[MethodImpl(MethodImplOptions.NoInlining)]
public static Assembly GetCallingAssembly()
{
StackCrawlMark lookForMyCallersCaller = StackCrawlMark.LookForMyCallersCaller;
return nGetExecutingAssembly(ref lookForMyCallersCaller);
}
The StackCrawlMark enum is interesting, "look for my callers caller" can't work well when the caller is going to be inlined. There's a comment in SSCLI20 source code for thread.cs where the enum gets declared:
declaring a local var of this enum type and passing it by ref into a function that needs to do a stack crawl will both prevent inlining of the calle [sic] and pass an ESP point to stack crawl to
Which is a good match for what happens in GetCallingAssembly(), it's a local variable and does indeed gets passed by ref. Not sure what the mechanism is, the jitter can however produce a method attribute named CORINFO_FLG_BAD_INLINEE. Which in turn forces a call to MethodDesc::SetNotInline(). That's a guess, this is very obscure.
To add my two cents to this. Don't rely on something the JIT may do to have your program function correctly.
Some of the conditions that would prohibit the JIT from being able to inline a method are as follows (taken from here)
Just because the JIT could inline the method, doesn't mean it necessarily will. Combine that with the difference in behavior between build configuration/runtime/OS combinations and, well... there you have it.
More information on the .Net 3.5 SP1 JIT's inlining behavior here
Actually, your call to Program.GetAssembly
was inlined into Program.Main
. But you can't see the difference since both Program.GetAssembly
and Program.Main
are defined in the same assembly named ConsoleApplication2
.
Although you can illustrate JIT-inlining in another way:
using System;
using System.Diagnostics;
namespace A3
{
public class Program
{
static void Main(string[] args)
{
StackFrame[] stackFrames = GetStackFrames();
foreach (StackFrame stackFrame in stackFrames)
Console.WriteLine(stackFrame.GetMethod().Name); // write method name
Console.ReadLine();
}
//[MethodImpl(MethodImplOptions.NoInlining)]
static StackFrame[] GetStackFrames()
{
StackTrace stackTrace = new StackTrace(); // get call stack
return stackTrace.GetFrames(); // get method calls (frames)
}
}
}
Without JIT-inlining (e.g. in Debug mode or if [MethodImpl(MethodImplOptions.NoInlining)]
attribute is applied to the GetStackFrames
) it will write at least two lines to console:
GetStackFrames
Main
But if inlining occurs, stackFrames
will contain only one method: Main
Update
Also, as you can read here: Debugging and the Hosting Process and as Rawling mentioned in his comment:
Assembly.GetCallingAssembly().FullName returns different results depending on whether the hosting process is enabled. If you call Assembly.GetCallingAssembly().FullName with the hosting process enabled, it returns mscorlib. If you call Assembly.GetCallingAssembly().FullName with the hosting process disabled, it returns the application name.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With