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:
nyash-codex
2025-11-27 17:05:46 +09:00
parent 6a5701ead9
commit 588129db65
15 changed files with 1891 additions and 69 deletions

View 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);
}
}

View File

@ -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
// - 実用MIRconst + 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),

View File

@ -13,12 +13,14 @@
//! - `stage1_using_resolver.rs`: Stage1UsingResolverBox.resolve_for_source entries loop loweringPhase 27.12
//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 loweringPhase 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