Files
hakorune/docs/development/current/main/investigations/loopbuilder-removal-compatibility-analysis.md
tomoaki ab76e39036 feat(parser): Phase 285A1.4 & A1.5 - Weak field sugar + Parser hang fix
A1.4: Add sugar syntax `public weak parent` ≡ `public { weak parent }`
A1.5: Fix parser hang on unsupported `param: Type` syntax

Key changes:
- A1.4: Extend visibility parser to handle weak modifier (fields.rs)
- A1.5: Shared helper `parse_param_name_list()` with progress-zero detection
- A1.5: Fix 6 vulnerable parameter parsing loops (methods, constructors, functions)
- Tests: Sugar syntax (OK/NG), parser hang (timeout-based)
- Docs: lifecycle.md, EBNF.md, phase-285a1-boxification.md

Additional changes:
- weak() builtin implementation (handlers/weak.rs)
- Leak tracking improvements (leak_tracker.rs)
- Documentation updates (lifecycle, types, memory-finalization, etc.)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-24 07:44:50 +09:00

20 KiB
Raw Blame History

LoopBuilder Removal Compatibility Analysis

Date: 2025-12-24 Investigator: Claude Code Test: core_direct_array_oob_set_rc_vm Error: [joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed User Hypothesis: "昔のjoinirが今のjoinirに対応してないだけじゃない" (Old JoinIR code not compatible with current JoinIR?)


Executive Summary

User's hypothesis is CORRECT! This is not a bug in the current code—it's a feature gap in JoinIR pattern coverage.

Root Cause

  1. LoopBuilder was physically deleted in Phase 187 (commit fa8a96a51, Dec 4, 2025)

    • Deleted: 8 files, ~1,758 lines of legacy loop lowering code
    • Preserved: Only IfInLoopPhiEmitter moved to minimal module
  2. BundleResolver.resolve/4 uses a loop pattern not yet supported by JoinIR

    • The .hako code in lang/src/compiler/entry/bundle_resolver.hako was written when LoopBuilder still existed
    • This specific loop pattern hasn't been migrated to JoinIR patterns yet
  3. This is NOT a regression—it's an expected gap documented in Phase 188

    • Phase 188 identified 5 failing loop patterns after LoopBuilder removal
    • The failing loop in phase264_p0_bundle_resolver_loop_min.hako matches the documented gap

Current Status

  • LoopBuilder: Completely removed (Dec 4, 2025, Phase 187)
  • JoinIR Patterns Implemented: Pattern 1, 2, 3 (Phase 188, Dec 5-6, 2025)
  • Coverage: ~80% of representative tests pass
  • Gap: BundleResolver loop falls into the 20% not yet covered

Timeline: LoopBuilder Removal

Phase 186: Hard Freeze (Dec 4, 2025)

  • Commit: 30f94c955
  • Added access control guard to prevent LoopBuilder usage
  • Intent: Make LoopBuilder opt-in only with NYASH_LEGACY_LOOPBUILDER=1
  • Gap Found: Guard was added but instantiation point wasn't protected!

Phase 187: Physical Removal (Dec 4, 2025)

  • Commit: fa8a96a51
  • Deleted:
    • src/mir/loop_builder/mod.rs (158 lines)
    • src/mir/loop_builder/loop_form.rs (578 lines)
    • src/mir/loop_builder/if_lowering.rs (298 lines)
    • src/mir/loop_builder/phi_ops.rs (333 lines)
    • src/mir/loop_builder/control.rs (94 lines)
    • src/mir/loop_builder/statements.rs (39 lines)
    • src/mir/loop_builder/joinir_if_phi_selector.rs (168 lines)
    • src/mir/loop_builder/README.md (15 lines)
  • Total Deletion: 1,758 lines
  • Preserved: Only IfInLoopPhiEmitter moved to src/mir/phi_core/if_in_loop_phi_emitter/mod.rs

Phase 188: JoinIR Pattern Expansion (Dec 5-6, 2025)

  • Implemented 3 loop patterns to replace LoopBuilder:
    1. Pattern 1: Simple While Loop (loop(i < 3) { i++ })
    2. Pattern 2: Loop with Conditional Break (loop(true) { if(...) break })
    3. Pattern 3: Loop with If-Else PHI (loop(...) { if(...) sum += x else sum += 0 })
  • Coverage: Estimated 80% of representative tests
  • Documented Gaps: 5 failing patterns identified in inventory.md

What is LoopBuilder?

Historical Context

LoopBuilder was the original loop lowering system (pre-Phase 186):

  • Purpose: Convert loop AST → MIR with SSA/PHI nodes
  • Implementation: 8 files, ~1,000+ lines
  • Problems:
    • Complex PHI handling with historical bugs
    • Mixed control flow responsibilities
    • Silent fallback behavior (hid pattern gaps)

Why Was It Removed?

From Phase 187 documentation:

Reasons for Deletion:

  1. Isolated: Single instantiation point made deletion safe
  2. Legacy: Original implementation had historical design issues
  3. Proven: Phase 181 confirmed 80% of patterns work with JoinIR only
  4. Explicit Failure: Removing fallback drives JoinIR improvement

What Replaced It?

JoinIR Frontend (modern loop lowering):

  • Pattern-based approach: Each loop pattern has dedicated lowering logic
  • Explicit failure: Unsupported patterns fail with clear error messages
  • Extensible: New patterns can be added incrementally

The Failing BundleResolver Loop

Location

Source: lang/src/compiler/entry/bundle_resolver.hako Function: BundleResolver.resolve/4 Lines: Multiple loops (25-45, 52-71, 76-87, 92-101, 106-112)

Example Loop (lines 25-45)

// Alias table parsing loop
local i = 0
loop(i < table.length()) {
    // find next delimiter or end
    local j = table.indexOf("|||", i)
    local seg = ""
    if j >= 0 {
        seg = table.substring(i, j)
    } else {
        seg = table.substring(i, table.length())
    }

    if seg != "" {
        local pos = -1
        local k = 0
        loop(k < seg.length()) {
            if seg.substring(k,k+1) == ":" {
                pos = k
                break
            }
            k = k + 1
        }
        // ... more processing
    }

    if j < 0 { break }
    i = j + 3
}

Why This Fails

Pattern Characteristics:

  • Multiple carriers: i, j, seg, pos, k (5+ variables)
  • Nested loop: Inner loop with break
  • Conditional assignments: seg = ... if ... else ...
  • Non-unit increment: i = j + 3 (not i++)
  • Complex control flow: Multiple if statements with break

Current JoinIR Pattern Coverage:

  1. Pattern 1 (Simple While): Too simple—only handles single carrier, no breaks
  2. Pattern 2 (Conditional Break): Doesn't handle multiple carriers + nested loops
  3. Pattern 3 (If-Else PHI): Only handles "if-sum" pattern (arithmetic accumulation)
  4. Pattern 4 (Continue): Not applicable (no continue statements)

Result: No pattern matches → falls through → explicit error


Phase 264: Recent Attempt to Fix Similar Issue

Context

Phase 264 P0 documented a similar failure in phase264_p0_bundle_resolver_loop_min.hako:

local i = 0
local seg = ""

loop(i < 10) {
    // Conditional assignment to seg
    if i == 0 {
        seg = "first"
    } else {
        seg = "other"
    }

    // Non-unit increment
    i = i + 2
}

Why It Failed

Pattern routing flow:

  1. Pattern 8 (BoolPredicateScan): REJECT (condition right is not .length())
  2. Pattern 3 (WithIfPhi): MATCHED → but rejects "Not an if-sum pattern"
  3. Pattern 1/2: Not tried
  4. Result: No match → ERROR

Root Cause (from Phase 264 analysis)

Classification Heuristic Issue:

// src/mir/loop_pattern_detection/mod.rs:227-230
// Pattern 3 heuristic: has_if_else_phi if carrier_count > 1
let has_if_else_phi = carrier_count > 1;

Problem:

  • This heuristic is too conservative
  • Any loop with 2+ carriers → classified as Pattern3IfPhi
  • But Pattern3 only handles if-sum patterns (arithmetic accumulation)
  • Simple conditional assignment → incorrectly routed to Pattern3 → rejected

Proposed Fix (Phase 264)

Option B (adopted): Improve classification heuristic

// Phase 264 P0: Improved if-else PHI detection
// Pattern3 heuristic: has_if_else_phi if there's an if-sum pattern signature
let has_if_else_phi = carrier_count > 1 && has_if_sum_signature(scope);

Impact:

  • Simple conditional assignment → falls through to Pattern1
  • If-sum pattern → correctly routed to Pattern3

Answer to Investigation Questions

1. Where is "LoopBuilder has been removed" error generated?

Location: src/mir/builder/control_flow/mod.rs:136-145

// Phase 186: LoopBuilder Hard Freeze - Legacy path disabled
// Phase 187-2: LoopBuilder module removed - all loops must use JoinIR
use crate::mir::join_ir::lowering::error_tags;
return Err(error_tags::freeze(&format!(
    "Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.\n\
     Function: {}\n\
     Hint: This loop pattern is not supported. All loops must use JoinIR lowering.",
    self.scope_ctx.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("<unknown>")
)));

2. When was LoopBuilder removed?

Date: December 4, 2025 Commit: fa8a96a51b909e1fbb7a61c8e1b989050c58d4ee Phase: 187 Files Deleted: 8 files, 1,758 lines

Git History:

git log --all --oneline --grep="LoopBuilder" | head -5
# fa8a96a51 docs(joinir): Phase 187 LoopBuilder Physical Removal
# 1e350a6bc docs(joinir): Phase 186-4 Documentation Update
# 30f94c955 feat(joinir): Phase 186 LoopBuilder Hard Freeze

3. Are there any remaining LoopBuilder calls?

No. The module was completely deleted in Phase 187:

# Before Phase 187
src/mir/loop_builder/
├── mod.rs (158 lines)
├── loop_form.rs (578 lines)
├── if_lowering.rs (298 lines)
├── phi_ops.rs (333 lines)
├── control.rs (94 lines)
├── statements.rs (39 lines)
├── joinir_if_phi_selector.rs (168 lines)
└── README.md (15 lines)

# After Phase 187
❌ DELETED (all files)
✅ PRESERVED: IfInLoopPhiEmitter → src/mir/phi_core/if_in_loop_phi_emitter/mod.rs

Remaining References: Only in documentation and git history

4. Where is BundleResolver defined?

Location: lang/src/compiler/entry/bundle_resolver.hako Type: .hako source code (selfhost compiler component) Function: BundleResolver.resolve/4 (static box method)

Purpose: Stage-B bundling resolver

  • Merges multiple source bundles
  • Resolves module dependencies
  • Handles duplicate/missing module detection

5. Is it using old JoinIR patterns?

YES—this is the core issue!

BundleResolver.resolve/4 characteristics:

  • Written during LoopBuilder era (before Phase 186-188)
  • Uses loop patterns that worked with LoopBuilder
  • Those patterns are NOT yet covered by current JoinIR patterns

Example incompatibility:

// This pattern worked with LoopBuilder
local i = 0
loop(i < table.length()) {
    local j = table.indexOf("|||", i)
    // ... complex processing
    if j < 0 { break }
    i = j + 3  // Non-unit increment
}

// Current JoinIR patterns can't handle:
// - Multiple carriers (i, j)
// - Non-unit increment (i = j + 3)
// - Conditional break
// - String method calls in condition/body

6. Can we see the actual loop structure that's failing?

Yes—see section "The Failing BundleResolver Loop" above.

Key problematic features:

  1. Multiple carrier variables (i, j, seg, pos, k)
  2. Nested loops (outer loop with inner loop)
  3. Conditional assignments (seg = if ... then ... else ...)
  4. Non-unit increment (i = j + 3)
  5. Complex control flow (multiple if/break)
  6. String operations (.indexOf(), .substring(), .length())

7. What replaced LoopBuilder?

JoinIR Frontend with pattern-based lowering:

Pattern Description Status Example
Pattern 1 Simple While Implemented loop(i < n) { i++ }
Pattern 2 Conditional Break Implemented loop(true) { if(...) break }
Pattern 3 If-Else PHI Implemented loop(...) { if(...) sum += x else sum += 0 }
Pattern 4 Continue Planned loop(...) { if(...) continue }
Pattern 5 Infinite Early Exit Planned loop(true) { if(...) break; if(...) continue }

Coverage: ~80% of representative tests pass with Patterns 1-3

8. Is there a migration path documented?

YES—documented in Phase 188:

From: docs/private/roadmap2/phases/phase-188-joinir-loop-pattern-expansion/README.md

Phase 188 Objective: Expand JoinIR loop pattern coverage to handle loop patterns currently failing with [joinir/freeze] error.

Status: Planning 100% complete, Pattern 1/2 lowering + execution 100% complete, Pattern 3 lowering 100% complete

Migration Strategy:

  1. Identify failing patterns (Task 188-1: Error Inventory) Done
  2. Classify patterns (Task 188-2: Pattern Classification) Done
  3. Prioritize 2-3 patterns (Task 188-2) Done (Patterns 1, 2, 3)
  4. Design JoinIR extensions (Task 188-3) Done
  5. Implement lowering (Task 188-4) Done (Patterns 1-3)
  6. Verify representative tests (Task 188-5) Partial (80% coverage)

Documented Gaps (from Phase 188 inventory):

# Pattern Status Priority
1 Simple While Loop Implemented HIGH
2 Loop with Conditional Break Implemented HIGH
3 Loop with If-Else PHI Implemented MEDIUM
4 Loop with One-Sided If May be auto-handled MEDIUM
5 Loop with Continue Deferred LOW
6 Complex Multi-Carrier NOT PLANNED ?

BundleResolver falls into Gap #6: Complex loops with multiple carriers, nested loops, and non-unit increments.


Root Cause Analysis

Is this a code update issue or a feature gap?

Answer: Feature gap (not a bug)

Detailed Analysis

What happened:

  1. Phase 186-187 (Dec 4): LoopBuilder removed to force explicit failures
  2. Phase 188 (Dec 5-6): JoinIR Patterns 1-3 implemented (80% coverage)
  3. Gap remains: Complex loops (like BundleResolver) not yet covered
  4. This test fails: Because BundleResolver uses a pattern outside the 80% coverage

Is the .hako code wrong?

  • No—the code is valid Nyash syntax
  • It worked when LoopBuilder existed
  • It's just a pattern that hasn't been migrated to JoinIR yet

Is the JoinIR implementation wrong?

  • No—JoinIR is working as designed
  • It explicitly fails on unsupported patterns (as intended)
  • This failure drives future pattern expansion

Is this a regression?

  • No—this is an expected gap documented in Phase 188
  • The removal of LoopBuilder was intentional to force explicit failures
  • Phase 188 documented 5 failing patterns; this is one of them

What's the Fix?

Short-term Options

Pros:

  • Immediate fix (no compiler changes needed)
  • Makes code compatible with current JoinIR
  • Educational exercise (learn supported patterns)

Cons:

  • Requires understanding which patterns are supported
  • May require restructuring loop logic

Example Rewrite:

Before (unsupported):

local i = 0
loop(i < table.length()) {
    local j = table.indexOf("|||", i)
    // ... complex processing
    if j < 0 { break }
    i = j + 3  // Non-unit increment
}

After (Pattern 2 compatible):

local i = 0
local done = 0
loop(i < table.length() and done == 0) {
    local j = table.indexOf("|||", i)
    // ... complex processing
    if j < 0 {
        done = 1
    } else {
        i = j + 3
    }
}

Option B: Implement new JoinIR pattern for complex loops FUTURE WORK

Pros:

  • Proper solution (makes compiler more capable)
  • Benefits all similar loop patterns

Cons:

  • Large effort (Pattern 1-3 took 1,802 lines + design docs)
  • Not urgent (only affects 20% of tests)

Steps:

  1. Design "Pattern 6: Complex Multi-Carrier Loop"
  2. Implement detection logic
  3. Implement JoinIR lowering
  4. Test with BundleResolver and similar loops

Pros:

  • Immediate fix (git revert)

Cons:

  • Defeats the purpose of Phase 186-188
  • Re-introduces legacy bugs
  • Hides pattern gaps (prevents JoinIR improvement)

From Phase 187 docs:

Lesson 3: Explicit Failure Drives Architecture Forward Removing the fallback path forces future work to improve JoinIR, not work around it.

Long-term Solution

Phase 189+ planning needed:

  1. Analyze remaining 20% of failing tests
  2. Identify common patterns (e.g., "Complex Multi-Carrier")
  3. Prioritize by impact (selfhost critical paths)
  4. Design and implement additional JoinIR patterns

From Phase 188 docs:

Next: Phase 189 - Multi-function JoinIR→MIR merge / Select instruction MIR bridge


Recommendations

Immediate Action (for this test failure)

1. Rewrite BundleResolver loops (Option A)

  • Who: Developer working on selfhost compiler
  • What: Refactor loops to use Pattern 1 or Pattern 2
  • Why: Immediate fix, no compiler changes needed
  • How: See "Example Rewrite" above

2. Document the pattern gap (if not already documented)

  • Where: docs/private/roadmap2/phases/phase-188-joinir-loop-pattern-expansion/inventory.md
  • Add: BundleResolver.resolve/4 as example of "Complex Multi-Carrier" pattern
  • Tag: Phase 189+ future work

Future Work

1. Analyze BundleResolver loop patterns systematically

  • Extract common loop structures
  • Classify into existing or new patterns
  • Prioritize by impact on selfhost pipeline

2. Design "Pattern 6: Complex Multi-Carrier Loop" (if needed)

  • Support multiple carrier variables (3+)
  • Support non-unit increments
  • Support nested loops
  • Support string operations in conditions

3. Consider intermediate patterns

  • "Pattern 2.5": Conditional Break + Multiple Carriers
  • "Pattern 3.5": If-Else PHI + Non-unit Increment

Testing Strategy

1. Create minimal reproducer

  • Extract minimal BundleResolver loop to separate test file
  • Use as regression test for future pattern work

2. Add to Phase 188 inventory

  • Document exact pattern characteristics
  • Mark as "Pattern 6 candidate" or "Defer to Phase 189+"

3. Track coverage metrics

  • Current: 80% (Patterns 1-3)
  • Target: 95%+ (add Pattern 4-6)
  • Critical: 100% of selfhost paths

Conclusion

Summary of Findings

  1. User hypothesis is CORRECT: This is old code (BundleResolver.hako) not compatible with new JoinIR patterns

  2. LoopBuilder was removed intentionally in Phase 187 (Dec 4, 2025)

    • Deleted: 8 files, 1,758 lines
    • Replaced by: JoinIR Frontend with pattern-based lowering
  3. Current JoinIR coverage: ~80% (Patterns 1-3 implemented in Phase 188)

    • Pattern 1: Simple While
    • Pattern 2: Conditional Break
    • Pattern 3: If-Else PHI
  4. BundleResolver loop falls in the 20% gap:

    • Multiple carriers (5+ variables)
    • Nested loops
    • Non-unit increments
    • Complex control flow
  5. This is NOT a bug—it's a documented feature gap

    • Expected from Phase 188 planning
    • Failure is intentional (drives JoinIR improvement)

Short-term: Rewrite BundleResolver loops to use Pattern 1 or Pattern 2 Long-term: Implement "Pattern 6: Complex Multi-Carrier Loop" in Phase 189+

Key Insight

The removal of LoopBuilder forces explicit failures rather than silent fallbacks. This is a feature, not a bug—it ensures:

  • Clear error messages guide developers
  • Pattern gaps are visible (not hidden)
  • Future work focuses on real needs (not legacy compatibility)

From Phase 187 documentation:

"Explicit failures replace implicit fallbacks. Future JoinIR expansion is the only way forward."


References

Documentation

  • Phase 186: docs/private/roadmap2/phases/phase-186-loopbuilder-freeze/README.md
  • Phase 187: docs/private/roadmap2/phases/phase-180-joinir-unification-before-selfhost/README.md#8-phase-187
  • Phase 188: docs/private/roadmap2/phases/phase-188-joinir-loop-pattern-expansion/README.md
  • Phase 264: docs/development/current/main/phases/phase-264/README.md

Source Files

  • Error location: src/mir/builder/control_flow/mod.rs:136-145
  • Pattern detection: src/mir/loop_pattern_detection/mod.rs
  • BundleResolver: lang/src/compiler/entry/bundle_resolver.hako
  • Test file: apps/tests/phase264_p0_bundle_resolver_loop_min.hako

Git Commits

  • Phase 186 Freeze: 30f94c955 (Dec 4, 2025)
  • Phase 187 Removal: fa8a96a51 (Dec 4, 2025)
  • Phase 188 Pattern 1: d303d24b4 (Dec 6, 2025)
  • Phase 188 Pattern 2: 87e477b13 (Dec 6, 2025)
  • Phase 188 Pattern 3: 638182a8a (Dec 6, 2025)
  • Phase 188 Error Inventory: 5 failing patterns documented
  • Phase 264 P0: Similar issue with classification heuristic
  • Phase 189 Planning: Multi-function JoinIR merge (future)

Investigation Complete Date: 2025-12-24 Status: Root cause identified, recommendations provided