feat(joinir): Phase 89 P0 - Continue + Early Return pattern detector
## Pattern4 Detector 締め - is_pattern4_continue_minimal() を厳しく - Select 必須 + conditional Jump exactly 1 - loop 内 return を P4 と誤認しない ## 新パターン箱 - LoopPattern::ContinueReturn enum 追加 - has_return_in_loop_body() helper 追加 - Fail-Fast: UnimplementedPattern error ## Normalized-dev 統合 - NormalizedDevShape::PatternContinueReturnMinimal - detector: Select + conditional Jumps >= 2 - canonical には入れない(dev-only) ## Documentation - 10-Now.md, CURRENT_TASK.md 更新 Impact: - 987 lib tests PASS - 60 normalized_dev tests PASS - Pattern4 誤爆防止 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -11,12 +11,13 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 今の状態(Phase 74–87 まで到達)
|
## 今の状態(Phase 74–89 まで到達)
|
||||||
|
|
||||||
- Scope/BindingId の段階移行(dev-only)は Pattern2/3/4 まで配線済み(dual-path 維持)。
|
- Scope/BindingId の段階移行(dev-only)は Pattern2/3/4 まで配線済み(dual-path 維持)。
|
||||||
- Pattern2 の promoted carriers(DigitPos/Trim)について ExitLine 契約(ConditionOnly を exit PHI から除外)を E2E で固定済み。
|
- Pattern2 の promoted carriers(DigitPos/Trim)について ExitLine 契約(ConditionOnly を exit PHI から除外)を E2E で固定済み。
|
||||||
- debug flag SSOT / DebugOutputBox 移行 / error tags 集約 / carrier init builder まで整備済み。
|
- debug flag SSOT / DebugOutputBox 移行 / error tags 集約 / carrier init builder まで整備済み。
|
||||||
- **LLVM exe line SSOT 確立**: `tools/build_llvm.sh` を使用した .hako → executable パイプライン標準化完了。
|
- **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 を維持(退行なし)。
|
- `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 対応する。
|
- JsonParserBox の残り複合ループを JoinIR 対応する。
|
||||||
|
|||||||
@ -55,10 +55,37 @@
|
|||||||
- Prerequisites documented: llvm-config-18, llvmlite, LLVM features
|
- Prerequisites documented: llvm-config-18, llvmlite, LLVM features
|
||||||
- Integration test: PASS (or SKIP if no LLVM)
|
- Integration test: PASS (or SKIP if no LLVM)
|
||||||
|
|
||||||
**Next**: Phase 88+ (TBD - await user direction)
|
|
||||||
|
|
||||||
**Reference**: phase87-selfhost-llvm-exe-line.md
|
**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 の段階移行ライン)
|
### Scope / BindingId(dev-only の段階移行ライン)
|
||||||
|
|
||||||
- MIR builder 側で lexical scope / shadowing を実在化し、言語仕様の “local はブロック境界で分離” を SSOT に揃えた。
|
- MIR builder 側で lexical scope / shadowing を実在化し、言語仕様の “local はブロック境界で分離” を SSOT に揃えた。
|
||||||
|
|||||||
Submodule docs/private updated: fd8368f491...b3ae0b0dfa
@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,12 +54,19 @@ pub fn detect_loop_pattern(
|
|||||||
"reduce" | "fold" => LoopPattern::Reduce,
|
"reduce" | "fold" => LoopPattern::Reduce,
|
||||||
|
|
||||||
// デフォルト: Simple パターン
|
// デフォルト: Simple パターン
|
||||||
// ただし Break/Continue があれば別パターン
|
// ただし Break/Continue/Return があれば別パターン
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(body) = loop_body {
|
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
|
LoopPattern::Break
|
||||||
} else if AstToJoinIrLowerer::has_continue_in_loop_body(body) {
|
} else if has_continue {
|
||||||
LoopPattern::Continue
|
LoopPattern::Continue
|
||||||
} else {
|
} else {
|
||||||
LoopPattern::Simple
|
LoopPattern::Simple
|
||||||
|
|||||||
@ -57,6 +57,12 @@ pub enum LoopPattern {
|
|||||||
/// Continue パターン(Phase P4)
|
/// Continue パターン(Phase P4)
|
||||||
/// 責務: if continue 条件で処理をスキップするループを Select に落とす
|
/// 責務: if continue 条件で処理をスキップするループを Select に落とす
|
||||||
Continue,
|
Continue,
|
||||||
|
|
||||||
|
/// ContinueReturn パターン(Phase 89)
|
||||||
|
/// 責務: continue + early return 両方を持つループを複合的に処理
|
||||||
|
/// - continue: Select で carrier 切り替え
|
||||||
|
/// - early return: 条件付き Jump で k_exit へ早期脱出
|
||||||
|
ContinueReturn,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ループパターン lowering エラー
|
/// ループパターン lowering エラー
|
||||||
@ -121,5 +127,9 @@ pub fn lower_loop_with_pattern(
|
|||||||
LoopPattern::Simple => simple::lower(lowerer, program_json),
|
LoopPattern::Simple => simple::lower(lowerer, program_json),
|
||||||
LoopPattern::Break => break_pattern::lower(lowerer, program_json),
|
LoopPattern::Break => break_pattern::lower(lowerer, program_json),
|
||||||
LoopPattern::Continue => continue_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(),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1172,6 +1172,11 @@ pub(crate) fn normalized_dev_roundtrip_structured(
|
|||||||
normalized_pattern2_to_structured(&norm)
|
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 {
|
match attempt {
|
||||||
|
|||||||
@ -84,6 +84,8 @@ pub enum NormalizedDevShape {
|
|||||||
// Phase 54: selfhost P2/P3 shape growth (structural axis expansion)
|
// Phase 54: selfhost P2/P3 shape growth (structural axis expansion)
|
||||||
SelfhostVerifySchemaP2,
|
SelfhostVerifySchemaP2,
|
||||||
SelfhostDetectFormatP3,
|
SelfhostDetectFormatP3,
|
||||||
|
// Phase 89: Continue + Early Return pattern (dev-only)
|
||||||
|
PatternContinueReturnMinimal,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Detector = fn(&JoinModule) -> bool;
|
type Detector = fn(&JoinModule) -> bool;
|
||||||
@ -171,6 +173,11 @@ const SHAPE_DETECTORS: &[(NormalizedDevShape, Detector)] = &[
|
|||||||
NormalizedDevShape::SelfhostDetectFormatP3,
|
NormalizedDevShape::SelfhostDetectFormatP3,
|
||||||
detectors::is_selfhost_detect_format_p3,
|
detectors::is_selfhost_detect_format_p3,
|
||||||
),
|
),
|
||||||
|
// Phase 89: Continue + Early Return pattern
|
||||||
|
(
|
||||||
|
NormalizedDevShape::PatternContinueReturnMinimal,
|
||||||
|
detectors::is_pattern_continue_return_minimal,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// direct ブリッジで扱う shape(dev 限定)。
|
/// direct ブリッジで扱う shape(dev 限定)。
|
||||||
@ -213,6 +220,8 @@ pub fn capability_for_shape(shape: &NormalizedDevShape) -> ShapeCapability {
|
|||||||
// Phase 54: selfhost P2/P3 shape growth
|
// Phase 54: selfhost P2/P3 shape growth
|
||||||
SelfhostVerifySchemaP2 => SelfhostP2Core,
|
SelfhostVerifySchemaP2 => SelfhostP2Core,
|
||||||
SelfhostDetectFormatP3 => SelfhostP3IfSum,
|
SelfhostDetectFormatP3 => SelfhostP3IfSum,
|
||||||
|
// Phase 89: Continue + Early Return pattern (dev-only, maps to P4 family)
|
||||||
|
PatternContinueReturnMinimal => P4ContinueSkipWs,
|
||||||
};
|
};
|
||||||
|
|
||||||
ShapeCapability::new(kind)
|
ShapeCapability::new(kind)
|
||||||
@ -832,6 +841,10 @@ mod detectors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 48-A: Check if module matches Pattern4 continue minimal shape
|
/// 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 {
|
pub(crate) fn is_pattern4_continue_minimal(module: &JoinModule) -> bool {
|
||||||
// Structure-based detection (avoid name-based heuristics)
|
// Structure-based detection (avoid name-based heuristics)
|
||||||
|
|
||||||
@ -848,11 +861,11 @@ mod detectors {
|
|||||||
|
|
||||||
// P4 characteristics:
|
// P4 characteristics:
|
||||||
// - Has Compare instruction (loop condition or continue check)
|
// - 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 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
|
// Phase 89: Tightened to exclude loop-internal return patterns
|
||||||
// which gets lowered to conditional tail call structure.
|
|
||||||
|
|
||||||
let has_compare = loop_step.body.iter().any(|inst| {
|
let has_compare = loop_step.body.iter().any(|inst| {
|
||||||
matches!(
|
matches!(
|
||||||
@ -861,16 +874,36 @@ mod detectors {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Has conditional jump or call (for continue/break check)
|
// Phase 89: Require Select (continue's core)
|
||||||
let has_conditional_flow = loop_step.body.iter().any(|inst| {
|
let has_select = loop_step.body.iter().any(|inst| match inst {
|
||||||
matches!(inst, JoinInst::Jump { cond: Some(_), .. })
|
JoinInst::Select { .. } => true,
|
||||||
|| matches!(inst, JoinInst::Call { k_next: None, .. })
|
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)
|
// P4 minimal has 2-4 params (i, acc, possibly n)
|
||||||
let reasonable_param_count = (2..=4).contains(&loop_step.params.len());
|
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 {
|
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")
|
.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> {
|
pub(super) fn find_loop_step(module: &JoinModule) -> Option<&JoinFunction> {
|
||||||
module
|
module
|
||||||
.functions
|
.functions
|
||||||
@ -1137,4 +1234,135 @@ mod tests {
|
|||||||
object_shapes
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,6 +140,10 @@ fn normalize_for_shape(
|
|||||||
.expect("P4 object normalization failed")
|
.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 {
|
match result {
|
||||||
|
|||||||
Reference in New Issue
Block a user