Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my method calling Assembly.GetCallingAssembly() not JIT-inlined?

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?

like image 359
sharptooth Avatar asked Oct 16 '12 08:10

sharptooth


3 Answers

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.

like image 140
Hans Passant Avatar answered Nov 11 '22 02:11

Hans Passant


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)

  • Methods that are greater than 32 bytes of IL will not be inlined.
  • Virtual functions are not inlined.
  • Methods that have complex flow control will not be in-lined. Complex flow control is any flow control other than if/then/else; in this case, switch or while.
  • Methods that contain exception-handling blocks are not inlined, though methods that throw exceptions are still candidates for inlining.
  • If any of the method's formal arguments are structs, the method will not be inlined.

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

like image 3
mlorbetske Avatar answered Nov 11 '22 01:11

mlorbetske


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:

  1. GetStackFrames
  2. 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.

like image 1
Nikolay Khil Avatar answered Nov 11 '22 01:11

Nikolay Khil