|
|
Subscribe / Log in / New account

Kawa — fast scripting on the Java platform

December 3, 2014

This article was contributed by Per Bothner

Kawa is a general-purpose Scheme-based programming language that runs on the Java platform. It aims to combine the strengths of dynamic scripting languages (less boilerplate, fast and easy start-up, a read-eval-print loop or REPL, no required compilation step) with the strengths of traditional compiled languages (fast execution, static error detection, modularity, zero-overhead Java platform integration). I created Kawa in 1996, and have maintained it since. The new 2.0 release has many improvements.

Projects and businesses using Kawa include: MIT App Inventor (formerly Google App Inventor), which uses Kawa to translate its visual blocks language; HypeDyn, which is a hypertext fiction authoring tool; and Nü Echo, which uses Kawa for speech-application development tools. Kawa is flexible: you can run source code on the fly, type it into a REPL, or compile it to .jar files. You can write portably, ignoring anything Java-specific, or write high-performance, statically-typed Java-platform-centric code. You can use it to script mostly-Java applications, or you can write big (modular and efficient) Kawa programs. Kawa has many interesting features; below we'll look at a few of them.

Scheme and standards

Kawa is a dialect of Scheme, which has a long history in programming-language and compiler research, and in teaching. Kawa 2.0 supports almost all of R7RS (Revised7 Report on the Algorithmic Language Scheme), the 2013 language specification. (Full continuations is the major missing feature, though there is a project working on that.) Scheme is part of the Lisp family of languages, which also includes Common Lisp, Dylan, and Clojure.

One of the strengths of Lisp-family languages (and why some consider them weird) is the uniform prefix syntax for calling a function or invoking an operator:

    (op arg1 arg2 ... argN)
If op is a function, this evaluates each of arg1 through argN, and then calls op with the resulting values. The same syntax is used for arithmetic:
    (+ 3 4 5)
and program structure:
    ; (This line is a comment - from semi-colon to end-of-line.)
    ; Define variable 'pi' to have the value 3.13.
    (define pi 3.13)

    ; Define single-argument function 'abs' with parameter 'x'.
    (define (abs x)
      ; Standard function 'negative?' returns true if argument is less than zero.
      (if (negative? x) (- x) x)

Having a simple regular core syntax makes it easier to write tools and to extend the language (including new control structures) via macros.

Performance and type specifiers

Kawa gives run-time performance a high priority. The language facilitates compiler analysis and optimization. Flow analysis is helped by lexical scoping and the fact that a variable in a module (source file) can only be assigned to in that module. Most of the time the compiler knows which function is being called, so it can generate code to directly invoke a method. You can also associate a custom handler with a function for inlining, specialization, or type-checking.

To aid with type inference and type checking, Kawa supports optional type specifiers, which are specified using two colons. For example:

    (define (find-next-string strings ::vector[string] start ::int) ::string
      ...)

This defines find-next-string with two parameters: strings is a vector of strings, and start is a native (Java) int; the return type is a string.

Kawa also does a good job of catching errors at compile time.

The Kawa runtime doesn't need to do a lot of initialization, so start-up is much faster than other scripting languages based on the Java virtual machine (JVM). The compiler is fast enough that Kawa doesn't use an interpreter. Each expression you type into the REPL is compiled on-the-fly to JVM bytecodes, which (if executed frequently) may be compiled to native code by the just-in-time (JIT) compiler.

Function calls and object construction

If the operator op in an expression like (op arg1 ... argN)) is a type, then the Kawa compiler looks for a suitable constructor or factory method.

    (javax.swing.JButton "click here")
    ; equivalent to Java's: new javax.swing.JButton("click here")

If the op is a list-like type with a default constructor and has an add method, then an instance is created, and all the arguments are added:

    (java.util.ArrayList 11 22 33)
    ; evaluates to: [11, 22, 33]

Kawa allows keyword arguments, which can be used in an object constructor form to set properties:

    (javax.swing.JButton text: "Do it!" tool-tip-text: "do it")

The Kawa manual has more details and examples. There are also examples for other frameworks, such as for Android and for JavaFX.

Other scripting languages also have convenient syntax for constructing nested object structures (for example Groovy builders), but they require custom builder helper objects and/or are much less efficient. Kawa's object constructor does most of the work at compile-time, generating code as good as hand-written Java, but less verbose. Also, you don't need to implement a custom builder if the defaults work, as they do for Swing GUI construction, for example.

