feat(mir): Loop Canonicalizer Phase 2 - dev-only observation

## Summary
ループ入口で LoopSkeleton/RoutingDecision を観測できるようにした。
既定挙動は完全不変(dev-only の並行観測のみ)。

## Changes
- src/mir/loop_canonicalizer/mod.rs: canonicalize_loop_expr() 追加
  - Phase 2 最小実装: loop 構造検証のみ
  - パターン検出は未実装(全て Fail-Fast)
  - LoopSkeleton の基本構造(HeaderCond step)を生成
  - 詳細な Fail-Fast 理由を返す
- src/mir/builder/control_flow/joinir/routing.rs: dev-only 観測ポイント
  - joinir_dev_enabled() ON 時のみ観測ログ出力
  - LoopSkeleton/RoutingDecision の内容を可視化
  - 既存の routing/lowering は完全無変更
- 最小実装: skip_whitespace 相当の構造検証のみ対応
- 追加テスト:
  - test_canonicalize_rejects_non_loop
  - test_canonicalize_minimal_loop_structure
  - test_canonicalize_rejects_multi_statement_loop
  - test_canonicalize_rejects_if_without_else

## Tests
- cargo test --release --lib: 1043 passed (退行なし)
- HAKO_JOINIR_DEBUG=1: 観測ログ出力確認
- デフォルト: 完全無変更(ログも挙動も)

## Acceptance Criteria
 joinir_dev_enabled() ON 時のみ観測ログが出る
 joinir_dev_enabled() OFF 時は完全に無変更(ログも挙動も)
 既存の smoke / cargo test --release --lib が退行しない
 最小パターン(if-else with break)で LoopSkeleton が生成できる
 未対応パターンは Fail-Fast で詳細理由を返す

Phase 137-2 complete
This commit is contained in:
nyash-codex
2025-12-16 05:17:56 +09:00
parent ec1ff5b766
commit e8d93f107c
2 changed files with 255 additions and 0 deletions

View File

@ -122,6 +122,38 @@ impl MirBuilder {
func_name: &str,
debug: bool,
) -> Result<Option<ValueId>, String> {
// Phase 137-2: Dev-only observation via Loop Canonicalizer
if crate::config::env::joinir_dev_enabled() {
use crate::ast::Span;
use crate::mir::loop_canonicalizer::canonicalize_loop_expr;
// Reconstruct loop AST for canonicalizer
let loop_ast = ASTNode::Loop {
condition: Box::new(condition.clone()),
body: body.to_vec(),
span: Span::unknown(),
};
match canonicalize_loop_expr(&loop_ast) {
Ok((skeleton, decision)) => {
eprintln!("[loop_canonicalizer] Function: {}", func_name);
eprintln!("[loop_canonicalizer] Skeleton steps: {}", skeleton.steps.len());
eprintln!("[loop_canonicalizer] Carriers: {}", skeleton.carriers.len());
eprintln!("[loop_canonicalizer] Has exits: {}", skeleton.exits.has_any_exit());
eprintln!("[loop_canonicalizer] Decision: {}",
if decision.is_success() { "SUCCESS" } else { "FAIL_FAST" });
if decision.is_fail_fast() {
eprintln!("[loop_canonicalizer] Reason: {}", decision.notes.join("; "));
eprintln!("[loop_canonicalizer] Missing caps: {:?}", decision.missing_caps);
}
}
Err(e) => {
eprintln!("[loop_canonicalizer] Function: {}", func_name);
eprintln!("[loop_canonicalizer] Error: {}", e);
}
}
}
// Phase 194: Use table-driven router instead of if/else chain
use super::patterns::{route_loop_pattern, LoopPatternContext};