I recently discovered that PowerShell 3.0 makes use of the DLR. This was unexpected because, after Microsoft discontinued the Iron languages, I assumed they were shelving the DLR. I believed they would keep it in the framework but wouldn’t actively utilize or develop it. I hope this isn’t the case, and the lack of news about DLR advancements in recent CLR versions simply means it’s already robust and doesn’t require immediate improvements.
This discovery, coupled with this excellent article about invokedynamic in Java, led me to revisit the distinct approaches of the JVM and CLR in optimizing dynamic language performance. Last year, I explored this previous article how Java 8 utilizes invokedynamic and runtime code generation to implement Lambdas and create necessary classes for lambda state. Conversely, C#, Scala, and pre-Java 8 Groovy generate these classes entirely at compile time.
Let’s shift our focus from Java Lambdas to the core “dynamic performance boosting techniques.” Both invokedynamic and the DLR heavily employ runtime code generation, but their methods differ.
invokedynamic: When the JVM interpreter encounters an invokedynamic bytecode instruction, it calls a bootstrap method. This method returns a CallSite object containing a MethodHandle. The invokedynamic instruction is then substituted with a call to this MethodHandle within the CallSite. Notably, this bootstrap invocation occurs only once per encountered invokedynamic instruction. As the code evolves, the MethodHandle within the CallSite can be replaced. A crucial aspect to understand about the bootstrapping process is:
“The essence of invokedynamic is its function as a JVM-level macro, deferring lambda translation strategy to LambdaMetaFactory, a library class.”
Furthermore, this other article states:
“Each invokedynamic call site in bytecode references a bootstrap method to determine the invoked method. This bootstrap method, implemented in standard Java, acts as a lookup routine for the target method. The lookup is performed only once, although manual rebinding of a dynamic call site is possible later.”
The key takeaway is understanding MethodHandles. Essentially, they are objects containing “method pointers.” Are they equivalent to .NET delegates? The answer is nuanced. .NET has various delegate types inheriting from System.MulticastDelegate. We define these types (often using the framework-provided Action and Func since .NET 2.0) for different method signatures, ensuring type safety and fast delegate invocation through compile-time checks. Java, however, employs a single MethodHandle class, achieving type safety differently. The JVM intrinsically understands MethodHandles, invoking them via a “polymorphic signature,” enabling calls like MethodHandle.invokeExact with varying parameters across call sites.
DLR: Enhancing .NET for dynamic languages didn’t involve new bytecode instructions or JVM modifications. Instead, it leverages existing features like Expression Trees, Delegates, and Lightweight Code Generation cleverly. series of posts offers an excellent explanation of the DLR’s inner workings. In essence, wherever a dynamic call occurs (e.g., using a variable declared as dynamic in C#), the compiler inserts the creation of a CallSite object and invokes the delegate referenced by its Target property. This mechanism sounds strikingly similar to Java’s CallSite-MethodHandle pair. However, .NET creates the CallSite at compile time, while Java does so during runtime, suggesting a more dynamic Java approach. This isn’t entirely accurate. The crucial aspect unfolds during runtime: based on the encountered object, dynamic code generation creates a new delegate (utilizing a caching system) to bind to CallSite.Target. This sophisticated process is elaborated in here. I presume the JVM employs a similar caching and decision-making system for CallSite MethodHandle rebinding, though I haven’t delved into it.
Some argue that the JVM’s approach is superior for dynamic languages. This is one of those sources suggests that the JVM’s ability to inline generated native code (unlike the CLR) contributes to this advantage. This topic is beyond my expertise, so I can’t assess its validity or impact. Nevertheless, the CLR + DLR combination has proven to be a highly capable platform for dynamic languages. Considering this and their investment in TypeScript, I would be thrilled to see Microsoft release an ES5-ES6 implementation for the CLR, similar to Oracle’s Nashorn.