feat(joinir): Phase 34-6 MethodCall 構造と本物の substring 意味論
**Phase 34-6 実装完了**: MethodCall 構造を JoinIR に追加し、本物の substring
呼び出しを通すことに成功。
## 主要変更
### 1. MethodCall 構造追加 (34-6.1)
- `src/mir/join_ir/mod.rs`: JoinInst::MethodCall バリアント (+8 lines)
- 構造: `{ dst, receiver, method, args }`
- 設計原則: JoinIR は構造のみ、意味論は MIR レベル
### 2. extract_value 更新 (34-6.2)
- `src/mir/join_ir/frontend/ast_lowerer.rs`: Method 処理本物化 (+37 lines)
- receiver/args を extract_value で再帰処理
- ダミー Const(0) 削除 → 本物の MethodCall 生成
- cond 処理修正: ValueId(0) ハードコード → extract_value で取得
### 3. JoinIR→MIR 変換実装 (34-6.3)
- `src/mir/join_ir_vm_bridge.rs`: MethodCall → BoxCall 変換 (+12 lines)
- `src/mir/join_ir/json.rs`: MethodCall JSON シリアライゼーション (+16 lines)
- `src/mir/join_ir_runner.rs`: MethodCall 未対応エラー (+7 lines)
### 4. テスト更新 (34-6.4)
- `docs/.../fixtures/json_shape_read_value.program.json`: 本物の substring 構造
- `src/tests/joinir_frontend_if_select.rs`: run_joinir_via_vm 使用
- テスト成功: v="hello", at=3 → "hel" ✅
## 成果
- ✅ テスト全通過(1 passed; 0 failed)
- ✅ 設計原則確立: JoinIR = 構造 SSOT、意味論 = MIR レベル
- ✅ Phase 33-10 原則との整合性: Method でも同じ原則適用
**ドキュメント更新**: CURRENT_TASK.md + TASKS.md(Phase 34-6 完了記録)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
171
src/mir/join_ir/lowering/if_dry_runner.rs
Normal file
171
src/mir/join_ir/lowering/if_dry_runner.rs
Normal file
@ -0,0 +1,171 @@
|
||||
//! Phase 33-10.0: If lowering dry-run スキャナー(箱化版)
|
||||
//!
|
||||
//! ## 責務
|
||||
//! - MIR モジュール内のすべての Branch ブロックをスキャン
|
||||
//! - try_lower_if_to_joinir() でパターンマッチングを試行(MIR書き換えなし)
|
||||
//! - パフォーマンス計測と統計情報の収集
|
||||
//!
|
||||
//! ## 非責務
|
||||
//! - MIR の書き換え(Route B実装時に別モジュールで実施)
|
||||
//! - Loop lowering(別のdispatch経路)
|
||||
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::{MirFunction, MirInstruction};
|
||||
use std::collections::HashMap;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// If lowering dry-run スキャナー
|
||||
pub struct IfLoweringDryRunner {
|
||||
debug_level: u8,
|
||||
}
|
||||
|
||||
/// Dry-run スキャン結果の統計情報
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DryRunStats {
|
||||
pub total_branches: usize,
|
||||
pub lowered_count: usize,
|
||||
pub select_count: usize,
|
||||
pub ifmerge_count: usize,
|
||||
pub scan_duration: Duration,
|
||||
}
|
||||
|
||||
impl IfLoweringDryRunner {
|
||||
/// 新しい dry-run スキャナーを作成
|
||||
pub fn new(debug_level: u8) -> Self {
|
||||
Self { debug_level }
|
||||
}
|
||||
|
||||
/// MIR モジュール全体をスキャンして If lowering 成功率を計測
|
||||
///
|
||||
/// ## 実装方針(Phase 33-9.2)
|
||||
/// - Loop専任関数はスキップ(is_loop_lowered_function())
|
||||
/// - 各 Branch ブロックで try_lower_if_to_joinir() 試行
|
||||
/// - パフォーマンス計測(マイクロ秒レベル)
|
||||
/// - 統計情報収集(Select/IfMerge分類)
|
||||
pub fn scan_module(
|
||||
&self,
|
||||
functions: &HashMap<String, MirFunction>,
|
||||
) -> DryRunStats {
|
||||
let mut total_branches = 0;
|
||||
let mut lowered_count = 0;
|
||||
let mut select_count = 0;
|
||||
let mut ifmerge_count = 0;
|
||||
let start_scan = Instant::now();
|
||||
|
||||
for (func_name, func) in functions {
|
||||
// Phase 33-9.1: Loop専任関数をスキップ
|
||||
if crate::mir::join_ir::lowering::is_loop_lowered_function(func_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 各Branchブロックに対してtry_lower_if_to_joinir()試行
|
||||
for (block_id, block) in &func.blocks {
|
||||
if matches!(
|
||||
block.terminator,
|
||||
Some(MirInstruction::Branch { .. })
|
||||
) {
|
||||
total_branches += 1;
|
||||
let start = Instant::now();
|
||||
|
||||
match crate::mir::join_ir::lowering::try_lower_if_to_joinir(
|
||||
func,
|
||||
*block_id,
|
||||
self.debug_level >= 3,
|
||||
) {
|
||||
Some(join_inst) => {
|
||||
lowered_count += 1;
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
let inst_type = match &join_inst {
|
||||
JoinInst::Select { .. } => {
|
||||
select_count += 1;
|
||||
"Select"
|
||||
}
|
||||
JoinInst::IfMerge { .. } => {
|
||||
ifmerge_count += 1;
|
||||
"IfMerge"
|
||||
}
|
||||
_ => "Other",
|
||||
};
|
||||
|
||||
if self.debug_level >= 1 {
|
||||
eprintln!(
|
||||
"[joinir/if_lowering] ✅ {} block {:?}: {} ({:.2}μs)",
|
||||
func_name,
|
||||
block_id,
|
||||
inst_type,
|
||||
elapsed.as_micros()
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if self.debug_level >= 2 {
|
||||
eprintln!(
|
||||
"[joinir/if_lowering] ⏭️ {} block {:?}: pattern not matched",
|
||||
func_name, block_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let scan_duration = start_scan.elapsed();
|
||||
|
||||
DryRunStats {
|
||||
total_branches,
|
||||
lowered_count,
|
||||
select_count,
|
||||
ifmerge_count,
|
||||
scan_duration,
|
||||
}
|
||||
}
|
||||
|
||||
/// 統計情報を標準エラー出力に表示
|
||||
pub fn print_stats(&self, stats: &DryRunStats) {
|
||||
if self.debug_level >= 1 && stats.total_branches > 0 {
|
||||
eprintln!("[joinir/if_lowering] 📊 Scan complete:");
|
||||
eprintln!(" Total branches: {}", stats.total_branches);
|
||||
eprintln!(
|
||||
" Lowered: {} ({:.1}%)",
|
||||
stats.lowered_count,
|
||||
(stats.lowered_count as f64 / stats.total_branches as f64) * 100.0
|
||||
);
|
||||
eprintln!(" - Select: {}", stats.select_count);
|
||||
eprintln!(" - IfMerge: {}", stats.ifmerge_count);
|
||||
eprintln!(
|
||||
" Scan time: {:.2}ms",
|
||||
stats.scan_duration.as_secs_f64() * 1000.0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dry_runner_creation() {
|
||||
let runner = IfLoweringDryRunner::new(0);
|
||||
assert_eq!(runner.debug_level, 0);
|
||||
|
||||
let runner_verbose = IfLoweringDryRunner::new(3);
|
||||
assert_eq!(runner_verbose.debug_level, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dry_run_stats_default() {
|
||||
let stats = DryRunStats {
|
||||
total_branches: 0,
|
||||
lowered_count: 0,
|
||||
select_count: 0,
|
||||
ifmerge_count: 0,
|
||||
scan_duration: Duration::from_millis(10),
|
||||
};
|
||||
|
||||
assert_eq!(stats.total_branches, 0);
|
||||
assert_eq!(stats.lowered_count, 0);
|
||||
}
|
||||
}
|
||||
@ -119,6 +119,21 @@ impl IfSelectLowerer {
|
||||
let then_block = func.blocks.get(&branch.then_block)?;
|
||||
let else_block = func.blocks.get(&branch.else_block)?;
|
||||
|
||||
// Phase 33-10: PHI早期チェック(パターンマッチング前)
|
||||
// JoinIRは「PHI生成器」であり「PHI変換器」ではない
|
||||
// then/elseがJumpで終わる場合、merge blockにPHI命令があるか早期確認
|
||||
if let Some(merge_block_id) = self.get_merge_block_if_jump_pattern(&branch, then_block, else_block) {
|
||||
let merge_block = func.blocks.get(&merge_block_id)?;
|
||||
if merge_block.instructions.iter().any(|inst| {
|
||||
matches!(inst, MirInstruction::Phi { .. })
|
||||
}) {
|
||||
if self.debug_level >= 2 {
|
||||
eprintln!("[IfSelectLowerer] ⏭️ PHI already exists in merge block, skipping");
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. simple パターンのチェック
|
||||
if let Some(pattern) = self.try_match_simple_pattern(&branch, then_block, else_block) {
|
||||
// Phase 33-8: Level 2 - Pattern matching details
|
||||
@ -146,6 +161,10 @@ impl IfSelectLowerer {
|
||||
}
|
||||
|
||||
/// simple パターン: if cond { return 1 } else { return 2 }
|
||||
///
|
||||
/// Phase 33-9.2: 実用MIR対応 - 副作用なし命令(Const/Copy)を許容
|
||||
/// - 旧: Return のみ(instructions empty)
|
||||
/// - 新: Const/Copy → Return を許容(実MIRパターン)
|
||||
fn try_match_simple_pattern(
|
||||
&self,
|
||||
branch: &IfBranch,
|
||||
@ -164,8 +183,12 @@ impl IfSelectLowerer {
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// 両方のブロックが命令を持たない(Return のみ)ことを確認
|
||||
if !then_block.instructions.is_empty() || !else_block.instructions.is_empty() {
|
||||
// Phase 33-9.2: 副作用なし命令(Const/Copy)のみを許容
|
||||
// - ユニットテスト(empty)も通過(空配列 → all() = true)
|
||||
// - 実用MIR(const + ret)も通過
|
||||
if !self.is_side_effect_free(&then_block.instructions)
|
||||
|| !self.is_side_effect_free(&else_block.instructions)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -178,7 +201,53 @@ impl IfSelectLowerer {
|
||||
})
|
||||
}
|
||||
|
||||
/// Phase 33-9.2: 副作用なし命令判定ヘルパー
|
||||
///
|
||||
/// Const/Copy のみを許容(分岐・call・書き込み等は除外)
|
||||
fn is_side_effect_free(&self, instructions: &[MirInstruction]) -> bool {
|
||||
instructions.iter().all(|inst| {
|
||||
matches!(
|
||||
inst,
|
||||
MirInstruction::Const { .. } | MirInstruction::Copy { .. }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Phase 33-10: Jump pattern 検出ヘルパー
|
||||
///
|
||||
/// then/else 両方が Jump で終わり、同じ merge block に飛んでいる場合、
|
||||
/// その merge block IDを返す
|
||||
fn get_merge_block_if_jump_pattern(
|
||||
&self,
|
||||
_branch: &IfBranch,
|
||||
then_block: &crate::mir::BasicBlock,
|
||||
else_block: &crate::mir::BasicBlock,
|
||||
) -> Option<BasicBlockId> {
|
||||
// then が Jump で終わるか確認
|
||||
let then_target = match then_block.terminator.as_ref()? {
|
||||
MirInstruction::Jump { target } => *target,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// else が Jump で終わるか確認
|
||||
let else_target = match else_block.terminator.as_ref()? {
|
||||
MirInstruction::Jump { target } => *target,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// 両方が同じ merge block に飛んでいるか確認
|
||||
if then_target == else_target {
|
||||
Some(then_target)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// local パターン: if cond { x = a } else { x = b }; return x
|
||||
///
|
||||
/// Phase 33-10: 実用MIR対応 - Const命令を許容
|
||||
/// - 旧: Copy命令のみ(ユニットテスト想定)
|
||||
/// - 新: Const/Copy命令を許容(実MIR対応、Simple patternと同じ修正)
|
||||
fn try_match_local_pattern(
|
||||
&self,
|
||||
func: &MirFunction,
|
||||
@ -186,15 +255,21 @@ impl IfSelectLowerer {
|
||||
then_block: &crate::mir::BasicBlock,
|
||||
else_block: &crate::mir::BasicBlock,
|
||||
) -> Option<IfPattern> {
|
||||
// then ブロックが「1命令 + Jump」の形か確認
|
||||
if then_block.instructions.len() != 1 {
|
||||
// Phase 33-10: 副作用なし命令のみを許容
|
||||
if !self.is_side_effect_free(&then_block.instructions) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// then ブロックの命令が代入(Copy)か確認
|
||||
let (dst_then, val_then) = match &then_block.instructions[0] {
|
||||
MirInstruction::Copy { dst, src } => (*dst, *src),
|
||||
_ => return None,
|
||||
// then ブロックの最後の値を取得
|
||||
// Phase 33-10: Const命令も許容(実MIR対応)
|
||||
let (dst_then, val_then) = if then_block.instructions.len() == 1 {
|
||||
match &then_block.instructions[0] {
|
||||
MirInstruction::Copy { dst, src } => (*dst, *src),
|
||||
MirInstruction::Const { dst, .. } => (*dst, *dst), // Constの場合、dst自身が値
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// then ブロックが Jump で終わるか確認
|
||||
@ -203,14 +278,20 @@ impl IfSelectLowerer {
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// else ブロックも同じ構造か確認
|
||||
if else_block.instructions.len() != 1 {
|
||||
// Phase 33-10: else ブロックも副作用なし命令のみを許容
|
||||
if !self.is_side_effect_free(&else_block.instructions) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (dst_else, val_else) = match &else_block.instructions[0] {
|
||||
MirInstruction::Copy { dst, src } => (*dst, *src),
|
||||
_ => return None,
|
||||
// else ブロックの最後の値を取得
|
||||
let (dst_else, val_else) = if else_block.instructions.len() == 1 {
|
||||
match &else_block.instructions[0] {
|
||||
MirInstruction::Copy { dst, src } => (*dst, *src),
|
||||
MirInstruction::Const { dst, .. } => (*dst, *dst), // Constの場合、dst自身が値
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// 代入先が同じ変数か確認
|
||||
@ -230,6 +311,8 @@ impl IfSelectLowerer {
|
||||
|
||||
// merge ブロックが「return dst」だけか確認
|
||||
let merge_block = func.blocks.get(&merge_block_id)?;
|
||||
// Phase 33-10: PHIチェックは find_if_pattern() で早期実行済み
|
||||
|
||||
match merge_block.terminator.as_ref()? {
|
||||
MirInstruction::Return {
|
||||
value: Some(v),
|
||||
|
||||
@ -13,12 +13,14 @@
|
||||
//! - `stage1_using_resolver.rs`: Stage1UsingResolverBox.resolve_for_source entries loop lowering(Phase 27.12)
|
||||
//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 lowering(Phase 27.14)
|
||||
//! - `if_select.rs`: Phase 33 If/Else → Select lowering
|
||||
//! - `if_dry_runner.rs`: Phase 33-10 If lowering dry-run スキャナー(箱化版)
|
||||
|
||||
pub mod common;
|
||||
pub mod exit_args_resolver;
|
||||
pub mod funcscanner_append_defs;
|
||||
pub mod funcscanner_trim;
|
||||
pub mod generic_case_a;
|
||||
pub mod if_dry_runner; // Phase 33-10.0
|
||||
pub mod if_merge; // Phase 33-7
|
||||
pub mod if_select; // Phase 33
|
||||
pub mod loop_form_intake;
|
||||
@ -116,6 +118,7 @@ pub fn try_lower_if_to_joinir(
|
||||
let is_allowed =
|
||||
// Test functions (always enabled)
|
||||
func.signature.name.starts_with("IfSelectTest.") ||
|
||||
func.signature.name.starts_with("IfSelectLocalTest.") || // Phase 33-10 test
|
||||
func.signature.name.starts_with("IfMergeTest.") ||
|
||||
func.signature.name.starts_with("Stage1JsonScannerTestBox.") || // Phase 33-5 test
|
||||
|
||||
|
||||
Reference in New Issue
Block a user