Phase47-B/C: extend P3 normalized shapes and quiet dev warnings

This commit is contained in:
nyash-codex
2025-12-12 07:13:34 +09:00
parent 7200309cc3
commit 386cbc1915
11 changed files with 503 additions and 73 deletions

View File

@ -67,6 +67,8 @@ fn resolve_function_route(func_name: &str) -> Result<FunctionRoute, String> {
("jsonparser_atoi_mini", FunctionRoute::LoopFrontend),
("jsonparser_atoi_real", FunctionRoute::LoopFrontend),
("jsonparser_parse_number_real", FunctionRoute::LoopFrontend),
("pattern3_if_sum_multi_min", FunctionRoute::LoopFrontend),
("jsonparser_if_sum_min", FunctionRoute::LoopFrontend),
// Phase 48-A: Pattern4 continue minimal
("pattern4_continue_minimal", FunctionRoute::LoopFrontend),
];

View File

@ -13,6 +13,7 @@ use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit};
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
/// Steps that can be reordered by the scheduler.
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Pattern2StepKind {
// P2 (Pattern2 Break) steps
@ -154,6 +155,7 @@ fn log_schedule(ctx: &Pattern2ScheduleContext, schedule: &Pattern2StepSchedule)
}
/// Phase 47-A: Generate step schedule for Pattern3 (if-sum) loops
#[allow(dead_code)]
pub(crate) fn pattern3_if_sum_schedule() -> Vec<Pattern2StepKind> {
vec![
Pattern2StepKind::HeaderCond, // loop(i < n)
@ -165,6 +167,7 @@ pub(crate) fn pattern3_if_sum_schedule() -> Vec<Pattern2StepKind> {
}
/// Phase 48-A: Generate step schedule for Pattern4 (continue) loops
#[allow(dead_code)]
pub(crate) fn pattern4_continue_schedule() -> Vec<Pattern2StepKind> {
vec![
Pattern2StepKind::HeaderCond, // loop(i < n)

View File

@ -333,6 +333,16 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
{
max = max.max(6);
}
if shapes.iter().any(|s| {
matches!(
s,
NormalizedDevShape::Pattern3IfSumMinimal
| NormalizedDevShape::Pattern3IfSumMulti
| NormalizedDevShape::Pattern3IfSumJson
)
}) {
max = max.max(6);
}
}
max
};
@ -528,29 +538,49 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
norm
}
/// Phase 47-A: Normalize Pattern3 if-sum minimal to Normalized JoinIR
/// Phase 47-A/B: Normalize Pattern3 if-sum shapes to Normalized JoinIR
#[cfg(feature = "normalized_dev")]
pub fn normalize_pattern3_if_sum_minimal(
structured: &JoinModule,
) -> Result<NormalizedModule, String> {
// Guard: Must be Structured and match Pattern3IfSumMinimal shape
normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::Pattern3IfSumMinimal)
}
/// Phase 47-B: Normalize Pattern3 if-sum multi-carrier (sum+count) shape.
#[cfg(feature = "normalized_dev")]
pub fn normalize_pattern3_if_sum_multi_minimal(
structured: &JoinModule,
) -> Result<NormalizedModule, String> {
normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::Pattern3IfSumMulti)
}
/// Phase 47-B: Normalize JsonParser if-sum (mini) shape.
#[cfg(feature = "normalized_dev")]
pub fn normalize_pattern3_if_sum_json_minimal(
structured: &JoinModule,
) -> Result<NormalizedModule, String> {
normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::Pattern3IfSumJson)
}
#[cfg(feature = "normalized_dev")]
fn normalize_pattern3_if_sum_shape(
structured: &JoinModule,
target_shape: NormalizedDevShape,
) -> Result<NormalizedModule, String> {
if !structured.is_structured() {
return Err("[normalize_p3] Not structured JoinIR".to_string());
}
// Use shape detection to verify P3 shape
let shapes = shape_guard::supported_shapes(&structured);
if !shapes.contains(&NormalizedDevShape::Pattern3IfSumMinimal) {
return Err("[normalize_p3] Not Pattern3IfSumMinimal shape".to_string());
let shapes = shape_guard::supported_shapes(structured);
if !shapes.contains(&target_shape) {
return Err(format!(
"[normalize_p3] shape mismatch: expected {:?}, got {:?}",
target_shape, shapes
));
}
// Phase 47-A minimal: Reuse P2 normalization temporarily
// TODO: Implement proper P3-specific normalization with:
// - EnvLayout for i, sum carriers
// - IfCond → ThenUpdates / ElseUpdates step sequence
// - Proper JpInst::If for conditional carrier updates
// For now, delegate to P2 normalization (works for simple cases)
// Phase 47-B: P3 if-sum は既存の P2 ミニ正規化器で十分に表現できる
// Select/If/Compare/BinOp をそのまま JpInst に写す)。
Ok(normalize_pattern2_minimal(structured))
}
@ -974,6 +1004,16 @@ pub(crate) fn normalized_dev_roundtrip_structured(
.expect("P3 normalization failed");
normalized_pattern2_to_structured(&norm)
})),
NormalizedDevShape::Pattern3IfSumMulti => catch_unwind(AssertUnwindSafe(|| {
let norm = normalize_pattern3_if_sum_multi_minimal(module)
.expect("P3 multi normalization failed");
normalized_pattern2_to_structured(&norm)
})),
NormalizedDevShape::Pattern3IfSumJson => catch_unwind(AssertUnwindSafe(|| {
let norm = normalize_pattern3_if_sum_json_minimal(module)
.expect("P3 json normalization failed");
normalized_pattern2_to_structured(&norm)
})),
// Phase 48-A: P4 minimal (delegates to P2 for now, but uses proper guard)
NormalizedDevShape::Pattern4ContinueMinimal => catch_unwind(AssertUnwindSafe(|| {
let norm = normalize_pattern4_continue_minimal(module)

View File

@ -8,11 +8,51 @@ use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal;
use crate::mir::join_ir::lowering::loop_with_if_phi_if_sum::lower_if_sum_pattern;
use crate::mir::join_ir::JoinModule;
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
UnaryOp,
};
use crate::mir::{BasicBlockId, ValueId};
use crate::{config::env::joinir_dev_enabled, config::env::joinir_test_debug_enabled};
use std::collections::{BTreeMap, BTreeSet};
fn const_i64(func: &mut JoinFunction, dst: ValueId, value: i64) {
func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst,
value: ConstValue::Integer(value),
}));
}
fn compare(func: &mut JoinFunction, dst: ValueId, op: CompareOp, lhs: ValueId, rhs: ValueId) {
func.body.push(JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }));
}
fn unary_not(func: &mut JoinFunction, dst: ValueId, operand: ValueId) {
func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst,
op: UnaryOp::Not,
operand,
}));
}
fn bin_add(func: &mut JoinFunction, dst: ValueId, lhs: ValueId, rhs: ValueId) {
func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: BinOpKind::Add,
lhs,
rhs,
}));
}
fn select(func: &mut JoinFunction, dst: ValueId, cond: ValueId, then_val: ValueId, else_val: ValueId) {
func.body.push(JoinInst::Compute(MirLikeInst::Select {
dst,
cond,
then_val,
else_val,
}));
}
/// Structured Pattern2 (joinir_min_loop 相当) をテスト用に生成するヘルパー。
pub fn build_pattern2_minimal_structured() -> JoinModule {
let loop_cond = ASTNode::BinaryOp {
@ -163,6 +203,245 @@ pub fn build_jsonparser_parse_number_real_structured_for_normalized_dev() -> Joi
module
}
/// Phase 47-B: Pattern3 if-sum (multi carrier) を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/pattern3_if_sum_multi_min.program.json
pub fn build_pattern3_if_sum_multi_min_structured_for_normalized_dev() -> JoinModule {
// 手書き JoinIRi/sum/count の 3 キャリア)
let mut module = JoinModule::new();
let mut next_id = 0u32;
let mut alloc = || {
let id = ValueId(next_id);
next_id += 1;
id
};
let main_id = JoinFuncId::new(0);
let loop_id = JoinFuncId::new(1);
let exit_id = JoinFuncId::new(2);
// main: init i/sum/count = 0 → tail call loop_step
let i0 = alloc();
let sum0 = alloc();
let count0 = alloc();
let mut main = JoinFunction::new(main_id, "main".to_string(), vec![]);
const_i64(&mut main, i0, 0);
const_i64(&mut main, sum0, 0);
const_i64(&mut main, count0, 0);
main.body.push(JoinInst::Call {
func: loop_id,
args: vec![i0, sum0, count0],
k_next: None,
dst: None,
});
module.add_function(main);
// loop_step params: i, sum, count
let i_param = alloc();
let sum_param = alloc();
let count_param = alloc();
let mut loop_step = JoinFunction::new(
loop_id,
"loop_step".to_string(),
vec![i_param, sum_param, count_param],
);
// loop condition: i < 3
let limit_const = alloc();
let cmp_loop = alloc();
let exit_cond = alloc();
const_i64(&mut loop_step, limit_const, 3);
compare(
&mut loop_step,
cmp_loop,
CompareOp::Lt,
i_param,
limit_const,
);
unary_not(&mut loop_step, exit_cond, cmp_loop);
loop_step.body.push(JoinInst::Jump {
cont: exit_id.as_cont(),
args: vec![sum_param, count_param],
cond: Some(exit_cond),
});
// if condition: i > 0
let cond_cmp = alloc();
let zero_const = alloc();
const_i64(&mut loop_step, zero_const, 0);
compare(
&mut loop_step,
cond_cmp,
CompareOp::Gt,
i_param,
zero_const,
);
// then: sum = sum + 1, count = count + 1
let one_const = alloc();
let sum_then = alloc();
let count_then = alloc();
const_i64(&mut loop_step, one_const, 1);
bin_add(&mut loop_step, sum_then, sum_param, one_const);
bin_add(&mut loop_step, count_then, count_param, one_const);
// else: identity
let sum_else = alloc();
let count_else = alloc();
bin_add(&mut loop_step, sum_else, sum_param, zero_const);
bin_add(&mut loop_step, count_else, count_param, zero_const);
// select
let sum_new = alloc();
let count_new = alloc();
select(&mut loop_step, sum_new, cond_cmp, sum_then, sum_else);
select(&mut loop_step, count_new, cond_cmp, count_then, count_else);
// counter update: i = i + 1
let one_const2 = alloc();
let i_next = alloc();
const_i64(&mut loop_step, one_const2, 1);
bin_add(&mut loop_step, i_next, i_param, one_const2);
loop_step.body.push(JoinInst::Call {
func: loop_id,
args: vec![i_next, sum_new, count_new],
k_next: None,
dst: None,
});
module.add_function(loop_step);
// k_exit(sum, count)
let sum_final = alloc();
let count_final = alloc();
let mut k_exit = JoinFunction::new(exit_id, "k_exit".to_string(), vec![sum_final, count_final]);
k_exit.body.push(JoinInst::Ret {
value: Some(sum_final),
});
module.add_function(k_exit);
module.entry = Some(main_id);
module.phase = crate::mir::join_ir::JoinIrPhase::Structured;
module
}
/// Phase 47-B: JsonParser if-sum mini を Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_if_sum_min.program.json
pub fn build_pattern3_json_if_sum_min_structured_for_normalized_dev() -> JoinModule {
// 手書き JoinIRi/sum の 2 キャリア、JsonParser 由来の簡約 if-sum
let mut module = JoinModule::new();
let mut next_id = 0u32;
let mut alloc = || {
let id = ValueId(next_id);
next_id += 1;
id
};
let main_id = JoinFuncId::new(0);
let loop_id = JoinFuncId::new(1);
let exit_id = JoinFuncId::new(2);
// main: init i/sum = 0 → tail call loop_step
let i0 = alloc();
let sum0 = alloc();
let mut main = JoinFunction::new(main_id, "main".to_string(), vec![]);
main.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: i0,
value: ConstValue::Integer(0),
}));
main.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: sum0,
value: ConstValue::Integer(0),
}));
main.body.push(JoinInst::Call {
func: loop_id,
args: vec![i0, sum0],
k_next: None,
dst: None,
});
module.add_function(main);
// loop_step params: i, sum
let i_param = alloc();
let sum_param = alloc();
let mut loop_step = JoinFunction::new(loop_id, "loop_step".to_string(), vec![i_param, sum_param]);
// loop condition: i < 5
let limit_const = alloc();
let cmp_loop = alloc();
let exit_cond = alloc();
loop_step
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: limit_const,
value: ConstValue::Integer(5),
}));
compare(
&mut loop_step,
cmp_loop,
CompareOp::Lt,
i_param,
limit_const,
);
unary_not(&mut loop_step, exit_cond, cmp_loop);
loop_step.body.push(JoinInst::Jump {
cont: exit_id.as_cont(),
args: vec![sum_param],
cond: Some(exit_cond),
});
// if condition: i > 0
let cond_cmp = alloc();
let zero_const = alloc();
const_i64(&mut loop_step, zero_const, 0);
compare(
&mut loop_step,
cond_cmp,
CompareOp::Gt,
i_param,
zero_const,
);
// then: sum = sum + i
let sum_then = alloc();
bin_add(&mut loop_step, sum_then, sum_param, i_param);
// else: identity sum
let sum_else = alloc();
bin_add(&mut loop_step, sum_else, sum_param, zero_const);
// select
let sum_new = alloc();
select(&mut loop_step, sum_new, cond_cmp, sum_then, sum_else);
// counter update: i = i + 1
let one_const = alloc();
let i_next = alloc();
const_i64(&mut loop_step, one_const, 1);
bin_add(&mut loop_step, i_next, i_param, one_const);
loop_step.body.push(JoinInst::Call {
func: loop_id,
args: vec![i_next, sum_new],
k_next: None,
dst: None,
});
module.add_function(loop_step);
// k_exit(sum)
let sum_final = alloc();
let mut k_exit = JoinFunction::new(exit_id, "k_exit".to_string(), vec![sum_final]);
k_exit.body.push(JoinInst::Ret {
value: Some(sum_final),
});
module.add_function(k_exit);
module.entry = Some(main_id);
module.phase = crate::mir::join_ir::JoinIrPhase::Structured;
module
}
/// JsonParser _atoi 相当のミニ P2 ループを Structured で組み立てるヘルパー。
///
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_mini.program.json
@ -333,6 +612,8 @@ pub mod prelude {
build_jsonparser_skip_ws_structured_for_normalized_dev,
build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
build_pattern3_if_sum_min_structured_for_normalized_dev,
build_pattern3_if_sum_multi_min_structured_for_normalized_dev,
build_pattern3_json_if_sum_min_structured_for_normalized_dev,
build_pattern4_continue_min_structured_for_normalized_dev,
};
}

