Phase 33 NORM canon test: enforce normalized dev route for P1/P2/JP mini

This commit is contained in:
nyash-codex
2025-12-11 20:54:33 +09:00
parent 59a985b7fa
commit af6f95cd4b
170 changed files with 4423 additions and 1897 deletions

View File

@ -27,13 +27,13 @@
//! - **Testability**: Can test conversion independently
//! - **Reduces duplication**: Eliminates 120 lines across Pattern 1-4
use crate::mir::ValueId;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::JoinModule;
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
use crate::mir::join_ir::JoinModule;
use crate::mir::ValueId;
use std::collections::BTreeMap;
pub struct JoinIRConversionPipeline;
pub(crate) struct JoinIRConversionPipeline;
impl JoinIRConversionPipeline {
/// Execute unified conversion pipeline
@ -82,41 +82,28 @@ impl JoinIRConversionPipeline {
pattern_name: &str,
debug: bool,
) -> Result<Option<ValueId>, String> {
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
use super::super::trace;
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
use crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir_with_meta;
// Step 1: Log JoinIR stats (functions and blocks)
trace::trace().joinir_stats(
pattern_name,
join_module.functions.len(),
join_module
.functions
.values()
.map(|f| f.body.len())
.sum(),
join_module.functions.values().map(|f| f.body.len()).sum(),
);
// Step 2: JoinModule → MirModule conversion
// Pass empty meta map since minimal lowerers don't use metadata
let empty_meta: JoinFuncMetaMap = BTreeMap::new();
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)
.map_err(|e| {
format!(
"[{}/pipeline] MIR conversion failed: {:?}",
pattern_name, e
)
})?;
let mir_module = bridge_joinir_to_mir_with_meta(&join_module, &empty_meta)
.map_err(|e| format!("[{}/pipeline] MIR conversion failed: {:?}", pattern_name, e))?;
// Step 3: Log MIR stats (functions and blocks)
trace::trace().joinir_stats(
pattern_name,
mir_module.functions.len(),
mir_module
.functions
.values()
.map(|f| f.blocks.len())
.sum(),
mir_module.functions.values().map(|f| f.blocks.len()).sum(),
);
// Step 4: Merge into current function

View File

@ -10,17 +10,17 @@
//!
//! This orchestrator coordinates the three modules for a complete workflow.
use crate::mir::ValueId;
use crate::mir::join_ir::lowering::inline_boundary::{
JoinInlineBoundary, LoopExitBinding,
};
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
use crate::mir::join_ir::lowering::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
use crate::mir::ValueId;
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
// Phase 222.5-C: Import modular components
use super::exit_binding_validator::validate_exit_binding;
use super::exit_binding_applicator::{
apply_exit_bindings_to_boundary, create_loop_var_exit_binding,
};
use super::exit_binding_constructor::build_loop_exit_bindings;
use super::exit_binding_applicator::{apply_exit_bindings_to_boundary, create_loop_var_exit_binding};
use super::exit_binding_validator::validate_exit_binding;
/// Builder for generating loop exit bindings
///
@ -29,7 +29,7 @@ use super::exit_binding_applicator::{apply_exit_bindings_to_boundary, create_loo
///
/// Eliminates hardcoded variable names and ValueId plumbing scattered across lowerers.
#[allow(dead_code)]
pub struct ExitBindingBuilder<'a> {
pub(crate) struct ExitBindingBuilder<'a> {
carrier_info: &'a CarrierInfo,
exit_meta: &'a ExitMeta,
variable_map: &'a mut BTreeMap<String, ValueId>, // Phase 222.5-D: HashMap → BTreeMap for determinism
@ -60,7 +60,7 @@ impl<'a> ExitBindingBuilder<'a> {
///
/// ExitBindingBuilder instance, or error if metadata is inconsistent
#[allow(dead_code)]
pub fn new(
pub(crate) fn new(
carrier_info: &'a CarrierInfo,
exit_meta: &'a ExitMeta,
variable_map: &'a mut BTreeMap<String, ValueId>, // Phase 222.5-D: HashMap → BTreeMap for determinism
@ -86,7 +86,7 @@ impl<'a> ExitBindingBuilder<'a> {
///
/// Vec of LoopExitBinding, one per carrier, sorted by carrier name
#[allow(dead_code)]
pub fn build_loop_exit_bindings(&mut self) -> Result<Vec<LoopExitBinding>, String> {
pub(crate) fn build_loop_exit_bindings(&mut self) -> Result<Vec<LoopExitBinding>, String> {
// Phase 222.5-C: Delegate to constructor module
build_loop_exit_bindings(self.carrier_info, self.exit_meta, self.variable_map)
}
@ -106,7 +106,7 @@ impl<'a> ExitBindingBuilder<'a> {
///
/// Success or error if boundary cannot be updated
#[allow(dead_code)]
pub fn apply_to_boundary(&self, boundary: &mut JoinInlineBoundary) -> Result<(), String> {
pub(crate) fn apply_to_boundary(&self, boundary: &mut JoinInlineBoundary) -> Result<(), String> {
// Phase 222.5-C: Delegate to applicator module
apply_exit_bindings_to_boundary(
self.carrier_info,
@ -160,7 +160,9 @@ mod tests {
let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, &mut variable_map)
.expect("Failed to create builder");
let bindings = builder.build_loop_exit_bindings().expect("Failed to build bindings");
let bindings = builder
.build_loop_exit_bindings()
.expect("Failed to build bindings");
assert_eq!(bindings.len(), 1);
assert_eq!(bindings[0].carrier_name, "sum");
@ -213,7 +215,9 @@ mod tests {
let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, &mut variable_map)
.expect("Failed to create builder");
let bindings = builder.build_loop_exit_bindings().expect("Failed to build bindings");
let bindings = builder
.build_loop_exit_bindings()
.expect("Failed to build bindings");
assert_eq!(bindings.len(), 2);
// Bindings should be sorted by carrier name
@ -345,24 +349,27 @@ mod tests {
let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, &mut variable_map)
.expect("Failed to create builder");
let _ = builder.build_loop_exit_bindings().expect("Failed to build bindings");
let _ = builder
.build_loop_exit_bindings()
.expect("Failed to build bindings");
let mut boundary = JoinInlineBoundary {
host_inputs: vec![],
join_inputs: vec![],
exit_bindings: vec![], // Phase 171: Add missing field
exit_bindings: vec![], // Phase 171: Add missing field
#[allow(deprecated)]
host_outputs: vec![], // legacy, unused in new assertions
host_outputs: vec![], // legacy, unused in new assertions
join_outputs: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Add missing field
condition_bindings: vec![], // Phase 171-fix: Add missing field
expr_result: None, // Phase 33-14: Add missing field
loop_var_name: None, // Phase 33-16: Add missing field
carrier_info: None, // Phase 228: Add missing field
condition_inputs: vec![], // Phase 171: Add missing field
condition_bindings: vec![], // Phase 171-fix: Add missing field
expr_result: None, // Phase 33-14: Add missing field
loop_var_name: None, // Phase 33-16: Add missing field
carrier_info: None, // Phase 228: Add missing field
};
builder.apply_to_boundary(&mut boundary)
builder
.apply_to_boundary(&mut boundary)
.expect("Failed to apply to boundary");
// Should have loop_var + sum carrier in exit_bindings

View File

@ -38,7 +38,7 @@ use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::BasicBlockId;
use std::collections::{BTreeMap, BTreeSet};
pub struct LoopScopeShapeBuilder;
pub(crate) struct LoopScopeShapeBuilder;
impl LoopScopeShapeBuilder {
/// Create LoopScopeShape with empty body_locals

View File

@ -46,20 +46,20 @@ pub(in crate::mir::builder) mod common_init;
pub(in crate::mir::builder) mod condition_env_builder;
pub(in crate::mir::builder) mod conversion_pipeline;
pub(in crate::mir::builder) mod exit_binding;
pub(in crate::mir::builder) mod exit_binding_validator; // Phase 222.5-C
pub(in crate::mir::builder) mod exit_binding_constructor; // Phase 222.5-C
pub(in crate::mir::builder) mod exit_binding_applicator; // Phase 222.5-C
pub(in crate::mir::builder) mod exit_binding_applicator; // Phase 222.5-C
pub(in crate::mir::builder) mod exit_binding_constructor; // Phase 222.5-C
pub(in crate::mir::builder) mod exit_binding_validator; // Phase 222.5-C
pub(in crate::mir::builder) mod loop_scope_shape_builder;
pub(in crate::mir::builder) mod pattern_pipeline;
pub(in crate::mir::builder) mod pattern1_minimal;
pub(in crate::mir::builder) mod pattern2_with_break;
pub(in crate::mir::builder) mod pattern3_with_if_phi;
pub(in crate::mir::builder) mod pattern4_carrier_analyzer;
pub(in crate::mir::builder) mod pattern4_with_continue;
pub(in crate::mir::builder) mod pattern_pipeline;
pub(in crate::mir::builder) mod router;
pub(in crate::mir::builder) mod trim_loop_lowering; // Phase 180: Dedicated Trim/P5 lowering module
pub(in crate::mir::builder) mod trim_pattern_validator;
pub(in crate::mir::builder) mod trim_pattern_lowerer;
pub(in crate::mir::builder) mod trim_pattern_validator;
// Re-export router for convenience
pub(in crate::mir::builder) use router::{route_loop_pattern, LoopPatternContext};

View File

@ -5,10 +5,10 @@
//! - Trim break condition generation
//! - Carrier binding setup in ConditionEnv
use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper;
use crate::ast::{ASTNode, Span, UnaryOperator};
use crate::mir::join_ir::lowering::condition_env::ConditionBinding;
use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper;
use crate::mir::ValueId;
use crate::ast::{ASTNode, UnaryOperator, Span};
pub(in crate::mir::builder::control_flow::joinir::patterns) struct TrimPatternLowerer;
@ -61,8 +61,12 @@ impl TrimPatternLowerer {
let carrier_name = &trim_helper.carrier_name;
// Get host ValueId for carrier
let host_value_id = get_host_value(carrier_name)
.ok_or_else(|| format!("[TrimPatternLowerer] Carrier '{}' not in variable_map", carrier_name))?;
let host_value_id = get_host_value(carrier_name).ok_or_else(|| {
format!(
"[TrimPatternLowerer] Carrier '{}' not in variable_map",
carrier_name
)
})?;
// Allocate JoinIR ValueId
let joinir_value_id = alloc_join_value();
@ -94,7 +98,8 @@ impl TrimPatternLowerer {
insert_to_env: impl FnOnce(String, ValueId),
alloc_join_value: &mut dyn FnMut() -> ValueId,
) -> Result<ConditionBinding, String> {
let binding = Self::setup_trim_carrier_binding(trim_helper, get_host_value, alloc_join_value)?;
let binding =
Self::setup_trim_carrier_binding(trim_helper, get_host_value, alloc_join_value)?;
// Insert into env
insert_to_env(binding.name.clone(), binding.join_value);
@ -122,7 +127,9 @@ mod tests {
// Should be UnaryOp::Not
match result {
ASTNode::UnaryOp { operator, operand, .. } => {
ASTNode::UnaryOp {
operator, operand, ..
} => {
assert_eq!(operator, UnaryOperator::Not);
// Operand should be Variable with carrier name
match *operand {
@ -151,11 +158,7 @@ mod tests {
};
let get_value = |name: &str| variable_map.get(name).copied();
let result = TrimPatternLowerer::setup_trim_carrier_binding(
&helper,
get_value,
&mut alloc,
);
let result = TrimPatternLowerer::setup_trim_carrier_binding(&helper, get_value, &mut alloc);
assert!(result.is_ok());
let binding = result.unwrap();
@ -178,11 +181,7 @@ mod tests {
};
let get_value = |name: &str| variable_map.get(name).copied();
let result = TrimPatternLowerer::setup_trim_carrier_binding(
&helper,
get_value,
&mut alloc,
);
let result = TrimPatternLowerer::setup_trim_carrier_binding(&helper, get_value, &mut alloc);
assert!(result.is_err());
assert!(result.unwrap_err().contains("not in variable_map"));
@ -209,12 +208,8 @@ mod tests {
env.insert(name, value);
};
let result = TrimPatternLowerer::add_to_condition_env(
&helper,
get_value,
insert,
&mut alloc,
);
let result =
TrimPatternLowerer::add_to_condition_env(&helper, get_value, insert, &mut alloc);
assert!(result.is_ok());
let binding = result.unwrap();

View File

@ -31,10 +31,10 @@ impl TrimPatternValidator {
ch_value: ValueId,
whitespace_chars: &[String],
) -> Result<ValueId, String> {
use crate::mir::builder::emission::constant::emit_string;
use crate::mir::builder::emission::compare::emit_eq_to;
use crate::mir::types::BinaryOp;
use crate::mir::builder::emission::constant::emit_string;
use crate::mir::instruction::MirInstruction;
use crate::mir::types::BinaryOp;
if whitespace_chars.is_empty() {
return Err("[emit_whitespace_check] Empty whitespace_chars".to_string());
@ -89,18 +89,32 @@ impl TrimPatternValidator {
) -> Option<(String, Box<ASTNode>)> {
for stmt in loop_body {
// Look for: local ch = ...
if let ASTNode::Local { variables, initial_values, .. } = stmt {
if let ASTNode::Local {
variables,
initial_values,
..
} = stmt
{
for (i, var) in variables.iter().enumerate() {
if var == var_name {
if let Some(Some(init_expr_box)) = initial_values.get(i) {
// Check if it's a substring method call
if let ASTNode::MethodCall { object, method, arguments, .. } = init_expr_box.as_ref() {
if let ASTNode::MethodCall {
object,
method,
arguments,
..
} = init_expr_box.as_ref()
{
if method == "substring" && arguments.len() == 2 {
// Extract object name
if let ASTNode::Variable { name, .. } = object.as_ref() {
// Return object name and start expression
// (We assume second arg is start+1, first arg is start)
return Some((name.clone(), Box::new(arguments[0].clone())));
return Some((
name.clone(),
Box::new(arguments[0].clone()),
));
}
}
}
@ -116,45 +130,41 @@ impl TrimPatternValidator {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{LiteralValue, Span, BinaryOperator};
use crate::ast::{BinaryOperator, LiteralValue, Span};
#[test]
fn test_extract_substring_args_valid() {
// Create: local ch = s.substring(start, start+1)
let body = vec![
ASTNode::Local {
variables: vec!["ch".to_string()],
initial_values: vec![
Some(Box::new(ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: "s".to_string(),
let body = vec![ASTNode::Local {
variables: vec!["ch".to_string()],
initial_values: vec![Some(Box::new(ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: "s".to_string(),
span: Span::unknown(),
}),
method: "substring".to_string(),
arguments: vec![
ASTNode::Variable {
name: "start".to_string(),
span: Span::unknown(),
},
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(ASTNode::Variable {
name: "start".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(1),
span: Span::unknown(),
}),
method: "substring".to_string(),
arguments: vec![
ASTNode::Variable {
name: "start".to_string(),
span: Span::unknown(),
},
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(ASTNode::Variable {
name: "start".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(1),
span: Span::unknown(),
}),
span: Span::unknown(),
},
],
span: Span::unknown(),
})),
},
],
span: Span::unknown(),
},
];
}))],
span: Span::unknown(),
}];
let result = TrimPatternValidator::extract_substring_args(&body, "ch");
assert!(result.is_some());
@ -173,32 +183,28 @@ mod tests {
#[test]
fn test_extract_substring_args_wrong_var() {
// local other_var = s.substring(0, 1)
let body = vec![
ASTNode::Local {
variables: vec!["other_var".to_string()],
initial_values: vec![
Some(Box::new(ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: "s".to_string(),
span: Span::unknown(),
}),
method: "substring".to_string(),
arguments: vec![
ASTNode::Literal {
value: LiteralValue::Integer(0),
span: Span::unknown(),
},
ASTNode::Literal {
value: LiteralValue::Integer(1),
span: Span::unknown(),
},
],
let body = vec![ASTNode::Local {
variables: vec!["other_var".to_string()],
initial_values: vec![Some(Box::new(ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: "s".to_string(),
span: Span::unknown(),
}),
method: "substring".to_string(),
arguments: vec![
ASTNode::Literal {
value: LiteralValue::Integer(0),
span: Span::unknown(),
})),
},
ASTNode::Literal {
value: LiteralValue::Integer(1),
span: Span::unknown(),
},
],
span: Span::unknown(),
},
];
}))],
span: Span::unknown(),
}];
let result = TrimPatternValidator::extract_substring_args(&body, "ch");
assert!(result.is_none());
@ -207,28 +213,22 @@ mod tests {
#[test]
fn test_extract_substring_args_wrong_method() {
// local ch = s.charAt(0)
let body = vec![
ASTNode::Local {
variables: vec!["ch".to_string()],
initial_values: vec![
Some(Box::new(ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: "s".to_string(),
span: Span::unknown(),
}),
method: "charAt".to_string(),
arguments: vec![
ASTNode::Literal {
value: LiteralValue::Integer(0),
span: Span::unknown(),
},
],
span: Span::unknown(),
})),
],
let body = vec![ASTNode::Local {
variables: vec!["ch".to_string()],
initial_values: vec![Some(Box::new(ASTNode::MethodCall {
object: Box::new(ASTNode::Variable {
name: "s".to_string(),
span: Span::unknown(),
}),
method: "charAt".to_string(),
arguments: vec![ASTNode::Literal {
value: LiteralValue::Integer(0),
span: Span::unknown(),
}],
span: Span::unknown(),
},
];
}))],
span: Span::unknown(),
}];
let result = TrimPatternValidator::extract_substring_args(&body, "ch");
assert!(result.is_none());