feat(joinir): Phase 188 JoinInlineBoundary + Pattern 1 working! 🎉
Major milestone: loop_min_while.hako outputs "0 1 2" correctly! ## JoinInlineBoundary (Option D from ChatGPT Pro design review) - New struct for clean SSA boundary between JoinIR and host function - JoinIR uses local ValueIds (0,1,2...) - no host ValueId dependency - Copy injection at entry block connects host → JoinIR values ## Pattern 1 Simple While Loop - Refactored to use pure local ValueIds - Removed Pattern1Context dependency on host ValueIds - Clean separation: lowerer generates, merger connects ## Key Design Principles (Box Theory) - Box A: JoinIR Frontend (host-agnostic) - Box B: Join→MIR Bridge (independent functions) - Box C: JoinInlineBoundary (boundary info only) - Box D: JoinMirInlineMerger (Copy injection) ## Files Changed - NEW: inline_boundary.rs - JoinInlineBoundary struct - control_flow.rs - merge with boundary, void return fix - simple_while_minimal.rs - pure local ValueIds - mod.rs - module export Test: NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune apps/tests/loop_min_while.hako Output: 0\n1\n2 ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -50,17 +50,21 @@ use crate::mir::join_ir::{
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Context passed from the host function to the Pattern 1 lowerer
|
||||
///
|
||||
/// Phase 188-Impl-3: This context is now REMOVED - JoinIR uses only local ValueIds
|
||||
/// The boundary mapping is handled by JoinInlineBoundary instead.
|
||||
#[deprecated(since = "188-Impl-3", note = "Use JoinInlineBoundary for host integration")]
|
||||
pub struct Pattern1Context {
|
||||
/// The loop variable ValueId from the host function (e.g., ValueId(6) for `i`)
|
||||
/// DEPRECATED: This is no longer used, JoinIR uses local ValueIds
|
||||
pub loop_var: ValueId,
|
||||
/// ValueId allocator function
|
||||
/// DEPRECATED: JoinIR allocates sequentially from 0
|
||||
pub value_allocator: Box<dyn FnMut() -> ValueId>,
|
||||
}
|
||||
|
||||
impl Pattern1Context {
|
||||
/// Create a standalone context with hardcoded ValueIds (for backward compatibility)
|
||||
pub fn standalone() -> Self {
|
||||
let mut counter = 1000u32;
|
||||
let mut counter = 0u32;
|
||||
Self {
|
||||
loop_var: ValueId(counter),
|
||||
value_allocator: Box::new(move || {
|
||||
@ -73,34 +77,51 @@ impl Pattern1Context {
|
||||
|
||||
/// Lower Pattern 1 (Simple While Loop) to JoinIR
|
||||
///
|
||||
/// This is a minimal implementation for loop_min_while.hako.
|
||||
/// It generates JoinIR that integrates with the host function's variable bindings.
|
||||
/// # Phase 188-Impl-3: Pure JoinIR Fragment Generation
|
||||
///
|
||||
/// # Phase 188-Impl-2: Host Variable Integration
|
||||
/// This version generates JoinIR using **local ValueIds only** (0, 1, 2, ...).
|
||||
/// It has NO knowledge of the host function's ValueId space. The boundary mapping
|
||||
/// is handled separately via JoinInlineBoundary.
|
||||
///
|
||||
/// This version accepts the host's loop variable ValueId and allocates fresh IDs
|
||||
/// for intermediate values. This ensures the generated JoinIR connects properly
|
||||
/// to the host function's variable bindings.
|
||||
/// ## Design Philosophy
|
||||
///
|
||||
/// If called without a context (from legacy code), it uses standalone mode with
|
||||
/// hardcoded ValueIds for backward compatibility.
|
||||
/// - **Box A**: JoinIR Frontend (doesn't know about host ValueIds)
|
||||
/// - **Box B**: This function - converts to JoinIR with local IDs
|
||||
/// - **Box C**: JoinInlineBoundary - stores boundary info
|
||||
/// - **Box D**: merge_joinir_mir_blocks - injects Copy instructions
|
||||
///
|
||||
/// This clean separation ensures JoinIR lowerers are:
|
||||
/// - Pure transformers (no side effects)
|
||||
/// - Reusable (same lowerer works in any context)
|
||||
/// - Testable (can test JoinIR independently)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_scope` - LoopScopeShape (reserved for future generic implementation)
|
||||
/// * `ctx` - Pattern1Context containing host variable bindings (or None for standalone)
|
||||
/// * `_ctx` - DEPRECATED: No longer used, kept for backward compatibility
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Some(JoinModule)` - Successfully lowered to JoinIR
|
||||
/// * `None` - Pattern not matched (fallback to other lowerers)
|
||||
///
|
||||
/// # Boundary Contract
|
||||
///
|
||||
/// This function returns a JoinModule with:
|
||||
/// - **Input slot**: ValueId(0) in loop_step function represents the loop variable
|
||||
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueId(0) to host's loop var
|
||||
pub fn lower_simple_while_minimal(
|
||||
_scope: LoopScopeShape,
|
||||
ctx: Option<Pattern1Context>,
|
||||
_ctx: Option<Pattern1Context>,
|
||||
) -> Option<JoinModule> {
|
||||
let mut ctx = ctx.unwrap_or_else(Pattern1Context::standalone);
|
||||
// Phase 188-Impl-1: Hardcoded JoinIR for loop_min_while.hako
|
||||
// This establishes the infrastructure. Generic implementation in Phase 188-Impl-2+.
|
||||
// Phase 188-Impl-3: Use local ValueId allocator (sequential from 0)
|
||||
// JoinIR has NO knowledge of host ValueIds - boundary handled separately
|
||||
let mut value_counter = 0u32;
|
||||
let mut alloc_value = || {
|
||||
let id = ValueId(value_counter);
|
||||
value_counter += 1;
|
||||
id
|
||||
};
|
||||
|
||||
let mut join_module = JoinModule::new();
|
||||
|
||||
@ -112,32 +133,32 @@ pub fn lower_simple_while_minimal(
|
||||
let k_exit_id = JoinFuncId::new(2);
|
||||
|
||||
// ==================================================================
|
||||
// ValueId allocation (Phase 188-Impl-2: Use host variable + allocator)
|
||||
// ValueId allocation (Phase 188-Impl-3: Sequential local IDs)
|
||||
// ==================================================================
|
||||
// Host's loop variable (e.g., ValueId(6) for `i`)
|
||||
let i_init = ctx.loop_var;
|
||||
|
||||
// Allocate fresh IDs for local values
|
||||
let loop_result = (ctx.value_allocator)();
|
||||
let const_0_main = (ctx.value_allocator)();
|
||||
// main() locals
|
||||
let i_init = alloc_value(); // ValueId(0) - loop init value
|
||||
let loop_result = alloc_value(); // ValueId(1) - result from loop_step
|
||||
let const_0_main = alloc_value(); // ValueId(2) - return value
|
||||
|
||||
// loop_step locals
|
||||
let i_param = (ctx.value_allocator)();
|
||||
let const_3 = (ctx.value_allocator)();
|
||||
let cmp_lt = (ctx.value_allocator)();
|
||||
let exit_cond = (ctx.value_allocator)();
|
||||
let const_1 = (ctx.value_allocator)();
|
||||
let i_next = (ctx.value_allocator)();
|
||||
let i_param = alloc_value(); // ValueId(3) - parameter
|
||||
let const_3 = alloc_value(); // ValueId(4) - comparison constant
|
||||
let cmp_lt = alloc_value(); // ValueId(5) - i < 3
|
||||
let exit_cond = alloc_value(); // ValueId(6) - !(i < 3)
|
||||
let const_1 = alloc_value(); // ValueId(7) - increment constant
|
||||
let i_next = alloc_value(); // ValueId(8) - i + 1
|
||||
|
||||
// k_exit locals
|
||||
let const_0_exit = alloc_value(); // ValueId(9) - exit return value
|
||||
|
||||
// ==================================================================
|
||||
// main() function
|
||||
// ==================================================================
|
||||
let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![]);
|
||||
// Phase 188-Impl-3: main() takes i as a parameter (boundary input)
|
||||
// The host will inject a Copy instruction: i_init_local = Copy host_i
|
||||
let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![i_init]);
|
||||
|
||||
// Phase 188-Impl-2: Skip i_init = 0 (host already initialized the variable)
|
||||
// The host's ValueId (i_init) is already bound to 0 in the host function
|
||||
|
||||
// result = loop_step(i_init) ← Use host's i directly
|
||||
// result = loop_step(i_init)
|
||||
main_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_init],
|
||||
@ -244,7 +265,7 @@ pub fn lower_simple_while_minimal(
|
||||
let mut k_exit_func = JoinFunction::new(k_exit_id, "k_exit".to_string(), vec![]);
|
||||
|
||||
// return 0 (Pattern 1 has no exit values)
|
||||
let const_0_exit = ValueId(3000);
|
||||
// Phase 188-Impl-3: Use pre-allocated const_0_exit (ValueId(9))
|
||||
k_exit_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_0_exit,
|
||||
value: ConstValue::Integer(0),
|
||||
|
||||
Reference in New Issue
Block a user