Welcome to MSDN Blogs Sign in | Join | Help

Last time I talked about the 3 levels of DLR caches. Level 0 stored directly in a delegate, Level 1 on the dynamic site, and finally Level 2 which spans multiple sites. Language runtime binders (also called Dynamic- or Action- binders) produce rules that then end up in the DLR caches for future retrieval. We also briefly looked at the code which performs the lookup. In the code you are looking at this logic is still on ActionBinder, but as soon we upload the latest sources, the logic will move to the ultimate place - CallSite<T>.

The question we'll look at today is: How do we actually search in the caches? And the quick answer is that we use what we call "Matchmaker" to do that.

To DLR, the rule is just a piece of code which examines the argument values and if the arguments match the rule, it performs operation. If the rule is not applicable to the argument values, the execution falls off the end. In Level 0 cache where the rules are neatly emitted into a delegate this is clearly visible:

public static object _stub_(
    Closure closure, CallSite site, CodeContext context,
    object obj1, object obj2) {

    // Rule 1
   
if ((obj1 is string) && (obj2 is string)) {
        return (((string)obj1) + ((string)obj2));
    }

    // Rule 2
    if (((obj1 != null) && (obj1.GetType() == typeof(double))) &&
        ((obj2 != null) && (obj2.GetType() == typeof(double)))) {

        return (((double)obj1) + ((double)obj2));
    }

    // Fall off the end into cache lookups
    return ((CallSite<DynamicSiteTarget<CodeContext, object, object, object>>)site)
        .Update(site, context, obj1, obj2);

}

If we fall of the end completely, the delegate will call into the cache lookup and update logic. It is the "Update" call above. The trick is that the Update is itself a delegate. For regular dynamic sites, this is always a delegate which packages up the arguments into an array and calls. We have predefined ones in "UpdateDelegates.Generated.cs" and if those are not enough, we use lightweight code generation to build a custom one.

