Files
hakorune/docs/phases/phase-11/BOXCALL_UNIFICATION_PITFALLS_AND_SOLUTIONS.md

157 lines
7.3 KiB
Markdown
Raw Normal View History

# BoxCall統一の落とし穴と対策ChatGPT5分析
Date: 2025-08-31
Status: Technical Advisory
From: ChatGPT5
**結論「RefNew/RefGet/RefSet全削除→すべてBoxCallに統一」は成立する**
ただし、いくつかの落とし穴があるので、それぞれに対策を打つ必要がある。
## 🚨 落とし穴と対策
### 1. メガモーフィック呼び出しでの失速
**症状**: 同じ`BoxCall("setField")`でも実行時の型/shapeが激しく変わると、ディスパッチが重くなる。
**対策**: **PICPolymorphic Inline Cache**をコールサイトごとに持つ
- 2〜4種のshapeを直列ジャンプで捌く
- 溢れたらインタプリタ/汎用スローへ
- JITなしでもAOT段階で形状統計から事前特化事前ガード直アクセスを埋め込める
### 2. GCバリアの見落とし・過剰挿入
**症状**: write barrier忘れ世代間参照漏れ逆に全部に入れて過剰オーバーヘッド
**対策**:
- Lowering時に**フィールドの"ポインタ/非ポインタ"メタ**を参照して自動挿入
- **世代同一・同アリーナ最適化**でbarrier省略
- `ExternCall`には**境界バリア**を必ず付与
- **Barrier Verifier**IRパスで「必要箇所に必ず入ってるか」を機械検証
### 3. 読み取りバリアRead Barrierが必要なGCを選ぶ場合
**症状**: 動くGC移動/並行でread barrierが必須だと、Get系もコスト上がる
**対策**:
- まずは**世代別・停止並行マークSATB**など「write側主体」の方式を選ぶ
- **read barrierなし運用**で始めるのが無難
- 将来read barrierが要る場合は、`getField` Loweringに条件付き埋め込み設計
### 4. 例外・再入・ファイナライザ再入
**症状**: `setField`中に例外→ファイナライザ→別の`BoxCall`で再入…地雷
**対策**:
- **安全点safepoint設計**を決める
- `BoxCall`中は原則再入禁止or 明示的許可フラグ)
- `fini`相当のコールは**再入ガード**と**順序保証**(トポロジカルな破棄順)を実装
### 5. ExternCall/FFI境界
**症状**: 外部コードが「未トラッキングの生ポインタ」を握るとGC・最適化が壊れる
**対策**:
- **ハンドル化**OpaqueHandle/PinBox**寿命契約**
- ExternCallの属性`noalloc`/`nothrow`/`readonly`/`atomic`等)を宣言させ、最適化に渡す
- 未注釈の呼び出しでは保守的にバリア&逃避扱い
### 6. 形状shape変更とレイアウト安定性
**症状**: フィールド追加/順序変更が既存の特化コードを壊す
**対策**:
- **ShapeIDを永続化**
- フィールドに**安定スロットID**を割り当て
- ABI的に「追加のみ」「削除は新shape」とする
- Lowering済みのガードは `if (shape==X) { direct store } else { slowpath }` で守る
### 7. 脱箱unboxingとコードサイズ膨張
**症状**: 激しいモモルフィック特化や整数Boxの脱箱で**コード肥大**
**対策**:
- **基本型はSROA/Scalar-Replaceの閾値**を設定
- ホット領域のみ特化(**PGO**やプロファイル使用)
- 低頻度パスは共通スローに集約
### 8. 並行性・メモリモデル
**症状**: `setField`の可視性がスレッド間で曖昧だと事故
**対策**:
- **既定は単一スレッドActorMailbox**に寄せる
- 共有可変を解禁するAPIは `nyash.atomic.*` で**Acquire/Release**を明示
- `BoxCall` Loweringで**必要時のみフェンス**
- 箱ごとに「可変・不変・スレッド送受可」など**能力capabilityビット**を持たせ最適化条件に使う
### 9. 反射・動的呼び出しの混入
**症状**: なんでも動的だと最適化が崩れる
**対策**:
- 反射APIは**分離名前空間**に押し込める
- 既定は静的解決できる書き方を推奨ガイドに
- 反射使用時は**deoptガード**を挿入
## 📈 推奨の最適化パイプラインAOT想定
1. **型/shape解析**(局所→関数間)
2. **BoxCall脱仮想化**(モノ/ポリモーフィック化PIC生成
3. **インライン化**(属性`pure`/`leaf`/`readonly`を最大活用)
4. **SROA/エスケープ解析**脱箱、stack allocation、alloc移動
5. **バリア縮約**(世代同一・同アリーナ・ループ内集約)
6. **境界チェック消去**`length`不変式の伝播)
7. **ループ最適化**LICM, unroll, vectorize
8. **DCE/GVN**Getter/Setter副作用ゼロなら畳み込み
9. **コードレイアウト**(ホット先頭、コールド折り畳み)
10. **PGO任意**でPIC順序・インライン閾値を再調整
## 🔧 Loweringの骨格フィールド書き込みの例
```llvm
; High-level
obj.setField(x)
; Guarded fast-pathshapeが既知最頻
if (obj.shape == SHAPE_A) {
; slot #k に直接store
store x, [obj + slot_k]
call gc_write_barrier(obj, x) ; 必要なら
} else {
; PICの次候補 or 汎用ディスパッチ
slow_path_setField(obj, x)
}
```
- `gc_write_barrier`はIR上は呼び出しに見せておく後段で**インライン**→**条件付きno-op化**可能)
- `read barrier`が要らないGCなら`getField`は**loadのみ**に落ちる
## ✅ 実装チェックリスト(まずここまで作れば盤石)
- [ ] **Boxメタ**: shapeID、安定スロットID、ポインタ/非ポインタビット、可変/不変、送受可
- [ ] **BoxCall Lowerer**: 形状ガード→直アクセス or 汎用ディスパッチ
- [ ] **PIC**: コールサイトごとに最大N件キャッシュ統計ヒット率/退避回数)
- [ ] **Barrier Verifier**: IR後段でwrite barrier必須箇所を自動検証
- [ ] **Extern属性**: `noalloc/nothrow/readonly/atomic`等を宣言・強制
- [ ] **逃避解析**でstack-alloc/arena-alloc
- [ ] **反射API分離**とdeoptガード
- [ ] **PGOフック**簡易でOKshape頻度、PICヒット率、inlining成果を記録
- [ ] **ベンチ群**:
- Field get/setmono vs mega
- Vec push/pop / Map ops
- 算術IntBoxの脱箱効果
- ExternCall`atomic.store`/`readonly`
- GCストレス大量生成世代越し参照
## 🎯 「簡単すぎて不安」への答え
- **正しさ**は「LoweringVerifier」で機械的に守る
- **速さ**は「PIC→インライン→脱箱→バリア縮約」で作る
- **拡張性**は「Everything is Box」の上に**属性**と**能力capability**を積む
- Ref系は**公開APIからは消す**が、**デバッグ用の隠しIntrinsic**として温存しておくと計測や一時退避に便利(将来の最適化検証にも効く)
## 🌟 結論
**落とし穴はあるけど全部"設計パターン"で踏まないようにできる**。
にゃーの「箱理論」、素朴だけど正しい地形を踏んでるにゃ。ここまでの方針なら**AOTでも十分に速い**ところまで持っていけるはず。
次は **PICBarrier Verifier小ベンチ**の3点を先に入れて、体感を固めに行こう
---
## 関連文書
- [BOX_SSA_CORE_15_FINAL_DECISION.md](../phase-11.5/BOX_SSA_CORE_15_FINAL_DECISION.md)
- [MIR_TO_LLVM_CONVERSION_PLAN.md](MIR_TO_LLVM_CONVERSION_PLAN.md)
- [MIR_ANNOTATION_SYSTEM.md](MIR_ANNOTATION_SYSTEM.md)