(rontolisp) docs
← リファレンス

WASM ホスト関数 (rontolisp.wasm)

rontolisp.wasm は、コンパイルした WebAssembly モジュールとホストのあいだを渡るものを 宣言します。defimport はホストが提供する関数を var に束縛し、export(または defn の {:wasm/export ...} メタデータ)は var をホストに渡します。どちらも WASM ホスト境界ガイドのディレクティブ (rontolisp:wasm-import、rontolisp:wasm-export)にローワリングされるので、同じ宣言をした Clojure のモジュールと Common Lisp のモジュールは同じバイト列にコンパイルされます。 clojure.string と同じように require します。

NameExampleResult
rontolisp.wasm/defimport(defimport add {:from "host" :params [:int :int] :returns :int})add がホストの add を呼ぶ
rontolisp.wasm/export(export add10 {:params [:int] :returns :int})モジュールが add10 をエクスポートする
:wasm/export メタデータ(defn add10 {:wasm/export {:params [:int] :returns :int}} [n] ...)モジュールが add10 をエクスポートする
$ cat main.clj
(ns main (:require [rontolisp.wasm :as wasm]))

(wasm/defimport add {:from "host" :params [:int :int] :returns :int})

(defn add10 {:wasm/export {:params [:int] :returns :int}} [n]
  (add n 10))
$ rontolisp main.clj -o main.wasm --no-wasi
$ rontolisp host.clj -o host.wasm --no-wasi     # a module exporting "add"
$ wasmtime run --preload host=host.wasm --invoke add10 main.wasm 32
42

インタプリタと JVM では、エクスポートは普通の関数で、インポートには呼ぶ相手のホストが ありません。

42
add is a host function (rontolisp.wasm/defimport): only a compiled WASM module can call it

型

型キーワードは境界のものです。:int、:long、:s8 ... :u64、:float、:bool、 :string、:s-expr、:bytes、:octets、:extern(最後の二つはインポートのみ)、それに 結果用の :void(:returns を書かない宣言はこれを意味します)。Clojure と境界とで綴りが 異なる値は、渡るときに変換します。

型ホストへホストから
:boolfalse と nil は偽として渡る偽は nil ではなく false として届く
:s-exprpr-str が出力するテキストread-string が読む値
:bytesバイト配列のオクテット。それ以外の値は ClassCastException を投げるバイト配列
:octets:bytes と同じバイト配列

したがって :s-expr を通ると、ベクタ、マップ、キーワード、false はそのまま往復します。 :s-expr はどのホストでも :string と同じ UTF-8 テキストとして渡ります。これらの型を どれも含まない宣言は、Common Lisp のソースが書くディレクティブとまったく同じものに ローワリングされます。:async は拒否します。サスペンドする境界越えが返す future は、 まだ Clojure の future に対応していないためです。

:bytes は生のオクテットを転送するので、:string なら UTF-8 として復号されるところを ff は ff のまま渡ります。結果としての :bytes は呼び出す側のバッファです。:bytes を 答えるインポートは、宣言した引数のあとにホストが書き込むバイト配列を受け取り、値の全長を 答えます。配列の大きさを超える長さは、配列が小さすぎたことを意味します。:bytes を答える エクスポートはバイト配列を答え、ホストはそれを自分が渡すバッファへ読み込みます。

$ cat bin.clj
(ns bin (:require [rontolisp.wasm :as wasm]))

(wasm/defimport read-chunk {:from "host" :as "readChunk" :params [:int] :returns :bytes})

(defn checksum {:wasm/export {:params [:bytes] :returns :int}} [data]
  (reduce + (map #(if (neg? %) (+ % 256) %) data)))

(defn first-chunk {:wasm/export {:as "firstChunk" :params [:int] :returns :string}} [id]
  (let [buf (byte-array 4096)
        n (read-chunk id buf)]
    (String. buf 0 (min n 4096) "UTF-8")))
$ rontolisp bin.clj -o bin.wasm --no-wasi --emit-js-glue

Common Lisp の :bytes 型と同じく、渡れるのは GC バックエンドの WASM コアモジュールだけです。 インタプリタと JVM では、:bytes を答えるインポートもバイト配列を受け取るスタブです。

:octets は同じオクテットを値として渡します。:octets を答えるインポートは、渡すバッファを 取らず、ホストが返したオクテットをそのまま持つバイト配列を答えます。 rontolisp.wit が WASM コアモジュールで WIT の list<u8> を宣言する型がこれです。

動くターゲット

ターゲットdefimportexport
WASM コアモジュール(-o out.wasm、--no-wasi)モジュールのインポートモジュールのエクスポート
--componentrontolisp:wasm-import が拒否する。コンポーネントは WIT インターフェースを呼ぶ(rontolisp.wit)型付きのコンポーネントエクスポート
インタプリタ、JVMUnsupportedOperationException を投げる関数なし。関数は呼べるまま

--emit-js-glue は、Common Lisp のモジュールと同じく --no-wasi モジュールの境界の JavaScript 側を書き出します。生成されたファイルを node から使うドライバを含む一式は examples/clojure/host-boundary/ にあります。