C/C++でWASMをサクッとやりたい

少し前にWASMの開発をやることがあって(maplibre-gl-layersの話を参照)、そこでWASMのコードを書きました。 WASMと言っても、今だとRustを使うことが多いのかもしれませんが、私はまだRustをやっていない(Linux kernelが置き換わるまでには...)のと、 C/C++には慣れているので、C/C++でWASMコードを書きたいと思いました。

実際のところ、開発時間の問題であまり悠長にコードを書いているわけにも行かなかったので、今回は(も)諦めたという

それで、C/C++でWASMのコードを書く場合は、 Emscripten SDK を使うのが定番ということで、 そのまま採用することにしたのです。 ただ、C/C++で複雑なコードを書くわけではなく、今回は殆ど座標計算を大量に高速にやらせたいというのが目標だったので、 ぶっちゃけ、C/C++のエコシステム、具体的には外部ライブラリなどを使う可能性は殆どありませんでした。 せいぜい、std 名前空間のコレクションなどを使うぐらいでしょう。

そういうわけで、WASM開発でも恐らく定番となる CMake は使わず、殆ど直に生書きという選択肢を取りました。 そこまでシンプルなら、Makefileでも良いんじゃないのと言うつもりだったのです。

環境セットアップの問題

ただ、環境整備が面倒なんですよね。と言っても、Emscripten SDKを使う場合は、リポジトリをcloneしてきてセットアップスクリプトを実行すれば、 ホスト環境で必要なtoolchainをダウンロードしてきてくれるので、何が面倒なんだよ configure もしないじゃん、というツッコミはあるかもしれません:

git clone https://github.com/emscripten-core/emsdk
./emsdk install latest
./emsdk activate latest

仕事で使うので、環境再現性が高くないと色々辛く(例えばCI)、あまり手動セットアップで便利でもそれはそれで困ってしまいます。 もしこれをCIでやる場合は、Emscripten SDKをどこにcloneすれば良いのか(あるいはしなければならないのか)、 スクリプトがどのように環境を整備するのか、ということを把握していないと問題が起きそうです。

一度だけで済めば良いが...

そういうわけで、これをできるだけ簡潔な環境で簡潔な手順で再現性のある方法で実現しておかないと、色々なことをやっている自分には覚えておける記憶スペースがなく、 後で「これどうするんだっけ」みたいに激しく後悔することになります。

Makefileはまあシンプルですが、Emscriptenの全体的になんとかしてシンプルにしたい。楽にやりたい。 そこで、これらの問題をひっくるめて簡潔に完結させる何かが必要だと感じで、それなら作るかそこまで大変でもなさそうだし(フラグ)、と考えたやつです。

emsdk-env

で、作りました。 "emsdk-env" です。

TypeScriptの開発を始めてからネックだったことの一つは、ビルドプロセスのライフサイクルの標準的な環境が存在しないことです。 NPMのscript定義があるだろう、と言われそうで、やれなくもないですがその辺り弱すぎるので、今はViteをその目的に使っています。

Viteを前提にすれば、プラグインシステムでかなり柔軟にビルドプロセスに介入できます。 今回のような目的にぴったりです。

ただ、世間一般では、ViteはVueやReactのフロントエンド開発で使われているようで、本当は少し毛色が違うとは感じています。 特に自分はフロントエンド開発は片手間のような感じなので余計に。 しかし、他に良い選択肢も無いので、ちょっと無理がありますがすべてのTS開発でViteを採用しています。

つまり、Node.jsを使うようなプロジェクトでも、Viteを使ってビルドしています。 まだ色々課題はありますが、ElectronでもViteを使おうとしています。 これと全く同じ流れで、既に様々なViteプラグインを作っていますが、それらの解説はまだ今度。

話をemsdk-envに戻すと、これは、誤解を恐れずに言えば、Viteプラグイン化した"make"です:

  • Emscripten SDKの自動セットアップ・キャッシュ
  • Viteプラグインによる、HMR対応(但しC/C++コードは全体ビルドが行われます)
  • 並行ビルド対応
  • エクスポートシンボルの簡易指定が可能
  • 複数のターゲットWASMバイナリを生成可能
  • ディレクトリパス・コンパイルオプション・リンカオプションのカスタマイズが可能
  • アーカイブライブラリ(*.a)のビルドと参照が可能
  • NPMパッケージでWASMライブラリの配布と参照が可能

どうやってWASMコードを実行できるか

こんなリストよりも、ビルド定義をどう書くかの方が、一目瞭然でしょう。 まず、インストールして:

