phase29ac(p1): normalize pattern6 reverse scan and flip smoke to PASS

This commit is contained in:
2025-12-28 16:54:01 +09:00
parent 209b04d808
commit 1e1679f361
6 changed files with 112 additions and 64 deletions

View File

@ -1,5 +1,5 @@
// Phase 29ab P7: Pattern6 reverse scan first-fail (step contract violation)
// Expect: JoinIR freeze (reverse step must be i = i - 1)
// Phase 29ac P1: Pattern6 reverse scan OK minimal
// Expect: last_index_bad_step("abc", "b") -> 1
static box Main {
last_index_bad_step(s, ch) {
@ -8,7 +8,7 @@ static box Main {
if s.substring(i, i + 1) == ch {
return i
}
i = i + 1
i = i - 1
}
return -1
}

View File

@ -24,7 +24,7 @@ Freeze conditions:
- forward scan with step != `i = i + 1`
- reverse scan with step != `i = i - 1`
Note:
- plan line is forward-only; reverse scans are currently treated as NotApplicable
- plan line supports reverse scan (cond: `i >= 0`, step: `i = i - 1`)
## Pattern7 (SplitScan)

View File

@ -0,0 +1,24 @@
# Phase 29ac: JoinIR thaw plan (Pattern6/7 near-miss → PASS)
Goal: Convert freeze-fixed Pattern6/7 near-miss cases into PASS while keeping contracts intact (no by-name or silent fallback).
## Target list (current freeze fixtures)
- `apps/tests/phase29ab_pattern6_firstfail_min.hako`
- `apps/tests/phase29ab_pattern6_matchscan_firstfail_min.hako`
- `apps/tests/phase29ab_pattern7_firstfail_min.hako`
## Pass order (recommended)
1. **Pattern6 reverse scan**
- Plan/Normalizer: add reverse support (`i >= 0`, step `i = i - 1`).
- Smoke: `phase29ab_pattern6_reverse_firstfail_min` now OK PASS (RC=1).
2. **Pattern6 matchscan missing step**
- Decide whether step can be synthesized safely; otherwise mark out-of-scope and add explicit contract note.
3. **Pattern7 split-scan near-miss**
- Resolve then/else update mismatches without relaxing contracts.
## Commands
- `./tools/smokes/v2/run.sh --profile integration --filter "phase29ab_pattern6_*"`
- `./tools/smokes/v2/run.sh --profile integration --filter "phase29ab_pattern7_*"`

View File

@ -65,14 +65,6 @@ pub(crate) fn extract_scan_with_init_plan(
"phase29ab/pattern6/contract",
)?;
// Phase 273 P1: Filter out patterns not supported by Plan-based normalizer
if let Some(ref p) = parts {
// P1 scope: Only forward scan (step=1) supported
if p.step_lit != 1 {
return Ok(None);
}
}
// Wrap in DomainPlan if extracted successfully
Ok(parts.map(DomainPlan::ScanWithInit))
}

View File

