中身がおじさんぽくてよかった。 40代かと言われるともう少し上な感じもしました。
bootstrap(セルフホスト)時のspinel_codegen.rb実行時のGC回数増
bootstrap(セルフホスト)時のspinel_codegen.rb実行時間の変わり方 - @ledsun blog で、Nodeテーブルを初期化するクラスを抽出すると、実効時間が160秒 => 344秒と伸びることが分かりました。 この原因を探りたいです。
まず、stackprof の CPU profile で計測したところGCに掛かる時間が増えていることが分かりました。
そこでGCに注目して計測してみました。
GC.stat による GC 回数の測定
GC.stat で GC 回数と allocation の差を確認します。
目的
ruby spinel_codegen.rb ast output.c の実行前後で GC.stat を取り、次の値の差分を比較します。
total_allocated_objectstotal_freed_objectsheap_live_slotsheap_free_slotsminor_gc_countmajor_gc_countold_objects
測定コマンド
抽出前の base と抽出後の extract-node-init を同じ入力 AST で測ります。
こんな感じです。
ruby -e ' ast, out, codegen = ARGV ARGV.replace([ast, out]) before = GC.stat t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) load codegen t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC) after = GC.stat keys = [ :total_allocated_objects, :total_freed_objects, :heap_live_slots, :heap_free_slots, :minor_gc_count, :major_gc_count, :old_objects, ] puts "elapsed=#{(t1 - t0).round(3)}" keys.each do |key| puts "#{key}=#{after[key]} delta=#{after[key].to_i - before[key].to_i}" end ' AST_PATH OUT_C_PATH SPINEL_CODEGEN_RB_PATH
測定対象
| 対象 | コミット | 内容 |
|---|---|---|
| base | 0d10645 |
extract-node-init の親 |
| extract | d94f5d6 |
refactor(codegen): extract node table loader |
結果
| 指標 | base | extract | 差 |
|---|---|---|---|
| elapsed | 189.826s |
356.201s |
+166.375s |
| total_allocated_objects delta | 2,149,566,411 |
2,064,184,970 |
-85,381,441 |
| total_freed_objects delta | 2,144,512,398 |
2,063,651,005 |
-80,861,393 |
| heap_live_slots delta | 5,054,013 |
533,965 |
-4,520,048 |
| heap_free_slots delta | 2,416,646 |
2,339,788 |
-76,858 |
| minor_gc_count delta | 2,082 |
19,438 |
+17,356 |
| major_gc_count delta | 19 |
33 |
+14 |
| old_objects delta | 3,393,053 |
526,672 |
-2,866,381 |
読み取り
extract-node-init では総 allocation 数は少し減っています。
minor_gc_count が 2,082 から 19,438 に増えています。約 9.3x の増加です。
推論ロジックよりGCに原因がありそうです。
さて、なぜGC回数が増えたのでしょうか? またGCを切ったらこの差は縮まるのでしょうか?
bootstrap(セルフホスト)時のspinel_codegen.rb実行時間の変わり方
spinel_codegen.rbのbootstrap(セルフホスト)時の性能評価手順を整理 - @ledsun blog をつかってどのくらい性能が変わるかを試しに測ってみます。
refactor(codegen): extract node table loader · ledsun/spinel@d94f5d6 · GitHub のようにNodeテーブルを初期化するクラスを抽出します。すると?
変更前 。 コミットで言うと https://github.com/matz/spinel/commit/f6b593711e8100d0ff0fbd77243b8e1678e39d7e です。
{ "results": [ { "command": "ruby spinel_codegen.rb /tmp/spinel_master_codegen.ast /tmp/spinel_master_bench_gen1.c", "mean": 160.00851559026, "stddev": 11.138275974757432, "median": 154.14327744726, "user": 158.99329034, "system": 1.0015925266666665, "min": 153.02849486026, "max": 172.85377446326, "times": [ 153.02849486026, 154.14327744726, 172.85377446326 ], "exit_codes": [ 0, 0, 0 ] } ] }
変更後
{ "results": [ { "command": "ruby spinel_codegen.rb build/codegen.ast /tmp/spinel_codegen_bench_gen1.c", "mean": 344.58640011078, "stddev": 9.74270517215269, "median": 347.07773677278, "user": 343.53394030666664, "system": 1.0031182066666666, "min": 333.83992995578, "max": 352.84153360378, "times": [ 333.83992995578, 352.84153360378, 347.07773677278 ], "exit_codes": [ 0, 0, 0 ] } ] }
雑に中央値を見ると 160秒 => 344秒。 軽く倍になっています。 「こんなに変わるんだ」と、驚きがあります。
ここから、性能に大きく影響を与える変更を絞り込んで行きたいです。
spinel_codegen.rbのbootstrap(セルフホスト)時の性能評価手順を整理
ビルド済みのspinelコマンドの実効時間の話ではありません。 spinelコマンドを作成するためにspinel_codegen.rbを実行する時間を計測します。
背景
[codex] Extract NodeStore from codegen by ledsun · Pull Request #284 · matz/spinel · GitHub にてリファクタリングしてみたところ、僕の予想以上に実効時間が伸びたことが始まりです。 いくらか憶測で修正してみましたが、どういう修正が実効時間に影響を与えるのか特定できていません。
基本に立ち返ってまずは計測方法を用意します。
目的
spinel_codegen.rb の分割や require 追加が、ruby spinel_codegen.rb の実行速度に与える影響を切り分けるための計測手順です。
計測対象
計測対象はコード生成フェーズだけに固定します。
ruby spinel_codegen.rb build/codegen.ast /tmp/spinel_codegen_bench_gen1.c
build/codegen.ast は計測前に一度だけ作り直します。
./spinel_parse spinel_codegen.rb build/codegen.ast
理由
make spinel_codegen は次の処理を含みます。
spinel_parseによる AST 生成ruby spinel_codegen.rb ...による C コード生成- C コンパイラによる
spinel_codegenバイナリ生成
今回見たいのは Ruby スクリプトの分割による影響なので、AST 生成と C コンパイルは計測から外します。これにより、Ruby のファイルロード、初期化、AST 読み込み、コード生成処理にかかる時間を比較しやすくします。
出力先は /tmp/spinel_codegen_bench_gen1.c に固定します。リポジトリ内の build/gen1.c を更新しないため、計測で作業ツリーを汚しません。
方法
hyperfine を使い、ウォームアップ 1 回、計測 5 回を標準条件にします。
#!/usr/bin/env bash set -euo pipefail RUNS="${RUNS:-5}" WARMUP="${WARMUP:-1}" AST="${AST:-build/codegen.ast}" OUT="${OUT:-/tmp/spinel_codegen_bench_gen1.c}" JSON_OUT="${JSON_OUT:-/tmp/spinel_codegen_hyperfine.json}" if ! command -v hyperfine >/dev/null 2>&1; then echo "error: hyperfine is required" >&2 exit 1 fi if ! command -v ruby >/dev/null 2>&1; then echo "error: ruby is required" >&2 exit 1 fi if [ ! -x ./spinel_parse ]; then echo "error: ./spinel_parse is required; build it before measuring" >&2 exit 1 fi mkdir -p "$(dirname "$AST")" echo "ruby: $(ruby -v)" echo "hyperfine: $(hyperfine --version)" echo "commit: $(git rev-parse --short HEAD 2>/dev/null || echo unknown)" echo "ast: $AST" echo "out: $OUT" echo "runs: $RUNS" echo "warmup: $WARMUP" ./spinel_parse spinel_codegen.rb "$AST" hyperfine \ --warmup "$WARMUP" \ --runs "$RUNS" \ --export-json "$JSON_OUT" \ "ruby spinel_codegen.rb $AST $OUT" wc -l "$AST" "$OUT" echo "json: $JSON_OUT"
benchmark/measure_codegen.sh
実行回数は環境変数で変更できます。
RUNS=10 WARMUP=2 benchmark/measure_codegen.sh
結果の JSON は標準では /tmp/spinel_codegen_hyperfine.json に出力します。
JSON_OUT=/tmp/codegen-master.json benchmark/measure_codegen.sh
比較時は同じ Ruby、同じマシン、同じ RUNS / WARMUP で測ります。ブランチを切り替えた後は毎回このスクリプトを実行し、各ブランチの平均値、中央値、標準偏差を比較します。
Ubuntu + llama.cppでローカルLLM環境構築メモ
llama.cpp のビルド
依存ツールインストール
sudo apt update sudo apt install -y build-essential cmake git
ソース取得
git clone https://github.com/ggml-org/llama.cpp cd llama.cpp
ビルド(CMake)
cmake -B build cmake --build build --config Release -j
動作確認
モデル実行
./build/bin/llama-cli -hf Qwen/Qwen2.5-Coder-3B-Instruct-GGUF
今のところ効果がありそうな設定
-t 8 -c 4096
下のパラメーターを指定したら遅くなりました。
-t 12 --cache-type-k q8_0 --cache-type-v q8_0
性能目安
- Prompt: ~60 tok/s
- Generation: ~10 tok/s
ノートPCのCPUで動かしているので、これくらい
WSLのrbenv経由のRubyの起動を速くする
結論
mkdir -p ~/.local/bin ln -sf /usr/bin/readlink ~/.local/bin/greadlink fish_add_path -p ~/.local/bin
問題
https://github.com/matz/spinel のテストをローカルで実行すると、229回 Ruby が実行されます。 Ruby の起動に時間が掛かると、それだけでテスト実行に時間が掛かります。
原因
rbenv はまず /usr/bin/rbenv がシンボリックリンクなので、それをたどって /usr/lib/rbenv/libexec/rbenv を見つけます。
このときに readlink コマンドを使います。
ただし macOS の標準 readlink は BSD 系で、GNU readlink とはオプションや挙動が違います。
macOS で GNU coreutils を入れると、GNU 版 readlink は greadlink という名前で提供されることがあります。
この差を吸収するため、rbenv は type -p greadlink readlink を実行し、まず greadlink コマンドを探します。
WSL で PATH に /mnt/c/... の Windows 側パスが多く含まれていると、Linux 側に存在しない greadlink を探すために Windows 側パスまで探索します。
この探索に時間が掛かります。
対策
greadlink コマンドを PATH の上流に置くと探索が即座に終わり、rbenv shim 経由の Ruby 起動が速くなります。
RubyKaigi 2026
0日目
移動トラブル
今回のRubyKaigiは、かなり印象的なスタートになりました。
もともとは羽田空港から函館空港へ飛行機で移動する予定でした。 管制機器のトラブルによって、飛行機の出発が危ぶまれる状況になったのです。 飛行機は諦めて新幹線で移動することにしました。
移動には4時間ほどかかり、函館駅に到着したのは10時過ぎでした。
ただ、このトラブルのおかげで良い体験もありました。 同じように移動トラブルに巻き込まれていた rhiroe さんが、Slackで「ご飯行く人いませんか?」と募集していたのを見かけて、それに乗っかる形で一緒にジンギスカンを食べに行きました。 Slack上ではIDを見かけていて認識はしていたのですが、実際にお会いするのは初めてでした。

