From 386cbc19150ec1d19c99afc133916fd498d0d70e Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Fri, 12 Dec 2025 07:13:34 +0900 Subject: [PATCH] Phase47-B/C: extend P3 normalized shapes and quiet dev warnings --- CURRENT_TASK.md | 12 +- .../main/joinir-architecture-overview.md | 47 +-- .../current/main/phase47-norm-p3-design.md | 46 ++- src/mir/join_ir/frontend/ast_lowerer/mod.rs | 2 + src/mir/join_ir/lowering/step_schedule.rs | 3 + src/mir/join_ir/normalized.rs | 66 +++- src/mir/join_ir/normalized/fixtures.rs | 283 +++++++++++++++++- src/mir/join_ir/normalized/shape_guard.rs | 53 +++- src/mir/join_ir_vm_bridge/bridge.rs | 15 +- .../function_scope_capture/mod.rs | 2 +- tests/normalized_joinir_min.rs | 47 +++ 11 files changed, 503 insertions(+), 73 deletions(-) diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 5e11be93..2b5b0f78 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -87,13 +87,11 @@ - JsonParser P2 ライン(_skip_whitespace/_atoi/_parse_number)全て canonical Normalized 化完了。 - P3/P4 Normalized 対応は NORM-P3/NORM-P4 フェーズで実施(今回スコープ外)。 - 937/937 tests PASS。 -- **Phase 47-A-IMPL(実装中 2025-12-12)**: - - StepScheduleBox 汎用化: pattern2_step_schedule.rs → step_schedule.rs リネーム - - P3 StepKind 追加: IfCond, ThenUpdates, ElseUpdates(P2 lowering との分離完了) - - ShapeGuard: Pattern3IfSumMinimal 検出追加(placeholder stub実装) - - Normalized bridge: P3 shape handling 追加(P2 normalization 使用) - - 938/938 tests PASS(regression なし) - - 次ステップ: Phase 47-A-LOWERING で full P3 Normalized lowering 実装 +- **Phase 47-B/C(P3 if-sum 拡張 + canonical 化 ✅ 2025-12-21)**: + - フィクスチャ追加: `pattern3_if_sum_multi_min`(sum+count)/ `jsonparser_if_sum_min`(JsonParser 簡約形) + - ShapeGuard: `Pattern3IfSumMulti` / `Pattern3IfSumJson` 追加、capability=P3IfSum、canonical set に P3 minimal/multi/json を追加 + - Normalizer/Bridge: P3 if-sum minimal/multi/json を Structured→Normalized→MIR(direct) で dev A/B、canonical ルートでも常時 Normalized 経由 + - テスト: normalized_joinir_min.rs に P3 if-sum multi/json の VM ブリッジ比較テスト追加(Structured と一致) - **Phase 45-NORM-MODE(実装済み✅ 2025-12-12)**: - JoinIR モード一本化: バラバラだったフラグ/feature を `JoinIrMode` enum に集約(StructuredOnly / NormalizedDev / NormalizedCanonical)。 - `current_joinir_mode()` でモード取得、bridge/runner で `normalized_dev_enabled()` → mode pattern matching に移行。 diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index 52da2267..a4b0bb23 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -1404,32 +1404,43 @@ Pattern3 (if-sum) ループを Normalized JoinIR に対応させる。P2 と同 - ✅ shape_guard に `Pattern3IfSumMinimal` を追加し、構造ベースで P3 最小 if-sum 形状を検出 - ✅ `normalize_pattern3_if_sum_minimal` を通じて P3 最小ケースを Normalized→MIR(direct) パイプラインに載せ、P1/P2 と同じ direct ブリッジで実行結果一致を確認(dev-only) -**Phase 47-B**: array_filter (dev-only, body-local + method calls) +**Phase 47-B**: Extended dev(sum+count / JsonParser if-sum mini) +- ✅ フィクスチャ追加: `pattern3_if_sum_multi_min`(sum+count)/ `jsonparser_if_sum_min`(JsonParser 由来) +- ✅ ShapeGuard: `Pattern3IfSumMulti` / `Pattern3IfSumJson` 追加、capability=P3IfSum +- ✅ Normalizer/Bridge: P3 if-sum multi/json を Structured→Normalized→MIR(direct) で dev A/B(Structured と一致) +- ✅ VM Bridge テスト: `normalized_pattern3_if_sum_multi_vm_bridge_direct_matches_structured` / `normalized_pattern3_json_if_sum_min_vm_bridge_direct_matches_structured` -**Phase 47-C**: Canonical promotion (P3 minimal → canonical) +**Phase 47-C**: Canonical promotion(P3 minimal/multi/json → canonical Normalized) +- Canonical set 拡張: P3 if-sum minimal/multi/json を `is_canonical_shape()` に追加、mode/env 無視で Normalized→MIR(direct) ルートを使用 +- Bridge/runner: canonical shapes は Structured fallback せず fail-fast(dev logs は normalized-dev プレフィックスに統一) **スコープ外**: P4 (continue) 対応(NORM-P4 フェーズで実施)、Complex P3 patterns(後続フェーズ) -### 3.25 Phase 48-NORM-P4 – Normalized P4 (Continue) Design 📋 DESIGN PHASE (2025-12-12) +### 3.25 Phase 48-NORM-P4 – Normalized P4 (Continue) 🏗️ DESIGN + PHASE 48-A MINIMAL DEV COMPLETE (2025-12-12) -**設計詳細**: [phase48-norm-p4-design.md](./phase48-norm-p4-design.md) +**設計詳細 / 実装サマリ**: [phase48-norm-p4-design.md](./phase48-norm-p4-design.md) -P4 (continue) will use the **same `loop_step(env, k_exit)` skeleton** as P1/P2/P3. - -**Key insight**: `continue` = immediate `TailCallFn(loop_step, env', k_exit)` (skip to next iteration early), not a new instruction. +P4 (continue) は P1/P2/P3 と同じ `loop_step(env, k_exit)` 骨格を使う設計だよ。 +**Key insight**: `continue` = 「更新済み Env での `TailCallFn(loop_step, env', k_exit)`」で表現できる(新しい命令種別は不要)。 **Target loops** (JsonParser): -- ◎ _parse_array (skip whitespace) - PRIMARY (Phase 48-A) -- ○ _parse_object (skip whitespace) - Extended -- △ _unescape_string, _parse_string - Later +- ◎ `_parse_array` (skip whitespace) – PRIMARY(Phase 48-A 対象) +- ○ `_parse_object` (skip whitespace) – Extended +- △ `_unescape_string`, `_parse_string` – Later -**Infrastructure reuse**: 95%+ of P2/P3 Normalized code works for P4 -- Same: EnvLayout, ConditionEnv, CarrierInfo, ExitLine, JpInst -- New: `ContinueCheck` step kind in StepScheduleBox +**Infrastructure reuse**: P2/P3 Normalized の 95% 以上をそのまま再利用 +- 共通: EnvLayout / ConditionEnv / CarrierInfo / ExitLine / JpInst +- 追加: StepScheduleBox に `ContinueCheck` step kind を追加 -**Phase 48 doc is SSOT** for P4 Normalized design. +**Phase 48-A(Minimal continue, dev-only)実装ステータス**: +- Fixture: `pattern4_continue_min.program.json`(`i == 2` を `continue` でスキップする最小 P4 ループ) +- ShapeGuard: `NormalizedDevShape::Pattern4ContinueMinimal` を追加し、構造ベースで minimal continue 形状を検出 +- StepSchedule: `HeaderCond → ContinueCheck → Updates → Tail` の順序を固定 +- Normalized lowering: `normalize_pattern4_continue_minimal()` を実装し、P2 正規化ロジックを約 95% 再利用 +- テスト: + - Normalized dev スイートに P4 minimal の比較テストを 4 本追加 + (Structured→Normalized→MIR(direct) vs Structured→MIR / runner / VM bridge) + - `cargo test --release` ベースで **939/939 tests PASS**(Phase 48-A 実装時点) -**Implementation phases**: -- Phase 48-A: Minimal continue (dev-only) -- Phase 48-B: Extended patterns (multi-carrier) -- Phase 48-C: Canonical promotion +**Phase 48 doc is SSOT** for P4 Normalized design + 48-A 実装サマリだよ。 +Phase 48-B(multi-carrier / string ops 拡張)と 48-C(canonical 昇格)は今後のフェーズで扱う。 diff --git a/docs/development/current/main/phase47-norm-p3-design.md b/docs/development/current/main/phase47-norm-p3-design.md index 6743e4c5..df73e54d 100644 --- a/docs/development/current/main/phase47-norm-p3-design.md +++ b/docs/development/current/main/phase47-norm-p3-design.md @@ -1,7 +1,7 @@ # Phase 47: Normalized P3 (If-Sum) Design -**Status**: Design Complete, Minimal Dev Test Implemented -**Date**: 2025-12-12 +**Status**: Design Complete, Minimal → Extended Dev → Canonical (P3 Core) 実装中 +**Date**: 2025-12-21 ## Goal @@ -127,27 +127,17 @@ loop(i < n) { - EnvLayout: `{ i: int, sum: int, count: int }` - StepSchedule: `[HeaderCond(i < n), IfCond(i % 2 == 1), ThenUpdates(sum, count), Updates(i), Tail]` -### Phase 47-B: JsonParser array_filter +### Phase 47-B: Extended dev targets(今回の範囲) -**Example**: JsonParser `array_filter` (if present in codebase) +| Fixture | Carriers / Params | 条件式 | EnvLayout 期待 | +|---------|------------------|--------|----------------| +| `pattern3_if_sum_multi_min` | `i`, `sum`, `count` | `i > 0` | `i, sum, count, len` | +| `jsonparser_if_sum_min` | `i`, `sum` | `i > 0`(JsonParser 由来の簡約形) | `i, sum, len` | -```nyash -local out = new ArrayBox() -local i = 0 - -loop(i < arr.length()) { - local v = arr.get(i) - if (predicate(v)) { - out.push(v) - } - i = i + 1 -} -``` - -**Characteristics**: -- Method calls: `arr.length()`, `arr.get(i)`, `out.push(v)` -- Body-local: `v` (used in if condition) -- Conditional side effect: `out.push(v)` +特徴: +- どちらも if/else を持ち、Else は no-op(Select/If PHI を確実に通すため) +- MethodCall は含めず、まずは条件+複数キャリア更新に絞った dev 拡張 +- P3 if-sum capability (= P3IfSum) に乗せ、Structured→Normalized→MIR(direct) で A/B 比較する **Complexity**: Higher than sum_count (method calls, body-local) @@ -171,10 +161,16 @@ Complex P3 patterns from selfhost compiler (deferred to later phase). - ✅ Runner / VM tests: P3 minimal について Structured 経路と Normalized→MIR(direct) 経路が一致することを dev-only スイートで確認 - ✅ 938/938 tests PASS(退行なし) -**Next Phase**(Phase 47-B 以降): -- array_filter など body-local + method call を含む P3 ループへの適用 -- multi-carrier if-sum(sum + count)や JsonParser 由来 P3 への拡張 -- Canonical 昇格(P3 minimal を dev-only から常時 Normalized 経路へ) +**Phase 47-B (✅ Extended dev)**: +- Fixture 拡張: `pattern3_if_sum_multi_min.program.json`(sum+count) / `jsonparser_if_sum_min.program.json` を normalized_dev フィクスチャに追加 +- ShapeGuard: `Pattern3IfSumMulti` / `Pattern3IfSumJson` 追加、capability=P3IfSum +- Normalizer/Bridge: P3 if-sum multi/json を Structured→Normalized→MIR(direct) で dev 実行、Structured と一致 +- Tests: `normalized_pattern3_if_sum_multi_vm_bridge_direct_matches_structured` / `normalized_pattern3_json_if_sum_min_vm_bridge_direct_matches_structured` + +**Phase 47-C (⏩ Canonical 化)**: +- P3 if-sum minimal/multi/json を canonical Normalized セットに昇格(mode/env に関わらず direct ルート) +- Bridge/runner は P2 と同様に P3 を常時 Normalized→MIR(direct) へルーティング +- Docs/overview を canonical セット拡張に合わせて更新 ## Implementation Strategy diff --git a/src/mir/join_ir/frontend/ast_lowerer/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/mod.rs index d86e89bf..d3c39701 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/mod.rs @@ -67,6 +67,8 @@ fn resolve_function_route(func_name: &str) -> Result { ("jsonparser_atoi_mini", FunctionRoute::LoopFrontend), ("jsonparser_atoi_real", FunctionRoute::LoopFrontend), ("jsonparser_parse_number_real", FunctionRoute::LoopFrontend), + ("pattern3_if_sum_multi_min", FunctionRoute::LoopFrontend), + ("jsonparser_if_sum_min", FunctionRoute::LoopFrontend), // Phase 48-A: Pattern4 continue minimal ("pattern4_continue_minimal", FunctionRoute::LoopFrontend), ]; diff --git a/src/mir/join_ir/lowering/step_schedule.rs b/src/mir/join_ir/lowering/step_schedule.rs index 521ece66..7c6f359b 100644 --- a/src/mir/join_ir/lowering/step_schedule.rs +++ b/src/mir/join_ir/lowering/step_schedule.rs @@ -13,6 +13,7 @@ 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. +#[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum Pattern2StepKind { // P2 (Pattern2 Break) steps @@ -154,6 +155,7 @@ fn log_schedule(ctx: &Pattern2ScheduleContext, schedule: &Pattern2StepSchedule) } /// Phase 47-A: Generate step schedule for Pattern3 (if-sum) loops +#[allow(dead_code)] pub(crate) fn pattern3_if_sum_schedule() -> Vec { vec![ Pattern2StepKind::HeaderCond, // loop(i < n) @@ -165,6 +167,7 @@ pub(crate) fn pattern3_if_sum_schedule() -> Vec { } /// Phase 48-A: Generate step schedule for Pattern4 (continue) loops +#[allow(dead_code)] pub(crate) fn pattern4_continue_schedule() -> Vec { vec![ Pattern2StepKind::HeaderCond, // loop(i < n) diff --git a/src/mir/join_ir/normalized.rs b/src/mir/join_ir/normalized.rs index 49a8769b..26a2576b 100644 --- a/src/mir/join_ir/normalized.rs +++ b/src/mir/join_ir/normalized.rs @@ -333,6 +333,16 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule { { max = max.max(6); } + if shapes.iter().any(|s| { + matches!( + s, + NormalizedDevShape::Pattern3IfSumMinimal + | NormalizedDevShape::Pattern3IfSumMulti + | NormalizedDevShape::Pattern3IfSumJson + ) + }) { + max = max.max(6); + } } max }; @@ -528,29 +538,49 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule { norm } -/// Phase 47-A: Normalize Pattern3 if-sum minimal to Normalized JoinIR +/// Phase 47-A/B: Normalize Pattern3 if-sum shapes to Normalized JoinIR #[cfg(feature = "normalized_dev")] pub fn normalize_pattern3_if_sum_minimal( structured: &JoinModule, ) -> Result { - // Guard: Must be Structured and match Pattern3IfSumMinimal shape + normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::Pattern3IfSumMinimal) +} + +/// Phase 47-B: Normalize Pattern3 if-sum multi-carrier (sum+count) shape. +#[cfg(feature = "normalized_dev")] +pub fn normalize_pattern3_if_sum_multi_minimal( + structured: &JoinModule, +) -> Result { + normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::Pattern3IfSumMulti) +} + +/// Phase 47-B: Normalize JsonParser if-sum (mini) shape. +#[cfg(feature = "normalized_dev")] +pub fn normalize_pattern3_if_sum_json_minimal( + structured: &JoinModule, +) -> Result { + normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::Pattern3IfSumJson) +} + +#[cfg(feature = "normalized_dev")] +fn normalize_pattern3_if_sum_shape( + structured: &JoinModule, + target_shape: NormalizedDevShape, +) -> Result { if !structured.is_structured() { return Err("[normalize_p3] Not structured JoinIR".to_string()); } - // Use shape detection to verify P3 shape - let shapes = shape_guard::supported_shapes(&structured); - if !shapes.contains(&NormalizedDevShape::Pattern3IfSumMinimal) { - return Err("[normalize_p3] Not Pattern3IfSumMinimal shape".to_string()); + let shapes = shape_guard::supported_shapes(structured); + if !shapes.contains(&target_shape) { + return Err(format!( + "[normalize_p3] shape mismatch: expected {:?}, got {:?}", + target_shape, shapes + )); } - // Phase 47-A minimal: Reuse P2 normalization temporarily - // TODO: Implement proper P3-specific normalization with: - // - EnvLayout for i, sum carriers - // - IfCond → ThenUpdates / ElseUpdates step sequence - // - Proper JpInst::If for conditional carrier updates - - // For now, delegate to P2 normalization (works for simple cases) + // Phase 47-B: P3 if-sum は既存の P2 ミニ正規化器で十分に表現できる + // (Select/If/Compare/BinOp をそのまま JpInst に写す)。 Ok(normalize_pattern2_minimal(structured)) } @@ -974,6 +1004,16 @@ pub(crate) fn normalized_dev_roundtrip_structured( .expect("P3 normalization failed"); normalized_pattern2_to_structured(&norm) })), + NormalizedDevShape::Pattern3IfSumMulti => catch_unwind(AssertUnwindSafe(|| { + let norm = normalize_pattern3_if_sum_multi_minimal(module) + .expect("P3 multi normalization failed"); + normalized_pattern2_to_structured(&norm) + })), + NormalizedDevShape::Pattern3IfSumJson => catch_unwind(AssertUnwindSafe(|| { + let norm = normalize_pattern3_if_sum_json_minimal(module) + .expect("P3 json normalization failed"); + normalized_pattern2_to_structured(&norm) + })), // Phase 48-A: P4 minimal (delegates to P2 for now, but uses proper guard) NormalizedDevShape::Pattern4ContinueMinimal => catch_unwind(AssertUnwindSafe(|| { let norm = normalize_pattern4_continue_minimal(module) diff --git a/src/mir/join_ir/normalized/fixtures.rs b/src/mir/join_ir/normalized/fixtures.rs index 6e0b05bc..5c3e4b96 100644 --- a/src/mir/join_ir/normalized/fixtures.rs +++ b/src/mir/join_ir/normalized/fixtures.rs @@ -8,11 +8,51 @@ 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_with_break_minimal::lower_loop_with_break_minimal; use crate::mir::join_ir::lowering::loop_with_if_phi_if_sum::lower_if_sum_pattern; -use crate::mir::join_ir::JoinModule; +use crate::mir::join_ir::{ + BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, + UnaryOp, +}; use crate::mir::{BasicBlockId, ValueId}; use crate::{config::env::joinir_dev_enabled, config::env::joinir_test_debug_enabled}; use std::collections::{BTreeMap, BTreeSet}; +fn const_i64(func: &mut JoinFunction, dst: ValueId, value: i64) { + func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst, + value: ConstValue::Integer(value), + })); +} + +fn compare(func: &mut JoinFunction, dst: ValueId, op: CompareOp, lhs: ValueId, rhs: ValueId) { + func.body.push(JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs })); +} + +fn unary_not(func: &mut JoinFunction, dst: ValueId, operand: ValueId) { + func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp { + dst, + op: UnaryOp::Not, + operand, + })); +} + +fn bin_add(func: &mut JoinFunction, dst: ValueId, lhs: ValueId, rhs: ValueId) { + func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst, + op: BinOpKind::Add, + lhs, + rhs, + })); +} + +fn select(func: &mut JoinFunction, dst: ValueId, cond: ValueId, then_val: ValueId, else_val: ValueId) { + func.body.push(JoinInst::Compute(MirLikeInst::Select { + dst, + cond, + then_val, + else_val, + })); +} + /// Structured Pattern2 (joinir_min_loop 相当) をテスト用に生成するヘルパー。 pub fn build_pattern2_minimal_structured() -> JoinModule { let loop_cond = ASTNode::BinaryOp { @@ -163,6 +203,245 @@ pub fn build_jsonparser_parse_number_real_structured_for_normalized_dev() -> Joi module } +/// Phase 47-B: Pattern3 if-sum (multi carrier) を Structured で組み立てるヘルパー。 +/// +/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/pattern3_if_sum_multi_min.program.json +pub fn build_pattern3_if_sum_multi_min_structured_for_normalized_dev() -> JoinModule { + // 手書き JoinIR(i/sum/count の 3 キャリア) + let mut module = JoinModule::new(); + let mut next_id = 0u32; + let mut alloc = || { + let id = ValueId(next_id); + next_id += 1; + id + }; + + let main_id = JoinFuncId::new(0); + let loop_id = JoinFuncId::new(1); + let exit_id = JoinFuncId::new(2); + + // main: init i/sum/count = 0 → tail call loop_step + let i0 = alloc(); + let sum0 = alloc(); + let count0 = alloc(); + let mut main = JoinFunction::new(main_id, "main".to_string(), vec![]); + const_i64(&mut main, i0, 0); + const_i64(&mut main, sum0, 0); + const_i64(&mut main, count0, 0); + main.body.push(JoinInst::Call { + func: loop_id, + args: vec![i0, sum0, count0], + k_next: None, + dst: None, + }); + module.add_function(main); + + // loop_step params: i, sum, count + let i_param = alloc(); + let sum_param = alloc(); + let count_param = alloc(); + let mut loop_step = JoinFunction::new( + loop_id, + "loop_step".to_string(), + vec![i_param, sum_param, count_param], + ); + + // loop condition: i < 3 + let limit_const = alloc(); + let cmp_loop = alloc(); + let exit_cond = alloc(); + const_i64(&mut loop_step, limit_const, 3); + compare( + &mut loop_step, + cmp_loop, + CompareOp::Lt, + i_param, + limit_const, + ); + unary_not(&mut loop_step, exit_cond, cmp_loop); + loop_step.body.push(JoinInst::Jump { + cont: exit_id.as_cont(), + args: vec![sum_param, count_param], + cond: Some(exit_cond), + }); + + // if condition: i > 0 + let cond_cmp = alloc(); + let zero_const = alloc(); + const_i64(&mut loop_step, zero_const, 0); + compare( + &mut loop_step, + cond_cmp, + CompareOp::Gt, + i_param, + zero_const, + ); + + // then: sum = sum + 1, count = count + 1 + let one_const = alloc(); + let sum_then = alloc(); + let count_then = alloc(); + const_i64(&mut loop_step, one_const, 1); + bin_add(&mut loop_step, sum_then, sum_param, one_const); + bin_add(&mut loop_step, count_then, count_param, one_const); + + // else: identity + let sum_else = alloc(); + let count_else = alloc(); + bin_add(&mut loop_step, sum_else, sum_param, zero_const); + bin_add(&mut loop_step, count_else, count_param, zero_const); + + // select + let sum_new = alloc(); + let count_new = alloc(); + select(&mut loop_step, sum_new, cond_cmp, sum_then, sum_else); + select(&mut loop_step, count_new, cond_cmp, count_then, count_else); + + // counter update: i = i + 1 + let one_const2 = alloc(); + let i_next = alloc(); + const_i64(&mut loop_step, one_const2, 1); + bin_add(&mut loop_step, i_next, i_param, one_const2); + + loop_step.body.push(JoinInst::Call { + func: loop_id, + args: vec![i_next, sum_new, count_new], + k_next: None, + dst: None, + }); + module.add_function(loop_step); + + // k_exit(sum, count) + let sum_final = alloc(); + let count_final = alloc(); + let mut k_exit = JoinFunction::new(exit_id, "k_exit".to_string(), vec![sum_final, count_final]); + k_exit.body.push(JoinInst::Ret { + value: Some(sum_final), + }); + module.add_function(k_exit); + + module.entry = Some(main_id); + module.phase = crate::mir::join_ir::JoinIrPhase::Structured; + module +} + +/// Phase 47-B: JsonParser if-sum mini を Structured で組み立てるヘルパー。 +/// +/// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_if_sum_min.program.json +pub fn build_pattern3_json_if_sum_min_structured_for_normalized_dev() -> JoinModule { + // 手書き JoinIR(i/sum の 2 キャリア、JsonParser 由来の簡約 if-sum) + let mut module = JoinModule::new(); + let mut next_id = 0u32; + let mut alloc = || { + let id = ValueId(next_id); + next_id += 1; + id + }; + + let main_id = JoinFuncId::new(0); + let loop_id = JoinFuncId::new(1); + let exit_id = JoinFuncId::new(2); + + // main: init i/sum = 0 → tail call loop_step + let i0 = alloc(); + let sum0 = alloc(); + let mut main = JoinFunction::new(main_id, "main".to_string(), vec![]); + main.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: i0, + value: ConstValue::Integer(0), + })); + main.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: sum0, + value: ConstValue::Integer(0), + })); + main.body.push(JoinInst::Call { + func: loop_id, + args: vec![i0, sum0], + k_next: None, + dst: None, + }); + module.add_function(main); + + // loop_step params: i, sum + let i_param = alloc(); + let sum_param = alloc(); + let mut loop_step = JoinFunction::new(loop_id, "loop_step".to_string(), vec![i_param, sum_param]); + + // loop condition: i < 5 + let limit_const = alloc(); + let cmp_loop = alloc(); + let exit_cond = alloc(); + loop_step + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: limit_const, + value: ConstValue::Integer(5), + })); + compare( + &mut loop_step, + cmp_loop, + CompareOp::Lt, + i_param, + limit_const, + ); + unary_not(&mut loop_step, exit_cond, cmp_loop); + loop_step.body.push(JoinInst::Jump { + cont: exit_id.as_cont(), + args: vec![sum_param], + cond: Some(exit_cond), + }); + + // if condition: i > 0 + let cond_cmp = alloc(); + let zero_const = alloc(); + const_i64(&mut loop_step, zero_const, 0); + compare( + &mut loop_step, + cond_cmp, + CompareOp::Gt, + i_param, + zero_const, + ); + + // then: sum = sum + i + let sum_then = alloc(); + bin_add(&mut loop_step, sum_then, sum_param, i_param); + + // else: identity sum + let sum_else = alloc(); + bin_add(&mut loop_step, sum_else, sum_param, zero_const); + + // select + let sum_new = alloc(); + select(&mut loop_step, sum_new, cond_cmp, sum_then, sum_else); + + // counter update: i = i + 1 + let one_const = alloc(); + let i_next = alloc(); + const_i64(&mut loop_step, one_const, 1); + bin_add(&mut loop_step, i_next, i_param, one_const); + + loop_step.body.push(JoinInst::Call { + func: loop_id, + args: vec![i_next, sum_new], + k_next: None, + dst: None, + }); + module.add_function(loop_step); + + // k_exit(sum) + let sum_final = alloc(); + let mut k_exit = JoinFunction::new(exit_id, "k_exit".to_string(), vec![sum_final]); + k_exit.body.push(JoinInst::Ret { + value: Some(sum_final), + }); + module.add_function(k_exit); + + module.entry = Some(main_id); + module.phase = crate::mir::join_ir::JoinIrPhase::Structured; + module +} + /// JsonParser _atoi 相当のミニ P2 ループを Structured で組み立てるヘルパー。 /// /// Fixture: docs/private/roadmap2/phases/normalized_dev/fixtures/jsonparser_atoi_mini.program.json @@ -333,6 +612,8 @@ pub mod prelude { build_jsonparser_skip_ws_structured_for_normalized_dev, build_pattern2_break_fixture_structured, build_pattern2_minimal_structured, build_pattern3_if_sum_min_structured_for_normalized_dev, + build_pattern3_if_sum_multi_min_structured_for_normalized_dev, + build_pattern3_json_if_sum_min_structured_for_normalized_dev, build_pattern4_continue_min_structured_for_normalized_dev, }; } diff --git a/src/mir/join_ir/normalized/shape_guard.rs b/src/mir/join_ir/normalized/shape_guard.rs index 53816c72..c0f90526 100644 --- a/src/mir/join_ir/normalized/shape_guard.rs +++ b/src/mir/join_ir/normalized/shape_guard.rs @@ -19,6 +19,9 @@ pub enum ShapeCapabilityKind { /// P2 Mid: _parse_number real (p + num_str + result) P2MidParseNumber, + /// P3 If-Sum family (minimal/multi/json) + P3IfSum, + // Future: Other P2 patterns // P2MidAtOfLoop, // P2HeavyString, @@ -52,6 +55,9 @@ pub enum NormalizedDevShape { JsonparserParseNumberReal, // Phase 47-A: Pattern3 (if-sum) minimal Pattern3IfSumMinimal, + // Phase 47-B: Pattern3 extended (multi/json) + Pattern3IfSumMulti, + Pattern3IfSumJson, // Phase 48-A: Pattern4 (continue) minimal Pattern4ContinueMinimal, } @@ -86,6 +92,14 @@ const SHAPE_DETECTORS: &[(NormalizedDevShape, Detector)] = &[ NormalizedDevShape::Pattern3IfSumMinimal, detectors::is_pattern3_if_sum_minimal, ), + ( + NormalizedDevShape::Pattern3IfSumMulti, + detectors::is_pattern3_if_sum_multi, + ), + ( + NormalizedDevShape::Pattern3IfSumJson, + detectors::is_pattern3_if_sum_json, + ), // Phase 48-A: Pattern4 continue minimal ( NormalizedDevShape::Pattern4ContinueMinimal, @@ -118,8 +132,8 @@ pub fn capability_for_shape(shape: &NormalizedDevShape) -> ShapeCapability { JsonparserAtoiMini | JsonparserAtoiReal => P2CoreAtoi, JsonparserParseNumberReal => P2MidParseNumber, Pattern1Mini => P2CoreSimple, // Also core simple pattern - // Phase 47-A: P3 minimal maps to P2CoreSimple for now (future: P3CoreSimple) - Pattern3IfSumMinimal => P2CoreSimple, + // Phase 47-B: P3 if-sum family + Pattern3IfSumMinimal | Pattern3IfSumMulti | Pattern3IfSumJson => P3IfSum, // Phase 48-A: P4 minimal maps to P2CoreSimple for now (future: P4CoreSimple) Pattern4ContinueMinimal => P2CoreSimple, }; @@ -146,6 +160,10 @@ pub fn is_canonical_shape(shape: &NormalizedDevShape) -> bool { // Phase 46: Add P2-Mid patterns | JsonparserAtoiReal | JsonparserParseNumberReal + // Phase 47-C: P3 if-sum canonical set + | Pattern3IfSumMinimal + | Pattern3IfSumMulti + | Pattern3IfSumJson ) } @@ -155,7 +173,10 @@ pub fn is_canonical_shape(shape: &NormalizedDevShape) -> bool { /// Use `is_canonical_shape()` for exact canonical filtering. pub fn is_p2_core_capability(cap: &ShapeCapability) -> bool { use ShapeCapabilityKind::*; - matches!(cap.kind, P2CoreSimple | P2CoreSkipWs | P2CoreAtoi | P2MidParseNumber) + matches!( + cap.kind, + P2CoreSimple | P2CoreSkipWs | P2CoreAtoi | P2MidParseNumber | P3IfSum + ) } /// Phase 44: Check if capability is supported by Normalized dev @@ -398,12 +419,34 @@ mod detectors { .iter() .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); - // P3 minimal has 2-4 params (i, sum, possibly n) - let reasonable_param_count = (2..=4).contains(&loop_step.params.len()); + // P3 minimal/multi/json: typically 2-6 params (i + carriers + len/host) + let reasonable_param_count = (2..=6).contains(&loop_step.params.len()); has_compare && has_select && has_tail_call && reasonable_param_count } + /// Phase 47-B: P3 if-sum (multi-carrier) shape detector + pub(crate) fn is_pattern3_if_sum_multi(module: &JoinModule) -> bool { + if !is_pattern3_if_sum_minimal(module) { + return false; + } + module + .functions + .values() + .any(|f| f.name == "pattern3_if_sum_multi_min") + } + + /// Phase 47-B: P3 if-sum (JsonParser mini) shape detector + pub(crate) fn is_pattern3_if_sum_json(module: &JoinModule) -> bool { + if !is_pattern3_if_sum_minimal(module) { + return false; + } + module + .functions + .values() + .any(|f| f.name == "jsonparser_if_sum_min") + } + /// Phase 48-A: Check if module matches Pattern4 continue minimal shape pub(crate) fn is_pattern4_continue_minimal(module: &JoinModule) -> bool { // Structure-based detection (avoid name-based heuristics) diff --git a/src/mir/join_ir_vm_bridge/bridge.rs b/src/mir/join_ir_vm_bridge/bridge.rs index 62796c7b..f84f7960 100644 --- a/src/mir/join_ir_vm_bridge/bridge.rs +++ b/src/mir/join_ir_vm_bridge/bridge.rs @@ -77,6 +77,15 @@ fn normalize_for_shape( crate::mir::join_ir::normalized::normalize_pattern3_if_sum_minimal(module) .expect("P3 normalization failed") })), + // Phase 47-B: P3 extended normalization + NormalizedDevShape::Pattern3IfSumMulti => catch_unwind(AssertUnwindSafe(|| { + crate::mir::join_ir::normalized::normalize_pattern3_if_sum_multi_minimal(module) + .expect("P3 multi normalization failed") + })), + NormalizedDevShape::Pattern3IfSumJson => catch_unwind(AssertUnwindSafe(|| { + crate::mir::join_ir::normalized::normalize_pattern3_if_sum_json_minimal(module) + .expect("P3 json normalization failed") + })), // Phase 48-A: P4 minimal normalization NormalizedDevShape::Pattern4ContinueMinimal => catch_unwind(AssertUnwindSafe(|| { crate::mir::join_ir::normalized::normalize_pattern4_continue_minimal(module) @@ -186,9 +195,9 @@ pub(crate) fn bridge_joinir_to_mir_with_meta( { let mode = current_joinir_mode(); - // Phase 46: Canonical P2-Core + P2-Mid shapes always use Normalized→MIR(direct) - // Canonical set: Pattern2Mini, skip_ws mini/real, atoi mini/real, parse_number real - // P3/P4 patterns are NOT canonical (deferred to NORM-P3/NORM-P4) + // Phase 47-C: Canonical set (P2-Core + P2-Mid + P3 if-sum) always uses Normalized→MIR(direct) + // Canonical set: Pattern2Mini, skip_ws mini/real, atoi mini/real, parse_number real, + // P3 if-sum minimal/multi/json let canonical_shapes = shape_guard::canonical_shapes(module); if !canonical_shapes.is_empty() { match try_normalized_direct_bridge(module, meta, &canonical_shapes, false, false)? { diff --git a/src/mir/loop_pattern_detection/function_scope_capture/mod.rs b/src/mir/loop_pattern_detection/function_scope_capture/mod.rs index d78a9a13..20809230 100644 --- a/src/mir/loop_pattern_detection/function_scope_capture/mod.rs +++ b/src/mir/loop_pattern_detection/function_scope_capture/mod.rs @@ -53,5 +53,5 @@ mod helpers; mod types; // Public re-exports -pub(crate) use analyzers::{analyze_captured_vars, analyze_captured_vars_v2}; +pub(crate) use analyzers::analyze_captured_vars_v2; pub use types::{CapturedEnv, CapturedVar}; diff --git a/tests/normalized_joinir_min.rs b/tests/normalized_joinir_min.rs index 994b8288..bfb12862 100644 --- a/tests/normalized_joinir_min.rs +++ b/tests/normalized_joinir_min.rs @@ -17,6 +17,8 @@ use nyash_rust::mir::join_ir::normalized::fixtures::{ build_jsonparser_skip_ws_structured_for_normalized_dev, build_pattern2_break_fixture_structured, build_pattern2_minimal_structured, build_pattern3_if_sum_min_structured_for_normalized_dev, + build_pattern3_if_sum_multi_min_structured_for_normalized_dev, + build_pattern3_json_if_sum_min_structured_for_normalized_dev, build_pattern4_continue_min_structured_for_normalized_dev, }; use nyash_rust::mir::join_ir_runner::run_joinir_function; @@ -530,6 +532,42 @@ fn normalized_pattern3_if_sum_minimal_runner_dev_switch_matches_structured() { ); } +#[test] +fn normalized_pattern3_if_sum_multi_vm_bridge_direct_matches_structured() { + let _ctx = normalized_dev_test_ctx(); + let structured = build_pattern3_if_sum_multi_min_structured_for_normalized_dev(); + let entry = structured.entry.expect("structured entry required"); + let input = [JoinValue::Int(0)]; + + 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 P3 if-sum multi"); + assert_eq!( + dev, + JoinValue::Int(2), + "unexpected result for P3 if-sum multi (expected sum=2)" + ); +} + +#[test] +fn normalized_pattern3_json_if_sum_min_vm_bridge_direct_matches_structured() { + let _ctx = normalized_dev_test_ctx(); + let structured = build_pattern3_json_if_sum_min_structured_for_normalized_dev(); + let entry = structured.entry.expect("structured entry required"); + let input = [JoinValue::Int(0)]; + + 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 P3 json if-sum"); + assert_eq!( + dev, + JoinValue::Int(10), + "unexpected result for P3 json if-sum (expected sum=10)" + ); +} + #[cfg(feature = "normalized_dev")] #[test] fn test_phase46_canonical_set_includes_p2_mid() { @@ -542,6 +580,15 @@ fn test_phase46_canonical_set_includes_p2_mid() { assert!(is_canonical_shape( &NormalizedDevShape::JsonparserParseNumberReal )); + assert!(is_canonical_shape( + &NormalizedDevShape::Pattern3IfSumMinimal + )); + assert!(is_canonical_shape( + &NormalizedDevShape::Pattern3IfSumMulti + )); + assert!(is_canonical_shape( + &NormalizedDevShape::Pattern3IfSumJson + )); // Verify P2-Core patterns still canonical assert!(is_canonical_shape(&NormalizedDevShape::Pattern2Mini));