I've been working on a type system...any suggestions?

May 22, 2009 at 5:05 PM

Hi all-

I've been working on a type system for a language I'm working on, built around delegates(generated from LambdaExpressions):

The general idea is that I generate an instance method that marshalls all it's arguments(plus itself) into an object array, then calls delegateFromLambdaE.DynamicInvoke(), essentially like so:

class generatedClass:SuperType

{

public static System.Delegate <methodName>Delegate;

public object <methodName>(arg1,arg2){

return <methodName>Delegate.DynamicInvoke( [this,arg1,arg2] )

}

}

 

I've got some simple test cases working with manually created delegates, but I've noticed that it's much, much, much slower than direct invocation.  Do any of you have a suggestion to improve speed?  Any ideas for an improvement?  Another idea I had involved emitting LambdaExpressions into static methods,then calling those from the instance.  How does that sound?

 

Any suggestions at all would be appreciated, posted below is the toy code I've been playing with(F#):

open System

open System.Reflection

open System.Reflection.Emit

open Microsoft.Scripting

open Microsoft.Linq.Expressions

open Microsoft.Scripting

type ConstructorInformation=

    {Types:System.Type array}

 

type MethodInformation=

    {ParamTypes:System.Type array;

     Name:string;

     Impl:System.Delegate;

     mutable Field:FieldBuilder option;}

 

let fieldName mi =mi.Name+":"+   String.concat "|" (seq{ for p in mi.ParamTypes -> p.ToString() })

 

let rec addConstructors (t:TypeBuilder) (baseType:System.Type) constructorInfos =

    match constructorInfos with

        |ci::rest ->

            let cb = t.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard,ci.Types)

            let ilGen = cb.GetILGenerator()

 

            ilGen.Emit(OpCodes.Ldarg_0)

            Array.iteri (fun (index:int) _-> ilGen.Emit(OpCodes.Ldarg, index+1)) ci.Types

            ilGen.Emit( OpCodes.Call, baseType.GetConstructor(ci.Types) )

            addConstructors t baseType rest

        |[] -> ()

 

let rec addMethods (tb:TypeBuilder) baseType methodInfos =

    match methodInfos with

    |mi::rest ->

        let fb = tb.DefineField(fieldName mi, typeof<Delegate>, (FieldAttributes.Public ||| FieldAttributes.Static));

        mi.Field <- Some(fb)

        let mb = tb.DefineMethod(mi.Name, MethodAttributes.Public, typeof<obj>, mi.ParamTypes)

        let ilGen = mb.GetILGenerator()

        let arrayLocal = ilGen.DeclareLocal((typeof<obj[]>))

        ilGen.Emit(OpCodes.Ldarg_0)

        ilGen.Emit(OpCodes.Ldfld, fb)

        ilGen.Emit(OpCodes.Ldc_I4, mi.ParamTypes.Length+1)

        ilGen.Emit(OpCodes.Newarr, typeof<obj>)

        ilGen.Emit(OpCodes.Stloc, arrayLocal)

        ilGen.Emit(OpCodes.Ldloc, arrayLocal)

 

        //everybody always gets themselves

        ilGen.Emit(OpCodes.Ldc_I4, 0)

        ilGen.Emit(OpCodes.Ldarg, 0)

        //ilGen.Emit(OpCodes.Castclass, typeof<obj>)

        ilGen.Emit(OpCodes.Stelem_Ref)

        ilGen.Emit(OpCodes.Ldloc, arrayLocal)

        Array.iteri (fun index (pt:System.Type) ->  ilGen.Emit(OpCodes.Ldc_I4, index+1)

                                                    ilGen.Emit(OpCodes.Ldarg, index+1)

                                                    if pt.IsValueType then

                                                        ilGen.Emit(OpCodes.Box, pt)

                                                    ilGen.Emit(OpCodes.Stelem_Ref)

                                                    ilGen.Emit(OpCodes.Ldloc, arrayLocal)) mi.ParamTypes

        ilGen.EmitCall(OpCodes.Callvirt, (mi.Impl.GetType()).GetMethod("DynamicInvoke", [|(typeof<obj[]>)|]), mi.ParamTypes)

        ilGen.Emit(OpCodes.Ret)

        addMethods tb baseType rest

    |[] -> ()

 