移動トラブルが良い思い出になりました。
1日目
キーノート
The Journey of Box Building - RubyKaigi 2026

1日目で一番印象に残ったのは、Tagomorisさんのキーノートでした。
3年間、Ruby::Boxを作り続けてきたのもすごいのですが、2023年のshioyama先生の発表がきっかけでRubyにコミットするモチベーションに気がついた話がエモくて良かったです。
自分の過去のことを思い出しました。 そういえば自分も2022年のkateinoigakukunさんの発表をみて、自分の「ブラウザでRubyを動かしたい」というモチベーションに思い出したのでした。
こういうRubyKaigiの発表を見ていた人が刺激を受けて次の発表者になるのが、とてもRubyKaigiで、エモいです。
PicoRuby.WASM
Funicular: A Browser App Framework Powered by PicoRuby.WASM - RubyKaigi 2026
はすみさんの PicoRuby.WASM の発表も印象に残っています。
スリープ処理を自前で実装しているのがかっこよかったです。
ruby.wasm は、WASIに依存する作戦をとったため、スリープの実装は諦めています。
必要であればアドホックにブラウザのsetTimeoutを使う方針です。
ここを諦めずに、自前でタスクスケジューラから実装しているのがかっこよかったです。
さらにブラウザでデバッガも動いていてかっこいいです。
はすみさんは継続的にアウトプットを出し続けている点もすごいです。
お昼ご飯とちょっとした雑談
1日目のお昼は、ANDPADさんが配られていたラッキーピエロのチャイニーズチキンバーガーを食べました。
杉野さんと大塚さんと一緒に食べていました。 RubyKaigiで毎年恒例になってきました。 美容院の話なんかしました。
杉野さんが血糖値の計測装置をつけていて、その話で盛り上がっていました。 ご飯を食べると本当に数値が上がるので面白かったです。
レッドブルを飲むと一気に上がって下がるという話を聞いて、へーって思いました。
はるかさんの発表
Building a Standalone Ruby Programming Environment - RubyKaigi 2026

