rontolisp:jvm-export
(rontolisp:jvm-export 'name :params '(type...) :returns type :as "javaName")
コンパイルされた .class に、型付きの Java から呼び出し可能なエントリ
ポイントを宣言します。JVM バックエンドはコンパイルされた defun の隣に、
プリミティブ/String/byte[] シグネチャを持つ public static メソッドを
生成し、境界で Java の値と内部表現を相互変換します。これは
rontolisp:wasm-export の JVM 版の双子です —
同じディレクティブの形、同じ型語彙で、同じ問題を解きます: コンパイルされた
defun の型なしメソッドは、ホストが安全に構築できない内部表現を受け取り、
返します。これは通常の関数ではなくコンパイル時のディレクティブです。
インタプリタでは名前付きシンボルをそのまま返す no-op となり、WASM
バックエンドはこれをスキップするため、同じソースがすべてのバックエンドで
動作します。詳細は JVM ライブラリのエクスポート
を参照してください。
-o Fact.class でコンパイルすると、クラスは型なしの FACT(Object) の隣に
次を持ちます。
public static long fact(long n);
引数
- エクスポートするトップレベル
defunを指すクォートされたシンボル。defun名と同じように現在のパッケージで解決されます。 :params— 各引数に対応する境界型指定子のリスト。省略、nil、'()の場合は 引数なしを意味します。:returns— 戻り値の境界型指定子。省略、nil、'()、:voidの場合はvoidメソッド (Lisp の戻り値は破棄される) を宣言します。:as— Java メソッド名の文字列。デフォルトは Lisp 名を lower-camel-case に 変換したもの (scaled-sumはscaledSumになる) で、これは Java の呼び出し側が 期待する綴りです。名前は有効な Java 識別子 (かつ Java キーワード以外) で なければなりません。変換結果が識別子にならない Lisp 名 (*scale*、string=) は:asでのリネームが必要です。クラス上の既存メソッドと重複する名前 — 別のエクスポートの名前や、コンパイルされたdefun自身のメソッド名 — は コンパイル時に拒否されます。
型指定子は rontolisp:wasm-export が受け付けるのと同じ語彙で、Java の型に
対応付けられます。
| Designator | Java parameter/return type | Notes |
|---|---|---|
:s8 :s16 :s32 :s64 | byte short int long | :int / :long は :s32 / :s64 の恒久的な別名。範囲は Java と一致するためガード不要 |
:u8 :u16 | int | 宣言された全範囲を運べる最小の慣習的な Java 型。範囲外の値はスローする |
:u32 :u64 | long | 2^63 以上の :u64 値はスローする (バックエンドが計算に使う符号付き 64 ビット整数に正確な表現を持たないため) |
:float | double | rontolisp に単精度浮動小数点数はない |
:bool | boolean | false が nil、true が t。nil 以外の結果はすべて true |
:string | String | ラッパーが内部文字列表現と相互変換する |
:s-expr | String | S 式テキスト: 入りは read、出は印字 (prin1 形式) — 関数以外の任意の値 |
:bytes | byte[] | (unsigned-byte 8) ベクタを生のバイト列として、双方向でコピーして運ぶ |
:float-vector | RontoFloatArray | ランク 1 のパック済み float 配列 (linalg:/vec:)。エイリアスされる — 下記参照 |
:float-matrix | RontoFloatArray | 同じハンドルのランク 2 版 |
境界は値を正確に運ぶか、さもなければスローします — WASM 境界と同じ
「マスクせずトラップする」規則です。宣言された型が表せない引数 (:u8 への
300、:u64 への負の long) は IllegalArgumentException を、宣言された
範囲の外の結果は ArithmeticException を、そもそも種類の違う結果 (関数が
数値を返した :string エクスポート) は ClassCastException をスローします。
何も暗黙にラップ・マスク・誤デコードされません。
パック済み float 配列のハンドル
:float-vector と :float-matrix はどちらも
am.ik.rontolisp.runtime.RontoFloatArray として渡ります。これはパック済み
float 配列の表現そのものを保持するハンドルで、of(double[]) /
of(float[]) が一度だけそこへコピーし、get/set/size/dims はその場で
添字アクセスし、toArray() が一度だけコピーして返します。その間の受け渡しは
変換ではなく参照です: 素の double[] を引数型にすると、黙って誤読され
うる (パック済み配列は次元ヘッダを持つので、ただの Java 配列はそれではない)
うえに、与えるカーネルのおよそ 10 倍の費用がかかります。
明示しておくべき帰結:
- 返されたハンドルは Lisp 側の配列をエイリアスします。 ハンドル経由の 書き込みは同じ配列を閉じ込めた Lisp のクロージャから見え、その逆も同様です。 防御的コピーは一切しません。
- 要素幅はどちらも 1 つの指定子を使います。
double-floatとsingle-floatの配列は互いに素な表現です。どちらかはwidth()が答え、 アクセサはどちらでもdoubleで読み書きします —arefとまったく同じです。 - ランクは境界で検査されます。
:float-vectorはランク 1 を、:float-matrixはランク 2 を受け取り返します。それ以外はスローします。 - ハンドルのクラスファイルはコンパイル済みクラスと一緒に運ばれます。
クラスの隣の
am/ik/rontolisp/runtime/に書き出されるので、成果物は 依存関係を持ちません。 - WASM にはこれを運ぶ手段がありません:
rontolisp:wasm-exportは 両指定子を名指しで拒否します。
計測と destination-passing のパターンは JVM ライブラリのエクスポート を参照してください。
トップレベルはクラス初期化時に実行される
型付きメソッドがクラスへの最初の呼び出しになり得るため、エクスポートを持つ
クラスはトップレベルのフォーム (defvar/defparameter の初期化を含む) を
main ではなくクラスイニシャライザで — JVM がクラスに最初に触れたとき一度
だけ — 実行します。これは --no-wasi リアクタの「インスタンス化時に
トップレベルを実行する」の JVM 版であり、帰結も同じ 2 つです:
シグナルするトップレベルフォームは Java の呼び出し側に
ExceptionInInitializerError として現れ、クラスを恒久的に汚染します。また
トップレベルの (uiop:quit ...) は呼び出し側の JVM を終了させます。main
(を残した場合) も引き続きプログラムを一度だけ実行します — main の呼び出しは
まずクラス初期化をトリガーし、JVM 自身の初期化ロックが冪等性になります。
エクスポートは --optimize の下でも残る
デッドコード除去 (デフォルトで ON) は main から到達可能なすべてのメソッドを
残します。各エクスポートは追加のルートなので、Java からしか呼ばれない
ライブラリも --optimize=off を必要とせず、デフォルトのサイズのまま生き残り
ます。
JVM バイトコードへのコンパイル
を参照してください。
--no-main
純粋なライブラリには main は不要です: --no-main でコンパイルすると、
クラスはエクスポートを通してのみ呼び出されます (このフラグは最低 1 つの
エクスポートを要求します — それがなければ main が唯一のツリーシェイカー
ルートだからです)。WASM リアクタを名指しする --no-wasi の双子です。
JVM ライブラリのエクスポート を参照してください。
制限
- エクスポートできるのは固定引数のトップレベル
defunのみです: 宣言した 引数の数はそのアリティと一致する必要があり、&optional/&rest/&keyを 取るラムダリストは拒否されます (固定の Java シグネチャを持たないため)。 固定アリティのdefunでラップしてそれをエクスポートしてください。 - パック済み float 配列が渡れるのはランク 1 か 2 のみです。一般 (ボックス化)
配列には指定子がなく、float 以外の配列は今も
:bytesだけです。 - ディレクティブは
wasm-exportと同様トップレベル専用です。