Files
hakorune/docs/development/current/main/phase131-2-consolebox-investigation.md
nyash-codex 05c5021147 docs(phase131): LLVM SSOT強化 + ConsoleBox調査完了 + Phase86-90要約
Phase 131-1 完了: LLVM exe line SSOT 強化
- phase87-selfhost-llvm-exe-line.md に 4セクション追加(+300行)
  - 環境変数リファレンス(14変数)
  - 成功/失敗基準(exit code 0/1/2/3)
  - コンパイラモード説明(harness vs crate)
  - デバッグセクション拡張
- "1コマンドで再現" 可能な状態を確立

Phase 131-2 完了: ConsoleBox 問題調査
- VM の 3重登録経路を特定(BoxFactoryRegistry/UnifiedRegistry/Builtin)
- LLVM backend は Phase 133 で解決済み
- 3つのドキュメント作成:
  - phase131-2-consolebox-investigation.md(詳細調査)
  - phase131-2-summary.md(エグゼクティブサマリ)
  - phase131-2-box-resolution-map.md(Box 解決マップ)

Phase 86-90 完了: Loop frontends 要約
- phase86-90-loop-frontends-summary.md 追加
- Pattern4/ContinueReturn/ParseStringComposite の経緯を1枚に集約
- INDEX から導線追加

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 05:24:31 +09:00

19 KiB
Raw Blame History

Phase 131-2: ConsoleBox 問題根治調査レポート

🎯 調査目的

実アプリの E2E で最初に詰まる「Box 登録/認識」問題を特定し、VM/LLVM 共通の土台を確立する。

📊 調査結果サマリ

問題の本質

ConsoleBox は既に両 backend で完全実装済み - 問題は 登録メカニズムの分岐 にある:

  1. VM backend: Box 解決が「UnifiedBoxRegistry入口 + BoxFactoryRegistryplugin provider mapping + VM fast path」の合成に見え、全体像が追いにくい
  2. LLVM backend: 別の経路TypeRegistry/FFIを使用Phase 133 の事例あり)
  3. 両者の規約不一致: “どれをSSOTとして読むべきか” が docs/コードで分散している

重要な発見

LLVM 側は統合事例があるPhase 133 / archive

LLVM backend の ConsoleBox 問題は Phase 133 で完全解決済み:

  • ConsoleLlvmBridge 箱化モジュール実装済み
  • TypeRegistry との ABI 完全一致
  • 7/7 テスト全て PASS

結論: LLVM 側は “参考モデル” として参照し、当面の焦点は VM 側の経路可視化と SSOT 明文化。

⚠️ VM backend の Box 解決が「2層 + 特例」に見える(問題の根源)

VM backend では “入口” と “plugin provider mapping” が分かれており、加えて入口に特例がある:

1. BoxFactoryRegistry (src/runtime/box_registry.rs)
   - Plugin-First アーキテクチャ
   - プラグイン設定で上書き可能
   - グローバルレジストリ

2. UnifiedBoxRegistry + global accessor
   - global accessor: `src/runtime/unified_registry.rs`
   - registry 本体: `src/box_factory/mod.rs`
   - handle_new_box()VM NewBoxが使用

3. VM fast path特例
   - `NYASH_VM_FAST=1` のとき `StringBox` をレジストリ経由せず生成bench/profile-only

🔍 詳細分析

1. VM Backend の Box 解決フロー

NewBox 命令処理src/backend/mir_interpreter/handlers/boxes.rs

pub(super) fn handle_new_box(
    &mut self,
    dst: ValueId,
    box_type: &str,
    args: &[ValueId],
) -> Result<(), VMError> {
    // ① Provider Lock guard既定は挙動不変
    provider_lock::guard_before_new_box(box_type)?;

    // ② Fast path (StringBox のみ、NYASH_VM_FAST=1 時)
    if box_type == "StringBox" { /* ... */ }

    // ③ 統一レジストリ経由で生成 ← ここが主経路
    let reg = unified_registry::get_global_unified_registry();
    let created = reg.lock().unwrap().create_box(box_type, &converted)?;

    // ④ 生成結果を VMValue に変換して格納
    self.regs.insert(dst, VMValue::from_nyash_box(created));

    Ok(())
}