@ -1,4 +1,5 @@
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, ScanWithInitPlan};
use crate::mir::builder::control_flow::plan::ScanDirection;
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
@ -34,12 +35,21 @@ impl super::PlanNormalizer {
);
}
// P1 Scope: Forward scan (step=1) only
if parts.step_lit != 1 {
return Err(format!(
"[normalizer] P1 scope: only forward scan supported (step={})",
parts.step_lit
));
// P1 Scope: forward (step=1) or reverse (step=-1)
match parts.scan_direction {
ScanDirection::Forward if parts.step_lit != 1 => {
return Err(format!(
"[normalizer] P1 scope: forward scan requires step=1 (step={})",
parts.step_lit
));
}
ScanDirection::Reverse if parts.step_lit != -1 => {
return Err(format!(
"[normalizer] P1 scope: reverse scan requires step=-1 (step={})",
parts.step_lit
));
}
_ => {}
}
// Step 1: Get host ValueIds for variables
@ -169,30 +179,55 @@ impl super::PlanNormalizer {
});
}
// len = s.length()
header_effects.push(CoreEffectPlan::MethodCall {
dst: Some(len_val),
object: s_host,
method: "length".to_string(),
args: vec![],
effects: EffectMask::PURE.add(Effect::Io),
});
match parts.scan_direction {
ScanDirection::Forward => {
// len = s.length()
header_effects.push(CoreEffectPlan::MethodCall {
dst: Some(len_val),
object: s_host,
method: "length".to_string(),
args: vec![],
effects: EffectMask::PURE.add(Effect::Io),
});
// bound = len - needle_len
header_effects.push(CoreEffectPlan::BinOp {
dst: bound_val,
lhs: len_val,
op: BinaryOp::Sub,
rhs: needle_len_val,
});
// bound = len - needle_len
header_effects.push(CoreEffectPlan::BinOp {
dst: bound_val,
lhs: len_val,
op: BinaryOp::Sub,
rhs: needle_len_val,
});
// cond_loop = i <= bound
header_effects.push(CoreEffectPlan::Compare {
dst: cond_loop,
lhs: i_current,
op: CompareOp::Le,
rhs: bound_val,
});
// cond_loop = i <= bound
header_effects.push(CoreEffectPlan::Compare {
dst: cond_loop,
lhs: i_current,
op: CompareOp::Le,
rhs: bound_val,
});
}
ScanDirection::Reverse => {
let zero_val = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(zero_val, MirType::Integer);
// zero = 0
header_effects.push(CoreEffectPlan::Const {
dst: zero_val,
value: ConstValue::Integer(0),
});
// cond_loop = i >= 0
header_effects.push(CoreEffectPlan::Compare {
dst: cond_loop,
lhs: i_current,
op: CompareOp::Ge,
rhs: zero_val,
});
}
}
// Step 6: Build body (emitted in body_bb)
let body = vec![
@ -221,15 +256,22 @@ impl super::PlanNormalizer {
];
// Step 7: Build step_effects (emitted in step_bb)
let step_effects = vec![
// i_next = i + 1
CoreEffectPlan::BinOp {
let step_effects = match parts.scan_direction {
ScanDirection::Forward => vec![CoreEffectPlan::BinOp {
// i_next = i + 1
dst: i_next_val,
lhs: i_current,
op: BinaryOp::Add,
rhs: one_val,
},
];
}],
ScanDirection::Reverse => vec![CoreEffectPlan::BinOp {
// i_next = i - 1
dst: i_next_val,
lhs: i_current,
op: BinaryOp::Sub,
rhs: one_val,
}],
};
// Step 8: Build block_effects (Phase 273 P2)
let block_effects = vec![

View File

@ -1,6 +1,6 @@
#!/bin/bash
# Phase 29ab P7: Pattern6 reverse scan first-fail (contract violation)
# Tests: reverse scan step mismatch must fail-fast
# Phase 29ac P1: Pattern6 reverse scan OK minimal
# Tests: return index 1
source "$(dirname "$0")/../../../lib/test_runner.sh"
export SMOKES_USE_PYVM=0
@ -19,22 +19,12 @@ if [ "$EXIT_CODE" -eq 124 ]; then
exit 1
fi
if [ "$EXIT_CODE" -eq 0 ]; then
echo "[FAIL] Expected JoinIR contract freeze error, got exit 0"
echo "[INFO] Output:"
echo "$OUTPUT" | tail -n 40 || true
test_fail "phase29ab_pattern6_reverse_firstfail_min_vm: Unexpected success"
exit 1
if [ "$EXIT_CODE" -eq 1 ]; then
test_pass "phase29ab_pattern6_reverse_firstfail_min_vm: RC=1 (expected)"
exit 0
fi
if echo "$OUTPUT" | grep -q "\[joinir/phase29ab/pattern6/contract\]"; then
test_pass "phase29ab_pattern6_reverse_firstfail_min_vm: joinir contract freeze detected"
exit 0
else
echo "[FAIL] Expected joinir contract freeze tag in output"
echo "[INFO] Exit code: $EXIT_CODE"
echo "[INFO] Output:"
echo "$OUTPUT" | tail -n 60 || true
test_fail "phase29ab_pattern6_reverse_firstfail_min_vm: Missing joinir contract freeze tag"
exit 1
fi
echo "[FAIL] Expected exit 1, got $EXIT_CODE"
echo "$OUTPUT" | tail -n 40 || true
test_fail "phase29ab_pattern6_reverse_firstfail_min_vm: Unexpected RC"
exit 1