npm install -D emsdk-env

vite.config.ts にプラグイン構成を加えます:

// `vite.config.ts`
import { defineConfig } from 'vite';
 
// emsdk-envのViteプラグインを参照
import emsdkEnv from 'emsdk-env/vite';
 
export default defineConfig({
  plugins: [
    // プラグインとして追加
    emsdkEnv({
      // WASMローダーコードを生成
      generatedLoader: { enable: true },
      // ビルドターゲット
      targets: {
        // "add.wasm"を生成
        add: {
          // コンパイルオプション
          options: ['-O3', '-std=c99'],
          // リンクオプション
          linkOptions: ['--no-entry'],
          // リンクディレクティブ
          linkDirectives: { STANDALONE_WASM: 1 },
          // エクスポートシンボル
          exports: ['_add'],
        },
      },
    }),
  ],
});

targets の辺りが、だいたい Makefileのように機能しますが、とにかく楽に最小限の定義で使えるように工夫してあります。 とは言ってもWASMでEmscripten SDKを使う場合のお約束、 STANDALONE_WASM--no-entry のような最低限のオプション指定は必要です。

それでも、そこさえ押さえれば、あとはJS世界からWASMで定義した関数が見えるように、 exports にシンボル名を書くだけで行けるようになっています。 以下のようなプロジェクトディレクトリ配置を行って:

project/
├── package.json
├── vite.config.ts
├── src/
│   ├── generated/
│   │   └── wasm-loader.ts   // (自動生成)
│   └── wasm/
│       └── add.wasm         // (ビルドされたWASMバイナリ)
└── wasm/
    └── add.c

Cのコードを書くだけです(もちろんC++でもOK):

int add(int a, int b) {
  return a + b;
}

WASM開発を行う場合は、もう一つ、どうやってTSからWASMに定義した関数を呼び出すのか、という問題があります。 これも毎回面倒なスタブ実装を書く必要があるのですが、emsdk-envでヘルパーコードを生成できるようにしてあります(generatedLoader)。 これが有効なら wasm-loader.ts ファイルが生成されるので、あとはこれを使うだけです:

import { loadAddWasm } from './generated/wasm-loader';
 
// WASMエクスポート関数の定義 (手動で定義が必要です)
interface AddExports {
  add?: (a: number, b: number) => number;
}
 
// WASMバイナリをロードして使用可能にする
const wasm = await loadAddWasm<AddExports>();
 
// `add()`関数を取得
const add = wasm.exports.add!;
 
// 関数を実行
const result = add(1, 2);

他にも様々なオプションや、ビルド戦略に使用できる柔軟な定義が出来るので、興味があれば READMEを参照してください (詳しく記載しています)。

重要な機能

重要な機能の説明を忘れるところだった... emsdk-envはViteプラグインなので、HMRに対応しているんですよ (!!!)

つまり、npm run dev などでVite devサーバーを起動しておき、C/C++のソースコードを編集して保存すると、ブラウザプレビューに反映されますよ。 もちろん、TS/JSのように、ページの部分更新まで含んだ真のHMRというわけには行かず、C/C++コードはフルビルドが発生します。 それでも、これは非常に開発体験が良いです。

一応、平行ビルドを行うようにして、あまりTS/JS側のHMRと見劣りしないように頑張ってはいますが、強いて挙げるならここは今後の課題です。

それともう一つ、emsdk-env向けのディレクトリ構成を守ってNPMパッケージを生成すれば、WASMパッケージを作れます (!!!!!!)

つまり、あなたのWASMライブラリをNPMで簡単に取り込んで使えるようになるのです。 例えば、作ったライブラリのヘッダファイルとアーカイブファイルがあれば、それらを取り込んだNPMパッケージを作り、 使用者はNPMパッケージをインストールして:

export default defineConfig({
  plugins: [
    emsdkEnv({
      // "wasm-calc-lib"パッケージのライブラリを使用する
      imports: ['wasm-calc-lib'],
      targets: {
        // "offload.wasm"
        offload: {
          // "wasm-calc-lib"パッケージの"libcalc.a"を参照
          linkOptions: ['-lcalc'],
 
          //  :
          //  :
        },
      },
    }),
  ],
});

のようにするだけで、そのライブラリを使えるようになります。 これは、私がVS2013の頃に、ライブラリのパッケージングで考えていた事(WASMの話ではありませんよ! その頃にはまだWASMは無いです)で、 今になって(環境は違うけど)やっと具現化したよなぁと言う、ちょっと胸熱な機能です。

その他

