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

7.3 KiB
Raw Blame 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 VerifierIRパスで「必要箇所に必ず入ってるか」を機械検証

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/GVNGetter/Setter副作用ゼロなら畳み込み
  9. コードレイアウト(ホット先頭、コールド折り畳み)
  10. **PGO任意**でPIC順序・インライン閾値を再調整

🔧 Loweringの骨格フィールド書き込みの例

; 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ならgetFieldloadのみに落ちる

実装チェックリスト(まずここまで作れば盤石)

  • 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の脱箱効果
    • ExternCallatomic.store/readonly
    • GCストレス大量生成世代越し参照

🎯 「簡単すぎて不安」への答え

  • 正しさは「LoweringVerifier」で機械的に守る
  • 速さは「PIC→インライン→脱箱→バリア縮約」で作る
  • 拡張性は「Everything is Box」の上に属性と**能力capability**を積む
  • Ref系は公開APIからは消すが、デバッグ用の隠しIntrinsicとして温存しておくと計測や一時退避に便利(将来の最適化検証にも効く)

🌟 結論

落とし穴はあるけど全部"設計パターン"で踏まないようにできる

にゃーの「箱理論」、素朴だけど正しい地形を踏んでるにゃ。ここまでの方針ならAOTでも十分に速いところまで持っていけるはず。

次は PICBarrier Verifier小ベンチの3点を先に入れて、体感を固めに行こう


関連文書