And now to what the matchmaker is. Matchmaker is a dynamic site which has the update logic modified. Instead of calling into the cache lookup, it will only take note of the fact that the rule did not match (and hence didn't perform the operation) and the cache lookup logic can continue searching. You can check out the pre-build matchmaker logic in "Matchmaker.Generated.cs".

The actual cache lookup is easy. We build a matchmaker dynamic site, get the list of applicable rules (from Level 1 or Level 2 caches) and iterate though those. Each step we reset the matchmaker, run the rule and check for result. If the rule didn't match, the "update" delegate on the matchmaker was called, and it set a flag accordingly. If rule performed work, no such thing happened so we know we are done searching.

Lastly, to "run" the rule we simply call the Invoke method on the rule's own delegate. In DLR, each rule has its own delegate. It looks very much like the code above, except it only contains the work part from the one rule. Then it immediately falls to the update path.

Interesting trivia about these delegates - called also monomorphic delegates - is that if DLR detects badly behaved dynamic site it will stop generating the delegates consisting of multiple rules. Reason is that we would spend too much time generating the new delegates for the site that obviously doesn't benefit too much from them. With such sites we fall back to simply running these monomorphic delegates. Each time we call the dynamic site we fall back to the cache lookup and do the work there.

The code in question lives in CallSite<T>.UpdateAndExecute (the new version). The old version which is still the latest available on CodePlex has it in ActionBinder.UpdateSiteAndExecute<T>.

In the earlier posts on dynamic operations I talked about dynamic binders and rules. Then, rule was two things combined - test and target, both represented as expression trees. Test would determine whether arguments of the dynamic operations 'fit the target' and target would then perform the operation.

These rules, I mentioned, were cached inside of DLR for later reuse (so that DLR didn't have to ask the language binder over and over for the same rule). Today we will explore these caches and also changes that happened in the DLR since those posts. I will base this post off of the latest published sources on CodePlex, and in few places I will point out what it is that you have to look forward to in the near future as we publish next version of the sources. I am trying to find the way to publish our sources more frequently.

As the language binder produces the rule for performing a given dynamic operations, DLR will add it to the caches. DLR maintains 3 levels of caches so let's dive in and see what and where they are.

Level 0

When the dynamic operation is happening, DLR calls into the dynamic site which will in turn immediately call a delegate stored on the dynamic site. The delegate is a level 0 cache. if we were to disassemble the delegate, we would see the cache lookup expressed directly as code below.

// Level 0 cache lookup

public static object _stub_(Closure c, CallSite site,
    CodeContext context, object obj1, object obj2) {

    // Trying to match the cache entry for strings

    if ((obj1 is string) && (obj2 is string)) {

        // Cache hit on strings

        string str = ((string)obj1) + ((string)obj2);
        return str;
    }

 

    // Trying to match the cache entry for doubles

    if (((obj1 != null) && (obj1.GetType() == typeof(double))) &&
        ((obj2 != null) && (obj2.GetType() == typeof(double)))) {

        // Cache hit on doubles

        double num = ((double)obj1) + ((double)obj2);
        return num;
    }

 

    // Level 0 cache miss, call for help

    return ((CallSite<DynamicSiteTarget<object, object, object>>)site).Update(
        site, context, obj1, obj2
    );
}

One idea that will apply to all of the cache levels is most visible here ... the cache lookup happens by executing the code, or in other words, by executing the rules in the level 0 cache.

Each dynamic site has its own Level 0 cache and the idea here is that well behaved programs generally only work with small set of types at a given call site and for such programs the level 0 cache will rarely experience a cache miss.

Level 1

However, a level 0 cache miss can happen. And when it does, the delegate cries for help by calling back into DLR. If you study the code above carefully and compare it to some of the older examples, you notice that the "cry for help" code has changed a little. Not to worry, it still serves the same purpose.

In general, the ultimate receiver of the 'cry for help' is a method "UpdateSiteAndExecute<T>" in ActionBinder.cs. It is the place where the Level 1 and Level 2 cache lookup happens. But where is the level 1 cache stored? Each call site has its own level 1 cache. If you look in CallSite.cs and find CallSite<T>, its first field:

    /// <summary>
    /// Dynamic site
    /// </summary>
    public sealed class CallSite<T> : CallSite {
        /// <summary>
        /// RuleSet - keeps history of the dynamic site
        /// </summary>

        private RuleSet<T> _rules;

        ...

    }

 

is the level 1 cache.

While Level 0 stores the rules compiled into an executable code, the Level 1 cache is just a read only (read only is important because it allows us to do cache lookups without holding locks) list of rules that the dynamic site has seen throughout its life. The delegate which is the level 0 cache is formed from (some of) the is the rules from this level 1 cache. Often from all of them, but not necessarily always.

Now we can go back to the cache lookup code in ActionBinder.UpdateSiteAndExecute<T>. The code we care about is starts here:

//
// Look in the site's local cache first, if we have rules, run those.
// If we find match, the site is polymorphic. Update the delegate in that
// case and keep running.
//

IList<Rule<T>> history = siteRules.GetRules();

 

if (history != null) {
    count = history.Count;
    for (index = 0; index < count; index ++) {
        rule = history[index];

        ...

    }
}

In short, we pull out all the rules from the level 1 cache as a list, iterate through them and the code I replaced with "..." is executing one rule at a time. Again, just like with the level 0 cache, the cache lookup happens by executing the rules.

Here also we run compiled code, but of a slightly different kind. This time they are individual delegates formed by the code pertinent to that one rule only. Good thing about those is that once the rule is created, this delegate never changes so we don't have to re-generate it on future Level 1 (and as we'll see, also level 2) cache lookups.

If we get level 1 cache hit, we update the Level 0 cache by including the rule we just found in the delegate and since we just performed the operation (remember, we executed the rule), we have result of the operation at hand so we are done and can go back to whatever the program was doing. The 'interrupt' (cache miss handling) is over.

Level 2

While level 0 and level 1 caches are stored on the dynamic site itself (as a delegate or as a read only list of rules respectively), the level 2 cache is bigger and is stored on the ActionBinder itself. Its purpose is to span many dynamic sites and allow for rule which was created by cache miss on one dynamic site to propagate to a different dynamic site, thus avoiding need to recreate the rule.

The level 2 cache lookup happens right beneath the Level 1 cache lookup. The code starts here:

//
// Look for applicable rules in the caches
//

Type[] argTypes = CompilerHelpers.GetTypes(args);
applicable = _ruleCache.FindApplicableRules<T>(site.Action, argTypes);

//
// Do we have rules? If so, run those
//

if (applicable != null) {
    count = applicable.Length;
    for (index = 0; index < count; index ++) {
        rule = applicable[index];

        ...

    }
}

Now that is awfully similar to the level 1 cache lookup, isn't it? Get list of applicable rules (granted, this time they come from _ruleCache - the Level 2 cache store), but other than that, we also iterate through the rules and ... no surprise ... execute them one by one until we find the match.

When we do find the match, we add the rule to the Level 1 cache and again, since we found the rule by executing it, the work has been done and we can return back to the program. Note that we don't regenerate the Level 0 cache in this case. There is good reason for it, but we'll talk about that in the future post.

Level 2 cache miss

If level 2 cache miss happens, we need to ask the language binder to create a new rule:

//
// No rule matched, we need to create one
//

for (; ; ) {

    rule = CreateNewRule<T>(context, site.Action, args);

    ...

}

Once the binder creates the new rule, we'll try to execute it. If all goes well we store the rule in the level 1 and 2 caches and since we already have the result of the operation (as we just executed the rule), we are done. However, it can happen that the rule we got from the language binder didn't work (due to an asynchronous update to the type system of the dynamic language that has invalidated the rule). If that happens, we try asking the language binder again...

Once we have matching rule, we store the rule in the caches, return the result and the program can continue executing until the next time it experiences a cache miss, and then we are back to square one (well, level 1 cache anyway).

For next time ...

In the future posts we'll explore:

  • Selection of rules from level 1 cache that form the level 0 cache delegate
  • Matchmakers (a mechanism we use for cache lookups)
  • Cache pruning
  • Changes in the Rule API
  • ...

It's been over a week since we released IronPython 2.0 Beta 1 which also contains the Beta 1 version of DLR. The release includes several favorites of mine in the area of expression trees and compilation, mostly invisible to users of IronPython, but of interest to people developing a DLR based languages.

Unfortunately, the side effect of the release is that most of the samples I put out in the previous posts will no longer work. Even though the ideas remain, some things are shifting a little.

The main difference is probably that what we used to call, confusingly, a CodeBlock, is now called LambdaExpression, and the release sources were snapped right before a checkin went through which made LambdaExpression actually an expression. But don't despair, you can get that change as the latest bits on CodePlex already.

Moreover, to create a lambda expression, one must provide a delegate type. Much of the invisible changes I mentioned before were to ensure that the product of compilation of the LambdaExpression - a delegate - will actually match the signature of the LambdaExpression itself (when DLR deals with closures, it adds an extra parameter to lambdas to carry closures around). Now that the signature of the compiled delegate matches the signature of the lambda expression, the factory method for the LambdaExpression requires delegate, or tries to infer one from a small pre-defined set.

Along with this direction, we are really really close making the whole expression tree fully read only and within days we'll be there completely. Why does it make me so excited? Well, DLR, being a runtime, needs to deal with multi-threaded issues and read only structures don't suffer from threading problems. Also, it is a very sane programming model which guarantees that during compilation the producer of the tree hasn't changed his/her mind and, say, added a for loop somewhere, or changed lexical structure of the lambdas.

I think these are the main big items within the DLR itself, and there's much more coming. In the near future I will post some updated samples to show how to work with the new lambda expression object model and I will also start talking more about the 2nd half of the DLR - the dynamic operations, sites, caches and how all that fits together. We scratched the surface in some early posts, but we glossed over some interesting parts and left few things unexplained.

Until then, happy DLR hacking.

Interesting question came to me ... where can one get (latest) sources of DLR?

Basically, right now there are two sources you can get DLR from:

  • RubyForge
  • CodePlex

RubyForge

RubyForge is where the whole IronRuby project lives and as the development progresses, periodically the IronRuby team pushes the latest code to RubyForge. I am not sure how often this happens, or when it happened last, but having poked around the DLR source code which is part of the RubyForge IronRuby project, it may be couple weeks old, which given the series of recent changes, is too old if you just want to play with DLR directly.

CodePlex

CodePlex is where the IronPython project lives. It is also a place where we publish the Alpha releases of IronPython 2.0 which include corresponding alpha releases of DLR. The latest release of IronPython + DLR was Alpha 8.

Apart from the Alpha releases of IronPython + DLR which happen roughly every month, we publish the source code itself more frequently, and you can download it as often as we upload updated version, which is every one to two weeks. The ultimate goal would be per commit, but realistically it happens about once a week.

Recently there was series of big changes happening in the DLR in areas I spent much of my blogs talking about, so if you are following those and playing with the samples, or even creating your own, your best bet is to use the CodePlex sources that we just published this Monday.

Good luck with your DLR projects and feel free to ask questions.

I am still working on answering the 2nd question. In the previous post and the one before that I answered most of it, but there is one more thing to address:

Problem Two

What if the language syntax doesn't provide good guidance for the placement of the dynamic operations. There are cases where syntax just doesn't provide enough information, for example when the dynamic operation needs so happen in a library function. What then? there is no way to create ActionExpression and let DLR do the code generation which will create DynamicSite and jump start the whole magic...

This last detail is quite easily answered, luckily. We can, in our library function, do the same magic that DLR does via code generation, except we'll do it by hand. To stay with the same example (formatting for output), we can build our function "Print" in a following way where we have an instance of dynamic site manually created and the Print function simply invokes it...

// The instance of the dynamic site used by the "Print"
// method to format the object for output

private static DynamicSite<object, string> _formatter =
    DynamicSite<object, string>.Create(
        DoOperationAction.Make(Operators.CodeRepresentation)
    );

// The Print built-in function

public static void Print(CodeContext context, object o) {
    // Invoke the dynamic site
    string formatted = _formatter.Invoke(context, o);  

    // print
    Console.WriteLine(formatted);
}

Same machinery will kick in as in all the cases we explored so far, the only disadvantage here is that the same dynamic site is shared by all calls into the "Print" method instead of just being shared by all calls that happen at the same location in the source code.

DLR uses this trick internally, you can check out the DynamicOperations.cs for many real world examples. And same goes for Python (Converter.cs, Builtin.cs, PythonCalls.cs, PythonSites.Generated.cs and many more).

In the previous post we explored options to express dynamic behavior of "formatting" an object for output. First option was to use relatively well fitting dynamic operation from the DLR core set - CodeRepresentation. There's another option which we'll look at now.

Using Extension Methods

Instead of using CodeRepresentation, we could 'encode' the dynamic operation of "format for output" as a call to an object's method. It is not that far fetched, actually. Python does just that with the __repr__ method.

Suppose we had this "FormatForPrint" method on each object, our ToyBinder would have very easy job ... find it and call it (create tree that will call it that is). Problem is, of course, that not every object has "FormatForPrint".

Luckily, we - the language implementers - are in complete control here, because we can modify the code in ToyBinder to maintain the correct pretense. If the object being passed in has a type we are interested in, we can simply generate appropriate call to the formatting method, similarly to what we did in the previous post, but with a significant difference.

In the last post, all "Format" methods were in the same place, regardless of the type of the object that they format. That's a good way to look at the problem if we only want to implement formatting. Once we add many other behaviors, we'd have to deal with many classes similar to Formatter to contain all such methods. We could refactor our code in a way that all helper methods relevant to a given type are in the same place. One for formatting, another for ... whatever. This way, once the language action binder sees object of a given type, it has only one place to look at to find all these helper methods.

The is very similar to what already exists in C# and VB ... extension methods. For a given type we can declare a static class which is a container for these extension methods and compiler will make them appear on the types they extend. All of a sudden, types have methods they haven't had before, something we are trying to get our ToyBinder to do.

Let's see if this can possibly work ...

First, the encoding of the dynamic operation. The "print" statement in ToyScript could transform into the following dynamic operation:

Ast.Statement(
    Span,
    Ast.Call(
        typeof(Console).GetMethod(
            "WriteLine", new Type[] { typeof(string) }
        ),
        Ast.Action.InvokeMember(
            SymbolTable.StringToId("FormatForPrint"),
            typeof(string),
            InvokeMemberActionFlags.None,
            new CallSignature(0),
            _expression.Generate(tg)
        )
    )
);

To print means, invoke "FormatForPrint" method on the argument, and pass the resulting string to Console.WriteLine.

If we run the code with this change, we get an exception: "There is no member 'FormatForPrint' on ... object". No surprise. We still have to modify our ToyBinder to do the work. It seems like a lot of coding though, detect the type of the argument, find the corresponding 'extension' and then look for the right method to call.

Luckily for us, DLR understands the notion of extension methods so all we need to do is to actually implement the extension methods and then tell DLR which class (or classes, there may be several of those) contain extension methods for a type. And that should be quite simple.

Extension methods

The implementation of FormatForPrint extension methods for some basic types may look like so:

public static class ObjectExtensions {
    public static string FormatForPrint(object o) {
        return "GENERIC: " + (o != null ? o.ToString() : "(null)");
    }
}

public static class DoubleExtensions {
    public static string FormatForPrint(double d) {
        return "DOUBLE: " + d;
    }
}

public static class DecimalExtensions {
    public static string FormatForPrint(decimal d) {
        return "DECIMAL: " + d;
    }
}

In ToyScript, there are actually already an extension methods for string (StringExtensions) so we can just add one more method:

public static class StringExtensions {
    public static string FormatForPrint(string o) {
        return "STRING: " + o;
    }

    // other extension methods 
    // ...

}

 

The last thing to do is tell DLR about these extensions. There's place for that in the virtual method ActionBinder.GetExtensionTypes(Type). We just need to override it and provide the extensions. This happens in ToyBinder.cs:

protected override IList<Type> GetExtensionTypes(Type t) {
    Type ext;
    if (t == typeof(string)) {
        ext = typeof(StringExtensions);
    } else if (t == typeof(double)) {
        ext = typeof(DoubleExtensions);
    } else if (t == typeof(decimal)) {
        ext = typeof(DecimalExtensions);
    } else {
        ext = typeof(ObjectExtensions);
    }

    return new Type[] { ext };
}

 

 

This is just a trivial implementation, the ultimate version would use some kind of a cache which would get populated as our binder loads assemblies with extensions, based on custom attributes. That's what Python does.

Now what will happen when we run?

  1. ToyScript defines that to print an object, we must dynamically invoke "FormatForPrint" method on it and then send the resulting string to Console.WriteLine.
  2. At runtime, DLR will try to figure out for each object what it means to perform the dynamic operation. Currently the "InvokeMember" means just find a member and then call it.
  3. Via the DLR extension mechanism we extended the basic types with extension methods that DLR will now see and all objects now have "FormatForPrintPrint" methods on them
  4. DLR already knows how to call a .NET method so our extension method will be called like any other .NET method would.

And the best part is that it does work:

image

Summary

The extension methods are pretty powerful mechanism that DLR has to extend existing types with additional functionality and it can certainly be used for the problem at hand (and many others).

The reason I said that there are several good ways to implement what Ales asked but no perfect way (yet) is that ultimately, the perfect solution could be if language could simply produce a custom dynamic operation for the operations that don't seem to fit too well into the pre-defined (and currently fixed) set. Beyond that, we can see that the two alternatives we explored both produce working results.

There is one last issue to solve ... so far, the placement of the dynamic operation was determined from source code. There was keyword "print" which we used as the guide as to where to place the dynamic operations. What if there was no keyword "print" (like in Python), what if it was just like any other function (like it is in JScript). What then? Where would be put the dynamic operations and where would the dynamic site live? We'll look at that problem next time.

Getting now to the second question that Ales asked:

" ... As an example let's take Print function .. where I want to print different data types - integers, strings, decimals etc and each type should be printed differently. Instead of doing the same as you did in previous article, having one method and testing if-elseif-elseif..else  for all the types, is it possible to use ActionExpression for calling a method instead of Ast.Call()? I hope ActionExpression could give better performance and caching for this, but I didn't figure out how to do this..."

Good news is that the answer is yes (you can take advantage of dynamic caching even in situations such as this one), but there's a bit of a bad news, which is that while there are few good ways to do it, the perfect way that we envision is not yet implemented, but hopefully will be soon.

Problem One

The first problem is representing the dynamic operation as an Action, and that is where the answer is not quite perfect yet.

Action in DLR represents encoding of the dynamic operation. You can think of it as a "dynamic instruction" of sorts. And as of right now, DLR only recognizes fixed set of Actions. The main kinds are (look into DynamicAction.cs for the enum and the DynamicAction base class):

  • DoOperation
  • ConvertTo
  • GetMember
  • SetMember
  • DeleteMember
  • InvokeMember
  • Call
  • CreateInstance

Each enum value above then has corresponding class which contains the encoding of the given dynamic operation. For DoOperations, the encoding of the operation is as simple as another enum value (from Operators.cs). There are quite a few of those, including unary and binary operators, slicing, indexing (Get/Set -Item) and many more. The general rule here is that the enum is enough to encode the operation. For our problem, there's one that could somewhat fit, but the fit may not be perfect: CodeRepresentation (inspired by Python's __repr__).

