Files
hakorune/docs/development/current/main/phase175-multicarrier-design.md
nyash-codex 99d329096f feat(joinir): Phase 176 Pattern2 multi-carrier lowering complete
Task 176-1: Pattern2 limitation investigation
- Identified 10 limitation points where only position carrier was handled
- Added TODO markers for Phase 176-2/3 implementation
- Created phase176-pattern2-limitations.md documentation

Task 176-2: CarrierUpdateLowerer helper implementation
- Implemented emit_carrier_update() helper function
- Supports CounterLike and AccumulationLike UpdateExpr patterns
- Added 6 unit tests (all passing)
- Fail-Fast error handling for carrier/variable not found

Task 176-3: Pattern2 lowerer multi-carrier extension
- Extended header PHI generation for all carriers
- Implemented loop update for all carriers using emit_carrier_update()
- Extended ExitLine/ExitMeta construction for all carriers
- Updated function call/jump args to include all carriers
- 9/10 tests passing (1 pre-existing test issue)

Task 176-4: E2E testing and bug fixes
- Fixed Trim pattern loop_var_name overwrite bug (pattern2_with_break.rs)
- Fixed InstructionRewriter latch_incoming mapping bug
- All E2E tests passing (RC=0): pos + result dual-carrier loops work
- test_jsonparser_parse_string_min2.hako verified

Task 176-5: Documentation updates
- Created phase176-completion-report.md
- Updated phase175-multicarrier-design.md with completion status
- Updated joinir-architecture-overview.md roadmap
- Updated CURRENT_TASK.md with Phase 176 completion + Phase 177 TODO
- Updated loop_pattern_space.md F-axis (multi-carrier support complete)

Technical achievements:
- Pattern2 now handles single/multiple carriers uniformly
- CarrierInfo architecture proven to work end-to-end
- Two critical bugs fixed (loop_var overwrite, latch_incoming mapping)
- No regressions in existing tests

Next: Phase 177 - Apply to JsonParser _parse_string full implementation
2025-12-08 15:17:53 +09:00

9.2 KiB
Raw Blame History

Phase 175: P5 Multiple Carrier Support Design

Date: 2025-12-08 Purpose: Extend P5 pipeline to support multiple carriers for complex loops like _parse_string


1. Target: _parse_string Carrier Analysis

1.1 Loop Structure (lines 150-178)

_parse_string(s, pos) {
    if s.substring(pos, pos+1) != '"' { return null }

    local p = pos + 1      // Carrier 1: position index
    local str = ""         // Carrier 2: result buffer

    loop(p < s.length()) {
        local ch = s.substring(p, p+1)  // LoopBodyLocal (promotion candidate)

        if ch == '"' {
            // End of string
            local result = new MapBox()
            result.set("value", me._unescape_string(str))  // Uses str carrier
            result.set("pos", p + 1)                        // Uses p carrier
            result.set("type", "string")
            return result
        }

        if ch == "\\" {
            // Escape sequence (Phase 176 scope)
            local has_next = 0
            if p + 1 < s.length() { has_next = 1 }
            if has_next == 0 { return null }

            str = str + ch
            p = p + 1
            str = str + s.substring(p, p+1)
            p = p + 1
            continue  // ⚠️ Phase 176 scope
        }

        str = str + ch  // Update carrier 2
        p = p + 1       // Update carrier 1
    }

    return null
}

1.2 Carrier Candidates Table

Variable Type Update Pattern Exit Usage Carrier Status
p IntegerBox p = p + 1 Position in result.set("pos", p + 1) Required Carrier
str StringBox str = str + ch String buffer in result.set("value", me._unescape_string(str)) Required Carrier
ch StringBox local ch = s.substring(p, p+1) Loop body comparison LoopBodyLocal (promotion target)
has_next IntegerBox local has_next = 0 Escape processing guard Loop body only (Phase 176)

1.3 Carrier Classification

Required Carriers (Exit-dependent):

  1. p: Position index - final value used in result.set("pos", p + 1)
  2. str: Result buffer - final value used in result.set("value", me._unescape_string(str))

Promoted Carriers (P5 mechanism): 3. is_ch_match: Bool carrier promoted from ch (Trim pattern detection)

  • Pattern: ch = s.substring(p, p+1)ch == "\"" equality chain
  • Promotion: LoopBodyCarrierPromoter converts to bool carrier

Loop-Internal Only (No carrier needed):

  • ch: LoopBodyLocal, promotion target → becomes is_ch_match carrier
  • has_next: Escape sequence guard (Phase 176 scope)

2. Phase 175 Minimal PoC Scope

Goal: Prove multi-carrier support with 2 carriers (p + str), excluding escape handling

2.1 Minimal PoC Structure

_parse_string_min2() {
    me.s = "hello world\""
    me.pos = 0
    me.len = me.s.length()
    me.result = ""

    // 2-carrier version: p + result updated together
    loop(me.pos < me.len) {
        local ch = me.s.substring(me.pos, me.pos+1)
        if ch == "\"" {
            break
        } else {
            me.result = me.result + ch  // Carrier 2 update
            me.pos = me.pos + 1         // Carrier 1 update
        }
    }

    // Exit: both pos and result are used
    print("Parsed string: ")
    print(me.result)
    print(", final pos: ")
    print(me.pos)
}

Carrier Count: 2 (pos, result) + 1 promoted (is_ch_match) = 3 total

