Closure implementation

Osvaldo Doederlein opinali at
Tue Dec 15 10:03:58 PST 2009


I was wondering about the implementation of closures (translation into Java
bytecode), a topic that's not yet covered by any of the current proposals
(CfJ 0.6a+b or the Strawman), although well illustrated by the BGGA
prototype. It's perhaps early to worry about implementation, but the
exception if some implementation requirement demand support from the
language spec.

I wish closures to be as lightweight as possible. This means:

a) The classes that closures are translated to, should be as small and fast
to load as possible. These classes could benefit from JSR-292's anonymous
classes, so each synthetic closure class doesn't load unnecessary metadata.
b) The compiler should be able to shortcut closure classes whenever
possible. This is important for multiple use cases, from control structures
(like in CfJ 0.6b) to Smalltalk-like extension methods for collections
(select(), etc.). I'd expect a sufficiently smart compiler to translate the
calls to such methods (and their literal-closure parameters) into fully
inline code without any overhead of synthetic classes/methods. For control
structures with non-local transfer, such optimization, when possible, would
remove the bulky code and helper exceptions that we see in the BGGA
c) For closures that cannot be optimized out and must be represented as
runtime classes/objects, I'd expect a best-effort to optimize that
representation. Remarkably, if the closure doesn't capture any state from
the enclosing instance, I expect javac to generate a singleton object so
there's no overhead to allocate a separate closure object at each execution.

Item (b) is a hard call for source compilation because javac cannot inline
methods generally, due to Java's compilation model. We could do this only
for non-virtual calls to methods from the same class (like switch for enums
does for ordinal numbers), but that would be too limited to be useful. On
the other hand, such optimizations could be performed by the JIT compiler.
This makes item (a) even more important, because all that class baggage
would often be temporary: the VM loads and links it, runs it a while, and
when the optimizer kicks in and gets rid of the class overhead, all that
stuff can be unloaded and GC'd.

Both (a) and (b) are only generally legal if the user code doesn't expect
closures to be full-blown objects. CfJ already says "this [keyword] has the
same meaning as it would where the lambda expression appears", so at least
code inside the closure cannot easily manipulate the closure object. Closure
conversion is also OK because the compiler wraps the closure code in a
normal class implementing some interface, in this case there's no invoke()
method. So the current proposal (and the BGGA prototype) already make no
promises about the runtime nature of compiled closures. But in the general
case, with closured-typed variables, we can reflect the closure class and
its members, and lock on its monitor. We could make
synchronized(someClosure) illegal at the source level, rejected by the
verifier at the bytecode level, and (to deal with aliasing and casts) 'mark'
all closure classes so their monitors will fail at runtime with a
IllegalMonitorStateException. Item (c) is clear; the user just shouldn't
expect the closure object to have separate identities for every usage, a
rule we can impose without confusion as the closure syntax is free from the
"new" operator.

The demands of (b) can be addressed by inlining + Escape Analysis-driven
scalar replacement + some specific optimizations for closures, e.g. to match
the patterns of code generated for non-local returns in order to optimize
that into straight local branches. Then we can expect optimally short and
fast code to generated. EA and scalar replacement are advanced, bleeding
edge optimizations in JDK7 (only C2), and extra optimizations for closures
won't likely fit in the JDK7 schedule. But we can wait for the performance -
right now I'm just worried to make sure that the language and bytecode won't
make optimization any harder than necessary, ad maybe make it easy. The
language should induce the user to not use closures as objects; (a) and (b)
are important to enforce programming idioms and APIs that result in bytecode
that's friendly to aggressive closure optimizations. And then, we could help
the optimizer; for example, have bytecode attributes that mark closure
bytecode semantically, so the JIT doesn't have to be tightly coupled to the
patterns of code emitted by javac, remarkably for complex scenarios like
non-local returns.

Lightweight closures are very important for any usage that involves
abundant, fine-grained closures. One of my old pet peeves with Java inner
classes is that they're so bloated; closures (as powerful as BGGA or CfJ
0.6a+b) will be even more bloated. We could use this opportunity to provide
lightweight closures/lambdas in the Java language. A sufficiently powerful
and efficient bytecode/runtime representation (including helper classes in
the core) would also be a great candidate for a universal representation of
"full closures" for most language that support closures and target the JVM,
so these languages have benefits in straight Java-interop and additional
runtime footprint.

-------------- next part --------------
An HTML attachment was scrubbed...

More information about the closures-dev mailing list