diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index cd9bbd24..d972907b 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -24,6 +24,9 @@ - LLVM Python: PHI を落とす/上書きする経路を除去(PHI SSOT を保護) - JoinIR merge: exit PHI dst の allocator を function-level に統一(ValueId 衝突を排除) - debug-only: Exit PHI collision を早期検出する verifier を追加(LLVM 実行時に壊れる前に Fail-Fast) +- **Phase 133 完了**: Promoted carrier の `join_id` 解決(Trim)を SSOT に寄せて根治(smoke は compile-only)。 +- **Phase 134 完了**: Plugin loader best-effort loading(決定的順序 + failure 集約 + 継続)を導入。 +- **Phase 135 実装**: ConditionLoweringBox が allocator SSOT を無視して ValueId 衝突を起こす問題を根治(検証はローカルで実施)。 - **Phase 88 完了**: continue + 可変ステップ(i=i+const 差分)を dev-only fixture で固定、StepCalculator Box 抽出。 - **Phase 89 完了**: P0(ContinueReturn detector)+ P1(lowering 実装)完了。 - **Phase 90 完了**: ParseStringComposite + `Null` literal + ContinueReturn(同一値の複数 return-if)を dev-only fixture で固定。 @@ -48,7 +51,21 @@ ## 次の指示書(優先順位) -### P0: Docs 整備(数の増殖を止める) +### P0: Phase 135 検証(Trim fixture の `--verify` を緑に固定) + +目的: +- `apps/tests/phase133_json_skip_whitespace_min.hako` で発生していた MIR SSA 破綻(ValueId 重複)を後戻りしない形で固定する。 + +やること: +1. `./target/release/hakorune --verify apps/tests/phase133_json_skip_whitespace_min.hako` が PASS することを確認 +2. 必要なら integration smoke を追加(quick は増やさない) +3. Phase 文書を更新: `docs/development/current/main/phases/phase-135/README.md` + +受け入れ基準: +- `--verify` が PASS +- 既存の Phase 132/133/134 の integration smoke が退行しない + +### P1: Docs 整備(数の増殖を止める) **目的**: SSOT への集約と導線整備(Phase 86–90 の情報が散らばらない状態にする) diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index db0484e1..008717ba 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -18,6 +18,13 @@ **追加(Phase 132-P3): Exit PHI collision の早期検出(debug-only)** - `verify_exit_phi_no_collision()` を `contract_checks.rs` に追加し、ValueId 衝突を JoinIR merge の段階で Fail-Fast する +## 2025‑12‑15:Phase 133–135(短報) + +- Phase 133: promoted carrier(Trim)の `join_id` 解決を SSOT に寄せて修正(smoke は compile-only)。 +- Phase 134: plugin loader best-effort loading を導入(決定的順序 + failure 集約 + 継続)。 +- Phase 135: ConditionLoweringBox が allocator SSOT を無視して ValueId 衝突を起こす問題を根治。 + - 詳細: `docs/development/current/main/phases/phase-135/README.md` + ## 2025‑12‑14:現状サマリ (補足)docs が増えて迷子になったときの「置き場所ルール(SSOT)」: diff --git a/docs/development/current/main/phases/README.md b/docs/development/current/main/phases/README.md index eb4f18a5..73b384b2 100644 --- a/docs/development/current/main/phases/README.md +++ b/docs/development/current/main/phases/README.md @@ -5,8 +5,9 @@ ## 現在の Phase - **Phase 132**: Exit Values Parity (VM == LLVM) -- **Phase 131**: LLVM Lowering & InfiniteEarlyExit パターン実装 🚀 -- **Phase 33**: Box Theory Modularization +- **Phase 133**: Promoted carrier join_id(Trim)修正 +- **Phase 134**: Plugin loader best-effort loading +- **Phase 135**: ConditionLoweringBox allocator SSOT(ValueId 衝突の根治) ## Phase フォルダ構成(推奨) diff --git a/docs/development/current/main/phases/phase-135/README.md b/docs/development/current/main/phases/phase-135/README.md new file mode 100644 index 00000000..887dee54 --- /dev/null +++ b/docs/development/current/main/phases/phase-135/README.md @@ -0,0 +1,27 @@ +# Phase 135: ConditionLoweringBox allocator SSOT(ValueId 衝突の根治) + +## Status +- 状態: ✅ 実装完了(動作確認はローカルで実施) +- スコープ: JoinIR の条件 lowering が `JoinValueSpace` と同一の allocator(SSOT)を使うことを保証する + +## Problem +`apps/tests/phase133_json_skip_whitespace_min.hako` などで `--verify` が失敗し、MIR に以下の SSA 破綻が出ることがあった: +- `Value %13/%14 defined multiple times`(ループ header PHI dst が後続命令で上書きされる) +- `Value %18 defined multiple times`(同一 JoinIR ValueId への alias binding が Copy を重複注入する) + +## Root Cause +1. `ExprLowerer` が `ConditionLoweringBox` 実装で `ConditionContext.alloc_value` を無視し、内部カウンタで ValueId を発行していた。 + → JoinIR 内で `main()` params(例: `ValueId(1000), ValueId(1001)`)と衝突し、merge の remap で header PHI dst に書き込む命令が生成される。 +2. `JoinInlineBoundary` の `condition_bindings` に同一 `join_value` が複数名で登録される場合があり、entry block への Copy 注入が同じ `dst` に重複する。 + → MIR SSA を破壊する(`copy dst` が 2 回発生)。 + +## Fix +- `ConditionLoweringBox` は `ConditionContext.alloc_value`(SSOT allocator)を必ず使う。 + - `ConditionLoweringBox::lower_condition` は `&mut ConditionContext` を受け取る(allocator の正当な可変借用のため)。 + - `condition_lowerer::lower_condition_to_joinir` は `&mut dyn FnMut() -> ValueId` を受理する。 +- `BoundaryInjector` は `condition_bindings` 注入を `dst` で重複排除し、異なる source が同一 dst に来る場合は Fail-Fast。 + +## Acceptance +- `./target/release/hakorune --verify apps/tests/phase133_json_skip_whitespace_min.hako` が PASS +- `./target/release/hakorune --dump-mir apps/tests/phase133_json_skip_whitespace_min.hako` のループ header で PHI dst の再定義がない + diff --git a/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs b/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs index a3a5e3f8..566ceddd 100644 --- a/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs +++ b/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs @@ -363,7 +363,8 @@ impl TrimLoopLowerer { use crate::mir::instruction::MirInstruction; use crate::mir::types::BinaryOp; let one = emit_integer(builder, 1); - let start_plus_1 = builder.value_gen.next(); + // Phase 135 P0: Use function-level ValueId (SSOT) + let start_plus_1 = builder.next_value_id(); builder.emit_instruction(MirInstruction::BinOp { dst: start_plus_1, op: BinaryOp::Add, @@ -372,7 +373,8 @@ impl TrimLoopLowerer { })?; // Generate: ch0 = s.substring(start, start+1) - let ch0 = builder.value_gen.next(); + // Phase 135 P0: Use function-level ValueId (SSOT) + let ch0 = builder.next_value_id(); builder.emit_method_call( Some(ch0), s_id, diff --git a/src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs b/src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs index a2d2cad0..490b3a39 100644 --- a/src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs +++ b/src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs @@ -47,12 +47,14 @@ impl TrimPatternValidator { let ws_const = emit_string(builder, ws_char.clone()); // eq_check = ch == ws_const - let eq_dst = builder.value_gen.next(); + // Phase 135 P0: Use function-level ValueId (SSOT) + let eq_dst = builder.next_value_id(); emit_eq_to(builder, eq_dst, ch_value, ws_const)?; result_opt = Some(if let Some(prev_result) = result_opt { // result = prev_result || eq_check - let dst = builder.value_gen.next(); + // Phase 135 P0: Use function-level ValueId (SSOT) + let dst = builder.next_value_id(); builder.emit_instruction(MirInstruction::BinOp { dst, op: BinaryOp::Or, diff --git a/src/mir/builder/joinir_inline_boundary_injector.rs b/src/mir/builder/joinir_inline_boundary_injector.rs index 215cb867..511816f4 100644 --- a/src/mir/builder/joinir_inline_boundary_injector.rs +++ b/src/mir/builder/joinir_inline_boundary_injector.rs @@ -178,6 +178,7 @@ impl BoundaryInjector { // We inject Copy: remapped_join_value = Copy host_value // // Phase 177-3 Option B: Use pre-allocated reallocations for PHI collision cases + let mut seen_dst_to_src: BTreeMap = BTreeMap::new(); for binding in &boundary.condition_bindings { // Look up the remapped JoinIR ValueId from value_map let remapped_join = value_map @@ -191,6 +192,23 @@ impl BoundaryInjector { .copied() .unwrap_or(remapped_join); + // Phase 135-P0: Deduplicate condition bindings that alias the same JoinIR ValueId. + // + // Promoters may produce multiple names that map to the same JoinIR ValueId + // (e.g. 'char' and 'is_char_match' sharing a promoted carrier slot). + // Emitting multiple `Copy` instructions to the same `dst` violates MIR SSA and + // fails `--verify` (ValueId defined multiple times). + if let Some(prev_src) = seen_dst_to_src.get(&final_dst).copied() { + if prev_src != binding.host_value { + return Err(format!( + "[BoundaryInjector] condition_bindings conflict: dst {:?} would be assigned from two different sources: {:?} and {:?} (JoinIR {:?}, name '{}')", + final_dst, prev_src, binding.host_value, binding.join_value, binding.name + )); + } + continue; + } + seen_dst_to_src.insert(final_dst, binding.host_value); + // Copy instruction: final_dst = Copy host_value let copy_inst = MirInstruction::Copy { dst: final_dst, diff --git a/src/mir/builder/stmts.rs b/src/mir/builder/stmts.rs index 622af497..471b2dcd 100644 --- a/src/mir/builder/stmts.rs +++ b/src/mir/builder/stmts.rs @@ -346,7 +346,8 @@ impl super::MirBuilder { } last_value = Some(var_id); } - Ok(last_value.unwrap_or_else(|| self.value_gen.next())) + // Phase 135 P0: Use function-level ValueId (SSOT) - build_local_statement is always in function context + Ok(last_value.unwrap_or_else(|| self.next_value_id())) } // Return statement diff --git a/src/mir/join_ir/lowering/condition_lowerer.rs b/src/mir/join_ir/lowering/condition_lowerer.rs index fb4e81c3..2a407773 100644 --- a/src/mir/join_ir/lowering/condition_lowerer.rs +++ b/src/mir/join_ir/lowering/condition_lowerer.rs @@ -59,14 +59,11 @@ use super::method_call_lowerer::MethodCallLowerer; /// &env, /// )?; /// ``` -pub fn lower_condition_to_joinir( +pub fn lower_condition_to_joinir( cond_ast: &ASTNode, - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, -) -> Result<(ValueId, Vec), String> -where - F: FnMut() -> ValueId, -{ +) -> Result<(ValueId, Vec), String> { let mut instructions = Vec::new(); let result_value = lower_condition_recursive(cond_ast, alloc_value, env, &mut instructions)?; Ok((result_value, instructions)) @@ -75,15 +72,12 @@ where /// Recursive helper for condition lowering /// /// Handles all supported AST node types and emits appropriate JoinIR instructions. -fn lower_condition_recursive( +fn lower_condition_recursive( cond_ast: &ASTNode, - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, instructions: &mut Vec, -) -> Result -where - F: FnMut() -> ValueId, -{ +) -> Result { match cond_ast { // Comparison operations: <, ==, !=, <=, >=, > ASTNode::BinaryOp { @@ -128,16 +122,14 @@ where } /// Lower a comparison operation (e.g., `i < end`) -fn lower_comparison( +fn lower_comparison( operator: &BinaryOperator, left: &ASTNode, right: &ASTNode, - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, instructions: &mut Vec, ) -> Result -where - F: FnMut() -> ValueId, { // Lower left and right sides let lhs = lower_value_expression(left, alloc_value, env, instructions)?; @@ -166,15 +158,13 @@ where } /// Lower logical AND operation (e.g., `a && b`) -fn lower_logical_and( +fn lower_logical_and( left: &ASTNode, right: &ASTNode, - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, instructions: &mut Vec, ) -> Result -where - F: FnMut() -> ValueId, { // Logical AND: evaluate both sides and combine let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?; @@ -193,15 +183,13 @@ where } /// Lower logical OR operation (e.g., `a || b`) -fn lower_logical_or( +fn lower_logical_or( left: &ASTNode, right: &ASTNode, - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, instructions: &mut Vec, ) -> Result -where - F: FnMut() -> ValueId, { // Logical OR: evaluate both sides and combine let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?; @@ -220,14 +208,12 @@ where } /// Lower NOT operator (e.g., `!cond`) -fn lower_not_operator( +fn lower_not_operator( operand: &ASTNode, - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, instructions: &mut Vec, ) -> Result -where - F: FnMut() -> ValueId, { let operand_val = lower_condition_recursive(operand, alloc_value, env, instructions)?; let dst = alloc_value(); @@ -243,13 +229,11 @@ where } /// Lower a literal value (e.g., `10`, `true`, `"text"`) -fn lower_literal( +fn lower_literal( value: &LiteralValue, - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, instructions: &mut Vec, ) -> Result -where - F: FnMut() -> ValueId, { let dst = alloc_value(); let const_value = match value { @@ -279,15 +263,12 @@ where /// /// This handles the common case where we need to evaluate a simple value /// (variable or literal) as part of a comparison. -pub fn lower_value_expression( +pub fn lower_value_expression( expr: &ASTNode, - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, instructions: &mut Vec, -) -> Result -where - F: FnMut() -> ValueId, -{ +) -> Result { match expr { // Variables - look up in ConditionEnv ASTNode::Variable { name, .. } => env @@ -334,16 +315,14 @@ where } /// Lower an arithmetic binary operation (e.g., `i + 1`) -fn lower_arithmetic_binop( +fn lower_arithmetic_binop( operator: &BinaryOperator, left: &ASTNode, right: &ASTNode, - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, instructions: &mut Vec, ) -> Result -where - F: FnMut() -> ValueId, { let lhs = lower_value_expression(left, alloc_value, env, instructions)?; let rhs = lower_value_expression(right, alloc_value, env, instructions)?; diff --git a/src/mir/join_ir/lowering/condition_lowering_box.rs b/src/mir/join_ir/lowering/condition_lowering_box.rs index a66cb0e6..caf11cb2 100644 --- a/src/mir/join_ir/lowering/condition_lowering_box.rs +++ b/src/mir/join_ir/lowering/condition_lowering_box.rs @@ -125,7 +125,7 @@ pub trait ConditionLoweringBox { fn lower_condition( &mut self, condition: &ASTNode, - context: &ConditionContext, + context: &mut ConditionContext, ) -> Result; /// Check if this lowerer supports the given condition pattern @@ -225,7 +225,7 @@ mod tests { id }; - let context = ConditionContext { + let mut context = ConditionContext { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), scope: &scope, @@ -245,7 +245,7 @@ mod tests { ); // Lower condition via ConditionLoweringBox trait (Step 2 implemented) - let result = expr_lowerer.lower_condition(&ast, &context); + let result = expr_lowerer.lower_condition(&ast, &mut context); assert!(result.is_ok(), "i < 10 should lower successfully via trait"); } @@ -280,7 +280,7 @@ mod tests { id }; - let context = ConditionContext { + let mut context = ConditionContext { loop_var_name: "i".to_string(), loop_var_id: ValueId(1), scope: &scope, diff --git a/src/mir/join_ir/lowering/expr_lowerer.rs b/src/mir/join_ir/lowering/expr_lowerer.rs index 4b98ea51..b60cd612 100644 --- a/src/mir/join_ir/lowering/expr_lowerer.rs +++ b/src/mir/join_ir/lowering/expr_lowerer.rs @@ -265,11 +265,43 @@ impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox for ExprLowerer<'e fn lower_condition( &mut self, condition: &ASTNode, - _context: &ConditionContext, + context: &mut ConditionContext, ) -> Result { - // Delegate to existing lower() method - // ConditionContext is unused because ExprLowerer already has scope access - self.lower(condition).map_err(|e| e.to_string()) + // Phase 244+ / Phase 201 SSOT: ValueId allocation must be coordinated by the caller. + // + // JoinIR lowering uses JoinValueSpace as SSOT for ValueId regions. + // If we allocate locally here (e.g. starting from 1000), we can collide with + // other JoinIR value users (main params, carrier slots), and after remapping + // this becomes a MIR-level ValueId collision. + if !ast_support::is_supported_condition(condition) { + return Err(format!("Unsupported condition node: {:?}", condition)); + } + + // Build ConditionEnv from the provided scope (the caller controls the scope + allocator). + #[cfg(feature = "normalized_dev")] + let condition_env = + scope_resolution::build_condition_env_from_scope_with_binding(context.scope, condition, self.builder) + .map_err(|e| e.to_string())?; + + #[cfg(not(feature = "normalized_dev"))] + let condition_env = scope_resolution::build_condition_env_from_scope(context.scope, condition) + .map_err(|e| e.to_string())?; + + // Delegate to the well-tested lowerer, but use the caller-provided allocator (SSOT). + let (result_value, instructions) = + lower_condition_to_joinir(condition, &mut *context.alloc_value, &condition_env) + .map_err(|e| e.to_string())?; + + self.last_instructions = instructions; + + if self.debug { + eprintln!( + "[expr_lowerer/phase244] Lowered condition → ValueId({:?}) (context alloc)", + result_value + ); + } + + Ok(result_value) } /// Phase 244: Check if ExprLowerer supports a given condition pattern diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs b/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs index f4fb6444..4c179367 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs @@ -51,14 +51,14 @@ pub(crate) fn lower_header_condition( let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder); - let context = ConditionContext { + let mut context = ConditionContext { loop_var_name: loop_var_name.to_string(), loop_var_id, scope: &scope_manager, alloc_value, }; - match expr_lowerer.lower_condition(condition, &context) { + match expr_lowerer.lower_condition(condition, &mut context) { Ok(value_id) => { let instructions = expr_lowerer.take_last_instructions(); eprintln!( @@ -105,7 +105,7 @@ pub(crate) fn lower_break_condition( let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder); - let context = ConditionContext { + let mut context = ConditionContext { loop_var_name: loop_var_name.to_string(), loop_var_id, scope: &scope_manager, @@ -113,7 +113,7 @@ pub(crate) fn lower_break_condition( }; let value_id = expr_lowerer - .lower_condition(break_condition, &context) + .lower_condition(break_condition, &mut context) .map_err(|e| { format!( "[joinir/pattern2/phase244] ConditionLoweringBox failed to lower break condition: {}", diff --git a/src/mir/join_ir/lowering/loop_with_continue_minimal.rs b/src/mir/join_ir/lowering/loop_with_continue_minimal.rs index 27146778..a0ad16fe 100644 --- a/src/mir/join_ir/lowering/loop_with_continue_minimal.rs +++ b/src/mir/join_ir/lowering/loop_with_continue_minimal.rs @@ -259,14 +259,14 @@ pub(crate) fn lower_loop_with_continue_minimal( let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder); - let context = ConditionContext { + let mut context = ConditionContext { loop_var_name: loop_var_name.clone(), loop_var_id: i_param, scope: &scope_manager, alloc_value: &mut alloc_value, }; - match expr_lowerer.lower_condition(condition, &context) { + match expr_lowerer.lower_condition(condition, &mut context) { Ok(value_id) => { let instructions = expr_lowerer.take_last_instructions(); eprintln!("[joinir/pattern4/phase244] Header condition via ConditionLoweringBox: {} instructions", instructions.len()); diff --git a/src/mir/join_ir/lowering/method_call_lowerer.rs b/src/mir/join_ir/lowering/method_call_lowerer.rs index 536c061d..26f9f66c 100644 --- a/src/mir/join_ir/lowering/method_call_lowerer.rs +++ b/src/mir/join_ir/lowering/method_call_lowerer.rs @@ -81,17 +81,14 @@ impl MethodCallLowerer { /// &mut instructions, /// )?; /// ``` - pub fn lower_for_condition( + pub fn lower_for_condition( recv_val: ValueId, method_name: &str, args: &[ASTNode], - alloc_value: &mut F, + alloc_value: &mut dyn FnMut() -> ValueId, env: &ConditionEnv, instructions: &mut Vec, - ) -> Result - where - F: FnMut() -> ValueId, - { + ) -> Result { // Resolve method name to CoreMethodId // Note: We don't know receiver type at this point, so we try all methods let method_id = CoreMethodId::iter() diff --git a/tools/smokes/v2/profiles/integration/apps/phase135_trim_mir_verify.sh b/tools/smokes/v2/profiles/integration/apps/phase135_trim_mir_verify.sh new file mode 100644 index 00000000..05186fd9 --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase135_trim_mir_verify.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Phase 135: MIR verify regression for Trim(A-3) + Pattern2 lowering +# Purpose: prevent ValueId/SSA corruption introduced by allocator mismatch or duplicated boundary copies. +# +# Expected: `--verify` PASS for the Phase 133 Trim-derived fixture. + +source "$(dirname "$0")/../../../lib/test_runner.sh" +export SMOKES_USE_PYVM=0 +require_env || exit 2 + +INPUT="$NYASH_ROOT/apps/tests/phase133_json_skip_whitespace_min.hako" + +echo "[INFO] Phase 135: --verify (phase133_json_skip_whitespace_min.hako)" +OUT="$(timeout "${RUN_TIMEOUT_SECS:-60}" "$NYASH_BIN" --verify "$INPUT" 2>&1)" +RC=$? + +if [ "$RC" -ne 0 ]; then + echo "[FAIL] verify: hakorune --verify failed (rc=$RC)" + echo "[INFO] output (tail):" + echo "$OUT" | tail -n 120 || true + exit 1 +fi + +if echo "$OUT" | grep -q "MIR verification failed"; then + echo "[FAIL] verify: MIR verification failed" + echo "[INFO] output (tail):" + echo "$OUT" | tail -n 120 || true + exit 1 +fi + +echo "[PASS] verify: MIR is valid (SSA/ValueId OK)" +exit 0 +