問題点:

  • unified_registrysrc/runtime/unified_registry.rs にあり、UnifiedBoxRegistrysrc/box_factory/mod.rs
  • UnifiedBoxRegistryPluginBoxFactorysrc/box_factory/plugin.rs)を通じて BoxFactoryRegistry を参照するため、間接的に両者が接続されている
  • Provider Lock の役割が曖昧(「既定は挙動不変」)

BoxFactoryRegistry の設計src/runtime/box_registry.rs

pub struct BoxFactoryRegistry {
    providers: RwLock<HashMap<String, BoxProvider>>,
}

pub enum BoxProvider {
    Builtin(BoxConstructor),  // 互換用(テスト専用)
    Plugin(String),           // プラグイン実装
}

特徴:

  • Plugin-First 設計(プラグインがビルトインを上書き可能)
  • apply_plugin_config() で nyash.toml から動的登録
  • create_plugin_box() → PluginHost 経由で実際の生成

疑問:

  • このレジストリは PluginBoxFactory の provider mapping として使用されるVM NewBox → UnifiedBoxRegistry → PluginBoxFactory → BoxFactoryRegistry
  • provider の populate は src/runtime/plugin_loader_unified.rs が行う(apply_plugin_config

Builtin Fallbacksrc/box_factory/builtin_impls/console_box.rs

/// Create builtin ConsoleBox instance
///
/// Primary: nyash-console-plugin
/// Fallback: This builtin implementation (selfhost support)
pub fn create(_args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> {
    // Phase 151: Quiet fallback (no deprecation warning)
    Ok(Box::new(ConsoleBox::new()))
}

用途:

  • セルフホスト Stage-3 パイプライン用
  • プラグイン初期化失敗時のバックアップ
  • Phase 151 で追加Phase 150 のエラー対処)

2. LLVM Backend の Box 解決フローPhase 133 完了済み)

ConsoleLlvmBridgesrc/llvm_py/console_bridge.py

CONSOLE_METHODS = {
    "log": "nyash.console.log",
    "println": "nyash.console.log",  # Phase 122: log のエイリアス
    "warn": "nyash.console.warn",
    "error": "nyash.console.error",
    "clear": "nyash.console.clear",
}

def emit_console_call(builder, module, method_name, args, ...):
    """ConsoleBox method call to LLVM IR"""
    if method_name not in CONSOLE_METHODS:
        return False

    runtime_fn_name = CONSOLE_METHODS[method_name]
    # LLVM IR generation...
    return True

特徴:

  • Phase 133 で箱化モジュール化済み
  • TypeRegistry の slot 400-403 と完全一致
  • Phase 122 の println/log エイリアス統一を継承

TypeRegistry との連携src/runtime/type_registry.rs

// ConsoleBox 用 slot 定義
const CONSOLE_METHODS: &[MethodEntry] = &[
    MethodEntry { name: "log", arity: 1, slot: 400 },
    MethodEntry { name: "println", arity: 1, slot: 400 },  // エイリアス
    MethodEntry { name: "warn", arity: 1, slot: 401 },
    MethodEntry { name: "error", arity: 1, slot: 402 },
    MethodEntry { name: "clear", arity: 0, slot: 403 },
];

SSOT 化の成果:

  • メソッド名 → slot 番号のマッピングが一元管理
  • VM/LLVM 両方で共通の型情報を参照
  • Phase 122 のエイリアス統一が完全適用

3. Plugin System との連携

TypeBox v2 FFIplugins/nyash-console-plugin/src/lib.rs

#[no_mangle]
pub static nyash_typebox_ConsoleBox: NyashTypeBoxFfi = NyashTypeBoxFfi {
    abi_tag: 0x54594258, // 'TYBX'
    version: 1,
    name: b"ConsoleBox\0".as_ptr() as *const c_char,
    resolve: Some(console_resolve),
    invoke_id: Some(console_invoke_id),
    capabilities: 0,
};

特徴:

  • TypeBox v2 FFI を実装
  • resolve() でメソッド名 → ID 変換
  • invoke_id() で実際のメソッド呼び出し

問題点:

  • VM backend の unified_registry がこの FFI を使っているか不明
  • プラグインローダーとの接続点が見えない

4. CoreBoxId システムPhase 87

CoreBoxId 定義src/runtime/core_box_ids.rs

pub enum CoreBoxId {
    String, Integer, Bool, Array, Map, Console,  // core_required
    Float, Null, File, Path, Regex, Math, Time, Json, Toml,  // core_optional
    Function, Result, Method, Missing,  // 特殊型
}

