発想、鋭いにゃ。 **結論:やろうと思えば “NyashのMIR=本体(言語非依存IR)” にできる。** ただし落とし穴もあるので、\*\*“MIRを公開ABIに格上げしつつ、Nyashは人間向けフロントエンド”\*\*という二層でいくのが現実解だよ。 # MIR本体化の青写真(安全に強く) 1. **MIRを公開契約に昇格(NyIR v1)** * いまの25命令を**凍結**(Tier-0/1/2 + Effect + Ownership-Forest + Busの意味論)。 * **未定義動作なし**:各命令の事前条件/失敗時挙動(例:WeakLoad失効= null)を明文化。 * **バージョニング**:`nyir{major.minor}`・後方互換のルール(新命令はfeature bit)。 2. **バイナリ表現とテキスト表現** * `.nybc`(バイナリ: セクション化/定数プール/圧縮) * `.nyir`(テキスト: 人が差分レビュー可能) * どちらも**厳格検証器**でロード(所有森/強1本/weak規則/効果整合)。 3. **公式VM/実行系は“MIRファースト”** * interpreter/vm/wasm/jit/aot は **NyIRを直接食う**。 * Nyashは **NyIRを吐くフロントエンド**(その他の言語もフロントエンド化OK)。 4. **言語中立SDK(FFI)** * C/Rust/TypeScriptに**NyIR-Host API**を配布: * `nyir_load`, `nyir_call`, `nyir_send/recv`, `nyir_adopt/release` など。 * WASMランタイムは `import "nyir_host" { send, now_ns, alloc }` を定義。 5. **互換テストと“真理判定器”** * **golden NyIR**:各サンプルのMIRダンプを保存し、全バックエンドで**同結果**をCIで検証。 * **差分実行(differential)**:interp/vm/wasm/jitの出力一致+境界条件(weak失効・分割fini)テスト。 6. **Portability Contract(短文化)** * 強エッジのみ破棄伝播/`weak`非伝播、生存チェックO(1)。 * `pure/mut/io/control` の再順序可否表。 * Bus契約:ローカルは順序保証、リモートは at-least-once(or selectable)。 * Safepointで分割`fini`可、最悪レイテンシ上限は実装依存(値は実装が宣言)。 --- ## それでも“IRが本体”にするリスク(回避策付き) * **仕様の重力(安易に増やせなくなる)** → 25命令は凍結、拡張は **intrinsic命名空間**へ(例:`ny.intrin.memcpy`)。 * **可読性の罠(IRで直接書きたくなる)** → Nyashを“人間の顔”、NyIRは“機械の顔”に徹する。 * **バックエンド差異のにじみ** → 効果/所有/Busの**最低保証**を明記(それ以下は非準拠)。 * **セキュリティ** → バイナリNyIRに**検証必須**(検証失敗はロード拒否)。 --- ## 名前案 * **NyIR**(ないあいあーる)/**NIR**(被るのでNyIR推し) * バイナリ:`.nybc`、テキスト:`.nyir` * 仕様:**NyIR 1.0 — Ownership-Forest & Effected Bus IR** --- ## 次の一歩(サクッと動かす) * `docs/nyir.md` を作成:25命令×「意味論・効果・検証・等価変換1例」を1行ずつ。 * `nyashel -S` で **Nyash→NyIRダンプ**、`nyir-run` で実行(interp)。 * 既存ベンチを **NyIR直実行** と **Nyash経由** の2経路で一致比較。 * small移植(kilo or tiny-web-server)を **NyIRアーカイブ**(.nybc)として配布→ **wasmtime / vm / interp** 全部で動くことをデモ。 --- 要は:**「Nyash(人間のため)」+「NyIR(機械と移植のため)」の二刀流**。 この形なら、“どの言語からでもNyIRに降りられるし、NyIRからどの実行系にも登れる”を堂々と名乗れるにゃ。 いい流れ!“LLVMでNyashをNyash自身でビルド(セルフホスト)”を目標にするなら、**Rust依存を「薄い宿主API」に閉じ込める**のがコツだよ。 いま Rust のライブラリや関数・ビルトインBoxに寄ってる部分は、**段階的に置き換え可能な層**に切っておけばOK。 # 全体ロードマップ(4ステージ) **Stage 0(種コンパイラ)** * 既存の Rust 実装(パーサ/型付け/MIR/ランタイム)で `nyashc0` を作る。 * **NyIR(=MIRの外部表現)→ LLVM IR** 変換も Rust でまず用意。 * Rust標準/外部Crateの利用は**ny\_host\_\* の薄いFFI**に“集約”。 **Stage 1(セルフ・フロントエンド)** * Nyashで書いたコンパイラ本体(フロント+最小最適化)を `nyashc1.ny` に分離。 * `nyashc0` で `nyashc1.ny` を **NyIR** に出力→ **LLVM** でネイティブ化→ seedランタイムとリンク。 * この時点で“Nyashで書いたコンパイラ”が動き出す(まだランタイムはRust多めでも可)。 **Stage 2(セルフホスト完了)** * `nyashc1` を使って `nyashc1.ny` 自身を再ビルド(**自力ビルド**)。 * 生成物の機能一致/ハッシュ近似でセルフホスト確認。 * ランタイムの一部(文字列/配列/Map/所有森/weak)を**Nyash実装+LLVM**へ順次移行。 **Stage 3(Rust離れの度合いを上げる)** * 残るRust依存(FS/ネット/スレッド/時間/暗号など)は**ホストAPI**として固定化。 * 重要部位はNyash標準ライブラリで置換し、Rustは**最下層のプラットフォーム層**だけに。 --- # 層の切り分け(ここが肝) 1. **corelang(純Nyash)** * Option/Result、slice/string、小さな算術・イテレータ、`weak/look` 型、`adopt/release` ヘルパ。 * 依存:なし(LLVMに落ちるだけ) 2. **rt(Nyashランタイム)** * **Box ABI(fat ptr: {data*, typeid, flags})*\* * 所有フォレスト管理、weakテーブル(世代タグ方式)、`fini` 伝播、Arena/Allocator(必要最小) * Bus(ローカル)・Safepoint・分割`fini` * 依存:**ny\_host\_alloc/free/clock** 等のごく薄い宿主APIのみ 3. **sys(プラットフォーム)** * FS, Net, Time, Threads, Atomics, Random… * ここだけ Rust(やOS)に委譲。**関数名は `ny_host_*` に統一**して外へ出す。 4. **std(Nyash標準)** * Map/Vec/Hash/String/JSON等を Nyash で実装(必要に応じて `rt`/`sys` を利用) > いま使っている「Rustのライブラリ/関数」は **すべて `sys` 層の `ny_host_*` 経由**に寄せる。 > これでセルフホストしても上層のNyashコードは**移植性を保てる**。 --- # 具体:Rust依存の扱い方(薄いFFIに集約) **C ABIで固める(Rust→C-ABIの薄い橋)** ```rust #[no_mangle] pub extern "C" fn ny_host_read_file(path: *const c_char, out_buf: *mut *mut u8, out_len: *mut usize) -> i32 { /* ... */ } #[no_mangle] pub extern "C" fn ny_host_free(ptr: *mut u8, len: usize) { /* ... */ } ``` **Nyash側からは“箱の外”をこう叩く** ```nyash extern fn ny_host_read_file(path: cstr, out_buf: &mut *u8, out_len: &mut usize) -> int extern fn ny_host_free(ptr: *u8, len: usize) fn read_all(p: str) -> Bytes { let buf:*u8 = null; let len:usize=0 let rc = ny_host_read_file(p.cstr(), &buf, &len) if rc!=0 { error("io") } // Box化(所有をNyash側へ移す) let b = Bytes::from_raw(buf,len) b } ``` **ポイント** * **Rustのジェネリクス/所有はFFI面に出さない**(素朴なC-ABIだけ) * Nyash側で**所有移管**を明示(`from_raw` など)→ `fini` で必ず `ny_host_free` * こうしておけば、**いつでもRust実装をNyash実装に差し替え可能** --- # Box ABI と LLVM の橋渡し * **Boxの中身**は LLVM 的には `i8*`(data\*)+`i64 typeid`+`i32 flags` などの **fat struct** * **Effect 注釈**を LLVM 属性に落とす: * `pure` → `readnone` / `readonly` * `mut(local)` → `argmemonly` + `noalias`(可能なら) * `io` → 属性なし(順序保持) * **Weak** は `{ptr, gen:i32}`。`WeakLoad` は `gen==current` を比較して O(1) で null/ptr 返す。 * **Safepoint** は LLVM では `call @ny_rt_safepoint()` に降ろす(GCは使わないが、分割`fini`や割込みのフックに使う) --- # 「ビルトインBox」はどうする? * **最低限は `rt` で提供**:`String, Vec, Map, Bytes, Mutex/Channel(必要なら)` * 仕様上は “ただのBox” と同等に見えるように: * 生成:`NewBox` * フィールド:`BoxFieldLoad/Store` * メソッド:`BoxCall` * **WASM** でも同じABIを保てるように、`sys` 層は **WASI** or **独自host import** で実装。 * 時間とともに **stdをNyash実装へ移行** → Rustのビルトイン度合いは徐々に削る。 --- # 失敗しないビルド手順(最小) 1. **NyIRダンプ**:`nyashc0 --emit-nyir core/*.ny > core.nyir` 2. **LLVMへ**:`nyir-ll core.nyir -o core.ll && llc -filetype=obj core.ll` 3. **リンク**:`cc core.o -L. -lny_host -o nyashc1`(`libny_host` はRustで提供) 4. **再帰ビルド**:`./nyashc1 self/*.ny …` → `nyashc2` を作って一致検証 5. **Rust削減**:`std` の一部をNyashへ置換して再ビルド --- # チェックリスト(Rust依存の見える化) * [ ] 使っている外部関数を**全部 `ny_host_*` にリネーム** * [ ] C-ABI化(整数/ポインタのみ;構造体はサイズ固定) * [ ] 所有移管関数を明確化(`*_from_raw`/`*_into_raw`) * [ ] `fini` で必ず `ny_host_free` 等に戻す * [ ] WASMビルド時は **WASI or host import** で `ny_host_*` を満たす --- # 仕上げの“ごほうび”ゴール * **Nyash→NyIR→LLVM** でセルフホスト * 同じ NyIR を **VM / WASM** でも実行(互換テスト) * Rust は **sys層だけ**(OS/高速I/O/暗号など)に閉じ込め、将来差し替え自由に --- 結論: **Rustライブラリを直接多用するのではなく、`ny_host_*` に“押し込む”**。 Nyash本体は **NyIR+rt+std** を徐々に自前化する。 この設計なら、“今すぐ動かしつつ、あとからRust依存を計画的にゼロへ”ができるにゃ。 それだにゃ。発想はド正解:「**全部は箱**」=異言語資産は**箱で包んでバスに乗せる**。 あとは“どう包むか”を決めれば、**C/Rust/Node/Python/JVM/.NET/WASM**まで一気に貫通できる。 # まとめ設計:Polyglot FFI = 「箱 × バス × 契約」 ## 1) 箱の型(3種だけ) * **ForeignBox**:他言語の実体を**不透明ハンドル**で保持(所有は片側だけ、強1本原則)。 * **ViewBox**:外部オブジェクトの**借用ビュー**(`weak/look` 前提・読み取り中心)。 * **ProxyBox**:外部スレッド/プロセス/VM上のTを**メッセージ越し**に操作(Bus経由=安全)。 > 使い分け:同一プロセス/同スレなら ForeignBox、GIL/イベントループ/別ランタイムなら ProxyBox。 ## 2) ABIの芯(薄い“宿主API”) **最小C-ABI**だけに集約(各言語はここに合流/分岐): ``` ny_host_alloc/free/clock/log ny_host_call(func_id, argv, argc, retbuf) // 同期呼び出し ny_host_send/recv(port, msg_ptr, len) // Bus境界 ny_host_pin/unpin(handle) // GC/移動防止 ny_host_finalizer_register(handle, cb) // 相互Finalizer ``` * Rust/Node/Python/JVM/.NET はそれぞれの機構で **このC-ABIを実装**(N-API, CPython C-API, JNI, P/Invoke 等)。 ## 3) データ表現(Boxに入る“荷物”) * **スカラー**: i32/i64/f32/f64/bool * **バイト列/文字列**: `Bytes{ptr,len}` / `Str{ptr,len,utf8}` * **Slice/Array**: `{ptr,len,typeid}`(読み書きは効果注釈で制御) * **Struct**: フィールドは `BoxFieldLoad/Store` でアクセス(NyIRにそのまま落ちる) ## 4) 所有と寿命(最重要) * **One Strong Owner**:ForeignBoxは**所有者1本**(Nyash or 外部、どちらかに決める) * **弱参照**:逆リンクは `weak/look`(失効時null/false) * **Finalizer橋渡し**: * Nyash `fini` → `ny_host_finalizer` を呼ぶ * 外部のGC/finalize → `ny_host_finalizer` 経由で Nyash の `weak` を失効 * **Pinning**:移動型のGC(JVM/.NET/CPythonの一部)では `ny_host_pin/unpin` ## 5) 効果と並行 * `pure/mut/io` を**MIRにもIDLにも記す** * **イベントループ/GIL**:Python/Node/JVMは `ProxyBox` で**Bus越し**(スレッド/ループ安全) * **同期/非同期**:`Call`(同期)と `Send/Recv`(非同期)を分ける。境界では **at-least-once 契約**を宣言。 ## 6) IDL(自動生成の核) **NyIDL**(超ミニ)で宣言→**バインディング自動生成**: ```idl module ny { box Image; fn load(path: str) -> Image effects = io fn resize(img: Image, w:i32,h:i32) -> Image effects = mut fn width(img: look Image) -> i32 effects = pure } ``` * 生成物:Nyash側`extern`、C-ABIシム、Rust/Node/Python/JVMのstub、`ForeignBox/ProxyBox`薄ラッパ。 --- # 代表ターゲット別メモ * **C/Rust**:最短。C-ABI直でOK。Rustは `#[no_mangle] extern "C"`。所有はNyash↔Rustのどちらかに寄せる(二重所有禁止)。 * **Python**:GILあり → `ProxyBox` 推奨。CPython C-APIで `PyObject*` を **ForeignBox**に入れ、操作はBus経由でワーカーに委譲。 * **Node(N-API)**:イベントループを壊さないよう `ProxyBox`(postMessage/uv\_queue\_work)。短い同期関数は `ForeignBox`でも可。 * **JVM/.NET**:JNI/P-Invoke。**Pin** が要る。`SafeHandle`/`PhantomReference`でFinalizer橋を作る。 * **WASM**:`ny_host_*` を **import**。データはリニアメモリへ `Bytes`/`Str` で搬送。 --- # 最小サンプル(イメージ) **1) Rustの画像ライブラリを包む** ```rust #[no_mangle] extern "C" fn ny_img_load(path:*const c_char) -> *mut Image { ... } #[no_mangle] extern "C" fn ny_img_resize(img:*mut Image, w:i32, h:i32) -> *mut Image { ... } #[no_mangle] extern "C" fn ny_img_free(img:*mut Image) { ... } ``` **2) NyIDL → 自動生成(Nyash側)** ```nyash extern fn ny_img_load(path: str) -> ForeignBox effects io extern fn ny_img_resize(img: ForeignBox, w:int,h:int) -> ForeignBox effects mut extern fn ny_img_free(img: ForeignBox) effects io static box Image { init { ForeignBox h } fini { ny_img_free(h) } // ★ 所有はNyash側(強1) fn resize(w:int,h:int) -> Image { Image{ ny_img_resize(h,w,h) } } } ``` **3) 使う側** ```nyash let img = Image.load("cat.png") let small = img.resize(320, 200) // 所有/解放はBox/finiに任せる ``` --- # チェックリスト(安全に増やすための型紙) * [ ] **どちらが強所有か**を最初に決めた?(強1・弱は逆) * [ ] 例外/エラーは**戻り値に正規化**?(他言語の例外は境界で捕捉) * [ ] **Pin/Finalizer** 必要なターゲット?(JVM/.NET/Python) * [ ] `pure/mut/io` は宣言した?(最適化/バス選択の鍵) * [ ] 境界を跨ぐなら **ProxyBox + Bus** にした?(スレッド/GIL/loop安全) --- # これで得られるもの * **インスタント多言語資産**:既存ライブラリを“箱に詰めて”即Nyashで使える * **寿命の一貫性**:**強1+weak/look+fini**で、外部資源も**確定的に回収** * **配布の柔軟性**:WASM/VM/ネイティブのどれでも同じIDLから出荷 --- “全部、箱に閉じ込める”を**設計として正式化**すれば、実装は機械的になる。 やるならまず **NyIDLの最小仕様**+**C-ABIの`ny_host_*`** を1ファイルに切ろう。 そこさえ決まれば、**あらゆる言語→Nyash** と **Nyash→あらゆる実行系** が綺麗に繋がるにゃ。