The following program should print "Printed!", then throw NRE. However it does not print anything.
using System;
using System.Runtime.CompilerServices;
public class Program
{
public static void Main()
{
Test(null);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Test(Base b)
{
b.Foo<string>(Bar());
}
private static int Bar()
{
Console.WriteLine("Printed!");
return 42;
}
}
public abstract class Base
{
public abstract void Foo<T>(int x);
}
public class Derived : Base
{
public override void Foo<T>(int x)
{
}
}
The cause is the following code that spills the function pointer after having popped the arguments into the call:
|
// Now make an indirect call through the function pointer |
|
|
|
unsigned lclNum = lvaGrabTemp(true DEBUGARG("VirtualCall through function pointer")); |
|
impStoreToTemp(lclNum, fptr, CHECK_SPILL_ALL); |
|
fptr = gtNewLclvNode(lclNum, TYP_I_IMPL); |
This reorders the evaluation of the function pointer with the arguments. Evaluating the function pointer for GVMs results in a null check.
The following program should print "Printed!", then throw NRE. However it does not print anything.
The cause is the following code that spills the function pointer after having popped the arguments into the call:
runtime/src/coreclr/jit/importercalls.cpp
Lines 420 to 424 in d23685a
This reorders the evaluation of the function pointer with the arguments. Evaluating the function pointer for GVMs results in a null check.