feat(mir): Phase 30 F-3.0.7 LoopScopeShape Case-A minimal routing

Add func_name-based routing in LoopScopeShape::from_existing_boxes()
to prepare for MIR-based independent analysis:

- Add is_case_a_minimal_target() helper for 4 Case-A targets
- Add analyze_case_a() method (delegates to legacy, future MIR-based)
- Add from_existing_boxes_legacy() for backward compatibility
- Update from_existing_boxes() with func_name: Option<&str> parameter
- Update 4 production call sites with specific func_name
- Update 6 test cases with None for legacy path
- Update module documentation with routing logic diagram

Tests: 10/10 LoopScopeShape tests PASS, 6/7 JoinIR VM bridge tests PASS

🤖 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-26 00:36:41 +09:00
parent 2b47f47061
commit bf3893b2cc
5 changed files with 263 additions and 75 deletions

View File

@ -346,7 +346,12 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
eprintln!("[joinir/funcscanner_append_defs/mir] CFG sanity checks passed ✅");
eprintln!("[joinir/funcscanner_append_defs/mir] Found: length(), get(), push(), i+1");
// Phase 30 F-3: LoopScopeShape 経由の新API を使用
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") {
use crate::mir::join_ir::lowering::generic_case_a::lower_case_a_append_defs_with_scope;
use crate::mir::join_ir::lowering::loop_form_intake::intake_loop_form;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
let header = query.succs(entry).get(0).copied().unwrap_or(entry);
let succs_header = query.succs(header);
let body = succs_header.get(0).copied().unwrap_or(header);
@ -366,20 +371,27 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
);
let var_classes = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new();
let exit_live = crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox::new();
if let Some(jm) = crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_append_defs_minimal(
&loop_form,
&var_classes,
&exit_live,
&query,
target_func,
) {
eprintln!(
"[joinir/funcscanner_append_defs/generic-hook] generic_case_a produced JoinIR, returning early"
);
return Some(jm);
// Phase 30 F-3: LoopFormIntake + LoopScopeShape 経由で呼び出し
if let Some(intake) = intake_loop_form(&loop_form, &var_classes, &query, target_func) {
if let Some(scope) = LoopScopeShape::from_existing_boxes(
&loop_form,
&intake,
&var_classes,
&exit_live,
&query,
Some("FuncScannerBox.append_defs/2"), // Phase 30 F-3.1: Case-A minimal target
) {
if let Some(jm) = lower_case_a_append_defs_with_scope(scope) {
eprintln!(
"[joinir/funcscanner_append_defs/generic-hook] generic_case_a produced JoinIR via _with_scope, returning early"
);
return Some(jm);
}
}
}
eprintln!(
"[joinir/funcscanner_append_defs/generic-hook] generic_case_a returned None, falling back to handwritten/MIR path"
"[joinir/funcscanner_append_defs/generic-hook] generic_case_a via _with_scope returned None, falling back to handwritten/MIR path"
);
}
}

View File

@ -619,7 +619,12 @@ fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
eprintln!("[joinir/trim/mir] CFG sanity checks passed ✅");
// Phase 30 F-3: LoopScopeShape 経由の新API を使用
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") {
use crate::mir::join_ir::lowering::generic_case_a::lower_case_a_trim_with_scope;
use crate::mir::join_ir::lowering::loop_form_intake::intake_loop_form;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
let header = query.succs(entry_id).get(0).copied().unwrap_or(entry_id);
let succs_header = query.succs(header);
let body = succs_header.get(0).copied().unwrap_or(header);
@ -639,12 +644,25 @@ fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
);
let var_classes = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new();
let exit_live = crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox::new();
if let Some(jm) = crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_trim_minimal(&loop_form, &var_classes, &exit_live, &query, target_func) {
eprintln!("[joinir/trim/generic-hook] generic_case_a produced JoinIR, returning early");
return Some(jm);
// Phase 30 F-3: LoopFormIntake + LoopScopeShape 経由で呼び出し
if let Some(intake) = intake_loop_form(&loop_form, &var_classes, &query, target_func) {
if let Some(scope) = LoopScopeShape::from_existing_boxes(
&loop_form,
&intake,
&var_classes,
&exit_live,
&query,
Some("FuncScannerBox.trim/1"), // Phase 30 F-3.1: Case-A minimal target
) {
if let Some(jm) = lower_case_a_trim_with_scope(scope) {
eprintln!("[joinir/trim/generic-hook] generic_case_a produced JoinIR via _with_scope, returning early");
return Some(jm);
}
}
}
eprintln!(
"[joinir/trim/generic-hook] generic_case_a placeholder returned None, falling back to handwritten"
"[joinir/trim/generic-hook] generic_case_a via _with_scope returned None, falling back to handwritten"
);
}
}

