22 KiB
22 KiB
Nyash統一Box設計の深い分析と実装戦略
Status: Active Design Created: 2025-08-25 Updated: 2025-08-26 (Codex深掘り版) Priority: Critical Related: Everything is Box哲学の実装レベルでの完全実現
📌 最新の合意事項(2025-08-26)
2つのAI専門家からの統一見解
- Codex (GPT-5): ハイブリッドアプローチ推奨、Method ID = Stable Slot Index
- Claude: MIRレベル完全統一の重要性を強調
- 共通結論: コアBox静的リンク+MIR統一+Thunk indirection = 最適解
核心的設計決定
- ハイブリッドアプローチ - コアは静的、拡張はプラグイン
- MIRレベル完全統一 - 3種類のBoxを数値IDで区別なく扱う
- Stable Slot Index - メソッドIDは型ごとのvtableスロット番号
- Thunk-based Safety - プラグインアンロード時の安全性保証
🎯 統一Box設計の全体像
1. Box分類の最終決定
// 絶対的コアBox(静的リンク必須)- 10個
const CORE_BOXES: &[&str] = &[
"NilBox", "BoolBox", "IntegerBox", "FloatBox",
"StringBox", "ArrayBox", "MapBox",
"ConsoleBox", "ResultBox", "MethodBox"
];
// 準コアBox(静的だが置換可能)- 4個
const SEMI_CORE_BOXES: &[&str] = &[
"MathBox", "DebugBox", "TimeBox", "RandomBox"
];
// プラグイン推奨Box - 残り全て
const PLUGIN_BOXES: &[&str] = &[
"FileBox", "NetworkBox", "AudioBox",
"P2PBox", "EguiBox", "WebDisplayBox", // ...
];
2. 革新的な統一レジストリ設計(Codex提案)
pub struct UnifiedBoxRegistry {
// 型名 → 型ID(安定)
type_registry: HashMap<String, BoxTypeId>,
// 型メタデータ(vtableベースアドレス含む)
type_meta: Vec<TypeMeta>,
// (型ID, メソッド名) → スロット番号(永続的)
slot_index: HashMap<(BoxTypeId, String), SlotIdx>,
// Thunkプール(固定アドレス、原子的更新可能)
thunks: Vec<MethodThunk>,
// グローバルエポック(大規模無効化用)
epoch: AtomicU64,
}
// 型ごとのメタデータ
struct TypeMeta {
version: AtomicU32, // 動的更新のバージョン
vtable_base: *const *const MethodThunk, // vtableベースポインタ
slot_count: u32, // 割り当て済みスロット数
}
// メソッドThunk(間接層)
struct MethodThunk {
target: AtomicPtr<c_void>, // 実装への原子的ポインタ
sig: Signature, // メソッドシグネチャ
flags: MethodFlags, // 純粋性、インライン可能性等
}
🚀 MIRレベルでの完全統一実装
1. 統一MIR命令(最終形)
pub enum MirInstruction {
// Box生成(すべて同じ)
BoxNew {
dst: ValueId,
type_id: BoxTypeId, // 数値ID(ビルトイン=1、ユーザー=1000でも同じ)
args: Vec<ValueId>,
},
// メソッド呼び出し(すべて同じ)
BoxCall {
dst: Option<ValueId>,
receiver: ValueId,
method_id: MethodId, // 安定したスロット番号
args: Vec<ValueId>,
effects: EffectFlags, // 純粋性、副作用情報
},
}
2. メソッドID解決戦略(Codex推奨)
impl MirBuilder {
fn compile_method_call(&mut self, receiver: ValueId, method: &str, args: Vec<ValueId>) {
let receiver_type = self.infer_type(receiver);
// 静的に解決可能な場合
if let Some(slot) = self.registry.resolve_slot(receiver_type, method) {
self.emit(MirInstruction::BoxCall {
dst: Some(self.new_value()),
receiver,
method_id: slot,
args,
effects: self.registry.get_method_flags(receiver_type, slot),
});
} else {
// 動的解決が必要(eval、動的ロード)
self.emit_late_bind_call(receiver, method, args);
}
}
}
3. VM実装の劇的簡素化
// 現在:Box種類で複雑な分岐
match determine_box_kind(&receiver) {
BoxKind::Instance => {
// ユーザー定義Boxの特殊処理
let func_name = format!("{}.{}", class_name, method);
self.call_mir_function(func_name, args)?
},
BoxKind::Plugin => {
// プラグインのFFI呼び出し
unsafe { plugin_invoke(receiver, method, args) }
},
BoxKind::Builtin => {
// ビルトインメソッド直接呼び出し
builtin_dispatch(receiver, method, args)
}
}
// 統一後:完全に均一なディスパッチ!
fn execute_boxcall(&mut self, receiver: ValueId, method_id: MethodId, args: Vec<ValueId>) {
// 1. 受信者の型を取得
let type_meta = self.get_type_meta(receiver);
// 2. vtableからThunkを取得
let thunk = unsafe { *type_meta.vtable_base.add(method_id as usize) };
// 3. Thunkのターゲットを原子的に読み取り
let target = thunk.target.load(Ordering::Acquire);
// 4. 統一的な呼び出し
let result = (target)(receiver, args);
self.push(result);
}
⚡ 高性能化の核心技術
1. Polymorphic Inline Cache (PIC) 実装
// コールサイトごとのインラインキャッシュ
struct InlineCache {
// モノモーフィックエントリ(最頻出)
mono_type: BoxTypeId,
mono_version: u32,
mono_target: *const fn,
// ポリモーフィックエントリ(2-4個)
poly_entries: [(BoxTypeId, u32, *const fn); 4],
poly_count: u8,
}
// JIT生成コード例(疑似コード)
fn generate_pic_stub(cache: &InlineCache) -> Code {
// 1. 受信者の型IDを取得
// 2. モノモーフィックチェック
if receiver.type_id == cache.mono_type &&
receiver.version == cache.mono_version {
jump cache.mono_target // 直接ジャンプ!
}
// 3. ポリモーフィックチェック
for entry in cache.poly_entries[..cache.poly_count] {
if receiver.type_id == entry.0 && receiver.version == entry.1 {
jump entry.2
}
}
// 4. スローパス
call slow_path_resolver
}
2. メソッドスロットの永続性保証
impl UnifiedBoxRegistry {
// スロット予約(一度割り当てたら永続)
pub fn reserve_method_slot(
&mut self,
type_id: BoxTypeId,
method: &str,
sig: &Signature
) -> SlotIdx {
let key = (type_id, method.to_string());
// 既存スロットがあれば返す
if let Some(&slot) = self.slot_index.get(&key) {
return slot;
}
// 新規スロット割り当て(削除後も再利用しない)
let type_meta = &mut self.type_meta[type_id as usize];
let slot = SlotIdx(type_meta.slot_count);
type_meta.slot_count += 1;
self.slot_index.insert(key, slot);
slot
}
}
3. 安全なプラグインアンロード
impl PluginManager {
fn unload_plugin(&mut self, plugin_id: PluginId) {
// 1. 影響を受ける型とメソッドを列挙
let affected = self.get_plugin_methods(plugin_id);
for (type_id, slot) in affected {
// 2. Thunkを原子的にスタブに差し替え
let thunk = &self.registry.get_thunk(type_id, slot);
let stub = get_unloaded_method_stub();
thunk.target.store(stub, Ordering::Release);
// 3. 型バージョンをインクリメント(キャッシュ無効化)
let type_meta = &self.registry.type_meta[type_id];
type_meta.version.fetch_add(1, Ordering::AcqRel);
}
// 4. RCU/Hazard Pointerで安全に回収
self.defer_plugin_cleanup(plugin_id);
}
}
// アンロード後のスタブ関数
fn unloaded_method_stub(_: ValueId, _: Vec<ValueId>) -> VMValue {
panic!("Method has been unloaded")
}
📊 パフォーマンス分析と予測
現状のベースライン
- インタープリター比: 13.5倍高速
- BoxCall実行時間: 全体の30%
- 主なオーバーヘッド: 文字列比較、型判定、間接呼び出し
改善による期待効果
| 最適化技術 | 個別改善率 | 全体への影響 |
|---|---|---|
| メソッドID化 | 50-70% | 15-21% |
| PICキャッシュ | 80-90% | 24-27% |
| インライン化 | 特定サイト10倍 | 10-25% |
| 純粋性解析 | CSE/LICM可能 | 5-15% |
20倍達成への道筋
現在: 13.5倍
目標: 20.0倍(+48%必要)
達成可能性:
- ID化+PIC: 1.15-1.20倍
- インライン化: 1.10-1.25倍
- 効果解析: 1.05-1.15倍
合計: 1.32-1.73倍 → 17.8-23.4倍(達成可能!)
🛠️ 実装ロードマップ(詳細版)
Phase 1: 基盤構築(1週間)
// Week 1のタスク
- [ ] MethodThunk構造体とアロケーター実装
- [ ] TypeMetaとvtable管理実装
- [ ] スロット予約API(reserve_method_slot)
- [ ] 基本的なレジストリ操作
Phase 2: MIR統合(1週間)
// Week 2のタスク
- [ ] BoxNew/BoxCallの数値ID化
- [ ] メソッド名→スロット解決
- [ ] late-bind placeholderサポート
- [ ] デバッグ情報サイドテーブル
Phase 3: VM最適化(2週間)
// Week 3-4のタスク
- [ ] 統一execute_boxcall実装
- [ ] モノモーフィックPIC実装
- [ ] ポリモーフィックPIC拡張
- [ ] ベンチマーク検証
Phase 4: プラグイン対応(1週間)
// Week 5のタスク
- [ ] プラグインAPIのスロット対応
- [ ] 安全なアンロード実装
- [ ] バージョン管理とキャッシュ無効化
- [ ] 実プラグイン移行(FileBox)
Phase 5: JIT準備(継続的)
// 継続タスク
- [ ] 純粋性フラグの伝搬
- [ ] インライン可能IRの提供
- [ ] Craneliftメタデータ整備
- [ ] PIC codegenサポート
🔍 技術的詳細と実装のコツ
デバッグ情報の管理
// MIRは数値のみ保持
struct MirDebugInfo {
// (type_id, slot) → 人間可読情報
method_names: HashMap<(BoxTypeId, SlotIdx), MethodDebug>,
// PC → ソース位置
source_map: HashMap<usize, SourceLocation>,
}
struct MethodDebug {
type_name: String,
method_name: String,
signature: String,
source_file: PathBuf,
line: u32,
}
スレッドセーフティ
// Read-Copy-Update パターン
impl UnifiedBoxRegistry {
fn update_method(&self, type_id: BoxTypeId, slot: SlotIdx, new_impl: *const fn) {
// 1. 新バージョンを準備
let new_thunk = MethodThunk {
target: AtomicPtr::new(new_impl),
// ...
};
// 2. RCUで安全に更新
rcu::synchronize(|| {
self.thunks[slot].target.store(new_impl, Ordering::Release);
});
}
}
メモリレイアウト最適化
// キャッシュフレンドリーな配置
#[repr(C, align(64))] // キャッシュライン境界
struct TypeVTable {
thunks: [MethodThunk; MAX_METHODS_PER_TYPE],
}
// ホットデータをまとめる
struct HotMethodData {
frequently_called: [MethodThunk; 8], // toString, equals等
cold_methods: *const ColdMethodTable,
}
💡 結論と次のステップ
統一設計の価値
- 簡潔性: VM/JIT実装が劇的にシンプルに
- 性能: 20倍高速化が現実的に達成可能
- 安全性: Thunk indirectionで動的更新も安全
- 拡張性: 新Box型追加が容易
ChatGPT5への相談ポイント
- Thunk実装の具体的なアセンブリ
- RCU/Hazard Pointerの実装詳細
- PICのCranelift codegenパターン
- 型推論とスロット解決の統合
- デバッガ統合のベストプラクティス
実装の第一歩
// まずはこれを実装!
pub struct MethodThunk {
target: AtomicPtr<extern "C" fn(*const u8, *const *const u8) -> *const u8>,
#[cfg(debug_assertions)]
debug_name: &'static str,
}
// そしてスロット予約
registry.reserve_method_slot(STRING_BOX_ID, "toString", &sig);
「Everything is Box」の理想が、ついに実装レベルで完全に実現される時が来ました!
🔍 統一デバッグインフラストラクチャ
📍 MIRレベルでの統一デバッグ実現
MIRレベルでのデバッグ実装が最も理想的であることが判明しました。 Gemini先生とCodex先生の両方が同じ結論に達しました:設計案2+3のハイブリッドが最適解です。
核心設計:メタデータ分離+プロファイリングAPI
// MIR本体はクリーンに保つ
pub struct MIRModule {
pub functions: HashMap<String, MIRFunction>,
pub constants: Vec<Constant>,
pub debug_info: Option<MIRDebugInfo>, // デバッグ時のみ生成
}
// 静的情報(設計案2)
pub struct MIRDebugInfo {
// ID→名前のマッピング(文字列を避けてIDベース)
pub type_table: HashMap<u16, BoxTypeDescriptor>, // TypeId → Box型情報
pub method_table: HashMap<u32, MethodInfo>, // MethodId → メソッド情報
pub site_table: HashMap<u32, SiteInfo>, // SiteId → 位置情報
pub source_map: HashMap<usize, SourceLocation>, // PC → ソース位置
}
// 動的収集(設計案3)
pub trait MIRProfiler: Send + Sync {
fn on_alloc(&mut self, type_id: u16, site_id: u32, obj_id: u64, size: usize);
fn on_free(&mut self, obj_id: u64);
fn on_method_enter(&mut self, method_id: u32, site_id: u32, instance_id: u64);
fn on_method_exit(&mut self, method_id: u32, site_id: u32, result: &VMValue);
fn on_field_access(&mut self, obj_id: u64, field_id: u16, is_write: bool);
}
なぜこの設計が美しいのか?
- MIRの純粋性を保つ - デバッグ命令でIRを汚染しない
- ゼロオーバーヘッド - 本番ビルドではdebug_info = None
- 全バックエンド統一 - VM/JIT/AOT/WASMで同じプロファイラAPI
- 業界標準に準拠 - LLVM、JVM、.NETと同じアプローチ
🧠 DeepInspectorBox - 統一デバッグ体験
Everything is Boxの哲学をデバッグでも実現
// グローバルシングルトン - すべてを見通す眼
static box DeepInspectorBox {
init {
enabled, // デバッグON/OFF
boxCreations, // すべてのBox生成履歴
methodCalls, // すべてのメソッド呼び出し
fieldAccess, // フィールドアクセス履歴
memorySnapshots, // メモリスナップショット
referenceGraph, // 参照グラフ(リーク検出用)
performanceMetrics // パフォーマンス統計
}
// === Box ライフサイクル完全追跡 ===
trackBoxLifecycle(boxType) {
// 特定のBox型の生成から破棄まで完全追跡
return me.boxCreations.filter(b => b.type == boxType)
}
// === メモリリーク検出(深い実装) ===
detectLeaks() {
// 参照グラフの構築
local graph = me.buildReferenceGraph()
// 循環参照の検出
local cycles = graph.findCycles()
// 到達不可能なBoxの検出
local unreachable = graph.findUnreachableFrom(me.getRootBoxes())
// weak参照の考慮
local suspicious = me.findSuspiciousWeakReferences()
return LeakReport {
cycles: cycles,
unreachable: unreachable,
suspicious: suspicious,
totalLeakedBytes: me.calculateLeakedMemory()
}
}
// === P2P非同期フロー可視化 ===
traceAsyncFlow(startEvent) {
// P2Pメッセージの送信から受信、ハンドラー実行までの完全追跡
local flow = []
// 送信イベント
flow.push(startEvent)
// Transport経由の転送
local transportEvents = me.findTransportEvents(startEvent.messageId)
flow.extend(transportEvents)
// MethodBox.invoke()の実行
local handlerExecution = me.findHandlerExecution(startEvent.to, startEvent.intent)
flow.push(handlerExecution)
return AsyncFlowTrace {
events: flow,
totalTime: flow.last().time - flow.first().time,
visualization: me.generateFlowDiagram(flow)
}
}
}
🔬 メモリリーク検出の深い仕組み
参照グラフベースの完全検出
impl DeepInspectorBox {
/// 参照グラフの構築(UnifiedBox設計と統合)
fn build_reference_graph(&self) -> ReferenceGraph {
let mut graph = ReferenceGraph::new();
// すべてのBoxを走査(ビルトイン/プラグイン/ユーザー定義を統一的に)
for (obj_id, box_info) in &self.live_boxes {
match box_info.content {
// InstanceBoxのフィールド参照
BoxContent::Instance(fields) => {
for (field_name, field_value) in fields {
if let Some(target_id) = field_value.get_box_id() {
graph.add_edge(*obj_id, target_id, EdgeType::Field(field_name));
}
}
}
// MethodBoxのインスタンス参照
BoxContent::Method { instance_id, .. } => {
graph.add_edge(*obj_id, instance_id, EdgeType::MethodInstance);
}
// P2PBoxのハンドラー参照
BoxContent::P2P { handlers, .. } => {
for (intent, handler_id) in handlers {
graph.add_edge(*obj_id, handler_id, EdgeType::Handler(intent));
}
}
// ArrayBox/MapBoxの要素参照
BoxContent::Container(elements) => {
for (index, element_id) in elements.iter().enumerate() {
graph.add_edge(*obj_id, element_id, EdgeType::Element(index));
}
}
}
}
graph
}
/// 高度なリーク検出アルゴリズム
fn detect_leak_patterns(&self, graph: &ReferenceGraph) -> Vec<LeakPattern> {
let mut patterns = vec![];
// Pattern 1: 単純な循環参照
let cycles = graph.tarjan_scc();
for cycle in cycles {
if cycle.len() > 1 {
patterns.push(LeakPattern::CircularReference(cycle));
}
}
// Pattern 2: イベントハンドラーリーク(P2P特有)
for (node_id, node_info) in &self.p2p_nodes {
for (intent, handler) in &node_info.handlers {
if let Some(method_box) = handler.as_method_box() {
let instance_id = method_box.get_instance_id();
if !self.is_box_alive(instance_id) {
patterns.push(LeakPattern::DanglingHandler {
node: node_id.clone(),
intent: intent.clone(),
dead_instance: instance_id,
});
}
}
}
}
// Pattern 3: 巨大オブジェクトグラフ
let subgraphs = graph.find_connected_components();
for subgraph in subgraphs {
let total_size = subgraph.iter()
.map(|id| self.get_box_size(*id))
.sum::<usize>();
if total_size > SUSPICIOUS_GRAPH_SIZE {
patterns.push(LeakPattern::LargeObjectGraph {
root: subgraph[0],
size: total_size,
object_count: subgraph.len(),
});
}
}
patterns
}
}
🚀 統一デバッグ実装ロードマップ
Phase 1: MIRデバッグ基盤(2週間)
- MIRDebugInfo構造の実装
- MIRProfilerトレイトの定義
- MIRビルダーでのデバッグ情報生成
Phase 2: VMプロファイラー統合(1週間)
- VMでのMIRProfiler実装
- DeepInspectorBoxのVM連携
- 基本的なメモリリーク検出
Phase 3: 非同期フロー可視化(1週間)
- P2Pメッセージトレース
- MethodBox実行追跡
- タイミング図の生成
Phase 4: WASM対応(2週間)
- nyash_debugインポートの実装
- カスタムセクションへのデバッグ情報埋め込み
- ブラウザ開発ツール連携
Phase 5: パフォーマンス最適化(1週間)
- ロックフリーリングバッファ
- サンプリングモード
- 増分参照グラフ更新
💎 統一デバッグの美しさ
この設計により、以下が実現されます:
- 完全な可視性 - Boxの生成から破棄、メソッド呼び出し、フィールドアクセスまですべて追跡
- メモリ安全性の保証 - リークパターンの自動検出と可視化
- 非同期フローの理解 - P2Pメッセージングの複雑な流れを完全に把握
- 統一された体験 - VM/JIT/AOT/WASMすべてで同じデバッグ機能
- Nyashらしさ - DeepInspectorBox自体もBoxとして実装
「Everything is Box」の哲学は、デバッグインフラストラクチャにおいても完全に実現されることになります。
🔮 将来の拡張可能性
- AI支援デバッグ - パターン認識によるバグの自動検出
- 時間遡行デバッグ - 実行履歴の巻き戻しと再実行
- 分散トレーシング - 複数ノード間のP2P通信の可視化
- パフォーマンスAI - ボトルネックの自動最適化提案
これらすべてが、統一されたMIRデバッグ基盤の上に構築可能です。