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?
Actually, your call to
Program.GetAssembly
was inlined intoProgram.Main
. But you can't see the difference since bothProgram.GetAssembly
andProgram.Main
are defined in the same assembly namedConsoleApplication2
.Although you can illustrate JIT-inlining in another way:
Without JIT-inlining (e.g. in Debug mode or if
[MethodImpl(MethodImplOptions.NoInlining)]
attribute is applied to theGetStackFrames
) 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:
This is the declaration for Assembly.GetCallingAssembly() in .NET 3.5:
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:
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