feat(joinir): Phase 248 - Normalized JoinIR infrastructure
Major refactoring of JoinIR normalization pipeline: Key changes: - Structured→Normalized→MIR(direct) pipeline established - ShapeGuard enhanced with Pattern2 loop validation - dev_env.rs: New development fixtures and env control - fixtures.rs: jsonparser_parse_number_real fixture - normalized_bridge/direct.rs: Direct MIR generation from Normalized - pattern2_step_schedule.rs: Extracted step scheduling logic Files changed: - normalized.rs: Enhanced NormalizedJoinModule with DevEnv support - shape_guard.rs: Pattern2-specific validation (+300 lines) - normalized_bridge.rs: Unified bridge with direct path - loop_with_break_minimal.rs: Integrated step scheduling - Deleted: step_schedule.rs (moved to pattern2_step_schedule.rs) New files: - param_guess.rs: Loop parameter inference - pattern2_step_schedule.rs: Step scheduling for Pattern2 - phase43-norm-canon-p2-mid.md: Design doc Tests: 937/937 PASS (+6 from baseline 931) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -24,9 +24,11 @@
|
|||||||
- Pattern3/4 の公開 API を `can_lower + lower` の最小セットに整理し、内部 helper を箱の中に閉じた。
|
- Pattern3/4 の公開 API を `can_lower + lower` の最小セットに整理し、内部 helper を箱の中に閉じた。
|
||||||
- `loop_pattern_detection` の classify() が代表ループを P1〜P4 に分類することをユニットテストで固定。
|
- `loop_pattern_detection` の classify() が代表ループを P1〜P4 に分類することをユニットテストで固定。
|
||||||
- Phase 245-EX / 245C:
|
- Phase 245-EX / 245C:
|
||||||
- `_parse_number` のループについて、ループ変数 `p` の header/break 条件と `p = p + 1` 更新を Pattern2 + ExprLowerer 経路に載せて本番化(num_str は未導入)。
|
- `_parse_number` のループについて、ループ変数 `p` の header/break 条件と `p = p + 1` 更新を Pattern2 + ExprLowerer 経路に載せて本番化。
|
||||||
- FunctionScopeCapture / CapturedEnv を拡張し、関数パラメータ(例: `s`, `len`)をループ条件/本体で読み取り専用なら CapturedEnv 経由で ConditionEnv に載せるようにした。
|
- FunctionScopeCapture / CapturedEnv を拡張し、関数パラメータ(例: `s`, `len`)をループ条件/本体で読み取り専用なら CapturedEnv 経由で ConditionEnv に載せるようにした。
|
||||||
- これにより、`p < s.length()` のような header 条件や JsonParser 系ループでのパラメータ参照が、ExprLowerer/ScopeManager から安全に解決される。
|
- これにより、`p < s.length()` のような header 条件や JsonParser 系ループでのパラメータ参照が、ExprLowerer/ScopeManager から安全に解決される。
|
||||||
|
- Phase 245B-IMPL:
|
||||||
|
- `_parse_number` 本体で `num_str = num_str + ch` を LoopState キャリアとして扱い、Program(JSON) フィクスチャ `jsonparser_parse_number_real` を Structured→Normalized→MIR(direct) で dev テスト固定(出力は num_str 文字列)。
|
||||||
- Phase 247-EX:
|
- Phase 247-EX:
|
||||||
- DigitPos promotion を二重値化し、`digit_pos` から boolean carrier `is_digit_pos`(ConditionOnly)と integer carrier `digit_value`(LoopState)を生成。
|
- DigitPos promotion を二重値化し、`digit_pos` から boolean carrier `is_digit_pos`(ConditionOnly)と integer carrier `digit_value`(LoopState)を生成。
|
||||||
- UpdateEnv で `digit_pos` 解決時に `digit_value` を優先し、NumberAccumulation(`result = result * 10 + digit_pos`)と break 条件の両方で DigitPos パターンが安全に利用可能に。
|
- UpdateEnv で `digit_pos` 解決時に `digit_value` を優先し、NumberAccumulation(`result = result * 10 + digit_pos`)と break 条件の両方で DigitPos パターンが安全に利用可能に。
|
||||||
@ -51,6 +53,14 @@
|
|||||||
- P1/P2 ミニ + JsonParser skip_ws/atoi ミニを Normalized→MIR 直ブリッジで実行できるようにし、normalized_dev ON 時は Structured→Normalized→MIR(復元なし)経路との比較テストで結果一致を固定。既定経路(Structured→MIR)は不変。
|
- P1/P2 ミニ + JsonParser skip_ws/atoi ミニを Normalized→MIR 直ブリッジで実行できるようにし、normalized_dev ON 時は Structured→Normalized→MIR(復元なし)経路との比較テストで結果一致を固定。既定経路(Structured→MIR)は不変。
|
||||||
- Phase 36-NORM-BRIDGE-DIRECT(dev-only):
|
- Phase 36-NORM-BRIDGE-DIRECT(dev-only):
|
||||||
- Normalized ブリッジを direct 実装(Normalized→MIR)と Structured 再構成に分離し、shape_guard で P1/P2 ミニ + JsonParser skip_ws/atoi ミニだけ direct 経路に通すよう整理。非対応は `[joinir/normalized-bridge/fallback]` ログ付きで再構成に落とし、テストで direct/従来経路の VM 出力一致を固定。
|
- Normalized ブリッジを direct 実装(Normalized→MIR)と Structured 再構成に分離し、shape_guard で P1/P2 ミニ + JsonParser skip_ws/atoi ミニだけ direct 経路に通すよう整理。非対応は `[joinir/normalized-bridge/fallback]` ログ付きで再構成に落とし、テストで direct/従来経路の VM 出力一致を固定。
|
||||||
|
- Phase 37-NORM-JP-REAL(dev-only):
|
||||||
|
- JsonParser `_skip_whitespace` 本体の P2 ループを Program(JSON) フィクスチャで Structured→Normalized→MIR(direct) に通し、Structured 直経路との VM 出力一致を比較するテストを追加。`extract_value` が `&&`/`||` を BinOp として扱えるようにし、Break パターンの param 推定を柔軟化して real 形状でも panic しないようにした。
|
||||||
|
- Phase 38-NORM-OBS(dev-only):
|
||||||
|
- Normalized/JoinIR dev 経路のログカテゴリを `[joinir/normalized-bridge/*]` / `[joinir/normalized-dev/shape]` に統一し、`JOINIR_TEST_DEBUG` 下だけ詳細を出すよう静音化。Verifier/Fail‑Fast メッセージも shape/役割付きに整え、デバッグ観測性を上げつつ通常実行のノイズを減らした。
|
||||||
|
- Phase 43-A(dev-only):
|
||||||
|
- JsonParser `_atoi` 本体の Program(JSON) フィクスチャを normalized_dev に追加し、Structured→Normalized→MIR(direct) と Structured→MIR の VM 出力を比較するテストで一致を固定(符号あり/なしの簡易パス対応。canonical 切替は後続フェーズ)。
|
||||||
|
- Phase 43-C(dev-only):
|
||||||
|
- JsonParser `_parse_number` 本体の Program(JSON) フィクスチャを normalized_dev に追加し、Structured→Normalized→MIR(direct) と Structured→MIR の VM 出力を比較するテストで一致を固定(num_str は現状仕様のまま据え置き、P2-Mid の足慣らし)。
|
||||||
|
|
||||||
### 1. いまコード側で意識しておきたいフォーカス
|
### 1. いまコード側で意識しておきたいフォーカス
|
||||||
|
|
||||||
|
|||||||
@ -987,7 +987,7 @@ JoinIR は Rust 側だけでなく、将来的に .hako selfhost コンパイラ
|
|||||||
- [x] 退行なし: Phase 190-196 テスト全 PASS ✅
|
- [x] 退行なし: Phase 190-196 テスト全 PASS ✅
|
||||||
- 詳細: phase197-lightweight-loops-deployment.md
|
- 詳細: phase197-lightweight-loops-deployment.md
|
||||||
|
|
||||||
7. **JsonParser/selfhost 実戦 JoinIR 適用状況** (2025-12-09 更新)
|
7. **JsonParser/selfhost 実戦 JoinIR 適用状況** (2025-12-09 更新 → Phase 42 で棚卸し済み)
|
||||||
|
|
||||||
| Function | Pattern | Status | Note |
|
| Function | Pattern | Status | Note |
|
||||||
|----------|---------|--------|------|
|
|----------|---------|--------|------|
|
||||||
@ -998,15 +998,15 @@ JoinIR は Rust 側だけでなく、将来的に .hako selfhost コンパイラ
|
|||||||
| `phase195_sum_count` | P3 | ✅ JoinIR OK | Phase 196 検証済み(multi-carrier)|
|
| `phase195_sum_count` | P3 | ✅ JoinIR OK | Phase 196 検証済み(multi-carrier)|
|
||||||
| `loop_if_phi` | P3 | ✅ JoinIR OK | Phase 196 検証済み(single-carrier)|
|
| `loop_if_phi` | P3 | ✅ JoinIR OK | Phase 196 検証済み(single-carrier)|
|
||||||
| `loop_min_while` | P1 | ✅ JoinIR OK | Phase 165 基本検証済み |
|
| `loop_min_while` | P1 | ✅ JoinIR OK | Phase 165 基本検証済み |
|
||||||
| `_parse_number` | P2 | ⚠️ Deferred | ConditionEnv 制約(Phase 200+)|
|
| `_parse_number` | P2 | ✅ JoinIR OK | Phase 245B-IMPL: P2-Mid(num_str LoopState キャリア)を Structured→Normalized(dev, direct) で固定 |
|
||||||
| `_atoi` | P2 | ⚠️ Deferred | ConditionEnv 制約(Phase 200+)|
|
| `_atoi` | P2 | ✅ JoinIR OK | Phase 246-EX で NumberAccumulation パターンとして統合(P2-Mid、Normalized: mini + real(dev, 符号対応) / canonical 準備中)|
|
||||||
| `_parse_string` | P3 | ⚠️ Deferred | 複雑キャリア(Phase 195+ 拡張後)|
|
| `_parse_string` | P3 | ⚠️ Deferred | 複雑キャリア(Phase 195+ 拡張後)|
|
||||||
| `_unescape_string` | P3 | ⚠️ Deferred | 複雑キャリア(Phase 195+ 拡張後)|
|
| `_unescape_string` | P3 | ⚠️ Deferred | 複雑キャリア(Phase 195+ 拡張後)|
|
||||||
| `_parse_array` | - | ⚠️ Deferred | 複数 MethodCall(Phase 195+)|
|
| `_parse_array` | - | ⚠️ Deferred | 複数 MethodCall(Phase 195+)|
|
||||||
| `_parse_object` | - | ⚠️ Deferred | 複数 MethodCall(Phase 195+)|
|
| `_parse_object` | - | ⚠️ Deferred | 複数 MethodCall(Phase 195+)|
|
||||||
|
|
||||||
**Coverage**: 7/13 ループ JoinIR 対応済み(54%)
|
**Coverage**: 9/13 ループ JoinIR 対応済み(約 69%)
|
||||||
**Verification**: 4/7 ループ E2E PASS、3/7 structural/routing 確認済み
|
**Verification**: 代表ループ(P1/P2 Core + Trim/P3)については E2E テストで挙動確認済み。詳細なケースごとの状況は各 Phase ドキュメント(Phase 197/245/246 など)を参照。
|
||||||
|
|
||||||
8. **JsonParser 残り複雑ループへの適用(Phase 198+, 200+)**
|
8. **JsonParser 残り複雑ループへの適用(Phase 198+, 200+)**
|
||||||
- Phase 200+: ConditionEnv 拡張 (function-scoped variables) → _parse_number, _atoi
|
- Phase 200+: ConditionEnv 拡張 (function-scoped variables) → _parse_number, _atoi
|
||||||
@ -1247,3 +1247,61 @@ Normalized JoinIR を 1 段挟むと、開発の手触りがどう変わるか
|
|||||||
|
|
||||||
- Normalized ブリッジを direct 実装と Structured 再構成の二段に分離し、shape_guard で direct 対象(P1/P2 ミニ + JsonParser skip_ws/atoi ミニ)だけを Normalized→MIR 直接生成に切り替えた。
|
- Normalized ブリッジを direct 実装と Structured 再構成の二段に分離し、shape_guard で direct 対象(P1/P2 ミニ + JsonParser skip_ws/atoi ミニ)だけを Normalized→MIR 直接生成に切り替えた。
|
||||||
- direct 経路は `normalized_bridge::direct` に閉じ込め、非対応形状は `[joinir/normalized-bridge/fallback]` ログ付きで Structured 再構成経路に落とす構造に整理。dev テストでは direct 経路の VM 出力が従来経路と一致することを固定。
|
- direct 経路は `normalized_bridge::direct` に閉じ込め、非対応形状は `[joinir/normalized-bridge/fallback]` ログ付きで Structured 再構成経路に落とす構造に整理。dev テストでは direct 経路の VM 出力が従来経路と一致することを固定。
|
||||||
|
|
||||||
|
### 3.15 Phase 37-NORM-JP-REAL – JsonParser `_skip_whitespace` 本体を dev Normalized で比較
|
||||||
|
|
||||||
|
- JsonParser 本体の `_skip_whitespace` ループを Program(JSON) フィクスチャ化し、`shape_guard` で real 版を検知して Structured→Normalized→MIR(direct) の dev 経路に通すように拡張。`extract_value` は `&&`/`||` を BinOp として受け付けるようにした。
|
||||||
|
- Break パターンのパラメータ推定を柔軟化(loop_var/acc/n が無いケースでも loop_var を優先し、acc が無ければ同一キャリアとして扱う)し、skip_ws real の構造で panic しないようにした。
|
||||||
|
- tests/normalized_joinir_min.rs に `_skip_whitespace` real フィクスチャの VM 比較テストを追加し、env ON 時は Structured→Normalized→MIR(direct) と Structured 直経路の stdout が一致することを固定(env OFF は既存経路のまま)。
|
||||||
|
- normalized_dev 用フィクスチャは `docs/private/roadmap2/phases/normalized_dev/fixtures/` に配置し、Program(JSON) から `AstToJoinIrLowerer` で読み込む運用に統一した。
|
||||||
|
|
||||||
|
### 3.16 Phase 38-NORM-OBS – Normalized dev ログ/Fail‑Fast の整備
|
||||||
|
|
||||||
|
- Normalized/JoinIR dev 経路のログカテゴリを `[joinir/normalized-bridge/*]` / `[joinir/normalized-dev/shape]` に統一し、`JOINIR_TEST_DEBUG` フラグ下のみ詳細を出すよう静音化。Verifier/Fail‑Fast メッセージも shape/役割付きに整理してデバッグ観測性を強化。
|
||||||
|
|
||||||
|
### 3.17 Phase 40-NORM-CANON-TESTS – テスト側で Normalized を“当たり前”に通す
|
||||||
|
|
||||||
|
- `normalized_dev_enabled()` と env ガードを整理し、P1/P2 ミニ + JsonParser skip_ws/atoi ミニ/real の代表テストは「Normalized dev 経路が必ず通る」前提にする(壊れたら normalized_* スイートが赤になる)。
|
||||||
|
- 既存の Structured 直経路は比較用に維持しつつ、tests/normalized_joinir_min.rs 経路では Structured→Normalized→MIR(direct) が第一観測点になるように整備(本番 CLI は Structured→MIR のまま)。
|
||||||
|
|
||||||
|
### 3.18 Phase 41-NORM-CANON-P2-CORE – Pattern2 コアケースの canonical Normalized 化
|
||||||
|
|
||||||
|
- Pattern2 のコアセット(P2 ミニ + JsonParser skip_ws/atoi ミニ/real)について、JoinIR→MIR Bridge の既定を Normalized→MIR に寄せ、Structured→MIR は比較テスト用/フォールバック用の位置づけにする(Fail‑Fast ポリシーは維持)。
|
||||||
|
- `shape_guard` で「Normalized 対応と宣言した P2 コアループ」は常に Normalized 経路を通すようにし、Normalized 側の invariant 破損は dev では panic、本番では明示エラーで早期検出する設計に寄せる。
|
||||||
|
|
||||||
|
### 3.19 Phase 42-NORM-P2-INVENTORY – P2 コア/ミドル/ヘビーの棚卸し
|
||||||
|
|
||||||
|
- JsonParser / selfhost で **現役の P2 ループ** を洗い出し、次の 3 クラスに整理した:
|
||||||
|
- **P2-Core**(すでに Normalized canonical なもの)
|
||||||
|
- test fixture 系: `loop_min_while` P2 ミニ, Phase 34 break fixture (`i/acc/n`)
|
||||||
|
- JsonParser 系: `_skip_whitespace` mini/real, `_atoi` mini
|
||||||
|
- これらは Phase 36–41 で **Structured→Normalized→MIR(direct)** が canonical になっており、`bridge_joinir_to_mir` でも優先的に Normalized 経路が選ばれる。
|
||||||
|
- **P2-Mid**(次に Normalized を当てる候補)
|
||||||
|
- JsonParser: `_parse_number`, `_atoi` 本体, `_atof_loop`
|
||||||
|
- いずれも Pattern2 Break で JoinIR(Structured) には載っており(Phase 245/246 系)、Normalized への写像は今後の拡張対象として扱う。
|
||||||
|
- **P2-Heavy**(複数 MethodCall / 複雑キャリアを持つもの)
|
||||||
|
- JsonParser: `_parse_string`, `_parse_array`, `_parse_object`, `_unescape_string`
|
||||||
|
- P2/P3/P4 が混在し、複雑なキャリアや MethodCall 多数のため、Phase 43 以降の後続フェーズで設計する。
|
||||||
|
- P2-Core については Phase 41 で canonical Normalized 化が完了しており、Structured→MIR は比較テスト用 / フォールバック用の経路として扱う。
|
||||||
|
- P2-Mid のうち、Phase 43 ではまず `_parse_number` を第 1 候補、`_atoi` 本体を第 2 候補として扱い、Normalized→MIR(direct) に必要な追加インフラ(EnvLayout 拡張 / JpInst パターン拡張)を段階的に入れていく前提を整理した。
|
||||||
|
|
||||||
|
### 3.20 Phase 43-NORM-CANON-P2-MID – JsonParser 本命 P2(_parse_number/_atoi)への適用
|
||||||
|
|
||||||
|
- JsonParser `_parse_number` / `_atoi` 本体の Pattern2 ループを、既存インフラ(DigitPos dual 値, LoopLocalZero, StepScheduleBox, ExprLowerer/MethodCall, Normalized ブリッジ)上で Structured→Normalized→MIR(direct) に載せる。
|
||||||
|
- dev で Structured 直経路との VM 実行結果一致を固定した上で、段階的に「この関数だけ Normalized canonical」とみなすプロファイル/フラグを導入し、最終的に JsonParser P2 の canonical route を Normalized 側に寄せるための足場にする。
|
||||||
|
- Phase 43-A(dev 専用): `_atoi` 本体を Program(JSON) フィクスチャ `jsonparser_atoi_real` で Structured→Normalized→MIR(direct) に通し、Structured 直経路との VM 出力一致を比較テストで固定(符号あり/なしの簡易パスまで対応。canonical 化は後続フェーズで検討)。
|
||||||
|
- Phase 43-C(dev 専用): `_parse_number` 本体を Program(JSON) フィクスチャ `jsonparser_parse_number_real` で Structured→Normalized→MIR(direct) に通し、`num_str = num_str + ch` の LoopState キャリアを含めた状態で Structured 直経路との VM 出力一致を比較テストで固定。
|
||||||
|
|
||||||
|
### 3.21 Phase 44-SHAPE-CAP – shape_guard の能力ベース化(計画)
|
||||||
|
|
||||||
|
- 現状の shape_guard は `JsonparserSkipWsMini/Real`, `JsonparserAtoiMini/Real` など「関数名ベースの個別 shape」が増えつつあるため、将来的には:
|
||||||
|
- 「P2 / LoopParam1 / Carrier≤N / MethodCall パターン = このセット」のような **能力ベースの ShapeCapability テーブル** に寄せる。
|
||||||
|
- JsonParser/selfhost の各ループは「どの capability を満たしているか」を参照するだけにし、関数名ベタ書き依存を減らす。
|
||||||
|
- この Phase では docs 上で API/テーブル設計を固め、コード側では shape_guard の内部表現を Capability 中心に書き換える前段として扱う。
|
||||||
|
|
||||||
|
### 3.22 Phase 45-NORM-MODE – JoinIR モードの一本化(計画)
|
||||||
|
|
||||||
|
- 現状は `normalized_dev_enabled()`, `NYASH_JOINIR_NORMALIZED_DEV_RUN`, `JOINIR_TEST_DEBUG` など複数の env/feature でモードを切り替えているため、将来的には:
|
||||||
|
- `JoinIrMode = { StructuredOnly, NormalizedDev, NormalizedCanonical }` のような enum を導入し、
|
||||||
|
- env/feature はこのモードの初期値を決めるだけに寄せる(コード側の `if`/分岐を減らす)。
|
||||||
|
- この Phase では JoinIR パイプラインの「モード遷移図」と `JoinIrMode` API を docs で設計し、後続フェーズで実装に反映する計画を置いておく。
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
# Phase 181: JsonParser 残りループ設計調査
|
# Phase 181: JsonParser 残りループ設計調査
|
||||||
|
|
||||||
|
Status: Historical + Updated in Phase 42
|
||||||
|
Note: Phase 181 時点の設計調査に基づくドキュメントだよ。最新の P2 分類と JoinIR/Normalized 状態は、このファイル内の「Phase 42 時点の P2 インベントリ」と `joinir-architecture-overview.md` を SSOT として見てね。
|
||||||
|
|
||||||
## 概要
|
## 概要
|
||||||
|
|
||||||
JsonParser(`tools/hako_shared/json_parser.hako`)の全11ループを詳細に分析し、
|
JsonParser(`tools/hako_shared/json_parser.hako`)の全11ループを詳細に分析し、
|
||||||
@ -201,6 +204,34 @@ loop(i < len) {
|
|||||||
|
|
||||||
**実行可能性**: ✅ Phase 182+ で実装可能(Pattern2 Break、_atoi の後継)
|
**実行可能性**: ✅ Phase 182+ で実装可能(Pattern2 Break、_atoi の後継)
|
||||||
|
|
||||||
|
## Phase 42 時点の P2 インベントリ(JsonParser)
|
||||||
|
|
||||||
|
Phase 42 では、上の 11 ループについて「P2 としてどこまで JoinIR / Normalized で扱えているか」を棚卸しして、P2-Core / P2-Mid / P2-Heavy の 3 クラスに整理したよ。
|
||||||
|
|
||||||
|
### 1. 分類ポリシー
|
||||||
|
|
||||||
|
- **P2-Core**: 既に Normalized→MIR(direct) まで実装され、Phase 41 で canonical route(既定経路)として扱っているループ群。
|
||||||
|
- **P2-Mid**: JoinIR(Structured) には載っているが、Normalized はこれから本格対応する「次候補」のループ群。
|
||||||
|
- **P2-Heavy**: MethodCall 多数・複雑キャリアなどの理由で、Normalized 対応は Phase 43 以降に送っている重めのループ群。
|
||||||
|
|
||||||
|
### 2. JsonParser ループの現在ステータス(2025‑12 時点)
|
||||||
|
|
||||||
|
| # | ループ | Pattern | P2 クラス | JoinIR 状態 | Normalized 状態 | 備考 |
|
||||||
|
|----|--------|---------|-----------|-------------|-----------------|------|
|
||||||
|
| 1 | _skip_whitespace | P2 / P5 Trim | P2-Core | ✅ JoinIR OK(Phase 173, 197, 245 系) | ✅ Normalized→MIR(direct) / canonical(Phase 37, 41) | mini / real の両方をフィクスチャ化して dev / canonical で比較済み |
|
||||||
|
| 2 | _trim (leading) | P2 / P5 Trim | P2-Heavy | ✅ JoinIR OK(TrimLoopHelper 経由) | 未対応(P5 専用経路のまま) | Trim/P5 専用 lowerer で処理。Normalized 対応は将来検討 |
|
||||||
|
| 3 | _trim (trailing) | P2 / P5 Trim | P2-Heavy | ✅ JoinIR OK | 未対応 | leading と同様に Trim/P5 ラインで運用 |
|
||||||
|
| 4 | _parse_number | P2 Break | P2-Mid | ✅ JoinIR OK(Phase 245-EX) | ✅ dev Normalized→MIR(direct)(Phase 43-C、フィクスチャ `jsonparser_parse_number_real`。num_str は現状仕様のまま据え置き) | header/break/p 更新は JoinIR 経路に載せ済み。数値正規化は Phase 43 以降で拡張予定 |
|
||||||
|
| 5 | _parse_string | P2/P4 | P2-Heavy | 部分的に JoinIR 対応(Pattern3/4 拡張後に対象) | 未対応 | return/continue・複数キャリアを含むため heavy クラス扱い |
|
||||||
|
| 6 | _atoi | P2 Break | P2-Mid | ✅ JoinIR OK(Phase 246-EX) | ✅ dev Normalized→MIR(direct)(mini + 本体符号あり/なし、Phase 43-A) | P2-Core には `_atoi` mini fixture が入っている。本体は Phase 43 以降で canonical 化予定 |
|
||||||
|
| 7 | _match_literal | P1 Simple | (P1) | ✅ JoinIR OK | Normalized 対応は P1 ラインで別途管理 | P1 simple なので P2 クラス分類の対象外。Phase 197 で JoinIR E2E 検証済み |
|
||||||
|
| 8 | _parse_array | P4 Continue | P2-Heavy | ⚠️ Deferred(複数 MethodCall) | 未対応 | continue + MethodCall 多数のため heavy クラス。ConditionEnv/MethodCall 拡張後に扱う |
|
||||||
|
| 9 | _parse_object | P4 Continue | P2-Heavy | ⚠️ Deferred | 未対応 | _parse_array と同種の heavy ループ |
|
||||||
|
| 10 | _unescape_string | P4 Continue | P2-Heavy | ⚠️ Deferred | 未対応 | 複数キャリア + flatten を含む。Pattern3/4 拡張後の対象 |
|
||||||
|
| 11 | _atof_loop | P2 Break | P2-Mid | JoinIR 対応候補(_atoi と同型) | 未対応 | `_atoi` 後継として P2-Mid 候補に分類。Phase 43 以降で `_atoi` 本体と一緒に扱う想定 |
|
||||||
|
|
||||||
|
最新の canonical / dev Normalized 経路や Shape 判定ロジックの詳細は `joinir-architecture-overview.md`(Phase 35–41 セクション)を参照してね。
|
||||||
|
|
||||||
## Pattern × Box マトリクス(JsonParser全体)
|
## Pattern × Box マトリクス(JsonParser全体)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
# Phase 223-1: LoopBodyLocal in Condition - Comprehensive Inventory
|
# Phase 223-1: LoopBodyLocal in Condition - Comprehensive Inventory
|
||||||
|
|
||||||
|
Status: Historical(Phase 26-H 以降の Normalized / DigitPos 導入で一部内容が古くなっています)
|
||||||
|
Note: LoopBodyLocal が原因で Fail-Fast していたループの在庫を Phase 223 時点で一覧化したメモだよ。DigitPos 系などの一部ループはその後の Phase 224/26-H/34 系で解消済みなので、最新の対応状況は `joinir-architecture-overview.md` と Phase 42 の P2 インベントリを合わせて参照してね。
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
This document inventories all loops that are currently **blocked** by the LoopConditionScopeBox Fail-Fast mechanism because they have `LoopBodyLocal` variables appearing in loop conditions (header, break, or continue).
|
This document inventories all loops that are currently **blocked** by the LoopConditionScopeBox Fail-Fast mechanism because they have `LoopBodyLocal` variables appearing in loop conditions (header, break, or continue).
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
# Phase 224-E: DigitPos Condition Normalizer
|
# Phase 224-E: DigitPos Condition Normalizer
|
||||||
|
|
||||||
|
Status: Active(DigitPos 条件正規化ラインの設計メモ / 実装ガイド)
|
||||||
|
Scope: digit_pos → is_digit_pos Carrier / ConditionEnv / ExprLowerer の正規化経路の SSOT ドキュメントだよ。Phase 26‑H / 34 系の JsonParser `_parse_number` / `_atoi` でもこの設計を前提にしている。
|
||||||
|
|
||||||
## Problem Statement
|
## Problem Statement
|
||||||
|
|
||||||
### Background
|
### Background
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
Status: Draft
|
Status: Implemented (Phase 245B-IMPL)
|
||||||
Scope: `_parse_number` で `num_str` を Pattern2/P5 のキャリアとして扱うかどうかを決める設計フェーズ(コード変更なし)。
|
Scope: `_parse_number` で `num_str` を Pattern2/P5 のキャリアとして扱うかどうかを決める設計フェーズ(実装は Structured→Normalized dev フィクスチャで完了)。
|
||||||
|
Notes: jsonparser_parse_number_real フィクスチャで `num_str = num_str + ch` を LoopState キャリアとして実装し、dev Normalized 比較テストで固定済み。
|
||||||
|
|
||||||
# Phase 245B: JsonParser `_parse_number` の `num_str` キャリア設計
|
# Phase 245B: JsonParser `_parse_number` の `num_str` キャリア設計
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
# Phase 245B: num_str Carrier Design Document
|
# Phase 245B: num_str Carrier Design Document
|
||||||
|
|
||||||
**Status**: Design Phase
|
**Status**: Implemented (Phase 245B-IMPL)
|
||||||
**Target**: `_parse_number` loop の `num_str` 文字列キャリア対応
|
**Target**: `_parse_number` loop の `num_str` 文字列キャリア対応
|
||||||
**Scope**: Pattern 2 (loop with break) + 既存インフラ活用
|
**Scope**: Pattern 2 (loop with break) + 既存インフラ活用
|
||||||
|
**Notes**: jsonparser_parse_number_real フィクスチャを Structured→Normalized→MIR(direct) で実装し、`num_str = num_str + ch` を LoopState キャリアとして dev テスト固定済み。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
148
docs/development/current/main/phase43-norm-canon-p2-mid.md
Normal file
148
docs/development/current/main/phase43-norm-canon-p2-mid.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
Status: Planned
|
||||||
|
Scope: Phase 43‑NORM‑CANON‑P2‑MID — JsonParser 本命 P2(`_parse_number` / `_atoi` 本体 / `_atof_loop`)を Normalized 基準に寄せるための事前設計メモ。
|
||||||
|
Update (Phase 43-A): `_atoi` 本体を Program(JSON) フィクスチャ `jsonparser_atoi_real` で dev Normalized→MIR(direct) 経路に載せ、Structured 直経路との VM 出力一致を確認済み(符号あり/なしの簡易パスまで対応。canonical 化は後続で検討)。
|
||||||
|
Update (Phase 43-C): `_parse_number` 本体を Program(JSON) フィクスチャ `jsonparser_parse_number_real` で dev Normalized→MIR(direct) 経路に載せ、Structured 直経路との VM 出力一致を dev テストで固定(num_str は現状仕様のまま据え置き)。
|
||||||
|
|
||||||
|
# Phase 43‑NORM‑CANON‑P2‑MID 設計メモ(JsonParser P2‑Mid 向け Normalized 拡張)
|
||||||
|
|
||||||
|
## 0. ゴールと前提
|
||||||
|
|
||||||
|
- ゴール
|
||||||
|
- JsonParser の本命 P2 ループ(P2‑Mid)を、既存の Normalized インフラの延長で扱えるようにするための設計方針を固める。
|
||||||
|
- 特に `_parse_number` / `_atoi` 本体 / `_atof_loop` について、
|
||||||
|
- どのキャリア・body‑local を EnvLayout に載せるか
|
||||||
|
- どの JpInst / JpOp パターンが追加で必要か
|
||||||
|
- StepScheduleBox / DigitPos / NumberAccumulation との責務分担
|
||||||
|
を整理する。
|
||||||
|
- 前提
|
||||||
|
- P2‑Core(P2 ミニ + JP skip_ws mini/real + JP atoi mini)は Phase 41 までで Normalized→MIR(direct) が canonical 済み。
|
||||||
|
- P2‑Mid は **JoinIR(Structured) までは載っているが Normalized は未対応** の状態(Phase 245/246 系)。
|
||||||
|
- 本メモは「設計レベル」で止め、実装・テスト追加は後続フェーズ(43 実装回)で扱う。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 対象ループと P2‑Mid クラスの整理
|
||||||
|
|
||||||
|
- 対象とする P2‑Mid(JsonParser 側)
|
||||||
|
- `_parse_number`(数値文字列の収集 + digit_pos break)
|
||||||
|
- `_atoi` 本体(範囲チェック + digit_pos + NumberAccumulation)
|
||||||
|
- `_atof_loop`(構造的には `_atoi` と同型の浮動小数点版)
|
||||||
|
- すべて Pattern2 Break で、既に JoinIR(Structured) には載っている:
|
||||||
|
- `_parse_number` → Phase 245‑EX で header / break / `p` 更新を Pattern2 に統合済み(`num_str` は当面対象外)。
|
||||||
|
- `_atoi` → Phase 246‑EX で DigitPos dual 値 + NumberAccumulation パターンとして JoinIR 経路に統合済み。
|
||||||
|
- `_atof_loop` → 設計上 `_atoi` と同型とみなし、P2‑Mid クラスに含める。
|
||||||
|
- P2‑Core との差分
|
||||||
|
- P2 ミニ / skip_ws / atoi ミニに比べて:
|
||||||
|
- Carrier の本数(`p` + `result` + 場合によっては `num_str`)が増える。
|
||||||
|
- body‑local / Derived Carrier(`digit_pos`, `is_digit_pos`, `digit_value` 等)の依存関係が複雑。
|
||||||
|
- 一部で文字列連結(`num_str = num_str + ch`)や Range チェック(`"0" <= ch <= "9"`)が入る。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Normalized IR に必要な拡張(EnvLayout / JpInst / JpOp)
|
||||||
|
|
||||||
|
### 2.1 EnvLayout / フィールド設計
|
||||||
|
|
||||||
|
- P2‑Mid で EnvLayout に載せる候補
|
||||||
|
- LoopState キャリア
|
||||||
|
- `_parse_number`: `p`(必須)、`num_str`(Phase 43 では **オプション**、まず `p` 単独で正規化する案も許容)。
|
||||||
|
- `_atoi` 本体 / `_atof_loop`: `i`, `result`(NumberAccumulation キャリア)。
|
||||||
|
- Condition 専用 / FromHost
|
||||||
|
- `len` / `s` / `digits` などの不変値は ParamRole::Condition として EnvLayout 側に持たない(現状どおり)。
|
||||||
|
- Derived LoopState(DigitPos 系)
|
||||||
|
- P2‑Core で既に導入済みの `digit_value` / `is_digit_pos` と同じ方針:
|
||||||
|
- EnvLayout に「FromHost ではない LoopState キャリア」として載せる。
|
||||||
|
- host_slot を持たず、ExitBinding には出さない(ループ内部完結キャリア)。
|
||||||
|
|
||||||
|
### 2.2 JpInst / JpOp 側の必要パターン
|
||||||
|
|
||||||
|
- 既存の Normalized が既に扱っているもの
|
||||||
|
- `Let { dst, op: Const / BinOp / Unary / Compare / BoxCall, args }`
|
||||||
|
- `If { cond, then_target, else_target, env }`
|
||||||
|
- `TailCallFn` / `TailCallKont`(loop_step / k_exit のみ)
|
||||||
|
- P2‑Mid で追加検証・明文化が必要なパターン
|
||||||
|
- `_parse_number`
|
||||||
|
- `substring` / `indexOf` の BoxCall パターン(P2‑Core でも使用済みだが、`num_str` 周辺の利用を含めてドキュメントで SSOT 化する)。
|
||||||
|
- 文字列連結 `num_str = num_str + ch` を
|
||||||
|
- 当面は「扱わない」(`num_str` を Carrier から外す)案
|
||||||
|
- もしくは `BinOp(Add)` として Normalized→MIR 直ブリッジに追加する案
|
||||||
|
のどちらにするかを Phase 43 実装メモで最終決定する。
|
||||||
|
- `_atoi` 本体 / `_atof_loop`
|
||||||
|
- Range チェック `ch < "0" || ch > "9"`:
|
||||||
|
- ExprLowerer / ConditionEnv 側で既に対応済みであれば、Normalized には Compare + BinOp(or) の形で入る。
|
||||||
|
- Normalized では追加の JpOp は不要(Compare / BinOp を利用)。
|
||||||
|
- NumberAccumulation パターン:
|
||||||
|
- Structured 側では `UpdateRhs::NumberAccumulation` として扱っているので、
|
||||||
|
- Normalized → MIR 直ブリッジ側で「Mul + Add + digit_value」の形を既に対応済み。
|
||||||
|
- Phase 43 では `_atoi` 本体 / `_atof_loop` でも同じシーケンスになることを前提とし、JpInst 種別追加は行わない。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. StepScheduleBox / DigitPos / NumberAccumulation の役割分担
|
||||||
|
|
||||||
|
### 3.1 StepScheduleBox(評価順)の適用範囲
|
||||||
|
|
||||||
|
- Phase 39 で導入した StepScheduleBox(Pattern2 用)は、P2‑Mid に対しても「どの StepKind をどの順番で評価するか」を決める SSOT として使う。
|
||||||
|
- `_parse_number` / `_atoi` 本体 / `_atof_loop` では、以下のようなフラグを想定:
|
||||||
|
- `has_digitpos_body_local`(DigitPos 二重値を使うか)
|
||||||
|
- `has_number_accumulation`(結果キャリアに Mul+Add があるか)
|
||||||
|
- `has_bodylocal_break`(break 条件が body‑local に依存するか)
|
||||||
|
- StepScheduleBox は、これらのフラグだけを見て
|
||||||
|
- 標準 P2: `[HeaderCond, BreakCheck, BodyInit, Updates, Tail]`
|
||||||
|
- DigitPos / atoi 系: `[HeaderCond, BodyInit, BreakCheck, Updates, Tail]`
|
||||||
|
など、評価順のバリエーションを返す「薄い箱」のまま保つ。
|
||||||
|
- Pattern2 lowerer / Normalized 変換側は、この StepSchedule に従って
|
||||||
|
- header 条件
|
||||||
|
- body‑local init(DigitPos / Range check 等)
|
||||||
|
- break 条件
|
||||||
|
- carrier 更新(NumberAccumulation / i++ 等)
|
||||||
|
を「並べるだけ」にし、条件式の詳細や body‑local の構造には踏み込まない。
|
||||||
|
|
||||||
|
### 3.2 DigitPos / NumberAccumulation との接続
|
||||||
|
|
||||||
|
- DigitPos 系
|
||||||
|
- `digit_pos` → `is_digit_pos` / `digit_value` の二重値設計は、P2‑Core と同じく「LoopState キャリア(FromHost なし)」として EnvLayout に載せる。
|
||||||
|
- ExitBinding には出さず、Normalized→MIR 直ブリッジでも Loop 内部だけで完結させる。
|
||||||
|
- Break 条件 `digit_pos < 0` は Phase 224 の DigitPosConditionNormalizer(AST→`!is_digit_pos`)を前提にし、Normalized 側は `!is_digit_pos` という bool 条件だけを受け取る。
|
||||||
|
- NumberAccumulation 系
|
||||||
|
- Structured 側の LoopUpdateAnalyzer / CarrierUpdateEmitter が `result = result * 10 + digit_value` パターンを `UpdateRhs::NumberAccumulation` として検出・JoinIR 生成済み。
|
||||||
|
- Normalized→MIR 直ブリッジは、P2‑Core と同じ Mul + Add シーケンスで MIR を吐く設計を維持し、P2‑Mid でも追加ロジックを増やさずに流用する。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Bridge / ShapeGuard / canonical 切り替え方針
|
||||||
|
|
||||||
|
- ShapeGuard 拡張
|
||||||
|
- 既存の P2‑Core 判定(P2 ミニ / skip_ws mini/real / atoi mini)に加えて、
|
||||||
|
- `_parse_number` 本体
|
||||||
|
- `_atoi` 本体
|
||||||
|
- `_atof_loop`
|
||||||
|
を P2‑Mid として検出できる Shape 種別を追加する(例: `ShapeKind::JsonparserParseNumber`, `ShapeKind::JsonparserAtoiCore`)。
|
||||||
|
- Phase 43 実装フェーズでは、まず P2‑Mid ループを **dev only** の Normalized 対象にする(canonical 切り替えは Phase 43 後半〜Phase 44 相当で検討)。
|
||||||
|
- Bridge 側の経路
|
||||||
|
- `bridge_joinir_to_mir` の入口で:
|
||||||
|
- P2‑Core: 既に canonical Normalized→MIR(direct)(Phase 41 の状態を維持)。
|
||||||
|
- P2‑Mid: `normalized_dev_enabled()` が true のときに限り Structured→Normalized→MIR(direct) を試し、テストで Structured 直経路と比較。
|
||||||
|
- Fail‑Fast 方針
|
||||||
|
- P2‑Mid では「Normalized が未対応の領域」がまだ多いため、
|
||||||
|
- dev / debug ビルドでは invariant 破壊・未対応命令で panic(Normalized 実装の穴を早期検出)。
|
||||||
|
- release / canonical OFF 時は Structured→MIR 直経路に落とす(サイレントフォールバックではなく「Normalized をそもそも使わない」構成にする)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. テストと完了条件(Phase 43 実装フェーズ向けメモ)
|
||||||
|
|
||||||
|
- テスト戦略(概要)
|
||||||
|
- `_parse_number`:
|
||||||
|
- 代表ケース("42", "7z" など)で
|
||||||
|
- Structured→MIR→VM
|
||||||
|
- Structured→Normalized→MIR(direct)→VM
|
||||||
|
の stdout / RC を比較する dev テストを追加。
|
||||||
|
- `num_str` をまだ Normalized キャリアに載せない場合でも、「p / break 条件 / DigitPos 周り」が齟齬なく動くことを確認する。
|
||||||
|
- `_atoi` 本体 / `_atof_loop`:
|
||||||
|
- 既存の `_atoi` mini dev fixture と同じ観点で、NumberAccumulation / DigitPos / Range check が Normalized 経路で再現できるかをチェック。
|
||||||
|
- Mini / 本体 / `_atof_loop` が同じ normalized helper / bridge ロジックを共有できることを確認する。
|
||||||
|
- 完了条件(Phase 43 実装のためのチェックリスト)
|
||||||
|
- EnvLayout / JpInst / JpOp / StepScheduleBox / DigitPos / NumberAccumulation の役割分担が本メモの通りに整理されている。
|
||||||
|
- P2‑Mid(`_parse_number` / `_atoi` 本体 / `_atof_loop`)に対して、どこまでを Phase 43 で扱い、どこから先を後続フェーズに回すかの線引きが明文化されている。
|
||||||
|
- `joinir-architecture-overview.md` の Phase 43 セクション(3.20)と、この設計メモの内容が矛盾していない。
|
||||||
5
src/config/env/joinir_dev.rs
vendored
5
src/config/env/joinir_dev.rs
vendored
@ -117,6 +117,11 @@ pub fn normalized_dev_enabled() -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// JOINIR_TEST_DEBUG=1 (or NYASH_JOINIR_TEST_DEBUG=1) - Verbose logging for normalized dev tests
|
||||||
|
pub fn joinir_test_debug_enabled() -> bool {
|
||||||
|
env_bool("JOINIR_TEST_DEBUG") || env_bool("NYASH_JOINIR_TEST_DEBUG")
|
||||||
|
}
|
||||||
|
|
||||||
/// Phase 82: NYASH_PHI_FALLBACK_DISABLED=1 - Disable if_phi fallback (dev mode)
|
/// Phase 82: NYASH_PHI_FALLBACK_DISABLED=1 - Disable if_phi fallback (dev mode)
|
||||||
///
|
///
|
||||||
/// lifecycle.rs の infer_type_from_phi* callsite を封じて、
|
/// lifecycle.rs の infer_type_from_phi* callsite を封じて、
|
||||||
|
|||||||
@ -8,9 +8,11 @@ use crate::mir::join_ir::lowering::condition_env::{ConditionBinding, ConditionEn
|
|||||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||||
|
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
|
||||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||||
use crate::mir::loop_pattern_detection::error_messages;
|
use crate::mir::loop_pattern_detection::error_messages;
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
fn log_pattern2(verbose: bool, tag: &str, message: impl AsRef<str>) {
|
fn log_pattern2(verbose: bool, tag: &str, message: impl AsRef<str>) {
|
||||||
if verbose {
|
if verbose {
|
||||||
@ -707,19 +709,13 @@ impl MirBuilder {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let original_carrier_count = inputs.carrier_info.carriers.len();
|
let original_carrier_count = inputs.carrier_info.carriers.len();
|
||||||
inputs.carrier_info.carriers.retain(|carrier| {
|
filter_carriers_for_updates(&mut inputs.carrier_info, &carrier_updates);
|
||||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole};
|
|
||||||
carrier_updates.contains_key(&carrier.name)
|
|
||||||
|| carrier.role == CarrierRole::ConditionOnly
|
|
||||||
|| carrier.init == CarrierInit::FromHost
|
|
||||||
|| carrier.init == CarrierInit::LoopLocalZero
|
|
||||||
});
|
|
||||||
|
|
||||||
log_pattern2(
|
log_pattern2(
|
||||||
verbose,
|
verbose,
|
||||||
"updates",
|
"updates",
|
||||||
format!(
|
format!(
|
||||||
"Phase 176-4: Filtered carriers: {} → {} (kept only carriers with updates)",
|
"Phase 176-4: Filtered carriers: {} → {} (kept only carriers with updates/condition-only/loop-local-zero)",
|
||||||
original_carrier_count,
|
original_carrier_count,
|
||||||
inputs.carrier_info.carriers.len()
|
inputs.carrier_info.carriers.len()
|
||||||
),
|
),
|
||||||
@ -858,6 +854,19 @@ impl MirBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 更新を持たない FromHost キャリアを落とすヘルパー。
|
||||||
|
fn filter_carriers_for_updates(
|
||||||
|
carrier_info: &mut CarrierInfo,
|
||||||
|
carrier_updates: &BTreeMap<String, UpdateExpr>,
|
||||||
|
) {
|
||||||
|
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole};
|
||||||
|
carrier_info.carriers.retain(|carrier| {
|
||||||
|
carrier_updates.contains_key(&carrier.name)
|
||||||
|
|| carrier.role == CarrierRole::ConditionOnly
|
||||||
|
|| carrier.init == CarrierInit::LoopLocalZero
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@ -138,6 +138,8 @@ impl AstToJoinIrLowerer {
|
|||||||
"-" => BinOpKind::Sub,
|
"-" => BinOpKind::Sub,
|
||||||
"*" => BinOpKind::Mul,
|
"*" => BinOpKind::Mul,
|
||||||
"/" => BinOpKind::Div,
|
"/" => BinOpKind::Div,
|
||||||
|
"&&" => BinOpKind::And,
|
||||||
|
"||" => BinOpKind::Or,
|
||||||
_ => panic!("Unsupported binary op: {}", op_str),
|
_ => panic!("Unsupported binary op: {}", op_str),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -24,10 +24,10 @@ use super::common::{
|
|||||||
build_join_module, create_k_exit_function, create_loop_context, parse_program_json,
|
build_join_module, create_k_exit_function, create_loop_context, parse_program_json,
|
||||||
process_local_inits,
|
process_local_inits,
|
||||||
};
|
};
|
||||||
|
use super::param_guess::{build_param_order, compute_param_guess};
|
||||||
use super::{AstToJoinIrLowerer, JoinModule, LoweringError};
|
use super::{AstToJoinIrLowerer, JoinModule, LoweringError};
|
||||||
use crate::mir::join_ir::{JoinFunction, JoinInst};
|
use crate::mir::join_ir::{JoinFunction, JoinInst};
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
/// Break パターンを JoinModule に変換
|
/// Break パターンを JoinModule に変換
|
||||||
///
|
///
|
||||||
@ -70,7 +70,10 @@ pub fn lower(
|
|||||||
|
|
||||||
let break_cond_expr = &break_if_stmt["cond"];
|
let break_cond_expr = &break_if_stmt["cond"];
|
||||||
|
|
||||||
let (param_order, loop_var_name, acc_name) = compute_param_order(&entry_ctx);
|
let param_guess = compute_param_guess(&entry_ctx);
|
||||||
|
let param_order = build_param_order(¶m_guess, &entry_ctx);
|
||||||
|
let loop_var_name = param_guess.loop_var.0.clone();
|
||||||
|
let acc_name = param_guess.acc.0.clone();
|
||||||
let loop_cond_expr = &loop_node["cond"];
|
let loop_cond_expr = &loop_node["cond"];
|
||||||
|
|
||||||
// 5. entry 関数を生成
|
// 5. entry 関数を生成
|
||||||
@ -245,48 +248,3 @@ fn create_loop_step_function_break(
|
|||||||
exit_cont: None,
|
exit_cont: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_param_order(
|
|
||||||
entry_ctx: &super::super::context::ExtractCtx,
|
|
||||||
) -> (Vec<(String, ValueId)>, String, String) {
|
|
||||||
let loop_var_name = "i".to_string();
|
|
||||||
let loop_var = entry_ctx
|
|
||||||
.get_var(&loop_var_name)
|
|
||||||
.expect("i must be initialized");
|
|
||||||
|
|
||||||
let (acc_name, acc_var) = if let Some(v) = entry_ctx.get_var("acc") {
|
|
||||||
("acc".to_string(), v)
|
|
||||||
} else if let Some(v) = entry_ctx.get_var("result") {
|
|
||||||
("result".to_string(), v)
|
|
||||||
} else {
|
|
||||||
panic!("acc or result must be initialized");
|
|
||||||
};
|
|
||||||
|
|
||||||
let (len_name, len_var) = if let Some(v) = entry_ctx.get_var("n") {
|
|
||||||
("n".to_string(), v)
|
|
||||||
} else if let Some(v) = entry_ctx.get_var("len") {
|
|
||||||
("len".to_string(), v)
|
|
||||||
} else {
|
|
||||||
panic!("n or len must be provided as parameter");
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut param_order = vec![
|
|
||||||
(loop_var_name.clone(), loop_var),
|
|
||||||
(acc_name.clone(), acc_var),
|
|
||||||
(len_name.clone(), len_var),
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut seen: BTreeSet<String> =
|
|
||||||
[loop_var_name.clone(), acc_name.clone(), len_name.clone()]
|
|
||||||
.into_iter()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for (name, var_id) in &entry_ctx.var_map {
|
|
||||||
if !seen.contains(name) {
|
|
||||||
param_order.push((name.clone(), *var_id));
|
|
||||||
seen.insert(name.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(param_order, loop_var_name, acc_name)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
//! - `create_k_exit_function()`: k_exit 関数生成
|
//! - `create_k_exit_function()`: k_exit 関数生成
|
||||||
|
|
||||||
use super::{AstToJoinIrLowerer, JoinModule};
|
use super::{AstToJoinIrLowerer, JoinModule};
|
||||||
|
use super::super::stmt_handlers::StatementEffect;
|
||||||
use crate::mir::join_ir::JoinIrPhase;
|
use crate::mir::join_ir::JoinIrPhase;
|
||||||
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst};
|
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst};
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
@ -119,19 +120,15 @@ pub fn process_local_inits(
|
|||||||
let stmt_type = stmt["type"].as_str().expect("Statement must have type");
|
let stmt_type = stmt["type"].as_str().expect("Statement must have type");
|
||||||
|
|
||||||
match stmt_type {
|
match stmt_type {
|
||||||
"Local" => {
|
"Local" | "Assignment" | "If" => {
|
||||||
let var_name = stmt["name"]
|
let (insts, effect) = lowerer.lower_statement(stmt, ctx);
|
||||||
.as_str()
|
|
||||||
.expect("Local must have 'name'")
|
|
||||||
.to_string();
|
|
||||||
let expr = &stmt["expr"];
|
|
||||||
|
|
||||||
// extract_value で式を評価
|
|
||||||
let (var_id, insts) = lowerer.extract_value(expr, ctx);
|
|
||||||
init_insts.extend(insts);
|
init_insts.extend(insts);
|
||||||
|
|
||||||
// 同名再宣言 = var_map を更新(再代入の意味論)
|
if let StatementEffect::VarUpdate { name, value_id } = effect {
|
||||||
ctx.register_param(var_name, var_id);
|
ctx.register_param(name, value_id);
|
||||||
|
} else if matches!(effect, StatementEffect::SideEffect) {
|
||||||
|
panic!("Unexpected side-effecting statement before Loop: {}", stmt_type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => panic!("Unexpected statement type before Loop: {}", stmt_type),
|
_ => panic!("Unexpected statement type before Loop: {}", stmt_type),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ pub mod break_pattern;
|
|||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod continue_pattern;
|
pub mod continue_pattern;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
|
pub mod param_guess;
|
||||||
pub mod print_tokens;
|
pub mod print_tokens;
|
||||||
pub mod simple;
|
pub mod simple;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,71 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
|
/// 推定したループ引数の並び。
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct ParamGuess {
|
||||||
|
pub loop_var: (String, ValueId),
|
||||||
|
pub acc: (String, ValueId),
|
||||||
|
pub len: Option<(String, ValueId)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Break パターン向けのパラメータ推定テーブル。
|
||||||
|
/// - ループ変数優先順: p → i → 先頭の var
|
||||||
|
/// - アキュムレータ優先順: num_str → acc → result → ループ変数
|
||||||
|
pub(crate) fn compute_param_guess(ctx: &super::super::context::ExtractCtx) -> ParamGuess {
|
||||||
|
let loop_var = ctx
|
||||||
|
.get_var("p")
|
||||||
|
.map(|v| ("p".to_string(), v))
|
||||||
|
.or_else(|| ctx.get_var("i").map(|v| ("i".to_string(), v)))
|
||||||
|
.or_else(|| {
|
||||||
|
ctx.var_map
|
||||||
|
.iter()
|
||||||
|
.find(|(name, _)| name.as_str() != "me")
|
||||||
|
.map(|(name, v)| (name.clone(), *v))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| panic!("[joinir/frontend] break_pattern: loop variable missing"));
|
||||||
|
|
||||||
|
let acc = if let Some(v) = ctx.get_var("num_str") {
|
||||||
|
("num_str".to_string(), v)
|
||||||
|
} else if let Some(v) = ctx.get_var("acc") {
|
||||||
|
("acc".to_string(), v)
|
||||||
|
} else if let Some(v) = ctx.get_var("result") {
|
||||||
|
("result".to_string(), v)
|
||||||
|
} else {
|
||||||
|
loop_var.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let len = ctx
|
||||||
|
.get_var("n")
|
||||||
|
.map(|v| ("n".to_string(), v))
|
||||||
|
.or_else(|| ctx.get_var("len").map(|v| ("len".to_string(), v)));
|
||||||
|
|
||||||
|
ParamGuess { loop_var, acc, len }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 推定結果と ExtractCtx からパラメータの並びを構成する。
|
||||||
|
pub(crate) fn build_param_order(
|
||||||
|
guess: &ParamGuess,
|
||||||
|
entry_ctx: &super::super::context::ExtractCtx,
|
||||||
|
) -> Vec<(String, ValueId)> {
|
||||||
|
let mut order = Vec::new();
|
||||||
|
order.push(guess.loop_var.clone());
|
||||||
|
if guess.acc.0 != guess.loop_var.0 {
|
||||||
|
order.push(guess.acc.clone());
|
||||||
|
}
|
||||||
|
if let Some(len) = &guess.len {
|
||||||
|
if len.0 != guess.loop_var.0 && len.0 != guess.acc.0 {
|
||||||
|
order.push(len.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut seen: BTreeSet<String> = order.iter().map(|(n, _)| n.clone()).collect();
|
||||||
|
for (name, var_id) in &entry_ctx.var_map {
|
||||||
|
if !seen.contains(name) {
|
||||||
|
order.push((name.clone(), *var_id));
|
||||||
|
seen.insert(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
order
|
||||||
|
}
|
||||||
@ -54,13 +54,21 @@ impl AstToJoinIrLowerer {
|
|||||||
pub(crate) fn has_break_in_loop_body(loop_body: &[serde_json::Value]) -> bool {
|
pub(crate) fn has_break_in_loop_body(loop_body: &[serde_json::Value]) -> bool {
|
||||||
loop_body.iter().any(|stmt| {
|
loop_body.iter().any(|stmt| {
|
||||||
if stmt["type"].as_str() == Some("If") {
|
if stmt["type"].as_str() == Some("If") {
|
||||||
if let Some(then_body) = stmt["then"].as_array() {
|
let then_has = stmt["then"]
|
||||||
then_body
|
.as_array()
|
||||||
.iter()
|
.map(|body| {
|
||||||
|
body.iter()
|
||||||
.any(|s| s["type"].as_str() == Some("Break"))
|
.any(|s| s["type"].as_str() == Some("Break"))
|
||||||
} else {
|
})
|
||||||
false
|
.unwrap_or(false);
|
||||||
}
|
let else_has = stmt["else"]
|
||||||
|
.as_array()
|
||||||
|
.map(|body| {
|
||||||
|
body.iter()
|
||||||
|
.any(|s| s["type"].as_str() == Some("Break"))
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
then_has || else_has
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@ -71,13 +79,21 @@ impl AstToJoinIrLowerer {
|
|||||||
pub(crate) fn has_continue_in_loop_body(loop_body: &[serde_json::Value]) -> bool {
|
pub(crate) fn has_continue_in_loop_body(loop_body: &[serde_json::Value]) -> bool {
|
||||||
loop_body.iter().any(|stmt| {
|
loop_body.iter().any(|stmt| {
|
||||||
if stmt["type"].as_str() == Some("If") {
|
if stmt["type"].as_str() == Some("If") {
|
||||||
if let Some(then_body) = stmt["then"].as_array() {
|
let then_has = stmt["then"]
|
||||||
then_body
|
.as_array()
|
||||||
.iter()
|
.map(|body| {
|
||||||
|
body.iter()
|
||||||
.any(|s| s["type"].as_str() == Some("Continue"))
|
.any(|s| s["type"].as_str() == Some("Continue"))
|
||||||
} else {
|
})
|
||||||
false
|
.unwrap_or(false);
|
||||||
}
|
let else_has = stmt["else"]
|
||||||
|
.as_array()
|
||||||
|
.map(|body| {
|
||||||
|
body.iter()
|
||||||
|
.any(|s| s["type"].as_str() == Some("Continue"))
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
then_has || else_has
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,24 +52,25 @@ enum FunctionRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_function_route(func_name: &str) -> Result<FunctionRoute, String> {
|
fn resolve_function_route(func_name: &str) -> Result<FunctionRoute, String> {
|
||||||
const IF_RETURN_NAMES: &[&str] = &["test", "local", "_read_value_from_pair"];
|
const TABLE: &[(&str, FunctionRoute)] = &[
|
||||||
const LOOP_NAMES: &[&str] = &[
|
("test", FunctionRoute::IfReturn),
|
||||||
"simple",
|
("local", FunctionRoute::IfReturn),
|
||||||
"filter",
|
("_read_value_from_pair", FunctionRoute::IfReturn),
|
||||||
"print_tokens",
|
("simple", FunctionRoute::LoopFrontend),
|
||||||
"map",
|
("filter", FunctionRoute::LoopFrontend),
|
||||||
"reduce",
|
("print_tokens", FunctionRoute::LoopFrontend),
|
||||||
"fold",
|
("map", FunctionRoute::LoopFrontend),
|
||||||
"jsonparser_skip_ws_mini",
|
("reduce", FunctionRoute::LoopFrontend),
|
||||||
"jsonparser_atoi_mini",
|
("fold", FunctionRoute::LoopFrontend),
|
||||||
|
("jsonparser_skip_ws_mini", FunctionRoute::LoopFrontend),
|
||||||
|
("jsonparser_skip_ws_real", FunctionRoute::LoopFrontend),
|
||||||
|
("jsonparser_atoi_mini", FunctionRoute::LoopFrontend),
|
||||||
|
("jsonparser_atoi_real", FunctionRoute::LoopFrontend),
|
||||||
|
("jsonparser_parse_number_real", FunctionRoute::LoopFrontend),
|
||||||
];
|
];
|
||||||
|
|
||||||
if IF_RETURN_NAMES.contains(&func_name) {
|
if let Some((_, route)) = TABLE.iter().find(|(name, _)| *name == func_name) {
|
||||||
return Ok(FunctionRoute::IfReturn);
|
return Ok(*route);
|
||||||
}
|
|
||||||
|
|
||||||
if LOOP_NAMES.contains(&func_name) {
|
|
||||||
return Ok(FunctionRoute::LoopFrontend);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if func_name == "parse_loop" {
|
if func_name == "parse_loop" {
|
||||||
|
|||||||
@ -58,7 +58,6 @@
|
|||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
mod boundary_builder;
|
mod boundary_builder;
|
||||||
mod header_break_lowering;
|
mod header_break_lowering;
|
||||||
mod step_schedule;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
@ -71,6 +70,9 @@ use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
|||||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||||
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
|
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
|
||||||
|
use crate::mir::join_ir::lowering::pattern2_step_schedule::{
|
||||||
|
build_pattern2_schedule, Pattern2ScheduleContext, Pattern2StepKind,
|
||||||
|
};
|
||||||
use crate::mir::join_ir::lowering::update_env::UpdateEnv;
|
use crate::mir::join_ir::lowering::update_env::UpdateEnv;
|
||||||
use crate::mir::join_ir::{
|
use crate::mir::join_ir::{
|
||||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
||||||
@ -84,7 +86,6 @@ use crate::mir::ValueId;
|
|||||||
use boundary_builder::build_fragment_meta;
|
use boundary_builder::build_fragment_meta;
|
||||||
use header_break_lowering::{lower_break_condition, lower_header_condition};
|
use header_break_lowering::{lower_break_condition, lower_header_condition};
|
||||||
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||||
use step_schedule::{Pattern2Step, Pattern2StepSchedule};
|
|
||||||
|
|
||||||
/// Lower Pattern 2 (Loop with Conditional Break) to JoinIR
|
/// Lower Pattern 2 (Loop with Conditional Break) to JoinIR
|
||||||
///
|
///
|
||||||
@ -260,6 +261,20 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|||||||
"[joinir/pattern2] Phase 201: loop_step params - i_param={:?}, carrier_params={:?}",
|
"[joinir/pattern2] Phase 201: loop_step params - i_param={:?}, carrier_params={:?}",
|
||||||
i_param, carrier_param_ids
|
i_param, carrier_param_ids
|
||||||
);
|
);
|
||||||
|
if crate::config::env::joinir_dev_enabled()
|
||||||
|
|| crate::config::env::joinir_test_debug_enabled()
|
||||||
|
{
|
||||||
|
eprintln!(
|
||||||
|
"[joinir/pattern2/debug] loop_var='{}' env.get(loop_var)={:?}, carriers={:?}",
|
||||||
|
loop_var_name,
|
||||||
|
env.get(loop_var_name),
|
||||||
|
carrier_info
|
||||||
|
.carriers
|
||||||
|
.iter()
|
||||||
|
.map(|c| (c.name.clone(), c.join_id))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Phase 169 / Phase 171-fix / Phase 240-EX / Phase 244: Lower condition
|
// Phase 169 / Phase 171-fix / Phase 240-EX / Phase 244: Lower condition
|
||||||
let (cond_value, mut cond_instructions) = lower_header_condition(
|
let (cond_value, mut cond_instructions) = lower_header_condition(
|
||||||
@ -327,23 +342,9 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|||||||
let mut loop_step_func = JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params);
|
let mut loop_step_func = JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params);
|
||||||
|
|
||||||
// Decide evaluation order (header/body-init/break/updates/tail) up-front.
|
// Decide evaluation order (header/body-init/break/updates/tail) up-front.
|
||||||
let schedule =
|
let schedule_ctx =
|
||||||
Pattern2StepSchedule::for_pattern2(body_local_env.as_ref().map(|env| &**env), carrier_info);
|
Pattern2ScheduleContext::from_env(body_local_env.as_ref().map(|env| &**env), carrier_info);
|
||||||
let schedule_desc: Vec<&str> = schedule
|
let schedule = build_pattern2_schedule(&schedule_ctx);
|
||||||
.iter()
|
|
||||||
.map(|step| match step {
|
|
||||||
Pattern2Step::HeaderAndNaturalExit => "header+exit",
|
|
||||||
Pattern2Step::BodyLocalInit => "body-init",
|
|
||||||
Pattern2Step::BreakCondition => "break",
|
|
||||||
Pattern2Step::CarrierUpdates => "updates",
|
|
||||||
Pattern2Step::TailCall => "tail",
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
eprintln!(
|
|
||||||
"[pattern2/schedule] Selected Pattern2 step schedule: {:?} ({})",
|
|
||||||
schedule_desc,
|
|
||||||
schedule.reason()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Collect fragments per step; append them according to the schedule below.
|
// Collect fragments per step; append them according to the schedule below.
|
||||||
let mut header_block: Vec<JoinInst> = Vec::new();
|
let mut header_block: Vec<JoinInst> = Vec::new();
|
||||||
@ -661,11 +662,11 @@ pub(crate) fn lower_loop_with_break_minimal(
|
|||||||
// Apply scheduled order to assemble the loop_step body.
|
// Apply scheduled order to assemble the loop_step body.
|
||||||
for step in schedule.iter() {
|
for step in schedule.iter() {
|
||||||
match step {
|
match step {
|
||||||
Pattern2Step::HeaderAndNaturalExit => loop_step_func.body.append(&mut header_block),
|
Pattern2StepKind::HeaderCond => loop_step_func.body.append(&mut header_block),
|
||||||
Pattern2Step::BodyLocalInit => loop_step_func.body.append(&mut body_init_block),
|
Pattern2StepKind::BodyInit => loop_step_func.body.append(&mut body_init_block),
|
||||||
Pattern2Step::BreakCondition => loop_step_func.body.append(&mut break_block),
|
Pattern2StepKind::BreakCheck => loop_step_func.body.append(&mut break_block),
|
||||||
Pattern2Step::CarrierUpdates => loop_step_func.body.append(&mut carrier_update_block),
|
Pattern2StepKind::Updates => loop_step_func.body.append(&mut carrier_update_block),
|
||||||
Pattern2Step::TailCall => loop_step_func.body.append(&mut tail_block),
|
Pattern2StepKind::Tail => loop_step_func.body.append(&mut tail_block),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
//! Pattern 2 step scheduler.
|
|
||||||
//!
|
|
||||||
//! Decides the evaluation order for Pattern 2 lowering without hardcoding it
|
|
||||||
//! in the lowerer. This keeps the lowerer focused on emitting fragments while
|
|
||||||
//! the scheduler decides how to interleave them (e.g., body-local init before
|
|
||||||
//! break checks when the break depends on body-local values).
|
|
||||||
|
|
||||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit};
|
|
||||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
|
||||||
|
|
||||||
/// Steps that can be reordered by the scheduler.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub(crate) enum Pattern2Step {
|
|
||||||
HeaderAndNaturalExit,
|
|
||||||
BodyLocalInit,
|
|
||||||
BreakCondition,
|
|
||||||
CarrierUpdates,
|
|
||||||
TailCall,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Data-driven schedule for Pattern 2 lowering.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(crate) struct Pattern2StepSchedule {
|
|
||||||
steps: Vec<Pattern2Step>,
|
|
||||||
reason: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pattern2StepSchedule {
|
|
||||||
/// Choose a schedule based on whether the break condition relies on fresh
|
|
||||||
/// body-local values (DigitPos-style).
|
|
||||||
pub(crate) fn for_pattern2(
|
|
||||||
body_local_env: Option<&LoopBodyLocalEnv>,
|
|
||||||
carrier_info: &CarrierInfo,
|
|
||||||
) -> Self {
|
|
||||||
let has_body_locals = body_local_env.map(|env| !env.is_empty()).unwrap_or(false);
|
|
||||||
let has_loop_local_carrier = carrier_info
|
|
||||||
.carriers
|
|
||||||
.iter()
|
|
||||||
.any(|c| matches!(c.init, CarrierInit::LoopLocalZero));
|
|
||||||
|
|
||||||
// If there are body-local dependencies, evaluate them before the break
|
|
||||||
// condition so the break uses fresh values.
|
|
||||||
if has_body_locals || has_loop_local_carrier {
|
|
||||||
Self {
|
|
||||||
steps: vec![
|
|
||||||
Pattern2Step::HeaderAndNaturalExit,
|
|
||||||
Pattern2Step::BodyLocalInit,
|
|
||||||
Pattern2Step::BreakCondition,
|
|
||||||
Pattern2Step::CarrierUpdates,
|
|
||||||
Pattern2Step::TailCall,
|
|
||||||
],
|
|
||||||
reason: "body-local break dependency",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Default order: header → break → updates → tail.
|
|
||||||
Self {
|
|
||||||
steps: vec![
|
|
||||||
Pattern2Step::HeaderAndNaturalExit,
|
|
||||||
Pattern2Step::BreakCondition,
|
|
||||||
Pattern2Step::BodyLocalInit,
|
|
||||||
Pattern2Step::CarrierUpdates,
|
|
||||||
Pattern2Step::TailCall,
|
|
||||||
],
|
|
||||||
reason: "default",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn iter(&self) -> impl Iterator<Item = Pattern2Step> + '_ {
|
|
||||||
self.steps.iter().copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn reason(&self) -> &'static str {
|
|
||||||
self.reason
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -61,6 +61,7 @@ pub(crate) mod loop_view_builder; // Phase 33-23: Loop lowering dispatch
|
|||||||
pub mod loop_with_break_minimal; // Phase 188-Impl-2: Pattern 2 minimal lowerer
|
pub mod loop_with_break_minimal; // Phase 188-Impl-2: Pattern 2 minimal lowerer
|
||||||
pub mod loop_with_continue_minimal;
|
pub mod loop_with_continue_minimal;
|
||||||
pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven)
|
pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven)
|
||||||
|
pub(crate) mod pattern2_step_schedule; // Phase 39: Pattern2 evaluation order scheduler
|
||||||
pub mod method_return_hint; // Phase 83: P3-D 既知メソッド戻り値型推論箱
|
pub mod method_return_hint; // Phase 83: P3-D 既知メソッド戻り値型推論箱
|
||||||
pub mod scope_manager; // Phase 231: Unified variable scope management // Phase 195: Pattern 4 minimal lowerer
|
pub mod scope_manager; // Phase 231: Unified variable scope management // Phase 195: Pattern 4 minimal lowerer
|
||||||
// Phase 242-EX-A: loop_with_if_phi_minimal removed - replaced by loop_with_if_phi_if_sum
|
// Phase 242-EX-A: loop_with_if_phi_minimal removed - replaced by loop_with_if_phi_if_sum
|
||||||
|
|||||||
224
src/mir/join_ir/lowering/pattern2_step_schedule.rs
Normal file
224
src/mir/join_ir/lowering/pattern2_step_schedule.rs
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
//! Pattern 2 step scheduler (StepScheduleBox).
|
||||||
|
//!
|
||||||
|
//! Decides the evaluation order for Pattern 2 lowering without hardcoding it
|
||||||
|
//! inside the lowerer. This keeps the lowerer focused on emitting fragments,
|
||||||
|
//! while this box decides how to interleave them (e.g., body-local init before
|
||||||
|
//! break checks when the break depends on body-local values).
|
||||||
|
|
||||||
|
use crate::config::env;
|
||||||
|
use crate::config::env::joinir_dev::joinir_test_debug_enabled;
|
||||||
|
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit};
|
||||||
|
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||||
|
|
||||||
|
/// Steps that can be reordered by the scheduler.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(crate) enum Pattern2StepKind {
|
||||||
|
HeaderCond,
|
||||||
|
BodyInit,
|
||||||
|
BreakCheck,
|
||||||
|
Updates,
|
||||||
|
Tail,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pattern2StepKind {
|
||||||
|
fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Pattern2StepKind::HeaderCond => "header-cond",
|
||||||
|
Pattern2StepKind::BodyInit => "body-init",
|
||||||
|
Pattern2StepKind::BreakCheck => "break",
|
||||||
|
Pattern2StepKind::Updates => "updates",
|
||||||
|
Pattern2StepKind::Tail => "tail",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data-driven schedule for Pattern 2 lowering.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct Pattern2StepSchedule {
|
||||||
|
steps: Vec<Pattern2StepKind>,
|
||||||
|
reason: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pattern2StepSchedule {
|
||||||
|
pub(crate) fn iter(&self) -> impl Iterator<Item = Pattern2StepKind> + '_ {
|
||||||
|
self.steps.iter().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn reason(&self) -> &'static str {
|
||||||
|
self.reason
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn steps(&self) -> &[Pattern2StepKind] {
|
||||||
|
&self.steps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimal context for deciding the step order.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub(crate) struct Pattern2ScheduleContext {
|
||||||
|
pub(crate) has_body_local_init: bool,
|
||||||
|
pub(crate) has_loop_local_carrier: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pattern2ScheduleContext {
|
||||||
|
pub(crate) fn from_env(
|
||||||
|
body_local_env: Option<&LoopBodyLocalEnv>,
|
||||||
|
carrier_info: &CarrierInfo,
|
||||||
|
) -> Self {
|
||||||
|
let has_body_local_init = body_local_env.map(|env| !env.is_empty()).unwrap_or(false);
|
||||||
|
let has_loop_local_carrier = carrier_info
|
||||||
|
.carriers
|
||||||
|
.iter()
|
||||||
|
.any(|c| matches!(c.init, CarrierInit::LoopLocalZero));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
has_body_local_init,
|
||||||
|
has_loop_local_carrier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires_body_init_before_break(&self) -> bool {
|
||||||
|
self.has_body_local_init || self.has_loop_local_carrier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a schedule for Pattern 2 lowering.
|
||||||
|
///
|
||||||
|
/// - Default P2: header → break → body-init → updates → tail
|
||||||
|
/// - Body-local break dependency (DigitPos/_atoi style):
|
||||||
|
/// header → body-init → break → updates → tail
|
||||||
|
pub(crate) fn build_pattern2_schedule(ctx: &Pattern2ScheduleContext) -> Pattern2StepSchedule {
|
||||||
|
let schedule = if ctx.requires_body_init_before_break() {
|
||||||
|
Pattern2StepSchedule {
|
||||||
|
steps: vec![
|
||||||
|
Pattern2StepKind::HeaderCond,
|
||||||
|
Pattern2StepKind::BodyInit,
|
||||||
|
Pattern2StepKind::BreakCheck,
|
||||||
|
Pattern2StepKind::Updates,
|
||||||
|
Pattern2StepKind::Tail,
|
||||||
|
],
|
||||||
|
reason: "body-local break dependency",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Pattern2StepSchedule {
|
||||||
|
steps: vec![
|
||||||
|
Pattern2StepKind::HeaderCond,
|
||||||
|
Pattern2StepKind::BreakCheck,
|
||||||
|
Pattern2StepKind::BodyInit,
|
||||||
|
Pattern2StepKind::Updates,
|
||||||
|
Pattern2StepKind::Tail,
|
||||||
|
],
|
||||||
|
reason: "default",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log_schedule(ctx, &schedule);
|
||||||
|
schedule
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_schedule(ctx: &Pattern2ScheduleContext, schedule: &Pattern2StepSchedule) {
|
||||||
|
if !(env::joinir_dev_enabled() || joinir_test_debug_enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let steps_desc = schedule
|
||||||
|
.steps()
|
||||||
|
.iter()
|
||||||
|
.map(Pattern2StepKind::as_str)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" -> ");
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"[joinir/p2-sched] steps={steps_desc} reason={} ctx={{body_local_init={}, loop_local_carrier={}}}",
|
||||||
|
schedule.reason(),
|
||||||
|
ctx.has_body_local_init,
|
||||||
|
ctx.has_loop_local_carrier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::mir::join_ir::lowering::carrier_info::{CarrierRole, CarrierVar};
|
||||||
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
|
fn carrier(loop_local: bool) -> CarrierVar {
|
||||||
|
let init = if loop_local {
|
||||||
|
CarrierInit::LoopLocalZero
|
||||||
|
} else {
|
||||||
|
CarrierInit::FromHost
|
||||||
|
};
|
||||||
|
CarrierVar::with_role_and_init(
|
||||||
|
"c".to_string(),
|
||||||
|
ValueId(1),
|
||||||
|
CarrierRole::LoopState,
|
||||||
|
init,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn carrier_info(carriers: Vec<CarrierVar>) -> CarrierInfo {
|
||||||
|
CarrierInfo {
|
||||||
|
loop_var_name: "i".to_string(),
|
||||||
|
loop_var_id: ValueId(0),
|
||||||
|
carriers,
|
||||||
|
trim_helper: None,
|
||||||
|
promoted_loopbodylocals: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_schedule_break_before_body_init() {
|
||||||
|
let ctx = Pattern2ScheduleContext::from_env(None, &carrier_info(vec![]));
|
||||||
|
let schedule = build_pattern2_schedule(&ctx);
|
||||||
|
assert_eq!(
|
||||||
|
schedule.steps(),
|
||||||
|
&[
|
||||||
|
Pattern2StepKind::HeaderCond,
|
||||||
|
Pattern2StepKind::BreakCheck,
|
||||||
|
Pattern2StepKind::BodyInit,
|
||||||
|
Pattern2StepKind::Updates,
|
||||||
|
Pattern2StepKind::Tail
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(schedule.reason(), "default");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn body_local_moves_init_before_break() {
|
||||||
|
let mut body_env = LoopBodyLocalEnv::new();
|
||||||
|
body_env.insert("tmp".to_string(), ValueId(5));
|
||||||
|
|
||||||
|
let ctx =
|
||||||
|
Pattern2ScheduleContext::from_env(Some(&body_env), &carrier_info(vec![carrier(false)]));
|
||||||
|
let schedule = build_pattern2_schedule(&ctx);
|
||||||
|
assert_eq!(
|
||||||
|
schedule.steps(),
|
||||||
|
&[
|
||||||
|
Pattern2StepKind::HeaderCond,
|
||||||
|
Pattern2StepKind::BodyInit,
|
||||||
|
Pattern2StepKind::BreakCheck,
|
||||||
|
Pattern2StepKind::Updates,
|
||||||
|
Pattern2StepKind::Tail
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(schedule.reason(), "body-local break dependency");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loop_local_carrier_triggers_body_first() {
|
||||||
|
let ctx =
|
||||||
|
Pattern2ScheduleContext::from_env(None, &carrier_info(vec![carrier(true)]));
|
||||||
|
let schedule = build_pattern2_schedule(&ctx);
|
||||||
|
assert_eq!(
|
||||||
|
schedule.steps(),
|
||||||
|
&[
|
||||||
|
Pattern2StepKind::HeaderCond,
|
||||||
|
Pattern2StepKind::BodyInit,
|
||||||
|
Pattern2StepKind::BreakCheck,
|
||||||
|
Pattern2StepKind::Updates,
|
||||||
|
Pattern2StepKind::Tail
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(schedule.reason(), "body-local break dependency");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -51,8 +51,6 @@ pub use normalized::{
|
|||||||
normalize_pattern1_minimal, normalize_pattern2_minimal, normalized_pattern1_to_structured,
|
normalize_pattern1_minimal, normalize_pattern2_minimal, normalized_pattern1_to_structured,
|
||||||
normalized_pattern2_to_structured, NormalizedModule,
|
normalized_pattern2_to_structured, NormalizedModule,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "normalized_dev")]
|
|
||||||
pub use normalized::fixtures;
|
|
||||||
pub use verify::verify_progress_for_skip_ws;
|
pub use verify::verify_progress_for_skip_ws;
|
||||||
|
|
||||||
// Phase 200-3: Contract verification functions are in merge/mod.rs (private module access)
|
// Phase 200-3: Contract verification functions are in merge/mod.rs (private module access)
|
||||||
|
|||||||
@ -93,6 +93,8 @@ pub enum JpOp {
|
|||||||
Unary(UnaryOp),
|
Unary(UnaryOp),
|
||||||
Compare(CompareOp),
|
Compare(CompareOp),
|
||||||
BoxCall { box_name: String, method: String },
|
BoxCall { box_name: String, method: String },
|
||||||
|
/// 三項演算子(cond ? then : else)
|
||||||
|
Select,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Normalized JoinIR モジュール(テスト専用)。
|
/// Normalized JoinIR モジュール(テスト専用)。
|
||||||
@ -115,7 +117,7 @@ impl NormalizedModule {
|
|||||||
#[cfg(feature = "normalized_dev")]
|
#[cfg(feature = "normalized_dev")]
|
||||||
fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
|
fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
|
||||||
if module.phase != JoinIrPhase::Normalized {
|
if module.phase != JoinIrPhase::Normalized {
|
||||||
return Err("Normalized verifier: phase must be Normalized".to_string());
|
return Err("[joinir/normalized-dev] pattern1: phase must be Normalized".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Env field bounds check
|
// Env field bounds check
|
||||||
@ -127,7 +129,7 @@ fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
|
|||||||
JpInst::EnvLoad { field, .. } | JpInst::EnvStore { field, .. } => {
|
JpInst::EnvLoad { field, .. } | JpInst::EnvStore { field, .. } => {
|
||||||
if *field >= field_count {
|
if *field >= field_count {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Env field out of range: {} (fields={})",
|
"[joinir/normalized-dev] pattern1: env field out of range: {} (fields={})",
|
||||||
field, field_count
|
field, field_count
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -156,7 +158,7 @@ fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
|
|||||||
JpInst::TailCallFn { .. } | JpInst::TailCallKont { .. } | JpInst::If { .. } => {}
|
JpInst::TailCallFn { .. } | JpInst::TailCallKont { .. } | JpInst::If { .. } => {}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Function '{}' does not end with tail call/if",
|
"[joinir/normalized-dev] pattern1: function '{}' does not end with tail call/if",
|
||||||
func.name
|
func.name
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -221,6 +223,12 @@ pub fn normalized_pattern1_to_structured(norm: &NormalizedModule) -> JoinModule
|
|||||||
op: *op,
|
op: *op,
|
||||||
operand: args.get(0).copied().unwrap_or(ValueId(0)),
|
operand: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||||
})),
|
})),
|
||||||
|
JpOp::Select => func.body.push(JoinInst::Compute(MirLikeInst::Select {
|
||||||
|
dst: *dst,
|
||||||
|
cond: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||||
|
then_val: args.get(1).copied().unwrap_or(ValueId(0)),
|
||||||
|
else_val: args.get(2).copied().unwrap_or(ValueId(0)),
|
||||||
|
})),
|
||||||
JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||||
dst: *dst,
|
dst: *dst,
|
||||||
op: *op,
|
op: *op,
|
||||||
@ -300,8 +308,30 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
|
|||||||
let mut max = 3;
|
let mut max = 3;
|
||||||
#[cfg(feature = "normalized_dev")]
|
#[cfg(feature = "normalized_dev")]
|
||||||
{
|
{
|
||||||
if shape_guard::is_jsonparser_atoi_mini(structured) {
|
let shapes = shape_guard::supported_shapes(structured);
|
||||||
max = 8;
|
if shapes
|
||||||
|
.iter()
|
||||||
|
.any(|s| matches!(s, NormalizedDevShape::JsonparserAtoiMini))
|
||||||
|
{
|
||||||
|
max = max.max(8);
|
||||||
|
}
|
||||||
|
if shapes
|
||||||
|
.iter()
|
||||||
|
.any(|s| matches!(s, NormalizedDevShape::JsonparserAtoiReal))
|
||||||
|
{
|
||||||
|
max = max.max(10);
|
||||||
|
}
|
||||||
|
if shapes
|
||||||
|
.iter()
|
||||||
|
.any(|s| matches!(s, NormalizedDevShape::JsonparserParseNumberReal))
|
||||||
|
{
|
||||||
|
max = max.max(12);
|
||||||
|
}
|
||||||
|
if shapes
|
||||||
|
.iter()
|
||||||
|
.any(|s| matches!(s, NormalizedDevShape::JsonparserSkipWsReal))
|
||||||
|
{
|
||||||
|
max = max.max(6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
max
|
max
|
||||||
@ -397,6 +427,18 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
|
|||||||
args: vec![*lhs, *rhs],
|
args: vec![*lhs, *rhs],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
JoinInst::Compute(MirLikeInst::Select {
|
||||||
|
dst,
|
||||||
|
cond,
|
||||||
|
then_val,
|
||||||
|
else_val,
|
||||||
|
}) => {
|
||||||
|
body.push(JpInst::Let {
|
||||||
|
dst: *dst,
|
||||||
|
op: JpOp::Select,
|
||||||
|
args: vec![*cond, *then_val, *else_val],
|
||||||
|
})
|
||||||
|
}
|
||||||
JoinInst::Jump { cont, args, cond } => {
|
JoinInst::Jump { cont, args, cond } => {
|
||||||
if let Some(cond_val) = cond {
|
if let Some(cond_val) = cond {
|
||||||
body.push(JpInst::If {
|
body.push(JpInst::If {
|
||||||
@ -412,6 +454,19 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
JoinInst::Select {
|
||||||
|
dst,
|
||||||
|
cond,
|
||||||
|
then_val,
|
||||||
|
else_val,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
body.push(JpInst::Let {
|
||||||
|
dst: *dst,
|
||||||
|
op: JpOp::Select,
|
||||||
|
args: vec![*cond, *then_val, *else_val],
|
||||||
|
});
|
||||||
|
}
|
||||||
JoinInst::Call { func, args, k_next, .. } => {
|
JoinInst::Call { func, args, k_next, .. } => {
|
||||||
if k_next.is_none() {
|
if k_next.is_none() {
|
||||||
body.push(JpInst::TailCallFn {
|
body.push(JpInst::TailCallFn {
|
||||||
@ -523,6 +578,14 @@ pub fn normalized_pattern2_to_structured(norm: &NormalizedModule) -> JoinModule
|
|||||||
op: *op,
|
op: *op,
|
||||||
operand: args.get(0).copied().unwrap_or(ValueId(0)),
|
operand: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||||
})),
|
})),
|
||||||
|
JpOp::Select => func
|
||||||
|
.body
|
||||||
|
.push(JoinInst::Compute(MirLikeInst::Select {
|
||||||
|
dst: *dst,
|
||||||
|
cond: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||||
|
then_val: args.get(1).copied().unwrap_or(ValueId(0)),
|
||||||
|
else_val: args.get(2).copied().unwrap_or(ValueId(0)),
|
||||||
|
})),
|
||||||
JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||||
dst: *dst,
|
dst: *dst,
|
||||||
op: *op,
|
op: *op,
|
||||||
@ -578,7 +641,9 @@ fn verify_normalized_pattern2(
|
|||||||
max_env_fields: usize,
|
max_env_fields: usize,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if module.phase != JoinIrPhase::Normalized {
|
if module.phase != JoinIrPhase::Normalized {
|
||||||
return Err("Normalized verifier (Pattern2): phase must be Normalized".to_string());
|
return Err(
|
||||||
|
"[joinir/normalized-dev] pattern2: phase must be Normalized".to_string(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut layout_sizes: HashMap<u32, usize> = HashMap::new();
|
let mut layout_sizes: HashMap<u32, usize> = HashMap::new();
|
||||||
@ -586,7 +651,7 @@ fn verify_normalized_pattern2(
|
|||||||
let size = layout.fields.len();
|
let size = layout.fields.len();
|
||||||
if !(1..=max_env_fields).contains(&size) {
|
if !(1..=max_env_fields).contains(&size) {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Normalized Pattern2 expects 1..={} env fields, got {}",
|
"[joinir/normalized-dev] pattern2: expected 1..={} env fields, got {}",
|
||||||
max_env_fields, size
|
max_env_fields, size
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -615,7 +680,10 @@ fn verify_normalized_pattern2(
|
|||||||
| JpInst::If { env, .. } => {
|
| JpInst::If { env, .. } => {
|
||||||
if let Some(expected) = expected_env_len {
|
if let Some(expected) = expected_env_len {
|
||||||
if env.is_empty() {
|
if env.is_empty() {
|
||||||
return Err("Normalized Pattern2 env must not be empty".to_string());
|
return Err(
|
||||||
|
"[joinir/normalized-dev] pattern2: env must not be empty"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let _ = expected;
|
let _ = expected;
|
||||||
}
|
}
|
||||||
@ -631,7 +699,7 @@ fn verify_normalized_pattern2(
|
|||||||
| JpInst::If { .. } => {}
|
| JpInst::If { .. } => {}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Function '{}' does not end with tail call/if",
|
"[joinir/normalized-dev] pattern2: function '{}' does not end with tail call/if",
|
||||||
func.name
|
func.name
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -813,11 +881,14 @@ pub(crate) fn normalized_dev_roundtrip_structured(
|
|||||||
return Err("[joinir/normalized-dev] module shape is not supported by normalized_dev".into());
|
return Err("[joinir/normalized-dev] module shape is not supported by normalized_dev".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let verbose = crate::config::env::joinir_dev_enabled();
|
let debug = dev_env::normalized_dev_logs_enabled() && crate::config::env::joinir_dev_enabled();
|
||||||
|
|
||||||
for shape in shapes {
|
for shape in shapes {
|
||||||
if verbose {
|
if debug {
|
||||||
eprintln!("[joinir/normalized-dev] attempting {:?} normalization", shape);
|
eprintln!(
|
||||||
|
"[joinir/normalized-dev/roundtrip] attempting {:?} normalization",
|
||||||
|
shape
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let attempt = match shape {
|
let attempt = match shape {
|
||||||
@ -827,7 +898,10 @@ pub(crate) fn normalized_dev_roundtrip_structured(
|
|||||||
})),
|
})),
|
||||||
NormalizedDevShape::Pattern2Mini
|
NormalizedDevShape::Pattern2Mini
|
||||||
| NormalizedDevShape::JsonparserSkipWsMini
|
| NormalizedDevShape::JsonparserSkipWsMini
|
||||||
| NormalizedDevShape::JsonparserAtoiMini => catch_unwind(AssertUnwindSafe(|| {
|
| NormalizedDevShape::JsonparserSkipWsReal
|
||||||
|
| NormalizedDevShape::JsonparserAtoiMini
|
||||||
|
| NormalizedDevShape::JsonparserAtoiReal
|
||||||
|
| NormalizedDevShape::JsonparserParseNumberReal => catch_unwind(AssertUnwindSafe(|| {
|
||||||
let norm = normalize_pattern2_minimal(module);
|
let norm = normalize_pattern2_minimal(module);
|
||||||
normalized_pattern2_to_structured(&norm)
|
normalized_pattern2_to_structured(&norm)
|
||||||
})),
|
})),
|
||||||
@ -835,9 +909,9 @@ pub(crate) fn normalized_dev_roundtrip_structured(
|
|||||||
|
|
||||||
match attempt {
|
match attempt {
|
||||||
Ok(structured) => {
|
Ok(structured) => {
|
||||||
if verbose {
|
if debug {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[joinir/normalized-dev] {:?} normalization succeeded (functions={})",
|
"[joinir/normalized-dev/roundtrip] {:?} normalization succeeded (functions={})",
|
||||||
shape,
|
shape,
|
||||||
structured.functions.len()
|
structured.functions.len()
|
||||||
);
|
);
|
||||||
@ -845,9 +919,9 @@ pub(crate) fn normalized_dev_roundtrip_structured(
|
|||||||
return Ok(structured);
|
return Ok(structured);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
if verbose {
|
if debug {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[joinir/normalized-dev] {:?} normalization failed (unsupported)",
|
"[joinir/normalized-dev/roundtrip] {:?} normalization failed (unsupported)",
|
||||||
shape
|
shape
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,38 +1,111 @@
|
|||||||
#![cfg(feature = "normalized_dev")]
|
#![cfg(feature = "normalized_dev")]
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::sync::Mutex;
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
/// RAII guard for normalized_dev env toggling (NYASH_JOINIR_NORMALIZED_DEV_RUN).
|
/// RAII guard for normalized_dev env toggling (NYASH_JOINIR_NORMALIZED_DEV_RUN).
|
||||||
/// 汚染防止のため tests/runner の両方で再利用できるようにここに置く。
|
/// ネストを許可し、最初の呼び出し時の状態だけを保存・復元する。
|
||||||
pub struct NormalizedDevEnvGuard {
|
pub struct NormalizedDevEnvGuard {
|
||||||
_lock: std::sync::MutexGuard<'static, ()>,
|
active: bool,
|
||||||
prev: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static NORMALIZED_ENV_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
|
#[derive(Default)]
|
||||||
|
struct EnvState {
|
||||||
|
stack: Vec<Option<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static NORMALIZED_ENV_STATE: Lazy<Mutex<EnvState>> = Lazy::new(|| Mutex::new(EnvState::default()));
|
||||||
|
static NORMALIZED_TEST_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
|
||||||
|
|
||||||
impl NormalizedDevEnvGuard {
|
impl NormalizedDevEnvGuard {
|
||||||
pub fn new(enabled: bool) -> Self {
|
pub fn new(enabled: bool) -> Self {
|
||||||
let lock = NORMALIZED_ENV_LOCK
|
let mut state = NORMALIZED_ENV_STATE
|
||||||
.lock()
|
.lock()
|
||||||
.expect("normalized env mutex poisoned");
|
.expect("normalized env mutex poisoned");
|
||||||
|
|
||||||
|
// Save current value before overriding.
|
||||||
let prev = std::env::var("NYASH_JOINIR_NORMALIZED_DEV_RUN").ok();
|
let prev = std::env::var("NYASH_JOINIR_NORMALIZED_DEV_RUN").ok();
|
||||||
|
state.stack.push(prev);
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", "1");
|
std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", "1");
|
||||||
} else {
|
} else {
|
||||||
std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN");
|
std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN");
|
||||||
}
|
}
|
||||||
Self { _lock: lock, prev }
|
|
||||||
|
Self { active: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for NormalizedDevEnvGuard {
|
impl Drop for NormalizedDevEnvGuard {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(prev) = &self.prev {
|
if !self.active {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut state = NORMALIZED_ENV_STATE
|
||||||
|
.lock()
|
||||||
|
.expect("normalized env mutex poisoned");
|
||||||
|
if let Some(prev) = state.stack.pop() {
|
||||||
|
if let Some(prev) = prev {
|
||||||
std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", prev);
|
std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", prev);
|
||||||
} else {
|
} else {
|
||||||
std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN");
|
std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// normalized_dev feature + env の ON/OFF をまとめた判定。
|
||||||
|
pub fn normalized_dev_enabled() -> bool {
|
||||||
|
crate::config::env::normalized_dev_enabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// normalized_dev かつ test/debug ログが有効なときだけ true。
|
||||||
|
pub fn normalized_dev_logs_enabled() -> bool {
|
||||||
|
crate::config::env::normalized_dev_enabled() && crate::config::env::joinir_test_debug_enabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// テスト用コンテキスト:env を ON にしつつロックで並列汚染を防ぐ。
|
||||||
|
pub struct NormalizedTestContext<'a> {
|
||||||
|
_lock: MutexGuard<'a, ()>,
|
||||||
|
_env_guard: NormalizedDevEnvGuard,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NormalizedTestContext<'a> {
|
||||||
|
fn new(lock: MutexGuard<'a, ()>) -> Self {
|
||||||
|
let env_guard = NormalizedDevEnvGuard::new(true);
|
||||||
|
NormalizedTestContext {
|
||||||
|
_lock: lock,
|
||||||
|
_env_guard: env_guard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// テストで使う共通ガード。
|
||||||
|
pub fn test_ctx() -> NormalizedTestContext<'static> {
|
||||||
|
let lock = NORMALIZED_TEST_LOCK
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|e| e.into_inner());
|
||||||
|
NormalizedTestContext::new(lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 簡易ラッパー:クロージャを normalized_dev ON で実行。
|
||||||
|
pub fn with_dev_env<F, R>(f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R,
|
||||||
|
{
|
||||||
|
let _ctx = test_ctx();
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// env が既に ON のときはそのまま、OFF のときだけ with_dev_env を噛ませる。
|
||||||
|
pub fn with_dev_env_if_unset<F, R>(f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R,
|
||||||
|
{
|
||||||
|
if normalized_dev_enabled() {
|
||||||
|
f()
|
||||||
|
} else {
|
||||||
|
with_dev_env(f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
|
|||||||
use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal;
|
use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal;
|
||||||
use crate::mir::join_ir::JoinModule;
|
use crate::mir::join_ir::JoinModule;
|
||||||
use crate::mir::{BasicBlockId, ValueId};
|
use crate::mir::{BasicBlockId, ValueId};
|
||||||
|
use crate::{config::env::joinir_dev_enabled, config::env::joinir_test_debug_enabled};
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
/// Structured Pattern2 (joinir_min_loop 相当) をテスト用に生成するヘルパー。
|
/// Structured Pattern2 (joinir_min_loop 相当) をテスト用に生成するヘルパー。
|
||||||
@ -113,6 +114,54 @@ pub fn build_jsonparser_skip_ws_structured_for_normalized_dev() -> JoinModule {
|
|||||||
lowerer.lower_program_json(&program_json)
|
lowerer.lower_program_json(&program_json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// JsonParser _skip_whitespace 本体相当の P2 ループを Structured で組み立てるヘルパー。
|
||||||
|
///
|
||||||
|
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_real.program.json
|
||||||
|
pub fn build_jsonparser_skip_ws_real_structured_for_normalized_dev() -> JoinModule {
|
||||||
|
const FIXTURE: &str = include_str!(
|
||||||
|
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_skip_ws_real.program.json"
|
||||||
|
);
|
||||||
|
|
||||||
|
let program_json: serde_json::Value =
|
||||||
|
serde_json::from_str(FIXTURE).expect("jsonparser skip_ws real fixture should be valid JSON");
|
||||||
|
|
||||||
|
let mut lowerer = AstToJoinIrLowerer::new();
|
||||||
|
let module = lowerer.lower_program_json(&program_json);
|
||||||
|
|
||||||
|
if joinir_dev_enabled() && joinir_test_debug_enabled() {
|
||||||
|
eprintln!(
|
||||||
|
"[joinir/normalized-dev] jsonparser_skip_ws_real structured module: {:#?}",
|
||||||
|
module
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JsonParser _parse_number 本体相当の P2 ループを Structured で組み立てるヘルパー。
|
||||||
|
///
|
||||||
|
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_parse_number_real.program.json
|
||||||
|
pub fn build_jsonparser_parse_number_real_structured_for_normalized_dev() -> JoinModule {
|
||||||
|
const FIXTURE: &str = include_str!(
|
||||||
|
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_parse_number_real.program.json"
|
||||||
|
);
|
||||||
|
|
||||||
|
let program_json: serde_json::Value =
|
||||||
|
serde_json::from_str(FIXTURE).expect("jsonparser parse_number real fixture should be valid JSON");
|
||||||
|
|
||||||
|
let mut lowerer = AstToJoinIrLowerer::new();
|
||||||
|
let module = lowerer.lower_program_json(&program_json);
|
||||||
|
|
||||||
|
if joinir_dev_enabled() && joinir_test_debug_enabled() {
|
||||||
|
eprintln!(
|
||||||
|
"[joinir/normalized-dev] jsonparser_parse_number_real structured module: {:#?}",
|
||||||
|
module
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module
|
||||||
|
}
|
||||||
|
|
||||||
/// JsonParser _atoi 相当のミニ P2 ループを Structured で組み立てるヘルパー。
|
/// JsonParser _atoi 相当のミニ P2 ループを Structured で組み立てるヘルパー。
|
||||||
///
|
///
|
||||||
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_mini.program.json
|
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_mini.program.json
|
||||||
@ -127,7 +176,7 @@ pub fn build_jsonparser_atoi_structured_for_normalized_dev() -> JoinModule {
|
|||||||
let mut lowerer = AstToJoinIrLowerer::new();
|
let mut lowerer = AstToJoinIrLowerer::new();
|
||||||
let module = lowerer.lower_program_json(&program_json);
|
let module = lowerer.lower_program_json(&program_json);
|
||||||
|
|
||||||
if std::env::var("JOINIR_TEST_DEBUG").is_ok() {
|
if joinir_dev_enabled() && joinir_test_debug_enabled() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[joinir/normalized-dev] jsonparser_atoi_mini structured module: {:#?}",
|
"[joinir/normalized-dev] jsonparser_atoi_mini structured module: {:#?}",
|
||||||
module
|
module
|
||||||
@ -136,3 +185,39 @@ pub fn build_jsonparser_atoi_structured_for_normalized_dev() -> JoinModule {
|
|||||||
|
|
||||||
module
|
module
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// JsonParser _atoi 本体相当の P2 ループを Structured で組み立てるヘルパー。
|
||||||
|
///
|
||||||
|
/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_real.program.json
|
||||||
|
pub fn build_jsonparser_atoi_real_structured_for_normalized_dev() -> JoinModule {
|
||||||
|
const FIXTURE: &str = include_str!(
|
||||||
|
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_real.program.json"
|
||||||
|
);
|
||||||
|
|
||||||
|
let program_json: serde_json::Value =
|
||||||
|
serde_json::from_str(FIXTURE).expect("jsonparser atoi real fixture should be valid JSON");
|
||||||
|
|
||||||
|
let mut lowerer = AstToJoinIrLowerer::new();
|
||||||
|
let module = lowerer.lower_program_json(&program_json);
|
||||||
|
|
||||||
|
if joinir_dev_enabled() && joinir_test_debug_enabled() {
|
||||||
|
eprintln!(
|
||||||
|
"[joinir/normalized-dev] jsonparser_atoi_real structured module: {:#?}",
|
||||||
|
module
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module
|
||||||
|
}
|
||||||
|
|
||||||
|
/// まとめて import したいとき用のプレリュード。
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::{
|
||||||
|
build_jsonparser_atoi_real_structured_for_normalized_dev,
|
||||||
|
build_jsonparser_atoi_structured_for_normalized_dev,
|
||||||
|
build_jsonparser_parse_number_real_structured_for_normalized_dev,
|
||||||
|
build_jsonparser_skip_ws_real_structured_for_normalized_dev,
|
||||||
|
build_jsonparser_skip_ws_structured_for_normalized_dev,
|
||||||
|
build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#![cfg(feature = "normalized_dev")]
|
#![cfg(feature = "normalized_dev")]
|
||||||
|
|
||||||
|
use crate::config::env::joinir_dev_enabled;
|
||||||
|
use crate::mir::join_ir::normalized::dev_env;
|
||||||
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst, JoinModule};
|
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst, JoinModule};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@ -7,41 +9,100 @@ pub(crate) enum NormalizedDevShape {
|
|||||||
Pattern1Mini,
|
Pattern1Mini,
|
||||||
Pattern2Mini,
|
Pattern2Mini,
|
||||||
JsonparserSkipWsMini,
|
JsonparserSkipWsMini,
|
||||||
|
JsonparserSkipWsReal,
|
||||||
JsonparserAtoiMini,
|
JsonparserAtoiMini,
|
||||||
|
JsonparserAtoiReal,
|
||||||
|
JsonparserParseNumberReal,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 直接 Normalized→MIR ブリッジで扱う shape を返す(dev 限定)。
|
type Detector = fn(&JoinModule) -> bool;
|
||||||
|
|
||||||
|
const SHAPE_DETECTORS: &[(NormalizedDevShape, Detector)] = &[
|
||||||
|
(NormalizedDevShape::Pattern1Mini, detectors::is_pattern1_mini),
|
||||||
|
(NormalizedDevShape::Pattern2Mini, detectors::is_pattern2_mini),
|
||||||
|
(
|
||||||
|
NormalizedDevShape::JsonparserSkipWsMini,
|
||||||
|
detectors::is_jsonparser_skip_ws_mini,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NormalizedDevShape::JsonparserSkipWsReal,
|
||||||
|
detectors::is_jsonparser_skip_ws_real,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NormalizedDevShape::JsonparserAtoiMini,
|
||||||
|
detectors::is_jsonparser_atoi_mini,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NormalizedDevShape::JsonparserAtoiReal,
|
||||||
|
detectors::is_jsonparser_atoi_real,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NormalizedDevShape::JsonparserParseNumberReal,
|
||||||
|
detectors::is_jsonparser_parse_number_real,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// direct ブリッジで扱う shape(dev 限定)。
|
||||||
pub(crate) fn direct_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
pub(crate) fn direct_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
||||||
supported_shapes(module)
|
let shapes = detect_shapes(module);
|
||||||
|
log_shapes("direct", &shapes);
|
||||||
|
shapes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Structured→Normalized の対象 shape(dev 限定)。
|
||||||
pub(crate) fn supported_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
pub(crate) fn supported_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
||||||
let mut shapes = Vec::new();
|
let shapes = detect_shapes(module);
|
||||||
if is_jsonparser_atoi_mini(module) {
|
log_shapes("roundtrip", &shapes);
|
||||||
shapes.push(NormalizedDevShape::JsonparserAtoiMini);
|
shapes
|
||||||
}
|
}
|
||||||
if is_jsonparser_skip_ws_mini(module) {
|
|
||||||
shapes.push(NormalizedDevShape::JsonparserSkipWsMini);
|
/// canonical(常時 Normalized 経路を通す)対象。
|
||||||
}
|
/// Phase 41: P2 コアセット(P2 mini + JP skip_ws mini/real + JP atoi mini)。
|
||||||
if is_pattern2_mini(module) {
|
pub(crate) fn canonical_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
||||||
shapes.push(NormalizedDevShape::Pattern2Mini);
|
let shapes: Vec<_> = detect_shapes(module)
|
||||||
}
|
.into_iter()
|
||||||
if is_pattern1_mini(module) {
|
.filter(|s| {
|
||||||
shapes.push(NormalizedDevShape::Pattern1Mini);
|
matches!(
|
||||||
}
|
s,
|
||||||
|
NormalizedDevShape::Pattern2Mini
|
||||||
|
| NormalizedDevShape::JsonparserSkipWsMini
|
||||||
|
| NormalizedDevShape::JsonparserSkipWsReal
|
||||||
|
| NormalizedDevShape::JsonparserAtoiMini
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
log_shapes("canonical", &shapes);
|
||||||
shapes
|
shapes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn is_direct_supported(module: &JoinModule) -> bool {
|
pub(crate) fn is_direct_supported(module: &JoinModule) -> bool {
|
||||||
!direct_shapes(module).is_empty()
|
!detect_shapes(module).is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_pattern1_mini(module: &JoinModule) -> bool {
|
fn detect_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
||||||
|
let mut shapes: Vec<_> = SHAPE_DETECTORS
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(shape, detector)| if detector(module) { Some(*shape) } else { None })
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Pattern1 は「最小の後方互換」なので、より具体的な shape が見つかった場合は外しておく。
|
||||||
|
if shapes.len() > 1 {
|
||||||
|
shapes.retain(|s| *s != NormalizedDevShape::Pattern1Mini);
|
||||||
|
}
|
||||||
|
|
||||||
|
shapes
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 判定ロジック(共通) ---
|
||||||
|
mod detectors {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn is_pattern1_mini(module: &JoinModule) -> bool {
|
||||||
module.is_structured() && find_loop_step(module).is_some()
|
module.is_structured() && find_loop_step(module).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_pattern2_mini(module: &JoinModule) -> bool {
|
pub(super) fn is_pattern2_mini(module: &JoinModule) -> bool {
|
||||||
if !module.is_structured() || module.functions.len() != 3 {
|
if !module.is_structured() || module.functions.len() != 3 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -63,17 +124,46 @@ pub(crate) fn is_pattern2_mini(module: &JoinModule) -> bool {
|
|||||||
.any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }));
|
.any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }));
|
||||||
|
|
||||||
has_cond_jump && has_tail_call
|
has_cond_jump && has_tail_call
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_jsonparser_skip_ws_mini(module: &JoinModule) -> bool {
|
pub(super) fn is_jsonparser_skip_ws_mini(module: &JoinModule) -> bool {
|
||||||
is_pattern2_mini(module)
|
is_pattern2_mini(module)
|
||||||
&& module
|
&& module
|
||||||
.functions
|
.functions
|
||||||
.values()
|
.values()
|
||||||
.any(|f| f.name == "jsonparser_skip_ws_mini")
|
.any(|f| f.name == "jsonparser_skip_ws_mini")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_jsonparser_atoi_mini(module: &JoinModule) -> bool {
|
pub(crate) fn is_jsonparser_skip_ws_real(module: &JoinModule) -> bool {
|
||||||
|
if !module.is_structured() || module.functions.len() != 3 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let loop_func = match find_loop_step(module) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
if !(2..=6).contains(&loop_func.params.len()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_cond_jump = loop_func
|
||||||
|
.body
|
||||||
|
.iter()
|
||||||
|
.any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. }));
|
||||||
|
let has_tail_call = loop_func
|
||||||
|
.body
|
||||||
|
.iter()
|
||||||
|
.any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }));
|
||||||
|
|
||||||
|
has_cond_jump
|
||||||
|
&& has_tail_call
|
||||||
|
&& module
|
||||||
|
.functions
|
||||||
|
.values()
|
||||||
|
.any(|f| f.name == "jsonparser_skip_ws_real")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_jsonparser_atoi_mini(module: &JoinModule) -> bool {
|
||||||
if !module.is_structured() || module.functions.len() != 3 {
|
if !module.is_structured() || module.functions.len() != 3 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -99,13 +189,81 @@ pub(crate) fn is_jsonparser_atoi_mini(module: &JoinModule) -> bool {
|
|||||||
&& module
|
&& module
|
||||||
.functions
|
.functions
|
||||||
.values()
|
.values()
|
||||||
.any(|f| f.name.contains("atoi"))
|
.any(|f| f.name == "jsonparser_atoi_mini")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_loop_step(module: &JoinModule) -> Option<&JoinFunction> {
|
pub(crate) fn is_jsonparser_atoi_real(module: &JoinModule) -> bool {
|
||||||
|
if !module.is_structured() || module.functions.len() != 3 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let loop_func = match find_loop_step(module) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
if !(3..=10).contains(&loop_func.params.len()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_cond_jump = loop_func
|
||||||
|
.body
|
||||||
|
.iter()
|
||||||
|
.any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. }));
|
||||||
|
let has_tail_call = loop_func
|
||||||
|
.body
|
||||||
|
.iter()
|
||||||
|
.any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }));
|
||||||
|
|
||||||
|
has_cond_jump
|
||||||
|
&& has_tail_call
|
||||||
|
&& module
|
||||||
|
.functions
|
||||||
|
.values()
|
||||||
|
.any(|f| f.name == "jsonparser_atoi_real")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_jsonparser_parse_number_real(module: &JoinModule) -> bool {
|
||||||
|
if !module.is_structured() || module.functions.len() != 3 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let loop_func = match find_loop_step(module) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
if !(3..=12).contains(&loop_func.params.len()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_cond_jump = loop_func
|
||||||
|
.body
|
||||||
|
.iter()
|
||||||
|
.any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. }));
|
||||||
|
let has_tail_call = loop_func
|
||||||
|
.body
|
||||||
|
.iter()
|
||||||
|
.any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }));
|
||||||
|
|
||||||
|
has_cond_jump
|
||||||
|
&& has_tail_call
|
||||||
|
&& module
|
||||||
|
.functions
|
||||||
|
.values()
|
||||||
|
.any(|f| f.name == "jsonparser_parse_number_real")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn find_loop_step(module: &JoinModule) -> Option<&JoinFunction> {
|
||||||
module
|
module
|
||||||
.functions
|
.functions
|
||||||
.values()
|
.values()
|
||||||
.find(|f| f.name == "loop_step")
|
.find(|f| f.name == "loop_step")
|
||||||
.or_else(|| module.functions.get(&JoinFuncId::new(1)))
|
.or_else(|| module.functions.get(&JoinFuncId::new(1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_shapes(tag: &str, shapes: &[NormalizedDevShape]) {
|
||||||
|
if shapes.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if dev_env::normalized_dev_logs_enabled() && joinir_dev_enabled() {
|
||||||
|
eprintln!("[joinir/normalized-dev/shape] {}: {:?}", tag, shapes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,9 @@ use std::collections::HashMap;
|
|||||||
use crate::config::env::normalized_dev_enabled;
|
use crate::config::env::normalized_dev_enabled;
|
||||||
use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId};
|
use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId};
|
||||||
#[cfg(feature = "normalized_dev")]
|
#[cfg(feature = "normalized_dev")]
|
||||||
use crate::mir::join_ir::normalized::{normalized_dev_roundtrip_structured, shape_guard};
|
use crate::mir::join_ir::normalized::{
|
||||||
|
dev_env, normalized_dev_roundtrip_structured, shape_guard,
|
||||||
|
};
|
||||||
|
|
||||||
// Phase 27.8: ops box からの再エクスポート
|
// Phase 27.8: ops box からの再エクスポート
|
||||||
pub use crate::mir::join_ir_ops::{JoinIrOpError, JoinValue};
|
pub use crate::mir::join_ir_ops::{JoinIrOpError, JoinValue};
|
||||||
@ -64,31 +66,32 @@ fn run_joinir_function_normalized_dev(
|
|||||||
args: &[JoinValue],
|
args: &[JoinValue],
|
||||||
) -> Result<JoinValue, JoinRuntimeError> {
|
) -> Result<JoinValue, JoinRuntimeError> {
|
||||||
// Keep dev path opt-in and fail-fast: only Structured P1/P2 minis are supported.
|
// Keep dev path opt-in and fail-fast: only Structured P1/P2 minis are supported.
|
||||||
let verbose = crate::config::env::joinir_dev_enabled();
|
dev_env::with_dev_env_if_unset(|| {
|
||||||
|
let debug = dev_env::normalized_dev_logs_enabled();
|
||||||
let args_vec = args.to_vec();
|
let args_vec = args.to_vec();
|
||||||
|
|
||||||
let shapes = shape_guard::supported_shapes(module);
|
let shapes = shape_guard::supported_shapes(module);
|
||||||
if shapes.is_empty() {
|
if shapes.is_empty() {
|
||||||
if verbose {
|
if debug {
|
||||||
eprintln!(
|
eprintln!("[joinir/normalized-dev/runner] shape unsupported; staying on Structured path");
|
||||||
"[joinir/runner/normalized-dev] shape unsupported; staying on Structured path"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return execute_function(vm, module, entry, args_vec);
|
return execute_function(vm, module, entry, args_vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
let structured_roundtrip = normalized_dev_roundtrip_structured(module)
|
let structured_roundtrip = normalized_dev_roundtrip_structured(module).map_err(|msg| {
|
||||||
.map_err(|msg| JoinRuntimeError::new(format!("[joinir/runner/normalized-dev] {}", msg)))?;
|
JoinRuntimeError::new(format!("[joinir/normalized-dev/runner] {}", msg))
|
||||||
|
})?;
|
||||||
|
|
||||||
if verbose {
|
if debug {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[joinir/runner/normalized-dev] normalized roundtrip succeeded (shapes={:?}, functions={})",
|
"[joinir/normalized-dev/runner] normalized roundtrip succeeded (shapes={:?}, functions={})",
|
||||||
shapes,
|
shapes,
|
||||||
structured_roundtrip.functions.len()
|
structured_roundtrip.functions.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
execute_function(vm, &structured_roundtrip, entry, args_vec)
|
execute_function(vm, &structured_roundtrip, entry, args_vec)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_function(
|
fn execute_function(
|
||||||
|
|||||||
@ -62,7 +62,10 @@ fn normalize_for_shape(
|
|||||||
}
|
}
|
||||||
NormalizedDevShape::Pattern2Mini
|
NormalizedDevShape::Pattern2Mini
|
||||||
| NormalizedDevShape::JsonparserSkipWsMini
|
| NormalizedDevShape::JsonparserSkipWsMini
|
||||||
| NormalizedDevShape::JsonparserAtoiMini => {
|
| NormalizedDevShape::JsonparserSkipWsReal
|
||||||
|
| NormalizedDevShape::JsonparserAtoiMini
|
||||||
|
| NormalizedDevShape::JsonparserAtoiReal
|
||||||
|
| NormalizedDevShape::JsonparserParseNumberReal => {
|
||||||
catch_unwind(AssertUnwindSafe(|| normalize_pattern2_minimal(module)))
|
catch_unwind(AssertUnwindSafe(|| normalize_pattern2_minimal(module)))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -80,44 +83,79 @@ fn normalize_for_shape(
|
|||||||
fn try_normalized_direct_bridge(
|
fn try_normalized_direct_bridge(
|
||||||
module: &JoinModule,
|
module: &JoinModule,
|
||||||
meta: &JoinFuncMetaMap,
|
meta: &JoinFuncMetaMap,
|
||||||
|
shapes: &[NormalizedDevShape],
|
||||||
|
allow_structured_fallback: bool,
|
||||||
|
use_env_guard: bool,
|
||||||
) -> Result<Option<MirModule>, JoinIrVmBridgeError> {
|
) -> Result<Option<MirModule>, JoinIrVmBridgeError> {
|
||||||
let shapes = shape_guard::direct_shapes(module);
|
|
||||||
if shapes.is_empty() {
|
if shapes.is_empty() {
|
||||||
return Ok(None);
|
crate::mir::join_ir_vm_bridge::normalized_bridge::log_dev(
|
||||||
|
"fallback",
|
||||||
|
"normalized dev enabled but shape unsupported; using Structured path",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return if allow_structured_fallback {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Err(JoinIrVmBridgeError::new(
|
||||||
|
"[joinir/bridge] canonical normalized route requested but shape unsupported",
|
||||||
|
))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let verbose = crate::config::env::joinir_dev_enabled();
|
let exec = || {
|
||||||
|
let debug = crate::mir::join_ir::normalized::dev_env::normalized_dev_logs_enabled();
|
||||||
for shape in shapes {
|
for &shape in shapes {
|
||||||
if verbose {
|
if debug {
|
||||||
eprintln!("[joinir/bridge] attempting normalized→MIR for {:?}", shape);
|
crate::mir::join_ir_vm_bridge::normalized_bridge::log_dev(
|
||||||
|
"direct",
|
||||||
|
format!("attempting normalized→MIR for {:?}", shape),
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
match normalize_for_shape(module, shape) {
|
match normalize_for_shape(module, shape) {
|
||||||
Ok(norm) => {
|
Ok(norm) => {
|
||||||
let mir = lower_normalized_to_mir_minimal(&norm, meta)?;
|
let mir =
|
||||||
if verbose {
|
lower_normalized_to_mir_minimal(&norm, meta, allow_structured_fallback)?;
|
||||||
eprintln!(
|
crate::mir::join_ir_vm_bridge::normalized_bridge::log_dev(
|
||||||
"[joinir/bridge] normalized→MIR succeeded (shape={:?}, functions={})",
|
"direct",
|
||||||
|
format!(
|
||||||
|
"normalized→MIR succeeded (shape={:?}, functions={})",
|
||||||
shape,
|
shape,
|
||||||
norm.functions.len()
|
norm.functions.len()
|
||||||
|
),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return Ok(Some(mir));
|
return Ok(Some(mir));
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if verbose {
|
if debug {
|
||||||
eprintln!(
|
crate::mir::join_ir_vm_bridge::normalized_bridge::log_dev(
|
||||||
"[joinir/bridge] {:?} normalization failed: {} (continuing)",
|
"direct",
|
||||||
|
format!(
|
||||||
|
"{:?} normalization failed: {} (continuing)",
|
||||||
shape, err.message
|
shape, err.message
|
||||||
|
),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if allow_structured_fallback {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
Err(JoinIrVmBridgeError::new(
|
Err(JoinIrVmBridgeError::new(
|
||||||
"[joinir/bridge] normalized_dev enabled but no normalization attempt succeeded",
|
"[joinir/bridge] canonical normalized route failed for all shapes",
|
||||||
))
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if use_env_guard {
|
||||||
|
crate::mir::join_ir::normalized::dev_env::with_dev_env_if_unset(exec)
|
||||||
|
} else {
|
||||||
|
exec()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JoinIR → MIR の単一入口。Normalized dev が有効なら roundtrip してから既存経路へ。
|
/// JoinIR → MIR の単一入口。Normalized dev が有効なら roundtrip してから既存経路へ。
|
||||||
@ -125,18 +163,28 @@ pub(crate) fn bridge_joinir_to_mir_with_meta(
|
|||||||
module: &JoinModule,
|
module: &JoinModule,
|
||||||
meta: &JoinFuncMetaMap,
|
meta: &JoinFuncMetaMap,
|
||||||
) -> Result<MirModule, JoinIrVmBridgeError> {
|
) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||||
if crate::config::env::normalized_dev_enabled() {
|
|
||||||
#[cfg(feature = "normalized_dev")]
|
#[cfg(feature = "normalized_dev")]
|
||||||
{
|
{
|
||||||
match try_normalized_direct_bridge(module, meta)? {
|
// Phase 41: canonical shapes は env が OFF でも常に Normalized → MIR を通す。
|
||||||
|
let canonical_shapes = shape_guard::canonical_shapes(module);
|
||||||
|
if !canonical_shapes.is_empty() {
|
||||||
|
match try_normalized_direct_bridge(module, meta, &canonical_shapes, false, false)? {
|
||||||
Some(mir) => return Ok(mir),
|
Some(mir) => return Ok(mir),
|
||||||
None => {
|
None => {
|
||||||
debug_log!(
|
return Err(JoinIrVmBridgeError::new(
|
||||||
"[joinir/bridge] normalized dev enabled but shape unsupported; falling back to Structured path"
|
"[joinir/bridge] canonical normalized route returned None unexpectedly",
|
||||||
);
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if crate::config::env::normalized_dev_enabled() {
|
||||||
|
let shapes = shape_guard::direct_shapes(module);
|
||||||
|
match try_normalized_direct_bridge(module, meta, &shapes, true, true)? {
|
||||||
|
Some(mir) => return Ok(mir),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lower_joinir_structured_to_mir_with_meta(module, meta)
|
lower_joinir_structured_to_mir_with_meta(module, meta)
|
||||||
|
|||||||
@ -11,6 +11,22 @@ use crate::mir::MirModule;
|
|||||||
|
|
||||||
mod direct;
|
mod direct;
|
||||||
|
|
||||||
|
fn dev_debug_enabled() -> bool {
|
||||||
|
crate::mir::join_ir::normalized::dev_env::normalized_dev_logs_enabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dev logging helper with unified category prefix.
|
||||||
|
pub(super) fn log_dev(category: &str, message: impl AsRef<str>, important: bool) {
|
||||||
|
let debug = dev_debug_enabled();
|
||||||
|
if debug || important {
|
||||||
|
eprintln!("[joinir/normalized-dev/bridge/{}] {}", category, message.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn log_debug(category: &str, message: impl AsRef<str>) {
|
||||||
|
log_dev(category, message, false);
|
||||||
|
}
|
||||||
|
|
||||||
/// Direct Normalized → MIR 変換が未対応のときに使うフォールバック。
|
/// Direct Normalized → MIR 変換が未対応のときに使うフォールバック。
|
||||||
fn lower_normalized_via_structured(
|
fn lower_normalized_via_structured(
|
||||||
norm: &NormalizedModule,
|
norm: &NormalizedModule,
|
||||||
@ -24,9 +40,14 @@ fn lower_normalized_via_structured(
|
|||||||
normalized_pattern2_to_structured(norm)
|
normalized_pattern2_to_structured(norm)
|
||||||
};
|
};
|
||||||
|
|
||||||
if crate::config::env::joinir_dev_enabled() {
|
log_dev(
|
||||||
eprintln!("[joinir/normalized-bridge/fallback] using structured path (functions={})", structured.functions.len());
|
"fallback",
|
||||||
}
|
format!(
|
||||||
|
"using structured path (functions={})",
|
||||||
|
structured.functions.len()
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
lower_joinir_structured_to_mir_with_meta(&structured, meta)
|
lower_joinir_structured_to_mir_with_meta(&structured, meta)
|
||||||
}
|
}
|
||||||
@ -35,6 +56,7 @@ fn lower_normalized_via_structured(
|
|||||||
pub(crate) fn lower_normalized_to_mir_minimal(
|
pub(crate) fn lower_normalized_to_mir_minimal(
|
||||||
norm: &NormalizedModule,
|
norm: &NormalizedModule,
|
||||||
meta: &JoinFuncMetaMap,
|
meta: &JoinFuncMetaMap,
|
||||||
|
allow_structured_fallback: bool,
|
||||||
) -> Result<MirModule, JoinIrVmBridgeError> {
|
) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||||
if norm.phase != JoinIrPhase::Normalized {
|
if norm.phase != JoinIrPhase::Normalized {
|
||||||
return Err(JoinIrVmBridgeError::new(
|
return Err(JoinIrVmBridgeError::new(
|
||||||
@ -42,11 +64,14 @@ pub(crate) fn lower_normalized_to_mir_minimal(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if std::env::var("JOINIR_TEST_DEBUG").is_ok() {
|
if dev_debug_enabled() {
|
||||||
eprintln!(
|
log_debug(
|
||||||
"[joinir/normalized-bridge] lowering normalized module (functions={}, env_layouts={})",
|
"debug",
|
||||||
|
format!(
|
||||||
|
"lowering normalized module (functions={}, env_layouts={})",
|
||||||
norm.functions.len(),
|
norm.functions.len(),
|
||||||
norm.env_layouts.len()
|
norm.env_layouts.len()
|
||||||
|
),
|
||||||
);
|
);
|
||||||
for layout in &norm.env_layouts {
|
for layout in &norm.env_layouts {
|
||||||
let fields: Vec<String> = layout
|
let fields: Vec<String> = layout
|
||||||
@ -54,19 +79,21 @@ pub(crate) fn lower_normalized_to_mir_minimal(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|f| format!("{}={:?}", f.name, f.value_id))
|
.map(|f| format!("{}={:?}", f.name, f.value_id))
|
||||||
.collect();
|
.collect();
|
||||||
eprintln!(
|
log_debug(
|
||||||
"[joinir/normalized-bridge] env_layout {} fields: {}",
|
"debug",
|
||||||
layout.id,
|
format!("env_layout {} fields: {}", layout.id, fields.join(", ")),
|
||||||
fields.join(", ")
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for func in norm.functions.values() {
|
for func in norm.functions.values() {
|
||||||
eprintln!(
|
log_debug(
|
||||||
"[joinir/normalized-bridge] fn {} (id={:?}) env_layout={:?} body_len={}",
|
"debug",
|
||||||
|
format!(
|
||||||
|
"fn {} (id={:?}) env_layout={:?} body_len={}",
|
||||||
func.name,
|
func.name,
|
||||||
func.id,
|
func.id,
|
||||||
func.env_layout,
|
func.env_layout,
|
||||||
func.body.len()
|
func.body.len()
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,11 +101,20 @@ pub(crate) fn lower_normalized_to_mir_minimal(
|
|||||||
// direct 対象は Normalized → MIR をそのまま吐く。未対応 shape は Structured 経由にフォールバック。
|
// direct 対象は Normalized → MIR をそのまま吐く。未対応 shape は Structured 経由にフォールバック。
|
||||||
match direct::lower_normalized_direct_minimal(norm) {
|
match direct::lower_normalized_direct_minimal(norm) {
|
||||||
Ok(mir) => Ok(mir),
|
Ok(mir) => Ok(mir),
|
||||||
Err(err) => {
|
Err(err) if allow_structured_fallback => {
|
||||||
if crate::config::env::joinir_dev_enabled() {
|
log_dev(
|
||||||
eprintln!("[joinir/normalized-bridge/fallback] direct path failed: {}; falling back to Structured path", err.message);
|
"fallback",
|
||||||
}
|
format!(
|
||||||
|
"direct path failed: {}; falling back to Structured path",
|
||||||
|
err.message
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
lower_normalized_via_structured(norm, meta)
|
lower_normalized_via_structured(norm, meta)
|
||||||
}
|
}
|
||||||
|
Err(err) => Err(JoinIrVmBridgeError::new(format!(
|
||||||
|
"[joinir/normalized-bridge] direct path failed and fallback disabled: {}",
|
||||||
|
err.message
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ use super::super::join_func_name;
|
|||||||
use super::super::JoinIrVmBridgeError;
|
use super::super::JoinIrVmBridgeError;
|
||||||
use super::super::convert_mir_like_inst;
|
use super::super::convert_mir_like_inst;
|
||||||
use crate::ast::Span;
|
use crate::ast::Span;
|
||||||
use crate::config::env::joinir_dev_enabled;
|
|
||||||
use crate::mir::join_ir::normalized::{JpFuncId, JpFunction, JpInst, JpOp, NormalizedModule};
|
use crate::mir::join_ir::normalized::{JpFuncId, JpFunction, JpInst, JpOp, NormalizedModule};
|
||||||
use crate::mir::join_ir::{JoinFuncId, JoinIrPhase, MirLikeInst};
|
use crate::mir::join_ir::{JoinFuncId, JoinIrPhase, MirLikeInst};
|
||||||
use crate::mir::{
|
use crate::mir::{
|
||||||
@ -20,19 +19,20 @@ pub(crate) fn lower_normalized_direct_minimal(
|
|||||||
) -> Result<MirModule, JoinIrVmBridgeError> {
|
) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||||
if norm.phase != JoinIrPhase::Normalized {
|
if norm.phase != JoinIrPhase::Normalized {
|
||||||
return Err(JoinIrVmBridgeError::new(
|
return Err(JoinIrVmBridgeError::new(
|
||||||
"[joinir/normalized-bridge] expected Normalized JoinIR module",
|
"[joinir/normalized-bridge/direct] expected Normalized JoinIR module",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let debug_dump = std::env::var("JOINIR_TEST_DEBUG").is_ok();
|
let debug_dump = crate::mir::join_ir::normalized::dev_env::normalized_dev_logs_enabled();
|
||||||
let verbose = joinir_dev_enabled();
|
super::log_dev(
|
||||||
if verbose {
|
"direct",
|
||||||
eprintln!(
|
format!(
|
||||||
"[joinir/normalized-bridge/direct] lowering normalized module (functions={}, env_layouts={})",
|
"using direct normalized bridge (functions={}, env_layouts={})",
|
||||||
norm.functions.len(),
|
norm.functions.len(),
|
||||||
norm.env_layouts.len()
|
norm.env_layouts.len()
|
||||||
|
),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let mut mir_module = MirModule::new("joinir_normalized_direct".to_string());
|
let mut mir_module = MirModule::new("joinir_normalized_direct".to_string());
|
||||||
|
|
||||||
@ -42,9 +42,9 @@ pub(crate) fn lower_normalized_direct_minimal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if debug_dump {
|
if debug_dump {
|
||||||
eprintln!(
|
super::log_debug(
|
||||||
"[joinir/normalized-bridge/direct] produced MIR (debug dump): {:#?}",
|
"direct",
|
||||||
mir_module
|
format!("produced MIR (debug dump): {:#?}", mir_module),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +55,6 @@ fn lower_normalized_function_direct(
|
|||||||
func: &JpFunction,
|
func: &JpFunction,
|
||||||
norm: &NormalizedModule,
|
norm: &NormalizedModule,
|
||||||
) -> Result<MirFunction, JoinIrVmBridgeError> {
|
) -> Result<MirFunction, JoinIrVmBridgeError> {
|
||||||
let verbose = joinir_dev_enabled();
|
|
||||||
let env_fields = func
|
let env_fields = func
|
||||||
.env_layout
|
.env_layout
|
||||||
.and_then(|id| norm.env_layouts.iter().find(|layout| layout.id == id));
|
.and_then(|id| norm.env_layouts.iter().find(|layout| layout.id == id));
|
||||||
@ -108,12 +107,16 @@ fn lower_normalized_function_direct(
|
|||||||
let mut current_insts: Vec<MirInstruction> = Vec::new();
|
let mut current_insts: Vec<MirInstruction> = Vec::new();
|
||||||
let mut terminated = false;
|
let mut terminated = false;
|
||||||
|
|
||||||
if verbose {
|
super::log_debug(
|
||||||
eprintln!(
|
"direct",
|
||||||
"[joinir/normalized-bridge/direct] lowering fn={} params={:?} remapped_params={:?} body_len={}",
|
format!(
|
||||||
func.name, params, remapped_params, func.body.len()
|
"lowering fn={} params={:?} remapped_params={:?} body_len={}",
|
||||||
|
func.name,
|
||||||
|
params,
|
||||||
|
remapped_params,
|
||||||
|
func.body.len()
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
for inst in &func.body {
|
for inst in &func.body {
|
||||||
if terminated {
|
if terminated {
|
||||||
@ -122,6 +125,56 @@ fn lower_normalized_function_direct(
|
|||||||
|
|
||||||
match inst {
|
match inst {
|
||||||
JpInst::Let { dst, op, args } => {
|
JpInst::Let { dst, op, args } => {
|
||||||
|
if matches!(op, JpOp::Select) {
|
||||||
|
let cond = remap(*args.get(0).unwrap_or(&ValueId(0)), &mut value_map);
|
||||||
|
let then_val = remap(*args.get(1).unwrap_or(&ValueId(0)), &mut value_map);
|
||||||
|
let else_val = remap(*args.get(2).unwrap_or(&ValueId(0)), &mut value_map);
|
||||||
|
let remapped_dst = remap(*dst, &mut value_map);
|
||||||
|
|
||||||
|
let then_bb = BasicBlockId(next_block_id);
|
||||||
|
next_block_id += 1;
|
||||||
|
let else_bb = BasicBlockId(next_block_id);
|
||||||
|
next_block_id += 1;
|
||||||
|
let merge_bb = BasicBlockId(next_block_id);
|
||||||
|
next_block_id += 1;
|
||||||
|
|
||||||
|
finalize_block(
|
||||||
|
&mut mir_func,
|
||||||
|
current_block_id,
|
||||||
|
mem::take(&mut current_insts),
|
||||||
|
MirInstruction::Branch {
|
||||||
|
condition: cond,
|
||||||
|
then_bb,
|
||||||
|
else_bb,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
finalize_block(
|
||||||
|
&mut mir_func,
|
||||||
|
then_bb,
|
||||||
|
Vec::new(),
|
||||||
|
MirInstruction::Jump { target: merge_bb },
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
finalize_block(
|
||||||
|
&mut mir_func,
|
||||||
|
else_bb,
|
||||||
|
Vec::new(),
|
||||||
|
MirInstruction::Jump { target: merge_bb },
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
current_block_id = merge_bb;
|
||||||
|
current_insts = vec![MirInstruction::Phi {
|
||||||
|
dst: remapped_dst,
|
||||||
|
inputs: vec![(then_bb, then_val), (else_bb, else_val)],
|
||||||
|
type_hint: None,
|
||||||
|
}];
|
||||||
|
mir_func.next_value_id = value_map.len() as u32;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let remapped_dst = remap(*dst, &mut value_map);
|
let remapped_dst = remap(*dst, &mut value_map);
|
||||||
let remapped_args = remap_vec(args, &mut value_map);
|
let remapped_args = remap_vec(args, &mut value_map);
|
||||||
let mir_like = jp_op_to_mir_like(remapped_dst, op, &remapped_args)?;
|
let mir_like = jp_op_to_mir_like(remapped_dst, op, &remapped_args)?;
|
||||||
@ -276,6 +329,17 @@ fn jp_op_to_mir_like(
|
|||||||
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
|
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
|
||||||
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
|
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
|
||||||
}),
|
}),
|
||||||
|
JpOp::Select => {
|
||||||
|
let cond = args.get(0).copied().unwrap_or(ValueId(0));
|
||||||
|
let then_val = args.get(1).copied().unwrap_or(ValueId(0));
|
||||||
|
let else_val = args.get(2).copied().unwrap_or(ValueId(0));
|
||||||
|
Ok(MirLikeInst::Select {
|
||||||
|
dst,
|
||||||
|
cond,
|
||||||
|
then_val,
|
||||||
|
else_val,
|
||||||
|
})
|
||||||
|
}
|
||||||
JpOp::BoxCall { box_name, method } => Ok(MirLikeInst::BoxCall {
|
JpOp::BoxCall { box_name, method } => Ok(MirLikeInst::BoxCall {
|
||||||
dst: Some(dst),
|
dst: Some(dst),
|
||||||
box_name: box_name.clone(),
|
box_name: box_name.clone(),
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! 目的: フィクスチャベースの AST→JoinIR テストを簡潔に書けるようにする
|
//! 目的: フィクスチャベースの AST→JoinIR テストを簡潔に書けるようにする
|
||||||
|
|
||||||
|
use crate::config::env::joinir_test_debug_enabled;
|
||||||
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
use crate::mir::join_ir::frontend::AstToJoinIrLowerer;
|
||||||
use crate::mir::join_ir::JoinModule;
|
use crate::mir::join_ir::JoinModule;
|
||||||
use crate::mir::join_ir_ops::JoinValue;
|
use crate::mir::join_ir_ops::JoinValue;
|
||||||
@ -22,7 +23,7 @@ impl JoinIrFrontendTestRunner {
|
|||||||
Self {
|
Self {
|
||||||
fixture_path: fixture_path.to_string(),
|
fixture_path: fixture_path.to_string(),
|
||||||
join_module: None,
|
join_module: None,
|
||||||
debug_enabled: std::env::var("JOINIR_TEST_DEBUG").is_ok(),
|
debug_enabled: joinir_test_debug_enabled(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,14 @@ use nyash_rust::mir::join_ir::{
|
|||||||
normalized_pattern2_to_structured, BinOpKind, ConstValue, JoinContId, JoinFuncId,
|
normalized_pattern2_to_structured, BinOpKind, ConstValue, JoinContId, JoinFuncId,
|
||||||
JoinFunction, JoinInst, JoinIrPhase, JoinModule, MirLikeInst,
|
JoinFunction, JoinInst, JoinIrPhase, JoinModule, MirLikeInst,
|
||||||
};
|
};
|
||||||
use nyash_rust::mir::join_ir::normalized::dev_env::NormalizedDevEnvGuard;
|
use nyash_rust::mir::join_ir::normalized::dev_env::{
|
||||||
|
normalized_dev_enabled, test_ctx, NormalizedDevEnvGuard, NormalizedTestContext,
|
||||||
|
};
|
||||||
use nyash_rust::mir::join_ir::normalized::fixtures::{
|
use nyash_rust::mir::join_ir::normalized::fixtures::{
|
||||||
build_jsonparser_atoi_structured_for_normalized_dev,
|
build_jsonparser_atoi_structured_for_normalized_dev,
|
||||||
|
build_jsonparser_atoi_real_structured_for_normalized_dev,
|
||||||
|
build_jsonparser_parse_number_real_structured_for_normalized_dev,
|
||||||
|
build_jsonparser_skip_ws_real_structured_for_normalized_dev,
|
||||||
build_jsonparser_skip_ws_structured_for_normalized_dev,
|
build_jsonparser_skip_ws_structured_for_normalized_dev,
|
||||||
build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
|
build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
|
||||||
};
|
};
|
||||||
@ -16,10 +21,19 @@ use nyash_rust::mir::join_ir_runner::run_joinir_function;
|
|||||||
use nyash_rust::mir::join_ir_ops::JoinValue;
|
use nyash_rust::mir::join_ir_ops::JoinValue;
|
||||||
use nyash_rust::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
use nyash_rust::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||||
use nyash_rust::mir::ValueId;
|
use nyash_rust::mir::ValueId;
|
||||||
|
fn normalized_dev_test_ctx() -> NormalizedTestContext<'static> {
|
||||||
|
let ctx = test_ctx();
|
||||||
|
assert!(
|
||||||
|
normalized_dev_enabled(),
|
||||||
|
"Phase 40: normalized_dev must be enabled for normalized_* tests (feature + NYASH_JOINIR_NORMALIZED_DEV_RUN=1)"
|
||||||
|
);
|
||||||
|
ctx
|
||||||
|
}
|
||||||
|
|
||||||
fn assert_normalized_dev_ready() {
|
fn assert_normalized_dev_ready() {
|
||||||
assert!(
|
assert!(
|
||||||
nyash_rust::config::env::normalized_dev_enabled(),
|
normalized_dev_enabled(),
|
||||||
"Phase 33: normalized_dev must be enabled for this suite (feature + env)"
|
"Phase 40: normalized_dev must be enabled for normalized_* tests (feature + NYASH_JOINIR_NORMALIZED_DEV_RUN=1)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +101,7 @@ fn build_structured_pattern1() -> JoinModule {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern1_minimal_smoke() {
|
fn normalized_pattern1_minimal_smoke() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_structured_pattern1();
|
let structured = build_structured_pattern1();
|
||||||
let normalized = normalize_pattern1_minimal(&structured);
|
let normalized = normalize_pattern1_minimal(&structured);
|
||||||
assert_eq!(normalized.phase, JoinIrPhase::Normalized);
|
assert_eq!(normalized.phase, JoinIrPhase::Normalized);
|
||||||
@ -103,6 +118,7 @@ fn normalized_pattern1_minimal_smoke() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern1_roundtrip_structured_equivalent() {
|
fn normalized_pattern1_roundtrip_structured_equivalent() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_structured_pattern1();
|
let structured = build_structured_pattern1();
|
||||||
let normalized = normalize_pattern1_minimal(&structured);
|
let normalized = normalize_pattern1_minimal(&structured);
|
||||||
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
||||||
@ -121,6 +137,7 @@ fn normalized_pattern1_roundtrip_structured_equivalent() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern1_exec_matches_structured() {
|
fn normalized_pattern1_exec_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_structured_pattern1();
|
let structured = build_structured_pattern1();
|
||||||
let normalized = normalize_pattern1_minimal(&structured);
|
let normalized = normalize_pattern1_minimal(&structured);
|
||||||
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
||||||
@ -136,6 +153,7 @@ fn normalized_pattern1_exec_matches_structured() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern1_exec_matches_structured_roundtrip_backup() {
|
fn normalized_pattern1_exec_matches_structured_roundtrip_backup() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_structured_pattern1();
|
let structured = build_structured_pattern1();
|
||||||
let normalized = normalize_pattern1_minimal(&structured);
|
let normalized = normalize_pattern1_minimal(&structured);
|
||||||
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
||||||
@ -156,6 +174,7 @@ fn normalized_pattern1_exec_matches_structured_roundtrip_backup() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern2_roundtrip_structure() {
|
fn normalized_pattern2_roundtrip_structure() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_pattern2_minimal_structured();
|
let structured = build_pattern2_minimal_structured();
|
||||||
let normalized = normalize_pattern2_minimal(&structured);
|
let normalized = normalize_pattern2_minimal(&structured);
|
||||||
assert_eq!(normalized.phase, JoinIrPhase::Normalized);
|
assert_eq!(normalized.phase, JoinIrPhase::Normalized);
|
||||||
@ -175,8 +194,39 @@ fn normalized_pattern2_roundtrip_structure() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalized_pattern2_jsonparser_parse_number_real_vm_bridge_direct_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
|
let structured = build_jsonparser_parse_number_real_structured_for_normalized_dev();
|
||||||
|
let entry = structured.entry.expect("structured entry required");
|
||||||
|
let cases = [
|
||||||
|
("42", 0, "42"),
|
||||||
|
("123abc", 0, "123"),
|
||||||
|
("9", 0, "9"),
|
||||||
|
("abc", 0, ""),
|
||||||
|
("xx7yy", 2, "7"),
|
||||||
|
("007", 0, "007"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (s, pos, expected) in cases {
|
||||||
|
let input = [JoinValue::Str(s.to_string()), JoinValue::Int(pos)];
|
||||||
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
||||||
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
||||||
|
|
||||||
|
assert_eq!(base, dev, "vm bridge mismatch for input '{}'", s);
|
||||||
|
assert_eq!(
|
||||||
|
dev,
|
||||||
|
JoinValue::Str(expected.to_string()),
|
||||||
|
"unexpected result for input '{}' (pos={}) (expected num_str)",
|
||||||
|
s,
|
||||||
|
pos
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern2_exec_matches_structured() {
|
fn normalized_pattern2_exec_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_pattern2_minimal_structured();
|
let structured = build_pattern2_minimal_structured();
|
||||||
let normalized = normalize_pattern2_minimal(&structured);
|
let normalized = normalize_pattern2_minimal(&structured);
|
||||||
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
||||||
@ -193,6 +243,7 @@ fn normalized_pattern2_exec_matches_structured() {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "normalize_pattern2_minimal")]
|
#[should_panic(expected = "normalize_pattern2_minimal")]
|
||||||
fn normalized_pattern2_rejects_non_pattern2_structured() {
|
fn normalized_pattern2_rejects_non_pattern2_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
// Pattern1 Structured module should be rejected by Pattern2 normalizer.
|
// Pattern1 Structured module should be rejected by Pattern2 normalizer.
|
||||||
let structured = build_structured_pattern1();
|
let structured = build_structured_pattern1();
|
||||||
let _ = normalize_pattern2_minimal(&structured);
|
let _ = normalize_pattern2_minimal(&structured);
|
||||||
@ -200,6 +251,7 @@ fn normalized_pattern2_rejects_non_pattern2_structured() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern2_real_loop_roundtrip_structure() {
|
fn normalized_pattern2_real_loop_roundtrip_structure() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_pattern2_break_fixture_structured();
|
let structured = build_pattern2_break_fixture_structured();
|
||||||
let normalized = normalize_pattern2_minimal(&structured);
|
let normalized = normalize_pattern2_minimal(&structured);
|
||||||
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
||||||
@ -221,6 +273,7 @@ fn normalized_pattern2_real_loop_roundtrip_structure() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern2_real_loop_exec_matches_structured() {
|
fn normalized_pattern2_real_loop_exec_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_pattern2_break_fixture_structured();
|
let structured = build_pattern2_break_fixture_structured();
|
||||||
let normalized = normalize_pattern2_minimal(&structured);
|
let normalized = normalize_pattern2_minimal(&structured);
|
||||||
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
||||||
@ -246,6 +299,7 @@ fn normalized_pattern2_real_loop_exec_matches_structured() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern1_runner_dev_switch_matches_structured() {
|
fn normalized_pattern1_runner_dev_switch_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_structured_pattern1();
|
let structured = build_structured_pattern1();
|
||||||
let entry = structured.entry.expect("structured entry required");
|
let entry = structured.entry.expect("structured entry required");
|
||||||
let input = [JoinValue::Int(7)];
|
let input = [JoinValue::Int(7)];
|
||||||
@ -259,6 +313,7 @@ fn normalized_pattern1_runner_dev_switch_matches_structured() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern2_runner_dev_switch_matches_structured() {
|
fn normalized_pattern2_runner_dev_switch_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_pattern2_break_fixture_structured();
|
let structured = build_pattern2_break_fixture_structured();
|
||||||
let entry = structured.entry.expect("structured entry required");
|
let entry = structured.entry.expect("structured entry required");
|
||||||
let cases = [0, 1, 3, 5];
|
let cases = [0, 1, 3, 5];
|
||||||
@ -281,6 +336,7 @@ fn normalized_pattern2_runner_dev_switch_matches_structured() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern2_jsonparser_runner_dev_switch_matches_structured() {
|
fn normalized_pattern2_jsonparser_runner_dev_switch_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_jsonparser_skip_ws_structured_for_normalized_dev();
|
let structured = build_jsonparser_skip_ws_structured_for_normalized_dev();
|
||||||
let entry = structured.entry.expect("structured entry required");
|
let entry = structured.entry.expect("structured entry required");
|
||||||
let cases = [0, 1, 2, 5];
|
let cases = [0, 1, 2, 5];
|
||||||
@ -297,6 +353,7 @@ fn normalized_pattern2_jsonparser_runner_dev_switch_matches_structured() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern2_vm_bridge_direct_matches_structured() {
|
fn normalized_pattern2_vm_bridge_direct_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_pattern2_break_fixture_structured();
|
let structured = build_pattern2_break_fixture_structured();
|
||||||
let entry = structured.entry.expect("structured entry required");
|
let entry = structured.entry.expect("structured entry required");
|
||||||
let cases = [0, 1, 3, 5];
|
let cases = [0, 1, 3, 5];
|
||||||
@ -319,6 +376,7 @@ fn normalized_pattern2_vm_bridge_direct_matches_structured() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern1_vm_bridge_direct_matches_structured() {
|
fn normalized_pattern1_vm_bridge_direct_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_structured_pattern1();
|
let structured = build_structured_pattern1();
|
||||||
let entry = structured.entry.expect("structured entry required");
|
let entry = structured.entry.expect("structured entry required");
|
||||||
let cases = [0, 5, 7];
|
let cases = [0, 5, 7];
|
||||||
@ -335,6 +393,7 @@ fn normalized_pattern1_vm_bridge_direct_matches_structured() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern2_jsonparser_vm_bridge_direct_matches_structured() {
|
fn normalized_pattern2_jsonparser_vm_bridge_direct_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_jsonparser_skip_ws_structured_for_normalized_dev();
|
let structured = build_jsonparser_skip_ws_structured_for_normalized_dev();
|
||||||
let entry = structured.entry.expect("structured entry required");
|
let entry = structured.entry.expect("structured entry required");
|
||||||
let cases = [0, 1, 2, 5];
|
let cases = [0, 1, 2, 5];
|
||||||
@ -349,8 +408,37 @@ fn normalized_pattern2_jsonparser_vm_bridge_direct_matches_structured() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalized_pattern2_jsonparser_skip_ws_real_vm_bridge_direct_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
|
let structured = build_jsonparser_skip_ws_real_structured_for_normalized_dev();
|
||||||
|
let entry = structured.entry.expect("structured entry required");
|
||||||
|
let cases = [
|
||||||
|
(" abc", 0, 3),
|
||||||
|
("abc", 0, 0),
|
||||||
|
(" \t\nx", 0, 3),
|
||||||
|
(" \t\nx", 2, 3),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (s, pos, expected) in cases {
|
||||||
|
let input = [JoinValue::Str(s.to_string()), JoinValue::Int(pos)];
|
||||||
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
||||||
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
||||||
|
|
||||||
|
assert_eq!(base, dev, "vm bridge mismatch for input '{}'", s);
|
||||||
|
assert_eq!(
|
||||||
|
dev,
|
||||||
|
JoinValue::Int(expected),
|
||||||
|
"unexpected result for input '{}' (pos={})",
|
||||||
|
s,
|
||||||
|
pos
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_pattern2_jsonparser_atoi_vm_bridge_direct_matches_structured() {
|
fn normalized_pattern2_jsonparser_atoi_vm_bridge_direct_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
let structured = build_jsonparser_atoi_structured_for_normalized_dev();
|
let structured = build_jsonparser_atoi_structured_for_normalized_dev();
|
||||||
let entry = structured.entry.expect("structured entry required");
|
let entry = structured.entry.expect("structured entry required");
|
||||||
let cases = [
|
let cases = [
|
||||||
@ -374,3 +462,48 @@ fn normalized_pattern2_jsonparser_atoi_vm_bridge_direct_matches_structured() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalized_pattern2_jsonparser_atoi_real_vm_bridge_direct_matches_structured() {
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
|
let structured = build_jsonparser_atoi_real_structured_for_normalized_dev();
|
||||||
|
if nyash_rust::config::env::joinir_test_debug_enabled() {
|
||||||
|
eprintln!(
|
||||||
|
"[joinir/normalized-dev/test] structured jsonparser_atoi_real: {:#?}",
|
||||||
|
structured
|
||||||
|
);
|
||||||
|
let normalized = normalize_pattern2_minimal(&structured);
|
||||||
|
eprintln!(
|
||||||
|
"[joinir/normalized-dev/test] normalized jsonparser_atoi_real: {:#?}",
|
||||||
|
normalized
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let entry = structured.entry.expect("structured entry required");
|
||||||
|
let cases = [
|
||||||
|
("42", 42),
|
||||||
|
("123abc", 123),
|
||||||
|
("007", 7),
|
||||||
|
("", 0),
|
||||||
|
("abc", 0),
|
||||||
|
("-42", -42),
|
||||||
|
("+7", 7),
|
||||||
|
("-0", 0),
|
||||||
|
("-12x", -12),
|
||||||
|
("+", 0),
|
||||||
|
("-", 0),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (s, expected) in cases {
|
||||||
|
let input = [JoinValue::Str(s.to_string())];
|
||||||
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
||||||
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
||||||
|
|
||||||
|
assert_eq!(base, dev, "vm bridge mismatch for input '{}'", s);
|
||||||
|
assert_eq!(
|
||||||
|
dev,
|
||||||
|
JoinValue::Int(expected),
|
||||||
|
"unexpected result for input '{}'",
|
||||||
|
s
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user