はるかさんの発表は、また違った意味で印象に残っています。 内容自体も面白かったのですが、それ以上に説明をはっしょった苦労ポイントがありそうでした。 DVI出力の速度が足りないときにどうやってボトルネックを特定したのか不思議でした。
詳しいことは、やんちゃバーで話を聞くことができました。 しかし残念なことに、聞いた内容は、僕の脳みその中のアルコールで蒸留されました。
後日、Boothで販売されていたマイコンも購入しました。
ライトニングトーク
今年は銅鑼が一杯聞けて良かったです。 去年の懸田さんのプチ炎上したメッセージは伝わっていたみたいです。



懇親会
移動は最初徒歩で進んでいたのですが、途中から市電が空いてきたため、市電に乗って駅前まで移動し、そこから歩いてホテルに向かいました。
Bashさん、高橋さん、nagachikaさんと話した記憶はあります。 翌日に発表があるので、ここで宿に帰りました。
発表資料修正
オチで悩んでいました。 そもそもこの時点で、デモの案はありましたが動いていませんでした。 代わりにひねったオチを用意してありました。 ただしRubyKaigiでコードやデモ見せないで口先だけでなんかいうのかっこ悪すぎるんですよね。
デモをつくってみました。 意外とちゃんと動いたので、オチは「乞うご期待」とシンプルに終わらせることにしました。
完成してみると、自分は思っていたよりピリピリしてたなと気がつきました。
やんちゃハウスとやんちゃバーという拠点
今回の滞在でかなり重要だったのが、やんちゃハウスの存在でした。
函館アリーナのすぐ近くにあり、徒歩ですぐに行き来できる距離にあったため、イベントの合間に戻ることができました。 これが想像以上に便利で、セッションの合間に休憩したり、お風呂に入ったり、ちょっと横になったりすることができました。
また、やんちゃバーが毎日20時頃から深夜2時頃まで開かれていて、「常に戻れる場所がある」感がありました。
2日目
2日目の朝は、近くの日帰り温泉に入りに行きました。 このあとあわせて、3日で5回入ります。
Astroの話が気になった
The AST Galaxy to the Virtual Machine Blues - RubyKaigi 2026

笹田さんのAstroの発表でした。
事前コンパイルすることでツリーウォークインタプリタ方式で実装を簡単にしつつ、YJIT並の実行速度を出すアイデアでした。 AIの力で2週間で新しいアイデアを試せるのすごい時代ですよね。
なんで「事前コンパイルすることでツリーウォークインタプリタ方式で実装を簡単にしつつ、YJIT並の実行速度が出る」のかはちんぷんかんぷんでした。
Matzの発表で出てきたSpinelの話とも対応してて少し面白いです。 AstroはフルのRubyをどう簡単(?)に実装するかという話で、Spinelは割り切ったRubyサブセットの高速化を狙うもので、同じようなアプローチでも目指すゴールは違いそうです。
関さん
Programming with a DJ Controller - not vibe coding - RubyKaigi 2026

見てるときは気がつかなかったけど、エディタが同時に二つ動くの異常なんだよなあ。 「エディタがRuby製でOSの機能を通さずに直接動かしているから」なんだけど、その場では気がつかなかった。 咳マニアとして不覚です。
「ログを音楽にするのは、直感に反して多分実用的なんじゃないかなあ?」と、思いました。 ログを目grepするときって文字じゃなくて、色の濃さとか波とか見ているので、慣れれば音楽からも同じことができる気がします。
発表前の準備と初めての通し練習
五島軒のお弁当をゲットして早めに食べて発表練習をしていました。 このタイミングで、初めて30分通しのリハーサルを行いました。
デモ込みでも、大体時間丁度だったので良かったです。 本番では、早めに進んで、30秒くらい時間が余りました。
nagachikaさん
Pure Intonation on Browser: Building a Sequencer with Ruby - RubyKaigi 2026