impl CoreBoxId {
    pub fn is_core_required(&self) -> bool {
        matches!(self, String | Integer | Bool | Array | Map | Console | File)
    }

    pub fn is_required_in(&self, profile: &RuntimeProfile) -> bool {
        match profile {
            RuntimeProfile::Default => self.is_core_required(),
            RuntimeProfile::NoFs => self.is_core_required() && *self != Self::File,
        }
    }
}

設計思想:

  • Core Box を型安全な enum で管理
  • Phase 85 の core_required/core_optional 分類を実装
  • Runtime profile による動的要件変更

問題点:

  • この enum が Box 登録の強制力 を持っているか不明
  • is_core_required() の結果が実際の登録処理に反映されているか要確認

5. CoreServicesPhase 91

CoreServices 定義src/runtime/core_services.rs

pub trait ConsoleService: Send + Sync {
    fn println(&self, msg: &str);
    fn print(&self, msg: &str);
}

pub struct CoreServices {
    pub string: Option<Arc<dyn StringService>>,
    pub integer: Option<Arc<dyn IntegerService>>,
    pub bool: Option<Arc<dyn BoolService>>,
    pub array: Option<Arc<dyn ArrayService>>,
    pub map: Option<Arc<dyn MapService>>,
    pub console: Option<Arc<dyn ConsoleService>>,
}

特徴:

  • Ring1-Core の Service trait 群
  • Phase 87 CoreBoxId の core_required をカバー
  • Optional 化により Graceful Degradation 対応

疑問:

  • この Service trait は 誰が実装しているのか?
  • VM の Box 呼び出しがこの trait を経由するのか?

🚨 発見した問題点

1. SSOT の欠如(最重要)

Box 登録規約が複数箇所に分散:

┌─ BoxFactoryRegistry (グローバル)
├─ UnifiedBoxRegistry (NewBox の入口)
├─ BuiltinBoxFactory (UnifiedBoxRegistry 内の factory)
├─ CoreBoxId enum (型定義)
├─ CoreServices (Service trait)
└─ TypeRegistry (メソッド情報)

問題:

  • どのレジストリが「正」なのか不明
  • 登録順序・優先度の規約がない
  • 初期化タイミングが散在

2. VM と LLVM の分岐

項目 VM Backend LLVM Backend
Box 登録 UnifiedBoxRegistry + BoxFactoryRegistry TypeRegistry + Plugin FFI
メソッド解決 BoxCall handler ConsoleLlvmBridge
ABI NyashBox trait i8* + i64
SSOT 化 不明 Phase 133 完了

3. 失敗パターンの特定

Phase 150/151 で報告されたエラー:

[ERROR] ❌ [rust-vm] VM error: Invalid instruction: NewBox ConsoleBox:
  invalid operation: Unknown Box type: ConsoleBox. Available: Main

原因推測:

  1. セルフホスト経由時にプラグイン初期化が失敗
  2. plugin provider mappingBoxFactoryRegistryが未設定で PluginBoxFactory が失敗
  3. BuiltinBoxFactory が無効(plugins-only)または該当 Box が builtin factory に登録されていない

Phase 151 の対処:

  • builtin_impls/console_box.rs を builtin 実装として追加/整備UnifiedBoxRegistry 内の BuiltinBoxFactory 経由)
  • "Available: Main" → ConsoleBox が登録されるようになった

💡 SSOT 化提案

設計原則

  1. Single Source of Truth: Box 登録規約を一箇所に集約
  2. Explicit Dependencies: 依存関係を明示化
  3. Fail-Fast: エラーは早期に明示的に失敗
  4. Box-First: Phase 33 の箱理論に基づく設計

提案 A: UnifiedBoxRegistry を入口SSOTとして強化保守的アプローチ

// src/runtime/unified_registry.rs / src/box_factory/mod.rs を “入口SSOT” として扱い、
// provider mapping や core_required 検証の責務をどこまで寄せるかを整理する。

pub struct UnifiedBoxRegistry {
    // SSOT: すべての Box 登録情報
    factories: RwLock<BTreeMap<String, BoxProvider>>,

    // CoreBoxId による必須 Box 検証
    core_validator: CoreBoxValidator,

    // プラグインローダーとの接続
    plugin_loader: Arc<PluginHost>,
}

