#mruby
> [!NOTE]
これは [mrubyアドベントカレンダー2024](https://qiita.com/advent-calendar/2024/mruby-family) 7日目の記事です
とくに誰かからプログラミングを教わったわけではなく、行き当たりばったりにやってきた自分のような者にとって、コンピューターにまつわる広い世界には、開かれた土地と、閉じた土地とがそれぞれ存在している。
開かれた土地では新参者を拒まないだけではなく、新参者を呼び込む努力が常に払われている。「ちょっと僕には合わないかな…」と思って席を立とうとすると「まままま。今とっておきのハーブティが入ったんで」ともてなされてしまい、「あ、そうすか」とソファに深く座り直してしまうかんじ。
近年はwebアプリケーションをつくるライブラリだのフレームワークだのは、初心者に開かれた土地になっている。土着の民である彼らは、自分たちのプロジェクトについて初心者に「説明」をするわけではない。(それは前提である。)それよりも、いかに「おれたちのライブラリがすごすぎるか」「いかにおれたちがネクストジェネレーションなのか」を「表現」する。深夜の高速道路か退廃した近未来かってかんじの真っ暗な背景にコントラストの強い文字、目には追えないスピードで過去を置き去りにして遠くへ去っていく滲んだヘッドライトを思わせるグラデーションをがんがんに炊いたかっこいい差し色。なんかよくわからないがかっこいいビジュアルのWebサイトが設置されていることは、開かれた土地では常識であり、いかに簡単に強力な機能を使いはじめられるか、一見さんのためのチュートリアルや視覚的な作図、動画が常に磨かれている。シェア争いに勝つための競争が繰り広げられている。
> [!NOTE]
>> こういった世界では、どのライブラリがいかに「イケてるか」を考察する人間が「デキる」とみなされるという。
一方、こうした動きはとくにない分野というのもある。そこでは「わかってる人」たちが、「わかってる」前提の議論や情報発信、やりとりを繰り広げているので、「わかってない」俺達に対しての噛み砕いた情報発信は善意の第三者に託されており、貴重な存在である。「わかってないやつ」は歓迎されてないわけではないかもしれないが、ハーブティーとかは出ない。ブレードランナー的な装飾もない。むかし、コンピューターいじりがものすごいマニア向けの趣味だった時代はだいたいどこもこんなかんじだったんだろうね。
初心者向けのガイドがあろうとなかろうと、本当にそれが好きな奴はやすやすと高い障壁を越えてくるものだが、そういうのってある意味で才能というか向き不向きはあるとおもうんだ。
そういう才覚があんまない僕にとっては、門戸が開かれていない分野での学習パスを見出すことは骨が折れるのだが、しかし僕にだって、どしゃぶりの雨のなか夜の高速道路で加速する音を聞きながら突っ走って事故りたい衝動くらいはある。
ここでひとつ打ち明けると、このページは実は [mrubyアドベントカレンダー 2024](https://qiita.com/advent-calendar/2024/mruby-family) 、7日目のバトンを受け取って書かれた。
もしここまでよんでる人がいるとするならば、「は? mruby全然関係ないわこいつ」と思っているかもしれないが、つまり言いたいのはこういうことだ。僕はmrubyのいちユーザであるが、mruby(あるいは組み込み?)はどちらかといえば閉じた土地に属している。
僕はmrubyを特定ハードウェア向け組み込みではなく、ゲーム制作に使っている。このまえ、自作のUnity向けRubyスクリプティングフレームワーク [[VitalRouter.MRuby - Unity向け汎用Rubyスクリプティングフレームワーク|VitalRouter.MRuby]]というライブラリを公開した。これは主にゲームプログラミングにおいて、ゲームのシナリオなどのシーケンスをRubyのDSLで記述することを目的にしている。
あと、さいきんは停滞気味だけど、mrubyのVMの実装にも着手している。その話をしたら
[@kishima](https://x.com/kishima)さんが[「mrubyバイトコードハンドブック」](https://silentworlds.booth.pm/items/1307790)を献本してくれた。調べるのがかなり大変そうな労作をいただき大変参考にしてます。
ただ、こういうかんじの公式の情報を補完してくれる有志の方々がもっといてくれると嬉しいわーてかんじはするし、Rubyの日本での知名度の割に、観測範囲ではmrubyより Lua のほうがポピュラーで、Luaのほうが採用事例を増やしているように見える。
あくまで自分の経験から言うとmrubyはふつうに使うだけでもそこそこのハードルを感じている。その理由は一重には、前提条件や概念の説明をスキップしたわかってる人向けのメモ書きのようなドキュメントにしか当たれなかったこと、すぐ使えるバイナリや実践的なサンプルコードに当たるのに苦労すること、公式には省かれている前提知識を拾ってくるのが面倒、あたりだとおもっておりますです。
そこで以下では、主に過去の自分へ向けて、mrubyへ入門するにあたってのガイド、公式ドキュメントやbookに当たれるようになるまでに必要な前提の共有、そんな情報をここに残し、mrubyアドベントカレンダー7日目という文字が掘られた墓標を立て、線香の一本でも上げたいと思う。
mruby入門入門とは、入門・mruby入門、Introduction to mruby introductionの意です。
## mrubyの歩きかた
Rubyに好感を持っています、というところから「mrubyつかってみよっかなー」と思ったとき、それなりの確立でドキュメントの薄さにつまづくんじゃないかと思う。
mrubyは 特定のハードウェアやシステムに「組み込む」ための設計されたソフトウェアだから、「mrubyをインストールして rubyコードを実行しましたっス」だけでは、実用的にはほぼ意味がない。外の世界のシステムと Rubyコードの橋渡しをしてはじめてなんかの役に立つ。それがmrubyである。
つまり99%の場合に mrubyのCのAPIを利用することになるし、CのAPIをつかってなんか外部のシステムとつなげることになる。
CRuby (ふつーのRubyのこと) の場合、ユーザはRubyの書き方を学べばよく、コア部分や CのAPI部分のドキュメントがなかろうとべつに困らないしそういうのはよくわからないけど誰かがやってくれてるんだろう、あざっす、と思っていればよかったが、mrubyの場合は困る。
困るのだが、公式に用意されている体系的な書物は API リファレンスくらいのようで、このリファレンスというのも現時点では あいうえお順にメソッドが並んでいるといった類のアレだから、知りたい知識を得るには探検が必要。
mrubyはビルドのフレキシビリティや設定をいくつか持っているため、自分でカスタムビルドをつくってこそ真価が発揮できるで!!!!! みたいなところがあるのだが、「まあそこはよくわかんないのでべつに有り物のビルドでいいッス」と思ったとしても、バイナリは配られてないのでビルドをする必要はあるし、mrubyのビルドシステムは21世紀以降の言語SDKしか知らない多くの若者にとってはつまづく点が多いんじゃないかと思う。
まとめると、君が mruby で 何をするにせよ、たぶん以下のような知識は必要になるはずだ。
- 0. rubyのかきかた
- 1. libmruby をビルドする知識。
- mrubyのビルドシステム。
- 2. 焼き上がったネイティブバイナリ ( `libmruby.a`) 組み込みたいところにぶちこむための知識
- Cのライブラリを使ったりリンクする知識。
- 3. mruby の CのAPI 。組み込み先のシステムと mruby の世界のつなげかた。
加えて、よほどCに親しんでいない限り、新しくネイティブ実行バイナリをつくっちゃおうかな〜 とおもったとき、現代ではRust や Zig とか そういうのを選びたいキッズのほうが多いんじゃないか。
それらを含む大抵の言語は、C のライブラリを呼び出す機能を備えているので、libmruby をそのまま使うことはできるが、色々な約束事が必要だ。その約束事、ある言語から CのABI のコードを呼び出す (あるいはその逆) の機能のことは 「FFI」とか呼ばれている。
だから、必要な知識にこれも加えとく。
- 4. あなたの愛用の言語から libmruby (C API) を扱うためのFFIの知識。
FFI というのは Foreign function interfaceの略で、ある言語から別の言語の関数を呼び出すという意味だ。「foreign funciton」というからにはいろいろな言語を呼べそうに思えるが、現実にはFFIという言葉は 「C言語以外から C 言語のABI (Application Binary Interface)に則ってビルドされたバイナリを呼び出す」を意味していることが多い。
「はあ? どゆことやねん。ちねや」と思ってしまうかもしれないが、C言語のABIというのは、実質、ネイティブの機能、あるいは複数の言語間を結びつける場合の共通インターフェイスとしていいかんじに機能している。なぜそうなのか俺にはわからない。わからないが WASM Component Model とかが世界を征服するまでは、 整数、浮動少数点数、ポインタ、連続したメモリ領域、関数呼び出し、といった、プリミティブな機能の汎用的な仕様は C の ABI とイコールなのである、と先輩方はおっしゃっているということだ。
libmrubyはCのライブラリなので、あなたが使いたい言語のFFIについて調べると、おのずとlibmruby を好きな言語から使えるようになる。
- 0については問題ないはず
- 1については、以下ですこし簡単に前提と背景と情報源を説明しようとおもう。
- 2 については、大きなトピックなのでここでは説明しないが、 Cのビルドの仕組みとライブラリみたいなことを知っておこう。
- 3 について。ソースコードの追いかた、ドキュメントの探しかた、みたかみたいなことをすこし説明してみたいとおもう。
- 4 についてはがんばれ。以上。
## mrubyのビルドシステム
Rust、Zig、Go 、Swift、C# AOTとかその他その他のクロスビルドについて知っているとしたら、なんか動かしたい OSとCPUアーキテクチャの名前を入れれば指定したとおりにバイナリが焼けるんでしょ? それがクロスビルドなんでしょ? というイメージを100%の確立で持っているとおもう。
mruby をビルドするにあたってそういうノリは一旦脇に置いておこう。
mrubyのリポジトリにはたくさんの環境向けのビルド設定ファイルがコミットされてはいるが、
https://github.com/mruby/mruby/tree/master/build_config
この設定はあくまでも、ビルドマシン上にビルドのためのツールがすべてインストール済みでパスが通っている状態を前提にしている。既にそこにあるコンパイラやリンカを呼び出す設定に過ぎない。ビルドに必要なSDKをもってくる機能はmruby本体にはないし、その環境向けのビルドを動かすために必要なSDKやコンパイラを用意するのはユーザの仕事になっている。SDKを持っていない環境でこの設定ファイルを使おうとしてもビルドは通らない。「**アレクサ、人気のプレイリストかけて**」とか叫んでもアレクサはいないので無音。みたいなかんじになる。
それから、mrbgem の設定をするとかそういう場合にもビルド設定をつくる必要がある。多くの場合はコミットされてる設定はそのまま使うよりもサンプルとして参照するかんじんじゃないかな。
mrubyはハードウェア組み込みとかを意識したりしているかんじだからなのか、自分で好きにターゲットをつくってつかってねと言っているのである。
mruby のビルドシステムについてのドキュメントはこのへん。
mruby のビルドシステムは Rake 。設定は `MRuby::` 名前空間のRuby ライブラリで記述するようになっている。
https://github.com/mruby/mruby/blob/master/doc/guides/compile.md
これを読む前に理解しておくといい知識は、ビルドターゲットが二種類あるという点だ。
mrubyは、ビルドターゲットとして大きく分けると「host」と「それ以外」という概念を持っている。
まず、「host」というのは、mrubyをビルドするマシンのこと。mrubyのビルドを実行すると、実行しているホストマシン向けのビルドも必ずセットで実行される。省略する方法はない模様。
そして、host ターゲットをビルドすると、必ず 以下のものが出力される。
- mrbc
- mirb
- etc..
mrubyは、host上でこれらのツールを動かし、こういうツールを持たない組み込み先へmrbを配信することを想定していると思われる。
host のビルドの設定をアレコレしたい場合は `MRuby::Build.new { .. }` で設定する。
ここにはターゲットの名前を指定できない。なぜならこれは hostターゲットの設定なので、名前は決め打ちで`"host"` なんだ。
hostターゲットは 基本的にはビルドマシンの環境で動くターゲットだし、暗黙のうちにmruby compiler とかいろいろついてくるので、通常、組み込み先のためのターゲットを別でつくる。
host以外のターゲットをつくりたいときの設定が `MRuby::CrossBuild.new("{名前}") { ... }` だ。
つまり、mrubyの言う `CrossBuild` とは、 host 以外のターゲットであるという意味に過ぎない。
クロスビルドでなくても host じゃない設定でビルドしたい場合は `CrossBuild` でターゲットをつくってゆこう。
TODO: Android SDK とか iOS SDK とかの話も書きたい鴨
## mruby の CのAPI
mruby の CのAPI について中身は詳しく説明しないが、調べる上で有用かもしれない事項を説明してみたいと思う。
リポジトリをクローンして `rake -T` を調べると、 CのAPIのドキュメントが読める、とある。
```bash
$ rake doc:capi
$ rake doc:view:capi
```
これで自動生成されたドキュメントが開くのだが、ここから読める API リファレンスは、くわしい説明が書かれている関数は一部であるし、あいうえお順に関数が並んでいる例のやつなので、使い方を逆引きで調べたいときはどう調べたもんかけっこう困る。
僕のおすすめはmrubyリポジトリのCのプロジェクトをクローンして、IDEで`include/mruby` フォルダにあるヘッダを読むこと。
僕は JetBrains重課金勢なので、CLion とかで リポジトリを開いている。とりあえず素の状態でもシンボルからヘッダファイルにジャンプするとかは動く。
(完全に実装へのジャンプを動かす環境を整えることに成功したらまた追記したいとおもうのだがいまのところあんまうまくいってない)
`include/mruby` あたりを眺めると数多ある mrb_* 関数が ざっくりとカテゴリごとにファイルに分かれているので、どのファイルにどういう機能が書かれているのかだいたい把握しとくと良い。
たとえば文字列のAPIをなにか調べているときは `string.h` とかを見に行くと全部載っている。実装も読めるしね。
初見でわかりずらいのは `boxing_*.h` というファイル。mrb_value の実装は、ビルド時の設定によってこの名前のヘッダファイルのどれかが選択されている。mrb_valueのフォーマットとか調べたいばわいはこのうちのどれかを見る。
### 略語
mrubyリポジトリ内でよく使われる概念はかなり積極的に略称が使われているスタイルなので、略称の意味をおさえておくと調べがはかどります。
- iv
- instance variable の意味。
- gv
- global variableの意味
- lv
- local variableの意味。
- irep
- internal representation。
- mrubyバイトコードをパースした後の、rubyコードのメモリ内表現。
- ccontext
- compiler context。
- mruby コンパイラで mrubyコードをパースして実行するときに渡せるオプションのようなもの。
- `_id`
- mruby 世界のメソッドや変数の一意な識別子として Symbol が使われている。それを内部的には _id と言っている。文字列を指定して メソッドや変数にアクセスするAPIに対して、Symbolを直接指定するオーバーロードは `_id` という名前になっている。
- nstring
- Cの文字列は cstr だが一方、nstring は charポインタと文字数の2つの値で表現された文字列のこと。
- TODO:
- なんかほかにもいろいろあったきがする……
### 規約
この勢いで独特の命名規約 にも触れておきたい。命名規約から導出できる API についてはいちいち説明が書かれていないことも多いので、規約を知っておくと調べがはかどります。
- 名前が `mrb_{型名}_p` で boolを返す関数
- たとえば `mrb_int_p` は 引数が int である場合に trueを返す。など
- p は 「predicate」の略で、値がどういった性質かテストする関数を意味する。
- LISP由来らしい。https://www.cliki.net/naming+conventions
- `mrb_{型名}_value`
- プリミティブな値やポインタを受け取って、 `mrb_value` を返す関数。
- mrb_value に対しての操作はマクロになっているため、定義からシグネチャを読みとりずらいが、 この規約を覚えておくとだいたいわかる。
- たとえば mrb_fixnum_value は 整数 を受けとって 整数型の mrb_value を返す。
- `mrb_{型名}`
- `mrb_{型名}_value`
TODO:
### マクロ
TODO:
## GC
TODO:
mrubyはgcの実装を持っている。ドキュメントを探すと、gc_arenaについてのガイドがみつかるが、gcそのものについてのガイドはとくにみつからなかった。
前提知識として、mrubyはシングルスレッド環境を想定している。mrubyは組み込まれているシステムのスレッド上で動き、mruby がGC専用スレッドを起動するようなことはない。mrubyがGC Collectを実行するのは、メモリ確保(mrb_obj_alloc)が起こったときなど特定の API コール時にセットで暗黙のうちに同じスレッド上で即座に行われるようである。
また、mrubyのGCはコンパクションを行わない(すくなくとも現時点では)。
これは、GC 管理対象のオブジェクトのアドレスが変わらない(生きているアドレスは保持し続けていても大丈夫であることを意味する。
さて、mruby の Cの API を利用する際、 mruby オブジェクトはすべてGC管理下にあるので、GCで回収される恐れのある mrb_value を外に持ち出したり、GCで回収してもらうべきメモリがリークしないように扱う必要がある。
つまりmruby の CのAPI を使う上でGCの挙動は知っておいたほうがよい知識だ。
ただ、mrubyは手動で細かくやらなくても代替の場合にうまくいくことを目指してもいるようで、そのへんについて公式ドキュメントから辿れる有力な説明としては以下の gc_arena の説明、くらいしかみつからない。
- https://matz.rubyist.net/20130731.html
- https://github.com/mruby/mruby/blob/master/doc/guides/gc-arena-howto.md
これは gc_arena そのものについての説明なので、より包括的で最初のとっかかりになるわかりやすい情報をまず探してみるといいとおもう。個人的には以下のページなんかを推したい。
https://tyfkda.github.io/blog/2013/04/18/mruby-gc.html
gc_arena の説明として、「C変数として保持している オブジェクトをGCから保護する目的である」、と書かれているが、
実際には mrb_obj_alloc によって確保されたオブジェクトは、例外なくすべて gc_arena によって保護される。 だから、mruby のオブジェクトを新しく生成したりとかとにかく mruby を使った ら、上流のどこかで *gc_arena_save/gc_arena_restore* のセットで囲われていないととメモリリークが起こると思ってよ。 (例外処理の方法を知っている上流で try/catch を書くようなもんだね)
だけども簡単なサンプルを見るとそんなコードがない。基本的には、mrbgem のエントリポイントなど 自明な場所については mruby が自動で代わりにやってくれているからだ。「一時オブジェクトをいっぱいつかう関数では gc_arena_save/restoreが必要」という結論になっているのはそのため。
## そんなかんじなんですが
すでに〆切に遅刻しまくっており時間間切れのため、このへんで一旦区切ります…。
書きたいことはまだまだたくさんあって、このページを引き続き更新するか、mruby 入門書とかつくるとかしたい。するかも。