nagachikaさんの発表も、結構印象に残っています。 音楽理論の話で、純正律と平均律の違いについて説明されていました。
普段自分たちが聞いている音楽は平均律で作られていて、これはオクターブを均等に分割したものです。均等に割ると無理数になるのでプログラマー的にはちょっと気持ち悪い数値になっています。 一方で純正律は、きれいな比率で音を作るので、理屈としてとても美しいです。
デモで ruby.wasm を使って、ブラウザのAPI経由で音を鳴らしていたのも印象的でした。 実際に音を聞いてみると、単音では逆に違和感がありました。普段から平均律に慣れているので、「あれ、なんか変だな」と感じるみたいです。 和音で聞くと綺麗に聞こえました。
ruby.wasm 経由でブラウザのWeb Audio APIが使えることで、Rubyから音楽プログラミングができるようになりました。 実際に発表で使っているのを見ると、胸が熱くなります。
しかも、あのnagachika新聞のnagachikaさんが使っているのです。
発表本番
ruby.wasm also enables JavaScript to call Ruby libraries. - RubyKaigi 2026
自分の発表を行いました。
メインテーマは「JavaScriptからRubyを呼び出す方向をがんばるぞい!」です。 裏テーマは「これまでのruby.wasm総復習」です。 裏番組が両方英語だったので、ruby.wasmに初めて触れる人も迷い込んできそうと読みました。

発表後の完全オフモード
発表が終わった後は、アフタヌーンブレイク以降のセッションには参加せず、やんちゃハウスに戻りました。 お風呂に入り横になったら、そのまま寝てしまったようです。
目覚めたら、少し時間があったので函館山周辺を散歩しました。 アリーナ周辺は平らなのに、函館山周辺だけめちゃ坂でした。


転職ドラフトの懇親会に行った
2日目の夜は、転職ドラフト主催の懇親会に参加しました。 ビールは4杯飲みました。APA、IPA、もものヴァイツェンと黒いのを飲みました。
「RubyKaigiで発表している人として見られている」感覚が新鮮でした。 過去3年RubyKaigiで発表しているので、別に不思議なことは何もないのですが。
このあと流れでタクシーで函館山に登りました。
函館山からの景色見れた!!
— マナティ / Mana Kinoshita (@m_k01104) 2026年4月23日
突然だったのに、一緒にタクシー乗っていただいた方々ありがとうございます🙏#rubyfriends #rubykaigi pic.twitter.com/EFYRO4DcqL

めちゃくちゃ寒かったです。
函館山から五稜郭へ
函館山で夜景を見たあと、そのままタクシーで五稜郭の方へ移動しました。
五稜郭のあたりで、会社の人たちと合流したら、なぜかないさるろーさんとも再会しました。 前に会ったのは沖縄でのRubyKaigiのときで、公式懇親会のあとに、麦汁さんたちと急に飲みに行きました。
それ以来だったので、久しぶりに会えて嬉しかったです。 こういうふうに、「前のRubyKaigiで会った人と、また次のRubyKaigiで会う」というのが続いていくのも、RubyKaigiの面白いところです。
今は横須賀.rb方面で活動されているらしいです。 横須賀.rbに行く理由が増えました。
このあと初やんちゃバーです。 さすがに一杯飲んで寝ました。
3日目
3日目の朝は、近くの日帰り温泉に入りに行きました。 3回目です。
Ruby Committers and the World
Ruby Committers and the World - RubyKaigi 2026

RubyKaigiの目玉イベントですね。 After Matz体制はみんな気になりますよね。
お昼
早めに会場を抜けて昼食はラーメンを食べました。

偶然、横須賀.rbの記念撮影現場に出くわして、ないさるろーさんに代わって撮影係をやりました。
国分さん
Lightning-Fast Method Calls with Ruby 4.1 ZJIT - RubyKaigi 2026
いまやJIT第一人者ですね。 言ってることは分かったつもりなのですが、なぜJITフレームに情報を逃がすと速くなるかはよくわかりませんでした。 保持する情報量は変わっていないように思えます。
最後の「AIで誰でもZJIT書けるようになったので、みんなもJIT書きましょう」って言ってたのが面白かったです。 RJITの時も「RUBYで誰でもRJIT書けるようになったので、みんなもJIT書きましょう」と言ってたのを思い出しました。
Matzのキーノートが良かった

最終日のMatzのキーノートも、今回かなり印象に残っています。
正直なところ、これまでのRubyKaigiのキーノートは、どちらかというとRubyの大枠の方針の話で技術の詳細に踏み込まない「締めの儀式」みたいな感覚で聞いていることが多かったです。 もちろん毎回面白いのですが、いわゆるテックトークとは少し違うものとして聞いていました。 今回はかなり印象が違いました。
Spinelの話が出てきて、「新しいことをやっているな」と感じました。 RubyをAOTでCにトランスパイルして、コンパイルしてから実行します。 Rubyのサブセットとして割り切って設計されているのが、mrubyの経験とかが生きているのかな?と思いました。つまり「RubyというのはサブセットであってもRubyである」という経験知がありそうです。 上手く発展すると、GoやRustが主戦場にしているCLIツール系にRubyの利用領域が広がるので夢があります。
Matzから新しいプレゼントがもらえて、自分は盛り上がりました。 あとで気がついたのですが、笹田さんのAstroとも少し繋がっていますね。
それと同時に、AIの使い方の話も印象に残っています。
Emacsで直接編集するのをやめて、Claude Codeだけでmrubyを実装している、という話をしていて、「そこまで振り切るのか」と思いました。自分だと「手で直した方が早い」と思ってしまうのですが、あえてそうしないのが、“縛りプレイ”的で面白かったです。
夜の懇親会
3日目の夜はTwoGateさんの懇親会に参加しました。 懇親会の前にお風呂に入りました。 4回目ともなると慣れた物で、RTAっぽくなってきました。
#rubyfriends at La Cachette pic.twitter.com/nCrA1uL1iD
— FUKUI Osamu (@iR3) 2026年4月24日
会場はモデルルームみたいなとてもおしゃれな一戸建ての家で、出張シェフの方がその場で肉を焼いて提供してくれるスタイルでした。 豚肉や鹿肉をいただきながら、ワインを飲みつつ、AOTコンパイラの話などで盛り上がりました。
TwoGateさんのドリンクアップでは、シェフの焼いてくれた美味しいお肉が美味しいです。
— れっどさん and the truth (@ledsun) 2026年4月24日
これは豚さん。#Rubykaigi pic.twitter.com/Khe3poZVJx
TwoGateさんのミートアップでは、シェフが焼いてくれた美味しいお肉が美味しいです。牛さん!#Rubykaigi pic.twitter.com/Hursqw2dal
— れっどさん and the truth (@ledsun) 2026年4月24日
やんちゃバー
その後、やんちゃバーに戻り、katsuyoshiさんとSpinelのデバッグなどをしていました。
Open class definitions emit duplicate C definitions · Issue #5 · matz/spinel · GitHub
記憶にないのですが、記録によるとやんちゃバーでバグ報告までやったみたいです。 バグ報告としては最速だったみたいです。
翌日にはMatzに直してもらえました。 SpinelにIssueを出すと、Matzが(ClaudeCodeをつかって)直してくれます。まるでMatzと会話しているような感じがあって楽しいです。
4日目
最終日は、少し観光をしてから帰ることにしました。
やんちゃハウスチェックアウトまえが最後のお風呂でした。 RTA記録はおおよそドア・ツー・ドアで20分くらいです。
そのあと、丸井今井に寄ってお土産を一通り買いました。 観光も兼ねて、五稜郭タワーに上ろうかなと思っていたのですが、これがかなり混んでいて驚きました。 金曜日までとは明らかに人の多さが違っていて、「これが花見&GW初日の威力か・・・」とおののきました。 タワーに登るのは諦めて、写真を撮りました。