Extended literals

Most programming languages provide convenient literal syntax only for certain built-in types, such as numbers, strings, and lists. Other types of values are encoded by constructing strings, which are susceptible to injection attacks, and which can't be checked at compile-time.

Kawa supports user-defined extended literal types, which have the form:

    &tag{text}
The tag is usually an identifier. The text can have escaped sub-expressions:
    &tag{some-text&[expression]more-text}
The expression is evaluated and combined with the literal text. Combined is often just string-concatenation, but it can be anything depending on the &tag. As an example, assume:
    (define base-uri "http://example.com/")
then the following concatenates base-uri with the literal "index.html" to create a new URI object:
    &URI{&[base-uri]index.html}

The above example gets de-sugared into:

    ($construct$:URI $<<$ base-uri $>>$ "index.html")

The $construct$:URI is a compound name (similar to an XML "qualified name") in the predefined $construct$ namespace. The $<<$ and $>>$ are just special symbols to mark an embedded sub-expression; by default they're bound to unique empty strings. So the user (or library writer) just needs to provide a definition of the compound name $construct$:URI as either a procedure or macro, resolved using standard Scheme name lookup rules; no special parser hooks or other magic is involved. This procedure or macro can do arbitrary processing, such as construct a complex data structure, or search a cache.

Here is a simple-minded definition of $construct$:URI as a function that just concatenates all the arguments (the literal text and the embedded sub-expressions) using the standard string-append function, and passes the result to the URI constructor function:

    (define ($construct$:URI . args)
      (URI (apply string-append args)))

The next section uses extended literals for something more interesting: shell-like process forms.

Shell scripting

Many scripting languages let you invoke system commands (processes). You can send data to the standard input, extract the resulting output, look at the return code, and sometimes even pipe commands together. However, this is rarely as easy as it is using the old Bourne shell; for example command substitution is awkward. Kawa's solution is two-fold:

  1. A "process expression" (typically a function call) evaluates to a Java Process value, which provides access to a Unix-style (or Windows) process.
  2. In a context requiring a string, a Process is automatically converted to a string comprising the standard output from the process.

A trivial example:

   #|kawa:1|# (define p1 &`{date --utc})

("#|...|#" is the Scheme syntax for nestable comments; the default REPL prompt has that form to aid cutting and pasting code.)

The &`{...} syntax uses the extended-literal syntax from the previous section, where the backtick is the 'tag', so it is syntactic sugar for

    ($construct$:` "date --utc")
where $construct$:` might be defined as:
(define ($construct$:` . args) (apply run-process args))
This in turns translates into an expression that creates a gnu.kawa.functions.LProcess object, as you see if you write it:
    #|kawa:2|# (write p1)
    gnu.kawa.functions.LProcess@377dca04

An LProcess is automatically converted to a string (or bytevector) in a context that requires it. This means you can convert to a string (or bytevector):

    #|kawa:3|# (define s1 ::string p1) ; Define s1 as a string.
    #|kawa:4|# (write s1)
    "Wed Nov  1 01:18:21 UTC 2014\n"
    #|kawa:5|# (define b1 ::bytevector p1)
    (write b1)
    #u8(87 101 100 32 74 97 110 ... 52 10)

The display procedure prints the LProcess in "human" form, as a unquoted string:

    #|kawa:6|# (display p1)
    Wed Nov  1 01:18:21 UTC 2014

This is also the default REPL formatting:

    #|kawa:7|# &`{date --utc}
    Wed Nov  1 01:18:22 UTC 2014

We don't have room here to discuss redirection, here documents, pipelines, adjusting the environment, and flow control based on return codes, though I will briefly touch on argument processing and substitution. See the Kawa manual for details, and here for more on text vs. binary files.

Argument processing

