(rontolisp) docs
← rontolisp パッケージの関数

rontolisp:wasm-import

(rontolisp:wasm-import 'name :from "module" :as "field" :params '(type...) [:param-names '(name...)] :returns type [:async t])

WASM ホスト (ブラウザの JavaScript、または wasmtime にプリロードされた別の モジュール) が提供する関数を宣言し、name という名前でトップレベルの defun とまったく同じように Lisp から呼び出せるようにします — #'name、funcall、 mapcar、eval も使えます。これは通常の関数ではなくコンパイル時の ディレクティブです。インタプリタおよび JVM バックエンドでは、呼び出すと エラーを通知するスタブを定義する (呼び出すべきホストが存在しない) ため、同じ ソースはすべてのバックエンドでロードできます。詳細は WASM ホスト境界ガイド を、完全なブラウザ プログラムは WebGL galaxy example を参照してください。

引数

  • Lisp から見える関数名を指すクォートされたシンボル。defun 名と同じように 現在のパッケージで解決されるため、(in-package mylib) の後のディレクティブは mylib:name を定義します。
  • :from — インポートモジュール名 (JavaScript 側のインポートオブジェクトの キー、wasmtime では --preload 名)。デフォルトは "env"。
  • :as — インポートフィールド名 (そのモジュールオブジェクト内のプロパティ)。 デフォルトは (パッケージ修飾子を除いた) 素の Lisp 名。
  • :params — 各引数に対応する境界型指定子のリスト。省略、nil、'() の場合は 引数なしを意味します。
  • :param-names — コンポーネントモデルのシグネチャにおけるパラメータ名。 :params の各項目に 1 つずつ、シンボルまたは文字列で指定します。デフォルトは p0、p1、…。読むのは --no-gc --component だけで、そこではインポートされる関数の型のラベルになります (インポートが WIT world を表すときはその名前)。コアモジュールのインポートにパラメータ名は ありません。
  • :returns — 戻り値の境界型指定子。省略、nil、'()、:void の場合は void の 戻り値 (Lisp は nil を受け取る) を宣言します。

型指定子は rontolisp:wasm-export と共通です。

DesignatorWASM boundaryNotes
:inti3231-bit signed range (the internal i31ref)
:floatf64an int or ratio argument is converted like the arithmetic built-ins
:booli32nil crosses as 0, anything else as 1; a non-zero result reads back as t
:string(ptr, len)UTF-8 bytes in linear memory
:s-expr(ptr, len)the argument is printed to readable text; a result is parsed by the embedded reader
:bytes(ptr, len) argument / (ptr, cap) -> len resultan (unsigned-byte 8) vector as raw bytes — no UTF-8 in either direction
:octets(ptr, len)imports only: an (unsigned-byte 8) vector as a value, raw bytes both ways; an argument may also be a string (its UTF-8 bytes)

:string/:s-expr の引数は、呼び出しがすでに保持しているメモリへの (ptr, len) ビューです — 読むのは構いませんが、そこへ書き込んではいけません。 バックエンドごとにポインタの正体が何で、なぜ書き込みが安全でないかは 境界ガイドを 参照してください。

:string の戻り値は、ホストがリニアメモリに書き込み (バッファはエクスポート された __ronto_alloc で確保)、(ptr, len) のペア (JavaScript からは要素数 2 の 配列) として返す必要があります。

:bytes の結果は呼び出し側バッファ方式 (read(2) の形) です: Lisp シグネチャの末尾に受信用の (unsigned-byte 8) ベクタが 1 つ加わり、ホスト 関数は末尾の (ptr, cap) ペア付きで呼ばれます — ptr に最大 cap バイトを 書き込み、値の全長を返す。Lisp の呼び出しはその全長を返すため、バッファより 長い結果は黙った切り詰めではなく、より大きいバッファでのリトライになります。 ラッパーのステージングは返却時にポップされるため、1 つのバッファを使い回す pull ループはリニアメモリをフラットに保ちます。

:octets の結果は生のバイトに対する :string の形です: ホストは __ronto_alloc でバッファを確保してオクテットを書き込み、(ptr, len) を返します。 呼び出しはそのオクテットだけを持つ新しい (unsigned-byte 8) ベクタを返します — デコードも受信バッファもありません。 rontolisp:wit-import :octets t が Preview 1 で WIT の list<u8> を宣言する型がこれです。

:async t — サスペンドしうるホスト関数

:async t は、ホストがこの関数を非同期に実装しうること — JavaScript ホストでは WebAssembly.Suspending でラップされた関数 (JSPI) — を宣言します。 呼び出しは rontolisp:await で解決できる future を 返すため、境界が非同期であることを呼び出し側のソースが語れます — このバックエンド ではまさにこのオプションに脱糖される rontolisp:wit-import の async func メンバーと同じ読みです。(この語は意図的に rontolisp:wasm-export の :async と揃えて あります。WIT は両方向とも async func と綴り、方向はディレクティブ自体が 示します。)

  • このバックエンドでは future は生成時点で settled です。ホスト呼び出しは wasm スタックをブロックする (同期的に、または JSPI でサスペンドして) ため、 呼び出しが返った時点で値は用意されており、await が実際にサスペンドすることは ありません。このオプションが買うのはどのバックエンドでも同じに読めるひとつの ソースであり、並行性ではありません。
  • ビルドはホストが負う義務を出力します: インポートを WebAssembly.Suspending でラップし、そこへ到達しうるすべてのエクスポート (ビルドが列挙します) を WebAssembly.promising 経由で呼び出し、呼び出しを直列化する — サスペンドしたモジュールは再入されうるためです。再入されたエクスポートは 両方の呼び出しを静かに壊す代わりにトラップで拒否します (サスペンドしうる モジュールのすべてのエクスポートラッパーが再入ガードを持ちます。ただし --reentrant でコンパイルした場合は例外で、JSPI ホストは呼び出しをオーバーラップできます)。 同期的に応答するホストも同様に有効で、その場合も呼び出しは settled 済みの future を返します。--emit-js-glue はその半分を説明する代わりに書き出します (ホスト境界ガイド)。 ホストに残るのは各関数が何をするかと、そのうちどれがサスペンドするかの宣言 だけです。
  • --no-wasi では、トップレベルフォームから到達しうる呼び出しは コンパイルエラーです。_initialize は promising が入っていない スタックで実行されるため、そこでのサスペンドは誰の名前も出さずにトラップ します。呼び出しをエクスポートの背後へ移すか、ホストが同期的に応答するなら :async t を外してください。

制限事項

  • デフォルト (wasm-GC) バックエンドではコアモジュール専用です。--component は このディレクティブをエラーで拒否します (コンポーネントはインターフェースを rontolisp:wit-import で束縛します)。インタプリタ および JVM では、宣言した名前を呼び出すとエラーを通知します。
  • --no-gc は このディレクティブを受け付けます。型の語彙は独自で、固定幅整数のファミリ全体 (ハウス整数が i64 のため)、:float、:bool、:string、:void を運びます。 :s-expr、:bytes、:octets、:async t は運べません。 --no-gc --component では到達するインポートがコンポーネントのインポートになるため、:from は lower-kebab-case のラベルか WIT インターフェース id、:as と :param-names の各項目は lower-kebab-case のラベルでなければなりません。
  • ディレクティブは defun と同様、使用前にトップレベルに置く必要があります。
  • コンパイルしたモジュールのインスタンス化には、ホストが宣言済みのすべての インポートを提供する必要があります。wasmtime run ではインポートモジュール名 ごとに --preload <module>=<file>.wasm が必要で、JavaScript ホストは インポートオブジェクトを渡します。
  • 引数は最大 10 個です (WASM バックエンド全般のアリティ制限)。