Kicking off: Jatha

11 views
Skip to first unread message

Ola Bini

unread,
Jun 2, 2007, 4:15:37 PM6/2/07
to jvm-la...@googlegroups.com
Hi,

A short introduction to Jatha (I'm not the lead, and haven't been
involved in a while, but did some work back in the days):

Jatha is an implementation of Common Lisp for the JVM that aims to be
more or less complete. Currently it doesn't support all of CL, and CLOS
is the most glaring omission. With some work it could get up to speed
but right now the people using it are those who want CL syntax and CL
style macros and some of the other CL goodies, but don't need complete CL.
(If I ever have time, I would like to get Jatha in better shape, but
alas...)

So, the implementation is currently a pure interpreter. It's a stack
based machine based on Peter Landin's SECD register system, with one
extra register added to the mix. The interpreter works with
S-expressions from the top to the bottom, including saving the contents
of the registers as S-expressions. This means that potentially almost
all of the runtime could be rewritten in Lisp. This is not currently the
case, though.

The Java integration features of Jatha only uses reflection for
invocation; right now performance isn't the issue: completeness comes first.

Jatha is a quite well implemented interpreter, the code base is in good
shape with quite good tests. It follows more or less object oriented
standards, but have defaulted to the same implementation of the "base
object" as Jython and JRuby have done at different stages (having a
LispObject with most of the available Lisp methods defined on it). This
could be better designed, but it makes many things easier.

What Jatha could benefit from in terms of JVM language support would in
the beginning be at the library level (the JVM equivalent of the DLR, if
you may). invoke_dynamic isn't that interesting right now, since the
dynamic invocation model doesn't really match Common Lisp (which is
interesting, since CL is one of the more dynamic languages).

Anyway, library level support for interchanging object hierarchies
between JVM language type systems would probably be useful for Java
integration; a nicer way to handle the "base object"-problem doubly so.

And that's about it.

--
Ola Bini (http://ola-bini.blogspot.com)
JRuby Core Developer
Developer, ThoughtWorks Studios (http://studios.thoughtworks.com)

"Yields falsehood when quined" yields falsehood when quined.


John Rose

unread,
Jun 4, 2007, 12:03:19 AM6/4/07
to jvm-la...@googlegroups.com
On Jun 2, 2007, at 1:15 PM, Ola Bini wrote:

invoke_dynamic isn't that interesting right now, since the 

dynamic invocation model doesn't really match Common Lisp (which is 

interesting, since CL is one of the more dynamic languages).


invokedynamic probably isn't useful for speeding up an interpretive system.

The time to consider invokedynamic is when the implementation uses
byte-compilation, and expects to perform calls in about 10 native instructions
(as opposed to hundreds or more).

Dynamism in Lisp (and Scheme) has a number of forms, and all of
the important ones (I think) can eventually benefit from JVM support:

1. Function calling can take a variable number of arguments,
and/or a final "spread" argument, and/or some keyworded arguments.

Making this fit together smoothly requires an interesting set of
calling sequences, with some on-the-fly argument transformation
when caller and callee do not exactly agree on number and type
of arguments.  ("Type" usually just means restarg or not,
but it could mean, esp. in the JVM, more interesting things,
such as fixnum vs. 'any'.)

Example:  (list 'a 'b 'c) might suppose it is passing two
positional arguments, but #'list probably expects a single
&rest parameter.  A hard-compiled implementation, if
it cannot guess this when compiling the call site, might
issue an invokedynamic of call(Object,Object,Object)
to the object #'list, and rely on the signature mismatch
to be fixed by an adapter routine, generated on demand
as part of invokedynamic's signature mismatch processing.
The method which eventually implements 'list' needs a
signature like call$rest(List).

(Put another way, this is an important use case for giving
invokedynamic an efficient hook for managing signature
mismatches.)

In this example, 'list' is a well-known function, so compilers
will often special-case it.  However, if you replace it by an
indirect call to a statically unknown function (a 'funcall')
then the problem of argument adaptation arises again
in full generality.

If you look at Neal G's Java closures proposal, you'll
see an open-ended set of interface types like 'call' above.
With invokedynamic, those interface types are not necessary.
That would be nice for Lisp.  (I suppose current JVM implementors
avoid this problem by predefining 'call' interfaces for any
number of arguments up to some fixed small limit, and
use restargs for all other calls.  This approach does not
scale well if you want to optimize a wider variety of
calling sequences, e.g., with unboxed numbers.)

2. Generic arithmetic, which requires some sort of
fast double dispatch.  It should also be able to make
use of compile-time (and JIT-time) types.

Example:  (+ A B) needs to compile to something
like A.op$plus(B) of signature Object(Object),
which is backed up by lots of secondary method
variants like Object(Integer).  A simpler expression
(+ A 12) should ideally compile to something like
A.op$plus(12) of signature Object(int).

As above, with simple procedure types, it is possible
to get by without invokedynamic, but you have to cope
with a large number of interfaces to manage the
intermediate dispatches (or else one central
decision tree, which does not scale).

Another advantage of invokedynamic (properly
designed) for generic arithmetic is integration
with standard JVM classes like Integer and Double.
The current JVM invoke instructions cannot
invoke 'op$plus' directly on an Integer, forcing
implementors to compromises such as defining
their own MyInteger types.

3. Generic sequence operations, which want to
dispatch on the sequence type.  They probably
want to use an interface which refines java.util.List
with Lisp-y additions, such as 'map', 'find', and 'concat'.

The value of invokedynamic here is that (as with
arithmetic) it can allow Lisp to use native String,
List, and array types as sequences, even if Lisp
has richer interfaces for its own language-specific
types (like multi-dimensional and/or resizable arrays).

Dynamic languages often treat strings, lists, and/or arrays
as multiple concrete implementations of a common sequence
type.  If the JVM had a Lisp-y interface which all of those types
implemented, then it would be obvious how to make this work
for Lisp, but of course the JVM does not define Lisp-y interfaces,
and even if it did that might not help the next dynamic language.

The basic need is for something one might call "counterfeit traits".
If an object does not implement some desired interface (the 'trait'),
the language runtime system can fake up a set of methods for
the object, in terms of its natural capabilities.  Example:
  class FakeUpAdapter$123ABC implements List {
    String original; ...
    Object get(int n) { return original.charAt(n); } ...
  }

I think invokedynamic should provide a way to do such fake-ups,
on a per-language basis, by trapping to a language-specific
error handler which can inspect types and provide a suitable
adapter method.  (E.g., an adapter which connects Lisp's
'elt' operation to String.charAt, if the receiver is a String.)
The adapter method would then happily process any number
of similar calls in the future, without further visits to the error
handler.

(The simplest way to do this language-specific error
handler is to allow the bytecode compiler to emit a declaration,
for each compilation unit, of a class to which invocation
errors are directed.  It does not work to put a catch-all
method on Object, as in the earliest proposals, since each
language has its own ideas about how to repair broken
calls.  In addition, the error handler should report a method
back to the JVM which the JVM can then use for more than
one call.)

4, 5, ....  Structs and CLOS.  It's probably not time to talk about
that yet, but clean, scalable solutions to the previous cases
would provide a good start.

Cheers,

-- John
Reply all
Reply to author
Forward
0 new messages