refactor(joinir): Pattern 4 modularization with CarrierInfo/ExitMeta

Removes hardcoded "sum" and ValueId(15) from Pattern 4 lowerer by
introducing CarrierInfo and ExitMeta structures.

Changes:
- New carrier_info.rs: CarrierInfo, CarrierVar, ExitMeta structs
- loop_with_continue_minimal.rs: Returns (JoinModule, ExitMeta)
- pattern4_with_continue.rs: Dynamic binding generation from metadata

Design approach: "Thin meta on existing boxes" (ChatGPT proposal)
- CarrierInfo: Built from variable_map, not AST re-analysis
- ExitMeta: Carrier name + JoinIR ValueId pairs from lowerer
- LoopExitBinding: Auto-generated from CarrierInfo + ExitMeta

Test: loop_continue_pattern4.hako outputs 25 (unchanged)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-06 02:05:19 +09:00
parent 318dceebfe
commit 60bd5487e6
4 changed files with 146 additions and 23 deletions

View File

@ -100,6 +100,7 @@ impl MirBuilder {
_func_name: &str, _func_name: &str,
debug: bool, debug: bool,
) -> Result<Option<ValueId>, String> { ) -> Result<Option<ValueId>, String> {
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierVar};
use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal; use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal;
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
use crate::mir::BasicBlockId; use crate::mir::BasicBlockId;
@ -121,14 +122,28 @@ impl MirBuilder {
) )
})?; })?;
// Get sum variable from variable_map // Phase 196: Build CarrierInfo from variable_map
let sum_var_id = self // Collect all non-loop-var variables as potential carriers
.variable_map let mut carriers = Vec::new();
.get("sum") for (var_name, &var_id) in &self.variable_map {
.copied() if var_name != &loop_var_name {
.ok_or_else(|| { carriers.push(CarrierVar {
format!("[cf_loop/pattern4] Sum variable 'sum' not found in variable_map") name: var_name.clone(),
})?; host_id: var_id,
});
}
}
let carrier_info = CarrierInfo {
loop_var_name: loop_var_name.clone(),
loop_var_id,
carriers: carriers.clone(),
};
eprintln!("[pattern4] CarrierInfo: loop_var={}, carriers={:?}",
carrier_info.loop_var_name,
carrier_info.carriers.iter().map(|c| &c.name).collect::<Vec<_>>()
);
// Phase 195: Use unified trace // Phase 195: Use unified trace
trace::trace().varmap("pattern4_start", &self.variable_map); trace::trace().varmap("pattern4_start", &self.variable_map);
@ -150,8 +165,8 @@ impl MirBuilder {
}; };
// Call Pattern 4 lowerer // Call Pattern 4 lowerer
let join_module = match lower_loop_with_continue_minimal(scope) { let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope) {
Some(module) => module, Some(result) => result,
None => { None => {
// Phase 195: Use unified trace // Phase 195: Use unified trace
trace::trace().debug("pattern4", "Pattern 4 lowerer returned None"); trace::trace().debug("pattern4", "Pattern 4 lowerer returned None");
@ -159,6 +174,11 @@ impl MirBuilder {
} }
}; };
eprintln!("[pattern4] ExitMeta: {} exit bindings", exit_meta.exit_values.len());
for (carrier_name, join_value) in &exit_meta.exit_values {
eprintln!("[pattern4] {} → ValueId({})", carrier_name, join_value.0);
}
// Phase 195: Use unified trace // Phase 195: Use unified trace
trace::trace().joinir_stats( trace::trace().joinir_stats(
"pattern4", "pattern4",
@ -181,19 +201,49 @@ impl MirBuilder {
mir_module.functions.values().map(|f| f.blocks.len()).sum(), mir_module.functions.values().map(|f| f.blocks.len()).sum(),
); );
// Phase 196: Dynamically generate exit_bindings from ExitMeta and CarrierInfo
let mut exit_bindings = Vec::new();
for (carrier_name, join_exit_value) in &exit_meta.exit_values {
// Find the host_slot for this carrier
let host_slot = carrier_info
.carriers
.iter()
.find(|c| &c.name == carrier_name)
.map(|c| c.host_id)
.ok_or_else(|| {
format!(
"[cf_loop/pattern4] Carrier '{}' not found in CarrierInfo",
carrier_name
)
})?;
exit_bindings.push(
crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding {
carrier_name: carrier_name.clone(),
join_exit_value: *join_exit_value,
host_slot,
},
);
eprintln!("[pattern4] Generated binding: {} → JoinIR={} Host={}",
carrier_name, join_exit_value.0, host_slot.0);
}
// Phase 196: Build host_inputs dynamically
// Order: [loop_var, carrier1, carrier2, ...]
let mut host_inputs = vec![carrier_info.loop_var_id];
for carrier in &carrier_info.carriers {
host_inputs.push(carrier.host_id);
}
eprintln!("[pattern4] host_inputs: {:?}", host_inputs.iter().map(|v| v.0).collect::<Vec<_>>());
// Merge JoinIR blocks into current function // Merge JoinIR blocks into current function
// Phase 195 FIX: Use exit_bindings to connect k_exit's sum_exit to host's sum variable // Phase 196: Use dynamically generated exit_bindings and host_inputs
// Pattern 4: k_exit(sum_exit) returns sum_exit, so we bind ValueId(15) to host's sum
let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_exit_bindings( let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_exit_bindings(
vec![ValueId(0), ValueId(1)], // JoinIR's main() parameters (i_init, sum_init) vec![ValueId(0), ValueId(1)], // JoinIR's main() parameters (i_init, sum_init)
vec![loop_var_id, sum_var_id], // Host's loop variables host_inputs, // Host's loop variables (dynamic)
vec![ exit_bindings,
crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding {
carrier_name: "sum".to_string(),
join_exit_value: ValueId(15), // k_exit's parameter (sum_exit)
host_slot: sum_var_id, // variable_map["sum"]
}
],
); );
// Phase 195: Capture exit PHI result (Pattern 4 returns sum) // Phase 195: Capture exit PHI result (Pattern 4 returns sum)
let result_val = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?; let result_val = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;

View File

@ -0,0 +1,64 @@
//! Carrier variable metadata for JoinIR loop lowering
//!
//! This module defines metadata structures for tracking carrier variables
//! in loop lowering. This enables dynamic generation of exit bindings
//! without hardcoded variable names or ValueIds.
use crate::mir::ValueId;
/// Information about a single carrier variable
#[derive(Debug, Clone)]
pub struct CarrierVar {
/// Variable name (e.g., "sum", "printed")
pub name: String,
/// Host ValueId for this variable
pub host_id: ValueId,
}
/// Complete carrier information for a loop
#[derive(Debug, Clone)]
pub struct CarrierInfo {
/// Loop control variable name (e.g., "i")
pub loop_var_name: String,
/// Loop control variable ValueId in host
pub loop_var_id: ValueId,
/// Additional carrier variables (e.g., sum, printed)
pub carriers: Vec<CarrierVar>,
}
/// Exit metadata returned by lowerers
///
/// This structure captures the mapping from JoinIR exit values to
/// carrier variable names, enabling dynamic binding generation.
#[derive(Debug, Clone)]
pub struct ExitMeta {
/// Exit value bindings: (carrier_name, join_exit_value_id)
///
/// Example for Pattern 4:
/// ```
/// vec![("sum".to_string(), ValueId(15))]
/// ```
/// where ValueId(15) is the k_exit parameter in JoinIR-local space.
pub exit_values: Vec<(String, ValueId)>,
}
impl ExitMeta {
/// Create new ExitMeta with no exit values
pub fn empty() -> Self {
Self {
exit_values: vec![],
}
}
/// Create ExitMeta with a single exit value
pub fn single(carrier_name: String, join_value: ValueId) -> Self {
Self {
exit_values: vec![(carrier_name, join_value)],
}
}
/// Create ExitMeta with multiple exit values
pub fn multiple(exit_values: Vec<(String, ValueId)>) -> Self {
Self { exit_values }
}
}

View File

@ -68,6 +68,7 @@
//! //!
//! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later. //! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later.
use crate::mir::join_ir::lowering::carrier_info::ExitMeta;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::{ use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
@ -101,7 +102,7 @@ use crate::mir::ValueId;
/// ///
/// # Returns /// # Returns
/// ///
/// * `Some(JoinModule)` - Successfully lowered to JoinIR /// * `Some((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata
/// * `None` - Pattern not matched (fallback to other lowerers) /// * `None` - Pattern not matched (fallback to other lowerers)
/// ///
/// # Boundary Contract /// # Boundary Contract
@ -109,8 +110,9 @@ use crate::mir::ValueId;
/// This function returns a JoinModule with: /// This function returns a JoinModule with:
/// - **Input slots**: ValueId(0) = i_init, ValueId(1) = sum_init /// - **Input slots**: ValueId(0) = i_init, ValueId(1) = sum_init
/// - **Output slot**: k_exit returns the final sum value /// - **Output slot**: k_exit returns the final sum value
/// - **Exit metadata**: ExitMeta containing ("sum", ValueId(15)) binding
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds /// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds
pub fn lower_loop_with_continue_minimal(_scope: LoopScopeShape) -> Option<JoinModule> { pub fn lower_loop_with_continue_minimal(_scope: LoopScopeShape) -> Option<(JoinModule, ExitMeta)> {
// Phase 195: Use local ValueId allocator (sequential from 0) // Phase 195: Use local ValueId allocator (sequential from 0)
// JoinIR has NO knowledge of host ValueIds - boundary handled separately // JoinIR has NO knowledge of host ValueIds - boundary handled separately
let mut value_counter = 0u32; let mut value_counter = 0u32;
@ -345,5 +347,11 @@ pub fn lower_loop_with_continue_minimal(_scope: LoopScopeShape) -> Option<JoinMo
eprintln!("[joinir/pattern4] Continue: Jump(loop_step) to skip iteration"); eprintln!("[joinir/pattern4] Continue: Jump(loop_step) to skip iteration");
eprintln!("[joinir/pattern4] Carriers: i, sum"); eprintln!("[joinir/pattern4] Carriers: i, sum");
Some(join_module) // Phase 196: Return ExitMeta with carrier bindings
// k_exit parameter sum_exit (ValueId(15)) should be bound to host's "sum" variable
let exit_meta = ExitMeta::single("sum".to_string(), sum_exit);
eprintln!("[joinir/pattern4] ExitMeta: sum → ValueId({})", sum_exit.0);
Some((join_module, exit_meta))
} }

View File

@ -15,6 +15,7 @@
//! - `if_select.rs`: Phase 33 If/Else → Select lowering //! - `if_select.rs`: Phase 33 If/Else → Select lowering
//! - `if_dry_runner.rs`: Phase 33-10 If lowering dry-run スキャナー(箱化版) //! - `if_dry_runner.rs`: Phase 33-10 If lowering dry-run スキャナー(箱化版)
pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering
pub mod common; pub mod common;
pub mod exit_args_resolver; pub mod exit_args_resolver;
pub mod funcscanner_append_defs; pub mod funcscanner_append_defs;