Cross-Compiling Common Lisp to WASM

2025/11/28 18:23

Cross-Compiling Common Lisp to WASM

RSS: https://news.ycombinator.com/rss

要約

WebAssembly で Common Lisp(WECL)を動かすには、①ECLをホスト版→ターゲットへクロスコンパイルし、emccでWASMモジュールを生成。②WECLはそのランタイムとCレイヤーを組み合わせ、JS‑FFI・REPL・Emacs接続機能を提供する。③ユーザーコードも同様のツールチェーンでコンパイルし、静的ライブラリとしてリンクできる。主要ポイント:クロスビルド手順、WECL構成とインタフェース、ASDF拡張による自動コンパイルとキャッシュ機能。

本文

WebAssembly で Common Lisp を使う(WECL)

著者: Daniel Kochmański
公開日: 2025‑11‑28

目次

  1. ECL のビルド
  2. WECL のビルド
  3. ユーザーコードのビルド
  4. ASDF を拡張する
  5. 資金調達

ECL のビルド

ECL(Embeddable Common Lisp)を WebAssembly 用にコンパイルするには、まずホスト版を構築し、その後でターゲットへクロスコンパイルします。

git clone https://gitlab.com/embeddable-common-lisp/ecl.git
cd ecl
export ECL_SRC=$(pwd)
export ECL_HOST=${ECL_SRC}/ecl-host

./configure --prefix=${ECL_HOST} && make -j32 && make install

Emscripten SDK のインストール

git clone https://github.com/emscripten-core/emsdk.git
pushd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
popd

WebAssembly ターゲットのビルド

make distclean   # build/ ディレクトリを削除
export ECL_WASM=${ECL_SRC}/ecl-wasm
export ECL_TO_RUN=${ECL_HOST}/bin/ecl

emconfigure ./configure \
  --host=wasm32-unknown-emscripten \
  --build=x86_64-pc-linux-gnu \
  --with-cross-config=${ECL_SRC}/src/util/wasm32-unknown-emscripten.cross_config \
  --prefix=${ECL_WASM} \
  --disable-shared \
  --with-tcp=no \
  --with-cmp=no

emmake make -j32 && emmake make install

生成されたランタイムファイルをコピーします。

cp build/bin/ecl.js build/bin/ecl.wasm ${ECL_WASM}

ホストの実行

Quicklisp と

hunchentoot
が必要な小さな Web サーバスクリプトで、ファイルを配信できます。

export WEBSERVER=${ECL_SRC}/src/util/webserver.lisp
${ECL_TO_RUN} --load $WEBSERVER
# その後、ブラウザで firefox localhost:8888/ecl-wasm/ecl.html を開く

Node.js で一度だけ評価することも可能です。

node ecl-wasm/ecl.js \
  --eval '(format t "Hello world!~%")' \
  --eval '(quit)'

生成された

.wasm
は、Emscripten が追加したインポート(例:
invoke_iii
)のために他のランタイムでは移植性がありません。WasmEdge で実行すると次のようなエラーになります。

[error] instantiation failed: unknown import, Code: 0x62
...

WECL のビルド

WECL はクロスコンパイルされた ECL ランタイムを、C 言語レイヤーと組み合わせて提供します。C レイヤーは WASM モジュールをロードし、JavaScript との相互作用、REPL サポート、および Emacs 接続機能を実装しています。

fossil clone https://fossil.turtleware.eu/wecl
cd wecl

アーティファクトと SLIME フォークを

Code/
にコピーします。

pushd Code
cp -r ${ECL_WASM} wasm-ecl
git clone git@github.com:dkochmanski/slime.git
popd

ビルドして起動します。

./make.sh build
./make.sh serve

Emacs へ接続するには

App/lime.el
を評価し、
(lime-net-listen "localhost" 8889)
を実行してください。
ブラウザで
<http://localhost:8888/slug.html>
を開き Connect ボタンをクリックします。

リポジトリ構成

