I've testing some new CLR 4.0 behavior in method inlining (cross-assembly inlining) and found some strage results:
Assembly ClassLib.dll:
using System.Diagnostics;
using System;
using System.Reflection;
using System.Security;
using System.Runtime.CompilerServices;
namespace ClassLib
{
public static class A
{
static readonly MethodInfo GetExecuting =
typeof(Assembly).GetMethod("GetExecutingAssembly");
public static Assembly Foo(out StackTrace stack) // 13 bytes
{
// explicit call to GetExecutingAssembly()
stack = new StackTrace();
return Assembly.GetExecutingAssembly();
}
public static Assembly Bar(out StackTrace stack) // 25 bytes
{
// reflection call to GetExecutingAssembly()
stack = new StackTrace();
return (Assembly) GetExecuting.Invoke(null, null);
}
public static Assembly Baz(out StackTrace stack) // 9 bytes
{
stack = new StackTrace();
return null;
}
public static Assembly Bob(out StackTrace stack) // 13 bytes
{
// call of non-inlinable method!
return SomeSecurityCriticalMethod(out stack);
}
[SecurityCritical, MethodImpl(MethodImplOptions.NoInlining)]
static Assembly SomeSecurityCriticalMethod(out StackTrace stack)
{
stack = new StackTrace();
return Assembly.GetExecutingAssembly();
}
}
}
Assembly ConsoleApp.exe
using System;
using ClassLib;
using System.Diagnostics;
class Program
{
static void Main()
{
Console.WriteLine("runtime: {0}", Environment.Version);
StackTrace stack;
Console.WriteLine("Foo: {0}\n{1}", A.Foo(out stack), stack);
Console.WriteLine("Bar: {0}\n{1}", A.Bar(out stack), stack);
Console.WriteLine("Baz: {0}\n{1}", A.Baz(out stack), stack);
Console.WriteLine("Bob: {0}\n{1}", A.Bob(out stack), stack);
}
}
Results:
runtime: 4.0.30128.1
Foo: ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
at ClassLib.A.Foo(StackTrace& stack)
at Program.Main()
Bar: ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
at ClassLib.A.Bar(StackTrace& stack)
at Program.Main()
Baz:
at Program.Main()
Bob: ClassLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
at Program.Main()
So questions are:
- Why JIT does not inlined
Foo
andBar
calls asBaz
does? They are lower than 32 bytes of IL and are good candidates for inlining. - Why JIT inlined call of
Bob
and inner call ofSomeSecurityCriticalMethod
that is marked with the[MethodImpl(MethodImplOptions.NoInlining)]
attribute? - Why
GetExecutingAssembly
returns a valid assembly when is called by inlinedBaz
andSomeSecurityCriticalMethod
methods? I've expect that it performs the stack walk to detect the executing assembly, but stack will contains onlyProgram.Main()
call and no methods ofClassLib
assenbly, toConsoleApp
should be returned.