Files
hakorune/src/mir/join_ir/lowering/skip_ws.rs
nyash-codex b0311e4bd2 feat(joinir): Phase 27.11.1 - skip_ws.rs Shared Builder Pattern 完了
## 成果
- **コード削減**: 444行 → 310行 (134行削除、30%削減)
- **重複コード根絶**: handwritten版とMIR版の重複JoinIR生成を統一
- **テスト結果**:
  - Baseline (toggle OFF): 380 passed
  - MIR-based (toggle ON): 385 passed  (5件改善!)

## 実装内容
1. `lower_skip_ws_handwritten()` → `build_skip_ws_joinir()` にリネーム
   - 共通JoinIRビルダー化
2. 新しい thin wrapper `lower_skip_ws_handwritten()` を作成
3. `lower_skip_ws_from_mir()` の重複コード (140行) を削除
   - CFGチェック後に `build_skip_ws_joinir()` を呼び出す構造に変更

## 設計パターン
- **Shared Builder Pattern**: funcscanner_trim.rs と同じパターン適用
- **CFG Sanity Checks**: MIR解析は軽量パターンマッチのみ
- **Graceful Degradation**: CFGチェック失敗時は自動フォールバック

Phase 27.11シリーズ 100%完了!

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-24 01:59:48 +09:00