impl UnifiedBoxRegistry {
    /// 初期化時に core_required Box を強制登録
    pub fn new(profile: RuntimeProfile) -> Self {
        let mut reg = Self { /* ... */ };

        // CoreBoxId に基づく必須 Box 登録
        for box_id in CoreBoxId::iter() {
            if box_id.is_required_in(&profile) {
                reg.register_core_box(box_id)?;
            }
        }

        reg
    }

    /// Box 生成(優先順位: Plugin > Builtin > Error
    pub fn create_box(&self, name: &str, args: &[Box<dyn NyashBox>])
        -> Result<Box<dyn NyashBox>, RuntimeError>
    {
        // 1. プラグイン検索
        if let Some(provider) = self.factories.read().unwrap().get(name) {
            match provider {
                BoxProvider::Plugin(plugin_name) => {
                    return self.plugin_loader.create_box(name, args);
                }
                BoxProvider::Builtin(constructor) => {
                    return constructor(args);
                }
            }
        }

        // 2. エラーSSOT なので見つからない = 存在しない)
        Err(RuntimeError::InvalidOperation {
            message: format!("Unknown Box type: {}", name)
        })
    }

    /// プラグイン設定の適用nyash.toml から)
    pub fn apply_plugin_config(&mut self, config: &PluginConfig) {
        for (box_name, plugin_name) in &config.plugins {
            self.factories.write().unwrap()
                .insert(box_name.clone(), BoxProvider::Plugin(plugin_name.clone()));
        }
    }
}

利点:

  • 既存コードへの影響最小
  • CoreBoxId による型安全性
  • プラグイン優先順位の明確化

提案 B: CoreBoxRegistry 新設(理想的アプローチ)

// src/runtime/core_box_registry.rs (新規)

/// Phase 131-2: Core Box 登録の SSOT
pub struct CoreBoxRegistry {
    // CoreBoxId → 登録情報のマッピング
    core_boxes: RwLock<HashMap<CoreBoxId, CoreBoxEntry>>,

    // ユーザー定義 BoxCoreBoxId 以外)
    user_boxes: RwLock<HashMap<String, UserBoxEntry>>,
}

struct CoreBoxEntry {
    box_id: CoreBoxId,
    provider: CoreBoxProvider,
    metadata: CoreBoxMetadata,
}

enum CoreBoxProvider {
    Plugin { plugin_name: String, type_id: u32 },
    Builtin { constructor: fn(&[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeError> },
}

impl CoreBoxRegistry {
    /// 初期化時の必須 Box 検証
    pub fn validate_core_boxes(&self, profile: &RuntimeProfile) -> Result<(), String> {
        let missing: Vec<_> = CoreBoxId::iter()
            .filter(|id| id.is_required_in(profile))
            .filter(|id| !self.has_core_box(*id))
            .collect();

        if !missing.is_empty() {
            return Err(format!("Missing core_required boxes: {:?}", missing));
        }

        Ok(())
    }

    /// Box 生成(型安全)
    pub fn create_core_box(&self, box_id: CoreBoxId, args: &[Box<dyn NyashBox>])
        -> Result<Box<dyn NyashBox>, RuntimeError>
    {
        let entry = self.core_boxes.read().unwrap()
            .get(&box_id)
            .ok_or_else(|| RuntimeError::InvalidOperation {
                message: format!("Core box not registered: {:?}", box_id)
            })?
            .clone();

        match entry.provider {
            CoreBoxProvider::Plugin { plugin_name, type_id } => {
                self.create_plugin_box(&plugin_name, type_id, args)
            }
            CoreBoxProvider::Builtin { constructor } => {
                constructor(args)
            }
        }
    }
}

利点:

  • CoreBoxId による完全な型安全性
  • 必須 Box の起動時検証
  • プラグイン vs ビルトインの明確な分離

🎯 次のステップ

Phase 131-3: SSOT 実装(推奨)

  1. 入口SSOTの確定VM NewBox

    • 入口は src/backend/mir_interpreter/handlers/boxes.rs::handle_new_boxsrc/runtime/unified_registry.rs / src/box_factory/mod.rsUnifiedBoxRegistryで固定する
    • BoxFactoryRegistry は “plugin provider mapping” として位置付け、関係図を SSOT 化する
  2. CoreBoxId との統合

    • CoreBoxValidator 実装
    • 起動時の必須 Box 検証
  3. プラグインローダーとの接続明確化

    • plugin_loader_unified が BoxFactoryRegistry を populate し、 PluginBoxFactoryUnifiedBoxRegistry 内)が BoxFactoryRegistry を参照する流れを明文化
    • TypeBox v2 FFI との整合method/type id の参照箇所)を整理
  4. 既存 BoxFactoryRegistry の整理

    • provider mapping を BoxFactoryRegistry に閉じるか、UnifiedBoxRegistry に吸収するかを決める
    • 移行ガイドの作成

