Emitting CallSite<T> manually

Jan 30, 2010 at 11:22 AM
Edited Jan 30, 2010 at 11:22 AM

I'm in the process of converting IronJS from using LambdaExpression.Compile() to LambdaExpresssion.CompileToMethod(), I've realized that I need to emit CallSite<T> objects directly instead of DynamicExpression objects, for example to create a call site that uses an invoke binder I should emit something like this:

    var site = 
        CallSite<Func<CallSite, object, object>>.Create(
            new MyInvokeBinder(
                new CallInfo(argCount)
            )
        );

I have then assumed that instead of emitting a DynamicExpression I am to emit an actual MethodCallExpression that invokes the site.Target delegate of the just created CallSite<T> object (to my understanding the CallSite<T> will handle the L0, L1 and L2 caching of delegates when I invoke CallSite<T>.Target - I don't need to bother updating the CallSite<T>, that is done automatically if site.Target is invoked and the restrictions emitted from the dynamic binder fail).

I am having problems with emitting the actual call to the site, I assumed something like this (using Et = System.Linq.Expressions.Expression):

    return Et.Call(
        Et.Field(
            Et.Constant(site),
            "Target"
        ),
        site.Target.GetType().GetMethod("Invoke"),
        Et.Constant(site),
        idNode.Generate2(etgen)
    );

But that gives me the (by now all to familiar) exception: "CompileToMethod cannot compile constant 'System.Runtime.CompilerServices.CallSite`1[System.Func`3[System.Runtime.CompilerServices.CallSite,System.Object,System.Object]]' because it is a non-trivial value, such as a live object. Instead, create an expression tree that can construct this value." which is the same error as I got when I asked about CompileToMethod earlier, so I tried doing:

But that will, of course, fail on the Et.Field with "Instance field 'Target' is not defined for type 'System.Object'", or fail on the Et.Call expression itself with "Expression of type 'System.Object' cannot be used for parameter of type 'System.Runtime.CompilerServices.CallSite' of method 'System.Object Invoke(System.Runtime.CompilerServices.CallSite, System.Object)'".

As always I've tried reading the IronRuby sources to get a better grasp (it's actually how I found out most of what I've groked about emitting CallSite<T> objects manually), but yet again they are way to complex to deduce a small example out of. 

Jan 30, 2010 at 4:03 PM
Edited Jan 30, 2010 at 4:07 PM

After a few hour of searching, reading the DLR sources, IPy sources, talking with a few guys on #monodev @ irc.gnome.org and finally reading the Clojure-CLR sources I cracked it (at least it works nows), the trick seems to be to emit a static field on the type you're building using TypeBuilder for each unique CallSite<T> in your code. You initialize each of these fields at startup to the correct CallSite<T> object, then you use the static field wherever you need to access the CallSite<T> in your expression tree. 

The following code for emitting a DynamicExpression when compiling to a dynamic method:

    return Et.Dynamic(
        new MyInvokeBinder(
            new CallInfo(0)
	),
        typeof(object),
        TargetExpr
    );

Is replaced by this, when compiling to a static method using CompileToMethod:

    var delegateType = typeof(Func<CallSite, object, object>);
    var siteType = typeof(CallSite<Func<CallSite, object, object>>);

    var staticField = Expression.Field(null,
        typeBuilder.DefineField("callsite$0", siteType, FieldAttributes.Static | FieldAttributes.Public)
    );

    var initExpr = Expression.Call(
            AstUtils.SimpleNewHelper(
                typeof(JsInvokeBinder2).GetConstructor(new[] { typeof(CallInfo)}),
                AstUtils.SimpleNewHelper(
                    typeof(CallInfo).GetConstructor(new[] { typeof(int) }),
                    Et.Constant(0)
                )
            )
        );

    // Here staticField and initExpr
    // needs to be pushed to some 
    // initialization handling code
    // that pushes them to the top
    // of the expression tree.
    //
    // This has to be done because
    // staticField has to be initialized
    // with the binder before we reach
    // this Call expression when we're
    // executing

    return Expression.Call(
        Expression.Field(
            staticField,
            siteType.GetField("Target")
        ),
        delegateType.GetMethod("Invoke"),
        staticField,
        TargetExpr
    );

This is how I ended up solving it, I'm posting so that anyone else searching for a solution to this might be spared 8 hours of digging through source code. It seems to be correct and is working for me, if anyone that is more knowledgeable in the DLR would comment and confirm (or show how it's supposed be done) that would be great (both for me, and anyone else looking into how to do this).

