diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index a15d38ee..0973e0df 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -11,12 +11,13 @@ --- -## 今の状態(Phase 74–87 まで到達) +## 今の状態(Phase 74–89 まで到達) - Scope/BindingId の段階移行(dev-only)は Pattern2/3/4 まで配線済み(dual-path 維持)。 - Pattern2 の promoted carriers(DigitPos/Trim)について ExitLine 契約(ConditionOnly を exit PHI から除外)を E2E で固定済み。 - debug flag SSOT / DebugOutputBox 移行 / error tags 集約 / carrier init builder まで整備済み。 - **LLVM exe line SSOT 確立**: `tools/build_llvm.sh` を使用した .hako → executable パイプライン標準化完了。 +- **Phase 89 P0 進行中**: Continue + Early Return パターンの dev-only 固定(Step 0 完了、detector 締め済み)。 - `cargo test --release --lib` は PASS を維持(退行なし)。 参照: @@ -31,7 +32,26 @@ ## 次の指示書(優先順位) -### P0: JoinIR / Selfhost depth-2 の前進(Phase 88 候補) +### P0 (進行中): Phase 89 - Continue + Early Return Pattern + +**現在地**: Step 0 完了(detector 締め済み)、Step 1 進行中(docs 更新) + +**残りステップ**: +1. ✅ Step 0: detector 締め + test(完了) +2. 🚧 Step 1: ドキュメント(10-Now.md, CURRENT_TASK.md)← 現在ここ +3. Step 2: 最小 fixture 作成(continue + early return、決定的出力) +4. Step 3: Frontend 新パターン箱追加(LoopPattern::ContinueReturn) +5. Step 4: normalized-dev 統合(shape + normalize 関数) +6. Step 5: 受け入れテスト(構造 vs vm-bridge 一致 + 期待値) + +**受け入れ基準**: +- `NYASH_JOINIR_NORMALIZED_DEV_RUN=1 cargo test --features normalized_dev --test normalized_joinir_min` PASS +- `cargo test --release --lib` PASS(退行なし) +- dev-only のみ(canonical には入れない) + +**参照**: ユーザー提供の Phase 89 指示書 + +### P1: JoinIR / Selfhost depth-2 の前進(Phase 90 候補) 目的: - JsonParserBox の残り複合ループを JoinIR 対応する。 diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 6c5b7d71..fa2be707 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -55,10 +55,37 @@ - Prerequisites documented: llvm-config-18, llvmlite, LLVM features - Integration test: PASS (or SKIP if no LLVM) -**Next**: Phase 88+ (TBD - await user direction) - **Reference**: phase87-selfhost-llvm-exe-line.md +### Phase 89 P0: Continue + Early Return Pattern (2025-12-14) 🚧 + +**Status**: IN PROGRESS - Dev-only fixture isolation + +**Goal**: +- Establish new control axis: Continue + Early Return (loop-internal return) +- Prevent Pattern4 detector false positives (canonical mismatch) +- Maintain default behavior (normalized_dev scope only) + +**Step 0 COMPLETE** (2025-12-14): +- ✅ Pattern4 detector tightened (require Select + exactly 1 conditional Jump) +- ✅ Test added: `test_pattern4_detector_rejects_loop_with_return` +- ✅ All existing Pattern4 tests pass + +**Next Steps**: +- P0-1: Documentation (10-Now.md, CURRENT_TASK.md) +- P0-2: Minimal fixture (continue + early return, deterministic output) +- P0-3: Frontend pattern (LoopPattern::ContinueReturn enum + lowering box) +- P0-4: Normalized-dev integration (shape + normalize function) +- P0-5: Acceptance tests (structured vs vm-bridge parity + expected output) + +**Constraints**: +- ❌ No default behavior changes +- ❌ No by-name branching +- ❌ No fallback processing (Fail-Fast) +- ❌ Not added to canonical shapes initially (dev-only first) + +**Reference**: (TBD - phase89-continue-return-pattern.md) + ### Scope / BindingId(dev-only の段階移行ライン) - MIR builder 側で lexical scope / shadowing を実在化し、言語仕様の “local はブロック境界で分離” を SSOT に揃えた。 diff --git a/docs/private b/docs/private index fd8368f4..b3ae0b0d 160000 --- a/docs/private +++ b/docs/private @@ -1 +1 @@ -Subproject commit fd8368f491be4dcb2c25d95a831f11771a142598 +Subproject commit b3ae0b0dfa185a10d2771095dd43154677e5e25c diff --git a/src/mir/join_ir/frontend/ast_lowerer/analysis.rs b/src/mir/join_ir/frontend/ast_lowerer/analysis.rs index acdd4ddf..d6796f54 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/analysis.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/analysis.rs @@ -412,4 +412,38 @@ impl AstToJoinIrLowerer { } }) } + + /// Phase 89: Loop body に Return があるかチェック + /// + /// ループパターン検出(loop_frontend_binding)で使用される。 + /// If文内のReturnステートメントを検出する(loop-internal early return)。 + /// + /// # Arguments + /// * `loop_body` - ループ本体のステートメント配列 + /// + /// # Returns + /// ループ内にReturnがあればtrue + pub(crate) fn has_return_in_loop_body(loop_body: &[serde_json::Value]) -> bool { + loop_body.iter().any(|stmt| { + if stmt["type"].as_str() == Some("If") { + let then_has = stmt["then"] + .as_array() + .map(|body| { + body.iter() + .any(|s| s["type"].as_str() == Some("Return")) + }) + .unwrap_or(false); + let else_has = stmt["else"] + .as_array() + .map(|body| { + body.iter() + .any(|s| s["type"].as_str() == Some("Return")) + }) + .unwrap_or(false); + then_has || else_has + } else { + false + } + }) + } } diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_frontend_binding.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_frontend_binding.rs index f07450cd..a59bb966 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_frontend_binding.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_frontend_binding.rs @@ -54,12 +54,19 @@ pub fn detect_loop_pattern( "reduce" | "fold" => LoopPattern::Reduce, // デフォルト: Simple パターン - // ただし Break/Continue があれば別パターン + // ただし Break/Continue/Return があれば別パターン _ => { if let Some(body) = loop_body { - if AstToJoinIrLowerer::has_break_in_loop_body(body) { + let has_break = AstToJoinIrLowerer::has_break_in_loop_body(body); + let has_continue = AstToJoinIrLowerer::has_continue_in_loop_body(body); + let has_return = AstToJoinIrLowerer::has_return_in_loop_body(body); + + // Phase 89: Continue + Return の複合パターン + if has_continue && has_return { + LoopPattern::ContinueReturn + } else if has_break { LoopPattern::Break - } else if AstToJoinIrLowerer::has_continue_in_loop_body(body) { + } else if has_continue { LoopPattern::Continue } else { LoopPattern::Simple diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs index 5ef2b79a..5e185051 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs @@ -57,6 +57,12 @@ pub enum LoopPattern { /// Continue パターン(Phase P4) /// 責務: if continue 条件で処理をスキップするループを Select に落とす Continue, + + /// ContinueReturn パターン(Phase 89) + /// 責務: continue + early return 両方を持つループを複合的に処理 + /// - continue: Select で carrier 切り替え + /// - early return: 条件付き Jump で k_exit へ早期脱出 + ContinueReturn, } /// ループパターン lowering エラー @@ -121,5 +127,9 @@ pub fn lower_loop_with_pattern( LoopPattern::Simple => simple::lower(lowerer, program_json), LoopPattern::Break => break_pattern::lower(lowerer, program_json), LoopPattern::Continue => continue_pattern::lower(lowerer, program_json), + LoopPattern::ContinueReturn => Err(LoweringError::UnimplementedPattern { + pattern: LoopPattern::ContinueReturn, + reason: "ContinueReturn pattern Phase 89 implementation in progress (Step 3)".to_string(), + }), } } diff --git a/src/mir/join_ir/normalized.rs b/src/mir/join_ir/normalized.rs index c12197e4..dfeeaced 100644 --- a/src/mir/join_ir/normalized.rs +++ b/src/mir/join_ir/normalized.rs @@ -1172,6 +1172,11 @@ pub(crate) fn normalized_dev_roundtrip_structured( normalized_pattern2_to_structured(&norm) })) } + // Phase 89: Continue + Early Return pattern (dev-only, delegates to P2 for now) + NormalizedDevShape::PatternContinueReturnMinimal => catch_unwind(AssertUnwindSafe(|| { + let norm = normalize_pattern2_minimal(module); + normalized_pattern2_to_structured(&norm) + })), }; match attempt { diff --git a/src/mir/join_ir/normalized/shape_guard.rs b/src/mir/join_ir/normalized/shape_guard.rs index 2f7c3ba4..bbcecb3a 100644 --- a/src/mir/join_ir/normalized/shape_guard.rs +++ b/src/mir/join_ir/normalized/shape_guard.rs @@ -84,6 +84,8 @@ pub enum NormalizedDevShape { // Phase 54: selfhost P2/P3 shape growth (structural axis expansion) SelfhostVerifySchemaP2, SelfhostDetectFormatP3, + // Phase 89: Continue + Early Return pattern (dev-only) + PatternContinueReturnMinimal, } type Detector = fn(&JoinModule) -> bool; @@ -171,6 +173,11 @@ const SHAPE_DETECTORS: &[(NormalizedDevShape, Detector)] = &[ NormalizedDevShape::SelfhostDetectFormatP3, detectors::is_selfhost_detect_format_p3, ), + // Phase 89: Continue + Early Return pattern + ( + NormalizedDevShape::PatternContinueReturnMinimal, + detectors::is_pattern_continue_return_minimal, + ), ]; /// direct ブリッジで扱う shape(dev 限定)。 @@ -213,6 +220,8 @@ pub fn capability_for_shape(shape: &NormalizedDevShape) -> ShapeCapability { // Phase 54: selfhost P2/P3 shape growth SelfhostVerifySchemaP2 => SelfhostP2Core, SelfhostDetectFormatP3 => SelfhostP3IfSum, + // Phase 89: Continue + Early Return pattern (dev-only, maps to P4 family) + PatternContinueReturnMinimal => P4ContinueSkipWs, }; ShapeCapability::new(kind) @@ -832,6 +841,10 @@ mod detectors { } /// Phase 48-A: Check if module matches Pattern4 continue minimal shape + /// + /// Phase 89: Tightened to prevent continue + early return misdetection: + /// - Requires at least one Select instruction (continue's core) + /// - Requires exactly one conditional Jump to k_exit (loop break, not early return) pub(crate) fn is_pattern4_continue_minimal(module: &JoinModule) -> bool { // Structure-based detection (avoid name-based heuristics) @@ -848,11 +861,11 @@ mod detectors { // P4 characteristics: // - Has Compare instruction (loop condition or continue check) - // - Has conditional Jump (for continue/break semantics) + // - Has Select instruction (continue's core - carrier switching) // - Has tail call (loop back) + // - Has exactly one conditional Jump to k_exit (loop break only) // - // Note: Simplified detector - relies on Continue being present in original AST - // which gets lowered to conditional tail call structure. + // Phase 89: Tightened to exclude loop-internal return patterns let has_compare = loop_step.body.iter().any(|inst| { matches!( @@ -861,16 +874,36 @@ mod detectors { ) }); - // Has conditional jump or call (for continue/break check) - let has_conditional_flow = loop_step.body.iter().any(|inst| { - matches!(inst, JoinInst::Jump { cond: Some(_), .. }) - || matches!(inst, JoinInst::Call { k_next: None, .. }) + // Phase 89: Require Select (continue's core) + let has_select = loop_step.body.iter().any(|inst| match inst { + JoinInst::Select { .. } => true, + JoinInst::Compute(mir_inst) => matches!( + mir_inst, + crate::mir::join_ir::MirLikeInst::Select { .. } + ), + _ => false, }); + // Phase 89: Count conditional Jumps to k_exit + // Continue pattern should have exactly 1 (loop break), not multiple (early returns) + let k_exit_jumps_count = loop_step.body.iter().filter(|inst| { + matches!(inst, JoinInst::Jump { cond: Some(_), .. }) + }).count(); + + let has_tail_call = loop_step + .body + .iter() + .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); + // P4 minimal has 2-4 params (i, acc, possibly n) let reasonable_param_count = (2..=4).contains(&loop_step.params.len()); - has_compare && has_conditional_flow && reasonable_param_count + // Phase 89: Tightened conditions + has_compare + && has_select + && has_tail_call + && reasonable_param_count + && k_exit_jumps_count == 1 // Exactly one loop break (not early return) } pub(crate) fn is_jsonparser_parse_array_continue_skip_ws(module: &JoinModule) -> bool { @@ -889,6 +922,70 @@ mod detectors { .any(|f| f.name == "jsonparser_parse_object_continue_skip_ws") } + /// Phase 89: Check if module matches Continue + Early Return pattern + /// + /// Structural characteristics: + /// - 3 functions (main, loop_step, k_exit) + /// - Has Select instruction (continue's core) + /// - Has TWO or more conditional Jumps to k_exit (loop break + early return) + /// - Has Compare instruction + /// - Has tail call (loop back) + pub(crate) fn is_pattern_continue_return_minimal(module: &JoinModule) -> bool { + // Must have exactly 3 functions + if !module.is_structured() || module.functions.len() != 3 { + return false; + } + + // Find loop_step function + let loop_step = match find_loop_step(module) { + Some(f) => f, + None => return false, + }; + + // Continue + Return characteristics: + // - Has Select instruction (continue's core) + // - Has TWO or more conditional Jumps (loop break + early return) + // - Has Compare instruction + // - Has tail call (loop back) + + let has_compare = loop_step.body.iter().any(|inst| { + matches!( + inst, + JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { .. }) + ) + }); + + let has_select = loop_step.body.iter().any(|inst| match inst { + JoinInst::Select { .. } => true, + JoinInst::Compute(mir_inst) => matches!( + mir_inst, + crate::mir::join_ir::MirLikeInst::Select { .. } + ), + _ => false, + }); + + // Continue + Return pattern requires TWO or more conditional Jumps + // (at least one for loop break, one for early return) + let k_exit_jumps_count = loop_step.body.iter().filter(|inst| { + matches!(inst, JoinInst::Jump { cond: Some(_), .. }) + }).count(); + + let has_tail_call = loop_step + .body + .iter() + .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); + + // Reasonable param count (i, acc, possibly n) + let reasonable_param_count = (2..=4).contains(&loop_step.params.len()); + + // Phase 89: Continue + Return pattern requires >= 2 conditional Jumps + has_compare + && has_select + && has_tail_call + && reasonable_param_count + && k_exit_jumps_count >= 2 // At least 2: loop break + early return + } + pub(super) fn find_loop_step(module: &JoinModule) -> Option<&JoinFunction> { module .functions @@ -1137,4 +1234,135 @@ mod tests { object_shapes ); } + + #[cfg(feature = "normalized_dev")] + #[test] + fn test_pattern4_detector_rejects_loop_with_return() { + // Phase 89: Verify that Pattern4 detector does NOT match + // modules with loop-internal return (continue + early return pattern) + + use crate::mir::join_ir::{JoinFuncId, JoinModule}; + use crate::mir::ValueId; + use std::collections::BTreeMap; + + // Minimal module with loop + continue + return + // (this would be the ContinueReturn pattern, NOT Pattern4) + let mut functions = BTreeMap::new(); + + // Entry function + let entry_func = JoinFunction { + id: JoinFuncId::new(0), + name: "loop_with_return_test".to_string(), + params: vec![ValueId(0)], + body: vec![ + JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: ValueId(1), + value: crate::mir::join_ir::ConstValue::Integer(0), + }), + JoinInst::Call { + func: JoinFuncId::new(1), + args: vec![ValueId(1), ValueId(1), ValueId(0)], + k_next: None, + dst: Some(ValueId(2)), + }, + JoinInst::Ret { value: Some(ValueId(2)) }, + ], + exit_cont: None, + }; + + // loop_step function with TWO conditional Jumps (break + early return) + let loop_step_func = JoinFunction { + id: JoinFuncId::new(1), + name: "loop_step".to_string(), + params: vec![ValueId(0), ValueId(1), ValueId(2)], + body: vec![ + // Compare for loop condition + JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { + dst: ValueId(10), + op: crate::mir::join_ir::CompareOp::Lt, + lhs: ValueId(0), + rhs: ValueId(2), + }), + JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: ValueId(11), + value: crate::mir::join_ir::ConstValue::Bool(false), + }), + JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { + dst: ValueId(12), + op: crate::mir::join_ir::CompareOp::Eq, + lhs: ValueId(10), + rhs: ValueId(11), + }), + // First Jump: loop break + JoinInst::Jump { + cont: JoinFuncId::new(2).as_cont(), + args: vec![ValueId(1)], + cond: Some(ValueId(12)), + }, + // Compare for early return condition + JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { + dst: ValueId(20), + op: crate::mir::join_ir::CompareOp::Eq, + lhs: ValueId(0), + rhs: ValueId(2), + }), + // Second Jump: early return (THIS MAKES IT NOT PATTERN4) + JoinInst::Jump { + cont: JoinFuncId::new(2).as_cont(), + args: vec![ValueId(1)], + cond: Some(ValueId(20)), + }, + // Select (continue's core) + JoinInst::Select { + dst: ValueId(30), + cond: ValueId(20), + then_val: ValueId(1), + else_val: ValueId(1), + type_hint: None, + }, + // Tail call (loop back) + JoinInst::Call { + func: JoinFuncId::new(1), + args: vec![ValueId(0), ValueId(30), ValueId(2)], + k_next: None, + dst: Some(ValueId(40)), + }, + JoinInst::Ret { value: Some(ValueId(40)) }, + ], + exit_cont: None, + }; + + // k_exit function + let k_exit_func = JoinFunction { + id: JoinFuncId::new(2), + name: "k_exit".to_string(), + params: vec![ValueId(0)], + body: vec![JoinInst::Ret { value: Some(ValueId(0)) }], + exit_cont: None, + }; + + functions.insert(JoinFuncId::new(0), entry_func); + functions.insert(JoinFuncId::new(1), loop_step_func); + functions.insert(JoinFuncId::new(2), k_exit_func); + + let module = JoinModule { + functions, + entry: Some(JoinFuncId::new(0)), + phase: crate::mir::join_ir::JoinIrPhase::Structured, + }; + + // Phase 89: This should NOT be detected as Pattern4ContinueMinimal + // because it has TWO conditional Jumps (loop break + early return) + assert!( + !detectors::is_pattern4_continue_minimal(&module), + "Module with loop-internal return should NOT match Pattern4ContinueMinimal" + ); + + let shapes = detect_shapes(&module); + assert!( + !shapes.contains(&NormalizedDevShape::Pattern4ContinueMinimal), + "Pattern4ContinueMinimal should not be detected for loop with return, got: {:?}", + shapes + ); + } } diff --git a/src/mir/join_ir_vm_bridge/bridge.rs b/src/mir/join_ir_vm_bridge/bridge.rs index 183677d9..b784ca42 100644 --- a/src/mir/join_ir_vm_bridge/bridge.rs +++ b/src/mir/join_ir_vm_bridge/bridge.rs @@ -140,6 +140,10 @@ fn normalize_for_shape( .expect("P4 object normalization failed") }, )), + // Phase 89: Continue + Early Return pattern (dev-only, delegates to P2 for now) + NormalizedDevShape::PatternContinueReturnMinimal => { + catch_unwind(AssertUnwindSafe(|| normalize_pattern2_minimal(module))) + } }; match result {