feat(joinir): S-5.2完了 - BoxCall → VM method_router 経由実装

実装内容:
- box_to_join_value() ヘルパー関数追加(VM Box → JoinValue 変換)
- StringBox.length/substring を VM 実装経由で呼び出し(hardcoded削除)
- safe_substring() 削除(不要になった)

技術的成果:
- ガードレール設計実現: 制御フローは JoinIR Runner、Box実装は VM に委譲
- VM 2号機 回避: Box実装の重複なし
- テスト全 PASS: joinir_runner_standalone_skip_ws/trim 両方成功

Phase 27-shortterm S-5.2 完了 
This commit is contained in:
nyash-codex
2025-11-24 08:01:56 +09:00
parent 03cb0a49c7
commit a8555e67d5
2 changed files with 40 additions and 21 deletions

View File

@ -12,7 +12,7 @@
- **25.x**: Stage0/Stage1/StageB / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。
- **25.1 系**: StageB / Stage1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。
- **26-F / 26-G**: Exit PHI / ExitLiveness 用の 4箱構成LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBoxと MirScanExitLiveness の準備。
- **26-H / 27.xNew**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage1 UsingResolver minimal/FuncScanner.append_defs minimal までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 予定。27.8〜27.11 で skip_ws/trim を Shared Builder PatternMIR-based lowering に移行し、27.12/27.13 で Stage1 UsingResolver minimal loop も同じ型に乗せ、27.14 では FuncScanner.append_defs 用の loweringminimal .hakoauto_lowering テストまで整備済み。短期フェーズ残りは JoinIR runner の命令セット整理とスモーク固め)。
- **26-H / 27.xNew**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage1 UsingResolver minimal/FuncScanner.append_defs minimal までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 予定。27.8〜27.11 で skip_ws/trim を Shared Builder PatternMIR-based lowering に移行し、27.12/27.13 で Stage1 UsingResolver minimal loop も同じ型に乗せ、27.14 では FuncScanner.append_defs 用の loweringminimal .hakoauto_lowering テストまで整備済み。短期フェーズ残りは JoinIR→Rust VM ブリッジの最小実装と、それを使った skip_ws/trim/Stage1 minimal あたりの A/B テスト整備)。
- Rust 側:
- LoopForm v2 + ControlForm + Conservative PHI は、代表テストStage1 UsingResolver / StageB 最小ループ)ではほぼ安定。
- 静的メソッド呼び出し規約と `continue` 絡みの PHI は 25.1m までで根治済み。

View File

@ -138,6 +138,9 @@ fn eval_compute(inst: &MirLikeInst, locals: &mut HashMap<VarId, JoinValue>) -> R
let v = crate::mir::join_ir_ops::eval_compare(*op, &l, &r)?;
locals.insert(*dst, v);
}
// S-5.2: BoxCall → VM method_router 経由(ガードレール設計)
// - 制御フロー: JoinIR Runner が担当
// - Box/Plugin 実装: Rust VM に委譲VM 2号機を避ける
MirLikeInst::BoxCall {
dst,
box_name,
@ -153,9 +156,14 @@ fn eval_compute(inst: &MirLikeInst, locals: &mut HashMap<VarId, JoinValue>) -> R
match method.as_str() {
"length" => {
let arg = expect_str(&read_var(locals, args[0])?)?;
locals.insert(*dst.as_ref().ok_or_else(|| {
// S-5.2: VM の StringBox.length() 実装を使用hardcoded 削除)
let string_box = crate::boxes::basic::StringBox::new(arg);
let result_box = string_box.length();
let result_value = box_to_join_value(result_box)?;
let dst_var = dst.ok_or_else(|| {
JoinRuntimeError::new("length call requires destination")
})?, JoinValue::Int(arg.len() as i64));
})?;
locals.insert(dst_var, result_value);
}
"substring" => {
if args.len() != 3 {
@ -164,13 +172,16 @@ fn eval_compute(inst: &MirLikeInst, locals: &mut HashMap<VarId, JoinValue>) -> R
));
}
let s = expect_str(&read_var(locals, args[0])?)?;
let start = expect_int(&read_var(locals, args[1])?)?;
let end = expect_int(&read_var(locals, args[2])?)?;
let slice = safe_substring(&s, start, end)?;
let start = expect_int(&read_var(locals, args[1])?)? as usize;
let end = expect_int(&read_var(locals, args[2])?)? as usize;
// S-5.2: VM の StringBox.substring() 実装を使用hardcoded 削除)
let string_box = crate::boxes::basic::StringBox::new(s);
let result_box = string_box.substring(start, end);
let result_value = box_to_join_value(result_box)?;
let dst_var = dst.ok_or_else(|| {
JoinRuntimeError::new("substring call requires destination")
})?;
locals.insert(dst_var, JoinValue::Str(slice));
locals.insert(dst_var, result_value);
}
_ => {
return Err(JoinRuntimeError::new(format!(
@ -184,20 +195,6 @@ fn eval_compute(inst: &MirLikeInst, locals: &mut HashMap<VarId, JoinValue>) -> R
Ok(())
}
fn safe_substring(s: &str, start: i64, end: i64) -> Result<String, JoinRuntimeError> {
if start < 0 || end < 0 {
return Err(JoinRuntimeError::new("substring indices must be non-negative"));
}
let (start_usize, end_usize) = (start as usize, end as usize);
if start_usize > end_usize {
return Err(JoinRuntimeError::new("substring start > end"));
}
if start_usize > s.len() || end_usize > s.len() {
return Err(JoinRuntimeError::new("substring indices out of bounds"));
}
Ok(s[start_usize..end_usize].to_string())
}
fn read_var(locals: &HashMap<VarId, JoinValue>, var: VarId) -> Result<JoinValue, JoinRuntimeError> {
locals
.get(&var)
@ -243,3 +240,25 @@ fn expect_str(value: &JoinValue) -> Result<String, JoinRuntimeError> {
))),
}
}
/// S-5.2: Convert Box<dyn NyashBox> from VM to JoinValue
///
/// Tries to downcast to known primitive types first (IntegerBox, BoolBox, StringBox),
/// otherwise wraps as BoxRef for future use.
fn box_to_join_value(nyash_box: Box<dyn crate::box_trait::NyashBox>) -> Result<JoinValue, JoinRuntimeError> {
use std::sync::Arc;
// Try to downcast to known primitive types first
if let Some(int_box) = nyash_box.as_any().downcast_ref::<crate::boxes::basic::IntegerBox>() {
return Ok(JoinValue::Int(int_box.value));
}
if let Some(bool_box) = nyash_box.as_any().downcast_ref::<crate::boxes::basic::BoolBox>() {
return Ok(JoinValue::Bool(bool_box.value));
}
if let Some(str_box) = nyash_box.as_any().downcast_ref::<crate::boxes::basic::StringBox>() {
return Ok(JoinValue::Str(str_box.value.clone()));
}
// Otherwise, wrap as BoxRef (for S-5.3/S-5.4 future use)
Ok(JoinValue::BoxRef(Arc::from(nyash_box)))
}