JVM バイトコードへのコンパイル
-o で .class で終わる出力パスを rontolisp に渡すと、ソースをインタプリタで実行する代わりに JVM バイトコードへ直接コンパイルします。ASM などのライブラリは使わず、バイトコードは手作業で出力されます。バックエンドを選択するのは出力の拡張子です(JVM なら .class、WASM なら .wasm)。
echo '(print (+ 1 2))' > hello.lisp
rontolisp hello.lisp -o Hello.class
java Hello
生成されるクラスは出力ファイルにちなんで命名されるため、java に渡す名前はファイルの語幹(ステム)になります。-o Hello.class はクラス Hello を生成し、java Hello で実行します。クラス名が一致しなければならないため、パスにディレクトリを含めないでください(out/Hello.class ではなく、単純な Hello.class を使用します)。プログラムのトップレベルのフォームはクラスのエントリーポイントになり、起動時に順番に実行されます。
例(hello.lisp):
3
最適化(デッドコード除去)
デフォルトでは、コンパイルされたクラスはプログラムが実際に使用するものに関係なく ランタイム全体(プリンター、数値、リーダーおよび eval ヘルパーメソッド、さらにすべての組み込み関数のファーストクラスラッパー)を埋め込みます。--optimize を追加すると、main から到達できないすべてのメソッドを、それらだけが参照していた static フィールドとともに除去し、それに合わせて定数プールを圧縮します。
rontolisp fact.lisp --optimize -o Fact.class
java Fact
fact のような小さなプログラムでは、クラスは約 46 KB から約 4.6 KB に縮小します。このフラグはオプトインであり、動作を保存します。到達可能性はバイトコード中の実際の invoke 命令を辿るため、ファーストクラスの関数値、funcall、または組み込みの eval/load がディスパッチできるものはすべて保持され(ディスパッチメソッドは登録されたすべての関数を直接呼び出します)、java: 連携ブリッジのリフレクションによるエントリーポイントも明示的なルートとして残ります。同じフラグは WASM 出力のツリーシェイキングも行います。
--optimize とは独立に、コンパイルは常に同梱の Lisp ソースライブラリ(linalg:、vec:、JSON、URL、equalp/string<)をツリーシェイキングします。プログラムがソース中でその名前に一切言及しない(クォートされたシンボルや文字列リテラルの中も含む)ライブラリ関数はコンパイル結果に含まれません。その帰結として、実行時に計算した文字列から名前を組み立てて eval/apply 経由で呼び出すライブラリ関数は、通常の「undefined function」エラーを通知します。その場合は --no-prune(または --dynamic)を付けてコンパイルすると、すべてのライブラリ定義が保持されます。
生成される .class ファイルは Java 6(クラスバージョン 50)をターゲットとするため、そのバイトコードは JRE 6 以降であれば読み込めます。java.lang と java.io のほか、出力されるランタイムヘルパーは java.math(オーバーフロー時に昇格する整数演算と厳密な有理数演算のための BigInteger/BigDecimal/MathContext)と java.util(ArrayList/Arrays、およびハッシュテーブル用の HashMap)を参照します。これらはいずれも Java 6 にすでに存在します。例外の 1 つは rontolisp:fetch を呼び出すプログラムで、これは追加で java.net/java.net.http を参照するため、JRE 11 以降が必要です(汎用のプロミス操作 rontolisp:await / rontolisp:then / rontolisp:promisep も、プロミスを Java 8 以降に存在する java.util.concurrent のフューチャーとして表現します)。もう 1 つは java: 連携パッケージを使うプログラムで、コンパイラが (プロジェクト自身の Java リリースでコンパイルされた) リフレクションブリッジをクラスに埋め込むため、rontolisp をビルドした JRE と同等以上に新しい JRE が必要です。