周りのお店もどこも混んでいて少し困りましたが、バス乗り場の前にあるスープカレー屋さんは空いていたので、そこでお昼を食べることにしました。

バスで函館空港へ向かいました。 荷物が結構増えていたので預け入れをしたのですが、そのときに航空会社の方から「座席をエコノミーに変更してもらえませんか」という相談を受けました。 もともと少し良い席を取っていたのですが、特に問題もなかったので、そのままオファーを受けることにしました。 搭乗口の前で呼び出されてチケットを変更しました。 たまに見る呼び出されている人はこういう感じなのかと、実績解除できました。
帰りは無事に羽田空港に到着して、そのままスムーズに帰宅しました。
帰宅後Spinelで遊んでバグ報告しました。
WSL2のディスクサイズを縮小する手順
WSL2のディスク(.vhdx)は動的拡張です。使っていると段々大きくなっていきます。
WSL内のファイル削除しても、Windowsからは空き領域が検知できずvhdxファイルの物理サイズは縮まりません。
exportで実データをtarに書き出す。importで再作成すると、未使用領域を除いた状態でvhdxを再作成できます。
事前準備
WSL内を掃除
sudo apt clean sudo apt autoremove docker system prune -a
TRIM
sudo fstrim -av
縮小手順
WSLを停止
wsl --shutdown
export
wsl --export Ubuntu D:\backup\ubuntu.tar
補足
pax format cannot archive socketsは 無視してOK(正常)- 実データ量だけtarになる
既存WSL削除
wsl --unregister Ubuntu
新しい保存先を作成
Microsoft StoreからインストールしたUbuntuはC:\Users\<ユーザー>\AppData\Local\Packages\...を使っています。
importするときは、使用ディレクトリを指定します。
mkdir C:\wsl\ubuntu
import
wsl --import Ubuntu C:\wsl\ubuntu D:\backup\ubuntu.tar --version 2
新しい ext4.vhdx が作られる
デフォルトユーザー復元
import後はrootになるので修正
sudo nano /etc/wsl.conf
内容:
[user] default=ledsun
wsl --shutdown
Gem.ruby_api_versionについて
ruby.wasmの怪現象 - @ledsun blog についてrubygemsにissueつくるかー、と思って調べていたら・・・迷宮入りしました。 現状をメモしておきます。
背景
ruby.wasm では、Ruby VM 初期化直後に次のような状態で返ることがあります。
Gemは定義されているrubygems.rbは未ロードGem.ruby_api_version/Gem.extension_api_versionは未定義
WASI向けにbundle install --standalone で生成される bundler/setup.rb を使うときに問題になります。k
ruby.wasm 側ではワークアラウンドを PR #622 で入れてあります。
Bundler に立てようと思っているIssueの概要
Bundler の standalone generator は generated setup.rb の中で:
require "rbconfig"は行うunless defined?(Gem)のときだけGem.ruby_api_version/Gem.extension_api_versionの簡易実装を定義する
そのため、今回のように
Gemは defined- しかし RubyGems API surface は未完成
という状態だと、フォールバック定義が入らず、 Gem.ruby_api_version を前提にしているソースコードが壊れます。
次のように個別にチェックすればいいのかな?とイメージしています。
def self.ruby_api_version RbConfig::CONFIG["ruby_version"] end unless respond_to?(:ruby_api_version) def self.extension_api_version if "no" == RbConfig::CONFIG["ENABLE_SHARED"] "\#{ruby_api_version}-static" else ruby_api_version end end unless respond_to?(:extension_api_version)
Bundlerの既存 Issue
Standalone bundle's setup.rb should implement Gem.try_activate to avoid breaking binding.irb · Issue #7545 · ruby/rubygems · GitHub が近いです。
standalone の setup.rb がpartial な Gem surface が問題を起こしています。
また、コメントで次のような設計案が出ています。
Gem.ruby_api_versionをRbConfig.ruby_api_versionに移せないかGem.extension_api_versionもRbConfig側に移せないかGem.*は将来的に deprecated にできないか
もし、これが実現すればアドホックにruby_api_versionを定義する必要が無くなります。
今回の問題の根本的な解決になりそうです。
bugs.ruby-lang.orgの既存議論
RbConfig.ruby_api_version の提案はありませんでした。
関連しそうな議論:
- Feature #16742: RbConfig.windows? and RbConfig.host_os - Ruby - Ruby Issue Tracking System
- Feature #6648: Provide a standard API for retrieving all command-line flags passed to Ruby - Ruby - Ruby Issue Tracking System
特に #6648 はとても長くて、議論に追いつけていません。
RbConfig.ruby_api_version を提案するなら、それなりに準備が必要そうです。
Gem.ruby_api_versionと RbConfig::CONFIG["ruby_version"] の違い
Gem.ruby_api_version は Gem.target_rbconfig["ruby_version"]を見ているようです。
これはクロスコンパイルと関係があるようですが、僕にはよくわかりません。
今のところ考えていること
とりあえず rubygems/rubygems にIssueを作るのは良さそうです。
あとは、せっかくRubyKaigiが近いので、RbConfigについての理解を深めておいて、誰かに直接相談したいところです。
Fiber initialization failure on WASI/ruby.wasm raises TypeError instead of FiberError
Rubyの不具合を再現するために - @ledsun blog でエラーの取り方を工夫しようと思いましたが、結局諦めました。 仕方が無いので、現状のややこしい再現コードのままバグ報告しようと思います。
というわけで英語で書いた説明の下書きです。 十分にややこしい説明になりました。 今日は見直す気力が尽きたので、後日見直します。
Summary
When Ruby is built for WASI and used through ruby.wasm, a Fiber initialization failure can surface as:
TypeError: wrong argument type false (expected Class)
instead of the expected FiberError.
This seems to happen because fiber_pool_expand() can call:
rb_raise(rb_eFiberError, ...)
during Init_Cont(), before rb_eFiberError has been initialized.
Reproduction environment
I observed this with ruby.wasm built against a Ruby revision older than:
For example, the parent revision reproduced the issue:
4f7dfbe58ee2915b0724251c6464c9b4e0c34245
The issue was observed during ruby.wasm VM initialization, before running user Ruby code.
Reproduction steps
Build ruby.wasm with a Ruby revision older than eac35edfd1101e8f7c34dbdd7b595fdac8f0ad4c.
For example, create build_manifest.json at the ruby.wasm repository root:
{"ruby_revisions":{"head":"4f7dfbe58ee2915b0724251c6464c9b4e0c34245"}}
Then rebuild from a clean state:
rm -rf build rubies rake npm:ruby-head-wasm-wasi
Add temporary instrumentation around ruby_setup() in ruby.wasm's initialization code.
In packages/gems/js/ext/js/witapi-core.c, replace ruby_init() with ruby_setup() and print rb_errinfo() when it fails:
int state = ruby_setup(); if (state) { fprintf(stderr, "ruby_setup state=%d\n", state); VALUE errinfo = rb_errinfo(); if (!NIL_P(errinfo)) { VALUE klass_name = rb_class_name(CLASS_OF(errinfo)); VALUE message = rb_funcall(errinfo, rb_intern("message"), 0); fprintf(stderr, "errinfo class=%s\n", StringValueCStr(klass_name)); fprintf(stderr, "errinfo message=%s\n", StringValueCStr(message)); } exit(EXIT_FAILURE); }
The actual failure happens before user Ruby code runs, so I used temporary instrumentation around ruby_setup() to inspect rb_errinfo().
Run a minimal JS driver that only initializes the VM:
import fs from "node:fs/promises"; import { WASI } from "node:wasi"; import { RubyVM, consolePrinter } from "@ruby/wasm-wasi"; const bytes = await fs.readFile("./packages/npm-packages/ruby-head-wasm-wasi/dist/ruby.wasm"); const module = await WebAssembly.compile(bytes); const wasi = new WASI({ version: "preview1", returnOnExit: false, args: ["ruby.wasm"], env: { RUBY_FIBER_MACHINE_STACK_SIZE: String(1024 * 1024), RUBY_FIBER_VM_STACK_SIZE: String(1024 * 1024), }, }); const vm = new RubyVM(); const imports = { wasi_snapshot_preview1: wasi.wasiImport }; vm.addToImports(imports); const printer = consolePrinter({ stdout: (s) => process.stdout.write(s), stderr: (s) => process.stderr.write(s), }); printer.addToImports(imports); const instance = await WebAssembly.instantiate(module, imports); printer.setMemory(instance.exports.memory); await vm.setInstance(instance); wasi.initialize(instance); vm.initialize();
Observed result
With instrumentation around ruby_setup() in ruby.wasm, initialization fails with TAG_RAISE:
ruby_setup state=6 errinfo class=TypeError errinfo message=wrong argument type false (expected Class)
The failure happens during vm.initialize() on the JavaScript side.
Expected result
If Fiber initialization fails, the raised exception should be FiberError, not TypeError.
Suspected cause
There seem to be two related code shapes: the revision where I reproduced the error, and current master.
In the reproduced revision
In that revision, fiber_pool_expand() could directly raise with rb_eFiberError:
https://github.com/ruby/ruby/blob/4f7dfbe58ee2915b0724251c6464c9b4e0c34245/cont.c#L557
rb_raise(rb_eFiberError, "can't set a guard page: %s", ERRNOMSG);
But this could happen while Init_Cont() was still running, before rb_eFiberError was initialized:
https://github.com/ruby/ruby/blob/4f7dfbe58ee2915b0724251c6464c9b4e0c34245/cont.c#L3387-L3409
fiber_pool_initialize(&shared_fiber_pool, stack_size, FIBER_POOL_INITIAL_SIZE, vm_stack_size); rb_cFiber = rb_define_class("Fiber", rb_cObject); rb_define_alloc_func(rb_cFiber, fiber_alloc); rb_eFiberError = rb_define_class("FiberError", rb_eStandardError);
So rb_raise() received an uninitialized rb_eFiberError, which appears to explain the secondary error:
TypeError: wrong argument type false (expected Class)
On current master
On current master, fiber_pool_expand() no longer directly raises on the guard page setup failure. It now returns NULL with errno set:
https://github.com/ruby/ruby/blob/7209523ffd909ed1914f4ec2544d327a950b19d2/cont.c#L600-L607
However, the initialization order in Init_Cont() still appears to be the same:
https://github.com/ruby/ruby/blob/7209523ffd909ed1914f4ec2544d327a950b19d2/cont.c#L3656-L3678
fiber_pool_initialize() is still called before rb_eFiberError is initialized.
So if fiber_pool_initialize() fails during Init_Cont(), it may still raise with rb_eFiberError before rb_eFiberError is initialized.
Relation to eac35edf
This was exposed by a WASI-specific guard page setup failure. That specific failure path was later avoided by:
However, I think the observed TypeError is a separate secondary issue. It seems to be caused by fiber_pool_expand() raising with rb_eFiberError before rb_eFiberError is initialized in Init_Cont().
So even though eac35edf avoids the specific WASI mprotect(PROT_NONE) failure, the initialization-order issue may still be worth fixing independently.
Rubyの不具合を再現するために
ruby.wasmのデバッグ - @ledsun blog で見つけたRuby側の不具合を再現するコードを作成中です。
まず、Rubyの初期化に失敗するコミットを指定してruby.wasmをビルドします。
build_manifest.jsonを作ります。
{"ruby_revisions":{"head":"4f7dfbe58ee2915b0724251c6464c9b4e0c34245"}}
ruby.wasmをビルドします。
rake npm:ruby-head-wasm-wasi
次に、Node.jsからruby.wasmを初期化するとエラーを再現できそうです。
import fs from "node:fs/promises"; import { WASI } from "node:wasi"; import { RubyVM, consolePrinter } from "@ruby/wasm-wasi"; const bytes = await fs.readFile("./packages/npm-packages/ruby-head-wasm-wasi/dist/ruby.debug+stdlib.wasm"); const module = await WebAssembly.compile(bytes); const wasi = new WASI({ version: "preview1", returnOnExit: false, args: ["ruby.wasm"], env: { RUBY_FIBER_MACHINE_STACK_SIZE: String(1024 * 1024), RUBY_FIBER_VM_STACK_SIZE: String(1024 * 1024), }, }); const vm = new RubyVM(); const imports = { wasi_snapshot_preview1: wasi.wasiImport, }; vm.addToImports(imports); const printer = consolePrinter({ stderr: (s) => process.stderr.write(`[ruby stderr] ${s}`), }); printer.addToImports(imports); const instance = await WebAssembly.instantiate(module, imports); printer.setMemory(instance.exports.memory); await vm.setInstance(instance); wasi.initialize(instance); vm.initialize();
こんな感じです。
►node repro.mjs (node:183986) ExperimentalWarning: WASI is an experimental feature and might change at any time (Use `node --trace-warnings ...` to show where the warning was created) [ruby stderr] ruby_setup state=6 [ruby stderr] errinfo class=TypeError [ruby stderr] errinfo message=wrong argument type false (expected Class)
エラーが起きることは期待通りです。 「ここで出るエラーがTypeErrorじゃなくてFiberErrorになってほしい」が、私が主張したいRubyの不具合です。
まだ、ちょっと問題があります。 https://github.com/ruby/ruby.wasm/blob/fb4a4e89184f306a4d1d802d0ac60e5b6c3cd136/packages/gems/js/ext/js/witapi-core.c#L205 に次のパッチを当てないといけません。
int state = ruby_setup(); if (state) { VALUE errinfo = rb_errinfo(); fprintf(stderr, "ruby_setup state=%d\n", state); if (!NIL_P(errinfo)) { VALUE klass_name = rb_class_name(CLASS_OF(errinfo)); VALUE message = rb_funcall(errinfo, rb_intern("message"), 0); fprintf(stderr, "errinfo class=%s\n", StringValueCStr(klass_name)); fprintf(stderr, "errinfo message=%s\n", StringValueCStr(message)); } exit(EXIT_FAILURE); }
元ネタはRubyのソースコードにあります。 https://github.com/ruby/ruby/blob/d95afb737e55dca56851ec5933637d217deec805/eval.c#L98-L108
ruby_init(void) { int state = ruby_setup(); if (state) { if (RTEST(ruby_debug)) { rb_execution_context_t *ec = GET_EC(); rb_ec_error_print(ec, ec->errinfo); } exit(EXIT_FAILURE); } }
そもそもデバッグ情報を出力してそうです。 でも、上手く出力できないんですよ。 ここをクリアして、スパッと再現コード作りたいなーって頑張っているところです。
fish shell 4を入れる
Release fish 4.6.0 · fish-shell/fish-shell · GitHub がリリースされています。 手元の環境を見ると
►fish --version fish, version 3.7.1
随分古いです。 WSLにfish shellをいれる - @ledsun blogみると、aptのリポジトリに3系を指定していました。
fish shell - 4.x release series : “Fish shell maintainers” team の情報を元に4系リポジトリを追加しました。
sudo add-apt-repository ppa:fish-shell/release-4 sudo apt update sudo apt upgrade
無事4.5になりました。
►fish --version fish, version 4.5.0
4.6にならないのはリリースが1時間前だからだと思います。

