feat(anf): Phase 145 P0/P1/P2 - ANF (A-Normal Form) transformation
Implement ANF transformation for impure expressions to fix evaluation order: Phase 145 P0 (Skeleton): - Add anf/ module with contract/plan/execute 3-layer separation - AnfDiagnosticTag, AnfOutOfScopeReason, AnfPlan enums - Stub execute_box (always returns Ok(None)) - 11 unit tests pass Phase 145 P1 (Minimal success): - String.length() whitelist implementation - BinaryOp + MethodCall pattern: x + s.length() → t = s.length(); result = x + t - Exit code 12 verification (VM + LLVM EXE) - 17 unit tests pass Phase 145 P2 (Generalization): - Recursive ANF for compound expressions - Left-to-right, depth-first evaluation order - Patterns: x + s.length() + z, s1.length() + s2.length() - ANF strict mode (HAKO_ANF_STRICT=1) - Diagnostic tags (joinir/anf/*) - 21 unit tests pass, 0 regression Also includes Phase 143 P2 (else symmetry) completion. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -2,12 +2,34 @@
|
||||
|
||||
## Next (planned)
|
||||
|
||||
- Phase 141 P2+: Call/MethodCall 対応(effects + typing を分離して段階投入)
|
||||
- Phase 143-loopvocab P2+: Loop-If-Exit パターン拡張(else/対称branch対応)
|
||||
- P2: else/対称branch 対応(continue/break の混合パターン)
|
||||
- P3+: 条件スコープ拡張(impure expressions 対応)
|
||||
- Phase 141 P2+: Call/MethodCall 対応(effects + typing を分離して段階投入、ANF を前提に順序固定)
|
||||
- Phase 143-loopvocab P3+: 条件スコープ拡張(impure conditions 対応)
|
||||
- Phase 146-147(planned): Loop/If condition への ANF 適用(順序固定と診断の横展開)
|
||||
- 詳細: `docs/development/current/main/30-Backlog.md`
|
||||
|
||||
## 2025-12-19:Phase 145-anf P0/P1/P2 完了 ✅
|
||||
|
||||
- SSOT docs:
|
||||
- `docs/development/current/main/phases/phase-145-anf/README.md`
|
||||
- `docs/development/current/main/phases/phase-144-anf/INSTRUCTIONS.md`
|
||||
- 実装 SSOT:
|
||||
- `src/mir/control_tree/normalized_shadow/anf/`
|
||||
- 入口(接続箇所 SSOT): `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs`
|
||||
- 環境変数:
|
||||
- `HAKO_ANF_DEV=1`(dev-only: ANF 有効化)
|
||||
- `HAKO_ANF_STRICT=1`(dev-only: ANF fail-fast)
|
||||
- Fixtures & smokes(VM/LLVM EXE parity):
|
||||
- `apps/tests/phase145_p1_anf_length_min.hako` → exit 12
|
||||
- `apps/tests/phase145_p2_compound_expr_binop_min.hako` → exit 18
|
||||
- `apps/tests/phase145_p2_compound_expr_double_intrinsic_min.hako` → exit 5
|
||||
|
||||
## 2025-12-19:Phase 143-loopvocab P2 完了 ✅
|
||||
|
||||
- 対象: else 対称化(B-C / C-B)
|
||||
- Fixtures & smokes(VM/LLVM EXE parity):
|
||||
- `apps/tests/phase143_p2_loop_true_if_bc_min.hako` → exit 8
|
||||
- `apps/tests/phase143_p2_loop_true_if_cb_min.hako` → exit 9
|
||||
|
||||
## 2025-12-19:Phase 143 実行系契約修正 ✅
|
||||
|
||||
**問題**: normalized_helpers が env params を `ValueId(1,2...)` で割り当てていた(PHI Reserved 領域 0-99)。
|
||||
|
||||
@ -46,14 +46,12 @@ Related:
|
||||
- Smoke: VM + LLVM EXE
|
||||
- Out-of-scope は `Ok(None)` のまま
|
||||
|
||||
- **Phase 143-loopvocab P2(planned): else 対応(break/continue 対称化)**
|
||||
- 対象: `if(cond){break}else{continue}` と `if(cond){continue}else{break}` を追加
|
||||
- 実装:
|
||||
- LoopIfExitShape で `has_else=true` + symmetric `then/else_` を許可
|
||||
- Contract で 4パターンを明示(P0: no-else, P1: no-else+continue, P2: with-else)
|
||||
- Fixtures: 2本(対称ケース)
|
||||
- Smoke: VM/LLVM EXE
|
||||
- 完了で「語彙として完成」に寄せる
|
||||
(DONE)Phase 143-loopvocab P2: else 対称化(B-C / C-B)
|
||||
- 記録: `docs/development/current/main/10-Now.md`
|
||||
|
||||
- **Phase 143-loopvocab P3+(planned): impure conditions 対応**
|
||||
- 目的: `if(cond_impure) break/continue` を ANF/順序固定の上で段階投入する
|
||||
- 方針: Phase 145-anf の契約(hoist + left-to-right)を条件式にも適用
|
||||
|
||||
- **real-app loop regression の横展開(VM + LLVM EXE)**
|
||||
- ねらい: 実コード由来ループを 1 本ずつ最小抽出して fixture/smoke で固定する(段階投入)。
|
||||
|
||||
1967
docs/development/current/main/phases/phase-144-anf/INSTRUCTIONS.md
Normal file
1967
docs/development/current/main/phases/phase-144-anf/INSTRUCTIONS.md
Normal file
File diff suppressed because it is too large
Load Diff
255
docs/development/current/main/phases/phase-145-anf/README.md
Normal file
255
docs/development/current/main/phases/phase-145-anf/README.md
Normal file
@ -0,0 +1,255 @@
|
||||
# Phase 145 P0: ANF (A-Normal Form) Skeleton Implementation
|
||||
|
||||
**Status**: Complete
|
||||
**Date**: 2025-12-19
|
||||
**Purpose**: Establish 3-layer ANF architecture (contract/plan/execute) without changing existing behavior
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Phase 145 P0 implements the skeleton for ANF (A-Normal Form) transformation in Normalized JoinIR, following the Phase 143 pattern of 3-layer separation (contract/plan/execute). **Existing behavior is unchanged** (P0 is non-invasive).
|
||||
|
||||
**Key Constraint**: execute_box always returns `Ok(None)` (stub), ensuring 0 regression.
|
||||
|
||||
**Next Steps**: P1 (String.length() hoist), P2 (compound expression ANF).
|
||||
|
||||
---
|
||||
|
||||
## Implementation Summary
|
||||
|
||||
### Files Created (5 + 1 doc)
|
||||
|
||||
**New Module** (`src/mir/control_tree/normalized_shadow/anf/`):
|
||||
1. `mod.rs` (~30 lines) - Module entry point + re-exports
|
||||
2. `contract.rs` (~200 lines) - 3 enums + 2 tests
|
||||
- `AnfDiagnosticTag` (OrderViolation, PureRequired, HoistFailed)
|
||||
- `AnfOutOfScopeReason` (ContainsCall, ContainsMethodCall, ...)
|
||||
- `AnfPlan` (requires_anf, impure_count)
|
||||
3. `plan_box.rs` (~200 lines) - AST walk + 4 tests
|
||||
- `plan_expr()`: Detect impure subexpressions (Call/MethodCall)
|
||||
- `is_pure()`: Helper for quick pure/impure discrimination
|
||||
4. `execute_box.rs` (~80 lines) - Stub + 1 test
|
||||
- `try_execute()`: Always returns `Ok(None)` (P0 stub)
|
||||
5. `README.md` (~100 lines) - Module architecture documentation
|
||||
|
||||
**Documentation**:
|
||||
6. `docs/development/current/main/phases/phase-145-anf/README.md` (this file)
|
||||
|
||||
### Files Modified (3)
|
||||
|
||||
1. `src/mir/control_tree/normalized_shadow/mod.rs` (+1 line)
|
||||
- Added `pub mod anf;`
|
||||
|
||||
2. `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs` (+23 lines)
|
||||
- Added ANF routing at Line 54-76 (before out_of_scope_reason check)
|
||||
- Dev-only (`HAKO_ANF_DEV=1`)
|
||||
- Fallback to legacy when execute_box returns None
|
||||
|
||||
3. `src/config/env/joinir_dev.rs` (+26 lines)
|
||||
- Added `anf_dev_enabled()` function
|
||||
- Environment variable: `HAKO_ANF_DEV=1`
|
||||
|
||||
---
|
||||
|
||||
## Architecture (Box-First, 3-layer separation)
|
||||
|
||||
### Layer 1: contract.rs - Diagnostic tags & plan structure (SSOT)
|
||||
|
||||
**Responsibility**:
|
||||
- Define `AnfDiagnosticTag` enum (future error categorization)
|
||||
- Define `AnfOutOfScopeReason` enum (graceful Ok(None) fallback)
|
||||
- Define `AnfPlan` struct (requires_anf, impure_count)
|
||||
|
||||
**Design Pattern**: Enum discrimination (prevents if-branch explosion)
|
||||
|
||||
### Layer 2: plan_box.rs - AST pattern detection
|
||||
|
||||
**Responsibility**:
|
||||
- Walk AST to detect impure subexpressions (Call/MethodCall)
|
||||
- Build `AnfPlan` indicating what transformation is needed
|
||||
- Does NOT perform transformation (separation of concerns)
|
||||
|
||||
**API**:
|
||||
```rust
|
||||
pub fn plan_expr(
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Result<Option<AnfPlan>, AnfOutOfScopeReason>
|
||||
```
|
||||
|
||||
**Returns**:
|
||||
- `Ok(Some(plan))`: Expression in scope (plan.requires_anf indicates if ANF needed)
|
||||
- `Ok(None)`: Expression out-of-scope (unknown AST node type)
|
||||
- `Err(reason)`: Expression explicitly out-of-scope (ContainsCall/ContainsMethodCall)
|
||||
|
||||
### Layer 3: execute_box.rs - ANF transformation execution (P0: stub)
|
||||
|
||||
**Responsibility**:
|
||||
- Execute ANF transformation for expressions that require it (per AnfPlan)
|
||||
- P0: Always returns `Ok(None)` (existing behavior unchanged)
|
||||
- P1+: Implement hoist + rebuild AST + lower
|
||||
|
||||
**API**:
|
||||
```rust
|
||||
pub fn try_execute(
|
||||
plan: &AnfPlan,
|
||||
ast: &ASTNode,
|
||||
env: &mut BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String>
|
||||
```
|
||||
|
||||
**Returns**:
|
||||
- `Ok(Some(vid))`: ANF transformation succeeded (P1+)
|
||||
- `Ok(None)`: Transformation not attempted (P0 stub)
|
||||
- `Err(msg)`: Internal error (strict mode only, P1+)
|
||||
|
||||
---
|
||||
|
||||
## Integration with expr_lowerer_box.rs
|
||||
|
||||
**Location**: `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs`
|
||||
|
||||
**Routing (Line 54-76)**:
|
||||
```rust
|
||||
// Phase 145 P0: ANF routing (dev-only)
|
||||
if crate::config::env::anf_dev_enabled() {
|
||||
use super::super::anf::{AnfPlanBox, AnfExecuteBox};
|
||||
match AnfPlanBox::plan_expr(ast, env) {
|
||||
Ok(Some(plan)) => {
|
||||
match AnfExecuteBox::try_execute(&plan, ast, &mut env.clone(), body, next_value_id)? {
|
||||
Some(vid) => return Ok(Some(vid)), // P1+: ANF succeeded
|
||||
None => {
|
||||
// P0: stub returns None, fallback to legacy
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
eprintln!("[phase145/debug] ANF plan found but execute returned None (P0 stub)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => { /* out-of-scope, continue */ }
|
||||
Err(_reason) => { /* out-of-scope, continue */ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Environment Variable**:
|
||||
- `HAKO_ANF_DEV=1`: Enable ANF routing
|
||||
- Default: ANF routing disabled (0 impact)
|
||||
|
||||
**Debug Logging**:
|
||||
- `[phase145/debug] ANF plan found but execute returned None (P0 stub)`
|
||||
- `[phase145/debug] ANF execute called (P0 stub, returning Ok(None))`
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests (7 total)
|
||||
|
||||
**contract.rs (2 tests)**:
|
||||
- `test_anf_plan_pure`: AnfPlan::pure() construction
|
||||
- `test_anf_plan_impure`: AnfPlan::impure(n) construction
|
||||
|
||||
**plan_box.rs (4 tests)**:
|
||||
- `test_plan_pure_variable`: Variable → pure plan
|
||||
- `test_plan_pure_literal`: Literal → pure plan
|
||||
- `test_plan_pure_binop`: BinaryOp (pure operands) → pure plan
|
||||
- `test_plan_call_out_of_scope`: Call → Err(ContainsCall)
|
||||
|
||||
**execute_box.rs (1 test)**:
|
||||
- `test_execute_stub_returns_none`: P0 stub always returns Ok(None)
|
||||
|
||||
**Regression Tests**:
|
||||
- All existing tests pass (0 regression)
|
||||
- Phase 97/131/143 smoke tests unchanged
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] 5 new files created (anf/ module)
|
||||
- [x] 3 existing files modified (mod.rs, expr_lowerer_box.rs, joinir_dev.rs)
|
||||
- [x] 7 unit tests pass
|
||||
- [x] cargo build --release passes
|
||||
- [x] 0 regression (existing tests unchanged)
|
||||
- [x] Debug log with `HAKO_ANF_DEV=1`
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Phase 145 P1: String.length() hoist (最小成功例)
|
||||
|
||||
**Goal**: Implement ANF transformation for 1 known intrinsic (String.length()).
|
||||
|
||||
**Pattern**:
|
||||
```hako
|
||||
x + s.length()
|
||||
↓ ANF
|
||||
t = s.length()
|
||||
result = x + t
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
- contract.rs: Add hoist_targets to AnfPlan (~50 lines)
|
||||
- plan_box.rs: Whitelist check + BinaryOp pattern detection (~100 lines)
|
||||
- execute_box.rs: Stub → implementation (~150 lines)
|
||||
|
||||
**Fixtures**:
|
||||
- `apps/tests/phase145_p1_anf_length_min.hako` (exit code 12)
|
||||
- `tools/smokes/.../phase145_p1_anf_length_vm.sh`
|
||||
- `tools/smokes/.../phase145_p1_anf_length_llvm_exe.sh`
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- Exit code 12 (VM + LLVM EXE parity)
|
||||
- String.length() hoisted (JoinInst::MethodCall emitted first)
|
||||
- BinaryOp uses temp variable (not direct MethodCall)
|
||||
- Whitelist enforcement (other methods → Ok(None))
|
||||
|
||||
### Phase 145 P2: Compound expression ANF (再帰的線形化)
|
||||
|
||||
**Goal**: Implement recursive ANF for compound expressions (multiple MethodCalls).
|
||||
|
||||
**Patterns**:
|
||||
```hako
|
||||
// Pattern 1: x + s.length() + z
|
||||
// → t1 = s.length(); t2 = x + t1; result = t2 + z
|
||||
|
||||
// Pattern 2: s1.length() + s2.length()
|
||||
// → t1 = s1.length(); t2 = s2.length(); result = t1 + t2
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
- execute_box.rs: Recursive processing (left-to-right, depth-first) (~80 lines)
|
||||
- Diagnostic tags: error_tags.rs integration (~30 lines)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- 2 fixtures pass (exit codes 18, 5)
|
||||
- Left-to-right order preserved
|
||||
- Recursive ANF documented
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
**Design SSOT**:
|
||||
- `docs/development/current/main/phases/phase-144-anf/INSTRUCTIONS.md` - ANF contract definition
|
||||
- `docs/development/current/main/design/normalized-expr-lowering.md` - ExprLowererBox SSOT
|
||||
|
||||
**Related Phases**:
|
||||
- Phase 140: NormalizedExprLowererBox (pure expression lowering)
|
||||
- Phase 143: LoopIfExitContract pattern (3-layer separation inspiration)
|
||||
- Phase 144: ANF docs-only specification
|
||||
|
||||
**Implementation SSOT**:
|
||||
- `src/mir/control_tree/normalized_shadow/anf/README.md` - Module architecture
|
||||
- `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs` - Integration point
|
||||
- `src/config/env/joinir_dev.rs` - Environment variable helpers
|
||||
|
||||
---
|
||||
|
||||
**Revision History**:
|
||||
- 2025-12-19: Phase 145 P0 skeleton implemented (contract/plan/execute separation)
|
||||
Reference in New Issue
Block a user