IronRuby Libraries
The IronRuby libraries are divided into two groups: built-ins and the standard libraries. We will distribute both sets of libraries in the standard IronRuby distribution. The distinction is whether a library must be required prior to use.
The code is organized in two separate directories under trunk\src:
The built-in libraries are compiled alongside the rest of the compiler into the Ruby.dll assembly (this will be renamed in the future to IronRuby.dll). The standard libraries are compiled into IronRuby.Libraries.dll for the time being. These libraries may be refactored into some additional assemblies in the future, but for the time being we'll likely go down the path of extending the assembly-level RubyLibraryAttribute to identify the Ruby libraries implemented within an assembly. All Ruby libraries must contain this attribute. Kernel#require will inspect these attributes when it goes to try and find a library by name.
Let's walk through some simple library code to better understand what's going on:
This defines a module called Sample which will be loaded implicitly when we require the IronRuby.Libraries assembly. In the future, we will be able to require Sample directly. We can run this code via rbx.exe:
There's something interesting in the implementation of the Hello method. As you know, all Ruby methods must return a value, yet we've defined Hello as a method that returns void. If you look at the output of the console, you'll see that it returns nil, as expected. So how did this magic work?
Let's take a tour via the debugger. Much of the magic happens in RubyActionBinder.cs. Set a breakpoint on MakeRuleForInvokeMember<T> and execute the require from the screenshot above (you'll need to step over the execution of the require method itself). When you execute Sample.hello, you'll hit the breakpoint.
First, notice that the type of T is DynamicSiteTarget<object, object>. This method is responsible for generating a rule from the available information: the name of the method, the type of the receiver, the execution context, and some information about the parameters.
What's a rule? A rule is (currently) a conditional statement. There are two parts, a Test and a Target. The Test guards the Target, and if true will execute the Target. Rules are used by the DLR to generate Dynamic Sites, which is our mechanism for caching the results of our method lookups.
Here's some pseudo-code for the rule produced by MakeRuleForInvokeMember<T>:
if (typeof(target) == Sample && !HasVersionChanged(target))
return target.hello();
Rules contain DLR expression trees. These expression trees are converted into executable CIL by the DLR. This is what the generated IL looks like after decompiling back to C# using Reflector:
public static object Handle2(object[] objArray1,
FastDynamicSite<object, object> site1, object obj1)
{
if ((obj1 == ((RubyModule) objArray1[0]))
&& (((RubyModule) objArray1[1]).Version == 0x815))
{
Sample.Hello((RubyModule) obj1);
return null;
}
return site1.UpdateBindingAndInvoke(obj1);
}
Notice that we return null from the site since the method binder knows that we are invoking a void method, so we generate the correct return statement.
If you want to see the dynamic site yourself, you can run IronRuby in debug mode using these command line switches:
rbx.exe -X:SaveAssemblies -X:StaticMethods -D app.rb
app.rb is a file that contains the code that we're running. IronRuby will generate a file called snippets1.dll in the same directory as app.rb. Open it up using Reflector, and you'll see the dynamic site that we generate for calling Sample#hello:
Quite a lot of the code in the RubyActionBinder is targeted for refactoring, so this isn't what it's going to look like in the long run. However, it's a good start and it generates correct, fast code today.
In a future post, I'll walk you through more complex samples where we accept parameters, dispatch to blocks, throw exceptions etc. Hope this helps you understand some of what we're doing for method dispatch to C# library code today.



Recent Comments