View File

@ -12,19 +12,34 @@
//! - LoopScopeShape: 変数の「役割」pinned/carrier/body_local/exit_live
//! - JoinIR: 関数と継続で表現された制御構造
//!
//! # Phase 29 Strategy
//! # Phase 30 F-3.1 Strategy: Case-A Minimal Routing
//!
//! 現在は「既存箱を呼ぶだけの薄いラッパー」として実装。
//! 将来は既存箱を吸収して LoopScopeShape を唯一のソースにするPhase 30
//! `from_existing_boxes` メソッドは、関数名に基づいてCase-A minimalターゲット
//! skip_ws, trim, append_defs, stage1_using_resolverを新しいパスに
//! ルーティングする。現在は同じ結果を返すが、将来的に `analyze_case_a` は
//! MIRベースの独立解析を行う基盤となる。
//!
//! ## Routing Logic
//!
//! ```ignore
//! from_existing_boxes(func_name: Option<&str>)
//! ├── Some("Main.skip/1") → analyze_case_a()
//! ├── Some("FuncScannerBox.trim/1") → analyze_case_a()
//! ├── Some("FuncScannerBox.append_defs/2") → analyze_case_a()
//! ├── Some("Stage1UsingResolverBox.resolve_for_source/5") → analyze_case_a()
//! └── None or other → from_existing_boxes_legacy()
//! ```
//!
//! # Usage
//!
//! ```ignore
//! let scope = LoopScopeShape::from_existing_boxes(
//! &loop_form_intake,
//! &loop_form,
//! &intake,
//! &var_classes,
//! &exit_live,
//! query,
//! Some("Main.skip/1"), // Case-A minimal target
//! )?;
//!
//! // JoinIR lowering では scope のフィールドを参照
@ -42,6 +57,36 @@ use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox;
use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox};
use crate::mir::{BasicBlockId, MirQuery, ValueId};
// ============================================================================
// Phase 30 F-3.1: Case-A Minimal Target Detection
// ============================================================================
/// Phase 30 F-3.1: Case-A minimal ターゲット判定
///
/// 現在 JoinIR lowering でサポートしている Case-A minimal ループのみ true を返す。
/// これらは LoopScopeShape の新しい analyze_case_a パスを通る。
///
/// # Supported Targets
///
/// - `Main.skip/1`: minimal_ssa_skip_ws.hako
/// - `FuncScannerBox.trim/1`: funcscanner_trim_min.hako
/// - `FuncScannerBox.append_defs/2`: funcscanner_append_defs_min.hako
/// - `Stage1UsingResolverBox.resolve_for_source/5`: stage1_using_resolver minimal
///
/// # Future
///
/// この関数は将来的に LoopForm/LoopScopeShape ベースの汎用判定に置き換わる予定。
/// その時点でこのハードコードリストは削除される。
pub(crate) fn is_case_a_minimal_target(func_name: &str) -> bool {
matches!(
func_name,
"Main.skip/1"
| "FuncScannerBox.trim/1"
| "FuncScannerBox.append_defs/2"
| "Stage1UsingResolverBox.resolve_for_source/5"
)
}
/// ループ変数スコープの統合ビュー
///
/// # Phase 30: 変数分類の唯一の仕様ソース (SSOT)
@ -160,7 +205,7 @@ pub(crate) struct LoopScopeShape {
}
impl LoopScopeShape {
/// Create LoopScopeShape from existing boxes (Phase 30: unified interface)
/// Create LoopScopeShape from existing boxes (Phase 30 F-3.1: unified interface)
///
/// # Arguments
///
@ -169,28 +214,146 @@ impl LoopScopeShape {
/// - `var_classes`: LoopVarClassBox for classification
/// - `exit_live_box`: LoopExitLivenessBox for exit liveness
/// - `query`: MirQuery for liveness computation
/// - `func_name`: Optional function name for Case-A minimal routing
///
/// # Returns
///
/// Some(LoopScopeShape) if successful, None if critical data is missing.
///
/// # Phase 30 Design
/// # Phase 30 F-3.1 Design
///
/// This is the primary entry point for creating LoopScopeShape.
/// Block IDs come from loop_form, variable classification from existing boxes.
/// Future phases will internalize the box logic entirely.
/// - Case-A minimal targets route through `analyze_case_a` (new path)
/// - Other loops use `from_existing_boxes_legacy` (existing path)
///
/// Both paths currently produce the same result, but analyze_case_a
/// is the foundation for future MIR-based independent analysis.
pub(crate) fn from_existing_boxes(
loop_form: &LoopForm,
intake: &LoopFormIntake,
var_classes: &LoopVarClassBox,
exit_live_box: &LoopExitLivenessBox,
query: &impl MirQuery,
func_name: Option<&str>,
) -> Option<Self> {
// Phase 30 F-3.1: Route Case-A minimal targets through new path
if let Some(name) = func_name {
if is_case_a_minimal_target(name) {
return Self::analyze_case_a(
loop_form, intake, var_classes, exit_live_box, query, name,
);
}
}
// Default: use legacy path
Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query)
}
/// Check if a variable needs header PHI
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
pub fn needs_header_phi(&self, var_name: &str) -> bool {
self.pinned.contains(var_name) || self.carriers.contains(var_name)
}
/// Check if a variable needs exit PHI
pub fn needs_exit_phi(&self, var_name: &str) -> bool {
self.exit_live.contains(var_name)
}
/// Get ordered pinned variables (for JoinIR parameter generation)
pub fn pinned_ordered(&self) -> Vec<String> {
self.pinned.iter().cloned().collect()
}
/// Get ordered carrier variables (for JoinIR parameter generation)
pub fn carriers_ordered(&self) -> Vec<String> {
self.carriers.iter().cloned().collect()
}
/// Get all variables that need header PHI (pinned + carriers)
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
pub fn header_phi_vars(&self) -> Vec<String> {
let mut result: Vec<String> = self.pinned.iter().cloned().collect();
result.extend(self.carriers.iter().cloned());
result
}
/// Get all variables that need exit PHI
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
pub fn exit_phi_vars(&self) -> Vec<String> {
self.exit_live.iter().cloned().collect()
}
// =========================================================================
// Phase 30 F-3.1: Case-A Minimal Analysis Path
// =========================================================================
/// Phase 30 F-3.1: Case-A minimal 用の解析パス
///
/// 現在は `from_existing_boxes_legacy` と同じ実装だが、
/// debug_assert で結果を検証し、将来的に MIR ベースの独立実装に移行する。
///
/// # Arguments
///
/// - `loop_form`: LoopForm containing block structure
/// - `intake`: LoopFormIntake containing classified variable info
/// - `var_classes`: LoopVarClassBox for classification
/// - `exit_live_box`: LoopExitLivenessBox for exit liveness
/// - `query`: MirQuery for liveness computation
/// - `func_name`: 関数名(ログ用)
///
/// # Returns
///
/// Some(LoopScopeShape) if successful, None if critical data is missing.
fn analyze_case_a(
loop_form: &LoopForm,
intake: &LoopFormIntake,
var_classes: &LoopVarClassBox,
exit_live_box: &LoopExitLivenessBox,
query: &impl MirQuery,
func_name: &str,
) -> Option<Self> {
// Phase 30 F-3.1: 現在は legacy と同じ実装
// 将来は MIR から独立して pinned/carriers/body_locals/exit_live を計算
let result = Self::from_existing_boxes_legacy(
loop_form, intake, var_classes, exit_live_box, query,
)?;
// Debug: Case-A minimal path が使われていることをログ
if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() {
eprintln!(
"[loopscope/case_a] {} via analyze_case_a path (pinned={}, carriers={}, exit_live={})",
func_name,
result.pinned.len(),
result.carriers.len(),
result.exit_live.len(),
);
}
// TODO (F-3.1+): MIR ベースの独立計算を実装し、ここで debug_assert_eq! する
// let mir_based_result = Self::compute_from_mir(...);
// debug_assert_eq!(result.pinned, mir_based_result.pinned);
// debug_assert_eq!(result.carriers, mir_based_result.carriers);
Some(result)
}
/// Phase 30 F-3.1: 従来の既存箱ベース実装legacy path
///
/// analyze_case_a と分離することで、Case-A minimal だけ新パスを通せる。
fn from_existing_boxes_legacy(
loop_form: &LoopForm,
intake: &LoopFormIntake,
var_classes: &LoopVarClassBox,
exit_live_box: &LoopExitLivenessBox,
query: &impl MirQuery,
) -> Option<Self> {
// Extract block IDs from LoopForm
let header = loop_form.header;
let body = loop_form.body;
let latch = loop_form.latch;
let exit = loop_form.exit;
// Extract pinned and carriers from intake (already classified)
let pinned: BTreeSet<String> = intake.pinned_ordered.iter().cloned().collect();
let carriers: BTreeSet<String> = intake.carrier_ordered.iter().cloned().collect();
@ -266,41 +429,6 @@ impl LoopScopeShape {
})
}
/// Check if a variable needs header PHI
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
pub fn needs_header_phi(&self, var_name: &str) -> bool {
self.pinned.contains(var_name) || self.carriers.contains(var_name)
}
/// Check if a variable needs exit PHI
pub fn needs_exit_phi(&self, var_name: &str) -> bool {
self.exit_live.contains(var_name)
}
/// Get ordered pinned variables (for JoinIR parameter generation)
pub fn pinned_ordered(&self) -> Vec<String> {
self.pinned.iter().cloned().collect()
}
/// Get ordered carrier variables (for JoinIR parameter generation)
pub fn carriers_ordered(&self) -> Vec<String> {
self.carriers.iter().cloned().collect()
}
/// Get all variables that need header PHI (pinned + carriers)
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
pub fn header_phi_vars(&self) -> Vec<String> {
let mut result: Vec<String> = self.pinned.iter().cloned().collect();
result.extend(self.carriers.iter().cloned());
result
}
/// Get all variables that need exit PHI
#[allow(dead_code)] // Phase 30: will be used in F-3 generic lowering
pub fn exit_phi_vars(&self) -> Vec<String> {
self.exit_live.iter().cloned().collect()
}
/// Phase 30 F-1.1: 変数を4分類に分類する
///
/// LoopVarClassBox.classify() と同じロジックを LoopScopeShape の内部状態から導出。
@ -548,6 +676,7 @@ mod tests {
&var_classes,
&exit_live_box,
&query,
None, // generic test - use legacy path
);
assert!(scope.is_some());
@ -586,6 +715,7 @@ mod tests {
&var_classes,
&exit_live_box,
&query,
None, // generic test - use legacy path
)
.unwrap();
@ -609,6 +739,7 @@ mod tests {
&var_classes,
&exit_live_box,
&query,
None, // generic test - use legacy path
)
.unwrap();
@ -632,6 +763,7 @@ mod tests {
&var_classes,
&exit_live_box,
&query,
None, // generic test - use legacy path
)
.unwrap();
@ -694,6 +826,7 @@ mod tests {
&var_classes,
&exit_live_box,
&query,
None, // generic test - use legacy path
)
.unwrap();
@ -741,6 +874,7 @@ mod tests {
&var_classes,
&exit_live_box,
&query,
None, // generic test - use legacy path
)
.unwrap();