ファイル用途
Code/packages.lisp
JS 相互作用関数用パッケージ
Code/utilities.lisp
早期ユーティリティ(例:
when-let
Code/wecl.lisp
JS‑FFI、オブジェクトレジストリ、コンソールストリーム
Code/jsapi/*.lisp
JavaScript オブジェクトとメソッドへのバインディング
Code/script-loader.lisp
HTML に埋め込まれた Common Lisp スクリプトをロード

典型的なエントリポイント:

  • main.html – REPL と xterm コンソールを読み込み
  • easy.html – JS ↔ CL の相互作用例
  • slug.html – Emacs に接続するボタン

最小 HTML スケルトン

<!doctype html>
<html>
  <head>
    <title>Web Embeddable Common Lisp</title>
    <script src="boot.js"></script>
    <script src="wecl.js"></script>
  </head>
  <body>
    <script type="text/common-lisp">
      (loop for i from 0 below 3
            for p = (|createElement| "document" "p")
            do (setf (|innerText| p) (format nil "Hello world ~a!" i))
               (|appendChild| "document.body" p))
    </script>
  </body>
</html>

Tip:

lispify-name
ヘルパーを使うと、JavaScript の演算子やアクセサの Lispy バインディングを自動生成できます。


ユーザーコードのビルド

ユーザーコードのクロスコンパイルは ECL と同じツールチェーンを使用します。
以下に例としてソースを示します。

;;; test-file-1.lisp
(in-package "CL-USER")
(defmacro twice (&body body) `(progn ,@body ,@body))

;;; test-file-2.lisp
(in-package "CL-USER")
(defun bam (x) (twice (format t "Hello world ~a~%" (incf x))))

(defvar *target*
  (c:read-target-info "/path/to/ecl-wasm/target-info.lsp"))

(with-compilation-unit (:target *target*)
  (compile-file "test-file-1.lisp" :system-p t :load t)
  (compile-file "test-file-2.lisp" :system-p t)
  (c:build-static-library "test-library"
                          :lisp-files '("test-file-1.o" "test-file-2.o")
                          :init-name "init_test"))

生成された

libtest-library.a
は WECL にリンクできます。
Emscripten の呼び出しに追加し、
Code/wecl.c
から
init_test
を呼び出します。

extern void init_test(cl_object);
ecl_init_module(NULL, init_test);

ASDF を拡張する

以下のモジュールは WebAssembly ターゲット用に静的ライブラリを生成するクロスコンパイル機能を追加します。

パッケージと変数

(defpackage "ASDF-ECL/CC"
  (:use "CL" "ASDF")
  (:export "CROSS-COMPILE" "CROSS-COMPILE-PLAN" "CLEAR-CC-CACHE"))
(in-package "ASDF-ECL/CC")

(defvar *host-target* (c::get-target-info))
(defparameter *cc-target* *host-target*)
(defparameter *cc-cache-dir* #P"/tmp/ecl-cc-cache/")

オペレーション

(defclass cross-object-op (downward-operation) ())
(defclass cross-compile-op (sideway-operation downward-operation) ())
(defclass cross-load-op (non-propagating-operation) ())

cross-object-op
は個別のソースファイルをオブジェクトファイルにコンパイルします。
cross-compile-op
はそれらを集めて静的ライブラリを作成し、
cross-load-op
はターゲット環境での依存関係ロードを処理します。

ヘルパーマクロと関数

(defmacro with-asdf-compilation-unit (() &body body)
  `(with-compilation-unit (:target *cc-target*)
     (flet ((cc-path ()
              (merge-pathnames "**/*.*"
                               (uiop:ensure-directory-pathname *cc-cache-dir*))))
       (let ((asdf::*output-translations* `(((t ,(cc-path)))))
             (*load-system-operation* 'load-source-op)
             (*features* (remove-duplicates
                          (list* :asdf :asdf2 :asdf3 :asdf3.1 *features*))))
         ,@body))))

(defun cross-compile (system &rest args
                      &key cache-dir target load-type &allow-other-keys)
  (let ((*cc-cache-dir* (or cache-dir *cc-cache-dir*))
        (*cc-target* (or target *cc-target*))
        (*cc-load-type* (or load-type *cc-load-type*)))
    (apply #'operate (make-operation 'cross-compile-op) system args)))

(defun cross-compile-plan (system target)
  (with-asdf-compilation-unit ()
    (map nil
         (lambda (a)
           (format t "~24a: ~a~%" (car a) (cdr a)))
         (asdf::plan-actions
          (make-plan 'sequential-plan 'cross-compile-op system)))))

使い方

(cross-compile "flexi-streams" :target *wasm-target*)
;; → #P"/tmp/ecl-cc-cache/libs/flexi-streams-20241012-git/libflexi-streams.a"

生成されたライブラリには

flexi-streams
とその依存関係である
trivial-gray-streams
のオブジェクトが含まれます。
キャッシュは変更されたファイルのみを再コンパイルすることでビルド時間を短縮します。


資金調達

本プロジェクトは NGI0 Commons Fund によって資金提供されています。この基金は NLnet が設立し、欧州委員会の Next Generation Internet プログラムからの財政支援を受けています。
詳細については NLnet のプロジェクトページ をご覧ください。

同じ日のほかのニュース

一覧に戻る →

2025/12/04 3:40

Ghostty is now non-profit

Ghostty は501(c)(3)非営利団体 Hack Club の財務スポンサーシップを受け、税優遇とコンプライアンスを確保しつつ無料・オープンソースで提供されます。 重要ポイント 1. **持続可能性**:個人依存から脱却し、寄付で運営を安定化。 2. **信頼性**:非営利体制により資金の乱用や商業転売が防止。 3. **公共利益**:ターミナル技術を公益優先で発展させ、広範な採用促進。

2025/12/03 5:33

Valve reveals it’s the architect behind a push to bring Windows games to Arm

SteamがArmチップ向けPCゲームの移植を支援し、Steam Frameは実質的にAndroidデバイスやノートPCでSteamを遊べるトロイの木馬。FexとProtonがx86コードをARMへJIT変換し、開発者は移植作業を減らせる。重要ポイント 1. ValveはArm向けオープンソース技術に資金提供している。 2. Fex+ProtonでWindowsゲームをスマホやノートPC上で実行可能。 3. Steam Frameは「VRヘッドセット」ではなく、ArmデバイスでSteam体験を拡張するためのハードウェア。

2025/12/04 2:44

Reverse engineering a $1B Legal AI tool exposed 100k+ confidential files

**要約(300字以内)** FilevineのAI法務プラットフォームで、サブドメイン `margolis.filevine.com` にアクセスすると、Box API管理者トークンが返る脆弱性を発見。1) **発見と報告**:2025年10月27日から責任ある報告を行い、Filevineは迅速に修正。2) **技術的詳細**:エンドポイント `/prod/recommend` に `{"projectName":"Very sensitive Project"}` を送るだけで、全Boxファイルシステムへの完全アクセス権が得られた。3) **リスクと教訓**:機密文書やHIPAA保護資料を数百万件抽出可能となり、法律事務所・クライアントに深刻被害。AI法務テック企業はデータ保護体制を徹底すべきである。