Jan 30, 2010 at 4:09 PM

One good resource for "how do I do this with the DLR" is the C#4 compiler. You can compile a method which uses a dynamic variable and then look at the output with Reflector. (Obviously, you need the VS2010 beta installed for this.)

Jan 30, 2010 at 5:04 PM
Edited Jan 30, 2010 at 5:09 PM

curthagenlocher: That was a very smart idea, will start doing that right away!

I have a follow up question also: Should I emit one CallSite<T> object per call site? or should I use canonical CallSite<T> objects like with Binders? 

Taking curthagenlochers advice I compiled this code in VS2010:

namespace Reflector
{
    public class Reflector
    {
        public void Invoke()
        {
            dynamic x = 1;
            x();
            x();
            x();
            x();
            x();
            x();
        }
    }
}

Which gave me this in reflector:

public void Invoke()
{
    object x = 1;
    if (<Invoke>o__SiteContainer0.<>p__Site1 == null)
    {
        <Invoke>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(Binder.Invoke(0x100, typeof(Reflector.Reflector), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null) }));
    }
    <Invoke>o__SiteContainer0.<>p__Site1.Target(<Invoke>o__SiteContainer0.<>p__Site1, x);
    if (<Invoke>o__SiteContainer0.<>p__Site2 == null)
    {
        <Invoke>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, object>>.Create(Binder.Invoke(0x100, typeof(Reflector.Reflector), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null) }));
    }
    <Invoke>o__SiteContainer0.<>p__Site2.Target(<Invoke>o__SiteContainer0.<>p__Site2, x);
    if (<Invoke>o__SiteContainer0.<>p__Site3 == null)
    {
        <Invoke>o__SiteContainer0.<>p__Site3 = CallSite<Action<CallSite, object>>.Create(Binder.Invoke(0x100, typeof(Reflector.Reflector), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null) }));
    }
    <Invoke>o__SiteContainer0.<>p__Site3.Target(<Invoke>o__SiteContainer0.<>p__Site3, x);
    if (<Invoke>o__SiteContainer0.<>p__Site4 == null)
    {
        <Invoke>o__SiteContainer0.<>p__Site4 = CallSite<Action<CallSite, object>>.Create(Binder.Invoke(0x100, typeof(Reflector.Reflector), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null) }));
    }
    <Invoke>o__SiteContainer0.<>p__Site4.Target(<Invoke>o__SiteContainer0.<>p__Site4, x);
    if (<Invoke>o__SiteContainer0.<>p__Site5 == null)
    {
        <Invoke>o__SiteContainer0.<>p__Site5 = CallSite<Action<CallSite, object>>.Create(Binder.Invoke(0x100, typeof(Reflector.Reflector), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null) }));
    }
    <Invoke>o__SiteContainer0.<>p__Site5.Target(<Invoke>o__SiteContainer0.<>p__Site5, x);
    if (<Invoke>o__SiteContainer0.<>p__Site6 == null)
    {
        <Invoke>o__SiteContainer0.<>p__Site6 = CallSite<Action<CallSite, object>>.Create(Binder.Invoke(0x100, typeof(Reflector.Reflector), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, nul) }));
    }
    <Invoke>o__SiteContainer0.<>p__Site6.Target(<Invoke>o__SiteContainer0.<>p__Site6, x);
}

So judging by how the C# compiler handles it, there should be one CallSite<T> object per callsite, even if they technically could use the same.

Jan 30, 2010 at 5:38 PM

One CallSite<> per callsite is generally the right choice.

Jan 31, 2010 at 8:47 AM

I've been thinking about what the actual benefits are of using CompileToMethod instead of Compile are? After some initial benchmarking CompileToMethod doesn't seem to execute faster then Compile, it's a lot more hassle to program for (you have to emit all the CallSite<T> object by hand). I know you can emit debug info into a lambda compiled with CompileToMethod compared to Compile, but is that the only benefit? 

