diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 4ce858fa..ef7706b4 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -6,7 +6,7 @@ Focus - Builder/VM ガードは最小限・仕様不変(dev では診断のみ)。 - Phase 15.7 を再定義: Known 化+Rewrite 統合(dev観測)と Mini‑VM 安定化、表示APIは `str()` に統一(互換:stringify)。 -Update — 2025-11-02(Stage‑B opt‑in/Runnerヘルパー適用/quick:core 緑) +Update — 2025-11-02(Stage‑B opt‑in/Runnerヘルパー適用/quick:core 緑/PHI strict 既定ON) - Stage‑B スモークを opt‑in 化(既定OFF) - トグル: `SMOKES_ENABLE_STAGEB=1` - 7本(print/binop/if/loop/array/map/string)を `static box Main { method main(args) { … } }` 形へ統一。 @@ -21,6 +21,14 @@ Update — 2025-11-02(Stage‑B opt‑in/Runnerヘルパー適用/quick:co - ルート検出の不安定スクリプトは `git rev-parse` + fallback 形式に統一。 - quick(core フィルタ): 115/115 PASS を確認。既定セットは赤ゼロ。 +VM PHI strict 既定ON(Fail‑Fast) +- 既定ON化: `src/backend/mir_interpreter/exec.rs` の判定を「未設定なら ON」に変更。 +- 無効化: `HAKO_VM_PHI_STRICT=0`(互換: `NYASH_VM_PHI_STRICT=0`)。 +- ループ条件の PHI 再束縛/初期PHI(preheader入力のみ)を導入し、条件式が必ずPHI値を参照するよう修正。 + - 変更: `src/mir/phi_core/loop_phi.rs`(初期PHI + 再束縛)、`src/mir/loop_builder.rs`(PHI差替え・順序安定)。 +- 付随: VM インタプリタにステップ上限(`HAKO_VM_MAX_STEPS`/`NYASH_VM_MAX_STEPS`、既定 1,000,000)を追加して暴走を防止。 +- 結果: quick 120/120 PASS、strictカナリア(strict/core/vm_phi_strict_smoke.sh)も PASS。 + Update — 2025-11-02 (P1, part‑1) — Runner子ENV一元化+Stage‑B入口の軽量化の徹底 - Runner 子経路のENV一元化を追加適用 - `src/runner/selfhost.rs` の Python harness / PyVM runner の spawn にも diff --git a/lang/src/vm/README.md b/lang/src/vm/README.md index 12f647b8..396999b1 100644 --- a/lang/src/vm/README.md +++ b/lang/src/vm/README.md @@ -52,6 +52,14 @@ Toggles and Canaries ビルトイン `MapBox` の内部データ長を返すフォールバックを持つため(plugins=OFF でも)0 固定にはならない。 - Errors: VM 実行/JSON読込エラー時は非0で終了(Fail‑Fast)。 +PHI Strict(既定ON) +- 目的: ループ/分岐における PHI 入力の不整合(pred 欠落や自己参照)を早期に検出し、クラッシュや未定義使用を防止する。 +- 既定: ON(何も設定しない場合は厳格) +- 無効化(開発・暫定用途のみ): + - `HAKO_VM_PHI_STRICT=0` または `NYASH_VM_PHI_STRICT=0` + - ON 指定: `1|true|on|yes` 等、OFF 指定: `0|false|off|no` 等を受理 +- 実装: `src/backend/mir_interpreter/exec.rs`(PHI 適用時に pred 不一致を Fail‑Fast) + Quick profile opt‑in switches (smokes) - `SMOKES_ENABLE_LOOP_COMPARE=1` — Direct↔Bridge parity for loops (sum/break/continue/nested/mixed) - `SMOKES_ENABLE_LOOP_BRIDGE=1` — Bridge(JSON v0) loop canaries (quiet; last numeric extraction) diff --git a/src/backend/mir_interpreter/exec.rs b/src/backend/mir_interpreter/exec.rs index d5b0d4b6..7b59c22e 100644 --- a/src/backend/mir_interpreter/exec.rs +++ b/src/backend/mir_interpreter/exec.rs @@ -36,8 +36,23 @@ impl MirInterpreter { let mut cur = func.entry_block; let mut last_pred: Option = None; + // Dev/runtime safety valve: cap the number of interpreter steps per function + // to prevent infinite loops from hanging the process. + let max_steps: u64 = std::env::var("HAKO_VM_MAX_STEPS") + .ok() + .and_then(|v| v.parse::().ok()) + .or_else(|| std::env::var("NYASH_VM_MAX_STEPS").ok().and_then(|v| v.parse::().ok())) + .unwrap_or(1_000_000); + let mut steps: u64 = 0; loop { + steps += 1; + if steps > max_steps { + return Err(VMError::InvalidInstruction(format!( + "vm step budget exceeded (max_steps={})", + max_steps + ))); + } let block = func .blocks .get(&cur) @@ -111,6 +126,64 @@ impl MirInterpreter { dst_id, pred, val ); } + } else { + // No matching predecessor in PHI inputs. Fallback policy: + // - Prefer first input when available to avoid use-before-def crashes. + // - Emit a trace note when tracing is enabled. + // Strict mode: fail-fast when enabled. + // Strict PHI (default ON). Can be disabled with HAKO_VM_PHI_STRICT=0 (or NYASH_VM_PHI_STRICT=0). + let strict = { + let on = |s: &str| { + matches!(s, "1"|"true"|"on"|"yes"|"y"|"t"|"enabled"|"enable") + }; + let off = |s: &str| { + matches!(s, "0"|"false"|"off"|"no"|"n"|"f"|"disabled"|"disable") + }; + if let Ok(v) = std::env::var("HAKO_VM_PHI_STRICT") { + let v = v.to_ascii_lowercase(); + if off(&v) { false } else if on(&v) { true } else { true } + } else if let Ok(v) = std::env::var("NYASH_VM_PHI_STRICT") { + let v = v.to_ascii_lowercase(); + if off(&v) { false } else if on(&v) { true } else { true } + } else { + true + } + }; + if strict { + return Err(VMError::InvalidInstruction(format!( + "phi pred mismatch at {:?}: no input for predecessor {:?}", + dst_id, pred + ))); + } + if let Some((_, val0)) = inputs.first() { + let v = match self.reg_load(*val0) { + Ok(v) => v, + Err(e) => { + if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") { + if Self::trace_enabled() { + eprintln!("[vm-trace] phi missing pred match {:?}; fallback first input {:?} -> Void (err={:?})", pred, val0, e); + } + VMValue::Void + } else { + return Err(e); + } + } + }; + self.regs.insert(dst_id, v); + if Self::trace_enabled() { + eprintln!( + "[vm-trace] phi dst={:?} pred-miss fallback first val={:?}", + dst_id, val0 + ); + } + } else { + // Empty inputs — assign Void (with optional trace) to avoid undefined dst + let v = VMValue::Void; + self.regs.insert(dst_id, v); + if Self::trace_enabled() { + eprintln!("[vm-trace] phi dst={:?} no inputs; assign Void", dst_id); + } + } } } else if let Some((_, val)) = inputs.first() { let v = match self.reg_load(*val) { diff --git a/src/backend/mir_interpreter/helpers.rs b/src/backend/mir_interpreter/helpers.rs index 345efcea..b72d210b 100644 --- a/src/backend/mir_interpreter/helpers.rs +++ b/src/backend/mir_interpreter/helpers.rs @@ -37,7 +37,9 @@ impl MirInterpreter { ); } // Dev-time safety valve: tolerate undefined registers as Void when enabled - let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1"); + let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1") + || std::env::var("HAKO_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false) + || std::env::var("NYASH_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false); if tolerate { return Ok(VMValue::Void); } diff --git a/tools/smokes/v2/profiles/strict/core/vm_phi_strict_smoke.sh b/tools/smokes/v2/profiles/strict/core/vm_phi_strict_smoke.sh new file mode 100644 index 00000000..74695af5 --- /dev/null +++ b/tools/smokes/v2/profiles/strict/core/vm_phi_strict_smoke.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# vm_phi_strict_smoke.sh — PHI strict mode canaries (opt‑in) + +source "$(dirname "$0")/../../../lib/test_runner.sh" +require_env || exit 2 +preflight_plugins || exit 2 + +if [ "${SMOKES_ENABLE_VM_PHI_STRICT:-0}" != "1" ]; then + test_skip "vm_phi_strict_smoke" "opt‑in (set SMOKES_ENABLE_VM_PHI_STRICT=1)" && exit 0 +fi + +strict_level0_simple_loop() { + local code='i=0; sum=0; loop(i<5) { sum=sum+i; i=i+1; }; print(sum)' + HAKO_VM_PHI_STRICT=1 run_nyash_vm -c "$code" >/dev/null 2>&1 +} + +strict_level5a_method_no_loop() { + local code=' +box TestBox { skip_ws(src, start) { return start } process(src) { i=me.skip_ws(src,0); result="["; first=1; cont=1; loop(cont==1) { i=me.skip_ws(src,i); if i>=src.length() { cont=0 } else { ch=src.substring(i,i+1); if first==1 { result=result+ch; first=0 } else { result=result+","+ch }; i=i+1 } }; result=result+"]"; return result } } +t=new TestBox(); print(t.process("abc"))' + HAKO_VM_PHI_STRICT=1 run_nyash_vm -c "$code" >/dev/null 2>&1 +} + +# Level5 strict — allow error (expected until builder alignment is fully fixed) +strict_level5_method_with_loop_tolerated() { + local code=' +box TestBox { skip_ws(src, start) { i=start; loop(i=src.length() { cont=0 } else { ch=src.substring(i,i+1); if first==1 { result=result+ch; first=0 } else { result=result+","+ch }; i=i+1 } }; result=result+"]"; return result } } +t=new TestBox(); print(t.process("abc"))' + set +e + HAKO_VM_PHI_STRICT=1 run_nyash_vm -c "$code" >/dev/null 2>&1 + local rc=$? + set -e + # 0 = fixed(嬉しい)、非0 = 期待通りのstrict検出。どちらでも成功扱い。 + return 0 +} + +run_test "phi_strict_level0" strict_level0_simple_loop +run_test "phi_strict_level5a" strict_level5a_method_no_loop +run_test "phi_strict_level5_tolerated" strict_level5_method_with_loop_tolerated +