type DefineTypeDelegateType = delegate of System.Type* ConstructorInformation list * MethodInformation list -> System.Type

let defineType (baseType:System.Type) constructorInfos methodInfos= 

    let ab =  AppDomain.CurrentDomain.DefineDynamicAssembly( AssemblyName("test"), AssemblyBuilderAccess.Run)

    let mb = ab.DefineDynamicModule("test")

    let typeBuilder = mb.DefineType("testType", TypeAttributes.Public, baseType)

 

    addConstructors typeBuilder baseType constructorInfos

    addMethods typeBuilder baseType methodInfos

    let t=typeBuilder.CreateType()

    List.iter (fun mi->

                    let name = fieldName mi

                    printfn "field Name: %s" name

                    (t.GetField(fieldName mi)).SetValue(null, mi.Impl)) methodInfos

    t

let defineTypeDelegate = new DefineTypeDelegateType(defineType)

type Delegate1 = delegate of obj * obj -> obj

let echo (self:obj) (y:#obj)= self

let del1 : Delegate1 = new Delegate1(echo)

 

type Delegate2 = delegate of obj* obj * obj -> obj

let echoFirst (self:obj) (x:obj) (y:obj)=((x:?>int)+(y:?>int):>obj)

let echoFirstDelegate:Delegate2 = new Delegate2(echoFirst)

echoFirstDelegate.DynamicInvoke( [| new obj();(1:>obj);(2:>obj)|])

echoFirstDelegate.Invoke(new obj(),1,2)

//let mis:MethodInformation list=[{Impl=del1; Name="Echo"; ParamTypes=[|(typeof<obj>)|];Field=None}]

//let cis:ConstructorInformation list=[]

//let t= defineType (typeof<obj>) cis mis

//let cinfo = t.GetConstructor([||])

//let instance =cinfo.Invoke([||])

//instance.GetType()

//(t.GetField("Echo_field")).SetValue(instance, del1)

//let fieldDelegate = (t.GetField("Echo_field")).GetValue(instance) :?> Delegate

//(t.GetMethod("Echo")).Invoke(instance, [| (1:>obj)|])

 

//del1.DynamicInvoke( [|(1:>obj)|])

 

let mis:MethodInformation list=[{Impl=del1; Name="Echo"; ParamTypes=[|(typeof<obj>)|];Field=None};

                                {Impl=echoFirstDelegate; Name="EchoFirst"; ParamTypes=[| (typeof<int>);(typeof<int>)|]; Field=None}]

let cis:ConstructorInformation list=[]

let t= defineType (typeof<obj>) cis mis

let cinfo = t.GetConstructor([||])

let instance =cinfo.Invoke([||])

instance.GetType()

 

defineType.GetType().GetMethods()

 

 

for i in seq{1..1000} do

    (t.GetMethod("Echo")).Invoke(instance, [| (1:>obj)|])

    ((t.GetMethod("EchoFirst")).Invoke(instance, [| (1:>obj);(2:>obj)|]))

    ((t.GetMethod("EchoFirst")).Invoke(instance, [| (1:>obj);(2:>obj)|]))


May 22, 2009 at 5:06 PM

OOF

May 22, 2009 at 5:28 PM

Sorry about the formatting..when I posted it looks like it went a bit weird.  But the indentation still looks correct..

FYI, the code of concern(well...most concern) is in addMethods.

May 22, 2009 at 10:52 PM

Delegate.DynamicInvoke is really slow. It would be faster if you can make the delegate be strongly typed, in the example “Func<object, object, object>” would be ideal, and then call “Invoke” instead of “DynamicInvoke”. Would that work?

- John

May 22, 2009 at 10:58 PM

Another option, possibly even faster, would be to use CompileToMethod to compile the ET into a MethodBuilder directly. It only works for static methods, but then you could call the static method from the instance method. CompileToMethod has certain limitations around compiling constants though, which may or may not be an issue depending on what you’re trying to do.

May 22, 2009 at 11:27 PM
I was thinking of doing this at some point.  I'm aware that CompileToMethod was an option(and one I am willing to try), but only somewhat aware of it's limitations(my understanding pretty much ends at being aware of this).  What else do I need to know?

Also, if you know, how do IronPython and IronRuby do it?

thanks for the tips..
Mike
May 24, 2009 at 1:42 AM
I've only got one of my two test cases working right now,  but the CompileToMethod option is hella fast on the one that works :-)
May 26, 2009 at 7:58 PM

> I've only got one of my two test cases working right now, but the CompileToMethod option is hella fast on the one that works :-)

Nice J

> I'm aware that CompileToMethod was an option(and one I am willing to try), but only somewhat aware of it's limitations(my understanding pretty much ends at being aware of this). What else do I need to know?

The main limitation is that some nodes like ConstantExpression can’t be compiled into a MethodBuilder unless the constant is something simple that IL supports: things like ints and strings, etc, but also public types/methodinfos (because of ldtoken). DynamicExpression is not supported for this reason, because the CallSiteBinder is a constant that we don’t know how to emit. The other problem you might run in to is that you often can only refer to types that are already completed.

- John

May 26, 2009 at 8:56 PM
Thanks so much for the tips, John.

This sounds like a tough nut.  Would compiling constants and the CallSiteBinder into delegate calls(so that a call to the delegate returns the real constant, or in the CallSiteBinder,the binder we want) work?



May 27, 2009 at 1:52 AM

> This sounds like a tough nut. Would compiling constants and the CallSiteBinder into delegate calls(so that a call to the delegate returns the real constant, or in the CallSiteBinder,the binder we want) work?

If you store the delegate as a constant, it still won’t work because the delegate itself will be a constant…

If you can pass the constants in as arguments, that works. Another idea is to store the constants in static fields, and then initialize those fields somehow. Or you can change the constant objects into expressions that reconstruct the value.

May 27, 2009 at 7:05 AM
Edited May 27, 2009 at 8:25 AM

ah.  So, as per the sites-binders-dynobj-interop document:


this gets bound to a static field on the class, initialized after type creation:

        

TypeWereBuilding.StaticFieldWithCallsite=CallSite<Func<CallSite,object[],object>>

        .Create( 

                               SomeDelegateTypeThatTakesArrayAndReturnsObj,

                               SomeBinder

        ); 

and that static class should be called like so:

object result = StaticFieldWithCallsite.Target(StaticFieldWithCallsite, []);


Cool.


Jun 26, 2009 at 2:15 PM
I've been away from the dlr for a little while(since shortly after this post), travelling Europe.

What is the expression tree needed to assign to a static field again?  I'm having a bit of trouble remembering...

thanks
Mike Kohout

On Wed, May 27, 2009 at 1:05 AM, Michael Kohout <mwkohout@gmail.com> wrote:
ah.  So, from the sites-binders-dynobj-interop document:

this gets bound to a static field on the class, initialized after type creation:

        

TypeWereBuilding.StaticFieldWithCallsite=CallSite<Func<CallSite,object[],object>>

        .Create( 

                               SomeDelegateTypeThatTakesArraysAndReturnsObj,

                               SomeBinder

        ); 

and that static class should be called like so:

object result = StaticFieldWithCallsite.Target(StaticFieldWithCallsite, [arg-array]);


Cool.

On Tue, May 26, 2009 at 7:52 PM, jmesserly <notifications@codeplex.com> wrote:

From: jmesserly

> This sounds like a tough nut. Would compiling constants and the CallSiteBinder into delegate calls(so that a call to the delegate returns the real constant, or in the CallSiteBinder,the binder we want) work?

If you store the delegate as a constant, it still won’t work because the delegate itself will be a constant…

If you can pass the constants in as arguments, that works. Another idea is to store the constants in static fields, and then initialize those fields somehow. Or you can change the constant objects into expressions that reconstruct the value.

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



Coordinator
Jun 26, 2009 at 4:48 PM

It’s: Expression.Assign(Expression.Field(null, fieldInfo), Expression.Constant(42));

From: mwkohout [mailto:notifications@codeplex.com]
Sent: Friday, June 26, 2009 6:15 AM
To: Dino Viehland
Subject: Re: I've been working on a type system...any suggestions? [dlr:57202]

From: mwkohout

I've been away from the dlr for a little while(since shortly after this post), travelling Europe.

What is the expression tree needed to assign to a static field again? I'm having a bit of trouble remembering...

thanks

Mike Kohout

On Wed, May 27, 2009 at 1:05 AM, Michael Kohout <mwkohout@gmail.com> wrote:

ah. So, from the sites-binders-dynobj-interop document:

this gets bound to a static field on the class, initialized after type creation:

TypeWereBuilding.StaticFieldWithCallsite=CallSite<Func<CallSite,object[],object>>

.Create(

SomeDelegateTypeThatTakesArraysAndReturnsObj,

SomeBinder

);

and that static class should be called like so:

object result = StaticFieldWithCallsite.Target(StaticFieldWithCallsite, [arg-array]);

Cool.

On Tue, May 26, 2009 at 7:52 PM, jmesserly <notifications@codeplex.com> wrote:

From: jmesserly

> This sounds like a tough nut. Would compiling constants and the CallSiteBinder into delegate calls(so that a call to the delegate returns the real constant, or in the CallSiteBinder,the binder we want) work?

If you store the delegate as a constant, it still won’t work because the delegate itself will be a constant…

If you can pass the constants in as arguments, that works. Another idea is to store the constants in static fields, and then initialize those fields somehow. Or you can change the constant objects into expressions that reconstruct the value.

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

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

Jun 30, 2009 at 11:34 AM
Edited Jun 30, 2009 at 11:52 AM
In order to make the rest of my ExpressionGenerators(which in my lang are the things that make DLR Expressions), I've gone through and made all of my binder-based operations use emitted static fields.  It's pretty simple, but for some reason I'm having trouble generating the correct call to Invoke(callsite, obj[]):
here's the code that generates callsites:
let createCallSite binder (constantSetupBlock:System.Collections.Generic.List) (argExpr:Expression list)=
        let callSiteExp = createConstant (CallSite>.Create(binder)) constantSetupBlock
        
        let targetField =(typeof>>).GetField("Target")
        let invokeMethod = (typeof>).GetMethod("Invoke")
        let targetExp = (Expression.Field(callSiteExp, targetField):>Expression)
        let args = Expression.NewArrayInit(typeof,
                                           List.map (fun x-> Expression.Convert(x, typeof):>Expression) 
                                                    (argExpr))  
        Expression.Call(targetExp,
                                invokeMethod,
                                callSiteExp, args ) :>Expression       
The trouble I'm having is in the final statement, where I generate a call to the callsite.  To the Invoke(callsite,obj[]) method, I pass in the callsite itself and an obj[]..
The trouble is that the complete args array is being passed as the target parameter to my binder:
type public LookupScopeBinder()=
    inherit DynamicMetaObjectBinder()
    override b.Bind( target, args)=
        let argsL = List.of_array args
        let v = target.Value :?> scopes  //ERROR: target.Value==[scopes, 'varname'] rather than just scopes
What type of magic am I missing?  I've tried variations without any sucess(they throw exceptions because the # of args is wrong to Invoke ).  FYI, I'm using the head version of the DLR from svn.
thanks for any ideas,
Mike
Coordinator
Jun 30, 2009 at 5:14 PM

Is the end result here that you want to have the array expanded and passed as individual arguments?  There’s no intrinsic support for that in the DLR.  Python exposes this via *args and whenever we call a foreign function we will embed a nested dynamic site which will expand the *args and call using the normal DLR protocol.  Internally we use a separate calling protocol which includes this in the call signature so we can expand it in-line during the function call.  But otherwise it’s quite fine and nothing special about having an argument strongly typed to object[] – it just means that the user passed an object array and you don’t need to do type checks to figure out what you can do with it.

From: mwkohout [mailto:notifications@codeplex.com]
Sent: Tuesday, June 30, 2009 3:35 AM
To: Dino Viehland
Subject: Re: I've been working on a type system...any suggestions? [dlr:57202]

From: mwkohout

In order to make the rest of my ExpressionGenerators(which in my lang are the things that make DLR Expressions), I've gone through and made all of my binder-based operations use emitted static fields. It's pretty simple, but for some reason I'm having trouble generating the correct call to Invoke(callsite, obj[]):

here's the code that generates callsites:

let createCallSite binder (constantSetupBlock:System.Collections.Generic.List<Expression>) (argExpr:Expression list)=

let callSiteExp = createConstant (CallSite<System.Func<CallSite, obj[], obj>>.Create(binder)) constantSetupBlock

let targetField =(typeof<CallSite<System.Func<CallSite,obj[],obj>>>).GetField("Target")

let invokeMethod = (typeof<System.Func<CallSite,obj[], obj>>).GetMethod("Invoke")

let targetExp = (Expression.Field(callSiteExp, targetField):>Expression)

let args = Expression.NewArrayInit(typeof<obj>,

List.map (fun x-> Expression.Convert(x, typeof<obj>):>Expression)

(argExpr))

Expression.Call(targetExp,

invokeMethod,

callSiteExp, args ) :>Expression

The trouble I'm having is in the final statement, where I generate a call to the callsite. To the Invoke(callsite,obj[]) method, I pass in the callsite itself and an obj[]..

The trouble is that the complete args array is being passed as the target parameter to my binder:

type public LookupScopeBinder()=

inherit DynamicMetaObjectBinder()

override b.Bind( target, args)=

let argsL = List.of_array args

let v = target.Value :?> scopes //ERROR: target.Value==[scopes, 'varname'] rather than just scopes

What type of magic am I missing? I've tried variations without any sucess(they throw exceptions because the # of args is wrong to Invoke ). FYI, I'm using the head version of the DLR from svn.

thanks for any ideas,

Mike

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

Jun 30, 2009 at 6:06 PM
Thanks for your reply, Dino.

I found my problem, I think-  the call site I was generating was incorrectly typed.  I originally created one of this type:  
CallSite<System.Func<CallSite, obj[], obj>>

when I should have been creating this:
CallSite<System.Func<CallSite, obj, obj[], obj>>

or I think-that was my first test case with this impl.  I still have to construct a few more to confirm my findings..

again, thanks for your reply.
Mike
Jun 30, 2009 at 6:33 PM
Nope, it's still doing it.  For whatever reason it's bunching up all the args into the first element of the arg array that's passed into the binder.

For you guys, is this where Expression.GetDelegateType and Expression.GetFuncType come into play?  You mention IronPython-where can I find the code you're talking about?


2009/6/30 mwkohout <notifications@codeplex.com>

From: mwkohout

Thanks for your reply, Dino.

I found my problem, I think-  the call site I was generating was incorrectly typed.  I originally created one of this type:  
CallSite<System.Func<CallSite, obj[], obj>>

when I should have been creating this:
CallSite<System.Func<CallSite, obj, obj[], obj>>

or I think-that was my first test case with this impl.  I still have to construct a few more to confirm my findings..

again, thanks for your reply.
Mike

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


Jun 30, 2009 at 7:06 PM

> here's the code that generates callsites:

let createCallSite binder (constantSetupBlock:System.Collections.Generic.List<Expression>) (argExpr:Expression list)=

let callSiteExp = createConstant (CallSite<System.Func<CallSite, obj[], obj>>.Create(binder)) constantSetupBlock

let targetField =(typeof<CallSite<System.Func<CallSite,obj[],obj>>>).GetField("Target")

let invokeMethod = (typeof<System.Func<CallSite,obj[], obj>>).GetMethod("Invoke")

let targetExp = (Expression.Field(callSiteExp, targetField):>Expression)

let args = Expression.NewArrayInit(typeof<obj>,

List.map (fun x-> Expression.Convert(x, typeof<obj>):>Expression)

(argExpr))

Expression.Call(targetExp,

invokeMethod,

callSiteExp, args ) :>Expression

> Nope, it's still doing it. For whatever reason it's bunching up all the args into the first element of the arg array that's passed into the binder.

What’s argExpr.Type? If it’s already an array, you shouldn’t need the NewArrayInit. I would take a look at the args array and the final MethodCallExpression in the debugger and make sure the arguments look like what you want. They’ll be passed verbatim to the callsite at runtime.

Also the signature of the callsite does not need to match DMOB.Bind(target, args). It will create the “args” parameter on its own, by packaging up all of the individual arguments to callsite.Target.Invoke. So if you’re passing just one argument (that happens to be an array), you’ll see that the “args” parameter is an array with one element.

- John

Jul 1, 2009 at 2:09 AM
Hi John-thanks for your suggestion. About the callsite type-I changed it back to CallSite<System.Func<CallSite, obj[],obj>>

To answer your inquery, argExpr is an Ff# list of Expressions.  For the 2 items in that list, the Type property is System.Object.  

Usurping a phrase from a local Minneapolis musical legend, Color me confused.

On Tue, Jun 30, 2009 at 1:07 PM, jmesserly <notifications@codeplex.com> wrote:

From: jmesserly

> here's the code that generates callsites:

let createCallSite binder (constantSetupBlock:System.Collections.Generic.List<Expression>) (argExpr:Expression list)=

let callSiteExp = createConstant (CallSite<System.Func<CallSite, obj[], obj>>.Create(binder)) constantSetupBlock

let targetField =(typeof<CallSite<System.Func<CallSite,obj[],obj>>>).GetField("Target")

let invokeMethod = (typeof<System.Func<CallSite,obj[], obj>>).GetMethod("Invoke")

let targetExp = (Expression.Field(callSiteExp, targetField):>Expression)

let args = Expression.NewArrayInit(typeof<obj>,

List.map (fun x-> Expression.Convert(x, typeof<obj>):>Expression)

(argExpr))

Expression.Call(targetExp,

invokeMethod,

callSiteExp, args ) :>Expression

> Nope, it's still doing it. For whatever reason it's bunching up all the args into the first element of the arg array that's passed into the binder.

What’s argExpr.Type? If it’s already an array, you shouldn’t need the NewArrayInit. I would take a look at the args array and the final MethodCallExpression in the debugger and make sure the arguments look like what you want. They’ll be passed verbatim to the callsite at runtime.

Also the signature of the callsite does not need to match DMOB.Bind(target, args). It will create the “args” parameter on its own, by packaging up all of the individual arguments to callsite.Target.Invoke. So if you’re passing just one argument (that happens to be an array), you’ll see that the “args” parameter is an array with one element.

- John

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


Jul 1, 2009 at 2:20 AM

> Hi John-thanks for your suggestion. About the callsite type-I changed it back to CallSite<System.Func<CallSite, obj[],obj>>

> To answer your inquery, argExpr is an Ff# list of Expressions. For the 2 items in that list, the Type property is System.Object.

Try making the CallSite type: “CallSite<Func<CallSite, object, object, object>>”, and remove the NewArrayInit, just pass the mapped args to the call. Does that work?

Jul 1, 2009 at 7:40 PM

It doesn't work, but it does get me to a different error-one that may be my fault.  

As far as my original problem, are you suggesting that I actually wrap all my callsites with another callsite, where this wrapping callsite will generate the correct "user level" callsite at runtime?  

wrappingCallSite = CallSite<System.Func<CallSite, DynamicMetaObjectBinder, obj[], CallSite>>.Create(wrappingBinder)

wrappingCallSite.Target.Invoke( wrappingCallSite, realBinder, arg[])

all wrappingBinder does is generate a new CallSite with the correct signature  and the realBinder.

thanks,
Mike

I've created a self contained test script for the original problem(but without the new question about the wrapping callsite):

 

#r @"FSharp.PowerPack.Linq.dll"
#r @"FSharp.PowerPack.dll"
#r @"Microsoft.Dynamic.dll"
#r @"Microsoft.Scripting.Core.dll"
#r @"Microsoft.Scripting.dll"
#r @"System.Xml.dll"
#r @"System.dll"
#r @"System.Core.dll"
#r @"System.Xml.Linq.dll"



open System
open System.Collections.Generic
open System.Reflection
open System.Text
open Microsoft.Scripting
open Microsoft.Linq.Expressions
open Microsoft.Scripting
open System.Reflection
open System.Reflection.Emit
open Microsoft.Runtime.CompilerServices


///Some basic utils

let staticFieldName= ref 0 |> (fun (counter:int ref) ->
                                (fun ()->
                                    incr counter
                                    "!?StaticFieldForCallSites:" + string(!counter)))

let castArgs (parameters:ParameterInfo list) (args:DynamicMetaObject list)=
    List.map2 (fun (p:ParameterInfo) (a:DynamicMetaObject) -> (Expression.Convert( a.Expression, p.ParameterType ):>Expression)) parameters args

let defer  list = 
    List.exists (fun (a:DynamicMetaObject)-> not (a.HasValue)) list


///more complex utils
let makeMetaObject (exp:#Expression) restrict =
    new DynamicMetaObject((if exp.Type.IsValueType then exp:>Expression
                           elif exp.Type = typeof then Expression.Block( exp, Expression.Default(typeof)) :>Expression
                           else Expression.Convert( exp, typeof) :>Expression ),restrict)
let makeTypeBuilder()=
    let ab =  System.AppDomain.CurrentDomain.DefineDynamicAssembly( AssemblyName("test"), AssemblyBuilderAccess.Run)
    let mb = ab.DefineDynamicModule("test")
    mb.DefineType("testType", TypeAttributes.Public, typeof)  

let mergeRestrictions (target:DynamicMetaObject) (args:DynamicMetaObject list) instanceRestrictionOnTarget=
    let restrictions = ref (target.Restrictions.Merge(BindingRestrictions.Combine(List.to_array args)))
    match instanceRestrictionOnTarget with
    |true->
                restrictions := (! restrictions).Merge(
                    BindingRestrictions.GetInstanceRestriction(
                        target.Expression,
                        target.Value
                    ))
    |false->    restrictions := (!restrictions).Merge(
                    BindingRestrictions.GetTypeRestriction(
                        target.Expression,
                        target.LimitType
                    ))
                    
    args|> List.iteri (fun i arg -> 
                        if (arg.HasValue && (arg.Value = null)) then
                            restrictions := (!restrictions).Merge(BindingRestrictions.GetInstanceRestriction(
                                                                    arg.Expression, null))
                        else
                            restrictions := (!restrictions).Merge(BindingRestrictions.GetTypeRestriction(
                                                                    arg.Expression, arg.LimitType)))
    !restrictions

   
//the methods that actually set up the CallSite and the call...these are the methods that may be wrong
let createConstant value (constantSetupBlock:System.Collections.Generic.List) =
        let fieldName = staticFieldName()
        let typeBuilder = makeTypeBuilder()
        typeBuilder.DefineField(fieldName, value.GetType(), FieldAttributes.Public|||FieldAttributes.Static)  |> ignore          
        let t = typeBuilder.CreateType()
        let field = t.GetField(fieldName)
        let fields = t.GetFields()
        let fieldExp = Expression.Field(null, field):>Expression
        constantSetupBlock.Add(Expression.Assign(fieldExp, Expression.Constant(value))) 
        fieldExp
    
let createCallSite binder (constantSetupBlock:System.Collections.Generic.List) (argExpr:Expression list)=
        let callSiteExp = createConstant (CallSite>.Create(binder)) constantSetupBlock
        
        let targetField =(typeof>>).GetField("Target")
        let invokeMethod = (typeof>).GetMethod("Invoke")
        let targetExp = (Expression.Field(callSiteExp, targetField):>Expression)
        let args = (argExpr)|> List.map 
                                (fun x-> 
                                    x.Type
                                    Expression.Convert(x, typeof):>Expression) 
                              
        //Expression.GetFuncType
        targetExp.Type
        callSiteExp.Type
        Expression.Call(targetExp,
                                invokeMethod,
                                (callSiteExp::argExpr) ) :>Expression       

       
//These types represent the various scopes this language with-lexical and dynamic/"host", where host is what he user
//provides as input.
type HostAndDynamicScope = Microsoft.FSharp.Collections.Map

type public Scope(vars,scope) = 
    let Vars:System.Collections.Generic.Dictionary =vars
    let ParentScope:Scope option = scope
    new() = Scope( new System.Collections.Generic.Dictionary(), None)
    new(parent) = Scope(new System.Collections.Generic.Dictionary(),parent) 
    member s.FindParameter(name) =
        match Vars.Item(name),ParentScope with
        | (null, Some(p))       -> p.FindParameter(name)
        | (null, None(_))       -> failwith("couldn't find item named "+name)
        | (t, _)                -> t   

    member s.Parent = 
        match ParentScope with
        |Some(par)  ->par
        |None       -> failwith "popped root scope..."
    member s.AddParameter paramName :ParameterExpression =
        let p = Expression.Parameter( typeof, paramName)
        Vars.[paramName] <- p
        p
        
 ///The offending callsitebinder
type public LookupScopeBinder()=
    inherit DynamicMetaObjectBinder()
    override b.Bind( target:DynamicMetaObject, args:DynamicMetaObject array)=
        let argsL = List.of_array args
        let v = target.Value 
        match (defer (target::argsL)),v with
        |true,_ -> 
                    b.Defer(target, args)
        |false,(:? HostAndDynamicScope as s)  ->
                    let methodInfo =  (typeof).GetMethod("get_Item")
                    let castedArgs = castArgs (List.of_array (methodInfo.GetParameters())) [List.hd argsL]
                    makeMetaObject (Expression.Call(
                                        (Expression.Constant(s):>Expression),
                                        methodInfo,
                                        castedArgs))
                                   (mergeRestrictions target argsL true)
        |false,(:? Scope as s)->
                    let key = (args.[0]).Value :?>String
                    let valPE = s.FindParameter key
                    makeMetaObject valPE (mergeRestrictions target argsL false)
        |false,_  -> failwith ("error-first parameter/target passed to LookupScopeBinder isn't a Scope or HostAndDynamicScope obj.  It's value is:\""+(v.ToString()))
let public lookupScopeBinder = LookupScopeBinder()            


///Simple test:
type topDelegate = delegate of unit->obj

let setupBlock = new System.Collections.Generic.List()
let scope = new Scope()
let testParam =(scope.AddParameter "test")
setupBlock.Add( 
    Expression.Assign(testParam, 
                      Expression.Convert(Expression.Constant(1), typeof)))
let simpleCall = createCallSite lookupScopeBinder setupBlock [(Expression.Constant(scope):>Expression);(Expression.Constant("test"):>Expression)]
setupBlock.Add(simpleCall)
let lambdaBody = Expression.Block([testParam],setupBlock)
(Expression.Lambda(lambdaBody,[||])).Compile().Invoke()

 

 

Jul 1, 2009 at 8:01 PM
Crap.  It looks like Codeplex stripped out a bunch of angle brackets.

I've uploaded the example/test to here:  http://codeviewer.org/view/code:8fc

The color formatting sucks but it's all there.
Jul 2, 2009 at 5:06 AM

> I've uploaded the example/test to here: http://codeviewer.org/view/code:8fc

Hmmm. When I run it, it works, but I get a different error. It finds the ParameterExpression in the scope, and then returns that. However, that results in an unbound variable error for “test”. That happens because the call site’s expressions must be self contained—they can’t access variables from the top level scope.

There are various ways you can implement this functionality, though. The easiest is probably to use the Expression.RuntimeVariables node, which will evaluate to a scope-like object that lets you get/set variables. You could then pass that object into your CallSites. IronPython does something sort of like that (passing “CodeContext” as the first argument to its sites).

- John

Jul 3, 2009 at 11:53 AM
Yes, the test works-but it's kind of a one-off.  The call site needed is tuned specifically to this binder.  I wrote this method which makes it generic:

let createCallSite binder (constantSetupBlock:System.Collections.Generic.List<Expression>) (argExpr:Expression list)=
        
        let signature = List.append (typeof<CallSite> :: (argExpr|> List.map (fun a-> a.Type))) [typeof<System.Object>]
        let callSiteSig = Expression.GetFuncType( List.to_array signature)        
        let callSite = CallSite.Create(callSiteSig, binder)
        let callSiteExp = createConstant callSite constantSetupBlock

        let targetField =(callSite.GetType()).GetField("Target")
        let invokeMethod = (callSiteSig).GetMethod("Invoke")
        let targetExp = (Expression.Field(callSiteExp, targetField):>Expression)
                              
        Expression.Call(targetExp,
                                invokeMethod,
                                (callSiteExp::argExpr) ) :>Expression     
       


Expression.RuntimeVariable-I'll give it a shot asap.

Thanks so much for your help with all this.  I owe you and Dino a beer.
Mike

2009/7/1 jmesserly <notifications@codeplex.com>

From: jmesserly

> I've uploaded the example/test to here: http://codeviewer.org/view/code:8fc

Hmmm. When I run it, it works, but I get a different error. It finds the ParameterExpression in the scope, and then returns that. However, that results in an unbound variable error for “test”. That happens because the call site’s expressions must be self contained—they can’t access variables from the top level scope.

There are various ways you can implement this functionality, though. The easiest is probably to use the Expression.RuntimeVariables node, which will evaluate to a scope-like object that lets you get/set variables. You could then pass that object into your CallSites. IronPython does something sort of like that (passing “CodeContext” as the first argument to its sites).

- John

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