Jan 31, 2010 at 2:25 PM

Doesn't Compile also have an overload that takes a DebugInfoGenerator? I think the main use of CompileToMethod is that it lets you build a type that can be written to disk.

Jan 31, 2010 at 3:03 PM

Yes Compile also has an overload that takes a DebugInfoGenerator, but as far as I know it's not usable for some reason. Ah, yes that's another benefit of CompileToMethod. I'm debating which route is the best one to go, all other DLR languages I've looked at have been using CompileToMethod (IPy, IRuby, IScheme, Clojure-clr) so I guess that's the safe road to take. But if everyone is using CompileToMethod, what is the intentions behind having Compile also?

Jan 31, 2010 at 3:54 PM
My understanding is that compiler creates a dynamic method on the heap that can be garbage collected. The assembly loaded from CompileToMethod will stay in memory until the process is restarted

From: [email removed]
Sent: Sunday, January 31, 2010 8:04 AM
To: [email removed]
Subject: Re: Emitting CallSite<T> manually [dlr:82523]

From: fholm

Yes Compile also has an overload that takes a DebugInfoGenerator, but as far as I know it's not usable for some reason. Ah, yes that's another benefit of CompileToMethod. I'm debating which route is the best one to go, all other DLR languages I've looked at have been using CompileToMethod (IPy, IRuby, IScheme, Clojure-clr) so I guess that's the safe road to take. But if everyone is using CompileToMethod, what is the intentions behind having Compile also?

Jan 31, 2010 at 4:11 PM

dotneteer: Yes, that is my understanding as well, but what makes me wonder then is why all DLR languages sources that I've read (IPy, IRuby, IScheme, Clojure-clr) they all use CompileToMethod when Compile seems to offer a lot more flexibility? (You can't use DebugInfo, or cache it on disk - but those don't seem like major drawbacks for a scripting/dynamic language).

It would be really interesting to get one of the IPy dev's insight into this - what the reasons are behind the use if CompileToMethod (I'm sure there are plenty and I'm just to thick to grasp them, but from where I'm standing right now I can't see any benefit).

Jan 31, 2010 at 5:15 PM
One of the things about Python is that is caches the compiled code to
disk so that the next time it is run it is faster. Both IP and CPython
do this. There is logic in the interpreter to load the cached,
compiled copy if the modified date on the script source is older than
the modified date on the cached copy. This allows you to run the
script several times and after the first time, it is fast. This might
be why they are using CompileToMethod, so they can cache the code off
to disk for speed gain on the next runs.

slide