ヘルパーコードは、型定義までは踏み込んでいないため、エクスポート関数の定義は手で書く必要があります。 ここはTypescript APIを使用すれば自動化できることは分かっているのですが、ビルド時間に影響があるので現在はそこまでやっていません。

TS APIは6.0でも(高速化について)消極的のように感じたので、サポートするかどうかはまだ未定です。

まとめ

emsdk-envはViteプラグインで、Emscripten SDKの自動セットアップやビルド管理、HMR対応を行います。 CMake を使うような大規模開発ではなく、もっと小規模で高速開発を行いたいのなら、良い選択肢だと思います。

maplibre-gl-layers や、 massive-sprites で、実際に使用しているので、参考にしてください。

第11回Center CLR勉強会

Center CLR No.11

忙しかったので準備も大変だったけど、終わってからもこれを書く時間が取れずにもう木曜日...

今回の私の登壇は、去年(2025)の成果の2つを (忘れないうちに) 発表するという感じでした。

伝えるべき情報を結構絞ったのですが、それでも1セッション辺り60ページ超えてるので、 1ページ1分弱のペースで喋る必要があり、ずっと喋り続けて喉が痛い...

でもこれで安心して脳の記憶スペースを空けることが出来たので、肩の荷が降りました (忘れないように色々覚え続けておくのは辛い)。

後で動画公開しようとして録画はしたのですが、残念ながら音声不良のためボツとなりました。

以下に私のセッションの内容について少し紹介します:

あなたが必要だったNuGetサーバー

nuget-server - あなたが必要だったNuGetサーバー
by Kouji Matsui
speakerdeck.com
Kouji Matsui

キャッチーなタイトルを付けてしまった...

一言で言うと、2025年現在 (もう去年だけど) 、プライベートでサクッと立ち上げることが出来る定番のNuGetサーバーが存在しないと思われるため、 自分で作った、という話です。

フィーチャー:

  • NuGet V3 API互換性:最新のNuGetクライアント操作をサポート
  • データベース管理不要:パッケージファイルとnuspecをファイルシステムに直接保存、データベース管理から解放
  • パッケージ公開:cURLやその他のツールでHTTP POSTを使用して.nupkgファイルを柔軟にアップロード
  • 基本認証:必要に応じて公開と一般アクセス用の認証を設定
  • リバースプロキシサポート:適切なURL解決のための信頼できるリバースプロキシ処理を設定可能
  • 拡張機能を備えたモダンなWeb UI:
    • 複数パッケージアップロード:複数の.nupkgファイルを一度にドラッグ&ドロップ
    • ユーザーアカウント管理:ユーザーの追加/削除、パスワードリセット(管理者のみ)
    • APIパスワード再生成:セルフサービスでAPIパスワードを更新
    • パスワード変更:ユーザーは自分のパスワードを変更可能
  • パッケージインポーター:既存のNuGetサーバーからのパッケージインポーター付属
  • Dockerイメージ利用可能

特に、ゼロコンフィグレーションにはこだわりました。要するに、これだけで起動します (要Node.js):

$ npx nuget-server

何でそうしたのか? というバックグラウンドはスライドで軽く触れていますが、セッション中も色々背景を話しました。 そして、実際に仕事でも使ってドッグフーディングしていて、公開直前まで調整を行っていました。

他にも、Dockerイメージについてはマルチアーキテクチャ対応をやってみたかったので、その対応方法について解説しています。 普段はpodmanを使っているので、podmanでのマルチアーキテクチャ、具体的にはamd64(x86_64)とarm64(aarch64)の両方に対応させています。 今だと、Raspberry Piの32ビット向けにarmv7に対応させる事も出来るかもしれません。

podmanを使う場合は、クロスアーキテクチャ実行が可能なので、普通にDockerイメージを起動するかのように、異なるアーキテクチャのイメージを実行できます (素晴らしい) 。 この機能を使って、amd64環境のUbuntu 24.04上でarm64のビルドも行っています。 但し、この機能はqemuを使って実現しているようです。なので、クロスアーキテクチャイメージを起動すると、それなりに遅いです。

解説中にも補足しましたが、アーキテクチャオプションの指定が --platform linux/arm64 なんですよね。 もしかしたらpodmanのチームは、(マボロシの) Windowsコンテナに対応させる気があるのかもしれませんね。

地図に移動体たくさん表示したい

maplibre-gl-layers - 地図に移動体たくさん表示したい
by Kouji Matsui
speakerdeck.com
Kouji Matsui

