Files
hakorune/docs/development/current/main/phase205-valueid-regions-design.md
nyash-codex d4f90976da refactor(joinir): Phase 244 - ConditionLoweringBox trait unification
Unify condition lowering logic across Pattern 2/4 with trait-based API.

New infrastructure:
- condition_lowering_box.rs: ConditionLoweringBox trait + ConditionContext (293 lines)
- ExprLowerer implements ConditionLoweringBox trait (+51 lines)

Pattern migrations:
- Pattern 2 (loop_with_break_minimal.rs): Use trait API
- Pattern 4 (loop_with_continue_minimal.rs): Use trait API

Benefits:
- Unified condition lowering interface
- Extensible for future lowering strategies
- Clean API boundary between patterns and lowering logic
- Zero code duplication

Test results: 911/911 PASS (+2 new tests)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-11 02:35:31 +09:00

560 lines
17 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 205: ValueId Region Boundaries - Design Document
**Author**: Claude Sonnet 4.5
**Date**: 2025-12-09
**Status**: In Progress
## Overview
Phase 205 establishes strict ValueId region contracts for JoinIR lowering, completing the Box-First architecture started in Phase 201. This phase ensures that ValueId allocation is:
1. **Predictable**: Each ValueId belongs to a clearly defined region
2. **Verifiable**: Region violations are detected in debug mode
3. **Maintainable**: All allocation goes through JoinValueSpace Box
## ValueId Region Architecture
### Region Layout
```text
0 100 1000 u32::MAX
├──────────┼──────────┼──────────────────────────┤
│ PHI │ Param │ Local │
│ Reserved│ Region │ Region │
└──────────┴──────────┴──────────────────────────┘
```
### Region Definitions
| Region | Range | Purpose | Examples |
|--------|-------|---------|----------|
| **PHI Reserved** | 0-99 | LoopHeader PHI destinations | `phi_dst: ValueId(0)` |
| **Param Region** | 100-999 | Loop arguments & environment | `Condition.bool_id`, `Carrier.join_id`, `CapturedEnv` |
| **Local Region** | 1000+ | JoinIR-internal values | Const, BinOp, Load, etc. |
### Constants (Phase 205)
```rust
// Explicit region boundaries
pub const PHI_RESERVED_MIN: u32 = 0;
pub const PHI_RESERVED_MAX: u32 = 99;
pub const PARAM_MIN: u32 = 100;
pub const PARAM_MAX: u32 = 999;
pub const LOCAL_MIN: u32 = 1000;
pub const LOCAL_MAX: u32 = 100000;
```
## Box-First Design
### ValueIdAllocator Box (JoinValueSpace)
**Responsibility**: Single Source of Truth for ValueId allocation
**API**:
```rust
impl JoinValueSpace {
// Primary allocation methods
pub fn alloc_param(&mut self) -> ValueId; // Returns 100+
pub fn alloc_local(&mut self) -> ValueId; // Returns 1000+
pub fn reserve_phi(&mut self, id: ValueId); // Marks PHI dst
// Phase 205: Enhanced verification
pub fn verify_region(&self, id: ValueId, expected: Region) -> Result<(), String>;
pub fn check_collision(&self, id: ValueId, role: &str); // debug-only
}
```
**Invariants**:
1. `alloc_param()` never returns id >= 1000
2. `alloc_local()` never returns id < 1000
3. No ValueId is allocated twice
4. PHI dst always in range 0-99
### RegionVerifier Box
**Responsibility**: Verify region contracts at merge boundaries
**Location**: `src/mir/builder/control_flow/joinir/merge/mod.rs`
**API**:
```rust
#[cfg(debug_assertions)]
fn verify_valueid_regions(
boundary: &JoinInlineBoundary,
loop_info: &LoopHeaderPhiInfo,
join_value_space: &JoinValueSpace,
);
```
**Checks**:
1. All `boundary.join_inputs` are in Param region
2. All `carrier_phis[].phi_dst` are in valid range (<= LOCAL_MAX)
3. No overlap between Param and Local regions
4. PHI reservations are in PHI Reserved region
## ValueId Role Mapping
### Param Region (100-999)
| Role | Allocated By | Example |
|------|-------------|---------|
| **Condition.bool_id** | `condition_env_builder.rs` | `ValueId(100)` |
| **Carrier.join_id** | Pattern frontend (P1/P2/P3/P4) | `ValueId(101)`, `ValueId(102)` |
| **CapturedEnv vars** | Pattern frontend | `ValueId(103+)` |
| **Boundary inputs** | `common_init.rs` | `ValueId(104+)` |
### Local Region (1000+)
| Role | Allocated By | Example |
|------|-------------|---------|
| **Const values** | Lowerers (pattern1-4, trim) | `ValueId(1000)` |
| **BinOp results** | Lowerers | `ValueId(1001)` |
| **Load results** | Lowerers | `ValueId(1002)` |
| **Intermediate values** | Lowerers | `ValueId(1003+)` |
### PHI Reserved (0-99)
| Role | Allocated By | Example |
|------|-------------|---------|
| **PHI dst** | MirBuilder (host side) | `ValueId(0)`, `ValueId(1)` |
**Note**: PHI dst comes from host MirBuilder, NOT JoinValueSpace. `reserve_phi()` is for verification only.
## Current State Inventory (Task 205-2)
### Pattern 1 (Minimal)
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs`
**Status**: Fully integrated with JoinValueSpace
**Allocation Sites**:
- ConditionEnv: Uses `alloc_param()` via `condition_env_builder.rs`
- Carrier (i): Uses `alloc_param()` in frontend
- Lowerer: Uses `alloc_local()` for all JoinIR values
**Raw ValueId Usage**: None detected
### Pattern 2 (With Break)
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
**Status**: Fully integrated with JoinValueSpace
**Allocation Sites**:
- ConditionEnv: Uses `alloc_param()` via `condition_env_builder.rs`
- Carrier (v): Uses `alloc_param()` in frontend
- Lowerer: Uses `alloc_local()` for all JoinIR values
**Raw ValueId Usage**: None detected
**Historical Note**: Pattern 2 was the original motivation for Phase 201 - previously had collision between `alloc_join_value()` (param) and `alloc_value()` (local starting from 0).
### Pattern 3 (With If-PHI)
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`
**Status**: Needs verification
**Allocation Sites**:
- ConditionEnv: Uses `alloc_param()` via `condition_env_builder.rs`
- Carriers (sum, count): Uses `alloc_param()` in frontend
- Lowerer: Uses `alloc_local()` for all JoinIR values
**Potential Issues**:
- If-PHI lowering: Need to verify all temporary values use `alloc_local()`
- ExitLine reconnection: Verify no raw `ValueId(..)` usage
**Action Required**: Task 205-5 will audit
### Pattern 4 (With Continue)
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
**Status**: Needs verification
**Allocation Sites**:
- ConditionEnv: Uses `alloc_param()` via `condition_env_builder.rs`
- Carriers: Uses `alloc_param()` in frontend
- Lowerer: Uses `alloc_local()` for all JoinIR values
**Potential Issues**:
- Continue-pattern has more complex control flow
- UpdateSummary handling: Verify all intermediate values use `alloc_local()`
**Action Required**: Task 205-5 will audit
### Trim Pattern Lowerer
**File**: `src/mir/builder/control_flow/joinir/patterns/trim_pattern_lowerer.rs`
**Status**: Needs verification
**Allocation Sites**:
- Uses `alloc_fn: &mut dyn FnMut() -> ValueId` pattern
- Should receive `space.local_allocator()` closure
**Potential Issues**:
- Multiple lowerer sites (JsonParser, other Trim use cases)
- Need to ensure all call sites pass `space.local_allocator()`
**Action Required**: Task 205-5 will audit
### ConditionEnv Builder
**File**: `src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs`
**Status**: Already uses `alloc_param()`
**Implementation**:
```rust
pub fn build_condition_env(
condition_ast: &AstNode,
join_value_space: &mut JoinValueSpace,
// ...
) -> Result<ConditionEnv, String> {
let bool_id = join_value_space.alloc_param(); // ✅ Correct
// ...
}
```
### Exit Binding & Common Init
**Files**:
- `src/mir/builder/control_flow/joinir/patterns/exit_binding.rs`
- `src/mir/builder/control_flow/joinir/patterns/common_init.rs`
**Status**: Needs verification
**Potential Issues**:
- Exit binding may create temporary ValueIds
- Common init should use `alloc_param()` for boundary inputs
**Action Required**: Task 205-5 will audit
## Implementation Plan
### Task 205-3: ValueIdAllocator Box Enhancement
**Changes to** `src/mir/join_ir/lowering/join_value_space.rs`:
```rust
// Add explicit max constants
pub const LOCAL_MAX: u32 = 100000;
// Add collision detection (debug-only)
#[cfg(debug_assertions)]
fn check_collision(&self, id: ValueId, role: &str) {
if self.allocated_ids.contains(&id) {
panic!(
"[JoinValueSpace] ValueId collision: {:?} already allocated (role: {})",
id, role
);
}
}
// Add region verification
#[cfg(debug_assertions)]
pub fn verify_region(&self, id: ValueId, expected_region: Region) -> Result<(), String> {
let actual = self.region_of(id);
if actual != expected_region {
return Err(format!(
"ValueId {:?} is in {:?} region, expected {:?}",
id, actual, expected_region
));
}
Ok(())
}
// Track allocated IDs (debug-only)
#[cfg(debug_assertions)]
allocated_ids: HashSet<u32>,
// Update alloc_param/alloc_local to track allocations
#[cfg(debug_assertions)]
pub fn alloc_param(&mut self) -> ValueId {
let id = self.next_param;
debug_assert!(id < LOCAL_BASE, "Param region overflow");
self.check_collision(ValueId(id), "param");
self.allocated_ids.insert(id);
self.next_param += 1;
ValueId(id)
}
```
### Task 205-4: RegionVerifier Box Implementation
**Location**: `src/mir/builder/control_flow/joinir/merge/mod.rs`
**Integration Point**: Add to existing `verify_joinir_contracts()` function
```rust
#[cfg(debug_assertions)]
fn verify_joinir_contracts(
func: &JoinIRFunction,
boundary: &JoinInlineBoundary,
loop_info: &LoopHeaderPhiInfo,
join_value_space: &JoinValueSpace,
) {
// Existing PHI contract verification
verify_phi_contracts(func, loop_info);
// Phase 205: Add region verification
verify_valueid_regions(boundary, loop_info, join_value_space);
}
#[cfg(debug_assertions)]
fn verify_valueid_regions(
boundary: &JoinInlineBoundary,
loop_info: &LoopHeaderPhiInfo,
join_value_space: &JoinValueSpace,
) {
// 1. Verify boundary inputs are in Param region
for join_id in &boundary.join_inputs {
let region = join_value_space.region_of(*join_id);
if region != Region::Param {
panic!(
"[RegionVerifier] Boundary input {:?} is in {:?} region, expected Param",
join_id, region
);
}
}
// 2. Verify PHI dst are in valid range
for (carrier_name, entry) in &loop_info.carrier_phis {
let region = join_value_space.region_of(entry.phi_dst);
// PHI dst may be in PHI Reserved or early Param range (depending on MirBuilder)
if entry.phi_dst.0 > LOCAL_MAX {
panic!(
"[RegionVerifier] Carrier '{}' PHI dst {:?} exceeds LOCAL_MAX",
carrier_name, entry.phi_dst
);
}
}
// 3. Verify JoinValueSpace internal consistency
if let Err(e) = join_value_space.verify_no_overlap() {
panic!("[RegionVerifier] JoinValueSpace overlap detected: {}", e);
}
}
```
### Task 205-5: Pattern Integration Audit
**Files to Audit**:
1. `pattern1_minimal.rs` - Already correct
2. `pattern2_with_break.rs` - Already correct
3. `pattern3_with_if_phi.rs` - Verify If-PHI lowering
4. `pattern4_with_continue.rs` - Verify UpdateSummary handling
5. `trim_pattern_lowerer.rs` - Verify all call sites
6. `exit_binding.rs` - Verify no raw ValueId usage
7. `common_init.rs` - Verify boundary input allocation
**Audit Checklist**:
- [ ] No raw `ValueId(..)` construction in lowerers
- [ ] All Carrier `join_id` use `alloc_param()`
- [ ] All lowerer intermediate values use `alloc_local()`
- [ ] All `alloc_fn` closures receive `space.local_allocator()`
**Fix Strategy**:
```rust
// ❌ Before (if found):
let temp = ValueId(next_id);
next_id += 1;
// ✅ After:
let temp = join_value_space.alloc_local();
```
### Task 205-6: Testing & Documentation
**Test Cases**:
1. `loop_min_while.hako` (Pattern 1)
2. `loop_with_break.hako` (Pattern 2)
3. `loop_if_phi.hako` (Pattern 3)
4. `loop_continue_pattern4.hako` (Pattern 4)
5. Trim/JsonParser representative case
**Expected Outcome**:
- All 821 tests pass
- No regression
- Debug assertions detect region violations (if any)
**Documentation Updates**:
1. `joinir-architecture-overview.md`:
- Add "ValueId Region Contract" section
- Update Box boundary diagram
- Link to this design doc
2. `CURRENT_TASK.md`:
- Mark Phase 205 complete
- Add handoff notes for Phase 206
## Fail-Fast Principles
### Region Violations
**Principle**: Detect region violations immediately, fail fast with clear error messages.
**Implementation**:
```rust
#[cfg(debug_assertions)]
fn verify_region(&self, id: ValueId, expected: Region) -> Result<(), String> {
let actual = self.region_of(id);
if actual != expected {
// ✅ Clear, actionable error message
return Err(format!(
"ValueId {:?} is in {:?} region, expected {:?}\n\
Hint: Use alloc_param() for loop arguments, alloc_local() for JoinIR values",
id, actual, expected
));
}
Ok(())
}
```
**No Fallback**: If a region violation occurs, panic immediately. Do not:
- Silently remap ValueIds
- Use fallback allocation
- Continue with corrupted state
### Collision Detection
**Principle**: Each ValueId allocated exactly once.
**Implementation**:
```rust
#[cfg(debug_assertions)]
fn check_collision(&self, id: ValueId, role: &str) {
if self.allocated_ids.contains(&id.0) {
panic!(
"[JoinValueSpace] ValueId collision detected!\n\
ID: {:?}\n\
Role: {}\n\
This indicates a bug in JoinIR lowering - contact maintainer",
id, role
);
}
}
```
## Box Boundaries
### SSOT (Single Source of Truth)
**JoinValueSpace is the SSOT for JoinIR ValueId allocation.**
**Boundary Rules**:
1. **Inside JoinIR lowering**: All ValueIds come from JoinValueSpace
2. **Outside JoinIR lowering**: MirBuilder allocates PHI dst independently
3. **Bridge**: `reserve_phi()` synchronizes PHI dst for verification
**Example**:
```rust
// ✅ Correct: JoinIR lowering
let mut join_value_space = JoinValueSpace::new();
let carrier_id = join_value_space.alloc_param(); // Inside SSOT boundary
// ✅ Correct: MirBuilder allocates PHI dst
let phi_dst = mir_builder.alloc_value(); // Outside SSOT boundary
// ⚠️ Bridge: Sync for verification
join_value_space.reserve_phi(phi_dst); // Tell JoinValueSpace about external PHI
```
### Allocator Closures
**Pattern**: Pass allocation function to lowerers
```rust
// ✅ Correct pattern:
fn lower_pattern3(
alloc_local: &mut dyn FnMut() -> ValueId, // Receives closure
// ...
) {
let const_id = alloc_local(); // ✅ Uses closure
}
// Call site:
lower_pattern3(
&mut join_value_space.local_allocator(), // ✅ Passes JoinValueSpace closure
// ...
);
```
**Benefits**:
- Lowerer doesn't need direct JoinValueSpace reference
- Maintains Box boundary
- Easy to test with mock allocators
## Success Criteria
Phase 205 is complete when:
1. Design document created (this file)
2. JoinValueSpace has collision detection & region verification (debug-only)
3. RegionVerifier integrated into merge verification
4. All patterns (P1/P2/P3/P4) audited for raw ValueId usage
5. All tests pass (821 tests, 0 regression)
6. Documentation updated (overview + CURRENT_TASK)
## Future Work (Phase 206+)
### Potential Enhancements
1. **Runtime Region Tracking** (if needed):
- Track ValueId Role mapping for better error messages
- Example: "ValueId(105) is carrier 'sum', expected local region"
2. **Region Statistics**:
- Report param/local/PHI usage per pattern
- Detect potential region exhaustion early
3. **Contract Testing**:
- Generate test cases that deliberately violate regions
- Verify debug assertions trigger correctly
4. **Allocator Modes**:
- Dense allocation (minimize gaps)
- Sparse allocation (easier debugging)
- Deterministic allocation (reproducible builds)
## References
- **Phase 201**: JoinValueSpace initial implementation
- **Phase 204**: PHI contract verification (dst overwrite, inputs sanity)
- **Box-First Principle**: CLAUDE.md Section "箱理論Box-First"
## Appendix: Region Math
### Current Capacity
| Region | Range | Capacity | Typical Usage |
|--------|-------|----------|---------------|
| PHI Reserved | 0-99 | 100 IDs | 1-5 PHIs per loop |
| Param | 100-999 | 900 IDs | 3-10 params per loop |
| Local | 1000-99999 | 99000 IDs | 10-1000 values per loop |
### Overflow Scenarios
**Param Overflow** (highly unlikely):
- Would require 900+ loop parameters
- Current max observed: ~10 params (Pattern 3)
- Debug assertion will catch at param #900
**Local Overflow** (theoretical):
- Would require 99000+ JoinIR instructions
- Current max observed: ~100 instructions (JsonParser)
- Would indicate pathological code generation
**PHI Overflow** (impossible):
- PHI dst allocated by MirBuilder, not JoinValueSpace
- JoinValueSpace only verifies PHI dst <= 99
- If violated, indicates bug in MirBuilder
## Version History
- **2025-12-09**: Initial design document (Claude Sonnet 4.5)
- **Phase 205-1**: Created as part of ValueId region boundary task
Status: Active
Scope: ValueId Regions 設計JoinIR/ValueId ライン