Excluded from Phase 175:

  • Escape sequence handling (\\", continue path)
  • Complex nested conditionals
  • Focus: Simple char accumulation + position increment

3. P5 Multi-Carrier Architecture

3.1 Existing Boxes Already Support Multi-Carrier

3.1.1 LoopUpdateAnalyzer (Multi-carrier ready )

File: src/mir/join_ir/lowering/loop_update_analyzer.rs API: identify_updated_carriers(body, all_carriers) -> CarrierInfo Multi-carrier support: Loops over all_carriers.carriers

Code:

pub fn identify_updated_carriers(
    body: &[ASTNode],
    all_carriers: &CarrierInfo,
) -> Result<CarrierInfo, String> {
    let mut updated = CarrierInfo::new();
    for carrier in &all_carriers.carriers {  // ✅ Multi-carrier loop
        if is_updated_in_body(body, &carrier.name) {
            updated.add_carrier(carrier.clone());
        }
    }
    Ok(updated)
}

3.1.2 LoopBodyCarrierPromoter (Adds carriers )

File: src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs API: try_promote(request) -> PromotionResult Multi-carrier support: Generates additional carriers from promotion

Behavior:

let promoted = LoopBodyCarrierPromoter::try_promote(&request)?;
carrier_info.merge_from(&promoted.to_carrier_info());  // Add promoted carrier
// Result: carrier_info.carriers = [pos, result, is_ch_match]

3.1.3 CarrierInfo (Multi-carrier container )

File: src/mir/join_ir/lowering/carrier_info.rs API: carriers: Vec<CarrierData>, merge_from(&other) Multi-carrier support: Vec holds arbitrary number of carriers

Phase 175-3 Usage:

let mut carrier_info = CarrierInfo::new();
carrier_info.add_carrier(CarrierData {  // Carrier 1
    name: "pos".to_string(),
    update_expr: UpdateExpr::Simple { ... },
});
carrier_info.add_carrier(CarrierData {  // Carrier 2
    name: "result".to_string(),
    update_expr: UpdateExpr::Simple { ... },
});
// carrier_info.carriers.len() == 2 ✅

3.1.4 ExitMeta / ExitBinding (Multi-carrier ready )

File: src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs API: carrier_exits: Vec<(String, ValueId)> Multi-carrier support: Vec holds all carrier exits

ExitMetaCollector Behavior:

for carrier in &carrier_info.carriers {  // ✅ Multi-carrier loop
    exit_bindings.push((carrier.name.clone(), exit_value_id));
}
// exit_bindings = [("pos", ValueId(10)), ("result", ValueId(20)), ("is_ch_match", ValueId(30))]

3.1.5 ExitLineReconnector (Multi-carrier ready )

File: src/mir/builder/control_flow/joinir/merge/mod.rs API: reconnect_exit_bindings(exit_bindings, loop_header_phi_info, variable_map) Multi-carrier support: Loops over all exit_bindings

Behavior:

for (carrier_name, _) in exit_bindings {  // ✅ Multi-carrier loop
    if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi_dst(carrier_name) {
        variable_map.insert(carrier_name.clone(), phi_dst);
    }
}
// variable_map: {"pos" -> ValueId(100), "result" -> ValueId(200), "is_ch_match" -> ValueId(300)}

4. Conclusion: Architecture Supports Multi-Carrier (Pattern2 limitation found)

4.1 Phase 175-3 Test Results

Test: local_tests/test_jsonparser_parse_string_min2.hako

MIR Analysis:

bb3:
    %9 = copy %8  // result = "" (initialization)

bb5:  // Loop header
    %25 = phi [%4, bb3], [%21, bb10]  // Only pos carrier!
    // ❌ Missing: result carrier PHI

bb10:  // Loop update
    %21 = %25 Add %20  // pos = pos + 1
    // ❌ Missing: result = result + ch update

bb12:  // Exit block
    %29 = copy %9  // Still uses original %9 (empty string)!

Root Cause: Pattern2's Trim optimization only emits pos carrier, ignoring result updates in loop body.

Architecture Validation :

  • CarrierInfo detected 3 carriers (pos, result, is_ch_match)
  • variable_map contains all carriers at pattern2_start
  • Existing boxes (ExitMeta, ExitLineReconnector) support multi-carrier
  • Pattern2 lowerer only emits loop update for pos, not result

Conclusion:

  • Architecture is sound - all boxes support multi-carrier
  • Pattern2 implementation gap - Trim optimization doesn't emit body updates for non-position carriers
  • Phase 176 scope - Extend Pattern2 to emit all carrier updates, not just position

4.2 Next Steps

  • Phase 175-3: Run PoC test (test_jsonparser_parse_string_min2.hako)
  • Phase 176: Add escape sequence handling (continue path, Phase 176 scope)
  • Phase 177: Full _parse_string with all edge cases

5. Phase 176 で解決済み (2025-12-08)

実装内容:

  • Pattern2 lowerer を全キャリア対応に拡張
  • ヘッダ PHI / ループ更新 / ExitLine で複数キャリアを正しく処理
  • CarrierUpdateLowerer ヘルパで UpdateExpr → MIR 変換を統一

修正されたバグ:

  1. Trim pattern で loop_var_name が上書きされていた問題pattern2_with_break.rs
  2. InstructionRewriter が loop_var を exit_bindings から除外していなかった問題

テスト結果:

  • 2キャリア E2E テスト全てパスpos + result
  • 回帰テストなし
  • Trim pattern も正常動作

次のステップ: Phase 177 で JsonParser の複雑ループへ拡張


6. References