Files
hakorune/src/mir/join_ir/lowering/loop_to_join.rs
nyash-codex b8893787dc feat(joinir): L-5.3 Phase 1 - progress carrier guard for generic_case_a
## 実装内容

1. has_safe_progress() helper 追加 (loop_to_join.rs:186-195)
   - Phase 1: scope.progress_carrier.is_some() をチェック (保守的)
   - Phase 2 (future): MirQuery で Add 命令チェック予定

2. is_supported_case_a_loop_view() に progress guard 追加 (256-266)
   - 無限ループの可能性があるループを事前にフォールバック
   - デバッグログで reject 理由を出力

## テスト結果

 25 passed; 0 failed - JoinIR 関連テスト全通過
 skip_ws / trim / append_defs / Stage‑1 UsingResolver 全ケース PASS
 既存テストへの影響なし

## 技術メモ

- 保守的アプローチ: progress_carrier.is_some() のみチェック
- LoopScopeShape で progress_carrier を carriers の先頭として設定済み
- ignored テスト失敗は MIR 自体の PHI バグで、本変更とは無関係 (git stash で確認済み)

関連: Phase 29 L-5.3 (TASKS.md), CURRENT_TASK.md 1-00v
2025-11-26 16:08:49 +09:00

439 lines
17 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 31: LoopToJoinLowerer - 統一 Loop→JoinIR 変換箱
//!
//! このモジュールは MIR の LoopForm を JoinIR に変換する統一インターフェースを提供する。
//!
//! ## 設計思想
//!
//! - **単一エントリポイント**: `LoopToJoinLowerer::lower()` ですべてのループを処理
//! - **パターン自動判定**: LoopScopeShape を解析して適切な変換を選択
//! - **既存コード再利用**: generic_case_a の `_with_scope` 関数を内部で呼び出し
//!
//! ## 使用例
//!
//! ```ignore
//! let lowerer = LoopToJoinLowerer::new();
//! let join_module = lowerer.lower(func, &loop_form, &query)?;
//! ```
use crate::mir::control_form::{analyze_exits, ExitEdge, LoopControlShape, LoopId, LoopRegion};
use crate::mir::join_ir::lowering::generic_case_a;
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::join_ir::JoinModule;
use crate::mir::loop_form::LoopForm;
use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox;
use crate::mir::phi_core::loop_var_classifier::LoopVarClassBox;
use crate::mir::query::MirQueryBox;
use crate::mir::MirFunction;
/// Phase 32 L-1.2: 汎用 Case-A lowering が有効かどうか
///
/// `NYASH_JOINIR_LOWER_GENERIC=1` の場合、関数名フィルタを外して
/// 構造ベースで Case-A ループを拾う。
fn generic_case_a_enabled() -> bool {
crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC")
}
/// Loop→JoinIR 変換の統一箱
///
/// Phase 31 で導入された統一インターフェース。
/// 全ての MIR LoopForm を JoinIR に正規化する。
pub struct LoopToJoinLowerer {
/// デバッグモード(詳細ログ出力)
debug: bool,
}
impl Default for LoopToJoinLowerer {
fn default() -> Self {
Self::new()
}
}
impl LoopToJoinLowerer {
/// 新しい LoopToJoinLowerer を作成
pub fn new() -> Self {
let debug = std::env::var("NYASH_LOOPTOJOIN_DEBUG")
.map(|v| v == "1")
.unwrap_or(false);
Self { debug }
}
/// MIR LoopForm を JoinIR に変換
///
/// # Arguments
///
/// - `func`: MIR 関数
/// - `loop_form`: 変換対象の LoopForm
/// - `func_name`: 関数名(オプション、ルーティング用)
///
/// # Returns
///
/// - `Some(JoinModule)`: 変換成功
/// - `None`: 変換失敗(フォールバック経路へ)
pub fn lower(
&self,
func: &MirFunction,
loop_form: &LoopForm,
func_name: Option<&str>,
) -> Option<JoinModule> {
if self.debug {
eprintln!(
"[LoopToJoinLowerer] lower() called for {:?}",
func_name.unwrap_or("<unknown>")
);
}
// Phase 32 L-1.2: 早期フィルタは削除。構造チェック後に条件付きで適用する。
// Step 1: MirQuery を構築
let query = MirQueryBox::new(func);
// Step 2: 分類箱を構築Phase 31 では空箱、将来 MIR 解析で埋める)
let var_classes = LoopVarClassBox::new();
let exit_live = LoopExitLivenessBox::new();
// Step 3: LoopFormIntake を構築
let intake = intake_loop_form(loop_form, &var_classes, &query, func)?;
// Step 4: LoopScopeShape を構築
let scope =
LoopScopeShape::from_existing_boxes(loop_form, &intake, &var_classes, &exit_live, &query, func_name)?;
if self.debug {
eprintln!(
"[LoopToJoinLowerer] LoopScopeShape built: pinned={:?}, carriers={:?}, exit_live={:?}",
scope.pinned, scope.carriers, scope.exit_live
);
}
// Phase 32 Step 3-C: View メソッドで構造情報を取得(常に実行)
let loop_id = LoopId(0); // 単一ループの場合は 0
let region = loop_form.to_region_view(loop_id);
let control = loop_form.to_control_view(loop_id);
let exit_edges = loop_form.to_exit_edges(loop_id);
// Debug: view ベースの情報をログ
if self.debug {
eprintln!(
"[LoopToJoinLowerer] Phase 32 views: func={:?} loop_id={:?} header={:?} exits={:?}",
func_name.unwrap_or("<unknown>"),
loop_id,
region.header,
exit_edges.iter().map(|e| e.to).collect::<Vec<_>>()
);
}
// Phase 32 L-1.4: ExitAnalysis ベースの Case-A サポートチェック
if !self.is_supported_case_a_loop_view(func, &region, &control, &exit_edges, &scope) {
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected by view-based check: {:?}",
func_name.unwrap_or("<unknown>")
);
}
return None;
}
// Phase 32 L-1.2: 環境変数で分岐
// OFF既定: 従来の minimal 4 本だけ
// ON: 構造だけで通す(関数名フィルタを外す)
if !generic_case_a_enabled() {
if !func_name.map_or(false, super::loop_scope_shape::is_case_a_minimal_target) {
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected by name filter (generic disabled): {:?}",
func_name.unwrap_or("<unknown>")
);
}
return None;
}
} else if self.debug {
eprintln!(
"[LoopToJoinLowerer] generic Case-A enabled, allowing {:?}",
func_name.unwrap_or("<unknown>")
);
}
// Step 5: パターンに応じた lowering を実行
self.lower_with_scope(scope, func_name)
}
/// Phase 29 L-5.3: Progress carrier の安全性をチェック
///
/// 無限ループの可能性があるループprogress carrier が無い、または更新されない)
/// を事前に弾く。
///
/// # Phase 1 実装(保守的)
///
/// - `scope.progress_carrier.is_some()` をチェック
/// - progress_carrier が設定されていればループは進捗すると仮定
///
/// # Phase 2 (future)
///
/// - MirQuery で header→latch 間に Add 命令があるかチェック
/// - skip_ws verifier のロジックを MIR レベルで簡略化して適用
///
/// # Arguments
///
/// - `scope`: LoopScopeShapeprogress_carrier 情報を持つ)
/// - `func`: MIR 関数(将来の MirQuery 用)
/// - `region`: LoopRegion将来の header→latch チェック用)
///
/// # Returns
///
/// - `true`: Progress carrier ありsafe
/// - `false`: Progress carrier なしunsafe、fallback すべき)
fn has_safe_progress(
scope: &LoopScopeShape,
_func: &MirFunction, // Phase 2 で使用予定
_region: &LoopRegion, // Phase 2 で使用予定
) -> bool {
// Phase 1: 保守的チェック
// progress_carrier が設定されていれば、ループは進捗すると仮定
// (典型的には 'i' のような loop index
scope.progress_carrier.is_some()
}
/// Case-A ループとしてサポートされているかチェックView ベース版)
///
/// Phase 32 L-1.4: ExitGroup ベースの判定に強化
///
/// # Case-A の定義
///
/// - **単一出口グループ**: 全ての出口辺が同じターゲットブロックに向かう
/// (例: `if c != ' ' && c != '\t' { break }` は複数 ExitEdge だが、
/// 同じ exit ブロックに向かうので Case-A として許可)
/// - **非ローカル出口なし**: Return/Throw がない
/// - ヘッダブロックの succ が 2 つcond true/false
/// - ループ変数または固定変数が存在
///
/// # Arguments
///
/// - `func`: MIR 関数(ヘッダ succ チェック用)
/// - `region`: LoopRegionブロック構造
/// - `control`: LoopControlShape制御フロー辺
/// - `exit_edges`: ExitEdge のリスト(グループ化分析用)
/// - `scope`: LoopScopeShape変数分類用
///
/// # Returns
///
/// - `true`: Case-A として lowering 可能
/// - `false`: 未サポート(フォールバック経路へ)
fn is_supported_case_a_loop_view(
&self,
func: &MirFunction,
region: &LoopRegion,
_control: &LoopControlShape, // Phase 32 L-1.4: ExitEdge ベースに移行したため未使用
exit_edges: &[ExitEdge],
scope: &LoopScopeShape,
) -> bool {
// Phase 32 L-1.4: ExitAnalysis ベースの出口判定
let exit_analysis = analyze_exits(exit_edges);
// 1) 単一出口グループ + 非ローカル出口なし
// 複数の ExitEdge でも、同じターゲットに向かうなら Case-A として許可
if !exit_analysis.is_single_exit_group() {
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected: not single exit group (groups={}, nonlocal={})",
exit_analysis.loop_exit_groups.len(),
exit_analysis.nonlocal_exits.len()
);
// 詳細ログ: 各グループのターゲットを出力
for (i, group) in exit_analysis.loop_exit_groups.iter().enumerate() {
eprintln!(
" group[{}]: target={:?}, edges={}, has_break={}",
i,
group.target,
group.edges.len(),
group.has_break
);
}
}
return false;
}
// Note: control.exits は ExitEdge の数(辺の数)なので、
// 複数でも単一グループなら OK という新しいロジック
// 2) ヘッダブロックの succ が 2 つcond true → body, cond false → exit
// これにより while(cond) 形式のループのみを対象とする
if let Some(header_block) = func.blocks.get(&region.header) {
let succ_count = header_block.successors.len();
if succ_count != 2 {
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected: header {:?} has {} successors (expected 2)",
region.header,
succ_count
);
}
return false;
}
} else {
// ヘッダブロックが見つからない(異常ケース)
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected: header block {:?} not found",
region.header
);
}
return false;
}
// 3) ループ変数または固定変数がある(空ループは対象外)
if scope.carriers.is_empty() && scope.pinned.is_empty() {
if self.debug {
eprintln!("[LoopToJoinLowerer] rejected: no carriers or pinned vars");
}
return false;
}
// 4) Phase 29 L-5.3: Progress carrier チェック
// 無限ループの可能性があるループを事前に弾く
if !Self::has_safe_progress(scope, func, region) {
if self.debug {
eprintln!(
"[LoopToJoinLowerer] rejected: no safe progress carrier (progress_carrier={:?})",
scope.progress_carrier
);
}
return false;
}
true
}
// Note: is_supported_case_a_loop (legacy) は Phase 32 で削除済み
// 代替: is_supported_case_a_loop_view() を使用
/// LoopScopeShape から JoinModule を生成(内部メソッド)
///
/// Phase 32 L-1.2: 関数名で 4 パターン + 汎用 Case-A にディスパッチ
fn lower_with_scope(
&self,
scope: LoopScopeShape,
func_name: Option<&str>,
) -> Option<JoinModule> {
let name = func_name.unwrap_or("");
// Phase 32 L-1.2: minimal 4 本にマッチしたらそれぞれの lowerer を使う
// マッチしない場合は汎用 Case-Aまだ未実装、None を返す)
let result = match name {
"Main.skip/1" => {
if self.debug {
eprintln!("[LoopToJoinLowerer] dispatching to skip_ws lowerer");
}
generic_case_a::lower_case_a_skip_ws_with_scope(scope)
}
"FuncScannerBox.trim/1" => {
if self.debug {
eprintln!("[LoopToJoinLowerer] dispatching to trim lowerer");
}
generic_case_a::lower_case_a_trim_with_scope(scope)
}
"FuncScannerBox.append_defs/2" => {
if self.debug {
eprintln!("[LoopToJoinLowerer] dispatching to append_defs lowerer");
}
generic_case_a::lower_case_a_append_defs_with_scope(scope)
}
"Stage1UsingResolverBox.resolve_for_source/5" => {
if self.debug {
eprintln!("[LoopToJoinLowerer] dispatching to stage1 lowerer");
}
generic_case_a::lower_case_a_stage1_usingresolver_with_scope(scope)
}
_ => {
// Phase 32 L-1.2: 汎用 Case-A 候補
// ここに来るのは NYASH_JOINIR_LOWER_GENERIC=1 の時だけ
// TODO: 汎用 Case-A lowerer を実装したらここで呼び出す
if self.debug {
eprintln!(
"[LoopToJoinLowerer] generic Case-A candidate: {:?} (no lowerer yet)",
name
);
}
None
}
};
result
}
// ========================================
// Case-A helpers for specific function patterns
// ========================================
/// Case-A 汎用 lowerer の「Main.skip/1 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_skip_ws(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("Main.skip/1"))
}
/// Case-A 汎用 lowerer の「FuncScannerBox.trim/1 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_trim(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("FuncScannerBox.trim/1"))
}
/// Case-A 汎用 lowerer の「FuncScannerBox.append_defs/2 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_append_defs(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("FuncScannerBox.append_defs/2"))
}
/// Case-A 汎用 lowerer の「Stage1UsingResolverBox.resolve_for_source/5 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_stage1_resolver(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("Stage1UsingResolverBox.resolve_for_source/5"))
}
/// Case-A 汎用 lowerer の「StageBBodyExtractorBox.build_body_src/2 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_stageb_body(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("StageBBodyExtractorBox.build_body_src/2"))
}
/// Case-A 汎用 lowerer の「StageBFuncScannerBox.scan_all_boxes/1 用」薄いラッパー。
/// 実際のロジックは `lower` に集約されている。
pub fn lower_case_a_for_stageb_funcscanner(
&self,
func: &MirFunction,
loop_form: &LoopForm,
) -> Option<JoinModule> {
self.lower(func, loop_form, Some("StageBFuncScannerBox.scan_all_boxes/1"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lowerer_creation() {
let lowerer = LoopToJoinLowerer::new();
assert!(!lowerer.debug || lowerer.debug); // Just check it compiles
}
}