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:
@ -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
|
||||
|
||||
Reference in New Issue
Block a user