Files
hakorune/docs/development/current/main/phase184-body-local-mir-lowering.md
nyash-codex b6e31cf8ca feat(joinir): Phase 184 - Body-local MIR Lowering Infrastructure
Phase 184 implements the foundation for body-local variable support in
update expressions, completing the three-box architecture:
LoopBodyLocalEnv (storage), UpdateEnv (composition), and
CarrierUpdateEmitter (emission).

## Implementation Summary

### Task 184-1: Design Document
- Created phase184-body-local-mir-lowering.md
- Two-Environment System design (ConditionEnv + LoopBodyLocalEnv)
- Box-First design principles documented

### Task 184-2: LoopBodyLocalEnv Implementation
- New file: src/mir/join_ir/lowering/loop_body_local_env.rs (216 lines)
- Storage box for body-local variable name → ValueId mappings
- BTreeMap for deterministic ordering (PHI consistency)
- 7 unit tests: empty env, single/multiple locals, get/contains, iteration

### Task 184-3: UpdateEnv Implementation
- New file: src/mir/join_ir/lowering/update_env.rs (237 lines)
- Composition box for unified variable resolution
- Priority order: ConditionEnv (condition vars) → LoopBodyLocalEnv (body-local)
- 8 unit tests: priority, fallback, not found, combined lookup

### Task 184-4: CarrierUpdateEmitter Integration
- Modified: src/mir/join_ir/lowering/carrier_update_emitter.rs
- Added emit_carrier_update_with_env() (UpdateEnv version)
- Kept emit_carrier_update() for backward compatibility
- 4 new unit tests: body-local variable, priority, not found, const update
- Total 10 tests PASS (6 existing + 4 new)

### Task 184-5: Representative Test Cases
- apps/tests/phase184_body_local_update.hako (Pattern1 baseline)
- apps/tests/phase184_body_local_with_break.hako (Pattern2, Phase 185 target)

### Task 184-6: Documentation Updates
- Updated: docs/development/current/main/joinir-architecture-overview.md
- Updated: CURRENT_TASK.md (Phase 184 completion record)

## Test Results

All 25 unit tests PASS:
- LoopBodyLocalEnv: 7 tests
- UpdateEnv: 8 tests
- CarrierUpdateEmitter: 10 tests (6 existing + 4 new)

Build:  Success (0 errors)

## Design Constraints

Following 箱理論 (Box Theory) principles:
- Single Responsibility: Each box has one clear purpose
- Deterministic: BTreeMap ensures consistent ordering
- Conservative: Pattern5 (Trim) integration deferred to Phase 185
- Fail-Fast: Explicit errors for unsupported patterns

## Scope Limitation

Phase 184 provides the **infrastructure only**:
-  Storage box (LoopBodyLocalEnv)
-  Composition box (UpdateEnv)
-  Emission support (CarrierUpdateEmitter)
-  Pattern2/4 integration (requires body-local collection, Phase 185)

## Next Steps

Phase 185: Pattern2/4 Integration
- Integrate LoopBodyLocalEnv into Pattern2/4 lowerers
- Add body-local variable collection from loop body AST
- Enable full E2E body-local variable support in update expressions

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 23:59:19 +09:00

11 KiB

Phase 184: Body-local MIR Lowering Design

Status: In Progress (2025-12-08)

Overview

Phase 183 completed LoopBodyLocal role separation (condition vs body-only). Phase 184 implements body-local MIR lowering - the ability to use body-only local variables in update expressions safely.

Problem Statement

Current limitation:

local sum = 0
local i = 0
loop(i < 5) {
    local temp = i * 2  // body-local variable
    sum = sum + temp    // ❌ ERROR: temp not found in ConditionEnv
    i = i + 1
}

Root cause:

  • CarrierUpdateEmitter only has access to ConditionEnv
  • ConditionEnv only contains condition variables (loop parameters)
  • Body-local variables (temp) are not in ConditionEnv
  • Update expression sum = sum + temp fails to resolve temp

Goal: Enable body-local variables in update expressions while maintaining architectural clarity.

Design Solution

Architecture: Two-Environment System

┌─────────────────────┐
│   ConditionEnv      │  Condition variables (loop parameters)
│  - i → ValueId(0)   │  Priority: HIGH
│  - end → ValueId(1) │
└─────────────────────┘
         ↓
┌─────────────────────┐
│ LoopBodyLocalEnv    │  Body-local variables
│  - temp → ValueId(5)│  Priority: LOW (only if not in ConditionEnv)
│  - digit → ValueId(6)│
└─────────────────────┘
         ↓