Phase 131-4: テストケース追加

#[test]
fn test_core_box_registration() {
    let reg = UnifiedBoxRegistry::new(RuntimeProfile::Default);

    // core_required が全て登録されていること
    for box_id in CoreBoxId::iter() {
        if box_id.is_core_required() {
            assert!(reg.has_box(box_id.name()));
        }
    }

    // ConsoleBox が生成できること
    let console = reg.create_box("ConsoleBox", &[]).unwrap();
    assert_eq!(console.type_name(), "ConsoleBox");
}

#[test]
fn test_plugin_priority() {
    let mut reg = UnifiedBoxRegistry::new(RuntimeProfile::Default);

    // ビルトイン ConsoleBox 登録済み
    assert!(reg.has_box("ConsoleBox"));

    // プラグイン設定で上書き
    let mut config = PluginConfig::default();
    config.plugins.insert("ConsoleBox".to_string(), "nyash-console".to_string());
    reg.apply_plugin_config(&config);

    // プラグイン版が優先されること
    let console = reg.create_box("ConsoleBox", &[]).unwrap();
    // プラグイン判定ロジック(実装依存)
}

📋 関連ファイル一覧

VM Backend

  • src/runtime/box_registry.rs - BoxFactoryRegistry
  • src/runtime/unified_registry.rs - global UnifiedBoxRegistry accessor
  • src/box_factory/mod.rs - UnifiedBoxRegistryFactoryPolicy / create_box
  • src/box_factory/plugin.rs - PluginBoxFactoryBoxFactoryRegistry を参照)
  • src/runtime/core_box_ids.rs - CoreBoxId enum
  • src/runtime/core_services.rs - CoreServices trait
  • src/runtime/type_registry.rs - TypeRegistry (メソッド情報)
  • src/backend/mir_interpreter/handlers/boxes.rs - handle_new_box()
  • src/box_factory/builtin_impls/console_box.rs - Builtin 実装BuiltinBoxFactory 経由)

LLVM Backend

  • src/llvm_py/console_bridge.py - ConsoleLlvmBridgePhase 133
  • src/llvm_py/instructions/boxcall.py - BoxCall lowering

Plugin System

  • plugins/nyash-console-plugin/src/lib.rs - TypeBox v2 FFI
  • src/runtime/plugin_host.rs - PluginHost

Core Infrastructure

  • src/boxes/console_box.rs - ConsoleBox 実装
  • crates/nyash_kernel/src/lib.rs - NyRT ランタイム

🔍 未解決の疑問

  1. UnifiedBoxRegistry ↔ BoxFactoryRegistry の責務境界

    • provider mapping の SSOT をどこに置くかBoxFactoryRegistry を残す/吸収する)
    • UnifiedBoxRegistry::FactoryPolicy と plugin_config の関係をどう可視化するか
  2. Provider Lock の役割

    • provider_lock::guard_before_new_box() の実装と目的
    • 「既定は挙動不変」の意味
  3. CoreServices の実装者

    • ConsoleService trait を実装しているのは誰か
    • VM の Box 呼び出しがこの trait を経由するのか
  4. 初期化順序

    • プラグインローダー、レジストリ、サービスの初期化タイミング
    • 循環依存の有無

成果

明確になった事実

  1. LLVM backend は問題なし - Phase 133 で完全解決済み
  2. VM backend の Box 解決が分散して見える - UnifiedBoxRegistry入口と BoxFactoryRegistryprovider mappingの関係を SSOT 化する必要がある
  3. ConsoleBox 自体は完全実装 - 登録メカニズムの問題
  4. CoreBoxId システム存在 - 型安全性の基盤あり

提案した解決策

  1. UnifiedBoxRegistry 強化 - 保守的アプローチ
  2. CoreBoxRegistry 新設 - 理想的アプローチ
  3. 起動時検証 - Fail-Fast 原則の実現

Status: Investigation Complete - Ready for Implementation Next Phase: 131-3 (SSOT Implementation) Estimated Time: 4-6 hours