Files
hakorune/docs/development/current/main/phase190-number-update-design.md
nyash-codex 1af92d8aea docs: Phase 190-impl-D complete - NumberAccumulation PHI wiring fixed
- Fixed ValueId collision between body-local and carrier params
- Added ExitLine contract verifier (debug assertions)
- Updated test files to use Main box
- E2E verified: atoi→12, parse_number→123

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 03:07:15 +09:00

983 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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 190: NumberAccumulation Update Design (Doc-Only)
**Status**: Design Complete (Code TBD)
**Date**: 2025-12-09
**Goal**: Design JoinIR support for `result = result * 10 + digit` style updates
---
## Section 1: Target Loops and RHS Patterns
### 1.1 JsonParserBox._atoi (lines 436-467)
**Loop Pattern**:
```nyash
local v = 0
local digits = "0123456789"
loop(i < n) {
local ch = s.substring(i, i+1)
if ch < "0" || ch > "9" { break }
local pos = digits.indexOf(ch)
if pos < 0 { break }
v = v * 10 + pos // ← NumberAccumulation pattern
i = i + 1
}
```
**AST Form of Update**:
```
Assign(
lhs = "v",
rhs = BinaryOp(
op = Add,
left = BinaryOp(
op = Mul,
left = Variable("v"),
right = Literal(Integer(10))
),
right = Variable("pos")
)
)
```
**Characteristics**:
- LHS appears exactly once in RHS (in left-most multiplication)
- Base: 10 (constant)
- Addend: `pos` (loop-local variable)
- Type: Integer (MirType::Integer)
### 1.2 JsonParserBox._parse_number (lines 106-142)
**Loop Pattern**:
```nyash
local num_str = ""
local digits = "0123456789"
loop(p < s.length()) {
local ch = s.substring(p, p+1)
local digit_pos = digits.indexOf(ch)
if digit_pos < 0 {
break
}
num_str = num_str + ch // ← StringAppendChar (already supported)
p = p + 1
}
```
**Note**: This is string accumulation (`num_str + ch`), not number accumulation.
Already handled by existing `StringAppendChar` / `AccumulationLike`.
### 1.3 Other Instances in Codebase
From grep results:
1. **apps/tests/phase189_atoi_mini.hako:10**
- `result = result * 10 + i`
- Simplified test case
2. **apps/tests/phase183_p2_atoi.hako:25**
- `result = result * 10 + digit`
- Similar pattern
3. **apps/tests/phase183_p2_parse_number.hako:28**
- `result = result * 10 + digit_pos`
- Same pattern with different variable name
4. **lang/src/compiler/builder/ssa/exit_phi/break_finder.hako:277**
- `result = result * 10 + (BreakFinderBox._char_to_digit(ch))`
- Includes method call on RHS (Complex)
### 1.4 Canonical Form Requirements
**Safe NumberAccumulation Pattern**:
```
lhs = lhs * BASE + addend
```
**Where**:
- `lhs`: carrier variable (appears exactly 1 time in RHS)
- `BASE`: integer constant (typically 10)
- `addend`: one of:
- Variable (loop-local or carrier)
- Constant
- **NOT** method call (→ Complex)
**Variants**:
- `lhs = lhs * BASE - addend` (subtraction also allowed)
- Base can be any integer constant (10, 2, 16, etc.)
---
## Section 2: Safe Pattern Candidates
### 2.1 Whitelist Criteria
A NumberAccumulation update is **safe** if:
1. **Type Check**: Carrier type is `MirType::Integer`
2. **Structure Check**: AST matches `lhs = Add(Mul(lhs, Const(base)), addend)`
3. **LHS Occurrence**: `lhs` appears exactly 1 time in RHS (in multiplication)
4. **Base Check**: Base is integer constant (not variable)
5. **Addend Check**: Addend is one of:
- `Variable(name)` - loop-local or carrier
- `Const(n)` - integer constant
6. **No Method Calls**: Addend does not contain method calls
### 2.2 Reject as Complex
Mark as `UpdateKind::Complex` if:
- LHS appears 2+ times in RHS
- Base is variable (not constant)
- Addend contains method call (e.g., `_char_to_digit(ch)`)
- Addend is complex expression (nested BinaryOp)
- Type is not Integer
- LoopBodyLocal appears in addend (Phase 5 未サポート)
### 2.3 Pattern Matrix
| Pattern | LHS Count | Base | Addend | Decision |
|---------|-----------|------|--------|----------|
| `v = v * 10 + pos` | 1 | Const(10) | Variable | NumberAccumulation |
| `v = v * 10 + 5` | 1 | Const(10) | Const(5) | NumberAccumulation |
| `v = v * base + x` | 1 | Variable | Variable | Complex |
| `v = v * 10 + f(x)` | 1 | Const(10) | MethodCall | Complex |
| `v = v * 10 + v` | 2 | Const(10) | Variable | Complex |
---
## Section 3: UpdateKind::NumberAccumulation Definition
### 3.1 Enum Extension
**Current** (Phase 170-C-2):
```rust
pub enum UpdateKind {
CounterLike,
AccumulationLike,
Other,
}
```
**Proposed** (Phase 190):
```rust
pub enum UpdateKind {
CounterLike,
AccumulationLike,
/// Phase 190: Number accumulation: result = result * base + addend
///
/// Typical pattern: digit expansion (atoi, parse_number)
/// Example: v = v * 10 + digit
NumberAccumulation { base: i64 },
/// Phase 178+: String append patterns (already implemented)
StringAppendChar,
StringAppendLiteral,
/// Complex or unrecognized patterns
Complex,
/// Deprecated
Other,
}
```
### 3.2 UpdateKind::name() Extension
```rust
impl UpdateKind {
pub fn name(&self) -> &'static str {
match self {
UpdateKind::CounterLike => "CounterLike",
UpdateKind::AccumulationLike => "AccumulationLike",
UpdateKind::NumberAccumulation { base } => "NumberAccumulation",
UpdateKind::StringAppendChar => "StringAppendChar",
UpdateKind::StringAppendLiteral => "StringAppendLiteral",
UpdateKind::Complex => "Complex",
UpdateKind::Other => "Other",
}
}
}
```
### 3.3 Alternative: Subfield on AccumulationLike
Instead of new enum variant, could extend AccumulationLike:
```rust
pub enum UpdateKind {
CounterLike,
AccumulationLike {
style: AccumulationStyle,
},
Complex,
Other,
}
pub enum AccumulationStyle {
Simple, // result = result + x
NumberDigit { base: i64 }, // result = result * base + x
StringAppend, // result = result + "literal"
}
```
**Decision**: Use dedicated `NumberAccumulation { base: i64 }` for clarity.
Simpler to pattern-match, clearer intent.
---
## Section 4: classify_number_update Pseudocode
### 4.1 Algorithm Overview
**Input**:
- `lhs: &str` - carrier variable name
- `rhs_ast: &ASTNode` - RHS expression AST
**Output**:
- `UpdateKind` - classified update pattern
**Steps**:
1. Check if RHS is `BinaryOp(Add, left, right)`
2. Check if `left` is `BinaryOp(Mul, mul_left, mul_right)`
3. Verify `mul_left` is `Variable(lhs)` (LHS appears once)
4. Verify `mul_right` is `Literal(Integer(base))`
5. Classify `right` (addend):
- `Const(n)` → NumberAccumulation
- `Variable(name)` → NumberAccumulation
- `MethodCall/Other` → Complex
### 4.2 Pseudocode
```rust
fn classify_number_update(lhs: &str, rhs_ast: &ASTNode) -> UpdateKind {
// Step 1: Check outer addition
let BinaryOp { op: Add, left: mul_expr, right: addend } = rhs_ast else {
return UpdateKind::Complex;
};
// Step 2: Check inner multiplication
let BinaryOp { op: Mul, left: mul_left, right: mul_right } = mul_expr else {
return UpdateKind::Complex;
};
// Step 3: Verify LHS appears in multiplication
let Variable { name: lhs_name } = mul_left else {
return UpdateKind::Complex;
};
if lhs_name != lhs {
return UpdateKind::Complex;
}
// Step 4: Extract base constant
let Literal { value: Integer(base) } = mul_right else {
return UpdateKind::Complex; // base is not constant
};
// Step 5: Classify addend
match addend {
// Variable or Const: NumberAccumulation
Literal { value: Integer(_) } | Variable { .. } => {
UpdateKind::NumberAccumulation { base: *base }
}
// Method call or complex expression: Complex
MethodCall { .. } | BinaryOp { .. } | Call { .. } => {
UpdateKind::Complex
}
_ => UpdateKind::Complex,
}
}
```
### 4.3 LHS Occurrence Check
**Goal**: Ensure LHS appears exactly 1 time in RHS.
**Implementation**:
```rust
fn count_lhs_occurrences(lhs: &str, rhs: &ASTNode) -> usize {
match rhs {
Variable { name } if name == lhs => 1,
BinaryOp { left, right, .. } => {
count_lhs_occurrences(lhs, left) + count_lhs_occurrences(lhs, right)
}
MethodCall { receiver, args, .. } => {
let mut count = count_lhs_occurrences(lhs, receiver);
for arg in args {
count += count_lhs_occurrences(lhs, arg);
}
count
}
_ => 0,
}
}
```
**Usage**:
```rust
fn classify_number_update(lhs: &str, rhs_ast: &ASTNode) -> UpdateKind {
// FIRST: Check LHS occurrence count
let lhs_count = count_lhs_occurrences(lhs, rhs_ast);
if lhs_count != 1 {
return UpdateKind::Complex;
}
// THEN: Check structure
// ... (rest of classify_number_update logic)
}
```
---
## Section 5: Pattern2/4 can_lower Specification
### 5.1 Current Behavior
**Pattern2 can_lower** (Phase 176+):
- Accepts: CounterLike, AccumulationLike, StringAppendChar, StringAppendLiteral
- Rejects: Complex → `[joinir/freeze]`
**Pattern4 can_lower** (Phase 33-19+):
- Similar criteria to Pattern2
### 5.2 Proposed Extension
**Pattern2 can_lower** (Phase 190+):
```rust
fn can_lower_carrier_updates(updates: &HashMap<String, UpdateExpr>) -> bool {
for (_name, update_expr) in updates {
let kind = classify_update_kind(update_expr);
match kind {
// Whitelist: Simple patterns
UpdateKind::CounterLike
| UpdateKind::AccumulationLike
| UpdateKind::StringAppendChar
| UpdateKind::StringAppendLiteral
| UpdateKind::NumberAccumulation { .. } // ← NEW!
=> { /* OK */ }
// Fail-Fast: Complex patterns
UpdateKind::Complex => {
eprintln!("[joinir/freeze] Complex carrier update detected");
return false;
}
UpdateKind::Other => {
eprintln!("[joinir/freeze] Unknown update pattern");
return false;
}
}
}
true
}
```
### 5.3 Type Constraint
**NumberAccumulation Type Check**:
```rust
fn verify_number_accumulation_type(carrier: &CarrierVar) -> bool {
// Phase 190: NumberAccumulation requires Integer type
// String types must use StringAppendChar/StringAppendLiteral
match carrier.mir_type {
MirType::Integer => true,
_ => {
eprintln!("[joinir/freeze] NumberAccumulation requires Integer type, got {:?}",
carrier.mir_type);
false
}
}
}
```
### 5.4 LoopBodyLocal Handling
**Phase 5 未サポート**:
```rust
fn check_loopbodylocal_constraint(addend: &UpdateRhs, loop_scope: &LoopScopeShape) -> bool {
match addend {
UpdateRhs::Variable(name) => {
// Check if variable is LoopBodyLocal
if loop_scope.body_locals.contains(name) {
eprintln!("[joinir/freeze] LoopBodyLocal in NumberAccumulation not supported yet");
return false;
}
true
}
_ => true,
}
}
```
### 5.5 Fallback Strategy
**Fail-Fast Principle**:
- NumberAccumulation detection failure → `Complex``[joinir/freeze]`
- **NO** silent fallback to LoopBuilder (already deleted)
- **NO** new fallback paths
**Error Message Template**:
```
[joinir/freeze] NumberAccumulation pattern not supported:
carrier: v
reason: addend contains method call
suggestion: extract method call to loop-local variable
```
---
## Section 6: CarrierUpdateLowerer Pseudocode
### 6.1 Responsibility Assignment
**CarrierUpdateLowerer** (Phase 176+):
- Input: `CarrierVar`, `UpdateExpr`, `JoinIRBuilder`
- Output: Emits JoinIR instructions for carrier update
**Current Support**:
- CounterLike: `dst = lhs + 1`
- AccumulationLike: `dst = lhs + rhs`
- StringAppendChar: (BoxCall to StringBox.append)
**Phase 190 Extension**:
- NumberAccumulation: `tmp = lhs * base; dst = tmp + addend`
### 6.2 JoinIR Emission Design
**Option A: 2-Instruction Approach** (Recommended)
```rust
fn emit_number_accumulation(
builder: &mut JoinIRBuilder,
carrier: &CarrierVar,
base: i64,
addend: &UpdateRhs,
) -> ValueId {
// Step 1: Multiply by base
// tmp = lhs * base
let base_value = builder.alloc_value();
builder.emit(JoinInst::Const {
dst: base_value,
value: ConstValue::Integer(base),
});
let mul_result = builder.alloc_value();
builder.emit(JoinInst::BinOp {
dst: mul_result,
op: BinOpKind::Mul,
left: carrier.join_id.unwrap(), // Current value from LoopHeader PHI
right: base_value,
});
// Step 2: Add addend
// dst = tmp + addend
let addend_value = emit_addend_value(builder, addend);
let result = builder.alloc_value();
builder.emit(JoinInst::BinOp {
dst: result,
op: BinOpKind::Add,
left: mul_result,
right: addend_value,
});
result
}
fn emit_addend_value(builder: &mut JoinIRBuilder, addend: &UpdateRhs) -> ValueId {
match addend {
UpdateRhs::Const(n) => {
let val = builder.alloc_value();
builder.emit(JoinInst::Const {
dst: val,
value: ConstValue::Integer(*n),
});
val
}
UpdateRhs::Variable(name) => {
// Look up variable in boundary.join_inputs or condition_bindings
builder.lookup_variable(name)
}
_ => unreachable!("Complex addend should be rejected in can_lower"),
}
}
```
**Option B: Single Complex Expression** (Not Recommended)
```rust
// Would require JoinInst::ComplexExpr or similar
// Violates "flat instruction" principle of JoinIR
// NOT RECOMMENDED
```
**Decision**: Use Option A (2-instruction approach).
- Pros: Clean separation, easy to optimize later, follows JoinIR flat instruction principle
- Cons: None
### 6.3 Type Constraint Enforcement
```rust
impl CarrierUpdateLowerer {
pub fn emit_update(
&self,
builder: &mut JoinIRBuilder,
carrier: &CarrierVar,
update_expr: &UpdateExpr,
) -> Result<ValueId, String> {
match classify_update_kind(update_expr) {
UpdateKind::NumberAccumulation { base } => {
// Type check
if carrier.mir_type != MirType::Integer {
return Err(format!(
"NumberAccumulation requires Integer type, got {:?}",
carrier.mir_type
));
}
// Emit instructions
Ok(self.emit_number_accumulation(builder, carrier, base, &extract_addend(update_expr)))
}
// ... other cases
}
}
}
```
### 6.4 Name Dependency Prohibition
**Phase 170-C-1 Principle**: No name-based heuristics in lowering.
**Enforcement**:
```rust
// ✅ GOOD: Structural detection
fn is_number_accumulation(update_expr: &UpdateExpr) -> bool {
matches!(
update_expr,
UpdateExpr::BinOp {
op: BinOpKind::Add,
lhs: _,
rhs: UpdateRhs::Variable(_) | UpdateRhs::Const(_),
}
)
}
// ❌ BAD: Name-based detection
fn is_number_accumulation(carrier_name: &str) -> bool {
carrier_name.contains("result") || carrier_name.contains("v")
}
```
**Rationale**:
- Variable names are user-controlled and unreliable
- Structural AST analysis is robust and refactoring-safe
- Follows "箱理論" separation of concerns
### 6.5 Integration with LoopHeaderPhiBuilder
**Phase 33-22 Context**:
- LoopHeader PHI creates SSOT for carrier current value
- `carrier.join_id` points to LoopHeader PHI dst
**Usage in NumberAccumulation**:
```rust
fn emit_number_accumulation(
builder: &mut JoinIRBuilder,
carrier: &CarrierVar,
base: i64,
addend: &UpdateRhs,
) -> ValueId {
// Use carrier.join_id (LoopHeader PHI dst) as current value
let current_value = carrier.join_id.expect("LoopHeader PHI should set join_id");
// tmp = current_value * base
let base_const = builder.emit_const(ConstValue::Integer(base));
let mul_result = builder.emit_binop(BinOpKind::Mul, current_value, base_const);
// dst = tmp + addend
let addend_value = emit_addend_value(builder, addend);
builder.emit_binop(BinOpKind::Add, mul_result, addend_value)
}
```
**Invariant**: Never directly access `carrier.host_id` in JoinIR emission.
Only use `carrier.join_id` (JoinIR-local ValueId).
---
## Section 7: Implementation Phases
### Phase 190-impl-A: Core Detection (2-3 commits)
**Goal**: Extend LoopUpdateAnalyzer to detect NumberAccumulation patterns.
**Tasks**:
1. Add `UpdateKind::NumberAccumulation { base: i64 }`
2. Implement `classify_number_update()` in LoopUpdateAnalyzer
3. Add `count_lhs_occurrences()` helper
4. Unit tests (5+ cases covering Section 2.3 matrix)
**Success Criteria**:
- `_atoi` loop detected as NumberAccumulation { base: 10 }
- Complex patterns (method calls) detected as Complex
- Type safety: Only Integer carriers allowed
### Phase 190-impl-B: CarrierUpdateLowerer Extension (2-3 commits)
**Goal**: Emit JoinIR for NumberAccumulation updates.
**Tasks**:
1. Extend `CarrierUpdateLowerer::emit_update()` with NumberAccumulation branch
2. Implement 2-instruction emission (mul + add)
3. Type constraint enforcement
4. Unit tests (3+ cases)
**Success Criteria**:
- Correct JoinIR emission: `tmp = v * 10; result = tmp + digit`
- Type error on non-Integer carriers
- Integration with LoopHeader PHI (carrier.join_id)
### Phase 190-impl-C: Pattern2/4 Integration (1-2 commits)
**Goal**: Enable NumberAccumulation in Pattern2/4 can_lower.
**Tasks**:
1. Update `can_lower_carrier_updates()` whitelist
2. Add NumberAccumulation test case to Pattern2
3. Verify Fail-Fast for Complex patterns
**Success Criteria**:
- `phase189_atoi_mini.hako` passes with JoinIR
- Complex patterns (with method calls) rejected at can_lower
- `[joinir/freeze]` message for unsupported cases
### Phase 190-impl-D: E2E Validation (1 commit)
**Goal**: Real-world JsonParserBox._atoi working via JoinIR.
**Tasks**:
1. Enable JoinIR for `_atoi` method
2. Verify MIR output correctness
3. Runtime test: parse "12345" → 12345
**Success Criteria**:
- JsonParserBox._atoi compiles via JoinIR Pattern2
- Correct MIR: multiply-add sequence
- Runtime correctness: atoi tests pass
---
## Section 8: Future Extensions
### 8.1 Other Bases
**Current**: `base` is extracted from AST (any integer constant).
**Example**:
- Binary: `v = v * 2 + bit`
- Hex: `v = v * 16 + hex_digit`
**No change needed**: Design already supports arbitrary bases.
### 8.2 Subtraction Variant
**Pattern**: `v = v * 10 - offset`
**Extension**:
```rust
fn classify_number_update(lhs: &str, rhs_ast: &ASTNode) -> UpdateKind {
match rhs_ast {
BinaryOp { op: Add | Sub, left: mul_expr, right: addend } => {
// ... (same logic for both Add and Sub)
}
_ => UpdateKind::Complex,
}
}
```
**Decision**: Support both Add and Sub in Phase 190-impl-A.
### 8.3 Complex Addends
**Examples**:
- `v = v * 10 + (ch - '0')`
- `v = v * 10 + digits.indexOf(ch)`
**Strategy**:
- Phase 190: Reject as Complex (Fail-Fast)
- Phase 191+: Extend to handle BinaryOp/MethodCall in addend
- Emit extra JoinIR instructions for addend computation
- Store addend in temporary ValueId
**Not in scope for Phase 190**.
### 8.4 Multi-Base Patterns
**Example** (unlikely):
```nyash
v1 = v1 * 10 + d1
v2 = v2 * 16 + d2
```
**Current Design**: Each carrier gets its own `NumberAccumulation { base }`.
Already supports different bases per carrier.
---
## Section 9: Testing Strategy
### 9.1 Unit Tests (LoopUpdateAnalyzer)
**Coverage Matrix**:
| Test Case | Pattern | Expected Kind |
|-----------|---------|---------------|
| `v = v * 10 + pos` | Basic NumberAccumulation | NumberAccumulation { base: 10 } |
| `v = v * 10 + 5` | Const addend | NumberAccumulation { base: 10 } |
| `v = v * 2 + bit` | Binary base | NumberAccumulation { base: 2 } |
| `v = v * 10 - offset` | Subtraction | NumberAccumulation { base: 10 } |
| `v = v * base + x` | Variable base | Complex |
| `v = v * 10 + f(x)` | Method call | Complex |
| `v = v * 10 + v` | LHS appears 2x | Complex |
**Implementation**:
```rust
#[test]
fn test_number_accumulation_basic() {
let ast = parse("v = v * 10 + pos");
let kind = LoopUpdateAnalyzer::classify_update("v", &ast);
assert!(matches!(kind, UpdateKind::NumberAccumulation { base: 10 }));
}
```
### 9.2 Integration Tests (Pattern2)
**Test Files**:
1. `apps/tests/phase190_number_update_basic.hako`
- Single carrier: `v = v * 10 + i`
- Verify JoinIR emission
2. `apps/tests/phase190_number_update_multi.hako`
- Two carriers: counter + accumulator
- Verify multi-carrier lowering
3. `apps/tests/phase190_number_update_complex.hako`
- Complex pattern (method call)
- Verify Fail-Fast rejection
### 9.3 E2E Tests (JsonParserBox)
**Test Cases**:
1. `test_jsonparser_atoi_simple.hako`
- Input: "123"
- Expected: 123
2. `test_jsonparser_atoi_negative.hako`
- Input: "-456"
- Expected: -456
3. `test_jsonparser_parse_number_min_v2.hako`
- Full `_parse_number` method
- Verify string + number accumulation
**Success Criteria**:
- All tests pass with JoinIR enabled
- Same behavior as LoopBuilder (deleted, but historical behavior)
---
## Section 10: Migration Notes
### 10.1 From LoopBuilder (Deleted)
**Legacy**: LoopBuilder handled all loop patterns (deleted in Phase 170).
**Current**: JoinIR Pattern1-4 handle specific patterns.
**NumberAccumulation Migration**:
- Old: LoopBuilder would blindly emit MIR for any loop
- New: Pattern2/4 detect NumberAccumulation, emit specialized JoinIR
**No fallback needed**: LoopBuilder is deleted.
### 10.2 From Pattern Detection
**Phase 170-C-1**: Name-based heuristics (e.g., `is_typical_index_name`).
**Phase 190**: Structural AST analysis (no name dependency).
**Principle**: Lowering must be name-agnostic, only detection can use names.
### 10.3 Backward Compatibility
**Existing Patterns**:
- CounterLike: `i = i + 1` (unchanged)
- AccumulationLike: `sum = sum + x` (unchanged)
- StringAppendChar: `s = s + ch` (unchanged)
**New Pattern**:
- NumberAccumulation: `v = v * 10 + digit` (additive)
**No Breaking Changes**: All existing patterns continue to work.
---
## Section 11: Open Questions
### Q1: Should we support division?
**Pattern**: `v = v / 10`
**Current**: Only +, -, *, / in BinOpKind.
**Decision**: Out of scope for Phase 190 (no real-world use case found).
### Q2: Should addend be restricted to loop params only?
**Current Design**: Allow any Variable (loop param, outer local, carrier).
**Alternative**: Only allow LoopParam variables in addend.
**Decision**: Current design is more flexible, restrict if problems arise.
### Q3: How to handle floating-point bases?
**Example**: `v = v * 1.5 + x`
**Current**: `base: i64` only supports integers.
**Decision**: Out of scope (no real-world use case in Nyash codebase).
---
## Section 12: Success Metrics
### 12.1 Functional Metrics
- ✅ JsonParserBox._atoi compiles via JoinIR
- ✅ phase189_atoi_mini.hako passes
- ✅ Complex patterns rejected with `[joinir/freeze]`
- ✅ No silent fallbacks (Fail-Fast verified)
### 12.2 Code Quality Metrics
- ✅ No name-based heuristics in lowering
- ✅ Type safety enforced (Integer only)
- ✅ Unit test coverage > 80%
- ✅ Documentation updated (this doc + overview)
### 12.3 Performance Metrics
**Not a goal for Phase 190**: Focus on correctness, not optimization.
**Future**: Phase 191+ can optimize mul+add to single instruction if needed.
---
## Appendix A: AST Examples
### A.1 Basic NumberAccumulation
**Source**:
```nyash
v = v * 10 + pos
```
**AST**:
```
Assignment {
target: Variable { name: "v" },
value: BinaryOp {
operator: Add,
left: BinaryOp {
operator: Mul,
left: Variable { name: "v" },
right: Literal { value: Integer(10) },
},
right: Variable { name: "pos" },
},
}
```
### A.2 Complex Pattern (Rejected)
**Source**:
```nyash
v = v * 10 + digits.indexOf(ch)
```
**AST**:
```
Assignment {
target: Variable { name: "v" },
value: BinaryOp {
operator: Add,
left: BinaryOp {
operator: Mul,
left: Variable { name: "v" },
right: Literal { value: Integer(10) },
},
right: MethodCall {
receiver: Variable { name: "digits" },
method: "indexOf",
args: [Variable { name: "ch" }],
},
},
}
```
**Decision**: Rejected as Complex (MethodCall in addend).
---
## Appendix B: References
### B.1 Related Phases
- **Phase 170-C-1**: Carrier name heuristics
- **Phase 170-C-2**: LoopUpdateSummary skeleton
- **Phase 176**: Multi-carrier lowering
- **Phase 178**: String update detection
### B.2 Related Files
**Core**:
- `src/mir/join_ir/lowering/loop_update_summary.rs`
- `src/mir/join_ir/lowering/loop_update_analyzer.rs`
- `src/mir/join_ir/lowering/carrier_update_lowerer.rs`
**Patterns**:
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
- `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
**Tests**:
- `tools/hako_shared/json_parser.hako` (line 436: _atoi)
- `apps/tests/phase189_atoi_mini.hako`
### B.3 Design Principles
1. **Fail-Fast**: Reject Complex patterns explicitly
2. **Whitelist Control**: Only safe patterns allowed
3. **Type Safety**: Integer-only for NumberAccumulation
4. **Name Agnostic**: No name-based lowering
5. **SSOT**: LoopHeader PHI as single source of truth
---
## Revision History
- **2025-12-09**: Initial design (Section 1-12)
- **2025-12-09**: Phase 190-impl 完了
- Phase 190-impl-A: LoopUpdateAnalyzer に NumberAccumulation 検出実装
- Phase 190-impl-B: CarrierUpdateLowerer で 2-instruction emission 実装
- Phase 190-impl-C: Pattern2 can_lower ホワイトリスト更新
- Phase 190-impl-D: E2E 検証成功 + PHI 配線修正
- **バグ発見**: body-local と carrier の ValueId 衝突問題
- **修正**: `body_local_start_offset = env.len() + carrier_info.carriers.len()` で安全な ValueId 空間分割
- **E2E 結果**: `phase190_atoi_impl.hako` → 12 ✅、`phase190_parse_number_impl.hako` → 123 ✅
- **制約**: body-local 変数 assignment は JoinIR 未対応Phase 186 残タスク)
- ExitLine contract Verifier 追加(`#[cfg(debug_assertions)]`