Files
hakorune/docs/development/current/main/design/control-tree.md

13 KiB
Raw Blame History

ControlTree / StepTree構造SSOT

Status: SSOTdesign / vocabulary
Scope: AST の“構造”だけを表現し、JoinIR/MIR の値・PHI・ブロックを一切持たない。

目的:

  • ループ/if の ネスト構造を SSOT として持ち、pattern/policy の増殖先を「構造」側へ寄せる。
  • LoopSkeletonloop_canonicalizer の骨格)を壊さずに、“次の一般化”の受け皿を追加する。

基本方針(禁止事項)

StepTree は「制御構造の形」だけを表す。

禁止(混ぜない):

  • ValueId / BlockId / PHI / JoinInst / MirInstruction など、値やCFGの概念
  • backend 依存の最適化・型情報
  • “動くための lowering” ロジック(生成・変換)

許可(持ってよいもの):

  • AST の最小要約(条件の形 / 文の種類 / span など)
  • Capability / Feature例: has_loop, has_if, has_return などの分類)

用語

ControlTree

StepTree後述を含む、構造SSOTの総称。将来 “BlockTree / BoundaryTree” 等を追加しても、値は持たない。

StepTree

AST を「構造ード」に落とした木または木Block列で、次のード種別を持つ:

  • Block: 文の列(順序を保持)
  • If: cond と then/else の Block
  • Loop: cond と body の Block
  • Stmt: 構造化していない文Local/Assign/Return/Break/Continue/Expr などを tag 化)

StepTreeContract構造契約SSOT

StepTreeContract は「この構造が何を含み、何を要求するか」を最小の契約として宣言する。 lowering/PHI/CFG の判断にはまだ使わないdev-onlyだが、再解析の増殖を防ぐための SSOT になる。

最小フィールド案P1:

  • exits: return / break / continue の存在(構造だけ)
  • writes: 変数への書き込み(最小は Assignment target=Variable(name)Local 宣言の集合)
  • required_caps: capability 宣言(例: NestedLoop, TryCatch, Throw, Lambda など)
  • cond_sig: if/loop 条件式の要約(下記)

Facts→Decision→Emit 設計Phase 120

責務分離Box-First原則:

  1. StepTreeBuilderBox: 構造 + facts 抽出まで

    • AST を StepNode 木に変換
    • StepTreeFacts を収集exits/writes/required_caps/cond_sig の生データ)
    • 意思決定・整形・署名生成はしない
  2. StepTreeContractBox: facts → contract の整形のみ

    • StepTreeFacts を受け取り StepTreeContract に整形
    • BTreeSet で安定性保証(順序決定性)
    • 意思決定はしないfacts をそのまま contract に移す)
  3. StepTree→Normalized: contract を読むだけ

    • AST 再解析禁止
    • contract に含まれる情報のみで lowering 判断
    • 将来実装時の契約

不変条件:

  • signature_basis_string() の決定性維持(既定挙動不変)
  • facts は順序に依存しないBTreeSet 使用)
  • contract 生成は冪等(同じ facts から同じ contract

cond の SSOTPhase 119

SSOT: condAST 参照ID/ハンドル) を保持する。

  • StepNode::If / StepNode::Loopcond_ast: Option<AstNodeHandle> を追加。
  • AstNodeHandle は AST 参照の軽量表現(将来的に AstExprId 等に移行可能)。
  • Phase 119: &ASTNode 直接参照(ライフタイム制約あり)として実装。
    • dev-only 用途なので、将来の ID 化は別 Phase で対応可能。

派生: cond_sig は署名/ログ/差分検知用の派生表現。

  • AstSummary から計算される要約文字列(比較・統計・ログ用)。
  • StepTreeSignaturesignature_basis_string() に含まれる。
  • Span は含めない(決定性保証)。

不変条件:

  • cond_astsignature_basis_string() に混ぜない(既存の署名安定性を維持)。
  • AstSummarycond_ast から計算され、構造分類/契約固定の責務を持つ。

将来計画Phase 119 以降):

  • StepTree→Normalized 変換箱を実装する際は、cond_ast を lowering 入力として活用する。
  • cond_sig は表示/署名用途として維持される。

StepTreeSignature構造署名

StepTreeSignature は StepTreeContract + node kinds の “安定な基底文字列” を hash した識別子。

用途:

  • dev-only ログの検索キー
  • “同型ループ/同型if” の増殖検知(再解析の増殖防止)

注意:

  • Span 等の位置情報は signature に含めない(入力差でブレるため)。

Capability段階投入のSSOT

StepTree は capability を“宣言”し、未対応は Fail-Fastdev-only / strict で止める。

想定する段階(例):

  1. if-onlyネストifまで: Phase 110 P1
  2. loop-in-if / if-in-loop: Phase 111+(予定)
  3. nested loop: capability guard のまま別Phaseで解禁

位置づけLoopSkeleton との関係)

  • LoopSkeletonloop_canonicalizer: loop 1個の骨格を正規化して、JoinIR に渡せる形へ整える。
  • StepTreecontrol_tree: 関数/ブロック全体の“構造”をSSOT化し、ネスト対応の入口を提供する。

両者は競合しない:

  • LoopSkeleton は “loop単体の正規化” が責務
  • StepTree は “構造の観測と分類” が責務値やCFGを持たない

デバッグ出力dev-only

  • 既定では出さない(既定挙動不変)。
  • NYASH_JOINIR_DEV=1 のときのみ StepTree をダンプするprefix は [trace:dev] control_tree/step_tree)。
  • StepTree は routing の入力にしない(当面は parity/観測のみ。routing SSOT は feature extractor + analyzer

Phase 121: StepTree→Normalized Shadow Lowering

目的: StepTree構造SSOTから Normalized 形式への最小ルートを確立し、if-only パターンで VM/LLVM との parity を検証する。

スコープ: if-onlyloop無しのみ。loop は capability guard で拒否。

設計ルール

入力SSOT:

  • StepTree + StepTreeContractfacts 再解析禁止)
  • contract に含まれる情報のみで lowering 判断

出力:

  • JoinModuleNormalized 方言)
  • または "Normalized 相当の中間" を JoinIR 既存型で表現

実行条件:

  • joinir_dev_enabled() のときのみ shadow 変換を実行dev-only
  • joinir_strict_enabled() のときのみ mismatch を Fail-Fast

禁止事項:

  • fallback 禁止: shadow 変換失敗時は "disabled 扱い" ではなく dev-only で理由ログ、strict で Fail-Fast
  • env 直読み禁止(src/config/env/* 経由必須)
  • ハードコード禁止fixture 名や変数名で分岐しない)

責務分離Box化

StepTreeNormalizedShadowLowererBox:

  • 責務: StepTree→JoinModule 変換if-only限定
  • 入力: &StepTree
  • 出力: Result<Option<(JoinModule, JoinFragmentMeta)>, String>
    • Ok(None) = if-only 対象外(例: loop 含む)
    • Ok(Some(...)) = shadow 生成成功
    • Err(...) = 生成できるはずなのに壊れている

normalized_shadow/contracts.rs:

  • 責務: "if-only に限定" チェック、capability 拒否理由の SSOT
  • Unsupported capability の明示的列挙Loop / Break / Continue 等)

normalized_shadow/parity_contract.rs:

  • 責務: router/既存経路との契約比較dev ログ / strict fail-fast
  • 比較対象: 出口契約(exits)と writes の一致(最小で壊れにくい)
  • strict mode では freeze_with_hint でエラーhint 必須)

normalized_shadow/normalized_verifier.rs:

  • 責務: 生成された Normalized JoinModule の構造検証strict で Fail-Fast
  • 例: env 引数個数、JoinFunction の形、tail-call 形式など

normalized_shadow/dev_pipeline.rs:

  • 責務: dev/strict の入口を一本化capability guard → shadow lowering → parity/verify

Parity検証最小セット

比較対象(値の一致まではやらない):

  • StepTreeContract.exits / writes
  • 既存ルータ・既存抽出から得られる "exit/writes" 相当

不一致時の挙動:

  • dev mode: 1行ログ [trace:dev] phase121/shadow/parity_mismatch: ...
  • strict mode: error_tags::freeze_with_hint("phase121/shadow/parity_mismatch", msg, hint)

デバッグ出力dev-only

1行ログ形式:

[trace:dev] phase121/shadow: step_tree_sig=... shadow_lowered=true/false reason=... exits=... writes=...

strict fail-fast:

  • "if-only なのに shadow が作れない" は即座に freeze_with_hint
  • hint 空禁止必ず具体的な理由を1行で記述

配線場所

src/mir/builder/calls/lowering.rs:

  • lower_function_body 内の StepTree capability guard 近辺
  • joinir_dev_enabled() のときのみ shadow lowerer を呼ぶ
  • 既存の本番経路はそのまま実行(結果は一切変えない)

テスト戦略

Smoke tests:

  • 既存 fixture を流用(phase103_if_only_merge_min.hako 等)
  • VM ラインと LLVM ライン両方で実行
  • HAKO_JOINIR_STRICT=1 で strict mode 検証
  • NYASH_JOINIR_DEV=1 で dev mode ログ確認

期待値:

  • 既存と同じ数値出力output_validator.sh で比較)
  • strict mode で落ちない(= shadow parity mismatch が無い)

Phase 122: if-only Normalized JoinModule emission (dev-only)

目的: Phase 121 の shadow契約だけを一段進めて、if-only を Normalized JoinIRenv+継続)として実際に JoinModule 生成する。

スコープ: if-only のみloop 無し。既定挙動は不変dev-only で生成・検証のみ)。

設計原則SSOT

入力SSOT:

  • StepTree + StepTreeContractfacts 再解析禁止)
  • cond_ast を lowering 入力として使用(条件式の AST 参照)
  • contract 以外の AST 再解析は禁止(構造は StepTree に固定)

env レイアウトSSOT:

  • writes に含まれる変数だけをフィールドとして持つ
  • 順序は決定的BTreeSet で安定性保証)
  • env 構造体は関数間の値の橋渡しに使用

merge 形式PHI 禁止):

  • merge = join_k(env) への tail-call
  • PHI ードは使用しないenv が PHI の役割を果たす)
  • if の両分岐から同じ継続関数に tail-call

strict mismatch 処理:

  • 生成できるはずが失敗 → freeze_with_hinthint 必須)
  • capability 外は Ok(None)(正常な範囲外)

対応ノード(最小セット)

Phase 122 で対応:

  • Ifthen/else 分岐)
  • Returnpayload なし/整数/変数の最小)
  • Assignx = <expr> の最小: Const/Variable/BinOp(Add)/Call の範囲)
    • Phase 115/116/117 で既に対応済みの範囲に限定

Phase 122 で非対応capability で拒否):

  • Loop / Break / Continuecapability guard で明示的に拒否)
  • Printreturn code parity に寄せるため不要)

実装責務Box-First

StepTreeNormalizedShadowLowererBox::try_lower_if_only:

  • 責務: StepTree → JoinModuleNormalized 方言)変換
  • env レイアウトは writes を SSOT として決定
  • cond_ast を lowering して Compare/Truthiness へ変換
  • if-only 限定Loop/Break/Continue は capability で拒否)

normalized_shadow/contracts.rs:

  • 責務: capability チェック(変更なし)
  • Unsupported capability の SSOTLoop/Break/Continue

normalized_shadow/normalized_verifier.rs:

  • 責務: 構造検証(関数数/継続数/tail-call 形式/env 引数)
  • strict 時: 生成失敗で freeze_with_hint
  • 実行器があれば RC 比較も可能(オプション)

禁止事項Fail-Fast

  • env 直読み禁止(src/config/env/* 経由必須)
  • ハードコード禁止fixture 名や変数名で分岐しない)
  • capability で弾くLoop/Break/Continue
  • strict で止める時は freeze_with_hinthint 必須)

配線dev-only

既定挙動不変:

  • Phase 121 と同じ配線点を使用
  • dev-only のとき: JoinModule を生成 → 検証 → 本経路の結果は変えない

テスト戦略

新規 fixture:

  • apps/tests/phase122_if_only_normalized_emit_min.hako
  • flag=0/1 で return 値が変わるPhase 114/115 系)
  • 期待出力: 数値 1-2 行

Smoke test:

  • tools/smokes/v2/profiles/integration/apps/phase122_if_only_normalized_emit_vm.sh
  • 期待: 既存実行は PASS + dev-only strict でも落ちない

デバッグ出力dev-only

[trace:dev] phase122/emit: step_tree_sig=... module_emitted=true/false funcs=... conts=... env_fields=...