feat(joinir): Phase 257 P1.1/P1.2/P1.3 - Pattern6 SSOT + LoopHeaderPhi CFG fix

P1.1: Pattern6 false positive fix (SSOT approach)
- can_lower() now calls extract_scan_with_init_parts() for SSOT
- index_of_string/2 no longer triggers false positive
- Graceful fall-through with Ok(None)

P1.2: LoopHeaderPhi CFG-based correction
- Step 0: Manual successor update from terminators
- CFG-based entry predecessor computation (header_preds - latch)
- Multi-entry-pred support (bb0 host + bb10 JoinIR main)
- Explicit host_entry_block addition (emit_jump runs after finalize)

P1.3: Smoke script validation
- phase254_p0_index_of_vm.sh: --verify + VM error detection
- phase257 smokes updated

Acceptance criteria (all PASS):
 phase254_p0_index_of_min.hako verify
 phase257_p0_last_index_of_min.hako verify
 ./tools/smokes/v2/run.sh --profile quick (no Pattern6 false positive)

Technical discovery:
- Host entry block (bb0) Jump set in Phase 6 (after finalize)
- instruction_rewriter bypasses set_terminator(), skips successor update

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-20 23:30:27 +09:00
parent 9ba89bada2
commit 73ddc5f58d
10 changed files with 492 additions and 71 deletions

View File

@ -79,57 +79,41 @@ struct ScanParts {
///
/// Detection is structure-based only (no function name checks).
pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool {
use crate::mir::loop_pattern_detection::LoopPatternKind;
// Phase 254 P0: Accept Pattern2Break OR Pattern3IfPhi
// - Pattern2Break: loop with break statement
// - Pattern3IfPhi: loop with return statement (not counted as break)
// index_of has return (early exit), which is classified as Pattern3IfPhi
match ctx.pattern_kind {
LoopPatternKind::Pattern2Break | LoopPatternKind::Pattern3IfPhi => {
// Continue to structure checks
// Phase 257 P1.1: SSOT between detect and extract
// Call extract to check if pattern is actually extractable
// This prevents false positives where detection is too broad
match extract_scan_with_init_parts(ctx.condition, ctx.body, None) {
Ok(Some(_)) => {
// Pattern is extractable
if ctx.debug {
trace::trace().debug(
"pattern6/can_lower",
"accept: pattern extractable (SSOT verified)",
);
}
true
}
_ => return false,
}
// Check for if statement with MethodCall in condition
let has_if_with_methodcall = ctx.body.iter().any(|stmt| {
matches!(stmt, ASTNode::If { condition, .. } if contains_methodcall(condition))
});
if !has_if_with_methodcall {
if ctx.debug {
trace::trace().debug(
"pattern6/can_lower",
"reject: no if with MethodCall in condition",
);
Ok(None) => {
// Not this pattern (fall through to other patterns)
if ctx.debug {
trace::trace().debug(
"pattern6/can_lower",
"reject: pattern not extractable (Ok(None))",
);
}
false
}
return false;
}
// Check for ConstStep (i = i + 1)
let has_const_step = ctx.body.iter().any(|stmt| {
matches!(stmt, ASTNode::Assignment { value, .. } if is_const_step_pattern(value))
});
if !has_const_step {
if ctx.debug {
trace::trace().debug(
"pattern6/can_lower",
"reject: no ConstStep pattern found",
);
Err(e) => {
// Extraction error (log in debug mode, fall through)
if ctx.debug {
trace::trace().debug(
"pattern6/can_lower",
&format!("reject: extraction error: {}", e),
);
}
false
}
return false;
}
if ctx.debug {
trace::trace().debug(
"pattern6/can_lower",
"MATCHED: ScanWithInit pattern detected",
);
}
true
}
/// Check if AST node contains MethodCall