え、何で (私が) いきなり地図? という疑問があるかもしれません。私もそう思います。 (仕事で) 必要だから作ったという、いつもの流れです。

MapLibre GL はもう十分有名な地図表示のためのライブラリプロジェクトですが、 多数の移動体(移動する物体)を表示するには難しい問題があります (多数の動かない地物を表示するのは全く問題ない)。 それで、MapLibreには、いわゆるプラグインインターフェイスが存在するので、これを使って多数の移動体を表示できるように拡張できる 「レイヤーライブラリ」を実装した、という話です。

フィーチャー:

  • 大量のスプライトを配置・変更・削除できる。
  • 各スプライトの座標点を自由に移動出来る。つまり、移動体を簡単に表現できる。
  • 各スプライトには、座標のアンカー位置を指定できる。精密な位置の描画が可能。
  • 各スプライトには複数の画像を追加出来て、回転・オフセット・スケール・不透明度などを指定できる。同様にテキストも配置できる。
  • スプライトの座標移動・回転・オフセットをアニメーション補間出来る。
  • 画像の重なりを制御するための、サブレイヤーとオフセット指定も可能。
  • 完全命令型API。高性能かつ拡張性のある更新を実現。
  • WASMとシェーダーによる計算処理の高速化。

この取り組みでも、経験の無かった方面 "WebGL", "WASM", "SIMD" について取り組んでいます (SIMDはSSE2までは少しかじったことがある)。

WebGLやWASM未経験から始めて見えた部分や失敗したことなどを色々紹介しています。

私はネイティブC/C++開発については経験があるので、そういう低レイヤーから眺めるこれらの技術、というような視点も少しあります。 特にウェブ界隈は高レイヤーなアーキテクチャの話が多いので、なんだか「ブラウザのはらわたを弄ってる」感じがして面白かったです。

機能面で言えば当初目標は十分に達成しているので、このプロジェクトも良い感じで終えることが出来ました。

ところでこのセッションでは、WASMの開発環境としてC/C++ (Emscripten SDK) を使った、という話しかしなかったのですが、 Emscripten SDKの準備をするのが割と面倒 (難しくはない) という問題があって、これを簡単に実現する emsdk-env も作っています。

(だいたい、開発環境周りを整備するために必要な道具も作る、みたいな事をやってるから苦労するんだというのは自覚がある...) これについては、近々ここに説明を載せようと思います。と言っても、私の書いているプロジェクトは、必要な情報をREADMEに盛り込むことが多いので、 被らない範囲の何かを書くことになると思います。

次回の開催

まだ第12回は未定ですが、しゃべるネタは色々あるので、梅雨か夏の前ぐらいを目処にconnpassに立てると思います。 特に中部圏の方は、ぜひご参加下さい。

リブート

とうとうブログをリセットすることにしました。

経緯

というよりも、以前から(かれこれ10年弱?)賞味期限切れ状態で久しかったのでリセットしたかったのですが、 リセット準備と言う名のやるやる詐欺が横行した結果、今の今まで伸び伸びになってしまってました。

理由の一つは、WordPressから脱却して、自分で設計したドキュメントサイトジェネレータに移行することだったんですが、 最初のバージョンが途中で頓挫し、それから作り直して形にするまでに時間が取れなくて数年経ってたという...

その、昔の作りかけのコードをようやくR.I.Pとしてアーカイブすることが出来ました。完成させられなくてごめんな。おやすみ。

新しいジェネレータのコードはもう公開してあるのですが、これについては今まさにドッグフーディングを始めたところなので、 紹介はまた今度の機会にしようと思います。

自らを試すため、これを公開したと同時に、以前のレンサバをバックアップ取らずに即解約したぞ

過去の記事をコンバートする事も考えたのですが、WPの記事の機械的な変換が結構面倒なのと、 最初に示したようにもう賞味期限切れの記事がほとんどだったので、思い切って捨てることにしました。

あれを読んで参考になったと言ってくれた人も居たので、ちょい惜しい気もしましたが、どうか忘れてください :)

近況

そんなわけで、最初の記事として何を書こうかと考えたんですが、近日にCenter CLRでしゃべる事もあるし(しかも3枠)、 その後のネタもどう発表するか考えあぐねていたこともあって、いざ書けるようになっても何書いて良いのかあんまり思いつかない...

なので、今取り組んでいることをリストアップしてみようかと思います。