The other kinds of actions are represented via their own classes, depending what data is part of the action's encoding. For example, all *Member actions must contain the name of the member in question, but there is no deep need for actually having 3 classes for Get/Set/Delete, anyway, that's area we are still actively working on and it'll get better.

Invoke/Call/Create instance are little more complicated because they also encode information about the call signature, whether the call includes a dictionary contents of which are mapped to callee's parameters etc. Invoke is combination of "GetMember" and Call:

a.b(c,d,e)

but done all at once. Python, for example, cannot use Invoke because in Python the above must be exactly equivalent to:

temp = a.b
temp(c,d,e)

The Python rules dictate that a.b be evaluated before any of the arguments. Invoke combines these two operations for languages whose semantic allows this.

... But that was a little digression ...

One option we have to encode our "format for printing" dynamic operation is via the DoOperation(CodeRepresentation), another would be to encode it as a call to a method which is to be found on the object (or its type) which will do the encoding. For example Call("FormatForPrinting").

Using DoOperation(CodeRepresentation)

Let's actually try to implement this. ToyScript has a keyword print which we can use to try this approach. Currently the print is transformed into DLR tree as:

Ast.Statement(
    Span,
    Ast.Call(
        typeof(ToyHelpers).GetMethod("Print"),
        Ast.CodeContext(),
        Ast.ConvertHelper(
            _expression.Generate(tg),
            typeof(object)
        )
    )
);

In other words, as a call to ToyHelpers.Print. Instead, we could change that to the following:

Ast.Statement(
    Span,
    Ast.Call(
        typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),
        Ast.Action.Operator(Operators.CodeRepresentation,
            typeof(string),
            _expression.Generate(tg)
        )
    )
);

where we take the argument to the print keyword (_expression), perform dynamic operation "CodeRepresentation" on it and simply pass the result to Console.WriteLine(string). Notice that the dynamic operation itself is typed as string which means it will always produce string, allowing for tighter code generation and faster code especially if value types are involved as it can save boxing/unboxing.

Of course, this is the easy part. If we try to run the ToyScript code:

import System

def f(x) {
    print x
}

f(1)
f("Hello")
f(new System.Decimal(10))

we'll get an exception:

image

The reason is that we haven't yet provided instructions to the DLR how to perform the operation. Obviously, the default DLR attempts to do something about what ToyScript requested have failed and the DLR dynamic binder pretty much produced tree which just throws exception:

//
// AST Rule.Test
//

(((.bound $arg0) != .null) && (((.bound $arg0)).(Object.GetType)() == ((Type)Double)))

//
// AST Rule.Target
//

.throw ((RuntimeHelpers.BadArgumentsForOperation)(
    (Operators)CodeRepresentation,
    .new Object[] = {
        (.bound $arg0),
    },
))

We have to go to ToyScript's dynamic binder and give some instructions as to what it means to perform CodeRepresentation. Before we do that, let's actually implement a really simple formatter. A class with bunch of static methods that do the formatting for the types we want to handle explicitly. This may be a trivial first attempt:

public static class Formatter {

    public static string Format(double v) {
        return "DOUBLE: " + v;
    }

    public static string Format(string s) {
        return "STRING: " + s;
    }

    public static string Format(decimal d) {
        return "DECIMAL: " + d;
    }

}

Really simple. Then, in the ToyBinder, we implement the rules for formatting. I put this code into MakeDoRule<T> right after the string addition rule:

if (action.Operation == Operators.CodeRepresentation) {
    Type argumentType = args[0].GetType();

    StandardRule<T> rule = new StandardRule<T>();

    // Create test: arg0 is argumentType
    rule.Test = Ast.TypeIs(
        rule.Parameters[0],
        argumentType
    );

 

    // Create the target:

    // 1. Look for the "Format" method with perfectly matching signature
    MethodInfo mi = typeof(Formatter).GetMethod("Format", new Type[] { argumentType });

    Expression target;

    if (mi != null) {
        // Call the custom formatter
        target = Ast.Call(
            mi,
            Ast.Convert(rule.Parameters[0], argumentType)
        );
    } else {
        // Call the ((object)arg0).ToString()
        target = Ast.Call(
            Ast.Convert(rule.Parameters[0], typeof(object)),
            typeof(object).GetMethod("ToString")
        );
    }

    // Finish the rule and return it
    rule.Target = rule.MakeReturn(this, target);
    return rule;

}

