feat(plan): Phase 273 P2 Step 3-6 - Pattern7 (SplitScan) to Plan line

Migrate Pattern7 from legacy lowering to Plan architecture:

DomainPlan (mod.rs):
- Added SplitScan(SplitScanPlan) variant
- SplitScanPlan: s_var, sep_var, result_var, i_var, start_var

Extractor (pattern7_split_scan.rs):
- extract_split_scan_plan() returning DomainPlan
- Reuses existing extract_split_scan_parts()

Router (router.rs):
- Pattern7 now uses Plan line (Normalize→Verify→Lower)
- Removed from LOOP_PATTERNS table

Normalizer (normalizer.rs):
- normalize_split_scan() - 400+ lines migrated from impl
- 6 blocks: preheader/header/body/then/else/step/after
- 4 PHIs: header(2) + step(2) for i/start carriers
- Side effect: push with EffectMask::MUT

Bug fixes:
- Pattern6 extractor returns Ok(None) for non-match (allows fallback)
- Reverse scan filtered early in extractor (P1 scope)

Tests:
- phase256_p0_split_vm: PASS (exit=3)
- phase258_p0_index_of_string_vm: PASS (exit=6)

Lowerer no longer contains "split" - pattern-agnostic achieved!

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-22 23:35:43 +09:00
parent d16800741f
commit e4b5a8e832
5 changed files with 515 additions and 9 deletions

View File

@ -43,7 +43,9 @@ pub(in crate::mir::builder) mod verifier;
pub(in crate::mir::builder) enum DomainPlan {
/// Pattern6: index_of / find scan
ScanWithInit(ScanWithInitPlan),
// P2+: Split(SplitPlan), BoolPredicate(BoolPredicatePlan), etc.
/// Pattern7: split / tokenization scan
SplitScan(SplitScanPlan),
// P2+: BoolPredicate(BoolPredicatePlan), etc.
}
/// Phase 273 P0: Scan direction for forward/reverse scan
@ -79,6 +81,23 @@ pub(in crate::mir::builder) struct ScanWithInitPlan {
pub dynamic_needle: bool,
}
/// Phase 273 P2: Extracted structure for split-scan pattern
///
/// This structure contains all the information needed to lower a split-style loop.
#[derive(Debug, Clone)]
pub(in crate::mir::builder) struct SplitScanPlan {
/// Haystack variable name (e.g., "s")
pub s_var: String,
/// Separator variable name (e.g., "separator")
pub sep_var: String,
/// Accumulator variable name (e.g., "result", ArrayBox)
pub result_var: String,
/// Loop index variable name (e.g., "i")
pub i_var: String,
/// Segment start position variable name (e.g., "start")
pub start_var: String,
}
// ============================================================================
// CorePlan (固定語彙 - 構造ノードのみ)
// ============================================================================

View File

