Blog Stats
  • Posts - 44
  • Articles - 0
  • Comments - 68
  • Trackbacks - 0

 

Friday, July 20, 2012

Inside the DLR - Callsites


The DLR was introduced in .NET 4 to allow dynamic languages, like Python or Ruby, to run on the CLR. The DLR is also used by C# 4, released at the same time, to implement dynamic binding via the dynamic keyword. In this post, I'll be looking at what exactly happens when you issue a dynamically-bound call in C#.

What is the DLR?

The Dynamic Language Runtime isn't a runtime. At least, not in the same way as the Common Language Runtime. The DLR is a library that runs on the CLR, just like any other arbitary assembly. The library itself is a large and complex beast, with hooks at all levels for various interoperability and extensibility scenarios. In this post, I'll only be looking at the core functionality - dynamically binding to methods and executing what is found quickly and efficiently.

Callsites

What happens when you compile a dynamically-bound statement in C#? Take the following example. This method contains a single dynamic invocation of something that looks like an instance method called Func, that takes two integer arguments:

public void DynamicCall(dynamic d, int i)
{
    d.Func(10, i);
}
When the C# compiler sees this, and notices that it's a dynamically bound call, it doesn't generate a normal method call using call or callvirt IL instructions. What is actually generates is this:
private static class SiteContainer
{
    public static CallSite<Action<CallSite, object, int, int>> Site;
}

public void DynamicCall(dynamic d, int i)
{
    if (SiteContainer.Site == null)
    {
        SiteContainer.Site = CallSite<Action<CallSite, object, int, int>>.Create(
            Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(
                CSharpBinderFlags.ResultDiscarded,
                "Func",
                null,
                typeof(Program),
                new CSharpArgumentInfo[] {
                    CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                    CSharpArgumentInfo.Create(
                        CSharpArgumentInfoFlags.Constant
                            | CSharpArgumentInfoFlags.UseCompileTimeType,
                        null),
                    CSharpArgumentInfo.Create(
                        CSharpArgumentInfoFlags.UseCompileTimeType,
                        null)
                }));
    }
    
    SiteContainer.Site.Target(SiteContainer.Site, d, 10, i);
}

Woah. What on earth is going on here?

Creating callsites

The core of the DLR is built around the CallSite and CallSite<T> types. These types provide the bulk of the infrastructure used to implement C# and VB dynamic, by providing a sophisticated cache for reflection lookups.

All the information required to resolve a dynamic call can be split into what is known at compile time, and what is known at runtime. Compile-time information includes the input and output types of the call (as represented by the Action or Func type argument to CallSite<T>), the name of what is being invoked, type arguments, argument names, etc. This information is encapsulated in a CallSiteBinder that is created the first time a dynamic call is invoked, and stored in the CallSite, inside a static site container.

Since this is compiled and invoked from C#, the C# runtime binder, in the Microsoft.CSharp.dll assembly, is used to create and populate a subclass of CallSiteBinder. As this particular dynamic call is something that looks like a method invocation, an instance of the CSharpInvokeBinder type is passed to Create, using the Binder.InvokeMember helper method.

This binder is created using everything it needs to resolve the call at runtime. What exactly is required varies from binder to binder, but the CSharpInvokeBinder that is used in this example requires information on:

  1. What happens to the return type (in this example, it gets discarded, if there is one)
  2. The name of the thing being invoked
  3. Any generic arguments to the method call (in this example, none)
  4. The type this call is made from (to resolve overloads based on accessibility)
  5. Information on the arguments (for example, where the value comes from, argument names, whether it's ref or out, etc)

Then, to actually invoke the dynamic call, the actual values used to invoke the callsite each time are passed into the callsite's Target delegate. This delegate handles resolving the right method to call at runtime, using the information stored in the CallSiteBinder. How this works is the subject of my next post.

Cross posted from Simple Talk.
 

 

Copyright © simonc