┌─────────────────────┐
│    UpdateEnv        │  Unified resolution layer
│  resolve(name):     │  1. Try ConditionEnv first
│    cond.get(name)   │  2. Fallback to LoopBodyLocalEnv
│    .or(body.get())  │
└─────────────────────┘

Box-First Design

Following 箱理論 (Box Theory) principles:

Box 1: LoopBodyLocalEnv (Storage Box)

Single Responsibility: Collect and manage body-local variable mappings (name → ValueId).

pub struct LoopBodyLocalEnv {
    locals: BTreeMap<String, ValueId>, // Deterministic ordering
}

impl LoopBodyLocalEnv {
    /// Scan loop body AST and collect local definitions
    pub fn from_loop_body(
        body: &[ASTNode],
        builder: &mut JoinIrBuilder
    ) -> Self;

    /// Resolve a body-local variable name to JoinIR ValueId
    pub fn get(&self, name: &str) -> Option<ValueId>;
}

Design rationale:

  • BTreeMap: Ensures deterministic iteration (PHI ordering consistency)
  • from_loop_body(): Separates collection logic from lowering logic
  • get(): Simple lookup, no side effects

Box 2: UpdateEnv (Composition Box)

Single Responsibility: Unified variable resolution for update expressions.

pub struct UpdateEnv<'a> {
    condition_env: &'a ConditionEnv,      // Priority 1: Condition vars
    body_local_env: &'a LoopBodyLocalEnv, // Priority 2: Body-local vars
}

impl<'a> UpdateEnv<'a> {
    /// Resolve variable name with priority order
    pub fn resolve(&self, name: &str) -> Option<ValueId> {
        self.condition_env.get(name)
            .or_else(|| self.body_local_env.get(name))
    }
}

Design rationale:

  • Composition: Combines two environments without owning them
  • Priority order: Condition variables take precedence (shadowing prevention)
  • Lightweight: No allocation, just references

Integration Points

Current Flow (Pattern2 Example)

// Pattern2Lowerer::lower()
let condition_env = /* ... */;

// Emit carrier update
let update_value = emit_carrier_update(
    &carrier,
    &update_expr,
    &mut alloc_value,
    &condition_env,  // ❌ Only has condition variables
    &mut instructions,
)?;

Phase 184 Flow

// Pattern2Lowerer::lower()
let condition_env = /* ... */;
let body_local_env = LoopBodyLocalEnv::from_loop_body(&body_nodes, builder);

// Create unified environment
let update_env = UpdateEnv {
    condition_env: &condition_env,
    body_local_env: &body_local_env,
};

// Emit carrier update (now with body-local support)
let update_value = emit_carrier_update_with_env(
    &carrier,
    &update_expr,
    &mut alloc_value,
    &update_env,  // ✅ Has both condition and body-local variables
    &mut instructions,
)?;

Scope and Constraints

In Scope (Phase 184)

  1. Body-local variable collection: LoopBodyLocalEnv::from_loop_body()
  2. Unified resolution: UpdateEnv::resolve()
  3. CarrierUpdateEmitter integration: Use UpdateEnv instead of ConditionEnv
  4. Pattern2/4 integration: Pass body_local_env to update lowering

Out of Scope

  1. Condition variable usage: Body-locals still cannot be used in conditions
  2. Pattern5 (Trim) integration: Defer to Phase 185
  3. Complex expressions: Only simple variable references in updates
  4. Type checking: Assume type correctness (existing type inference handles this)

Design Constraints

Following Phase 178 Fail-Fast and 箱理論 principles:

  1. Single Responsibility: Each Box has one clear purpose
  2. Deterministic: BTreeMap for consistent ordering
  3. Conservative: No changes to Trim/Pattern5 logic
  4. Explicit errors: If body-local used in condition → Fail loudly

Implementation Tasks

Task 184-1: Design Document (This document)

Task 184-2: LoopBodyLocalEnv Implementation

File: src/mir/join_ir/lowering/loop_body_local_env.rs (new)

Core logic:

impl LoopBodyLocalEnv {
    pub fn from_loop_body(body: &[ASTNode], builder: &mut JoinIrBuilder) -> Self {
        let mut locals = BTreeMap::new();

        for node in body {
            if let ASTNode::LocalDecl { name, init_value } = node {
                // Lower init_value to JoinIR
                let value_id = builder.lower_expr(init_value)?;
                locals.insert(name.clone(), value_id);
            }
        }

        Self { locals }
    }
}