View File

@ -352,8 +352,12 @@ fn lower_skip_ws_handwritten_or_mir(module: &crate::mir::MirModule) -> Option<Jo
}
/// トグル ON 時にだけ試す generic Case A ロワーminimal_ssa_skip_ws 限定)
///
/// Phase 30 F-3: LoopScopeShape 経由の新API を使用
fn try_lower_skip_ws_generic_case_a(module: &crate::mir::MirModule) -> Option<JoinModule> {
use crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_minimal_skip_ws;
use crate::mir::join_ir::lowering::generic_case_a::lower_case_a_skip_ws_with_scope;
use crate::mir::join_ir::lowering::loop_form_intake::intake_loop_form;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::loop_form::LoopForm;
use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox;
use crate::mir::phi_core::loop_var_classifier::LoopVarClassBox;
@ -380,14 +384,23 @@ fn try_lower_skip_ws_generic_case_a(module: &crate::mir::MirModule) -> Option<Jo
break_targets: vec![exit],
};
// Phase 30 F-3: 実データ Box を使用(空箱から実箱へ)
let var_classes = LoopVarClassBox::new();
let exit_live = LoopExitLivenessBox::new();
lower_case_a_loop_to_joinir_for_minimal_skip_ws(
// LoopFormIntake 構築
let intake = intake_loop_form(&loop_form, &var_classes, &query, target_func)?;
// LoopScopeShape 構築
let scope = LoopScopeShape::from_existing_boxes(
&loop_form,
&intake,
&var_classes,
&exit_live,
&query,
target_func,
)
Some("Main.skip/1"), // Phase 30 F-3.1: Case-A minimal target
)?;
// 新API経由で JoinModule 生成
lower_case_a_skip_ws_with_scope(scope)
}