ruby.wasmのデバッグ
ruby.wasmでテストが落ちる現象を発見しIssueを報告しました。
それから、Codex CLIとデバッグをしました。 状況が複雑過ぎて、僕の理解可能なコンテキストを超えています。 一度、ここにまとめます。
というか、Codex CLIにまとめてもらいました。
現状はこう整理できます。
最初に見えていたのは Node 側の
UnhandledPromiseRejection ... "Symbol(kExitCode)"でした。これは Node WASI のreturnOnExit: trueによる sentinel で、Ruby 側のexitやproc_exitがそのままSymbol(kExitCode)として JS 側へ飛んできていただけでした。packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs側でこれを吸収するようにした結果、ノイズなUnhandledPromiseRejectionは消え、単純な exit code だけが見えるようになりました。そこから追うと、test-unit 実行以前に
vm.initialize()の段階で Ruby 本体が落ちていました。さらに掘るとruby_setup()がTAG_RAISEで失敗していて、当初見えていた例外はTypeError: wrong argument type false (expected Class)でした。これは本当の原因ではなく、Ruby 本体のInit_Contでrb_eFiberErrorを定義する前にrb_raise(rb_eFiberError, ...)していたため、未初期化のrb_eFiberErrorがfalse扱いされて壊れた例外になっていた、という二次バグでした。Ruby 本体側に診断を入れて追った結果、失敗地点は
build/checkouts/head/cont.cのInit_Cont内、fiber_pool_initialize(...)から呼ばれるfiber_pool_expand(...)だと分かりました。そこでは fiber stack 用 guard page を張るためにmprotect(page, RB_PAGE_SIZE, PROT_NONE)を使っていますが、Node 上の wasip1 実行ではここが失敗していました。rb_eFiberErrorの代わりに一時的にrb_eRuntimeErrorで投げるようにすると、本来のエラーが見えました。最終的に見えている実際の失敗はこれです。
RuntimeError: can't set a guard page: Invalid argument要するに、問題は 2 層あります。
- 本質的な失敗 wasip1 上で fiber guard page 用の mprotect(PROT_NONE) が成立せず、Ruby 初期化が Init_Cont で止まる
- Ruby 本体の別バグ その失敗を c未初期化のまま raise していたため、最初は
TypeError: wrong argument type false (expected Class)に化けていたなので、
Symbol(kExitCode)は根本原因ではなく、最初に表面に出ていた Node 側の見え方でした。実際の本丸は Ruby 本体 cont.c の fiber guard page 初期化失敗です。次の対応方針は自然に 2 つです。
- ruby.wasm 側では downstream patch で wasm32-wasi の guard page 処理を無効化するか、mprotect 失敗を許容して起動できるようにする
- Ruby upstream 側には
- wasm32-wasi で guard page 前提が破綻している件
- rb_eFiberError 未初期化で壊れた例外を出す件 の両方を報告する
問題を整理します。
問題1
Codexは見落としているけど、「Node WASIでruby.wasmを動かしたときにRuby内でexitするとJS側でSymbol(kExitCode)が飛ぶ」があります。
https://github.com/nodejs/node/blob/052aec7127f40f8b70fc185bf6091f10a259d376/lib/wasi.js#L102
の動作をruby.wasm側で見落としていそうです。
問題2
「wasip1 上で fiber guard page 用の mprotect(PROT_NONE) が成立しない」 これは僕にとって一番難解です。 https://github.com/ruby/ruby/blob/d95afb737e55dca56851ec5933637d217deec805/cont.c#L559の分岐がwasip1の時に漏れているのでしょうか? Codexとパッチを書きながら、実際に動かして理解を深めていくと良さそうです。
問題3
「rb_eFiberError 未初期化で壊れた例外を出す」はhttps://github.com/ruby/ruby/blob/d95afb737e55dca56851ec5933637d217deec805/cont.c#L565
に到達したときにrb_eFiberErrorが未初期化という、そのまんまではないかと思います。
再現コードを作ってhttps://bugs.ruby-lang.org/に、報告するのが良さそうです。
「https://github.com/ruby/ruby/blob/d95afb737e55dca56851ec5933637d217deec805/cont.c#L3502 でFiberの初期化に入るけど、rb_eFiberErrorの初期化はhttps://github.com/ruby/ruby/blob/d95afb737e55dca56851ec5933637d217deec805/cont.c#L3524」という単純な話に思えます。
20260322追記
問題2の原因は僕がPCに残っていた、古いRubyのソースコードを使っていることでした。
ruby.wasmの怪現象
ruby.wasmでは、CRubyと同様にRubyのVMを初期化し、その上でRubyスクリプトを動かします。
CRubyでRubyのVMを初期化が完了すると定数Gemが定義されてかつrubygems.rbが読み込まれています。
Ruby WASM上でRuby VMを初期化すると、Gemは定義されているのに、rubygems.rbは読み込まれていない状態で返ってくることがあります*1。
通常は、問題になりません。
Bundlerをスタンドアローン形式でインストールした際、つまりbundle install --standaloneで作成されるbundler/setup.rbコマンドを使うと問題になります。
ruby.wasmではWASIのコンポーネントモデルを使う場合に、Bundlerをスタンドアローン形式で使っています。 このとき問題が起きます。
理由は以下の通りです:
bundler/setup.rbのフォールバック処理において、Gemが定義されているかどうかをチェックしているrubygems.rbで定義されているメソッドが存在するかどうかは見ていない
ruby.wasm側は https://github.com/ruby/ruby.wasm/pull/622 でパッチを当てて修正しました。 Bundler側にも修正を入れた方がよい気もします。一方ruby.wasm特有の問題な気もします。 Bundler側に説明 or 相談するとして、どう説明したものかわかりません。
考えをまとめるために、とりあえずこの文章を書いています。
*1:私にはなぜこれが起きるのかわかっていません
Ubuntuのrustupをsnapをやめる
AIエージェントが動くサンドボックスから、snapアプリケーションを動かすのは制約があるみたいです。 CUIアプリケーションのインストールにはsnapは使わない方が良さそうです。 rustをsanpで入れていたので、入れ直します。
アンインストール
sudo snap remove rustup
再インストール
curl https://sh.rustup.rs -sSf | sh
1) Proceed with installation (default)
を選択。
確認
►which rustc /home/ledsun/.cargo/bin/rustc
