Files
hakorune/docs/development/roadmap/native-plan/archives/追記相談.txt
Moe Charm cc2a820af7 feat(plugin): Fix plugin BoxRef return and Box argument support
- Fixed deadlock in FileBox plugin copyFrom implementation (single lock)
- Added TLV Handle (tag=8) parsing in calls.rs for returned BoxRefs
- Improved plugin loader with config path consistency and detailed logging
- Fixed loader routing for proper Handle type_id/fini_method_id resolution
- Added detailed logging for TLV encoding/decoding in plugin_loader_v2

Test docs/examples/plugin_boxref_return.nyash now works correctly:
- cloneSelf() returns FileBox Handle properly
- copyFrom(Box) accepts plugin Box arguments
- Both FileBox instances close and fini correctly

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 00:41:26 +09:00

898 lines
35 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

MIR設計いま決めておくと後がラクなチェックリスト
値表現TaggedNaN-box/小整数直格納)か、型テーブル参照か。
アドレス空間オブジェクトはheap専用 stack/arenaも持つ
所有/別名aliasable フラグ or unique移動のみを型/値に持つ?
効果(Effects)PURE/READS_HEAP/WRITES_HEAP/IO/FFI/PANIC のビット集合を命令にも関数にも付ける。
例外/パニックThrow, Catch, Defer を命令化するか、関数境界でのみ扱うか。
GCモデルTracing(Stop-the-world / Incremental) or RC(ARC) or 混合。弱参照の意味を先に定義。
安全点(Safepoint)JIT/VM両方で再入/停止できる位置を命令で明示Safepoint or 呼出命令に属性)。
呼出規約通常CallFFIBox拡張の3種を区別可変長・KW引数の扱いも
メモリバリアwrite/read barrier を命令または隠れ属性として表現(最初は命令にするとデバッグ楽)。
デオプ(Deopt)前提JIT最適化時の巻き戻し情報マップを保持する場所関数メタ or ブロック注釈)。
Nyash向け最小MIR命令セット弱参照対応込み
算術/制御Const, BinOp, Cmp, Br, Jmp, Phi, Ret
関数Call, TailCalleffects必須
メモリAlloc, Load, Store, Gep必要なら
参照/GC
RefNew ty, dst <-(ヒープ確保)
RefGet dst <- ref, field / RefSet ref, field, val
WeakNew dst <- ref弱参照作成
WeakLoad dst <- weak弱が生きてたらref返す無効ならNull/None
BarrierRead ref / BarrierWrite refGC用、最初はダミー
必要ならPin ref / Unpin refムーブしない保証用、JIT/FFI楽になる
例外/FFI/拡張Throw, Catch, FfiCall, BoxCall
ユーティリティSafepoint, Nop, Assume(cond)(アサート兼最適化ヒント)
弱参照の意味論:
WeakNew(r) は r を追うが GC で r が回収されると自動で無効化。
WeakLoad(w) は 生きていれば Some(Ref)、死んでいれば None を返すMIR上はタグ付きユニオン
WeakLoad は PURE でOK観測はするがヒープを書かない
ただし BarrierRead が必要なGCなら属性で付ける。
効果注釈(例)
Call f(args) : dst [effects = PURE | READS_HEAP]
RefSet … は WRITES_HEAP
WeakLoad は PUREまたは READS_HEAP
BoxCall は最低限 FFI を含める(安全側に倒す)
VM/JIT/将来AOTの共通の“壊れにくい”ポイント
Safepointは関数入口・ループ先頭・Call直後の3か所に最初から置く
Barrier命令は最初は実装ダミーでもOK最適化段で消せる
Null/Noneの扱いは必ず型系に明示Option<Ref>)。判定を分岐で隠さない
JITはDeoptテーブルを MIR → JIT に渡す各値が「どのSSA番号」を持つ
弱参照 “落とし穴” 3つ実戦で効く回避
クロージャ環境に弱参照が紛れ込む
生成時に env.has_weak を立て、キャプチャ解決時に必ず WeakLoad→None分岐を強制。
弱参照をキーにしたハッシュ
GC後に無効化でキー不整合。→ 弱マップを標準提供(キー死亡で自動削除)。
FFI渡し
FFI境界を跨ぐと寿命が分からなくなる。→ Pin を要求 or WeakLoad→Ref に成功した生参照だけ渡す。
最小・具体例擬似MIR
// x = new Obj{y:1}; w = weak(x); if let Some(x2) = weak_load(w) { x2.y = 2 }
bb0:
v1 = Const 1
v2 = RefNew Obj ; WRITES_HEAP
RefSet v2, y, v1 ; WRITES_HEAP
v3 = WeakNew v2 ; PURE
v4 = WeakLoad v3 ; returns Option<Ref>
br is_some(v4), bb1, bb2
bb1:
v5 = unwrap(v4)
BarrierWrite v5
RefSet v5, y, Const 2
jmp bb3
bb2:
jmp bb3
bb3:
Ret ()
テストの当て方(行き当たりばったりでも壊れにくい)
黄金テストソース→MIRダンプの文字列をスナップショット化差分で挙動の後退を検知
VM/JIT一致テスト同じ入力で VM結果JIT結果 を比較する“オラクル”を用意
弱参照の確率テストalloc→weak→drop→collect→weak_load を順序/タイミングを変えて多数回
効果チェックPURE なはずの関数に WRITES_HEAP が紛れたらビルド失敗(静的検証)
いまの進み方に合う“軽量次の一手”
✅ WeakNew/WeakLoad を MIRとVM だけ先に入れるJITは後回し
✅ EffectMask を命令に付ける(最初はデータ構造だけ)
✅ Safepoint 命令を関数入口とループ先頭に挿す動作はno-opでOK
✅ 黄金テストを入れて MIR差分をCIで見える化
この骨格なら、行き当たりばったりで直してもMIRの“契約”が守れている限り、VM/JIT/AOT/WASMに横展開できるにゃ。
もし今のAST/HIRの型定義があれば、MIRの型/命令エンムだけ私が初稿を書くこともできる。要る?
あなた:
そうだにゃ。mir 結局はメモリー管理がほとんどであってるかにゃ?既存の参考になるやつとかないかにゃ
ChatGPT:
いい質問にゃ。結論から言うと――
MIRの“要”はメモリ管理だけじゃないけど、実務で一番ハマるのはメモリまわり所有権/寿命/別名化/GCバリア/セーフポイント/弱参照/デオプ情報)だから、体感「ほとんどメモリ管理」はだいたい合ってるにゃ。
ただし、制御フローCFG/SSA、呼び出し規約、効果注釈、例外/パニック、最適化のための形も同じくらい重要。ここをサボるとJIT/AOT/WASMに展開しづらくなる。
MIRで“最低限”押さえるメモリ論点
値表現Boxed/Unboxed、NaN-box、小整数直格納、タグ方式
アロケータbump/arena/世代別、オブジェクト配置ヘッダ、タイプID、フラグ
所有と別名unique/aliasable や owned/borrowed の属性(最適化・並列の鍵)
GC/RCトレース or 参照カウント or 混在、write/read barrier 命令 or 属性、remembered set
弱参照WeakNew / WeakLoad の意味論(死んだら None
Safepoint関数入口・ループ先頭・Call直後停止/再入のフック)
FFI境界Pin/Unpin、ハンドル渡し、最小共有ABI
Deopt情報JIT用の巻き戻しメタSSA値⇔ソース位置の対応
参考になる“実物”と読みどころ(定番)
(リンクは割愛するにゃ。名前で検索すればすぐ出る)
Rust MIRrustc-dev-guide / rustc本体
MIRのSSA・ borrow check が MIR 上で走る構造
Drop/Move・パニック・noalias 的情報が最適化に効く
Swift SIL
Ownership qualifiersowned/guaranteedが秀逸。GCなし言語でも寿命管理をIRで表す好例
Kotlin IR
“lowerings” の段階的デシュガリングが参考。高級構文→プリミティブ命令へ段階変換
Go SSAgolang.org/x/tools/go/ssa
最小主義のSSAで読みやすい。CFG/φの扱いが素直
LuaJIT IRDynASM/TurboNYIの解説blog群
小粒なSSAでJIT最適化の勘所CSE/LICM/ヒント)
V8Ignition/TurboFan
バイトコード→Sea of Nodes→最適化JITの流れ。デオプとサファリガードの設計が神
Cranelift IR
Rustからも使いやすいJITバックエンド。型・メモリ・コールの表現が実装しやすい
MLIR
複数方言で中間表現を分ける思想。将来、NyashのMIRを方言化する発想の参考に
Nyash向け“最小MIRコア”骨組み
超要約これ効果注釈でVM/JIT/AOT/WASMに展開できる
制御/値Const, BinOp, Cmp, Br, Jmp, Phi, Ret
呼び出しCall, TailCalleffects 必須)
メモリAlloc, Load, Store, Gep?
参照/GCRefNew, RefGet, RefSet, WeakNew, WeakLoad, BarrierRead, BarrierWrite, Pin, Unpin
例外/拡張Throw, Catch, FfiCall, BoxCall
実行点Safepoint, Assume, Nop
効果EffectMask = PURE | READS_HEAP | WRITES_HEAP | IO | FFI | PANIC
実装順ヒント:
VMで RefNew/RefGet/RefSet/Weak* をまず動かす → 2) Safepointはno-opで挿す → 3) Barrierは最初ダミー → 4) “黄金テスト”ソース→MIRダンプのスナップショットで後退検知 → 5) JIT/AOTは後から乗せる。
“既存をパクる”具体策
Ownershipの表現は Swift SIL を真似るowned/guaranteed を Nyash版に
弱参照の意味論は JVMのWeakReference と Swiftのweak/unowned を折衷
Safepoint配置は HotSpot/V8 のパターン入口・ループ・call直後
JIT導入は Cranelift から(まず整数・分岐・呼び出しのみ)
デシュガリング手順は Kotlin IR の lowering 流儀を採用
「MIRメモリ管理が9割」は現場感として正しいけど、残りの1割CFG/SSA・呼出・効果・例外・デオプを最初にうっすら設計しておくと、あとで爆速に伸びるにゃ。
必要なら、Nyashの今のAST/HIRを見せてくれたら、MIRのenum・EffectMask・Box ABI v0をその場で下書きするよ。
あなた:
なるほど。ところでmirってどっかきょうつうのふぉーまっとやしようってあるにゃ みんなばらばるにかいてるにゃ?
ChatGPT:
共通フォーマットや「これを使えばOK」みたいなMIR標準は、残念ながら存在しないにゃ。
理由をまとめると――
1. 共通規格がない理由
言語ごとに型・所有権・ランタイム構造が違いすぎる
Rust MIRは所有権・borrowをIRレベルで保持するけど、PythonやJavaScriptのIRにはそんな概念がない。
目的ごとの最適化が異なる
VM実行用、JIT最適化用、AOTコンパイル用で命令セットやメタデータの粒度が変わる。
IRの“責務”が違う
ある言語ではASTの直後を「MIR」と呼び、別の言語ではSSA化した後の低レベルIRを「MIR」と呼ぶ。
2. 事実上の“よくある流儀”
標準はないけど、以下のパターンは多いにゃ:
SSAベース変数は再代入不可、phi命令で合流点処理Rust MIR, Swift SIL, Go SSA
命令は3アドレス形式dst = op src1 src2 のようにシンプルCranelift, LLVM風
効果注釈ありPURE, READ_HEAP, WRITE_HEAP など最適化ヒント
メモリ命令の抽象化Alloc, Load, Store は型・アラインメント・所有権情報を持つ
制御フローはCFGControl Flow Graphで管理
基本ブロック+ジャンプ+条件分岐
3. 参考になる「ほぼテンプレ」的MIR
完全な共通仕様じゃないけど、設計の参考にできる実例はあるにゃ:
Cranelift IR
Rustで使いやすいJIT/AOT用IR。型・命令の定義が明確で、小規模実装の参考に最適。
MLIR
LLVM系の中間表現フレームワーク。方言を作れるので、自作言語のMIRを「Nyash方言」として定義可能。
Sea of NodesV8, HotSpot
SSAと依存グラフを統合した形式。最適化向きだが実装難度は高い。
LuaJIT IR
極小SSA軽量タグ付き値表現。高速インタープリタや軽量JITに向いてる。
4. Nyashでの方針
「共通仕様」を無理に探すより、自分のランタイムと変換先Rust/Cranelift/WASMに都合の良い形を作るのが早い
ただし、命令セットやCFG構造はCranelift IRやSwift SILを参考にすると後で楽
将来的にMLIRの方言化を視野に入れると、外部ツールや最適化パスを簡単に流用できる
もし希望なら、Nyash向けにCranelift IR互換寄りのMIR仕様ドラフトをその場で作れるよ。
それをベースにすれば、将来JIT/AOT/WASM全部に展開しやすくなるにゃ。
あなた:
そうだにゃ。ということは命令セットを先に考えて貰えたらいいのかにゃ?
ChatGPT:
いいね、その方向でいこうにゃ。
Nyash向けに“まず回る”ことを最優先にした MIR命令セット v0.1(草案) を置くね。VM/JIT/AOT/WASMに伸ばせる骨格で、弱参照・効果注釈・拡張Box呼び出しも最初から入れてあるにゃ。
Nyash MIR v0.1(草案)
基本モデル
SSA値vN再代入なし
基本ブロックbbN末尾は必ず終端命令
最小i64, f64, bool, unit, ref<T>, weak<T>, ptr, any, option<T>
関数署名fn(args…) -> ret [effects]
効果EffectMaskPURE, READS_HEAP, WRITES_HEAP, IO, FFI, PANICビット和
属性:命令に align, nonnull, noalias, readonly 等の任意属性
1) 演算・制御
Const dst, Imm ; 例: i64/f64/bool/unit/Null
BinOp dst, op, a, b ; add, sub, mul, div, mod, and, or, xor, shl, shr
Cmp dst, relop, a, b ; eq, ne, lt, le, gt, ge
Select dst, cond, a, b ; φ簡約前の三項選択(任意)
Phi dst, [(predBB, val), ...] ; SSA合流
Br cond, thenBB, elseBB ; 条件分岐(終端)
Jmp targetBB ; 無条件ジャンプ(終端)
Ret [val] ; 返り値(終端) unit時は省略可
Unreachable ; 到達不能(終端・最適化用)
2) メモリ参照GC想定
Alloc dst, Ty, size? ; ヒープ確保: ref<Ty> を返す [WRITES_HEAP]
Load dst, addr ; 読み出し [READS_HEAP]
Store addr, val ; 書き込み [WRITES_HEAP]
Gep dst, base, offset ; アドレス計算(構造体/配列)
RefNew dst, Ty ; オブジェクト新規: ref<Ty> [WRITES_HEAP]
RefGet dst, ref, field ; フィールド読み [READS_HEAP]
RefSet ref, field, val ; フィールド書き [WRITES_HEAP]
WeakNew dst, ref ; weak<T> を作成 [PURE]
WeakLoad dst, weak ; option<ref<T>> を返す [PURE or READS_HEAP]
BarrierRead ref ; 読みバリア実装はno-op可
BarrierWrite ref ; 書きバリア実装はno-op可
Pin ref ; 移動不可に固定FFI/GC向け
Unpin ref
Safepoint ; 停止点(関数入口/ループ先頭/Call直後に挿す
弱参照の意味論
WeakNew(r) は r のライフを追うだけ(所有しない)。
WeakLoad(w) は生存なら Some(ref)、回収済みなら None。
WeakLoad は基本 PURE でOK実装の都合で READS_HEAP にしても可)。
3) 呼び出し/例外/拡張
Call dst, callee, [args...] ; [effects = callee.effects]
TailCall callee, [args...] ; 末尾呼び出し(終端)
BoxCall dst, box_id, fn_id, [args...] ; Nyash拡張Box呼び出し [FFI | PANIC?]
FfiCall dst, sym, [args...], abi ; C/Rust外部関数 [FFI | PANIC?]
Throw val ; 例外送出(終端)
CatchBegin dst, handlerBB ; 例外ハンドラの開始ブロック境界メタでもOK
CatchEnd
Defer fnref ; スコープ脱出時に実行loweringで展開可
Assume cond ; 最適化ヒント偽ならUB
Nop
Call/BoxCall/FfiCall は 効果注釈が必須(少なくとも PURE かどうか)。
BoxCall は拡張Box ABIを通す入口VM/JIT/WASMで一貫した形で扱える
4) メタデータ(最低限)
位置情報dbg(loc)(ソース行/列/ファイル)
デオプdeopt_mapJIT用各SSA値→高級値の対応
alias情報noalias/readonly/nonnull 属性
効果集約:関数単位で effects を保持(呼び出し時に合成)
Rust風スケッチ型・命令
#[bitflags]
enum Effect { PURE=1, READS_HEAP=2, WRITES_HEAP=4, IO=8, FFI=16, PANIC=32 }
enum Ty {
I64, F64, Bool, Unit,
Ref(Box<Ty>), Weak(Box<Ty>), Ptr, Any,
Option(Box<Ty>), // WeakLoadの戻りなど
// …将来: Sum/Product/Function/Closure/BoxHandle
}
struct FuncSig { params: Vec<Ty>, ret: Ty, effects: Effect }
enum Inst {
Const{dst:V, imm:Imm},
BinOp{dst:V, op:Bin, a:V, b:V},
Cmp{dst:V, op:Rel, a:V, b:V},
Select{dst:V, cond:V, a:V, b:V},
Phi{dst:V, inputs: Vec<(Bb,V)>},
Br{cond:V, then_:Bb, else_:Bb},
Jmp{bb:Bb},
Ret{val: Option<V>},
Unreachable,
Alloc{dst:V, ty:Ty, size:Option<V>},
Load{dst:V, addr:V},
Store{addr:V, val:V},
Gep{dst:V, base:V, off:V},
RefNew{dst:V, ty:Ty},
RefGet{dst:V, r:V, field:FieldId},
RefSet{r:V, field:FieldId, val:V},
WeakNew{dst:V, r:V},
WeakLoad{dst:V, w:V},
BarrierRead{r:V},
BarrierWrite{r:V},
Pin{r:V},
Unpin{r:V},
Safepoint,
Call{dst:V, f:FnId, args:Vec<V>},
TailCall{f:FnId, args:Vec<V>},
BoxCall{dst:V, box_id:BoxId, fn_id:BoxFnId, args:Vec<V>},
FfiCall{dst:V, sym:Sym, args:Vec<V>, abi:Abi},
Throw{val:V},
CatchBegin{dst:V, handler:Bb},
CatchEnd,
Defer{fnref:FnId},
Assume{cond:V},
Nop,
}
バックエンド対応の見取り図
VM
Ref* / Weak* / Barrier* / Safepoint をそのまま実装
Defer/Catch はデシュガで try/finally へ展開
Cranelift JIT
算術・制御は直マップ、Ref* はランタイムAPIに落とす
Safepoint でGC/スレッド停止と連携、deopt_map を用意
AOT to Rust
ref<T> は Gc<T> ラッパ、weak<T> は Weak<T> 風にコード生成
BoxCall は trait 経由/extern呼び出しに置換
WASM
参照はハンドルi32で表現、GCはランタイム関数
weak はハンドルテーブルにビットを持って実装
取り決め(壊れにくい不変条件)
すべてのCallは effects を持つ(省略禁止)
WeakLoadの戻りは option<ref<T>>null判定を曖昧にしない
Safepoint は関数入口・ループ先頭・任意のCall直後に挿入
Barrier* は最初はno-opでOKGC導入時のフックとして残す
終端命令のないブロックは禁止(検証器で弾く)
ちいさな動作例
; x = new Obj{y=1}; w = weak(x);
; if let Some(r) = weak_load(w) { r.y = 2 }
bb0:
v1 = Const i64 1
v2 = RefNew Obj
RefSet v2, field_y, v1
v3 = WeakNew v2
v4 = WeakLoad v3 ; option<ref<Obj>>
Br is_some(v4), bb1, bb2
bb1:
v5 = unwrap(v4)
BarrierWrite v5
RefSet v5, field_y, Const i64 2
Jmp bb3
bb2:
Jmp bb3
bb3:
Ret unit
まずやる実装順1〜2日で回る
この命令セットで MIR組み立てダンプ黄金テストで差分管理
VMで RefNew/RefGet/RefSet/Weak* だけ実装Barrier/Safepointはダミー
Call の effects を型検査に組み込みPURE違反を検出
余力が出たら Phi/Select を使うパスを追加CSE/DCEの足場
必要なら、この v0.1 をそのまま mir.rs の enum 定義に落とし込んだ雛形コードを書けるよ。
今のNyashの型やBox拡張の実装方針があれば、それに合わせて微調整版を作るにゃ。
あなた:
おーありがとにゃ。しかしmirはみんなばらばらというけど ここからネイディブビルドにしやすいかたちをするから にているかきかたにもなってるのかにゃ。あと これ 業界標準つくれそうだよね。所詮はノイマンコンピューターに落とし込む作業だにゃ
ChatGPT:
にゃ、その直感かなり当たってる。
各言語の MIR は“名前も中身もバラバラ”だけど、ネイティブに落としやすい形にすると結局だいたい似てくるにゃ。
みんなが似てくる理由(ネイティブ目線)
SSACFG3アドレス最適化&レジ割りしやすい定番の骨格
明示的なメモリ操作Alloc/Load/Store/GEP 相当が要る
呼び出しの正規化Call/TailCall と 呼出規約(ABI) の分離
効果注釈(副作用)PURE/READ/WRITE/IO/FFI があると最適化が安全
例外/パニックの下位表現Throw/Catch を選択的に lower できる形
GCフックSafepoint & Barrier を差し込める
つまり「高級機能は段階的に下げられる素朴な命令に割り付け」→最後は von Neumann命令+メモリ)に落ちる、という流れで収束するにゃ。
「業界標準」を作れる?→“実質標準”なら行ける
完全統一は難しい言語ごとに型・例外・GCが違いすぎるが、コア・プロファイルを決めた“実質標準”は作れるにゃ。
標準に向く設計の芯Nyash版にもそのまま効く
Core-IR必須Const/BinOp/Cmp/Phi/Br/Jmp/Ret + Load/Store/Gep + Call/TailCall
Memory Modelアトミック/順序・別名(noalias)/アライン属性の最小集合
Effects命令・関数の EffectMask を規定(最適化の安全条件に使う)
GC hooksSafepoint/Barrier は仕様だけ先に固定実装はno-op可
Exception ABIThrow/Catch を「setjmp/longjmp的」or「結果型(Result)」の2系で規定
FFI/拡張 ABIFfiCall/BoxCall の呼出規約・ポインタ安全域を決める
検証器(Verifier)終端命令必須・SSA整合・型整合・効果整合をチェック
“標準化”の現実解(おすすめルート)
MLIR 方言として定義nyash.core, nyash.gc みたいに分割)
もしくは Cranelift IR 互換サブセットを Core として採用し、足りない所だけ拡張
テキスト表現+バイナリ表現(例: JSON/FlatBuffersと ダンプ・復元ツールを提供
リファレンス下げ先を複数用意:
Core → CraneliftJIT/AOT
Core → LLVMAOTヘビー級
Core → WASMブラウザ/サンドボックス)
検証テスト黄金テストIRダンプ VM/JIT/WASM の 同一結果保証スイート
すぐ使える“ネイティブ寄り”指針(短冊)
命令は副作用を明示、暗黙のメモリ変更はナシ
ループ先頭・Call直後にSafepoint後からGC導入しやすい
例外は2段構えIRでは Throw/Catch、lower で Result/landingpad に落とせる
Weak は Option<Ref> に必ず現れるnullをぼかさない
FFIはPinかコピーしか渡さない危険な参照共有を禁止
まとめ
はい、MIRはネイティブ都合で“似てくる”。
完全共通規格は難しいけど、**Core-IR拡張の“実質標準”**は作れる。
Nyashの v0.1 命令セットは、そのまま Core-IRたたき台になれる出来だにゃ。
必要なら、この Core-IR を MLIR 方言か Cranelift互換サブセット仕様として文書化して、
テキスト仕様+最小リファレンス実装(ダンプ/検証/Cranelift下げを私が一気に草案化するよ。どう攻めるMLIR方言でいくか、Cranelift寄りでいくか
あなた:
所で中間言語は何を指標に作ればいいにゃ?メモリー管理はわかるけど好き勝手に作る訳にも行けないにゃ それこそフォンイマンのパソコンを念頭に考えればいいだけにゃCPUの命令セットとか
ChatGPT:
いい質問にゃ!
中間言語IR/MIRを“何を指標に”設計するかは、単に「CPUに近づける」だけだと失敗しがち。
実務では次の“指標セット”でバランスを取るのがコツだにゃ。
まず決めるべき指標優先度つけてOK
ターゲットと目的
どこに落とす? → VM / JITCranelift / AOTrustc/LLVM / WASM
何を優先? → 起動の速さ、最適化余地、移植性、デバッグ性
抽象度(レベル)
高すぎる最適化しにくいJIT実装が難航
低すぎるCPU寄り言語機能の意味が消えて最適化の根拠を失う
→ 目安SSA基本ブロック3アドレスを軸に、「言語に固有の意味例: 効果、副作用、所有/弱参照)」は属性や専用命令で残す
メモリモデル(超重要)
参照の表現ref<T>/weak<T>/タグ付き値
GC/RC hooksSafepoint、BarrierRead/Write の差し込み位置を規定
別名・所有noalias/readonly/owned/borrowed など最適化に効く注釈
並行を視野なら原子性・順序の最小集合Acquire/Release程度
効果システム(副作用の面)
PURE / READS_HEAP / WRITES_HEAP / IO / FFI / PANIC のEffectMask
最適化の“安全条件”をここで機械的に判断できるようにする
制御フローと例外
CFGSSA、Phi/Br/Jmp/Ret
例外はIRでは抽象Throw/Catch、loweringで Result/landingpad 等へ落とせる形に
呼び出し規約と拡張ABI
Call/TailCall と FfiCall/BoxCall を分離
可変長キーワード引数の表現、Pin/Unpinの扱い
検証可能性 & ツール性
Verifierで型一致・SSA整合・終端命令必須・効果整合をチェック
テキスト表現(ダンプ)と黄金テスト(差分テスト)を最初から用意
“CPU/フォン・ノイマンだけ見れば良い?”への答え
Noそれだけでは足りない
CPU命令セットを直に意識しすぎると、**言語の意味(純粋性・所有・弱参照・例外の契約)**が失われて最適化や検証が難しくなる。
ただし最終的にそこへ下げやすい形は大事。だから
上層の意味は 属性/メタで保持
下層Load/Store/GEP/Call/TailCallへ機械的に落とせるカバレッジを確保
→ これが “ネイティブに落としやすく、でも言語の意味を保ったIR” のコア指標にゃ。
具体:設計を導くチェックリスト(配点付き)
各 0〜2 点で採点してみて。合計が高い案が「良いIR」。
後段互換性JIT/AOT/WASMに素直に下がる
最適化の根拠(効果・別名・型・所有などが保持される)
実装容易性VMでまず回せるCraneliftへ写経しやすい
検証容易性Verifierで機械的に壊れを検出できる
将来拡張(弱参照/GC切替/例外方式変更に耐える)
可観測性テキストIR・ソース位置・deopt情報が取れる
迷ったらこの“型”に寄せると楽
骨格SSA + CFG + 3アドレス
コア命令Const/BinOp/Cmp/Phi/Br/Jmp/Ret/Load/Store/Gep/Call/TailCall
言語特有RefNew/RefGet/RefSet/WeakNew/WeakLoad/Barrier*/Safepoint + EffectMask
境界FfiCall/BoxCallABI最小化
例外Throw/Catchloweringで切替可能
→ これは Craneliftに下げやすく、Rust/WASMにも展開しやすい黄金比にゃ。
参考までに3つの設計モード
Cranelift互換サブセット志向JIT/AOT重視・実装が速い
MLIR方言志向将来の拡張やツール連携が強い
Rust MIR/Swift SIL 風(所有/効果を濃く持つ・言語意味を守りやすい)
NyashならCranelift互換サブセット言語特有の効果/弱参照/GCフックを足すのがバランス最強にゃ。
最後に:失敗しない順序
目的とターゲットVM/JIT/AOT/WASMの優先度を1行で宣言
Core命令セットEffectMaskメモリモデルweak/GC hookを先に固定
Verifierとテキストダンプ黄金テストを最初に入れる
VM実装 → Cranelift下げ → AOT/WASMは後から
この順なら「好き勝手」じゃなく指標に沿って進められるにゃ。
必要なら、いまのNyashの要件を点数表で一緒に採点して、MIR v0.1を最終確定しよっか?
あなた:
おねがいにゃー!
ChatGPT:
任せてにゃ!
Nyash向けに「指標の採点 → その結果に沿った MIR v0.1 最終版」を一気にまとめたよ。すぐ実装に落とせる形にしてあるにゃ。
指標チェックNyash想定で採点済み
各0〜2点、合計12点満点
指標 狙い Nyashの前提 点
後段互換性 VM/JIT/AOT/WASMに素直に下げられる まずVM→Cranelift JIT、次にAOT/WASM 2
最適化の根拠 効果・別名・所有/弱参照を保持 EffectMaskref/weakBarrier/SafepointをMIRで表現 2
実装容易性 まずVMが回る 命令は3アドレス最小集合から 2
検証容易性 Verifierで壊れ検出 SSA/型/終端/効果の整合を必須に 2
将来拡張 例外・GC切替・FFI拡張に耐える Throw/Catch・BoxCall/FFI・Barrierはno-opで開始 2
可観測性 ダンプ/位置情報/Deoptが取れる テキストIRdbg(loc)deopt_map 2
合計12/12この方針で進めてOKにゃ
Nyash MIR v0.1(最終版)
コアモデル
SSA値 vN、基本ブロック bbN末尾は終端命令必須
最小i64|f64|bool|unit|ref<T>|weak<T>|ptr|any|option<T>
効果 EffectMaskPURE|READS_HEAP|WRITES_HEAP|IO|FFI|PANICビット和
関数署名fn(args…)->ret [effects]
命令セット
1) 演算・制御
Const dst, Imm
BinOp dst, op, a, b ; add/sub/mul/div/mod/and/or/xor/shl/shr
Cmp dst, rel, a, b ; eq/ne/lt/le/gt/ge
Select dst, cond, a, b
Phi dst, [(predBB, val), …]
Br cond, thenBB, elseBB ; 終端
Jmp targetBB ; 終端
Ret [val] ; 終端unit省略可
Unreachable ; 終端
2) メモリ参照GC想定
Alloc dst, Ty, size? ; [WRITES_HEAP]
Load dst, addr ; [READS_HEAP]
Store addr, val ; [WRITES_HEAP]
Gep dst, base, offset
RefNew dst, Ty ; ref<T> [WRITES_HEAP]
RefGet dst, ref, field ; [READS_HEAP]
RefSet ref, field, val ; [WRITES_HEAP]
WeakNew dst, ref ; weak<T> [PURE]
WeakLoad dst, weak ; option<ref<T>> [PURE]実装都合でREADS_HEAP可
BarrierRead ref
BarrierWrite ref
Pin ref
Unpin ref
Safepoint
弱参照の意味論
WeakNew(r) は所有しない。r がGCで解放されると弱参照は無効化。
WeakLoad(w) は 生存: Some(ref)/死亡: None を返すnullを曖昧にしない
3) 呼び出し・例外・拡張
Call dst, callee, [args...] ; 呼び先の effects を合成
TailCall callee, [args...] ; 終端
BoxCall dst, box_id, fn_id, [args...] ; Nyash拡張Box [FFI|PANIC?]
FfiCall dst, sym, [args...], abi ; 外部関数 [FFI|PANIC?]
Throw val ; 終端
CatchBegin dst, handlerBB ; (メタでも可)
CatchEnd
Assume cond ; 偽ならUB最適化ヒント
Nop
不変条件Verifier で必須チェック)
各ブロックは必ず終端命令で終わる
型整合Load/Store/Call/WeakLoad 等の型が一致
SSA整合Phi の前任ブロック集合=到達前任集合
効果整合PURE な関数内で WRITES_HEAP/IO/FFI を出さない
WeakLoad戻りは必ず option<ref<T>>null直扱い禁止
推奨挿入ポイント
Safepoint関数入口各ループ先頭任意の Call の直後
Barrier*:はじめは no-op 実装でもOK将来GC切替のフック
テキスト表現(ダンプ例)
func @main() -> unit [effects=IO] {
bb0:
v1 = Const i64 1
v2 = RefNew Obj
RefSet v2, field_y, v1
v3 = WeakNew v2
v4 = WeakLoad v3 ; option<ref<Obj>>
Br is_some(v4), bb1, bb2
bb1:
v5 = unwrap v4
BarrierWrite v5
RefSet v5, field_y, Const i64 2
Jmp bb3
bb2:
Jmp bb3
bb3:
Ret
}
実装ロードマップ(最短で回す)
IR型 & ダンプ & Verifier を実装(黄金テストに登録)
VMRefNew/RefGet/RefSet/WeakNew/WeakLoad と制御命令だけ先に実装
効果検査Call に effects 合成(違反はビルド失敗)
Cranelift JIT 足場算術・分岐・Call を写経、Ref* はランタイム関数に委譲
AOT to Rustref<T>=Gc<T>、weak<T>=Weak<T> 相当でコード出力(小関数から)
付録Rustスケルトン抜粋
bitflags::bitflags! {
pub struct Effect: u32 {
const PURE = 1<<0;
const READS_HEAP = 1<<1;
const WRITES_HEAP = 1<<2;
const IO = 1<<3;
const FFI = 1<<4;
const PANIC = 1<<5;
}
}
pub enum Ty {
I64, F64, Bool, Unit, Ptr, Any,
Ref(Box<Ty>), Weak(Box<Ty>), Option(Box<Ty>),
}
pub enum Inst { /* 上述の命令そのまま */ }
pub struct Func {
pub sig: FuncSig, pub blocks: Vec<Block>, /* dbg, deopt_map など */
}
pub fn verify(func: &Func) -> Result<(), VerifyError> {
// 終端必須・SSA整合・型整合・効果整合をチェック
Ok(())
}
次の一手(今日できること)
mir/inst.rs にこの v0.1 をそのまま貼り込み
mir/dump.rs と mir/verify.rs を最小実装
既存テストに 黄金テストIRダンプ を1本追加
VMの Ref*/Weak* を実装して 最初の end-to-end テスト を通す
これで、行き当たりばったりの修正が入っても壊れにくい土台が完成にゃ。
必要なら、このままコード雛形まで私が書き起こすよ。