## Phase 92全体の成果 **Phase 92 P0-P2**: ConditionalStep JoinIR生成とbody-local変数サポート - ConditionalStep(条件付きキャリア更新)のJoinIR生成実装 - Body-local変数(ch等)の条件式での参照サポート - 変数解決優先度: ConditionEnv → LoopBodyLocalEnv **Phase 92 P3**: BodyLocalPolicyBox + 安全ガード - BodyLocalPolicyDecision実装(Accept/Reject判定) - BodyLocalSlot + DualValueRewriter(JoinIR/MIR二重書き込み) - Fail-Fast契約(Cannot promote LoopBodyLocal検出) **Phase 92 P4**: E2E固定+回帰最小化 (本コミット) - Unit test 3本追加(body-local変数解決検証) - Integration smoke追加(phase92_pattern2_baseline.sh、2ケースPASS) - P4-E2E-PLAN.md、P4-COMPLETION.md作成 ## 主要な実装 ### ConditionalStep(条件付きキャリア更新) - `conditional_step_emitter.rs`: JoinIR Select命令生成 - `loop_with_break_minimal.rs`: ConditionalStep検出と統合 - `loop_with_continue_minimal.rs`: Pattern4対応 ### Body-local変数サポート - `condition_lowerer.rs`: body-local変数解決機能 - `lower_condition_to_joinir`: body_local_env パラメータ追加 - 変数解決優先度実装(ConditionEnv優先) - Unit test 3本追加: 変数解決/優先度/エラー - `header_break_lowering.rs`: break条件でbody-local変数参照 - 7ファイルで後方互換ラッパー(lower_condition_to_joinir_no_body_locals) ### Body-local Policy & Safety - `body_local_policy.rs`: BodyLocalPolicyDecision(Accept/Reject) - `body_local_slot.rs`: JoinIR/MIR二重書き込み - `dual_value_rewriter.rs`: ValueId書き換えヘルパー ## テスト体制 ### Unit Tests (+3) - `test_body_local_variable_resolution`: body-local変数解決 - `test_variable_resolution_priority`: 変数解決優先度(ConditionEnv優先) - `test_undefined_variable_error`: 未定義変数エラー - 全7テストPASS(cargo test --release condition_lowerer::tests) ### Integration Smoke (+1) - `phase92_pattern2_baseline.sh`: - Case A: loop_min_while.hako (Pattern2 baseline) - Case B: phase92_conditional_step_minimal.hako (条件付きインクリメント) - 両ケースPASS、integration profileで発見可能 ### 退行確認 - ✅ 既存Pattern2Breakテスト正常(退行なし) - ✅ Phase 135 smoke正常(MIR検証PASS) ## アーキテクチャ設計 ### 変数解決メカニズム ```rust // Priority 1: ConditionEnv (loop params, captured) if let Some(value_id) = env.get(name) { return Ok(value_id); } // Priority 2: LoopBodyLocalEnv (body-local like `ch`) if let Some(body_env) = body_local_env { if let Some(value_id) = body_env.get(name) { return Ok(value_id); } } ``` ### Fail-Fast契約 - Delta equality check (conditional_step_emitter.rs) - Variable resolution error messages (ConditionEnv) - Body-local promotion rejection (BodyLocalPolicyDecision::Reject) ## ドキュメント - `P4-E2E-PLAN.md`: 3レベルテスト戦略(Level 1-2完了、Level 3延期) - `P4-COMPLETION.md`: Phase 92完了報告 - `README.md`: Phase 92全体のまとめ ## 将来の拡張(Phase 92スコープ外) - Body-local promotionシステム拡張 - P5bパターン認識の汎化(flagベース条件サポート) - 完全なP5b E2Eテスト(body-local promotion実装後) 🎯 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
341 lines
15 KiB
Rust
341 lines
15 KiB
Rust
//! JoinIR routing logic for loop lowering
|
||
|
||
use super::trace;
|
||
use crate::ast::ASTNode;
|
||
use crate::mir::builder::MirBuilder;
|
||
use crate::mir::ValueId;
|
||
|
||
/// Pattern 選択の SSOT 入口
|
||
///
|
||
/// 既存の分散した選択ロジックをここに集約する。
|
||
/// 将来的には Canonicalizer decision に委譲する。
|
||
///
|
||
/// Phase 137-6-S1: 現時点では既存の router ロジック(LoopFeatures ベース)を使用
|
||
/// Phase 137-6-S2: dev-only で canonicalizer decision を提案として受け取る
|
||
pub(in crate::mir::builder) fn choose_pattern_kind(
|
||
condition: &ASTNode,
|
||
body: &[ASTNode],
|
||
) -> crate::mir::loop_pattern_detection::LoopPatternKind {
|
||
use crate::mir::builder::control_flow::joinir::patterns::ast_feature_extractor as ast_features;
|
||
use crate::mir::loop_pattern_detection;
|
||
|
||
// Phase 193: Use AST Feature Extractor Box for break/continue detection
|
||
let has_continue = ast_features::detect_continue_in_body(body);
|
||
let has_break = ast_features::detect_break_in_body(body);
|
||
|
||
// Phase 193: Extract features using modularized extractor
|
||
let features = ast_features::extract_features(condition, body, has_continue, has_break);
|
||
|
||
// Phase 192: Classify pattern based on features (既存の router 結果)
|
||
let router_choice = loop_pattern_detection::classify(&features);
|
||
|
||
// Phase 137-6-S2: dev-only で Canonicalizer の提案を取得
|
||
if crate::config::env::joinir_dev_enabled() {
|
||
use crate::ast::Span;
|
||
use crate::mir::loop_canonicalizer::canonicalize_loop_expr;
|
||
|
||
let loop_ast = ASTNode::Loop {
|
||
condition: Box::new(condition.clone()),
|
||
body: body.to_vec(),
|
||
span: Span::unknown(),
|
||
};
|
||
|
||
if let Ok((_skeleton, decision)) = canonicalize_loop_expr(&loop_ast) {
|
||
if let Some(canonical_choice) = decision.chosen {
|
||
// parity check
|
||
if canonical_choice != router_choice {
|
||
let msg = format!(
|
||
"[choose_pattern_kind/PARITY] router={:?}, canonicalizer={:?}",
|
||
router_choice, canonical_choice
|
||
);
|
||
|
||
if crate::config::env::joinir_dev::strict_enabled() {
|
||
// strict mode: 不一致は Fail-Fast
|
||
panic!("{}", msg);
|
||
} else {
|
||
// debug mode: ログのみ
|
||
trace::trace().dev("choose_pattern_kind/parity", &msg);
|
||
}
|
||
} else {
|
||
// Patterns match - success!
|
||
trace::trace().dev(
|
||
"choose_pattern_kind/parity",
|
||
&format!(
|
||
"[choose_pattern_kind/PARITY] OK: canonical and actual agree on {:?}",
|
||
canonical_choice
|
||
),
|
||
);
|
||
}
|
||
|
||
// TODO (Phase 137-6-S3): ここで canonical_choice を返す
|
||
// 現時点では router_choice を維持(既定挙動不変)
|
||
//
|
||
// 有効化条件(将来実装):
|
||
// 1. joinir_dev_enabled() && 新フラグ(例: canonicalizer_preferred())
|
||
// 2. または joinir_dev_enabled() をそのまま使用
|
||
//
|
||
// 注意: 有効化時は全 Pattern の parity が green であること
|
||
//
|
||
// 有効化後のコード例:
|
||
// ```rust
|
||
// if crate::config::env::canonicalizer_preferred() {
|
||
// return canonical_choice;
|
||
// }
|
||
// ```
|
||
}
|
||
}
|
||
}
|
||
|
||
router_choice
|
||
}
|
||
|
||
impl MirBuilder {
|
||
/// Phase 49: Try JoinIR Frontend for mainline integration
|
||
///
|
||
/// Returns `Ok(Some(value))` if the loop is successfully lowered via JoinIR,
|
||
/// `Ok(None)` if no JoinIR pattern matched (unsupported loop structure).
|
||
/// Phase 187-2: Legacy LoopBuilder removed - all loops must use JoinIR.
|
||
///
|
||
/// # Phase 49-4: Multi-target support
|
||
///
|
||
/// Targets are enabled via separate dev flags:
|
||
/// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0
|
||
/// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2
|
||
///
|
||
/// Note: Arity in function names does NOT include implicit `me` receiver.
|
||
/// - Instance method `print_tokens()` → `/0` (no explicit params)
|
||
/// - Static method `filter(arr, pred)` → `/2` (two params)
|
||
pub(in crate::mir::builder) fn try_cf_loop_joinir(
|
||
&mut self,
|
||
condition: &ASTNode,
|
||
body: &[ASTNode],
|
||
) -> Result<Option<ValueId>, String> {
|
||
// Get current function name
|
||
let func_name = self
|
||
.scope_ctx
|
||
.current_function
|
||
.as_ref()
|
||
.map(|f| f.signature.name.clone())
|
||
.unwrap_or_default();
|
||
|
||
// Phase 195: Use unified trace
|
||
trace::trace().routing("router", &func_name, "try_cf_loop_joinir called");
|
||
|
||
// Phase 170-4: Structure-based routing option
|
||
// When NYASH_JOINIR_STRUCTURE_ONLY=1, skip function name whitelist
|
||
// and route purely based on loop structure analysis
|
||
// Phase 196: Default to structure-first routing now that LoopBuilder is removed.
|
||
// - Default: ON (structure_only = true) to allow JoinIR patterns to run for all loops.
|
||
// - To revert to the previous whitelist-only behavior, set NYASH_JOINIR_STRUCTURE_ONLY=0.
|
||
let structure_only = match std::env::var("NYASH_JOINIR_STRUCTURE_ONLY").ok().as_deref() {
|
||
Some("0") | Some("off") => false,
|
||
_ => true,
|
||
};
|
||
|
||
if structure_only {
|
||
trace::trace().routing(
|
||
"router",
|
||
&func_name,
|
||
"Structure-only mode enabled, skipping whitelist",
|
||
);
|
||
} else {
|
||
// Phase 49-4 + Phase 80: Multi-target routing (legacy whitelist)
|
||
// - JoinIR は常時 ON。legacy LoopBuilder は削除済み。
|
||
// - 代表2本(print_tokens / ArrayExt.filter)も常に JoinIR で試行する。
|
||
// Note: Arity does NOT include implicit `me` receiver
|
||
// Phase 188: Add "main" routing for loop pattern expansion
|
||
// Phase 170: Add JsonParserBox methods for selfhost validation
|
||
let is_target = match func_name.as_str() {
|
||
"main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1)
|
||
"JoinIrMin.main/0" => true, // Phase 188-Impl-2: Enable JoinIR for JoinIrMin.main/0 (Pattern 2)
|
||
"JsonTokenizer.print_tokens/0" => true,
|
||
"ArrayExtBox.filter/2" => true,
|
||
// Phase 170-A-1: Enable JsonParserBox methods for JoinIR routing
|
||
"JsonParserBox._trim/1" => true,
|
||
"JsonParserBox._skip_whitespace/2" => true,
|
||
"JsonParserBox._match_literal/3" => true, // Phase 182: Fixed arity (s, pos, literal)
|
||
"JsonParserBox._parse_string/2" => true,
|
||
"JsonParserBox._parse_array/2" => true,
|
||
"JsonParserBox._parse_object/2" => true,
|
||
// Phase 182: Add simple loop methods
|
||
"JsonParserBox._parse_number/2" => true, // P2 Break (s, pos)
|
||
"JsonParserBox._atoi/1" => true, // P2 Break (s)
|
||
// Phase 170-A-1: Test methods (simplified versions)
|
||
"TrimTest.trim/1" => true,
|
||
"Main.trim/1" => true, // Phase 171-fix: Main box variant
|
||
"Main.trim_string_simple/1" => true, // Phase 33-13: Simple trim variant
|
||
"TrimTest.main/0" => true, // Phase 170: TrimTest.main for loop pattern test
|
||
// Phase 173: JsonParser P5 expansion test
|
||
"JsonParserTest._skip_whitespace/3" => true,
|
||
"JsonParserTest.main/0" => true,
|
||
// Phase 174: JsonParser complex loop P5B extension test
|
||
"JsonParserStringTest.parse_string_min/0" => true,
|
||
"JsonParserStringTest.main/0" => true,
|
||
// Phase 175: P5 multi-carrier support (2 carriers: pos + result)
|
||
"JsonParserStringTest2.parse_string_min2/0" => true,
|
||
"JsonParserStringTest2.main/0" => true,
|
||
_ => false,
|
||
};
|
||
|
||
if !is_target {
|
||
return Ok(None);
|
||
}
|
||
}
|
||
|
||
// Debug log when routing through JoinIR Frontend
|
||
// Phase 195: Check trace flags directly from JoinLoopTrace
|
||
let debug = trace::trace().is_loopform_enabled() || trace::trace().is_mainline_enabled();
|
||
trace::trace().routing(
|
||
"router",
|
||
&func_name,
|
||
"Routing through JoinIR Frontend mainline",
|
||
);
|
||
|
||
// Phase 49-3: Implement JoinIR Frontend integration
|
||
self.cf_loop_joinir_impl(condition, body, &func_name, debug)
|
||
}
|
||
|
||
/// Phase 49-3: JoinIR Frontend integration implementation
|
||
///
|
||
/// Routes loop compilation through either:
|
||
/// 1. Pattern-based router (Phase 194+) - preferred for new patterns
|
||
/// 2. Legacy binding path (Phase 49-3) - for whitelisted functions only
|
||
pub(in crate::mir::builder) fn cf_loop_joinir_impl(
|
||
&mut self,
|
||
condition: &ASTNode,
|
||
body: &[ASTNode],
|
||
func_name: &str,
|
||
debug: bool,
|
||
) -> Result<Option<ValueId>, String> {
|
||
// Phase 137-2/137-4: 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)) => {
|
||
trace::trace().dev(
|
||
"loop_canonicalizer",
|
||
&format!("Function: {}", func_name),
|
||
);
|
||
trace::trace().dev(
|
||
"loop_canonicalizer",
|
||
&format!(" Skeleton steps: {}", skeleton.steps.len()),
|
||
);
|
||
trace::trace().dev(
|
||
"loop_canonicalizer",
|
||
&format!(" Carriers: {}", skeleton.carriers.len()),
|
||
);
|
||
trace::trace().dev(
|
||
"loop_canonicalizer",
|
||
&format!(" Has exits: {}", skeleton.exits.has_any_exit()),
|
||
);
|
||
trace::trace().dev(
|
||
"loop_canonicalizer",
|
||
&format!(
|
||
" Decision: {}",
|
||
if decision.is_success() {
|
||
"SUCCESS"
|
||
} else {
|
||
"FAIL_FAST"
|
||
}
|
||
),
|
||
);
|
||
if let Some(pattern) = decision.chosen {
|
||
trace::trace().dev(
|
||
"loop_canonicalizer",
|
||
&format!(" Chosen pattern: {:?}", pattern),
|
||
);
|
||
}
|
||
trace::trace().dev(
|
||
"loop_canonicalizer",
|
||
&format!(" Missing caps: {:?}", decision.missing_caps),
|
||
);
|
||
if decision.is_fail_fast() {
|
||
trace::trace().dev(
|
||
"loop_canonicalizer",
|
||
&format!(" Reason: {}", decision.notes.join("; ")),
|
||
);
|
||
}
|
||
|
||
// Phase 137-4: Router parity verification
|
||
if let Some(canonical_pattern) = decision.chosen {
|
||
// Get actual pattern from router (will be determined by LoopPatternContext)
|
||
// We need to defer this check until after ctx is created
|
||
// Store decision for later parity check
|
||
trace::trace().debug(
|
||
"canonicalizer",
|
||
&format!(
|
||
"Phase 137-4: Canonical pattern chosen: {:?} (parity check pending)",
|
||
canonical_pattern
|
||
),
|
||
);
|
||
}
|
||
}
|
||
Err(e) => {
|
||
trace::trace().dev("loop_canonicalizer", &format!("Function: {}", func_name));
|
||
trace::trace().dev("loop_canonicalizer", &format!(" Error: {}", e));
|
||
}
|
||
}
|
||
}
|
||
|
||
// Phase 194: Use table-driven router instead of if/else chain
|
||
use super::patterns::{route_loop_pattern, LoopPatternContext};
|
||
|
||
// Phase 200-C: Pass fn_body_ast to LoopPatternContext if available
|
||
// Clone fn_body_ast to avoid borrow checker issues
|
||
let fn_body_clone = self.comp_ctx.fn_body_ast.clone();
|
||
trace::trace().routing(
|
||
"router",
|
||
func_name,
|
||
&format!(
|
||
"fn_body_ast is {}",
|
||
if fn_body_clone.is_some() {
|
||
"SOME"
|
||
} else {
|
||
"NONE"
|
||
}
|
||
),
|
||
);
|
||
let mut ctx = if let Some(ref fn_body) = fn_body_clone {
|
||
trace::trace().routing(
|
||
"router",
|
||
func_name,
|
||
&format!("Creating ctx with fn_body ({} nodes)", fn_body.len()),
|
||
);
|
||
LoopPatternContext::with_fn_body(condition, body, &func_name, debug, fn_body)
|
||
} else {
|
||
LoopPatternContext::new(condition, body, &func_name, debug)
|
||
};
|
||
|
||
// Phase 137-4: Router parity verification (after ctx is created)
|
||
// Phase 92 P1-0: Skeleton setting removed - patterns retrieve skeleton internally if needed
|
||
if crate::config::env::joinir_dev_enabled() {
|
||
let (result, _skeleton_opt) = self.verify_router_parity(condition, body, func_name, &ctx);
|
||
result?;
|
||
}
|
||
|
||
if let Some(result) = route_loop_pattern(self, &ctx)? {
|
||
trace::trace().routing("router", func_name, "Pattern router succeeded");
|
||
return Ok(Some(result));
|
||
}
|
||
|
||
// Phase 187-2: Pattern router failed, try legacy whitelist
|
||
trace::trace().routing(
|
||
"router",
|
||
func_name,
|
||
"Pattern router found no match, trying legacy whitelist",
|
||
);
|
||
|
||
// Delegate to legacy binding path (routing_legacy_binding.rs)
|
||
self.cf_loop_joinir_legacy_binding(condition, body, func_name, debug)
|
||
}
|
||
}
|