feat(joinir): Phase 33-9.1 Loop/If lowering responsibility separation

Implement clear separation between Loop and If lowering responsibilities:

**Guard Implementation:**
- Add is_loop_lowered_function() to identify 6 Loop-dedicated functions
- Exclude Loop functions from If lowering path in try_lower_if_to_joinir()
- Enforce "1 function → 1 lowering" principle

**Documentation:**
- Add responsibility comments to loop_to_join.rs
- Add responsibility comments to if_select.rs and if_merge.rs
- Update if_joinir_design.md with Phase 33-9.1 section

**Testing:**
- Add unit test test_is_loop_lowered_function() (PASS)
- Verify no regression in existing JoinIR tests

**Loop-dedicated functions (6):**
- Main.skip/1
- FuncScannerBox.trim/1
- FuncScannerBox.append_defs/2
- Stage1UsingResolverBox.resolve_for_source/5
- StageBBodyExtractorBox.build_body_src/2
- StageBFuncScannerBox.scan_all_boxes/1

This prevents future conflicts when both Loop and If lowering expand.

🤖 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 10:58:56 +09:00
parent 517b20fe88
commit 9e9e08eb84
5 changed files with 99 additions and 1 deletions

View File

@ -47,6 +47,34 @@ pub use stageb_funcscanner::lower_stageb_funcscanner_to_joinir;
use crate::mir::join_ir::JoinInst;
use crate::mir::{BasicBlockId, MirFunction};
/// Phase 33-9.1: Loop lowering対象関数の判定
///
/// これらの関数は Phase 32/33 で LoopToJoinLowerer によって処理されます。
/// If lowering (Select/IfMerge) の対象から除外することで、Loop/If の責務を明確に分離します。
///
/// ## 対象関数6本
/// - Main.skip/1: 空白スキップループ
/// - FuncScannerBox.trim/1: 前後空白削除ループ
/// - FuncScannerBox.append_defs/2: 配列結合ループ
/// - Stage1UsingResolverBox.resolve_for_source/5: using解析ループ
/// - StageBBodyExtractorBox.build_body_src/2: Stage-B本体抽出ループ
/// - StageBFuncScannerBox.scan_all_boxes/1: Stage-B Box走査ループ
///
/// ## 将来の拡張
/// NYASH_JOINIR_LOWER_GENERIC=1 で汎用 Case-A ループにも拡張可能
pub(crate) fn is_loop_lowered_function(name: &str) -> bool {
const LOOP_LOWERED_FUNCTIONS: &[&str] = &[
"Main.skip/1",
"FuncScannerBox.trim/1",
"FuncScannerBox.append_defs/2",
"Stage1UsingResolverBox.resolve_for_source/5",
"StageBBodyExtractorBox.build_body_src/2",
"StageBFuncScannerBox.scan_all_boxes/1",
];
LOOP_LOWERED_FUNCTIONS.contains(&name)
}
/// Phase 33-7: Try to lower if/else to JoinIR Select/IfMerge instruction
///
/// Scope:
@ -73,6 +101,13 @@ pub fn try_lower_if_to_joinir(
return None;
}
// Phase 33-9.1: Loop専任関数の除外Loop/If責務分離
// Loop lowering対象関数はIf loweringの対象外にすることで、
// 1関数につき1 loweringの原則を保証します
if is_loop_lowered_function(&func.signature.name) {
return None;
}
// Phase 33-8: デバッグログレベル取得0-3
let debug_level = crate::config::env::joinir_debug_level();
let _debug = debug || debug_level >= 1;
@ -148,3 +183,40 @@ pub fn try_lower_if_to_joinir(
result
}
#[cfg(test)]
mod tests {
use super::*;
/// Phase 33-9.1: is_loop_lowered_function() の動作確認
#[test]
fn test_is_loop_lowered_function() {
// Loop 専任関数6本は true を返す
assert!(is_loop_lowered_function("Main.skip/1"));
assert!(is_loop_lowered_function("FuncScannerBox.trim/1"));
assert!(is_loop_lowered_function("FuncScannerBox.append_defs/2"));
assert!(is_loop_lowered_function(
"Stage1UsingResolverBox.resolve_for_source/5"
));
assert!(is_loop_lowered_function(
"StageBBodyExtractorBox.build_body_src/2"
));
assert!(is_loop_lowered_function(
"StageBFuncScannerBox.scan_all_boxes/1"
));
// If lowering 対象関数は false を返す
assert!(!is_loop_lowered_function("IfSelectTest.simple_return/0"));
assert!(!is_loop_lowered_function("IfMergeTest.multiple_true/0"));
assert!(!is_loop_lowered_function(
"JsonShapeToMap._read_value_from_pair/1"
));
assert!(!is_loop_lowered_function(
"Stage1JsonScannerBox.value_start_after_key_pos/2"
));
// 一般的な関数も false を返す
assert!(!is_loop_lowered_function("SomeBox.some_method/3"));
assert!(!is_loop_lowered_function("Main.main/0"));
}
}