Unit tests (3-5 tests):

  • test_empty_body: No locals → empty env
  • test_single_local: One local → one mapping
  • test_multiple_locals: Multiple locals → sorted keys
  • test_get_existing: Lookup succeeds
  • test_get_nonexistent: Lookup returns None

Task 184-3: UpdateEnv Implementation

File: src/mir/join_ir/lowering/update_env.rs (new)

Core logic:

pub struct UpdateEnv<'a> {
    condition_env: &'a ConditionEnv,
    body_local_env: &'a LoopBodyLocalEnv,
}

impl<'a> UpdateEnv<'a> {
    pub fn new(
        condition_env: &'a ConditionEnv,
        body_local_env: &'a LoopBodyLocalEnv,
    ) -> Self {
        Self { condition_env, body_local_env }
    }

    pub fn resolve(&self, name: &str) -> Option<ValueId> {
        self.condition_env.get(name)
            .or_else(|| self.body_local_env.get(name))
    }
}

Unit tests (2-3 tests):

  • test_resolve_condition_priority: Condition var found first
  • test_resolve_body_local_fallback: Body-local found when condition absent
  • test_resolve_not_found: Neither env has variable → None

Task 184-4: CarrierUpdateEmitter Integration

File: src/mir/join_ir/lowering/carrier_update_emitter.rs (modify)

Changes:

  1. Add new function variant accepting UpdateEnv:
pub fn emit_carrier_update_with_env(
    carrier: &CarrierVar,
    update: &UpdateExpr,
    alloc_value: &mut dyn FnMut() -> ValueId,
    env: &UpdateEnv,  // New: UpdateEnv instead of ConditionEnv
    instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String> {
    // Use env.resolve() instead of env.get()
}
  1. Keep existing emit_carrier_update() for backward compatibility
  2. Update Pattern2/4 callers to use new variant

Validation: Existing tests must pass (backward compatibility).

Task 184-5: Representative Tests

File: apps/tests/phase184_body_local_update.hako (new)

// Body-local used in update expression
static box Main {
    main() {
        local sum = 0
        local i = 0
        loop(i < 5) {
            local temp = i * 2  // Body-local variable
            sum = sum + temp    // Use in update expression
            i = i + 1
        }
        print(sum)  // Expected: 0+2+4+6+8 = 20
    }
}

Test commands:

# Structure trace
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako

# Full execution
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako

Task 184-6: Documentation Updates

  1. Update: docs/development/current/main/joinir-architecture-overview.md

    • Add LoopBodyLocalEnv section
    • Update UpdateEnv integration diagram
  2. Update: CURRENT_TASK.md

    • Mark Phase 184 complete
    • Add Phase 185 preview

Validation Strategy

Success Criteria

  1. Unit tests pass: LoopBodyLocalEnv and UpdateEnv tests green
  2. Backward compatibility: Existing Pattern2/4 tests still pass
  3. Representative test: phase184_body_local_update.hako executes correctly (output: 20)
  4. No regression: Trim patterns (Pattern5) unaffected

Test Commands

# Unit tests
cargo test --release --lib loop_body_local_env
cargo test --release --lib update_env
cargo test --release --lib carrier_update

# Integration test
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako

# Regression check (Trim pattern)
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase172_trim_while.hako

Known Limitations

Not Supported (Explicit Design Decision)

  1. Body-local in conditions:
loop(i < 5) {
    local temp = i * 2
    if (temp > 6) break  // ❌ ERROR: Body-local in condition not allowed
}

Reason: Condition variables must be loop parameters (JoinIR entry signature constraint).

  1. Shadowing:
loop(i < 5) {
    local i = 10  // ❌ ERROR: Shadows condition variable
}

Reason: UpdateEnv prioritizes condition variables - shadowing forbidden.

  1. Complex expressions:
loop(i < 5) {
    local temp = obj.method()  // ⚠️ May not work yet
}

Reason: Limited to expressions JoinIrBuilder::lower_expr() supports.

Future Work (Phase 185+)

Phase 185: Trim Pattern Integration

  • Extend LoopBodyLocalEnv to handle Trim carrier variables
  • Update TrimLoopLowerer to use UpdateEnv

Phase 186: Condition Expression Support

  • Allow body-local variables in break/continue conditions
  • Requires inline expression evaluation in condition lowering

References

  • Phase 183: LoopBodyLocal role separation (condition vs body-only)
  • Phase 178: Fail-Fast error handling principles
  • Phase 171-C: LoopBodyCarrierPromoter original design
  • carrier_update_emitter.rs: Current update emission logic
  • condition_env.rs: Condition variable environment design