To substitute the result of an expression into the argument list is simple using the &[] construct:

    (define my-printer (lookup-my-printer))
    &`{lpr -P &[my-printer] log.pdf}
Because a process is auto-convertible to a string, no special syntax is needed for command substitution:
    &`{echo The directory is: &[&`{pwd}]}
though you'd normally use this short-hand:
    &`{echo The directory is: &`{pwd}}

Splitting a command line into arguments follows shell quoting and escaping rules. Dealing with substitution depends on quotation context. The simplest case is when the value is a list (or vector) of strings, and the substitution is not inside quotes. In that case each list element becomes a separate argument:

    (define arg-list ["-P" "office" "foo.pdf" "bar.pdf"])
    &`{lpr &[arg-list]}

An interesting case is when the value is a string, and we're inside double quotes; in that case newline is an argument separator, but all other characters are literal. This is useful when you have one filename per line, and the filenames may contain spaces, as in the output from find:

    &`{ls -l "&`{find . -name '*.pdf'}"}
This solves a problem that is quite painful with traditional shells.

Using an external shell

The sh tag uses an explicit shell, like the C system() function:

    &sh{lpr -P office *.pdf}
This is equivalent to:
    &`{/bin/sh "lpr -P office *.pdf"}

Kawa adds quotation characters in order to pass the same argument values as when not using a shell (assuming no use of shell-specific features such as globbing or redirection). Getting shell quoting right is non-trivial (in single quotes all characters except single quote are literal, including backslash), and not something you want application programmers to have to deal with. Consider:

    (define authors ["O'Conner" "de Beauvoir"])
    &sh{list-books &[authors]}
The command passed to the shell is the following:
    list-books 'O'\''Conner' 'de Beauvoir'

Having quoting be handled by the $construct$:sh implementation automatically eliminates common code injection problems. I intend to implement a &sql form that would avoid SQL injection the same way.

In closing

Some (biased) reasons why you might choose Kawa over other languages, concentrating on those that run on the Java platform: Java is verbose and requires a compilation step; Scala is complex, intimidating, and has a slow compiler; Jython, JRuby, Groovy, and Clojure are much slower in both execution and start-up. Kawa is not standing still: plans for the next half-year include a new argument-passing convention (which will enable ML-style patterns); full continuation support (which will help with coroutines and asynchronous event handling); and higher-level optimized sequence/iteration operations. I hope you will try out Kawa, and that you will find it productive and enjoyable.

Index entries for this article
GuestArticlesBothner, Per


to post comments

language-implementation startup times

Posted Dec 4, 2014 5:43 UTC (Thu) by idupree (guest, #71169) [Link] (5 responses)

Thank you for showing that the JVM doesn't doom us to 1.5-second startup times (Clojure is amazing...ly slow there).[1] I get 0.33 seconds for cache-warm Kawa running an empty file or 0.44 seconds for the file (display "Hello world\n"). For comparison:

The following languages compile+run Hello World in under 0.1 second for me: Guile Scheme, Common Lisp (sbcl, clisp), Python, Bash, Perl, Ruby, JavaScript, PHP, Lua, OCaml, C (GCC and Clang; for C but not C++), and – almost – Go and Ada.

Other language implementations I tested come in somewhere above 0.2 second to compile+run Hello World: C++ (g++, clang++), D (dmd or gdc), Java (OpenJDK), Rust, SML (mlton), Haskell (GHC), Clojure. I love some of these languages. The compile/startup time is a downside.

How limiting do you find the JVM dependency to be for startup time? A precompiled Java Hello World runs in about 0.06 seconds for me – could be better, could be worse, and I recognize that benchmarking Hello World is not representative of real code.

[1] tests run on CPU i7-2630QM, DDR3-1600 RAM, SSD disk, Arch Linux x86_64

language-implementation startup times

Posted Dec 4, 2014 8:33 UTC (Thu) by Per_Bothner (subscriber, #7375) [Link] (3 responses)

On my i7-4700MQ laptop running:
$ bin/time kawa -e '(format #t "hello~%")'
reports:
0.36user 0.02system 0:00.23elapsed 164%CPU (0avgtext+0avgdata 41304maxresident)

I find it difficult to understand why you care whether it takes 0.1 seconds or 0.3 seconds, or why 0.06 elicits a "could be better, could be worse". If you're a human starting up an application, half a second or less seems plenty fast enough. It's an issue if you write a shell script that calls lots of little applications each of which take more than half a second, but in that case you'd merge these little applications into a bigger program. Bringing up a complex GUI is a different matter, or course. OTOH if you're making a request to a server, you really don't want that to take half a second plus networking time, but in those situations you'd have a server already running.

In a previous life, I was the technical lead for the GCJ project, and in the past Kawa could be built and run on GCJ. We also supported compiling Scheme programs to native (going via Java bytecode). Even that wasn't immune to startup delays - for example you had shared library relocation. I've removed support for using GCJ because GCJ is no longer being actively maintained, and does not support newer Java features which I found useful for Kawa. (There is a fair amount of conditional code in Kawa that can be pre-processed for different Java platforms. Not that long ago I had conditional code to support as far back as JDK 1.1. However, I now require Java 5 or newer, to avoid cluttering up the source with code that never gets tested ...)

language-implementation startup times

Posted Dec 4, 2014 10:54 UTC (Thu) by dgm (subscriber, #49227) [Link] (1 responses)

> I find it difficult to understand why you care whether it takes 0.1 seconds or 0.3 seconds, or why 0.06 elicits a "could be better, could be worse".

One reason may be that when you extrapolate to slower systems, 0.1 seconds on an i7 could be the difference between acceptable and unbearable. I just ran your test on the Raspberry Pi I'm currently developing for:

 time java -jar kawa-2.0.jar -e '(format #t "hello~%")'
 hello

 real    0m7.720s
 user    0m7.260s
 sys     0m0.430s
That's almost 8 seconds, just to print "hello". For comparison:
 time (gcc -o hello hello.c && ./hello)
 hello
 real    0m1.967s
 user    0m0.520s
 sys     0m0.040s
And after that:
 time ./hello
 hello
 real    0m0.009s
 user    0m0.000s
 sys     0m0.000s
But also:
 time (javac Hello.java && java Hello)
 hello

 real    0m13.716s
 user    0m12.750s
 sys     0m0.860s
And then:
 time java Hello
 hello

 real    0m1.618s
 user    0m1.460s
 sys     0m0.150s
While the relation is not linear, Idupree's tests give me a hint of how usable would be Kawa for my project.

language-implementation startup times

Posted Dec 4, 2014 15:42 UTC (Thu) by Per_Bothner (subscriber, #7375) [Link]

One reason may be that when you extrapolate to slower systems, 0.1 seconds on an i7 could be the difference between acceptable and unbearable.

Point taken. On a slower system you might to use a different JVM, or perhaps a more limited version of Java, such as one that implements Java ME (rather than Java SE). These are designed for hardware that is slower or has less memory.

Specifically Android, which is sufficiently Java-like that I ported Java to it a few years ago. Android is of course designed for more resource-restricted environments than traditional Java (though these days a smart-phone is pretty powerful). The main limitation of Kawa for Android is that it currently doesn't support eval - everything has to be pre-compiled. This is because Android doesn't run Java bytecodes directly - Java .class files have to be translated to Dalvik ahead-of-time. It looks like it should be possible to fix this, either being by using a library to do the translation on the device, or to modify Kawa to generate Dalvik bytecode directly. However, I haven't looked into this.

I haven't experimented with Kawa on "micro" systems except for Android. However, if Kawa depends on some Java language or library feature that is not available on your platform of interest, I will happily work with you to avoid the dependency. The Kawa conditional-compilation framework (see the PreProcess class) is very useful for this.

language-implementation startup times

Posted Dec 28, 2014 8:00 UTC (Sun) by pcarrier (guest, #65446) [Link]

I've been weirdly interested in timing startup for various runtimes, and the resulting "project" is available at https://github.com/pcarrier/benchmarking-uselessness ; old results are visible as https://gist.github.com/pcarrier/3790598

I might run it again now that I have a Linux laptop running Arch Linux natively again.

I might try to add Kawa, but given the terrible timing of the JVM for a single Java class, I don't think it'll fare all that well. Could get interesting results with Avian or jamvm though (alternative runtimes for JVM class files).

For what it's worth Clojure and JRuby were kept out as significantly slower than everything else.

language-implementation startup times

Posted Dec 4, 2014 12:44 UTC (Thu) by cortana (subscriber, #24596) [Link]

Which JVM are you testing with? This is something of an aside, but I've found that Oracle's JVM builds are much faster to start up than Debian's OpenJDK builds. This reminds me to get around to filing a bug...

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 10:17 UTC (Thu) by peter-b (guest, #66996) [Link] (11 responses)

Am I right in thinking that Kawa doesn't support tail-call optimisation because it runs on the JVM? TCO is a required feature of conformant Scheme implementations...

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 11:25 UTC (Thu) by ms (subscriber, #41272) [Link] (3 responses)

I'm afraid I can't answer your question. However, other data points:

1. Tail calls are actually essential for object oriented languages too: http://www.eighty-twenty.org/2011/10/01/oo-tail-calls/

2. Scala does TCO for recursive functions (I'm hazy on the details: apparently not mutually recursive: http://programmers.stackexchange.com/questions/157684/wha...).

3. For some reason I thought TCO was going to be in Java 8 but I now can't find any evidence for that.

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 12:10 UTC (Thu) by peter-b (guest, #66996) [Link] (1 responses)

> 1. Tail calls are actually essential for object oriented languages too

They're not essential, but they are highly beneficial. :-) If proper tail call optimisation (PTC) was *required* for object oriented language implementations, then Python would have to provide PTC. The fact that Python doesn't is disproof by contradiction of the requirement, I think.

> 2. Scala does TCO for recursive functions

Yes; self-calls in the tail position can be optimised by the JVM (actually this is quite a trivial optimisation), but Scheme requires PTC. This enables a variety of very useful control structure-like syntax to be defined using Scheme's hygienic macro system.

> 3. For some reason I thought TCO was going to be in Java 8 but I now can't find any evidence for that.

I haven't really been keeping track of it; I'm not too keen on Java programming and I looked into Scala/Clojure a few years ago but decided my time was better spent playing with Guile Scheme instead!

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 12:22 UTC (Thu) by ms (subscriber, #41272) [Link]

> > 1. Tail calls are actually essential for object oriented languages too
> They're not essential, but they are highly beneficial. :-) If proper tail call optimisation (PTC) was *required* for object oriented language implementations, then Python would have to provide PTC. The fact that Python doesn't is disproof by contradiction of the requirement, I think.

My reading is that without tail calls, you are necessarily forced to break various abstraction, encapsulation and delegation core principles of OO. To what extent that means OO languages without tail calls are not "proper" OO languages is of course debatable. However, I have a personal bias against OO generally and have no intention of trolling so I'll stop here.

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 15:23 UTC (Thu) by Per_Bothner (subscriber, #7375) [Link]

3. For some reason I thought TCO was going to be in Java 8 but I now can't find any evidence for that.

The main Java enginers at Oracle (including John Rose) are very aware of the demand for TCO, and it has been expressed by some quite influential people (including Guy Steele and Doug Lea). There are some design issues: the kind of "tail-call optimization" needed can't be just an optimization. It needs to be part of the actual mandatory semantics, so it needs some specific new bytecode instruction or other machanism to inform the JVM when to do the "optimization" (and to fail when it can't). Working patches exist, but that is only a small part of changing the JVM.

Unfortunately, TCO is not in Java 8, and last I heard there has been no commitment yet for Java 9.

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 14:39 UTC (Thu) by tialaramex (subscriber, #21167) [Link] (3 responses)

I don't know enough about the guarantee required by Scheme to say whether it's met, but a language running on the JVM can arrange for a function not to waste stack when that function is tail-recursive. The sort of trivial examples you did in school (e.g. recursive factorial, and then adding memoization) can be optimised by a conforming JVM. Of course the Java standard does not require this optimisation, but you should be able to find an implementation that does it anyway.

What you aren't allowed to do in the narrowly interpreted standard is optimise out a mutual recursion. If functions A() and B() each call the other as a mutually recursive solution to a problem, the JVM ought to put something on the call stack for each call back and forth, which it will make available to third parties if an exception occurs. The third parties aren't required to understand mutual recursion or any other special cases, so there needs to be one call on the stack for each transition, and obviously a long-running mutually recursive function will blow out the stack doing that even if it has no other recursive state.

It would not be technically that difficult to "fix" this, except that it would violate the promises the JVM made to those third parties. What is my dull, non-recursive code supposed to see in the stack when, on the billionth time that control passed between third party mutually recursive functions A and B the B function attempted to divide by zero and an Exception is thrown back to my code?

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 15:32 UTC (Thu) by peter-b (guest, #66996) [Link] (2 responses)

I don't know enough about the guarantee required by Scheme to say whether it's met
Well, it's easy enough to refer to R6RS:
Implementations of Scheme must be properly tail-recursive. Procedure calls that occur in certain syntactic contexts called tail contexts are tail calls. A Scheme implementation is properly tail-recursive if it supports an unbounded number of active tail calls. A call is active if the called procedure may still return. Note that this includes regular returns as well as returns through continuations captured earlier by call-with-current-continuation that are later invoked. In the absence of captured continuations, calls could return at most once and the active calls would be those that had not yet returned. A formal definition of proper tail recursion can be found in Clinger's paper [5]. The rules for identifying tail calls in constructs from the (rnrs base (6)) library are described in section 11.20.
So from the sounds of it, by definition you cannot implement a conformant Scheme on a conformant JVM!
What is my dull, non-recursive code supposed to see in the stack when, on the billionth time that control passed between third party mutually recursive functions A and B the B function attempted to divide by zero and an Exception is thrown back to my code?
In the case of Guile Scheme, all TCO'd functions are removed from the trace. Let's try!
$ cat > recurse.scm << EOF
(define (a) (b))
(define (b) (c))
(define (c)
  (if (= 0 (random 10000))
      (error "Whoops")
      (a)))
EOF

$ guile -l recurse.scm
...
scheme@(guile-user)> (a)
ERROR: In procedure scm-error:
ERROR: Whoops

Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user)> ,bt
        0 (scm-error misc-error #f "~A" ("Whoops") #f)
scheme@(guile-user)>
As you can see, because the call to (error) was in a tail position, even the "if" expression was eliminated from the stack.

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 20:49 UTC (Thu) by tialaramex (subscriber, #21167) [Link] (1 responses)

Thank you, I was aware that R6RS existed but I wasn't familiar enough with it to know whether it would be worth trying to find this requirement without greater Scheme expertise than I have.

There we are then, as you say, it's not possible.

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 22:31 UTC (Thu) by Per_Bothner (subscriber, #7375) [Link]

You can certainly implement a conformant Scheme on the JVM - the issue is how difficult is it, and what are the implications - for example for performance.

As discussed in my earlier reply (continue reading), Kawa does offer proper tail-recursion as an option, but it's not the default.

Similarly, Kawa does not currently implement full continuations, and the "obvious" way to do so doesn't work on the JVM unless you simulate your own call stack, which is expensive. However, a student is working on a smarter implementation for his master's thesis, so I expect we'll have it by the end of the school year. (Even then it will probably not be the default, but will be controlled by a switch, because of performance concerns.)

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 15:15 UTC (Thu) by Per_Bothner (subscriber, #7375) [Link] (2 responses)

Kawa has two compilation modes: In default mode, the compiler will replace a call to a known function with a goto, as long as it get compiled in the same method. This is compiled with conservative inlining: Kawa only inlines when it doesn't cause code duplication, which means there can be one non-tail call plus any number of tail calls to the function. This is more general than simple tail-recursion: It can handle "state machine" patterns of mutual recursion.

In --full-tailcalls mode a tail-call (that hasn't been optimized as in the previous paragraph) is compiled to use a trampoline: The argument are evaluated and stored in a per-thread CallContext object. A reference to the called procedure is also stored in the CallContext, and then the current function returns, leaving it to the trampoline to call the function after the current call frame has been popped.

The --no-full-tailcalls mode is the default; partly for performance, and partly for smoother Java interoperability. The two modes are fully interoperable, and can co-exist even in the same source-file: a function compiled in one mode can call a function compiled in the other mode.

Now that Kawa 2.0 is out my primary focus on the calling convention and various related matters, including how to support ML-style patterns. This will change how trampolines work (only the specifics, not the general model), as well as the glue methods used for type-checking and type-conversion when calling an unknown function. It should allow some performance improvement, but more importantly some simplification, more flexibility, and more consistent handling of "multi-methods".

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 15:38 UTC (Thu) by peter-b (guest, #66996) [Link] (1 responses)

Cool -- thanks for the explanation!

> In default mode, the compiler will replace a call to a known function with a goto, as long as it get compiled in the same method.

By "in the same method," do you mean in the same compilation unit, or in the same mode?

> This is more general than simple tail-recursion: It can handle "state machine" patterns of mutual recursion.

I've implemented state machines using mutually-recursive coroutines in the past, so this is cool to have. :-)

Kawa's definitely on my list of things to play with. :-)

Kawa — fast scripting on the Java platform

Posted Dec 4, 2014 15:54 UTC (Thu) by Per_Bothner (subscriber, #7375) [Link]

> In default mode, the compiler will replace a call to a known function with a goto, as long as it get compiled in the same method.

By "in the same method," do you mean in the same compilation unit, or in the same mode

The former. I.e. the same class-method in the generated bytecode. The JVM has a goto instruction (not available to Java source code), which can jump to another instruction - but only in the same method. Also note that while the bytecodes are defined in terms of a stack architecture (i.e. there are instructions to push and pop values) the bytecode verifier restricts what you can do: the stack size and types are fixed, so in effect it's just a more compact register architecture, with implicitly-named registers.


Copyright © 2014, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds