Span trace utilities and runner source hint

This commit is contained in:
nyash-codex
2025-11-24 14:17:02 +09:00
parent 3154903121
commit 466e636af6
106 changed files with 4597 additions and 958 deletions

View File

@ -14,6 +14,26 @@
- JoinIR は「PHI/SSA の実装負担を肩代わりする層」として導入し、ヘッダ/exit φ や BodyLocal の扱いを **関数の引数と継続** に吸収していく。
- PHI 専用の箱HeaderPhiBuilder / ExitPhiBuilder / BodyLocalPhiBuilder など)は、最終的には JoinIR 降ろしの補助に縮退させることを目標にする。
### JoinIR ロワーが「やらないこと」チェックリスト(暴走防止用)
JoinIR への変換はあくまで「LoopForm で正規化された形」を前提にした **薄い汎用ロワー** に寄せる。
このため、以下は **JoinIR ロワーでは絶対にやらない**こととして明示しておく。
- 条件式の **中身** を解析しない(`i < n``flag && x != 0` かを理解しない)
- 見るのは「header ブロックの succ が 2 本あって、LoopForm が body/exit を教えてくれるか」だけ。
- 多重ヘッダ・ネストループを自力で扱おうとしない
- LoopForm 側で「単一 header / 単一 latch のループ」として正規化できないものは、JoinIR 対象外(フォールバック)。
- LoopForm/VarClass で判定できない BodyLocal/ExitLiveness を、JoinIR 側で推測しない
- pinned/carrier/exit 値は LoopVarClassBox / LoopExitLivenessBox からだけ受け取り、独自解析はしない。
- 各ループ用に「特別な lowering 分岐」を増やさない
- `skip_ws` / `trim` / `stageb_*` / `stage1_using_resolver` 向けの per-loop lowering は Phase 27.x の実験用足場であり、最終的には Case A/B/D 向けの汎用ロワーに吸収する。
> 要するに: JoinIR は「LoopForm変数分類の結果」だけを入力にして、
> ループ/if の **構造(続行 or exit の2択持ち回り変数** を関数呼び出しに写す箱だよ。
> 構文パターンや条件式そのものを全網羅で理解しに行くのは、この層の責務から外す。
Phase 28 メモ: generic_case_a ロワーは LoopForm / LoopVarClassBox / LoopExitLivenessBox から実データを読み、minimal_skip_ws の JoinIR を組み立てるステップに進化中。Case A/B/D を汎用ロワーに畳み込む足場として扱う。
位置づけ
- 変換パイプラインにおける位置:
@ -258,6 +278,115 @@ enum JoinInst {
| 構文/概念 | JoinIR での表現 | φ/合流の扱い |
|-----------|-----------------|--------------|
| if/merge | join 関数呼び出し | join 関数の引数が φ 相当 |
---
## 7. 汎用 LoopForm→JoinIR ロワー設計Case A/B ベース案)
Phase 28 以降は、`skip_ws``trim` のような「ループごとの lowering」を増やすのではなく、
LoopForm v2 と変数分類箱を入力にした **汎用ロワー** で Case A/B 型ループをまとめて JoinIR に落とすのがゴールになる。
ここでは、その v1 として **単一ヘッダの Case A/B ループ** を対象にした設計をまとめておく。
### 7-1. 入力と前提条件
汎用ロワーが見るのは、次の 4つだけに限定する。
- `LoopForm` / `ControlForm`(構造)
- `preheader`, `header`, `body`, `latch`, `exit`, `continue_merge` の各ブロック ID
- `header` ブロックの succ がちょうど 2 本であること
- LoopForm が「どちらが body 側 / exit 側か」を与えてくれていること
- `LoopVarClassBox`(変数分類)
- pinned: ループ中で不変な変数集合
- carriers: ループごとに更新される LoopCarried 変数集合
- body-local: ループ内部だけで完結する変数集合(基本は JoinIR では引数にしない)
- `LoopExitLivenessBox`Exit 後で必要な変数)
- exit ブロック以降で実際に参照される変数集合 `E`
- 条件式の形そのもの(`i < n``1 == 1` か等)は **見ない**
- 汎用ロワーが使うのは「header の succ が body/exit に分かれている」という事実だけ。
前提条件として、v1 では次のようなループだけを対象にする。
- LoopForm が「単一 header / 単一 latch の loop」として構築できていること。
- header の succ が 2 本で、ControlForm が `(cond → {body, exit})` を一意に教えてくれること。
- break/continue が LoopForm の設計どおり `exit` / `latch` に正規化されていること。
これを満たさないループ(多重ヘッダ・ネスト・例外的な jump 等)は **JoinIR 対象外(フォールバック)** とする。
### 7-2. 出力の形(ループごとの JoinFunction セット)
Case A/B 型の単一ヘッダ loop について、汎用ロワーは原則として次の 2 関数を生成する。
- `loop_step(pinned..., carriers..., k_exit)`
- 引数:
- pinned: LoopVarClassBox の pinned 変数(ループ外で初期化され、ループ中不変)
- carriers: LoopVarClassBox の carriersループをまたいで値を持ち回る
- `k_exit`: ループを抜けたあとの処理を表す継続
- 本体:
- header 条件の判定(`header` ブロック)
- body/latch の処理(`body`/`latch` ブロックから拾った Compute/BoxCall 等)
- break/continue の分岐を、それぞれ `k_exit(exit_args...)` / `loop_step(next_carriers..., k_exit)` に変換したもの
- `k_exit` 相当の関数(もしくは呼び出し先関数の entry
- ExitLiveness が教えてくれる `E`exit 後で必要な変数)を引数とし、
exit ブロック以降の処理をそのまま MIR→JoinIR で表現したもの。
これにより:
- header φ: `LoopHeaderShape` / carriers 引数として `loop_step` に吸収される。
- exit φ: `LoopExitShape` / exit 引数として `k_exit` に吸収される。
- LoopCarried 変数は常に `loop_step` の引数経由で再帰されるので、PHI ノードは JoinIR 側では不要になる。
### 7-3. 汎用ロワー v1 のアルゴリズムCase A/B
Case A/B を対象にした最初の汎用ロワーは、次のような流れになる。
1. **対象ループかどうかのチェック**
- LoopForm が単一 header / 単一 latch を持つことを確認。
- header の succ が 2 本で、ControlForm が body/exit を特定していることを確認。
- 条件: LoopForm の invariants を満たすループだけを対象にし、それ以外は `None` でフォールバック。
2. **変数セットの決定**
- pinned 集合 `P` と carriers 集合 `C` を LoopVarClassBox から取得。
- ExitLiveness から exit 後で必要な変数集合 `E` を取得。
- `LoopHeaderShape``LoopExitShape` を構築しておき、`loop_step` / `k_exit` の引数順を固定。
3. **`loop_step` 関数の生成**
- JoinFunction を新規に作成し、`params = P C`(+必要なら `k_exit`)とする。
- header ブロックの Compare/BinOp から「続行 or exit」の判定命令を MirLikeInst として移植。
- body/latch ブロックの Compute / BoxCall を順に MirLikeInst へ写し、carrier 更新を `C_next` として集約。
- break:
- LoopForm/ControlForm が break 経路としてマークしたブロックからは、`Jump { cont: k_exit, args: exit_values }` を生成。
- continue:
- latch への backedge 経路からは、`Call { func: loop_step, args: pinned..., carriers_next..., k_next: None/dst=None }` を生成。
4. **エントリ関数からの呼び出し**
- 元の MIR 関数の entry から、LoopForm が示す preheader までの処理を MirLikeInst として保持。
- preheader の最後で `loop_step(pinned_init..., carriers_init..., k_exit)` 呼び出しを挿入。
5. **Exit 継続の構築**
- exit ブロック以降の MIR 命令を、`k_exit` 相当の JoinFunction か、呼び出し先関数の entry に写す。
- `E` の各変数を引数として受け取り、そのまま下流の処理に流す。
### 7-4. 既存 per-loop lowering との関係
Phase 27.x で実装した以下の lowering は、この汎用ロワーの「見本」として扱う。
- `skip_ws` 系: minimal_ssa_skip_wsCase B: `loop(1 == 1)` + break
- `FuncScanner.trim_minimal`: Case D の簡易版(`loop(e > b)` + continue+break
- `FuncScanner.append_defs_minimal`: Case A配列走査
- `Stage1UsingResolver minimal`: Case A/B の混合Region+next_i 形)
- StageB minimal ループ: Case A の defs/body 抽出
Phase 28 では:
- まず minimal_ssa_skip_ws だけを対象に、`generic_case_a` のような汎用ロワー v1 を実装し、
既存の手書き JoinIR と同じ構造が得られることを確認する(実装済み: `lower_case_a_loop_to_joinir_for_minimal_skip_ws`)。
- そのあとで `trim_minimal` / `append_defs_minimal` / Stage1 minimal / StageB minimal に順に適用し、
per-loop lowering を「汎用ロワーを呼ぶ薄いラッパー」に置き換えていく。
- 最終的には per-loop lowering ファイルは削減され、LoopForm変数分類箱から JoinIR へ落とす汎用ロワーが SSOT になることを目指す。
このセクションは「汎用ロワー設計のターゲット像」として置いておき、
実装は Phase 28-midterm のタスクgeneric lowering v1で少しずつ進めていく。
| loop | step 再帰 + k_exit 継続 | LoopCarried/Exit の値を引数で渡す |
| break | k_exit 呼び出し | φ 不要(引数で値を渡す) |
| continue | step 呼び出し | φ 不要(引数で値を渡す) |