Pretty much what we do here is look into our Formatter class for a perfectly matching overload of "Format". If we find one, good, call that, otherwise fallback to Object.ToString(). Now we can run our code and see output:

image

If we pass something else to our formatting function "f", our binder falls back to Object.ToString. Suppose we add this to our ToyScript script and run again:

f(new System.Collections.ArrayList())

image

And it worked again :) Really simple example, but if we dig into the saved assembly (must run with
-D -X:SaveAssemblies -X:StaticMethods) we'll find the generated code:

image

Note here that I had to play the same trick I played in one of the first posts where if the dynamic site sees a type once and never again, it won't actually generate the more complicated "if" statement. To work around this optimization, I ran the four lines of code twice in a row to get this behavior. In reality, this is really what we don't want programs to do :)

So this approach does work. In the next post we'll explore a second option how to handle the first problem - trying to encode the string formatting as an invocation of a method which each object should have. Of course, we'll hit a problem that not every object has the method we want to invoke. There is no "OurCustomFormat" on Int32 or Double, is there? But as we'll see, DLR has a way to help us with that also.

Then we will need to address the second problem that arises from the question, which we haven't even identified so stay tuned, we'll get to it all.

Until then, happy hacking.

Yesterday a question from one of the readers prompted me to talk a bit more about the DLR trees, a means DLR uses to represent programs. We focused on just couple of nodes, and today won't be much different, except I'll bring a friend in ... LINQ Expression Trees.

DLR Trees

On the quest to discover what the static DLR tree nodes compile into, we ended up doing a bit of work that compilers generally do for us and hand-crafted a DLR tree to represent function performing an integer addition. You can check out the original source code, today I have pretty much the same thing, module few renames:

using Dlr = Microsoft.Scripting.Ast;

...

public delegate int Adder(int a, int b);

...

// Representing a function performing integer addition in DLR
Dlr.Variable var_a = Dlr.Variable.Parameter(SymbolTable.StringToId("a"), typeof(int));
Dlr.Variable var_b = Dlr.Variable.Parameter(SymbolTable.StringToId("b"), typeof(int));

Dlr.CodeBlock dlr_block = Dlr.Ast.CodeBlock(
    "adder",                                    // Function name
    typeof(int),                                // Return type
    Dlr.Ast.Return(                             // Body
        Dlr.Ast.Add(
            Dlr.Ast.Read(var_a),                
            Dlr.Ast.Read(var_b)
        )
    ),
    new Dlr.Variable[] { var_a, var_b },        // Parameters
    new Dlr.Variable[0]                         // Locals (none needed)
);

// Compile into a delegate
Adder
dlr_add = Dlr.TreeCompiler.CompileBlock<Adder>(dlr_block);

// Call the delegate
Console
.WriteLine("DLR: 7 + 11 = {0}", dlr_add(7, 11));

Nothing surprising here, just a recap of something we already know. If we run this code, we get the following output:

image

LINQ Expression Trees

Let's try the same thing with LINQ Expression trees. They have similar object model so the code shouldn't look too different:

using Linq = System.Linq.Expressions;

...

// Representing function performing integer addition in Linq Expression Tree
Linq.ParameterExpression param_a = Linq.Expression.Parameter(typeof(int), "a");
Linq.ParameterExpression param_b = Linq.Expression.Parameter(typeof(int), "b");

Linq.Expression<Adder> linq_lambda_manual = Linq.Expression.Lambda<Adder>(
    Linq.Expression.Add(            // Lambda value
        param_a,
        param_b
    ),
    param_a,                        // params[] of the parameters
    param_b
);

// Compile LINQ lambda into a delegate
Adder linq_add = linq_lambda_manual.Compile();

// And call it
Console
.WriteLine("LINQ: 3 + 4 = {0}", linq_add(3, 4));

Pretty much the same thing. Create parameters, build the lambda, compile and run. When we run now, we get both DLR and LINQ to answer:

image

The Best Part

Well, the best part to this all is that C# compiler has direct support in the language for the LINQ expression trees. Using that we get the ultimate version of the same thing:

// C# Compiler support for LINQ
Linq.Expression<Adder> linq_labda = (a, b) => a + b;
Adder
super_add = linq_labda.Compile();
Console.WriteLine("C# LINQ: 2 + 3 = {0}", super_add(2, 3));

In fact, the code is now so short it is hard to see the actual tree in there, so let me pull it out:

(a, b) => a + b;

The rest is pure compiler magic of C#. Type inference, detection that the lambda is being assigned to Expression<Adder> and therefore the lambda gets represented as tree (quoted), instead of compiled into a delegate directly. Let's run once more with the compiler supported version of LINQ and we get, not surprisingly:

image

Conclusion

The unfortunate part is that there is no such support for the DLR trees. The only compilers that know how to produce them, at the moment, are Python, Ruby, ToyScritpt and few others...

In my time working on DLR I wrote many a tree by hand, and it may be just that it is becoming a second nature to me so the typing issue is secondary one. On the other hand, there is a a lot of beauty in other formats, too. Consider this for example:

image

Next time, I promise to answer the 2nd question Ales posted... can we leverage the DLR caching mechanism in less obvious situations, such as custom formatting support for printing, in general situations where we'd like to avoid switching on types. Until then, happy hacking.

