Implement actual MIR generation for Trim pattern loops, enabling LoopBodyLocal variables to work through bool carrier promotion. ## Implementation (pattern2_with_break.rs) - emit_whitespace_check(): Generate OR chain for whitespace comparison - extract_substring_args(): Extract s, start from substring call - Initial carrier generation: ch0 = s.substring(start, start+1) - Whitespace check: is_ch_match0 = (ch0 == " " || "\t" || "\n" || "\r") - ConditionEnv integration: Register carrier for JoinIR condition - Break condition replacement: !is_ch_match instead of original ch checks ## Architecture - Host MIR: substring calls, OR chain evaluation, BoxCall - JoinIR: Only sees bool carrier for break control - No new JoinIR instructions added ## Documentation - phase172-trim-lowering-impl.md: Design and implementation details - loop_pattern_space.md: Analysis of all loop pattern combinations Test: Trim loops compile and generate JoinIR successfully Build: 0 errors, clean compilation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
10 KiB
Phase 172: Trim Pattern JoinIR Lowering Implementation
Objective
Implement actual JoinIR → MIR lowering for Trim pattern loops, making test_trim_main_pattern.hako work end-to-end.
Target Loop
File: local_tests/test_trim_main_pattern.hako
Leading whitespace trim loop:
loop(start < end) {
local ch = s.substring(start, start+1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
start = start + 1
} else {
break
}
}
Using Boxes from Phase 171
Phase 171 Infrastructure
- ✅ LoopConditionScopeBox: Detects
chas LoopBodyLocal - ✅ LoopBodyCarrierPromoter: Promotes
chto bool carrieris_ch_match - ✅ TrimPatternInfo: Contains promotion metadata
- ✅ TrimLoopHelper: Attached to CarrierInfo for lowering guidance
- ✅ Pattern2 validation: Currently returns informative error at line 234-239
Design Philosophy
Responsibility Separation
Host MIR Side (before/after JoinIR):
- Execute
substring()method calls - Evaluate OR chain comparisons (
ch == " " || ch == "\t" || ...) - Generate bool result for carrier
- BoxCall operations
JoinIR Side (pure control flow):
- bool carrier-based loop control
- break condition:
!is_ch_match - No knowledge of substring/OR chain
- No new JoinIR instructions
Key Insight
The Trim pattern transformation:
Original:
local ch = s.substring(start, start+1)
if (ch == " " || ch == "\t" || ...) { start++ } else { break }
Transformed:
is_ch_match = (s.substring(start, start+1) == " " || ...) # Host MIR
if (!is_ch_match) { break } # JoinIR
Implementation Steps
Step 1: Loop Pre-Initialization (Task 172-2)
Location: pattern2_with_break.rs, before JoinIR generation
Goal: Initialize bool carrier before entering loop
Implementation:
// After Line 264 (after carrier_info.merge_from)
if let Some(helper) = carrier_info.trim_helper() {
if helper.is_safe_trim() {
// Generate initial whitespace check
// ch0 = s.substring(start, start+1)
let ch0 = self.emit_method_call("substring", s_id, vec![start_id, start_plus_1_id])?;
// is_ch_match0 = (ch0 == " " || ch0 == "\t" || ...)
let is_ch_match0 = emit_whitespace_check(self, ch0, &helper.whitespace_chars)?;
// Update carrier_info initial value
// (Will be used in JoinIR input mapping)
}
}
Helper Function (new):
fn emit_whitespace_check(
builder: &mut MirBuilder,
ch_value: ValueId,
whitespace_chars: &[String],
) -> Result<ValueId, String> {
// Build OR chain: ch == " " || ch == "\t" || ...
let mut result = None;
for ws_char in whitespace_chars {
let ws_const = emit_string(builder, ws_char.clone());
let eq_check = emit_eq_to(builder, ch_value, ws_const)?;
result = Some(if let Some(prev) = result {
// prev || eq_check
emit_binary_op(builder, BinaryOp::Or, prev, eq_check)?
} else {
eq_check
});
}
result.ok_or("Empty whitespace_chars".to_string())
}
Step 2: Loop Body Update Logic (Task 172-3)
Location: Pattern2 lowerer's latch generation (after start = start + 1)
Goal: Update bool carrier for next iteration
Implementation:
// In latch block generation (conceptually after line 267+)
if let Some(helper) = carrier_info.trim_helper() {
if helper.is_safe_trim() {
// ch_next = s.substring(start_next, start_next+1)
let ch_next = self.emit_method_call("substring", s_id, vec![start_next, start_next_plus_1])?;
// is_ch_match_next = (ch_next == " " || ...)
let is_ch_match_next = emit_whitespace_check(self, ch_next, &helper.whitespace_chars)?;
// Store for JoinIR latch → header PHI
// (Will be used in carrier update mapping)
}
}
Note: The actual MIR emission happens in host space, not JoinIR space. The JoinIR only sees the bool carrier as a loop parameter.
Step 3: JoinIR break Condition Replacement (Task 172-4)
Location: lower_loop_with_break_minimal call at line 267
Goal: Replace break condition with !is_ch_match
Current Code:
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(
scope,
condition, // loop condition: start < end
&break_condition_node, // if condition: ch == " " || ...
&env,
&loop_var_name
) { ... }
Modified Approach:
// Phase 172: Trim pattern special route
if let Some(helper) = carrier_info.trim_helper() {
if helper.is_safe_trim() {
// Create negated carrier check: !is_ch_match
let carrier_var_node = ASTNode::Variable {
name: helper.carrier_name.clone(), // "is_ch_match"
span: Span::unknown(),
};
let negated_carrier_check = ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand: Box::new(carrier_var_node),
span: Span::unknown(),
};
// Use negated carrier as break condition
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(
scope,
condition, // start < end (unchanged)
&negated_carrier_check, // !is_ch_match (REPLACED)
&env,
&loop_var_name
) { ... }
// Continue with normal merge flow...
}
}
Critical Design Decision:
- The break condition AST is replaced, but the loop condition (
start < end) remains unchanged - JoinIR sees bool carrier as just another loop parameter
- No new JoinIR instructions needed!
Step 4: Carrier Mapping Integration
Location: Boundary setup (line 283-291)
Goal: Ensure bool carrier is properly mapped between host and JoinIR
Current boundary setup:
let mut boundary = JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR: loop param
vec![loop_var_id], // Host: "start"
);
boundary.condition_bindings = condition_bindings;
boundary.exit_bindings = exit_bindings.clone();
Enhanced with Trim carrier:
// Phase 172: Add bool carrier to condition_bindings
if let Some(helper) = carrier_info.trim_helper() {
let carrier_host_id = self.variable_map.get(&helper.carrier_name)
.copied()
.ok_or_else(|| format!("Carrier '{}' not in variable_map", helper.carrier_name))?;
let carrier_join_id = alloc_join_value(); // Allocate JoinIR-local ID
env.insert(helper.carrier_name.clone(), carrier_join_id);
condition_bindings.push(ConditionBinding {
name: helper.carrier_name.clone(),
host_value: carrier_host_id,
join_value: carrier_join_id,
});
}
Implementation Constraints
What We DON'T Change
- ExitLine Architecture: No changes to ExitLineReconnector/ExitMetaCollector
- Header PHI Generation: Existing LoopHeaderPhiBuilder handles bool carrier automatically
- JoinIR Instruction Set: No new instructions added
- Pattern2 Basic Logic: Core control flow unchanged
What We DO Change
- Pre-loop initialization: Add bool carrier setup
- Latch update logic: Add bool carrier update
- Break condition AST: Replace with
!is_ch_match - Condition bindings: Add bool carrier mapping
Testing Strategy
E2E Test
Command:
./target/release/hakorune local_tests/test_trim_main_pattern.hako
Expected Behavior:
- Before: Error message "✅ Trim pattern validation successful! ... JoinIR lowering: TODO"
- After:
Result: [hello]andPASS: Trimmed correctly
Debug Traces
With JoinIR debug:
NYASH_JOINIR_DEBUG=1 ./target/release/hakorune local_tests/test_trim_main_pattern.hako 2>&1 | grep -E "\[pattern2\]|\[trim\]"
Expected log patterns:
[pattern2/promotion] LoopBodyLocal detected in condition scope
[pattern2/promoter] LoopBodyLocal 'ch' promoted to carrier 'is_ch_match'
[pattern2/trim] Safe Trim pattern detected, bypassing LoopBodyLocal restriction
[pattern2/trim] Carrier: 'is_ch_match', original var: 'ch', whitespace chars: [" ", "\t", "\n", "\r"]
[pattern2/trim] Emitting whitespace check initialization
[pattern2/trim] Replacing break condition with !is_ch_match
MIR Validation
Dump MIR:
./target/release/hakorune --dump-mir local_tests/test_trim_main_pattern.hako 2>&1 | less
Check for:
- Pre-loop:
substring()call + OR chain comparison - Loop header: PHI for
is_ch_match - Loop body: Conditional branch on
!is_ch_match - Loop latch:
substring()call + OR chain update - Exit block: Proper carrier value propagation
Success Criteria
- ✅
test_trim_main_pattern.hakoexecutes without errors - ✅ Output:
Result: [hello](correctly trimmed) - ✅ Test result:
PASS: Trimmed correctly - ✅
cargo build --releasesucceeds with 0 errors - ✅
cargo testall tests pass - ✅ No new warnings introduced
- ✅ MIR dump shows proper SSA form
Implementation Status
- Task 172-1: Design document (THIS FILE)
- Task 172-2: Loop pre-initialization
- Task 172-3: Loop body update logic
- Task 172-4: JoinIR break condition replacement
- Task 172-5: E2E test validation
- Task 172-6: Documentation update
Future Work (Out of Scope for Phase 172)
Pattern 4 Support
- Apply same Trim lowering to Pattern 4 (loop with continue)
- Reuse
emit_whitespace_check()helper
JsonParser Integration
- Apply to all trim loops in JsonParser._trim
- Validate with JSON parsing smoke tests
P6+ Patterns
- Complex control flow with Trim-like patterns
- Multi-carrier bool promotion
Notes
Why This Design Works
- Minimal Invasiveness: Changes only affect Trim-specific paths
- Reusability:
emit_whitespace_check()can be shared with Pattern 4 - Maintainability: Clear separation between host MIR and JoinIR concerns
- Testability: Each step can be validated independently
Phase 171 Validation Hook
Current code (line 234-239) already validates Trim pattern and returns early:
return Err(format!(
"[cf_loop/pattern2] ✅ Trim pattern validation successful! \
Carrier '{}' ready for Phase 172 implementation. \
(Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO)",
helper.carrier_name
));
Phase 172 will replace this early return with actual lowering logic.
References
- Phase 171-C: LoopBodyCarrierPromoter implementation
- Phase 171-C-5: TrimLoopHelper design
- Pattern2 Lowerer:
src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs - Test File:
local_tests/test_trim_main_pattern.hako