feat(joinir): Phase 200-B/C/D capture analysis + Phase 201-A reserved_value_ids infra

Phase 200-B: FunctionScopeCaptureAnalyzer implementation
- analyze_captured_vars_v2() with structural loop matching
- CapturedEnv for immutable function-scope variables
- ParamRole::Condition for condition-only variables

Phase 200-C: ConditionEnvBuilder extension
- build_with_captures() integrates CapturedEnv into ConditionEnv
- fn_body propagation through LoopPatternContext to Pattern 2

Phase 200-D: E2E verification
- capture detection working for base, limit, n etc.
- Test files: phase200d_capture_minimal.hako, phase200d_capture_in_condition.hako

Phase 201-A: MirBuilder reserved_value_ids infrastructure
- reserved_value_ids: HashSet<ValueId> field in MirBuilder
- next_value_id() skips reserved IDs
- merge/mod.rs sets/clears reserved IDs around JoinIR merge

Phase 201: JoinValueSpace design document
- Param/Local/PHI disjoint regions design
- API: alloc_param(), alloc_local(), reserve_phi()
- Migration plan for Pattern 1-4 lowerers

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-09 18:32:03 +09:00
parent 3a9b44c4e2
commit 32a91e31ac
24 changed files with 2815 additions and 193 deletions

View File

@ -0,0 +1,25 @@
// Phase 200-B: Minimal atoi with digits capture
static box Main {
main() {
local s = "123"
local digits = "0123456789" // ← Captured var
local i = 0
local v = 0
local n = s.length()
loop(i < n) {
local ch = s.substring(i, i+1)
local pos = digits.indexOf(ch) // ← Uses captured digits
if pos < 0 {
break
}
v = v * 10 + pos
i = i + 1
}
print(v) // Expected: 123
}
}

View File

@ -0,0 +1,25 @@
// Phase 200-B: Minimal parse_number with digits capture
static box Main {
main() {
local s = "42abc"
local digits = "0123456789" // ← Captured var
local p = 0
local num_str = ""
local n = s.length()
loop(p < n) {
local ch = s.substring(p, p+1)
local digit_pos = digits.indexOf(ch) // ← Uses captured digits
if digit_pos < 0 {
break
}
num_str = num_str + ch
p = p + 1
}
print(num_str) // Expected: "42"
}
}

View File

@ -0,0 +1,31 @@
// Phase 200-D: Capture variable used in CONDITION (not just body)
// This test verifies that captured variables can be used in break conditions.
//
// Key points:
// - limit is captured (function-scoped constant)
// - Used in break condition: if v > limit { break }
// - This proves ConditionEnv.captured is properly connected
static box Main {
main() {
local limit = 50 // Captured var (used in break condition)
local i = 0
local v = 0
local n = 100 // Loop up to 100 times
// Pattern 2: loop with break using captured var in condition
loop(i < n) {
v = v + 10
i = i + 1
// Break condition uses CAPTURED variable 'limit'
if v > limit {
break
}
}
// v should be 60 (broke when v=60 > limit=50)
print(v) // Expected: 60
}
}

View File

@ -0,0 +1,38 @@
// Phase 200-D: Minimal capture test (Pattern 2)
// This test verifies that captured variables work in JoinIR Pattern 2
// without using substring or other unsupported methods.
//
// Key points:
// - base/offset are captured (function-scoped constants)
// - No substring/indexOf in body-local init (Phase 193 limitation)
// - Simple accumulation using captured value
// - Break condition: i == 100 (never true, just to trigger Pattern 2)
static box Main {
main() {
local base = 10 // Captured var (used in multiplication)
local offset = 5 // Captured var (used in addition)
local i = 0
local v = 0
local n = 3 // Loop 3 times
// Pattern 2: loop with break (break never fires)
loop(i < n) {
// Simple break condition that never fires
if i == 100 {
break
}
// Use captured variable in accumulation
// v = v + base
// For i=0: v = 0 + 10 = 10
// For i=1: v = 10 + 10 = 20
// For i=2: v = 20 + 10 = 30
v = v + base
i = i + 1
}
print(v) // Expected: 30
}
}

View File

@ -0,0 +1,33 @@
// Phase 200-D: Digits accumulation test (no body-local in condition)
// This test verifies captured variable (digits) with string accumulation
// without requiring Pattern 5 body-local promotion.
//
// Key constraints:
// - Loop condition uses only LoopParam (p) and OuterLocal (n)
// - No body-local variables in break/if conditions
// - digits is captured and used in loop body
static box Main {
main() {
local digits = "0123456789" // Captured var
local s = "abc" // Input string
local p = 0
local result = ""
local n = s.length() // n = 3
// Simple loop: iterate exactly n times
loop(p < n) {
local ch = s.substring(p, p + 1)
// Use digits to check if char is a digit (result not used in condition)
local is_digit = digits.indexOf(ch) // digits: captured
// Always append the char (no conditional break)
result = result + ch
p = p + 1
}
print(result) // Expected: "abc"
}
}

View File

@ -0,0 +1,35 @@
// Phase 200-D: Simple digits capture test (Pattern 2 with break)
// This test verifies that captured variables (digits) work in JoinIR Pattern 2.
//
// Key constraints:
// - Loop condition uses only LoopParam (i) and OuterLocal (n)
// - Break condition uses outer-scoped variable (maxIter), NOT body-local
// - digits is captured and used in loop body
static box Main {
main() {
local digits = "0123456789" // Captured var
local s = "12" // Input string
local i = 0
local v = 0
local n = s.length() // n = 2
local maxIter = 10 // Safety limit (outer-scoped, not body-local)
// Pattern 2: loop with break (break condition uses outer var)
loop(i < n) {
// Break if too many iterations (uses outer var, not body-local)
if i > maxIter {
break
}
local ch = s.substring(i, i + 1)
local d = digits.indexOf(ch) // digits: captured
v = v * 10 + d
i = i + 1
}
print(v) // Expected: 12
}
}