311 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Phase 27.1: minimal_ssa_skip_ws 専用の MIR → JoinIR 変換
//!
//! 目的: apps/tests/minimal_ssa_skip_ws.hako の MIR を JoinIR に変換する実装
//!
//! 期待される変換:
//! ```text
//! // MIR (元):
//! static box Main {
//! skip(s) {
//! local i = 0
//! local n = s.length()
//! loop(1 == 1) {
//! if i >= n { break }
//! local ch = s.substring(i, i + 1)
//! if ch == " " { i = i + 1 } else { break }
//! }
//! return i
//! }
//! }
//!
//! // JoinIR (変換後):
//! fn skip(s_param, k_exit) {
//! i_init = 0
//! n = s_param.length()
//! loop_step(s_param, i_init, n, k_exit)
//! }
//!
//! fn loop_step(s, i, n, k_exit) {
//! if i >= n {
//! k_exit(i) // break
//! } else {
//! ch = s.substring(i, i + 1)
//! if ch == " " {
//! loop_step(s, i + 1, n, k_exit) // continue
//! } else {
//! k_exit(i) // break
//! }
//! }
//! }
//! ```
use crate::mir::ValueId;
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction,
JoinInst, JoinModule, LoopExitShape, LoopHeaderShape, MirLikeInst,
};
/// Phase 27.11.1: Common JoinIR builder for Main.skip/1
///
/// This function generates the JoinIR for skip/1, shared by both:
/// - lower_skip_ws_handwritten (always uses this)
/// - lower_skip_ws_from_mir (uses this after CFG sanity checks pass)
fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
// Step 1: "Main.skip/1" を探す
let target_func = module.functions.get("Main.skip/1")?;
eprintln!("[joinir/skip_ws/build] Found Main.skip/1");
eprintln!("[joinir/skip_ws/build] MIR blocks: {}", target_func.blocks.len());
// Step 2: JoinModule を構築
let mut join_module = JoinModule::new();
// Phase 27.1: 固定的な JoinIR を生成(実際の MIR 解析は Phase 28 以降)
// skip 関数: i_init = 0, n = s.length(), loop_step(s, 0, n, k_exit)
let skip_id = JoinFuncId::new(0);
let s_param = ValueId(3000);
let mut skip_func = JoinFunction::new(skip_id, "skip".to_string(), vec![s_param]);
let i_init = ValueId(3001);
let n = ValueId(3002);
// i_init = 0
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: i_init,
value: ConstValue::Integer(0),
}));
// n = s.length() (BoxCall でメソッド呼び出し)
skip_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(n),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![s_param],
}));
// loop_step(s, i_init, n, k_exit)
let loop_step_id = JoinFuncId::new(1);
skip_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![s_param, i_init, n],
k_next: None,
dst: None,
});
join_module.entry = Some(skip_id);
join_module.add_function(skip_func);
// Phase 27.4-A: loop_step の Pinned/Carrier 構造を明示
// skip_ws ループの場合:
// - Pinned: s (文字列), n (長さ) - ループ中で不変
// - Carrier: i (現在位置) - ループで更新される
let s_loop = ValueId(4000); // Pinned
let i_loop = ValueId(4001); // Carrier
let n_loop = ValueId(4002); // Pinned
let _header_shape = LoopHeaderShape::new_manual(
vec![s_loop, n_loop], // Pinned: s, n
vec![i_loop], // Carrier: i
);
// 将来: LoopHeaderShape.to_loop_step_params() は [pinned..., carriers...] の順を返す。
// 現在は既存 JoinIR テストとの互換性のため、手動で [s, i, n] の順を維持している。
// loop_step 関数: if i >= n { return i } else if ch == " " { loop_step(i + 1) } else { return i }
let mut loop_step_func = JoinFunction::new(
loop_step_id,
"loop_step".to_string(),
vec![s_loop, i_loop, n_loop], // [pinned, carrier, pinned] の順(現行実装)
);
let cmp1_result = ValueId(4003);
let ch = ValueId(4004);
let cmp2_result = ValueId(4005);
let i_plus_1 = ValueId(4006);
let const_1 = ValueId(4007);
let const_space = ValueId(4010);
let bool_false = ValueId(4011);
let cmp2_is_false = ValueId(4012);
// cmp1_result = (i >= n)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp1_result,
op: CompareOp::Ge,
lhs: i_loop,
rhs: n_loop,
}));
// Phase 27.5: Exit φ の意味を LoopExitShape で明示
// skip_ws のループ脱出時は i の値だけを返す(先頭空白の文字数)
let _exit_shape = LoopExitShape::new_manual(vec![i_loop]); // exit_args = [i]
// if i >= n { return i }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0),
args: vec![i_loop], // ← LoopExitShape.exit_args に対応
cond: Some(cmp1_result),
});
// const 1
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
// i_plus_1 = i + 1 (再利用: substring end / continue path)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_loop,
rhs: const_1,
}));
// ch = s.substring(i, i + 1)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![s_loop, i_loop, i_plus_1],
}));
// const " " (space)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_space,
value: ConstValue::String(" ".to_string()),
}));
// cmp2_result = (ch == " ")
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp2_result,
op: CompareOp::Eq,
lhs: ch,
rhs: const_space,
}));
// bool false (for negation)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: bool_false,
value: ConstValue::Bool(false),
}));
// cmp2_is_false = (cmp2_result == false)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp2_is_false,
op: CompareOp::Eq,
lhs: cmp2_result,
rhs: bool_false,
}));
// Phase 27.5: 2箇所目の exit パス(同じく exit_args = [i]
// if ch != " " { return i }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1),
args: vec![i_loop], // ← LoopExitShape.exit_args に対応1箇所目と同じ
cond: Some(cmp2_is_false),
});
// continue path: loop_step(s, i + 1, n)
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![s_loop, i_plus_1, n_loop],
k_next: None,
dst: None,
});
join_module.add_function(loop_step_func);
eprintln!("[joinir/skip_ws] Generated {} JoinIR functions", join_module.functions.len());
Some(join_module)
}
/// Phase 27.8: Main.skip/1 の JoinIR loweringMIR 自動解析版)
///
/// MIR 構造を解析して自動的に JoinIR を生成する実装。
/// Phase 27.8 で導入、将来的に hand-written 版を置き換える予定。
///
/// ## 環境変数:
/// - `NYASH_JOINIR_LOWER_FROM_MIR=1`: この実装を有効化
///
/// ## 実装状況:
/// - Phase 27.8: 基本実装MirQuery を使用した MIR 解析)
fn lower_skip_ws_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
use crate::mir::query::MirQueryBox;
use super::common::{ensure_entry_has_succs, has_const_int, has_string_method, log_fallback};
// Step 1: "Main.skip/1" を探す
let target_func = module.functions.get("Main.skip/1")?;
eprintln!("[joinir/skip_ws/mir] Found Main.skip/1 (MIR-based lowering)");
eprintln!("[joinir/skip_ws/mir] MIR blocks: {}", target_func.blocks.len());
// NOTE:
// このフェーズでは minimal_ssa_skip_ws.hako 固定のパターンを前提に、
// MIR の CFG を軽く確認した上で JoinIR を組み立てる。
// (完全一般化は次フェーズ以降で行う)
// 簡易チェック: ブロック数が最低限あるか確認
if target_func.blocks.len() < 3 {
log_fallback("skip_ws", &format!("insufficient blocks ({})", target_func.blocks.len()));
return lower_skip_ws_handwritten(module);
}
// Phase 27.10: Lightweight CFG sanity checks using common utilities
let query = MirQueryBox::new(target_func);
let entry_id = target_func.entry_block;
// Check 1: Entry block has at least 1 successor
if !ensure_entry_has_succs(&query, entry_id) {
log_fallback("skip_ws", "entry has no successors");
return lower_skip_ws_handwritten(module);
}
// Check 2: Entry block contains Const(0) and BoxCall(String.length)
if !has_const_int(&query, entry_id, 0) || !has_string_method(&query, entry_id, "length") {
log_fallback("skip_ws", "entry block missing Const(0) or String.length");
return lower_skip_ws_handwritten(module);
}
eprintln!("[joinir/skip_ws/mir] CFG sanity checks passed ✅");
// Phase 27.11.1: Generate JoinIR using shared builder
// CFG checks passed, so we can use build_skip_ws_joinir() directly
eprintln!("[joinir/skip_ws/mir] Calling build_skip_ws_joinir() after CFG validation");
return build_skip_ws_joinir(module);
}
/// Phase 27.11.1: Handwritten lowering wrapper for Main.skip/1
///
/// This is a thin wrapper that calls the shared build_skip_ws_joinir() function.
/// Maintains the handwritten lowering path as the baseline reference.
fn lower_skip_ws_handwritten(module: &crate::mir::MirModule) -> Option<JoinModule> {
eprintln!("[joinir/skip_ws/handwritten] Using handwritten lowering path");
build_skip_ws_joinir(module)
}
/// Phase 27.8: Main.skip/1 の JoinIR loweringトグル対応ディスパッチャー
///
/// 環境変数 `NYASH_JOINIR_LOWER_FROM_MIR=1` に応じて、
/// hand-written 版または MIR 自動解析版を選択する。
///
/// ## トグル制御:
/// - **OFF (デフォルト)**: `lower_skip_ws_handwritten()` を使用
/// - **ON**: `lower_skip_ws_from_mir()` を使用
///
/// ## 使用例:
/// ```bash
/// # 手書き版(既定)
/// ./target/release/hakorune program.hako
///
/// # MIR 自動解析版
/// NYASH_JOINIR_LOWER_FROM_MIR=1 ./target/release/hakorune program.hako
/// ```
pub fn lower_skip_ws_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
super::common::dispatch_lowering(
"skip_ws",
module,
lower_skip_ws_from_mir,
lower_skip_ws_handwritten,
)
}