After a month break (a month which I spent deep in the guts of the DLR compiler so I wasn't really slacking off) I am back and notice several questions, and since they are good ones (Diky moc, Alesi!), let me address those questions here.

First question was, why do we have the seemingly dual nodes in the DLR Tree and how are they different? One can express operation either using the 'static' nodes, with which one would construct binary addition node as:

Ast.Add(left, right)

And the other is using the dynamic (action) nodes, in which case dynamic addition would be expressed as:

Ast.Action.Operator(Operators.Add, left, right)

How are those two nodes different, which one do I use when, and what are their trade-offs?

Static Nodes

Let's start with the simple one, the static node. Unfortunately, they are hard to come by in dynamic languages so we'll have to use some tricks here and there to really see what's going on. The goal is to write a function that adds integers. Should be pretty simple. Below is the code which does just that in the language of DLR trees (in addition to also setting up the few tricks).

using Microsoft.Scripting;
using
Microsoft.Scripting.Runtime;
using
Microsoft.Scripting.Generation;
using
Microsoft.Scripting.Ast;

namespace Tree {
    public class Program {

       
// Delegate type for our "Add two integers" function
        public delegate int AddInt(int l, int r);

       
public static void Main() {
            // Setting DLR options to see the emitted code
            ScriptDomainManager.Options.DebugMode = true;
            Snippets.Shared.SaveSnippets = true;

            // Create parameters for the function
            Variable left = Variable.Parameter(SymbolTable.StringToId("left"), typeof(int));
            Variable right = Variable.Parameter(SymbolTable.StringToId("right"), typeof(int));

            // Create function itself
            CodeBlock add = Ast.CodeBlock(
                "Add",                              // Name
                typeof(int),                        // Return Type
                Ast.Return(                         // Body... it is return result of 
                    Ast.Add(                        //  adding
                        Ast.Read(left),             //  left argument
                        Ast.Read(right)             //  and right argument
                    )
                ),
                new Variable[] { left, right },     // Parameters of the function
                new Variable[0]                     // These would be local variables
            );

            // Compile and get delegate back
            AddInt ai = TreeCompiler.CompileBlock<AddInt>(add);

            // Call it
            int r = ai(1, 2);

            // Save the generated code
            Snippets.Shared.Dump();
        }
    }
}

Ok, so what's going on here...

The function we are defining will be adding two integers, and ideally we would compile it into a delegate which we can later call, so AddInt is the delegate of choice, function taking two ints and returning int also. Let's fast forward to the lines which define Variable left and Variable right. They are the parameters to our function. in DLR, parameters and variables differ very little so they are, as of right now, represented simply as Variable. To create a variable/parameter, we provide name (currently as the interned SymbolId) and type, in our case typeof(int).

As for the function itself, we need to give it name ("Add"), return type (int) body, array of parameters (left, right) and local variables (no locals for this one). Let's go back to the body:

Ast.Return(                         // Body... it is return result of 
    Ast.Add(                        //  adding
        Ast.Read(left),             //  left argument
        Ast.Read(right)             //  and right argument
    )
)

This code creates the static "Add" node, an instance of BinaryExpression, give it left and right as children and package it all up in a return for the function. Before we look at the generated code itself, let's poke around in a debugger for a little bit. I'll save the sub-tree into a temp and bring up a watch window:

image 

One thing to notice is that the type of the addExpression is System.Int32, as are the types of the left and right (which we created as such), in other words, the tree is completely statically typed. And now to the tricks I used to get DLR to save the code...

The code that DLR generates goes to various places. In ideal case, it goes into dynamic methods, which are pieces of code which get garbage collected as soon as nobody uses them anymore. Problem is, they can' be saved and later inspected (which makes it really fun to debug cases where one emits unverifiable code ... another reason to use the DLR trees and let us do the code generation :) The debug flag and setting a "Save" flag on snippets (set of assemblies DLR keeps around to generate code - and especially types - into if needed) and later calling Snippets.Shared.Dump() will ensure that after our program exits, we'll get to inspect the generated assembly (called Snippets.dll in this case), so let's drill into it:

image

Looking at the IL view, we see a function with 3 parameters, the 2nd and 3rd are our two ints, left and right (let's ignore the first one for now), and the IL is very simple. Load left, load right, add, return. As for the last two instructions, DLR always adds them so because it doesn't yet do code flow analysis to determine whether all function's branches terminate with return.

So the function itself is pretty tight IL, and that's due to the fact that the input tree was statically typed, using the BinaryExpression node.

As for the first argument ... We'll see it play bigger role in the future posts, but for now let's just say that the DLR compiler may face situations in which it needs to pass data from compilation to runtime. After the compiler is done with the delegate, it will put all such data into the "Closure" and bind it to the delegate so that it is available to the delegate wherever life takes it. I would love to take detour and tell you all about it, but you'll just have to wait for another post as we'd never get to the dynamic nodes ...

The conclusion of this experiment is that using the static nodes will produce statically typed trees (the binary expression was also typed as int) and the generated code is very much static, and as a result, it is also faster.

Where would one use these nodes? Obviously, where compiler knows for sure the types of the operands and the full runtime semantic of the operation at compile time (when generating the trees). There's another place where these nodes are useful, and we'll get to it in a minute.

Dynamic nodes

Dynamic nodes are much easier to bring to life. All we need to do, just like in the previous posts, is write little Python script (or ToyScript script). This is in fact what we discussed few posts back. ToyScript, or Python, when facing a dynamic operation (which are pretty much all of them), will generate the dynamic "Add" node. Unlike the statically typed expression node, the dynamic node's arguments are, in general sense, typed as object. That's why the tree dump of our little toy function:

def f(a,b) {
    return a + b;
}

Looks like this:

.codeblock Object f ()(
    .arg Object a (Parameter,InParameterArray)
    .arg Object b (Parameter,InParameterArray)
) {

    {
        .return .action (Object) Do Add( // DoOperation Add
            (.bound a)
            (.bound b)
        );
    }
}

Where as far as you can see, everything is an object. So really what one expresses when using the dynamic "add" node is: "I really have no idea what exactly is going to happen here, all I know is that the operation is binary addition." And we already discussed that this node will create what we call DynamicSite, and when, at runtime, the operation gets invoked, only then can the language produce the rule as to what to do in a given case.

And here's where the static nodes come into play again. In a way, using the dynamic nodes means postponing certain decisions until runtime. But no matter how hard you try, nobody can add two instances of object. At some point we get the actual values, the language gets to examine them in the binder and will produce a rule. When it does so, the language implementation has the advantage of knowing the types of the values that are, in this precise moment, being operands to a given operation. And to express the actual operation that needs to be performed, the language uses ... the static nodes.

Let me add call to the above function f, taking two integers, and let's have DLR run wild with it and produce dump of all trees that it encounters, by running:

ts.exe -X:ShowASTs -X:ShowRules x.ts

The rule that DLR produced for the addition when presented with 2 doubles (ToyScript doesn't deal with integers, only doubles), is:

//
// AST Rule.Test
//

((((.bound $arg0) != .null) && (((.bound $arg0)).(Object.GetType)() == ((Type)Double))) &&
(((.bound $arg1) != .null) && (((.bound $arg1)).(Object.GetType)() == ((Type)Double))))

//
// AST Rule.Target
//

.return .comma {
    (.bound $retVal) = ((Double)(.bound $arg0) + (Double)(.bound $arg1))
    (Object)(.bound $retVal)
}

The rule says that if we are dealing with two doubles, this is how you add them ... and here it produces code which is using the statically typed BinaryExpression with two children, both of type double. And if we were to look at the generated code in the reflector (save it using -X:SaveAssemblies -X:StaticMethods), we'd find the compiled code to be very similar to the tree dump above:

image

and in IL, the actual addition of doubles is here:

image

So again, the statically compiled addition operator in the form of a very tight IL. Notice that in this case the parameters are both of type object so the code must cast them to double first by un-boxing them.

Summary

It turns out that the dynamic operation nodes postpone certain decisions until runtime. When the actual operands are known, the language decides what needs to be done, expresses it via the static nodes (which compile into very nice IL) and that's the code that runs from then on. To answer the actual question, the static nodes are faster. The dynamic ones are slow the first time around, but from then on they are very fast too. There's some overhead in the tests present in the dynamic site, but it is hardly anything compared to what runtime lookup on each call would do.

The second question Ales asked will have to wait until next time. For now, I am back to coding...

In the last installment we discussed how language can express dynamic behavior using ActionExpression and how DLR treats such cases. In short summary, DLR will turn the ActionExpression into sophisticated cache which keeps updating itself as new runtime conditions occur.

We left off at the point where DLR will cry for help when faced with a new runtime condition that it hasn't seen before and doesn't know how to handle. We even saw the actual code that does the crying (the call to "site1.UpdateBindingAndInvoke"), but beyond that all we know that the response comes in some form and that DLR uses the response to learn - to update the code of the delegate handling the dynamic behavior.

Using a little trick that I revealed at the very end of the post, I showed you the actual code of the delegate and we saw that it is very similar to our original attempt to handle dynamic behavior using runtime helper methods.

It is worth reiterating that the key is in the question that DLR asks: "Tell me how to perform the operation!" It is the format of the question which allows DLR to learn - to cache the answers and only ask for help when it has no appropriate cached recipe for handling runtime behaviors.

Rules

In DLR we call these recipes Rules. When DLR asks the question how to perform a given dynamic behavior, it expects a rule back. It is the rule which in the generated code looks like:

//
// Adding strings
//
if (((obj1 != null) && (obj1.GetType() == typeof(string))) &&
    ((obj2 != null) && (obj2.GetType() == typeof(string)))) {

    return StringOps.Add((string)obj1, (string)obj2);

}

The rule consists of a test and a target. Test is a condition which examines the arguments and target is an operation to be performed if the test succeeds. In the code snippet above, the test is the detection whether obj1 and obj2 are both strings and the target is a return of a result of the call to StringOps.Add.

And to close the loop, the rule is expressed as the DLR Tree. When the DLR asks for help determining the exact operation for a given dynamic behavior at runtime, the response comes in the form of a rule which encapsulates two trees. One for the test, the other for the target.

By passing "-X:ShowRules" command line parameter we can have DLR print a trace of all rules created during the program's execution so let's run small ToyScript code:

def add(a, b) {
    return a + b
}

print add("Hello", "ToyScript")

with "-X:ShowRules" and see that for the addition we get following rule as an output:

//
// AST Rule.Test
//

((.bound $arg0) .is String && (.bound $arg1) .is String)

//
// AST Rule.Target
//

.return .comma {
    (.bound $retVal) = (String.Concat)(
        (String)(.bound $arg0),
        (String)(.bound $arg1),
    )
    (Object)(.bound $retVal)
}
;

If you put the test and target together, you can reconstruct the rule's behavior in the C#-like format:

if (arg0 is string && arg1 is string) {

    return String.Concat((string)arg0, (string)arg1);

}

which is exactly what you would expect for string concatenation. Notice that the generated code for the rule that was created when Python executed string addition is slightly different than the rule created for ToyScript addition. If we look at the rules generated when executing Python code, the rule for string addition will trace as:

//
// AST Rule.Test
//

((((.bound $arg0) != .null) && (((.bound $arg0)).(Object.GetType)() == ((Type)String))) &&
(((.bound $arg1) != .null) && (((.bound $arg1)).(Object.GetType)() == ((Type)String))))


//
// AST Rule.Target
//

.return (StringOps.Add)(
    (String)(.bound $arg0),
    (String)(.bound $arg1),
);

yielding exactly the code we saw at the beginning of this post. The difference that may strike you right away is in the rule's test. When we ran ToyScript. the test came out as: "arg0 is string" and with Python, the corresponding part of the test is more complicated: "arg0 != null && arg0.GetType() == typeof(string)". Why the difference, or better yet, why the latter version?

It turns out that when we experimented with different ways to implement the test we realized that JIT (the .NET just-in-time compiler) has an optimization for the latter case and while it looks longer in the source code and in the IL, it will amount to almost nothing in the final executable code. In the case of strings, value types, and generally all sealed types, the two are exactly identical, however the latter - longer - version will check for exact type identity, but "is" operator will succeed if the left operand is a subclass of the right, so there's a subtle difference here also.

The last question we'll ask today is: "Who makes the rules?"

Binders

When DLR requests a rule to be made for a given action which is being invoked with given arguments, it will first call "UpdateBindingAndInvoke" on the dynamic site. These method are in DynamicSite.Generated.cs. At the end of each method is a call to "context.LanguageContext.Binder.UpdateSiteAndExecute". The binder is what the individual language implements to define semantic for the dynamic behaviors determined at runtime.

Not all answers do come from the language binder itself. The binder can simply decide that it doesn't know what to do with given operation on given arguments and pass the task up to the binder base class (ActionBinder) which implements large set of default behaviors, for example behaviors on 'standard' .NET objects such as what it means to get a member "Count" from an instance of System.Collections.Hashtable. It would be silly to expect each language implementer to provide the rules for all possible situations and that's why DLR provides the large default behaviors.

The rule for string addition that we tried with ToyScript is actually implemented in ToyScript, but simply because the DLR default behaviors don't include string addition (in my opinion it is a bug and we'll fix it, but we haven't got to it yet)

The implementation for the string addition in ToyScript is in ToyBinder.MakeRule<T>. You can see that the dynamic action "DoOperation" is singled out and passed to MakeDoRule<T> where addition is singled out and implemented as a construction of the tree that we saw above.

When the language binder receives the request from the DLR to create the rule, it will look at what action is being performed (operation - add, subtract, ..., get member, call, ...) and then examine the arguments being passed in. With all that knowledge the language binder will construct the resulting tree that captures the semantic of the operation. Sometimes the rule is simple (such as our addition above), sometimes it can get pretty complicated. The DLR will cache the rule and only call back again if new circumstances arise.

In the last post we experimented with expressing a dynamic behavior (addition) using a helper function and came to conclusion that such implementation is definitely feasible (after all, IronPython 1.x uses just that), but it has its drawbacks. Today we'll address the issues and get into the real magic of the Dynamic Language Runtime.

The second way to express dynamic behavior in the DLR Tree is using the ActionExpression. In general terms, ActionExpression represents a dynamic invocation of an "action" with a list of arguments. The addition operation is an action "Add" and it accepts two arguments, as we can see in the tree produced by ToyScript from the script:

def add(a, b) {
    return a + b
}

the DLR Tree for the function body is:

//
// CODE BLOCK: add (1)
//

.codeblock Object add ()(
    .arg Object a (Parameter,InParameterArray)
    .arg Object b (Parameter,InParameterArray)
) {
    {
        .return .action (Object) Do Add( // DoOperation Add
            (.bound a)
            (.bound b)
        );
    }
}

The tree that ToyScript compiler produced essentially says: "There's an addition happening here, but the exact details will be figured out at runtime".

But who figures out the "exact details" and how?

There are two things that the DLR code generation does now when it encounters ActionExpression. First, it will emit an instance of what we call a dynamic site and initialize it with the appropriate action. If we were to write a C# pseudo-code for the generated code, it would be close to:

static DynamicSite a_plus_b_site = new DynamicSite(Add);

In the place of the actual operation the code generator will emit an invocation of the dynamic site:

a_plus_b_site.Invoke(a, b);

And that's pretty much it. There are two details that you would notice if you looked at the generated code in the reflector:

First, the DynamicSite is a generic type. Its generic parameters are the types of the ActionExpression's arguments and the desired result type. In our case they would be all objects, but there are cases when even ToyScript generates ActionExpression with more specific concrete types. For example when adding constants directly:

x = 2 + 3

The ToyScript compiler will create an ActionExpression whose arguments are of type System.Double and as a result the dynamic site will be strongly typed as well, as: DynamicSite<Double, Double, Object>. The result is still object because that is what we desire for the result to be so that we can assign it to the variable x (typed as object). The tree created by ToyScript for the assignment above is:

(.bound x) = .action (Object) Do Add( // DoOperation Add
    2
    3
);

Python generates even tighter trees. For example if the action expression is used as a condition in the conditional expression, Python will produce an ActionExpression returning bool directly so that a cast (and moreover, boxing and un-boxing) can be avoided if possible.

Second omission I made in the above pseudo-code is that when invoking the dynamic site, compiler not only passes the arguments, but also an additional argument - a code context. CodeContext is a class which contains an important thing for the whole system to work - a reference to the executing language.

Now that we know all the details, we should look at the function "add" in the reflector. Unfortunately, at the time of writing this post, the optional command line argument which will force DLR to save generated assemblies to the disk is temporarily out of commission in ToyScript (to be fixed within a day or two). Good we have Python around:

def py_add(a, b):
   
return a + b

will actually compile into an identical DLR Tree to that which ToyScript produced above:

//
// CODE BLOCK: py_add (1)
//

.codeblock Object py_add ()(
    .arg Object a (Parameter,InParameterArray)
    .arg Object b (Parameter,InParameterArray)
) {

    .return .action (Object) Do Add( // DoOperation Add
        (.bound a)
        (.bound b)
    );
}

Now isn't that curious? Two different languages with potentially rather different semantic compiling into the very same tree? It actually makes a lot of sense. The behavior of the addition is determined at runtime so each language can still apply its own semantic.

As for the actual generated code (saved by the DLR into debugSnippets1.dll), here it is:

image

You can clearly see the invocation of the dynamic site (and the additional argument - the code context.

Now we are finally getting to the place where the really interesting stuff happens... It is inside the invocation of the dynamic site. But even that is quite simple:

public Tret Invoke(CodeContext context, T0 arg0, T1 arg1) {
    Validate(context);
   
return _target(this, context, arg0, arg1);
}

After some validation of the incoming context (and the validation is debug mode only), the site blindly calls the _target delegate with whatever arguments it itself received.

While the DynamicSite.Invoke doesn't do much of anything, it is the delegate "_target" which is the key to things. The benefit of it being a delegate is that it can be dynamically updated (replaced with 'better version').

When the dynamic site gets initialized at the beginning of the program's execution, its delegate does only one thing: it cries for help. No matter what arguments are coming into the dynamic site, the delegate cannot know what to do with them. It is the language who knows what to do. When the dynamic site cries for help, it hopes to find a listening ear with the language implementation, which conveniently travels with the CodeContext $context parameter.

A key thing to this mechanism is the format of the DLR's cry. Instead of asking: "Hey language, please perform this operation on these arguments!", it will choose rather different wording:

"Hey, language, HOW can I perform this operation on these arguments?"

And it is the choice of words on the part of the Dynamic Language Runtime that makes the world of a difference. By asking the "how" question instead of demanding that the language actually does the work, DLR can learn. So long as whatever DLR learnt so far still applies, there is no need to cry for help again. Only when DLR is faced with objects with which it doesn't know what to do, the cry for help comes again and the language provides more answers.

This learning is expressed in the "_target" delegate inside the dynamic site. When the DLR learns something new it will update the delegate and and continue running. It will be therefore interesting to look at the evolution of the delegate contents. We'll use yet another nifty command line argument
(-X:StaticMethods) to look at those, and we have to do it in Python again, while ToyScript is temporarily indisposed:

Let's add a call to our py_add function, passing couple of integers:

def py_add(a, b):
    return a + b

py_add(1, 2)

... and see what code we find in the reflector (it may take some digging around, because there are many of the delegates DLR creates, but with some practice and intuition it is not too hard to find). This time I copied the function body out so I can reformat it and make it little more readable:

public static object $IronPython.Runtime.Operations.Int32Ops.Add(
    object[],
    DynamicSite<object, object, object> site1,
    CodeContext context1,
    object obj1,
    object obj2)
{

    //
    // The language's answer
    //
   
if (((obj1 != null) && (obj1.GetType() == typeof(int))) &&
        ((obj2 != null) && (obj2.GetType() == typeof(int))))
{

        return Int32Ops.Add((int) obj1, (int) obj2);

    }

    //
    // Cry for help !!!
    //
   
return site1.UpdateBindingAndInvoke(context1, obj1, obj2);

}

There are two parts to the body of the delegate updated by addition of two integers. The first part comes from the language and expresses what it means to add two integers (in Python). The Int32Ops.Add method will for example overflow from integers to big integers if the numbers are too large.

Second part of the delegate is, again, crying for help. This will happen, for example, if we were to add, say ... strings, and here would be the updated delegate:

public static object $IronPython.Runtime.Operations.StringOps.Add(
    object[],
    DynamicSite<object, object, object> site1,
    CodeContext context1,
    object obj1, object obj2)
{

    //
    // Adding strings
    //
   
if (((obj1 != null) && (obj1.GetType() == typeof(string))) &&
        ((obj2 != null) && (obj2.GetType() == typeof(string))))
{

        return StringOps.Add((string) obj1, (string) obj2);

    }

    //
    // Adding integers
    //
    if (((obj1 != null) && (obj1.GetType() == typeof(int))) &&
        ((obj2 != null) && (obj2.GetType() == typeof(int))))
{

        return Int32Ops.Add((int) obj1, (int) obj2);

    }

    //
    // Cry for help !!!
    //
    return site1.UpdateBindingAndInvoke(context1, obj1, obj2);

}

Doesn't that  look familiar? It probably does. In fact, it is very similar to our attempt to implement dynamic behavior in the previous post when we iterated over the ToyHelpers.Add to make it increasingly smarter. First implementing addition for doubles, then adding strings and ultimately the reflected call to the op_Addition.

There is an important difference here though! There is no reflective call (something we identified as a possible performance problem with our helper solution) and the generated delegate only contains code to deal with those types which actually appear in the program being executed. If my program uses only addition on integers, the code of the delegate will never evolve beyond the first updated version which handles integers and cries for help on anything else.

Here is where I confess that I played a little trick. DLR is just little too smart and if you execute a code like:

def py_add(a, b):
    return a + b

py_add(1, 2)
py_add("Hello", "Python")

The first time around in the addition, DLR sees two integers and will cry for help, yielding exactly the delegate which we saw above. Second time around, DLR sees two strings, cries for help again, but doesn't exactly produce the second delegate I showed you above. Yet! At that point DLR has a choice to make:

Either the program ran on integers until now and the fact it now sees strings is a sign that it is going to be dealing with strings from now on, in which case presence of the tests for integers may be just a waste of code in the delegate.

Or, the string is just a singularity and program will be back with integers, or even alternate between the two types (or even among even more types, which will trigger more cries for help).

DLR takes the first approach. It will assume that from now on it is strings only and will generate a delegate to deal with strings and cry for help otherwise. It is only when an integer shows up again that DLR will realize that it has seen integer before, it still remembers the recipe how to deal with integers, and will combine both of them to the final delegate I showed you above. So my trick was really quite innocent. Instead of the Python code which only adds two integers and then two strings, I used the following:

def py_add(a, b):
    return a + b

py_add(1, 2)
py_add("Hello", "Python")
py_add(1, 2)

And that did the trick.

The next question we'll explore is going to be how it exactly works when the DLR cries for help and how the language provides the answers. But we'll do it next time. Until then, happy hacking!

How does the programming language express dynamic behaviors in the DLR? So far most of the trees we saw were statically and strongly typed. For example, by constructing tree such as:

Ast.Add(
    Ast.Constant(1),
    Ast.Constant(2)
);

we are expressing fully fully static behavior - adding two integer constants with the result also being an integer. What about adding arbitrary objects, just like in the ToyScript function from the end of the last post:

def add(a, b) {
    return a + b
}

... a function that adds any two things we'll pass to it. If you read the last post or experimented with ToyScript or any other DLR language, then you probably know that ToyScript will generate an ActionExpression in the place of the addition. We'll get to that soon enough. For now let's explore other means we have for expressing runtime dynamic behaviors, something we have tools for already: calling a helper method.

The compiler could turn the addition (or any other dynamic operation for that matter) into a method call. Since we don't know the types of the arguments or the result ahead of time, the helper method ToyHelpers.Add can simply take and return object types:

public static object Add(object a, object b) {
    throw new NotImplementedException();
}

And in the Binary.Generate let's single out the addition to implement it as a call to the ToyHelpers.Add method:

if (_op == Operator.Add) {
    return Ast.Call(
        typeof(ToyHelpers).GetMethod("Add"),
        Ast.ConvertHelper(left, typeof(object)),
        Ast.ConvertHelper(right, typeof(object))
    );
}

The generated tree for the function "add" above will of course look as follows (running "ts.exe -X:ShowASTs add.ts"):

//
// CODE BLOCK: add (1)
//

.codeblock Object add ()(
    .arg Object a (Parameter,InParameterArray)
    .arg Object b (Parameter,InParameterArray)
) { 
    {
        .return (ToyHelpers.Add)(
            (.bound a),
            (.bound b),
        );
    }
}

If we actually call the function "add" within the ToyScript program, we'll encounter the exception thrown from within the helper, no surprise here, but we can quickly get a simple program running. Here's our simple program:

def add(a, b) {
    return a + b
}

print add(1, 2)
print add("Hello ", "ToyScript")

Let's place breakpoint inside the ToyHelper.Add because that's where we place need to implement the actual addition:

image

ToyScript is really simple language so all numeric constant simply become doubles. We can now implement the addition for doubles, and add strings while we are at it:

if (a is double && b is double) {
   
return (double)a + (double)b;
}
if (a is string && b is string) {
    return (string)a + (string)b;
}

throw new NotImplementedException();

This makes our program work:

C:\ToyScript\Bin\Debug > ts.exe add.ts
3
Hello ToyScript

But only for numbers and strings. Other objects cannot be added by our implementation. We could pick few others and implement them in the spirit of double and string, but there will be still plenty to go around. Here's an idea ... we could detect an overloaded operator addition on the .NET objects and call that if we saw one.

The code to add into our Add helper could look something like the following:

if (a != null && b != null) {
    Type aType = a.GetType();
    Type bType = b.GetType();

   
MethodInfo mi = aType.GetMethod(
        "op_Addition",
        new Type[] { aType, bType }
    );

   
if (mi != null) {
       
return mi.Invoke(null, new object[] { a, b });
    }
}

We made few simplifications, of course. We are looking only for "exact match" operators, whose signature is exactly matching the arguments' types and we only examine the left argument for overloaded operator, but you get the idea.

With our new code we can add instances of System.Decimal if we append the following code to our "add.ts" source:

import System

ten = new System.Decimal(10)
twenty = new System.Decimal(20)

print add(ten, twenty)

And running the whole script will produce:

C:\ToyScript\Bin\Debug> ts.exe add.ts
3
Hello ToyScript
30

We could have the script print the type of the result to see that the "30" at the end is indeed an instance of System.Decimal.

With little effort we taught the ToyScript to add by simply calling and implementing a runtime helper method ToyHelpers.Add, and it works quite well. Unfortunately, there are two disadvantages to our approach:

First, we can only single out a finite number of types to handle the way we handled double and string. We could add int, long, char, lists, tuples (if ToyScript supported them) and the sequence of "if" statements in our helper would grow and so would the number of possible type tests we'd need to perform before finding the right one.

This is actually what IronPython did/does in versions 1.x. If program uses mostly types which are at the bottom of our test, the helper will waste a lot of time just testing for types.

Second disadvantage is that the final attempt - finding the overloaded operator "op_Addition" is implemented as a reflected call, which is rather slow.

It won't be until the next post where we'll finally explore how DLR addresses both of these issues at the same time and where we'll see ActionExpressions finally coming to the picture. I hope you can wait until tomorrow for that.

So far we looked at simple expressions and statements. This time we'll talk about constructs for describing functions / lambdas. ToyScript language supports simple functions. Consider rather trivial ToyScript code:

def f(a) {
    var x = "Hello"
    print x
    print a
}

f("ToyScript")

The function declares a local variable "x", performs rather unnecessary assignment and prints the value of the local and that of the parameter. We are consciously staying away from expressions because those would quickly get us into the exploration of dynamic behavior (ToyScript is a dynamic language after all). We'll get to it shortly, but for now the focus is on functions and how languages can express those in the context of DLR.

Running the code with -X:ShowASTs will output following two trees / code blocks. The first one is the top level code (function definition followed by the call to the function just defined). The second tree is the body of the ToyScript function "f" itself. We'll explore them in reverse of the print order, the body of the "f" first:

//
// CODE BLOCK: f (1)
//

.codeblock Object f ()(
    .arg Object a (Parameter,InParameterArray)
) {
    .var Object x (Local,InParameterArray)
    {
        (.bound x) = (Object)"Hello";
        (ToyHelpers.Print)(
            (.bound x),
        );
        (ToyHelpers.Print)(
            (.bound a),
        );
    }
}

Very little surprise here. We see what look like a function (.codeblock) called "f" which takes an argument "a" of type object and returns object as well. In addition to what we know from earlier (calls to ToyHelpers.Print), there's a local variable "x" in the DLR Tree and an assignment to it.

To represent the function body, the ToyScript creates a DLR CodeBlock using the Ast.CodeBlock factory. This happens in the ToyScope constructor, but the initial impulse comes from Def.Generate (Def.cs file contains the ToyScript node representing function definition) by calling tg.PushNewScope(_name).

The CodeBlock then lets ToyScript compiler create local variables, add parameters and ultimately populate the body.

Now that we are looking at the Def.Generate, it is also quite straightforward how the second code block which represents the top level code came to being:

//
// AST D:\Dev\x.ts
//

.codeblock Object D:\Dev\x.ts ()() {
    .var Object f (Local,InParameterArray)
    {
        (.bound f) = (ToyFunction.Create)(
            "f",
            .new String[] = {
                "a",
            },
            .block (f #1
                Declarative
            ),
        );
        .action (Object) Call( // CallSimple
            (.bound f)
            "Hello ToyScript"
        );
    }
}

Similar to Python, where functions are first class and defining a function essentially means assigning the instance of the function object to a variable named after the function's name, making:

def foo(): pass

pretty much equivalent to:

foo = def (): pass

... if the following syntax were allowed, that is. Same for ToyScript. The Def.Generate() builds such assignment. It creates a variable named after the function name (or uses existing one if it already exists) and assigns a result of a call to ToyFunction.Create to it. The ToyFunction.Create, in addition to function name, accepts the names of arguments and the delegate to the function body (expressed as CodeBlockExpression).

The last node in the top level code is the actual function call. The exact semantic is: "Call the content of the variable 'f' with the argument 'Hello ToyScript'". Since what the call actually does depends on the contents of the variable "f", it is a dynamic operation expressed via an ActionExpression - a gateway into the dynamic behavior of the DLR.

In Dynamic languages the dynamic operations are everywhere - a reason why we were avoiding operators until now. For example, another trivial function:

def add(a, b) {
    return a + b
}

also uses ActionExpression to express the dynamic operation "Add":

.codeblock Object add ()(
    .arg Object a (Parameter,InParameterArray)
    .arg Object b (Parameter,InParameterArray)
) {

    {
        .return .action (Object) Do Add( // DoOperation Add
            (.bound a)
            (.bound b)
        );
    }
}

In short, the ActionExpressions represent dynamic operations whose exact semantic is not known at compile time and has to be determined at runtime. In the next chapter we'll explore how the runtime semantic can be captured using the ActionExpressions, and using other means as well and how the Dynamic Language Runtime works with respect to the dynamic operations expressed as Actions.

The ToyScript sample language's front-end is very similar to that of any traditional compiler. It has tokenizer, parser and an abstract syntax tree representation of the toy language. What makes it a DLR based language (from the compiler pipeline perspective) is that instead of generating its own intermediate code, or generating Microsoft .NET IL directly, ToyScript will generate a tree representation of the code - DLR Trees. Dynamic Language Runtime will then take care of the code generation.

The DLR Trees are essentially the DLR representation of programs so every language that targets DLR produces the DLR Trees. ToyScript's design is similar to that of all the other DLR based languages, such as IronPython or IronRuby in the sense that it first parses into its own AST and then generates the DLR trees. At one point ToyScript (being very simple language) parsed directly into the DLR trees, but we changed that to behave more like the other DLR based languages, even though for ToyScript it is not strictly necessary.

By generating the DLR Trees instead of IL, the compiler writer doesn't have to worry about the IL generation and the DLR will take care of the intricacies. The developer can then focus on the correct semantic of the language at hand by implementing the front-end and implementing the correct runtime semantic.

Let's take look at the following snippet of ToyScript code:

import System
print "Hello ToyScript"

Both of the language elements shown (import and print) are implemented simply by runtime library functions (ToyHelpers.Import and ToyHelpers.Print respectively).

There is a useful feature in the DLR which allows to trace all of the trees created during the program's execution: -X:ShowASTs. We can run the code above with the "-X:ShowASTs" command line parameter and see the output:

//
// AST D:\Dev\x.ts
//

.codeblock Object D:\Dev\x.ts ()() {
    .var Object System (Local,InParameterArray)

    {
        (.bound System) = (ToyHelpers.Import)(
            "System",
        );
        (ToyHelpers.Print)(
            (Object)"Hello ToyScript",
        );
    }
}

The output of the trace shows essentially what we'd expect. The import statement turned into an assignment in which variable "System" is assigned return value of the call to the ToyHelpers.Import, passing "System" as an argument. Similarly, the print statement got turned into the call with a "Hello ToyScript" argument.

At the DLR Tree level, both of these nodes are MethodCallExpression, one of many expressions the DLR Trees support. Another one that we can see in this example is an assignment (BoundAssignment) and constant (ConstantExpression).

Who created this DLR Tree? Each AST node of the ToyScript AST has a "Generate" method which is responsible for creating the DLR Tree for the node. The Generate for "print" statement (in Print.cs) then creates the tree that we already saw above:

return Ast.Statement(
    Span,
    Ast.Call(
        typeof(ToyHelpers).GetMethod("Print"),
        Ast.ConvertHelper(
           
_expression.Generate(tg),
                typeof(object)
            )
        )
    );

The ToyScript sample above uses actually only few of the many available tree nodes. It only uses assignment (Ast.Assign), block (Ast.Block), call to a method specified as MethodInfo (Ast.Call), constant (Ast.Constant) and conversion/cast (Ast.Convert / Ast.ConvertHelper). Outside of these few, the tree supports quite a few nodes and factory methods for each of them.

Some of the other most important nodes in the DLR Trees are:

  • UnaryExpression  - Negate, Convert, logical Not
  • BinaryExpression - comparisons, arithmetic and logical operations, array index
  • MemberExpression - field or property access
  • CallExpression - call to a method specified via MethodInfo
  • NewExpression - calling a constructor to create an instance of .NET class
  • BoundAssignment - assignment to a variable
  • BoundExpression - a value of the variable
  • ConditionalExpression - condition ? ifTrue : ifFalse
  • ...

Since DLR represents complex programs, statement-like constructs are needed also:

  • DoStatement - a "do .. while ..." loop
  • LoopStatement - a simplified form of the C# -style "for" loop
  • ReturnStatement
  • ThrowStatement
  • TryStatement
  • SwitchStatement
  • ...

The underlying idea for all of the nodes is that they are statically typed and the factory methods enforce the restrictions. For example, when creating a MethodCallExpression to call "System.Console.WriteLine(string)", or when creating an assignment, the factory method checks that the type of the variable is assignable from the type of the right-hand expression.

Now that we know how ToyScript generates the DLR Tree for a (very) simple constructs, the obvious question comes to mind: How does it deal with constructs that are not as obvious at compile time? For example "a + b". What happens when executing "a + b" completely depends on the values of "a" and "b" at the time when the expression is being evaluated. Adding integers or strings are completely different matter, not to mention objects that may implement operator "+". This is what we'll discuss in detail in the following chapters.

Starting with Alhpa 6 release of the Dynamic Language Runtime and IronPython, the distribution includes a small sample language ToyScript. As the name suggests, rather than a serious 'real' language, it is a toy complex barely enough to deserve the label 'language'. The language is truly simple (for example it doesn't even support operator precedence or comments), but its simplicity allows us to focus on some of the design elements shared by the languages that currently target DLR.

Once you unzip the DLR distribution, ToyScript solution is easily found in the Src\ToyScript\ToyScript directory. The solution is all you need to get started since it includes reference to the DLR project (Microsoft.Scripting).

Like a traditional compiler, ToyScript has a tokenizer and parser (ToyTokenizer.cs and ToyParser.cs, both in the Parser directory) and an abstract syntax tree (in the Ast subdirectory). At the root of the AST hierarchy is a ToyNode from which Expression and Statement derive.

ToyScripts expressions are:

Assignment a = b
Constant 1, "Hello"
New new a(arg, ...)
Binary a + b
Index a[b]
Parenthesized (a)
Member a.b
Call a(b, ...)
Named a

And the statements:

If "if" statement
While "while" loop
Def Python-style function definition
Import Python-style import statement
Empty empty statement
Var Variable definition
Block Block of statements
Print Python-style "print"
ExpressionStatement  
Return  

So far, all of this is quite similar to the majority compilers out there. In the later stages of compilation the effects of targeting DLR will be visible. We'll talk about that in next chapter.

More Posts Next page »
 
Page view tracker