LoopBuilder: bind variable_map to Phi result on seal

- After inserting Phi at loop header, update variable_map so that
  subsequent uses (after the loop) refer to the Phi result instead of
  the latch/body value. This fixes dominance issues in verifier.
- Add tests: loop phi normalization and loop+nested-if phi; both pass.
This commit is contained in:
Moe Charm
2025-08-26 06:35:39 +09:00
parent ea672eaa2c
commit 378a2bc174
3 changed files with 184 additions and 1 deletions

View File

@ -438,3 +438,93 @@ struct BoxVTable {
- [ ] プラグインBoxの透過的な動作
- [ ] パフォーマンス改善の確認
- [ ] メモリ使用量の変化なし
## 🚀 究極の統一ビルトインBox完全プラグイン化構想
### 現状の二重実装問題
- **plugin_loader.rs** (1217行) - ビルトインBoxの動的ライブラリ化
- **plugin_loader_v2.rs** (906行) - プラグインBoxシステム
- 合計2000行以上の重複
### 完全プラグイン化の提案
#### すべてをプラグインに統一
```rust
// 現在
ビルトインFileBox → 静的リンク
プラグインFileBox → 動的ロード(.so
// 統一後
すべてのBox → プラグイン(.soとして実装
```
#### コアBoxの自動ロード戦略
```rust
const CORE_BOXES: &[&str] = &[
"libnyash_string_box.so", // StringBox必須
"libnyash_integer_box.so", // IntegerBox必須
"libnyash_bool_box.so", // BoolBox必須
"libnyash_console_box.so", // ConsoleBoxprint用
];
// 起動時に自動ロード
fn init_core_boxes() {
for plugin in CORE_BOXES {
plugin_loader.load_required(plugin)
.expect("Core box loading failed");
}
}
```
### メリット
1. **コード削減**: plugin_loader.rs (1217行) を完全削除
2. **統一性**: Everything is Boxの究極の実現
3. **柔軟性**: StringBoxすら置き換え可能
4. **ビルド高速化**: 本体が軽量に
5. **配布の柔軟性**: 必要なBoxだけ選択可能
### 考慮事項
#### パフォーマンス
- FFI境界のオーバーヘッドは**ナノ秒レベル**
- 実用上の影響なし
#### デバッグの課題と対策
```rust
// 課題:エラー時のスタックトレース
thread 'main' panicked at 'FFI boundary: 0x7f8b2c001234'
// 対策1プラグイン側でのロギング
#[no_mangle]
pub extern "C" fn box_method_toString() {
eprintln!("[StringBox::toString] called from {:?}", std::thread::current().id());
}
// 対策2デバッグシンボル保持
cargo build --features debug-symbols
// 対策3プラグイン単体テストの充実
#[test]
fn test_string_box_methods() { /* ... */ }
```
### 実装ロードマップ
1. **Phase A**: コアBoxのプラグイン化
- StringBox, IntegerBox, BoolBox, ConsoleBox
2. **Phase B**: 起動時自動ロード機構
3. **Phase C**: plugin_loader.rs削除
4. **Phase D**: ドキュメント・テスト整備
### 設定ファイル案
```toml
# ~/.nyash/config.toml
[plugins]
core_path = "./plugins/core/"
search_paths = ["./plugins", "/usr/lib/nyash/plugins"]
[core_boxes]
required = ["string", "integer", "bool", "console"]
optional = ["file", "math", "time"]
```
これにより、「Everything is Box」哲学が実装レベルでも完全に実現される

View File

@ -162,6 +162,8 @@ impl<'a> LoopBuilder<'a> {
// 完成したPhi nodeを発行
self.emit_phi_at_block_start(block_id, phi.phi_id, phi.known_inputs)?;
// 重要: ループ外から参照される変数はPhi結果に束縛し直す
self.update_variable(phi.var_name.clone(), phi.phi_id);
}
}

View File

@ -753,4 +753,95 @@ mod tests {
assert!(errs.iter().any(|e| matches!(e, VerificationError::MergeUsesPredecessorValue{..} | VerificationError::DominatorViolation{..})),
"Expected merge/dominator error, got: {:?}", errs);
}
#[test]
fn test_loop_phi_normalization() {
// Program:
// local i = 0
// loop(false) { i = 1 }
// i
let ast = ASTNode::Program {
statements: vec![
ASTNode::Local {
variables: vec!["i".to_string()],
initial_values: vec![Some(Box::new(ASTNode::Literal { value: LiteralValue::Integer(0), span: Span::unknown() }))],
span: Span::unknown(),
},
ASTNode::Loop {
condition: Box::new(ASTNode::Literal { value: LiteralValue::Bool(false), span: Span::unknown() }),
body: vec![ ASTNode::Assignment {
target: Box::new(ASTNode::Variable { name: "i".to_string(), span: Span::unknown() }),
value: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }),
span: Span::unknown(),
}],
span: Span::unknown(),
},
ASTNode::Variable { name: "i".to_string(), span: Span::unknown() },
],
span: Span::unknown(),
};
let mut builder = MirBuilder::new();
let module = builder.build_module(ast).expect("build mir");
// Verify SSA/dominance: should pass
let mut verifier = MirVerifier::new();
let res = verifier.verify_module(&module);
if let Err(errs) = &res { eprintln!("Verifier errors: {:?}", errs); }
assert!(res.is_ok(), "MIR loop with phi normalization should pass verification");
// Ensure phi is printed (header phi for variable i)
let printer = MirPrinter::verbose();
let mir_text = printer.print_module(&module);
assert!(mir_text.contains("phi"), "Printed MIR should contain a phi for loop header\n{}", mir_text);
}
#[test]
fn test_loop_nested_if_phi() {
// Program:
// local x = 0
// loop(false) { if true { x = 1 } else { x = 2 } }
// x
let ast = ASTNode::Program {
statements: vec![
ASTNode::Local {
variables: vec!["x".to_string()],
initial_values: vec![Some(Box::new(ASTNode::Literal { value: LiteralValue::Integer(0), span: Span::unknown() }))],
span: Span::unknown(),
},
ASTNode::Loop {
condition: Box::new(ASTNode::Literal { value: LiteralValue::Bool(false), span: Span::unknown() }),
body: vec![ ASTNode::If {
condition: Box::new(ASTNode::Literal { value: LiteralValue::Bool(true), span: Span::unknown() }),
then_body: vec![ ASTNode::Assignment {
target: Box::new(ASTNode::Variable { name: "x".to_string(), span: Span::unknown() }),
value: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }),
span: Span::unknown(),
}],
else_body: Some(vec![ ASTNode::Assignment {
target: Box::new(ASTNode::Variable { name: "x".to_string(), span: Span::unknown() }),
value: Box::new(ASTNode::Literal { value: LiteralValue::Integer(2), span: Span::unknown() }),
span: Span::unknown(),
}]),
span: Span::unknown(),
}],
span: Span::unknown(),
},
ASTNode::Variable { name: "x".to_string(), span: Span::unknown() },
],
span: Span::unknown(),
};
let mut builder = MirBuilder::new();
let module = builder.build_module(ast).expect("build mir");
let mut verifier = MirVerifier::new();
let res = verifier.verify_module(&module);
if let Err(errs) = &res { eprintln!("Verifier errors: {:?}", errs); }
assert!(res.is_ok(), "Nested if in loop should pass verification with proper phis");
let printer = MirPrinter::verbose();
let mir_text = printer.print_module(&module);
assert!(mir_text.contains("phi"), "Printed MIR should contain phi nodes for nested if/loop\n{}", mir_text);
}
}