@ -12,7 +12,8 @@
//! Lowerer processes CorePlan without any pattern knowledge.
use super::{
CoreCarrierInfo, CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan, ScanWithInitPlan,
CoreCarrierInfo, CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan,
ScanWithInitPlan, SplitScanPlan,
};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
@ -36,6 +37,7 @@ impl PlanNormalizer {
) -> Result<CorePlan, String> {
match domain {
DomainPlan::ScanWithInit(parts) => Self::normalize_scan_with_init(builder, parts, ctx),
DomainPlan::SplitScan(parts) => Self::normalize_split_scan(builder, parts, ctx),
}
}
@ -370,4 +372,420 @@ impl PlanNormalizer {
Ok(CorePlan::Loop(loop_plan))
}
/// SplitScan → CorePlan 変換
///
/// Expands split-specific semantics into generic CorePlan:
/// - 2 carriers: i (loop index), start (segment start)
/// - 6 blocks: preheader, header, body, then, else, step, after
/// - 4 PHI nodes: header (i_current, start_current) + step (i_next, start_next)
/// - Side effect: result.push(segment) in then_bb
fn normalize_split_scan(
builder: &mut MirBuilder,
parts: SplitScanPlan,
ctx: &LoopPatternContext,
) -> Result<CorePlan, String> {
use crate::mir::builder::control_flow::joinir::trace;
let trace_logger = trace::trace();
let debug = ctx.debug;
if debug {
trace_logger.debug(
"normalizer/split_scan",
&format!(
"Phase 273 P2: Normalizing SplitScan for {}",
ctx.func_name
),
);
}
// Step 1: Get host ValueIds for variables
let s_host = builder
.variable_ctx
.variable_map
.get(&parts.s_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.s_var))?;
let sep_host = builder
.variable_ctx
.variable_map
.get(&parts.sep_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.sep_var))?;
let result_host = builder
.variable_ctx
.variable_map
.get(&parts.result_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.result_var))?;
let i_init_val = builder
.variable_ctx
.variable_map
.get(&parts.i_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.i_var))?;
let start_init_val = builder
.variable_ctx
.variable_map
.get(&parts.start_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.start_var))?;
// Step 2: Capture preheader block
let preheader_bb = builder
.current_block
.ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?;
// Step 3: Allocate BasicBlockIds for 6 blocks
let header_bb = builder.next_block_id();
let body_bb = builder.next_block_id();
let then_bb = builder.next_block_id();
let else_bb = builder.next_block_id();
let step_bb = builder.next_block_id();
let after_bb = builder.next_block_id();
// Step 4: Allocate ValueIds for PHI destinations (before blocks)
let i_current = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_current, MirType::Integer);
let start_current = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(start_current, MirType::Integer);
let i_next = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_next, MirType::Integer);
let start_next = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(start_next, MirType::Integer);
// Step 5: Allocate ValueIds for expressions
let sep_len = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(sep_len, MirType::Integer);
let s_len = builder.next_value_id();
builder.type_ctx.value_types.insert(s_len, MirType::Integer);
let limit = builder.next_value_id();
builder.type_ctx.value_types.insert(limit, MirType::Integer);
let cond_loop = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(cond_loop, MirType::Bool);
let i_plus_sep = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_plus_sep, MirType::Integer);
let chunk = builder.next_value_id();
builder.type_ctx.value_types.insert(chunk, MirType::String);
let cond_match = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(cond_match, MirType::Bool);
let segment = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(segment, MirType::String);
let start_next_then = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(start_next_then, MirType::Integer);
let one = builder.next_value_id();
builder.type_ctx.value_types.insert(one, MirType::Integer);
let i_next_else = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_next_else, MirType::Integer);
if debug {
trace_logger.debug(
"normalizer/split_scan",
&format!(
"Allocated: preheader={:?}, header={:?}, body={:?}, then={:?}, else={:?}, step={:?}, after={:?}",
preheader_bb, header_bb, body_bb, then_bb, else_bb, step_bb, after_bb
),
);
}
// Step 6: Build header_effects
let header_effects = vec![
// sep_len = sep.length()
CoreEffectPlan::MethodCall {
dst: Some(sep_len),
object: sep_host,
method: "length".to_string(),
args: vec![],
effects: EffectMask::PURE.add(Effect::Io),
},
// s_len = s.length()
CoreEffectPlan::MethodCall {
dst: Some(s_len),
object: s_host,
method: "length".to_string(),
args: vec![],
effects: EffectMask::PURE.add(Effect::Io),
},
// limit = s_len - sep_len
CoreEffectPlan::BinOp {
dst: limit,
lhs: s_len,
op: BinaryOp::Sub,
rhs: sep_len,
},
// cond_loop = i <= limit
CoreEffectPlan::Compare {
dst: cond_loop,
lhs: i_current,
op: CompareOp::Le,
rhs: limit,
},
];
// Step 7: Build body effects and plans
let body = vec![
// i_plus_sep = i + sep_len
CorePlan::Effect(CoreEffectPlan::BinOp {
dst: i_plus_sep,
lhs: i_current,
op: BinaryOp::Add,
rhs: sep_len,
}),
// chunk = s.substring(i, i_plus_sep)
CorePlan::Effect(CoreEffectPlan::MethodCall {
dst: Some(chunk),
object: s_host,
method: "substring".to_string(),
args: vec![i_current, i_plus_sep],
effects: EffectMask::PURE.add(Effect::Io),
}),
// cond_match = chunk == sep
CorePlan::Effect(CoreEffectPlan::Compare {
dst: cond_match,
lhs: chunk,
op: CompareOp::Eq,
rhs: sep_host,
}),
];
// Step 8: Build then_effects (push + updates)
let then_effects = vec![
// segment = s.substring(start, i)
CoreEffectPlan::MethodCall {
dst: Some(segment),
object: s_host,
method: "substring".to_string(),
args: vec![start_current, i_current],
effects: EffectMask::PURE.add(Effect::Io),
},
// result.push(segment) - Side effect!
CoreEffectPlan::MethodCall {
dst: None, // push returns Void
object: result_host,
method: "push".to_string(),
args: vec![segment],
effects: EffectMask::MUT,
},
// start_next_then = i + sep_len
CoreEffectPlan::BinOp {
dst: start_next_then,
lhs: i_current,
op: BinaryOp::Add,
rhs: sep_len,
},
];
// Step 9: Build else_effects (increment i)
let else_effects = vec![
// one = const 1
CoreEffectPlan::Const {
dst: one,
value: ConstValue::Integer(1),
},
// i_next_else = i + 1
CoreEffectPlan::BinOp {
dst: i_next_else,
lhs: i_current,
op: BinaryOp::Add,
rhs: one,
},
];
// Step 10: Build block_effects (SSOT ordering: preheader, header, body, then, else, step)
let block_effects = vec![
(preheader_bb, vec![]), // No effects in preheader
(header_bb, header_effects.clone()),
(body_bb, vec![]), // Body effects are in body CorePlan
(then_bb, then_effects),
(else_bb, else_effects),
(step_bb, vec![]), // No effects in step
];
// Step 11: Build phis (4 PHIs: 2 in header + 2 in step)
let phis = vec![
// Header PHI 1: i_current
CorePhiInfo {
block: header_bb,
dst: i_current,
inputs: vec![
(preheader_bb, i_init_val),
(step_bb, i_next),
],
tag: format!("loop_carrier_i_{}", parts.i_var),
},
// Header PHI 2: start_current
CorePhiInfo {
block: header_bb,
dst: start_current,
inputs: vec![
(preheader_bb, start_init_val),
(step_bb, start_next),
],
tag: format!("loop_carrier_start_{}", parts.start_var),
},
// Step PHI 1: i_next
CorePhiInfo {
block: step_bb,
dst: i_next,
inputs: vec![
(then_bb, start_next_then), // i = start (from then)
(else_bb, i_next_else), // i = i + 1 (from else)
],
tag: format!("step_phi_i_{}", parts.i_var),
},
// Step PHI 2: start_next
CorePhiInfo {
block: step_bb,
dst: start_next,
inputs: vec![
(then_bb, start_next_then), // start updated
(else_bb, start_current), // start unchanged
],
tag: format!("step_phi_start_{}", parts.start_var),
},
];
// Step 12: Build Frag (2 branches + 3 wires)
let empty_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
};
let branches = vec![
// header -> body/after
BranchStub {
from: header_bb,
cond: cond_loop,
then_target: body_bb,
then_args: empty_args.clone(),
else_target: after_bb,
else_args: empty_args.clone(),
},
// body -> then/else
BranchStub {
from: body_bb,
cond: cond_match,
then_target: then_bb,
then_args: empty_args.clone(),
else_target: else_bb,
else_args: empty_args.clone(),
},
];
let wires = vec![
// then -> step
EdgeStub {
from: then_bb,
kind: ExitKind::Normal,
target: Some(step_bb),
args: empty_args.clone(),
},
// else -> step
EdgeStub {
from: else_bb,
kind: ExitKind::Normal,
target: Some(step_bb),
args: empty_args.clone(),
},
// step -> header (back-edge)
EdgeStub {
from: step_bb,
kind: ExitKind::Normal,
target: Some(header_bb),
args: empty_args,
},
];
let mut frag = Frag::new(header_bb);
frag.branches = branches;
frag.wires = wires;
// Step 13: Build final_values (i, start for post-loop)
let final_values = vec![
(parts.i_var.clone(), i_current),
(parts.start_var.clone(), start_current),
];
// Step 14: Build CoreLoopPlan (generalized fields only)
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb: after_bb, // No early exit for split pattern
header_effects,
body,
step_effects: vec![], // No step_effects (done in then/else)
carriers: vec![], // Legacy field (not used with generalized)
cond_loop,
cond_match,
loop_var_name: parts.i_var,
// Phase 273 P2: Generalized fields populated
block_effects: Some(block_effects),
phis: Some(phis),
frag: Some(frag),
final_values: Some(final_values),
};
if debug {
trace_logger.debug(
"normalizer/split_scan",
"CorePlan construction complete (6 blocks, 4 PHIs)",
);
}
Ok(CorePlan::Loop(loop_plan))
}
}