Files
hakorune/docs/development/current/main/phases/phase-145-anf
tomoaki 6a3b6deb20 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>
2025-12-19 16:19:49 +09:00
..

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:

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:

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):

// 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

  • 5 new files created (anf/ module)
  • 3 existing files modified (mod.rs, expr_lowerer_box.rs, joinir_dev.rs)
  • 7 unit tests pass
  • cargo build --release passes
  • 0 regression (existing tests unchanged)
  • 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:

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:

// 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)