On Sun, Jan 31, 2010 at 10:11 AM, fholm <notifications@codeplex.com> wrote:
> From: fholm
>
> dotneteer: Yes, that is my understanding as well, but what makes me wonder
> then is why all DLR languages sources that I've read (IPy, IRuby, IScheme,
> Clojure-clr) they all use CompileToMethod when Compile seems to offer a lot
> more flexibility? (You can't use DebugInfo, or cache it on disk - but those
> don't seem like major drawbacks for a scripting/dynamic language).
>
> It would be really interesting to get one of the IPy dev's insight into this
> - what the reasons are behind the use if CompileToMethod (I'm sure there are
> plenty and I'm just to thick to grasp them, but from where I'm standing
> right now I can't see any benefit).
>
> Read the full discussion online.
>
> To add a post to this discussion, reply to this email
> ([email removed])
>
> To start a new discussion for this project, email
> [email removed]
>
> You are receiving this email because you subscribed to this discussion on
> CodePlex. You can unsubscribe or change your settings on codePlex.com.
>
> Please note: Images and attachments will be removed from emails. Any posts
> to this discussion will also be available online at codeplex.com



--
slide-o-blog
http://slide-o-blog.blogspot.com/
Jan 31, 2010 at 5:23 PM

slide_o_mix: Yes, that's also a very good point.

I've been trying to find the DLLs that IPy produces to look inside them, but I can't seem to find where they are cached?

Jan 31, 2010 at 5:48 PM
This is the main routine that produces a delegate for a method or top-level code in IronRuby (RubyScriptCode.cs):
      internal static Delegate/*!*/ CompileLambda(LambdaExpression/*!*/ lambda, bool debugMode, bool noAdaptiveCompilation, 
            int compilationThreshold) {

            if (debugMode) {
                // try to use PDBs and fallback to CustomGenerator if not allowed to:
                if (_HasPdbPermissions) {
                    try {
                        return CompilerHelpers.CompileToMethod(lambda, DebugInfoGenerator.CreatePdbGenerator(), true);
                    } catch (SecurityException) {
                        // do not attempt next time in this app-domain:
                        _HasPdbPermissions = false;
                    }
                }
                return CompilerHelpers.CompileToMethod(lambda, new CustomGenerator(), false);
            } else if (noAdaptiveCompilation) {
                Delegate result = lambda.Compile();
                // DLR closures should not be used:
                Debug.Assert(!(result.Target is Closure) || ((Closure)result.Target).Locals == null);
                return result;
            } else {
                return lambda.LightCompile(compilationThreshold);
            }
        }
As you can see we only use CompileToMethod when we need to emit debug information. If we are in partial trust PDBs might not be available and thus we fall back to using our custom debug info generator. This doesn't make the code debuggable in Visual Studio but at least we can associate debugging information with generated MethodInfos and use it when displaying line numbers in stack traces.
If we are not in debug mode (-D command line option) we use adaptive compilation (lambda.LightCompile) unless turned off. The adaptive compiler interprets the code. If some part of it (a loop or a method) gets executed more than compilationThreshold-times it runs the compiler on a background thread for it. When the compilation is done it stops interpreting the part and switches to the compiled code.
Currently, we don't save code to disk in IronRuby. IronPython does so.
Coordinator
Jan 31, 2010 at 7:14 PM

We don’t automatically generate cached DLLs – you need to call clr.CompileModules (or use the pyc script that ships w/ IronPython which does this) to do this.  You can also run with the –X:SaveAssemblies option and we’ll generate the code into a DLL for inspection.  Otherwise we’re doing the same basic thing as IronRuby.

From: fholm [mailto:notifications@codeplex.com]
Sent: Sunday, January 31, 2010 10:24 AM
To: Dino Viehland
Subject: Re: Emitting CallSite<T> manually [dlr:82523]

From: fholm

slide_o_mix: Yes, that's also a very good point.

I've been trying to find the DLLs that IPy produces to look inside them, but I can't seem to find where they are cached?

Read the full discussion online.

To add a post to this discussion, reply to this email (dlr@discussions.codeplex.com)

To start a new discussion for this project, email dlr@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com

Jan 31, 2010 at 8:00 PM
It appears to me that both IronPython and IronRuby can compile both with Compile() and CompileToMethod(). If the GenerateDebugInfo flag is on, it is definitely using CompileToMethd in order to generate the debug symbol. I do see the user cases for Compile as well. If you are running REPL. you probably would not want to generate a new assembly on every new statement. If you run Eval function in a loop, you prabably want to use Compile for your Eval function. Like what you said, CompileToMethod is more difficult because one has to create callsite<T> manually. I also found that it will not compile ConstantExpression unless it is a trivial expression. For any none trivial object, it looks like that I have to construct the expression tree to construct the object.

On Sun, Jan 31, 2010 at 9:11 AM, fholm <notifications@codeplex.com> wrote:

From: fholm

dotneteer: Yes, that is my understanding as well, but what makes me wonder then is why all DLR languages sources that I've read (IPy, IRuby, IScheme, Clojure-clr) they all use CompileToMethod when Compile seems to offer a lot more flexibility? (You can't use DebugInfo, or cache it on disk - but those don't seem like major drawbacks for a scripting/dynamic language).

It would be really interesting to get one of the IPy dev's insight into this - what the reasons are behind the use if CompileToMethod (I'm sure there are plenty and I'm just to thick to grasp them, but from where I'm standing right now I can't see any benefit).

Read the full discussion online.

To add a post to this discussion, reply to this email (dlr@discussions.codeplex.com)

To start a new discussion for this project, email dlr@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com