View File

@ -343,7 +343,12 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
eprintln!("[joinir/stage1_using_resolver/mir] CFG sanity checks passed ✅");
// Phase 30 F-3: LoopScopeShape 経由の新API を使用
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") {
use crate::mir::join_ir::lowering::generic_case_a::lower_case_a_stage1_usingresolver_with_scope;
use crate::mir::join_ir::lowering::loop_form_intake::intake_loop_form;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
let header = query.succs(entry).get(0).copied().unwrap_or(entry);
let succs_header = query.succs(header);
let body = succs_header.get(0).copied().unwrap_or(header);
@ -365,21 +370,27 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
let exit_live = crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox::new();
let params_len = target_func.params.len();
if params_len == 5 {
if let Some(jm) = crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal(
&loop_form,
&var_classes,
&exit_live,
&query,
target_func,
) {
eprintln!(
"[joinir/stage1_using_resolver/generic-hook] generic_case_a produced JoinIR, returning early"
);
return Some(jm);
// Phase 30 F-3: LoopFormIntake + LoopScopeShape 経由で呼び出し
if let Some(intake) = intake_loop_form(&loop_form, &var_classes, &query, target_func) {
if let Some(scope) = LoopScopeShape::from_existing_boxes(
&loop_form,
&intake,
&var_classes,
&exit_live,
&query,
Some("Stage1UsingResolverBox.resolve_for_source/5"), // Phase 30 F-3.1: Case-A minimal target
) {
if let Some(jm) = lower_case_a_stage1_usingresolver_with_scope(scope) {
eprintln!(
"[joinir/stage1_using_resolver/generic-hook] generic_case_a produced JoinIR via _with_scope, returning early"
);
return Some(jm);
}
}
}
}
eprintln!(
"[joinir/stage1_using_resolver/generic-hook] generic_case_a returned None or params mismatch, falling back to handwritten/MIR path"
"[joinir/stage1_using_resolver/generic-hook] generic_case_a via _with_scope returned None or params mismatch, falling back to handwritten/MIR path"
);
}
}