最近は、仕事と趣味両方ともTypeScript環境主軸で、一部C/C++でコードを書いてます。 仕事専用のものは公開できないのですが、一部のコードはOSSにしつつ、仕事でも使っています:

  • nuget-server: NuGetサーバー。プライベートまたはチーム・個人運用の公開向けNuGetサーバー。簡単に立ち上げることが出来て、認証周りも出来て、データベース不要でモダンな外観でDocker imageもあってコマンドライン一発で起動もできるやつ(今度登壇して解説)。
  • maplibre-gl-layers: MapLibre GL JS用のプラグイン的なライブラリで、多数(>1000)のスプライトを同時にアニメーション動作させることができる。車両や地物のアイコン表示、識別のためのラベル表記、自動追尾など、これ一つで「動くターゲット」を地図に多数表示できます(今度登壇して解説)。
  • funcity: テキストプロセッサで関数型言語を使いたかったので実装した、テキストプロセッシング言語処理系。完全に機能します(恐らく)。awk+sed とかの代わりに使うことを強く想定しています。そのとおりに使えるかどうかはしらんけど。
  • mark-deco: markdownをHTMLに変換する際に欲しくなる、細々とした付随機能を全部盛り込んだ、変換ライブラリ(このサイトジェネレータでも使用しています)。
  • async-primitives: TypeScriptで非同期処理(Promise)やるときに道具が無さすぎて困るので、まとまったライブラリを作った。
  • prettier-max: ソースコード整形のPrettierを自動的に走らせる、Viteプラグイン。deprecated検出とかもできる。ESLintの管理に疲れたので作った。
  • screw-up: TypeScriptなどのトランスパイル結果の成果物に、バージョン番号等を自動挿入する、Viteプラグイン。RelaxVersionerの、TypeScript/NPM用的な位置づけ。バージョン計算アルゴリズムがRelaxVersionerと同一なので、両方を一元管理する場合は、Gitだけでバージョン管理できるので特に重宝する。名前が不吉なのは洒落だと思って忘れてください。
  • dir4json: ディレクトリ内のファイル情報をJSONで生成するやつ。dir2jsonがイマイチだったので2番煎じ。
  • typed-message: Reactアプリケーションのメッセージの多国語化。これもn番煎じだけど、これのアドバンテージは定義体のシンボルがタイプセーフになること。Viteプラグイン。
  • tar-vern: tarの他の実装の公開インターフェイスがイマイチだったので作った。screw-upで、NPMパッケージの操作に使用。
  • scheme-cd-ripper: CDのリッピングソフトウェア。CLI。Ubuntu/Debian向けのネイティブ実装。アルバムアート画像をAAで表示できるのが最大の売り(?)(今度登壇して解説)。
  • screw-up-native: screw-upが思いのほか良いツールに仕上がったので、将来を見越してネイティブ版を実装。C言語のみで実装。
  • ga_runner: GitHub Actionsで、イミュータブルビルドをプライベートランナーで実現するためのスクリプトセット。内部はpodmanでのコンテナリサイクル、GAのアップデートに自動追従など、GAのホスティングと同じような感じでランナーを使えるようにすることを目標にしたけど、まだちょっと詰めが甘いかもしれない。

あとはまだ完成が遠いものがいくつかあります(上に挙げたものは基本的に完成、もしくはほぼ完成のものです)。 数が多いので全部細かく紹介出来ませんが、いくつかはそのうちここで取り上げるかもしれません。

それではまた。

About

Kouji Matsui

  • Full name: Kouji Matsui.
  • kekyo, けきょ, kozy_kekyoというクレジットを使うことがあります。
  • 愛知県の付近のモグリで引き籠モラー。
  • 自転車乗りです(最近忙しすぎて乗れてない)
  • Center CLR主催しています。
  • バックエンドシステム・言語処理系とそのツールチェイン・ライブラリインターフェイス設計、などが主戦場/興味の中心。
  • 最近はTypeScript、またはC/C++、ハードウェアデザイン(コンピューター回路設計)辺り。 TypeScriptやってるとUIerと間違われそうだけど、専門領域は違います。 成果物はだいたい公開しているので、GitHubを参照してください。
  • 赤い色は、某WP 1520由来。今ではマイカラー。燃える赤 🔥

Links

おことわり

  • 本サイトではGoogle Analyticsによる統計を参照しています。 この情報は、私がこのサイト内のアクセス状況を見て、自分でご満悦、あるいは失望するためだけに把握します。 また、閲覧者が何らかのツールでこのトラフィックをブロックしても全く問題ありません。
  • ページに含まれるリンクには、Amazonのアフィリエイトリンクが含まれる可能性があります。ご了承ください。一杯おごるつもりで踏んで貰えると嬉しいです。