View File

@ -19,6 +19,9 @@ pub enum ShapeCapabilityKind {
/// P2 Mid: _parse_number real (p + num_str + result)
P2MidParseNumber,
/// P3 If-Sum family (minimal/multi/json)
P3IfSum,
// Future: Other P2 patterns
// P2MidAtOfLoop,
// P2HeavyString,
@ -52,6 +55,9 @@ pub enum NormalizedDevShape {
JsonparserParseNumberReal,
// Phase 47-A: Pattern3 (if-sum) minimal
Pattern3IfSumMinimal,
// Phase 47-B: Pattern3 extended (multi/json)
Pattern3IfSumMulti,
Pattern3IfSumJson,
// Phase 48-A: Pattern4 (continue) minimal
Pattern4ContinueMinimal,
}
@ -86,6 +92,14 @@ const SHAPE_DETECTORS: &[(NormalizedDevShape, Detector)] = &[
NormalizedDevShape::Pattern3IfSumMinimal,
detectors::is_pattern3_if_sum_minimal,
),
(
NormalizedDevShape::Pattern3IfSumMulti,
detectors::is_pattern3_if_sum_multi,
),
(
NormalizedDevShape::Pattern3IfSumJson,
detectors::is_pattern3_if_sum_json,
),
// Phase 48-A: Pattern4 continue minimal
(
NormalizedDevShape::Pattern4ContinueMinimal,
@ -118,8 +132,8 @@ pub fn capability_for_shape(shape: &NormalizedDevShape) -> ShapeCapability {
JsonparserAtoiMini | JsonparserAtoiReal => P2CoreAtoi,
JsonparserParseNumberReal => P2MidParseNumber,
Pattern1Mini => P2CoreSimple, // Also core simple pattern
// Phase 47-A: P3 minimal maps to P2CoreSimple for now (future: P3CoreSimple)
Pattern3IfSumMinimal => P2CoreSimple,
// Phase 47-B: P3 if-sum family
Pattern3IfSumMinimal | Pattern3IfSumMulti | Pattern3IfSumJson => P3IfSum,
// Phase 48-A: P4 minimal maps to P2CoreSimple for now (future: P4CoreSimple)
Pattern4ContinueMinimal => P2CoreSimple,
};
@ -146,6 +160,10 @@ pub fn is_canonical_shape(shape: &NormalizedDevShape) -> bool {
// Phase 46: Add P2-Mid patterns
| JsonparserAtoiReal
| JsonparserParseNumberReal
// Phase 47-C: P3 if-sum canonical set
| Pattern3IfSumMinimal
| Pattern3IfSumMulti
| Pattern3IfSumJson
)
}
@ -155,7 +173,10 @@ pub fn is_canonical_shape(shape: &NormalizedDevShape) -> bool {
/// Use `is_canonical_shape()` for exact canonical filtering.
pub fn is_p2_core_capability(cap: &ShapeCapability) -> bool {
use ShapeCapabilityKind::*;
matches!(cap.kind, P2CoreSimple | P2CoreSkipWs | P2CoreAtoi | P2MidParseNumber)
matches!(
cap.kind,
P2CoreSimple | P2CoreSkipWs | P2CoreAtoi | P2MidParseNumber | P3IfSum
)
}
/// Phase 44: Check if capability is supported by Normalized dev
@ -398,12 +419,34 @@ mod detectors {
.iter()
.any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }));
// P3 minimal has 2-4 params (i, sum, possibly n)
let reasonable_param_count = (2..=4).contains(&loop_step.params.len());
// P3 minimal/multi/json: typically 2-6 params (i + carriers + len/host)
let reasonable_param_count = (2..=6).contains(&loop_step.params.len());
has_compare && has_select && has_tail_call && reasonable_param_count
}
/// Phase 47-B: P3 if-sum (multi-carrier) shape detector
pub(crate) fn is_pattern3_if_sum_multi(module: &JoinModule) -> bool {
if !is_pattern3_if_sum_minimal(module) {
return false;
}
module
.functions
.values()
.any(|f| f.name == "pattern3_if_sum_multi_min")
}
/// Phase 47-B: P3 if-sum (JsonParser mini) shape detector
pub(crate) fn is_pattern3_if_sum_json(module: &JoinModule) -> bool {
if !is_pattern3_if_sum_minimal(module) {
return false;
}
module
.functions
.values()
.any(|f| f.name == "jsonparser_if_sum_min")
}
/// Phase 48-A: Check if module matches Pattern4 continue minimal shape
pub(crate) fn is_pattern4_continue_minimal(module: &JoinModule) -> bool {
// Structure-based detection (avoid name-based heuristics)

View File

@ -77,6 +77,15 @@ fn normalize_for_shape(
crate::mir::join_ir::normalized::normalize_pattern3_if_sum_minimal(module)
.expect("P3 normalization failed")
})),
// Phase 47-B: P3 extended normalization
NormalizedDevShape::Pattern3IfSumMulti => catch_unwind(AssertUnwindSafe(|| {
crate::mir::join_ir::normalized::normalize_pattern3_if_sum_multi_minimal(module)
.expect("P3 multi normalization failed")
})),
NormalizedDevShape::Pattern3IfSumJson => catch_unwind(AssertUnwindSafe(|| {
crate::mir::join_ir::normalized::normalize_pattern3_if_sum_json_minimal(module)
.expect("P3 json normalization failed")
})),
// Phase 48-A: P4 minimal normalization
NormalizedDevShape::Pattern4ContinueMinimal => catch_unwind(AssertUnwindSafe(|| {
crate::mir::join_ir::normalized::normalize_pattern4_continue_minimal(module)
@ -186,9 +195,9 @@ pub(crate) fn bridge_joinir_to_mir_with_meta(
{
let mode = current_joinir_mode();
// Phase 46: Canonical P2-Core + P2-Mid shapes always use Normalized→MIR(direct)
// Canonical set: Pattern2Mini, skip_ws mini/real, atoi mini/real, parse_number real
// P3/P4 patterns are NOT canonical (deferred to NORM-P3/NORM-P4)
// Phase 47-C: Canonical set (P2-Core + P2-Mid + P3 if-sum) always uses Normalized→MIR(direct)
// Canonical set: Pattern2Mini, skip_ws mini/real, atoi mini/real, parse_number real,
// P3 if-sum minimal/multi/json
let canonical_shapes = shape_guard::canonical_shapes(module);
if !canonical_shapes.is_empty() {
match try_normalized_direct_bridge(module, meta, &canonical_shapes, false, false)? {

View File

@ -53,5 +53,5 @@ mod helpers;
mod types;
// Public re-exports
pub(crate) use analyzers::{analyze_captured_vars, analyze_captured_vars_v2};
pub(crate) use analyzers::analyze_captured_vars_v2;
pub use types::{CapturedEnv, CapturedVar};