diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 9309b559..1a83dbd3 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -17,12 +17,14 @@ - 制御構造と PHI の意味論は **JoinIR(+LoopScopeShape/IfPhiContext 等の薄い箱)** に一本化する。 - 実行の SSOT は VM / LLVM ラインとし、JoinIR→MIR→VM/LLVM は「構造 SSOT → 実行 SSOT」への変換として扱う。 - 既存の PHI 箱(if_phi.rs / PhiBuilderBox / conservative.rs / Trio 等)は、JoinIR 側のカバレッジが十分になったところから順に削っていく。 - - Stage-3 parser デフォルトON化(Phase 30.1 完了): `config::env::parser_stage3_enabled()` で NYASH_FEATURES=stage3 をSSOT化し、legacy env は明示OFF専用の互換に縮退。 +- Stage-3 parser デフォルトON化(Phase 30.1 完了): `config::env::parser_stage3_enabled()` で NYASH_FEATURES=stage3 をSSOT化し、legacy env は明示OFF専用の互換に縮退。 +- JoinIR Strict 着手(Phase 81): `NYASH_JOINIR_STRICT=1` で代表パスのフォールバックを禁止(JoinIR失敗は即エラー)。dev/trace は観測のみ継続。 - **これから(Phase 69+)** - wasm/Web デモライン: JoinIR ベースの軽量デモ実装。 - 最適化ライン: JoinIR の最適化パスと LLVM/ny-llvmc 統合。 - Trio 削除ライン: 完了(Phase 70、LoopScopeShape SSOT) + - JoinIR Strict ライン(Phase 81): 代表 If/Loop/VM ブリッジについては `NYASH_JOINIR_STRICT=1` で常に JoinIR 経路のみを通すようにし、レガシー if_phi / LoopBuilder / 旧 MIR Builder は「未対応関数専用」に縮退。 --- @@ -97,7 +99,7 @@ - 未了: - 69-5: conservative.rs の docs/ 移設も今後の小フェーズとして残しておく。 - 追加完了 (Phase 70): - - 69-4: Trio 3 箱(LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox)を削除し、LoopScopeShape を SSOT とする構成に移行。 + - 69-4: Trio 3 箱(LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox)を削除し、LoopScopeShape を SSOT とする構成に移行。2025-12-01 時点でコードベース再スキャン済みで、Trio 本体ファイルおよび Trio Box 直接参照は **src/mir/** から完全に除去されていることを確認(名称としての「Trio」は docs の歴史メモ内にのみ残存)。 ## 2. 次の一手(Phase 69+) @@ -107,6 +109,15 @@ - GenericTypeResolver 経由で全 P3-C ケースをカバー - `infer_type_from_phi` 本体削除と if_phi.rs 大掃除 +- **Phase 71: Self‑Hosting 再ブートストラップ(docs 起点 / SSA デバッグモードで一時停止中)** + - `docs/private/roadmap2/phases/phase-71-selfhost-reboot/README.md` で代表パス 1 本(Stage‑3 + JoinIR 前提)と ENV 方針を整理済み。 + - 代表パス(仮固定): `NYASH_FEATURES=stage3 NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_EMIT_ONLY=1 ./tools/selfhost/selfhost_build.sh --in apps/tests/stage1_run_min.hako --run` + - プラグイン初期化(nyash.toml 経由)は NYASH_DEBUG_PLUGIN=1 で成功確認済みだが、Stage‑B/SSA/JoinIR/dev verify の複合経路で Program(JSON v0) emit 前に失敗し、`Stage‑B emit failed` で selfhost_minimal が落ちている。 + - RAW 観測フラグ追加済み: `NYASH_SELFHOST_KEEP_RAW` / `NYASH_EMIT_MIR_KEEP_RAW` で Stage‑B rc/stdout/stderr/抽出状況を `logs/selfhost` / `logs/emit_mir` に保存可能。`stageb_min_emit.sh` でも dev verify ON/OFF いずれも Program 行 0 件(ssa-undef-debug は trim/parse_params 系に集中)。 + - SSA 修正進捗: `FuncScannerBox.parse_params/1` をダミーカンマ+静的 `_trim` 呼び出しに整理し、`ParserBox.trim` 系も `skip_ws` ベースに統一。`mir_funcscanner_parse_params_trim_min` は緑、Stage‑B RAW から ParserBox.* の `ssa-undef-debug` は消えたが、emit 失敗は継続(dev verify/birth 警告は残存)。 + - dev verify 緩和トグル(`NYASH_STAGEB_DEV_VERIFY`)を実装済み。代表パスで ON/OFF を比較したが、OFF にしても emit は復活せず、SSA/Stage‑B 本体側の欠損が疑われる。 + - quick プロファイルでは JoinIR/VM 系は緑維持を目標としつつ、selfhost_minimal / stageb_min_emit は「SSA ラインの観測窓」として赤許容。Stage‑B/SSA 起因の赤は Phase 71-SSA 側でハンドルする。 + - **Phase 72: JoinIR dev フラグ棚卸し**(docs + env ポリシー整備済み、配線寄せ残) - `config::env::joinir_core_enabled()` / `joinir_dev_enabled()` を追加し、Core/DevOnly/Deprecated の区分を整理 - docs/private/roadmap2/phases/phase-72-joinir-dev-flags/README.md に一覧表を追加済み @@ -142,6 +153,17 @@ - `MirFunction.blocks: HashMap` → `BTreeMap` で非決定的テスト解消 - Phase 25.1 同様のパターン適用 +- Phase 71-SSA: Stage‑B / selfhost ラインは「SSA デバッグ用の観測窓」として切り出し、 + 代表パス(selfhost_build + stage1_run_min.hako)が JSON v0 emit まで通るかどうかを別フェーズで追う。 + 詳細: `docs/private/roadmap2/phases/phase-71-ssa-debug/README.md` + +- Phase 81-JoinIR-Strict: JoinIR を「if/loop/PHI の真」として扱い、 + JoinIR 対象関数では `if_phi` / `LoopBuilder` / 旧 MIR Builder にフォールバックしない Strict モードを導入する。 + 代表 If/Loop(mir_joinir_if_select / mir_stage1_staticcompiler_receiver / mir_joinir_stage1_using_resolver_min)は + `NYASH_JOINIR_CORE=1 NYASH_JOINIR_STRICT=1` で全て green を確認済み(Strict で lowering 失敗なし)。 + 代表パス(skip_ws / trim / resolve / print_tokens / filter / read_quoted / Stage‑1/Stage‑B 代表)では、 + JoinIR lowering / VM ブリッジ失敗を即エラー扱いとし、レガシー経路は「未対応関数専用」に縮退させる。 + --- ## 3. 旧フェーズ(過去ログへのポインタ) diff --git a/apps/tests/emit_boxcall_length_canary_vm.hako b/apps/tests/emit_boxcall_length_canary_vm.hako new file mode 100644 index 00000000..3c189b54 --- /dev/null +++ b/apps/tests/emit_boxcall_length_canary_vm.hako @@ -0,0 +1,8 @@ +// Minimal canary for Stage-B emit -> MIR(JSON) testing. +// StringBox.length boxcall should appear in MIR. +static box Main { + method main(args) { + local s = new StringBox("nyash"); + return s.length() + } +} diff --git a/lang/src/compiler/entry/compiler.hako b/lang/src/compiler/entry/compiler.hako index cf4c28d0..2c0f9b4b 100644 --- a/lang/src/compiler/entry/compiler.hako +++ b/lang/src/compiler/entry/compiler.hako @@ -36,10 +36,17 @@ static box Main { local flags = { emit: 0, ret: null, source: null, stage_b: 0, prefer_cfg: 1, stage3: 0, v1_compat: 0 } if args == null { return flags } + local trace_flag = env.get("HAKO_STAGEB_TRACE") + local trace_on = 0 + if trace_flag != null && ("" + trace_flag) == "1" { trace_on = 1 } + local i = 0 local n = args.length() loop(i < n) { local token = "" + args.get(i) + if trace_on == 1 { + print("[compiler/flags] arg[" + ("" + i) + "]=" + token) + } if token == "--min-json" { flags.emit = 1 } else if token == "--stage-b" { @@ -62,6 +69,14 @@ static box Main { } i = i + 1 } + if trace_on == 1 { + local src_len = 0 + if flags.source != null { src_len = ("" + flags.source).length() } + print("[compiler/flags] summary stage_b=" + ("" + flags.stage_b) + + " stage3=" + ("" + flags.stage3) + + " emit=" + ("" + flags.emit) + + " src_len=" + ("" + src_len)) + } return flags } @@ -478,12 +493,30 @@ static box Main { } main(args) { + // Always emit entry tag (stdout/stderr) to confirm Main.main is running + print("[compiler/main] enter args=" + ("" + args)) + env.error("[compiler/main] enter args=" + ("" + args)) + local flags = me._collect_flags(args) + { + local trace_flag = env.get("HAKO_STAGEB_TRACE") + if trace_flag != null && ("" + trace_flag) == "1" { + print("[compiler/main] flags stage_b=" + ("" + flags.stage_b) + + " stage3=" + ("" + flags.stage3) + + " emit=" + ("" + flags.emit)) + } + } if flags.stage_b == 1 { - // Phase 28.2 Quick Win 3: Direct call to SSOT - local json = StageBDriverBox.compile(flags.source, flags.prefer_cfg, flags.stage3, flags.v1_compat) - print(json) - return 0 + // Stage‑B 経路は SSOT: StageBDriverBox.main をそのまま叩く(末尾で print(ast_json) 済み) + // stdout / stderr 両方にタグを出して、実行経路を確実に観測する + print("[compiler/main] enter stage-b args=" + ("" + args)) + env.error("[compiler/main] enter stage-b args=" + ("" + args)) // dev trace + print("[compiler/main] stage-b entry") + env.error("[compiler/main] stage-b entry") // dev trace + local rc = StageBDriverBox.main(args) + print("[compiler/main] stage-b ret=" + ("" + rc)) + env.error("[compiler/main] stage-b ret=" + ("" + rc)) // dev trace + return rc } if flags.emit == 1 { local json = me._compile_source_to_json_v0(flags.source) diff --git a/lang/src/compiler/entry/compiler_stageb.hako b/lang/src/compiler/entry/compiler_stageb.hako index cd4a595b..7c5a6265 100644 --- a/lang/src/compiler/entry/compiler_stageb.hako +++ b/lang/src/compiler/entry/compiler_stageb.hako @@ -1236,6 +1236,9 @@ static box StageBDriverBox { } main(args) { + // Dev trace to confirm entry dispatch + print("[stageb/main] enter") + // ============================================================================ // Phase 25.1c: Guaranteed marker for entry point confirmation (dev-only) // ============================================================================ @@ -1252,16 +1255,19 @@ static box StageBDriverBox { { local depth = env.get("HAKO_STAGEB_DRIVER_DEPTH") if depth != null && ("" + depth) != "0" { - print("[stageb/recursion] StageBDriverBox.main recursion detected") + print("[stageb/error] depth_guard tripped: HAKO_STAGEB_DRIVER_DEPTH!=0") return -1 } env.set("HAKO_STAGEB_DRIVER_DEPTH", "1") } + // Depth guard cleared → main 続行できることを明示 + print("[stageb/main] depth ok") // Dev-only: direct FuncScanner harness { local test_flag = env.get("HAKO_STAGEB_FUNCSCAN_TEST") if test_flag != null && ("" + test_flag) == "1" { + print("[stageb/info] FUNC_SCAN_TEST=1, skipping emit") StageBFuncScannerBox.test_fib_scan() // Clear depth guard before returning env.set("HAKO_STAGEB_DRIVER_DEPTH", "0") @@ -1293,6 +1299,9 @@ static box StageBDriverBox { // local externs_json = p.get_externs_json() local body_src = StageBBodyExtractorBox.build_body_src(src, args) + if body_src == null { + print("[stageb/error] no body_src (StageBBodyExtractorBox returned null)") + } { local l2 = 0 if body_src != null { l2 = ("" + body_src).length() } @@ -1465,7 +1474,9 @@ static box StageBDriverBox { } } + print("[stageb/main] before ast_json") print(ast_json) + print("[stageb/main] after ast_json") { local tracer = new StageBTraceBox() tracer.log("StageBDriverBox.main:exit rc=0") diff --git a/lang/src/compiler/entry/func_scanner.hako b/lang/src/compiler/entry/func_scanner.hako index e5837165..12d68f38 100644 --- a/lang/src/compiler/entry/func_scanner.hako +++ b/lang/src/compiler/entry/func_scanner.hako @@ -416,30 +416,34 @@ static box FuncScannerBox { // FuncScannerBox._trim を使用(static helper パターン) // 戻り値: ArrayBox(トリム済みパラメータ名のリスト) method parse_params(params_str) { - // NOTE: keep the control flow simple to reduce SSA/PHI complexity. - // skip_whitespace/trim are already well‑tested helpers, so we reuse them here. + // NOTE: SSA/PHI が崩れにくいよう、1 ループ+単純状態でスキャンする。 if params_str == null { return new ArrayBox() } + + // 状態変数は params / cur / i のみを更新する。 local params = new ArrayBox() - local pstr = "" + params_str - local n = pstr.length() - local pos = 0 + local s = "" + params_str + local n = s.length() + local cur = "" + local i = 0 - loop(pos < n) { - pos = FuncScannerBox.skip_whitespace(pstr, pos) - if pos >= n { break } + // Scan characters plus a sentinel comma at the end to reuse the same branch. + loop(i <= n) { + // When i == n we synthesize a trailing comma to flush the last token. + local ch = "," + if i < n { ch = s.substring(i, i + 1) } - // Find next comma (or end of string). - local next = pos - loop(next < n) { - if pstr.substring(next, next + 1) == "," { break } - next = next + 1 + if ch == "," { + // push current token if non-empty after trim, then reset + // Use static helper to avoid capturing instance state (SSA-friendly). + local tok = FuncScannerBox._trim(cur) + if tok.length() > 0 { params.push(tok) } + cur = "" + i = i + 1 + continue + } else { + cur = cur + ch } - - // Trim and collect the parameter name. - local pname = FuncScannerBox.trim(pstr.substring(pos, next)) - if pname.length() > 0 { params.push(pname) } - - pos = next + 1 + i = i + 1 } return params @@ -532,6 +536,26 @@ static box FuncScannerBox { // Static helper: 前後空白削除(外部 API) method _trim(s) { - return FuncScannerBox.trim(s) + // NOTE: keep in sync with trim/1. 静的 alias でも SSA が崩れないよう、本体を薄くインラインする。 + if s == null { return "" } + + local n = s.length() + + __mir__.log("trim/pre", n) + + // Leading whitespace removal is delegated to skip_whitespace to keep SSA simple. + local b = FuncScannerBox.skip_whitespace(s, 0) + if b >= n { return "" } + + // Trailing whitespace: walk backwards until a non-space is found. + local e = n + loop(e > b) { + local ch = s.substring(e - 1, e) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { e = e - 1 } else { break } + } + + __mir__.log("trim/exit", b, e) + if e > b { return s.substring(b, e) } + return "" } } diff --git a/lang/src/compiler/parser/scan/parser_common_utils_box.hako b/lang/src/compiler/parser/scan/parser_common_utils_box.hako index 78da6d55..7ad5ce5b 100644 --- a/lang/src/compiler/parser/scan/parser_common_utils_box.hako +++ b/lang/src/compiler/parser/scan/parser_common_utils_box.hako @@ -2,6 +2,7 @@ // ParserCommonUtilsBox — shared utility functions for parser boxes // Responsibility: Provide common string/character operations used across parser components // Notes: Pure utility functions; no state, no dependencies +using lang.compiler.parser.scan.parser_string_utils_box as ParserStringUtilsBox static box ParserCommonUtilsBox { // ===== 数値・文字列変換 ===== @@ -47,12 +48,8 @@ static box ParserCommonUtilsBox { } trim(s) { - local i = 0 - local n = s.length() - loop(i < n && (s.substring(i,i+1) == " " || s.substring(i,i+1) == "\t")) { i = i + 1 } - local j = n - loop(j > i && (s.substring(j-1,j) == " " || s.substring(j-1,j) == "\t" || s.substring(j-1,j) == ";")) { j = j - 1 } - return s.substring(i, j) + // Delegate to string_utils to keep SSA/semantic consistency. + return ParserStringUtilsBox.trim(s) } esc_json(s) { @@ -69,4 +66,3 @@ static box ParserCommonUtilsBox { return out } } - diff --git a/lang/src/compiler/parser/scan/parser_string_utils_box.hako b/lang/src/compiler/parser/scan/parser_string_utils_box.hako index e46250de..6a6c8982 100644 --- a/lang/src/compiler/parser/scan/parser_string_utils_box.hako +++ b/lang/src/compiler/parser/scan/parser_string_utils_box.hako @@ -71,16 +71,18 @@ static box ParserStringUtilsBox { if s == null { return "" } local str = "" + s local n = str.length() - local b = 0 - loop(b < n) { - local ch = str.substring(b, b + 1) - if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { b = b + 1 } else { break } - } + + // Leading whitespace: reuse shared skip_ws to keep semantics aligned with Stage‑B. + local b = StringHelpers.skip_ws(str, 0) + if b >= n { return "" } + + // Trailing whitespace: walk backwards until a non-space is found. local e = n loop(e > b) { local ch = str.substring(e - 1, e) - if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { e = e - 1 } else { break } + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" || ch == ";" { e = e - 1 } else { break } } + if e > b { return str.substring(b, e) } return "" } diff --git a/nyash.toml b/nyash.toml index b74ec398..b1cd45ad 100644 --- a/nyash.toml +++ b/nyash.toml @@ -57,6 +57,9 @@ path = "lang/src/shared/json/stringify.hako" [using.sh_core] path = "lang/src/shared/common/string_helpers.hako" +[plugin_paths] +search_paths = ["target/release"] + [modules] # Core shared helpers (needed by parser/compiler in Stage-B) "sh_core" = "lang/src/shared/common/string_helpers.hako" diff --git a/src/backend/mir_interpreter/exec.rs b/src/backend/mir_interpreter/exec.rs index 35cb6480..839dc001 100644 --- a/src/backend/mir_interpreter/exec.rs +++ b/src/backend/mir_interpreter/exec.rs @@ -1,5 +1,7 @@ use super::*; use crate::mir::basic_block::BasicBlock; +use std::fs::OpenOptions; +use std::io::Write; use std::mem; impl MirInterpreter { @@ -66,6 +68,16 @@ impl MirInterpreter { }) .unwrap_or(1_000_000); let mut steps: u64 = 0; + let trace_log_path: Option = std::env::var("NYASH_VM_TRACE_LOG") + .ok() + .map(|v| { + let trimmed = v.trim(); + if trimmed.is_empty() || trimmed == "1" { + "__mir__.log".to_string() + } else { + v + } + }); loop { steps += 1; @@ -94,6 +106,23 @@ impl MirInterpreter { .get(&cur) .ok_or_else(|| VMError::InvalidBasicBlock(format!("bb {:?} not found", cur)))?; + if let Some(path) = trace_log_path.as_ref() { + let _ = OpenOptions::new() + .create(true) + .append(true) + .open(path) + .and_then(|mut f| { + writeln!( + f, + "[vm-trace-log] fn={} bb={:?} pred={:?} step={}", + self.cur_fn.as_deref().unwrap_or(""), + cur, + last_pred, + steps + ) + }); + } + if Self::trace_enabled() { eprintln!( "[vm-trace] enter bb={:?} pred={:?} fn={}", diff --git a/src/backend/mir_interpreter/mod.rs b/src/backend/mir_interpreter/mod.rs index f747c6b0..79dec28b 100644 --- a/src/backend/mir_interpreter/mod.rs +++ b/src/backend/mir_interpreter/mod.rs @@ -225,16 +225,19 @@ impl MirInterpreter { // Try candidates in order let mut chosen: Option<&nyash_rust::mir::MirFunction> = None; + let mut chosen_name: Option = None; for c in &candidates { // exact if let Some(f) = module.functions.get(c) { chosen = Some(f); + chosen_name = Some(c.clone()); break; } // if contains '/': try name before '/' if let Some((head, _)) = c.split_once('/') { if let Some(f) = module.functions.get(head) { chosen = Some(f); + chosen_name = Some(head.to_string()); break; } } @@ -242,6 +245,7 @@ impl MirInterpreter { if c.ends_with(".main") { if let Some(f) = module.functions.get("main") { chosen = Some(f); + chosen_name = Some("main".to_string()); break; } } @@ -268,6 +272,16 @@ impl MirInterpreter { } }; + if std::env::var("NYASH_EMIT_MIR_TRACE") + .ok() + .as_deref() + == Some("1") + { + if let Some(name) = chosen_name { + eprintln!("[vm/entry] main={}", name); + } + } + // Prepare arguments if the entry takes parameters (pass script args as ArrayBox) let ret = if func.signature.params.len() == 0 { self.execute_function(func)? diff --git a/src/config/env.rs b/src/config/env.rs index 407cba86..0ab221d1 100644 --- a/src/config/env.rs +++ b/src/config/env.rs @@ -150,6 +150,12 @@ pub fn verify_ret_purity() -> bool { env_bool("NYASH_VERIFY_RET_PURITY") } +/// Stage-B/selfhost 専用の dev verify トグル(SSA などの厳格チェックを一時緩和するためのスイッチ) +/// Default: ON(現行挙動)。NYASH_STAGEB_DEV_VERIFY=0 で Stage-B 経路の dev verify をスキップ。 +pub fn stageb_dev_verify_enabled() -> bool { + env_flag("NYASH_STAGEB_DEV_VERIFY").unwrap_or(true) +} + // ---- LLVM harness toggle (llvmlite) ---- pub fn llvm_use_harness() -> bool { // Phase 15: デフォルトON(LLVMバックエンドはPythonハーネス使用) @@ -195,7 +201,11 @@ fn nyash_features_list() -> Option> { } }) .collect(); - if list.is_empty() { None } else { Some(list) } + if list.is_empty() { + None + } else { + Some(list) + } } fn feature_stage3_enabled() -> bool { @@ -249,6 +259,12 @@ pub fn joinir_vm_bridge_enabled() -> bool { joinir_core_enabled() && env_bool("NYASH_JOINIR_VM_BRIDGE") } +/// JoinIR strict mode: when enabled, JoinIR 対象のフォールバックを禁止する。 +/// 既定OFF。NYASH_JOINIR_STRICT=1 のときのみ有効。 +pub fn joinir_strict_enabled() -> bool { + env_flag("NYASH_JOINIR_STRICT").unwrap_or(false) +} + /// JoinIR VM bridge debug output. Enables verbose logging of JoinIR→MIR conversion. /// Set NYASH_JOINIR_VM_BRIDGE_DEBUG=1 to enable. pub fn joinir_vm_bridge_debug() -> bool { @@ -266,6 +282,10 @@ pub fn joinir_llvm_experiment_enabled() -> bool { /// Phase 33: JoinIR If Select 実験の有効化 /// Primary: HAKO_JOINIR_IF_SELECT (Phase 33-8+). pub fn joinir_if_select_enabled() -> bool { + // Core ON なら既定で有効化(JoinIR 本線化を優先) + if joinir_core_enabled() { + return true; + } // Primary: HAKO_JOINIR_IF_SELECT if let Some(v) = env_flag("HAKO_JOINIR_IF_SELECT") { return v; diff --git a/src/grammar/generated.rs b/src/grammar/generated.rs index d94cb9c1..25bb6e51 100644 --- a/src/grammar/generated.rs +++ b/src/grammar/generated.rs @@ -36,37 +36,15 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[ ]; pub fn lookup_keyword(word: &str) -> Option<&'static str> { for (k, t) in KEYWORDS { - if *k == word { return Some(*t); } + if *k == word { + return Some(*t); + } } None } pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[ - "box", - "global", - "function", - "static", - "if", - "loop", - "break", - "return", - "print", - "nowait", - "include", - "local", - "outbox", - "try", - "throw", - "using", - "from", + "box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait", + "include", "local", "outbox", "try", "throw", "using", "from", ]; -pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[ - "add", - "sub", - "mul", - "div", - "and", - "or", - "eq", - "ne", -]; \ No newline at end of file +pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"]; diff --git a/src/mir/builder/control_flow.rs b/src/mir/builder/control_flow.rs index 49ee6c43..e9882860 100644 --- a/src/mir/builder/control_flow.rs +++ b/src/mir/builder/control_flow.rs @@ -26,10 +26,12 @@ impl super::MirBuilder { /// /// This is the unified entry point for all loop lowering. Specific functions /// are routed through JoinIR Frontend instead of the traditional LoopBuilder path - /// when enabled via dev flags: + /// when enabled via dev flags (Phase 49) or Core policy (Phase 80): /// - /// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0 - /// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2 + /// - Core ON (`joinir_core_enabled()`): print_tokens / ArrayExt.filter はまず JoinIR Frontend を試す + /// - Dev フラグ(既存): + /// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0 + /// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2 /// /// Note: Arity does NOT include implicit `me` receiver. pub(super) fn cf_loop( @@ -37,7 +39,7 @@ impl super::MirBuilder { condition: ASTNode, body: Vec, ) -> Result { - // Phase 49: Try JoinIR Frontend route for mainline targets + // Phase 49/80: Try JoinIR Frontend route for mainline targets if let Some(result) = self.try_cf_loop_joinir(&condition, &body)? { return Ok(result); } @@ -77,20 +79,31 @@ impl super::MirBuilder { .map(|f| f.signature.name.clone()) .unwrap_or_default(); - // Phase 49-4: Multi-target routing with separate dev flags + // Phase 49-4 + Phase 80: Multi-target routing + // - Core ON なら代表2本(print_tokens / ArrayExt.filter)は JoinIR を優先し、失敗したら LoopBuilder へフォールバック + // - Core OFF では従来通り dev フラグで opt-in // Note: Arity does NOT include implicit `me` receiver + let core_on = crate::config::env::joinir_core_enabled(); let is_target = match func_name.as_str() { "JsonTokenizer.print_tokens/0" => { - std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN") - .ok() - .as_deref() - == Some("1") + if core_on { + true + } else { + std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN") + .ok() + .as_deref() + == Some("1") + } } "ArrayExtBox.filter/2" => { - std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN") - .ok() - .as_deref() - == Some("1") + if core_on { + true + } else { + std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN") + .ok() + .as_deref() + == Some("1") + } } _ => false, }; diff --git a/src/mir/builder/lifecycle.rs b/src/mir/builder/lifecycle.rs index 9aa0b410..777251a0 100644 --- a/src/mir/builder/lifecycle.rs +++ b/src/mir/builder/lifecycle.rs @@ -2,6 +2,7 @@ use super::{ BasicBlockId, EffectMask, FunctionSignature, MirInstruction, MirModule, MirType, ValueId, }; use crate::ast::ASTNode; +use crate::config; // Lifecycle routines extracted from builder.rs fn has_main_static(ast: &ASTNode) -> bool { @@ -299,7 +300,8 @@ impl super::MirBuilder { None }; // Phase 67: P3-C 対象なら GenericTypeResolver を優先使用 - if hint.is_none() && TypeHintPolicy::is_p3c_target(&function.signature.name) { + if hint.is_none() && TypeHintPolicy::is_p3c_target(&function.signature.name) + { if let Some(mt) = GenericTypeResolver::resolve_from_phi( &function, *v, @@ -342,11 +344,9 @@ impl super::MirBuilder { }; // Phase 67: P3-C 対象なら GenericTypeResolver を優先使用 if hint.is_none() && TypeHintPolicy::is_p3c_target(&function.signature.name) { - if let Some(mt) = GenericTypeResolver::resolve_from_phi( - &function, - *v, - &self.value_types, - ) { + if let Some(mt) = + GenericTypeResolver::resolve_from_phi(&function, *v, &self.value_types) + { if std::env::var("NYASH_P3C_DEBUG").is_ok() { eprintln!( "[lifecycle/p3c] {} type inferred via GenericTypeResolver: {:?}", @@ -373,7 +373,9 @@ impl super::MirBuilder { } } // Dev-only verify: NewBox → birth() invariant (warn if missing) + // Stage‑B 用トグル: NYASH_STAGEB_DEV_VERIFY=0 のときは StageBDriverBox だけ警告をスキップする。 if crate::config::env::using_is_dev() { + let stageb_dev_verify_on = config::env::stageb_dev_verify_enabled(); let mut warn_count = 0usize; for (_bid, bb) in function.blocks.iter() { let insns = &bb.instructions; @@ -385,6 +387,11 @@ impl super::MirBuilder { args, } = &insns[idx] { + // Stage‑B dev verify OFF のときは StageBDriverBox の birth 警告のみスキップ + if !stageb_dev_verify_on && box_type == "StageBDriverBox" { + idx += 1; + continue; + } // Skip StringBox (literal optimization path) if box_type != "StringBox" { let expect_tail = format!("{}.birth/{}", box_type, args.len()); diff --git a/src/mir/join_ir/lowering/generic_type_resolver.rs b/src/mir/join_ir/lowering/generic_type_resolver.rs index dfcefe4b..a2c2d1a4 100644 --- a/src/mir/join_ir/lowering/generic_type_resolver.rs +++ b/src/mir/join_ir/lowering/generic_type_resolver.rs @@ -154,10 +154,7 @@ mod tests { // P3-C 対象 assert!(GenericTypeResolver::is_generic_method(&array_type, "get")); assert!(GenericTypeResolver::is_generic_method(&array_type, "pop")); - assert!(GenericTypeResolver::is_generic_method( - &array_type, - "first" - )); + assert!(GenericTypeResolver::is_generic_method(&array_type, "first")); assert!(GenericTypeResolver::is_generic_method(&array_type, "last")); // P3-A/P3-B 対象(非 P3-C) @@ -194,9 +191,7 @@ mod tests { fn test_is_p3c_candidate() { // 全関数が P3-C 候補(P1/P2/P3-A/B 以外) assert!(GenericTypeResolver::is_p3c_candidate("Main.main/0")); - assert!(GenericTypeResolver::is_p3c_candidate( - "FuncScanner.parse/1" - )); + assert!(GenericTypeResolver::is_p3c_candidate("FuncScanner.parse/1")); // 空文字列は false assert!(!GenericTypeResolver::is_p3c_candidate("")); diff --git a/src/mir/join_ir/lowering/loop_form_intake.rs b/src/mir/join_ir/lowering/loop_form_intake.rs index 8dbf2e63..e742b6a5 100644 --- a/src/mir/join_ir/lowering/loop_form_intake.rs +++ b/src/mir/join_ir/lowering/loop_form_intake.rs @@ -170,8 +170,16 @@ pub(crate) fn intake_loop_form( // Phase 70-1: Trio 分類を削除し、pinned_hint/carrier_hint をそのまま返す // 実際の分類は LoopScopeShape::from_loop_form() 内部で実施される(二重分類問題解消) - let ordered_pinned: Vec = pinned_hint.into_iter().collect::>().into_iter().collect(); - let ordered_carriers: Vec = carrier_hint.into_iter().collect::>().into_iter().collect(); + let ordered_pinned: Vec = pinned_hint + .into_iter() + .collect::>() + .into_iter() + .collect(); + let ordered_carriers: Vec = carrier_hint + .into_iter() + .collect::>() + .into_iter() + .collect(); if ordered_pinned.is_empty() || ordered_carriers.is_empty() { return None; diff --git a/src/mir/join_ir/lowering/loop_scope_shape/builder.rs b/src/mir/join_ir/lowering/loop_scope_shape/builder.rs index 18e007df..51b3dbc5 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/builder.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/builder.rs @@ -58,10 +58,7 @@ impl LoopScopeShape { Some(result) } - fn build_from_intake( - loop_form: &LoopForm, - intake: &LoopFormIntake, - ) -> Option { + fn build_from_intake(loop_form: &LoopForm, intake: &LoopFormIntake) -> Option { let layout = block_layout(loop_form); if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() { @@ -80,13 +77,8 @@ impl LoopScopeShape { let carriers: BTreeSet = intake.carrier_ordered.iter().cloned().collect(); let variable_definitions = collect_variable_definitions(intake, &layout); - let (body_locals, exit_live) = classify_body_and_exit( - intake, - &pinned, - &carriers, - &variable_definitions, - &layout, - ); + let (body_locals, exit_live) = + classify_body_and_exit(intake, &pinned, &carriers, &variable_definitions, &layout); let progress_carrier = carriers.iter().next().cloned(); @@ -150,10 +142,7 @@ fn collect_variable_definitions( for (bb, snap) in &intake.exit_snapshots { for var_name in snap.keys() { - var_defs - .entry(var_name.clone()) - .or_default() - .insert(*bb); + var_defs.entry(var_name.clone()).or_default().insert(*bb); } } diff --git a/src/mir/join_ir/lowering/loop_scope_shape/shape.rs b/src/mir/join_ir/lowering/loop_scope_shape/shape.rs index 09d0f522..58f6ddd0 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/shape.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/shape.rs @@ -18,14 +18,16 @@ pub enum LoopVarClass { impl LoopVarClass { #[cfg(test)] pub fn needs_exit_phi(self) -> bool { - matches!(self, LoopVarClass::Pinned | LoopVarClass::Carrier | LoopVarClass::BodyLocalExit) + matches!( + self, + LoopVarClass::Pinned | LoopVarClass::Carrier | LoopVarClass::BodyLocalExit + ) } #[cfg(test)] pub fn needs_header_phi(self) -> bool { matches!(self, LoopVarClass::Pinned | LoopVarClass::Carrier) } - } /// ループ変数スコープの統合ビュー diff --git a/src/mir/join_ir/lowering/loop_scope_shape/structural.rs b/src/mir/join_ir/lowering/loop_scope_shape/structural.rs index a4993965..2931f0b7 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/structural.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/structural.rs @@ -151,9 +151,7 @@ mod tests { pinned: vec!["s".to_string()].into_iter().collect(), carriers: vec!["i".to_string()].into_iter().collect(), body_locals: BTreeSet::new(), - exit_live: vec!["s".to_string(), "i".to_string()] - .into_iter() - .collect(), + exit_live: vec!["s".to_string(), "i".to_string()].into_iter().collect(), progress_carrier: Some("i".to_string()), variable_definitions: BTreeMap::new(), } diff --git a/src/mir/join_ir/lowering/loop_scope_shape/tests.rs b/src/mir/join_ir/lowering/loop_scope_shape/tests.rs index 92f31ca3..f46cbafc 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape/tests.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape/tests.rs @@ -1,6 +1,6 @@ +use super::shape::LoopVarClass; use super::*; use crate::mir::join_ir::lowering::loop_form_intake::LoopFormIntake; -use super::shape::LoopVarClass; use crate::mir::{BasicBlockId, MirQuery, ValueId}; use std::collections::{BTreeMap, BTreeSet}; @@ -362,9 +362,13 @@ fn test_is_available_in_all_phase48_5_future() { ); variable_definitions.insert( "i".to_string(), - vec![BasicBlockId::new(2), BasicBlockId::new(3), BasicBlockId::new(4)] - .into_iter() - .collect(), + vec![ + BasicBlockId::new(2), + BasicBlockId::new(3), + BasicBlockId::new(4), + ] + .into_iter() + .collect(), ); let scope = LoopScopeShape { @@ -391,7 +395,11 @@ fn test_is_available_in_all_phase48_5_future() { // i は block 2, 3, 4 で定義 → すべて要求しても true assert!(scope.is_available_in_all( "i", - &[BasicBlockId::new(2), BasicBlockId::new(3), BasicBlockId::new(4)] + &[ + BasicBlockId::new(2), + BasicBlockId::new(3), + BasicBlockId::new(4) + ] )); // unknown は variable_definitions にない → false @@ -469,7 +477,11 @@ fn test_variable_definitions_partial_availability() { // s は全 exit で利用可能 assert!(scope.is_available_in_all( "s", - &[BasicBlockId::new(20), BasicBlockId::new(21), BasicBlockId::new(22)] + &[ + BasicBlockId::new(20), + BasicBlockId::new(21), + BasicBlockId::new(22) + ] )); // y は exit1 でのみ利用可能 diff --git a/src/mir/join_ir/lowering/loop_to_join.rs b/src/mir/join_ir/lowering/loop_to_join.rs index 4b7abd9a..a737262d 100644 --- a/src/mir/join_ir/lowering/loop_to_join.rs +++ b/src/mir/join_ir/lowering/loop_to_join.rs @@ -85,6 +85,10 @@ impl LoopToJoinLowerer { loop_form: &LoopForm, func_name: Option<&str>, ) -> Option { + let strict_on = crate::config::env::joinir_strict_enabled(); + let is_minimal_target = func_name + .map(super::loop_scope_shape::is_case_a_minimal_target) + .unwrap_or(false); if self.debug { eprintln!( "[LoopToJoinLowerer] lower() called for {:?}", @@ -137,6 +141,12 @@ impl LoopToJoinLowerer { func_name.unwrap_or("") ); } + if strict_on && is_minimal_target { + panic!( + "[joinir/loop] strict mode: view-based check failed for {}", + func_name.unwrap_or("") + ); + } return None; } @@ -151,6 +161,12 @@ impl LoopToJoinLowerer { func_name.unwrap_or("") ); } + if strict_on && is_minimal_target { + panic!( + "[joinir/loop] strict mode: name filter rejected {}", + func_name.unwrap_or("") + ); + } return None; } } else if self.debug { @@ -161,7 +177,14 @@ impl LoopToJoinLowerer { } // Step 5: パターンに応じた lowering を実行 - self.lower_with_scope(scope, func_name) + let out = self.lower_with_scope(scope, func_name); + if out.is_none() && strict_on && is_minimal_target { + panic!( + "[joinir/loop] strict mode: lowering failed for {}", + func_name.unwrap_or("") + ); + } + out } /// Phase 29 L-5.3: Progress carrier の安全性をチェック diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index 19db5692..f8d961ce 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -20,6 +20,7 @@ pub mod exit_args_resolver; pub mod funcscanner_append_defs; pub mod funcscanner_trim; pub mod generic_case_a; +pub mod generic_type_resolver; // Phase 66: P3-C ジェネリック型推論箱 pub mod if_dry_runner; // Phase 33-10.0 pub mod if_merge; // Phase 33-7 pub mod if_phi_context; // Phase 61-1 @@ -35,7 +36,6 @@ pub mod stageb_body; pub mod stageb_funcscanner; pub mod type_hint_policy; // Phase 65.5: 型ヒントポリシー箱化 pub mod type_inference; // Phase 65-2-A -pub mod generic_type_resolver; // Phase 66: P3-C ジェネリック型推論箱 pub mod value_id_ranges; // Re-export public lowering functions @@ -148,6 +148,8 @@ pub fn try_lower_if_to_joinir( if !crate::config::env::joinir_if_select_enabled() { return None; } + let core_on = crate::config::env::joinir_core_enabled(); + let strict_on = crate::config::env::joinir_strict_enabled(); // Phase 33-9.1: Loop専任関数の除外(Loop/If責務分離) // Loop lowering対象関数はIf loweringの対象外にすることで、 @@ -179,15 +181,21 @@ pub fn try_lower_if_to_joinir( "Stage1JsonScannerBox.value_start_after_key_pos/2" ); - if !is_allowed { - if debug_level >= 2 { - eprintln!( - "[try_lower_if_to_joinir] skipping non-allowed function: {}", - func.signature.name - ); + // Phase 80: Core ON のときは許可リストを「JoinIRをまず試す」対象とみなす。 + // Core OFF のときは従来どおり whitelist + env に頼る。 + if !is_allowed || !core_on { + // Core OFF かつ許可外なら従来のガードでスキップ + if !is_allowed { + if debug_level >= 2 { + eprintln!( + "[try_lower_if_to_joinir] skipping non-allowed function: {}", + func.signature.name + ); + } + return None; } - return None; } + let strict_allowed = strict_on && core_on && is_allowed; if debug_level >= 1 { eprintln!( @@ -232,6 +240,12 @@ pub fn try_lower_if_to_joinir( func.signature.name ); } + if strict_allowed { + panic!( + "[joinir/if] strict mode: pattern not matched for {}", + func.signature.name + ); + } return None; } @@ -244,6 +258,13 @@ pub fn try_lower_if_to_joinir( ); } + if result.is_none() && strict_allowed { + panic!( + "[joinir/if] strict mode: lowering failed for {}", + func.signature.name + ); + } + result } diff --git a/src/mir/join_ir_vm_bridge_dispatch/exec_routes.rs b/src/mir/join_ir_vm_bridge_dispatch/exec_routes.rs index 21c34c5d..bdb62c11 100644 --- a/src/mir/join_ir_vm_bridge_dispatch/exec_routes.rs +++ b/src/mir/join_ir_vm_bridge_dispatch/exec_routes.rs @@ -3,6 +3,7 @@ use crate::mir::join_ir_ops::JoinValue; use crate::mir::join_ir_vm_bridge::run_joinir_via_vm; use crate::mir::MirModule; use std::process; +use crate::config::env::{joinir_dev_enabled, joinir_strict_enabled}; /// Main.skip/1 用 JoinIR ブリッジ(Exec: JoinIR→VM 実行まで対応) /// @@ -21,6 +22,10 @@ pub(crate) fn try_run_skip_ws(module: &MirModule, quiet_pipe: bool) -> bool { let input = std::env::var("NYASH_JOINIR_INPUT").unwrap_or_else(|_| " abc".to_string()); eprintln!("[joinir/vm_bridge] Input: {:?}", input); + let dev_bridge = joinir_dev_enabled() + || std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1"); + let strict = joinir_strict_enabled(); + match run_joinir_via_vm(&join_module, JoinFuncId::new(0), &[JoinValue::Str(input)]) { Ok(result) => { let exit_code = match &result { @@ -35,10 +40,23 @@ pub(crate) fn try_run_skip_ws(module: &MirModule, quiet_pipe: bool) -> bool { _ => 0, }; eprintln!("[joinir/vm_bridge] ✅ JoinIR result: {:?}", result); - if !quiet_pipe { - println!("RC: {}", exit_code); + if dev_bridge { + // Devモード: 結果を出しても exit しない(後続パスを継続して観測する) + if !quiet_pipe { + println!("RC: {}", exit_code); + } + return true; + } else if strict { + if !quiet_pipe { + println!("RC: {}", exit_code); + } + process::exit(exit_code); + } else { + if !quiet_pipe { + println!("RC: {}", exit_code); + } + process::exit(exit_code); } - process::exit(exit_code); } Err(e) => { eprintln!("[joinir/vm_bridge] ❌ JoinIR execution failed: {:?}", e); @@ -64,16 +82,38 @@ pub(crate) fn try_run_trim(module: &MirModule, quiet_pipe: bool) -> bool { let input = std::env::var("NYASH_JOINIR_INPUT").unwrap_or_else(|_| " abc ".to_string()); eprintln!("[joinir/vm_bridge] Input: {:?}", input); + let dev_bridge = joinir_dev_enabled() + || std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1"); + let strict = joinir_strict_enabled(); + match run_joinir_via_vm(&join_module, JoinFuncId::new(0), &[JoinValue::Str(input)]) { Ok(result) => { eprintln!("[joinir/vm_bridge] ✅ JoinIR trim result: {:?}", result); - if !quiet_pipe { - match &result { - JoinValue::Str(s) => println!("{}", s), - _ => println!("{:?}", result), + if dev_bridge { + if !quiet_pipe { + match &result { + JoinValue::Str(s) => println!("{}", s), + _ => println!("{:?}", result), + } } + return true; + } else if strict { + if !quiet_pipe { + match &result { + JoinValue::Str(s) => println!("{}", s), + _ => println!("{:?}", result), + } + } + process::exit(0); + } else { + if !quiet_pipe { + match &result { + JoinValue::Str(s) => println!("{}", s), + _ => println!("{:?}", result), + } + } + process::exit(0); } - process::exit(0); } Err(e) => { eprintln!("[joinir/vm_bridge] ❌ JoinIR trim failed: {:?}", e); diff --git a/src/mir/join_ir_vm_bridge_dispatch/mod.rs b/src/mir/join_ir_vm_bridge_dispatch/mod.rs index a4550925..3ed246d5 100644 --- a/src/mir/join_ir_vm_bridge_dispatch/mod.rs +++ b/src/mir/join_ir_vm_bridge_dispatch/mod.rs @@ -40,6 +40,7 @@ use crate::mir::MirModule; /// Exec(実行)または LowerOnly(検証のみ)のパスに分岐する。 pub fn try_run_joinir_vm_bridge(module: &MirModule, quiet_pipe: bool) -> bool { let flags = JoinIrEnvFlags::from_env(); + let strict = crate::config::env::joinir_strict_enabled(); // Phase 32 L-4: テーブルから対象関数を探す let Some(target) = find_joinir_target(module) else { @@ -56,12 +57,25 @@ pub fn try_run_joinir_vm_bridge(module: &MirModule, quiet_pipe: bool) -> bool { // Phase 32 L-4: テーブル駆動ディスパッチ // 関数名でルーティング(将来は lowering テーブルベースに差し替え予定) - match target.func_name { + let handled = match target.func_name { "Main.skip/1" => try_run_skip_ws(module, quiet_pipe), "FuncScannerBox.trim/1" => try_run_trim(module, quiet_pipe), "Stage1UsingResolverBox.resolve_for_source/5" => try_run_stage1_usingresolver(module), "StageBBodyExtractorBox.build_body_src/2" => try_run_stageb_body(module), "StageBFuncScannerBox.scan_all_boxes/1" => try_run_stageb_funcscanner(module), _ => false, + }; + + if !handled { + if strict { + eprintln!( + "[joinir/bridge] ERROR: target={} lowering/exec failed (strict, no fallback)", + target.func_name + ); + std::process::exit(1); + } else { + return false; + } } + true } diff --git a/src/mir/phi_core/loop_snapshot_merge.rs b/src/mir/phi_core/loop_snapshot_merge.rs index 03703fec..9ff5de9e 100644 --- a/src/mir/phi_core/loop_snapshot_merge.rs +++ b/src/mir/phi_core/loop_snapshot_merge.rs @@ -123,9 +123,7 @@ impl LoopSnapshotMergeBox { if debug { eprintln!( "[Option C] var '{}': {:?} needs_exit_phi={}", - var_name, - class, - needs_exit_phi + var_name, class, needs_exit_phi ); if let Some(defining_blocks) = definitions.get(&var_name) { eprintln!("[Option C] defining_blocks: {:?}", defining_blocks); diff --git a/src/mir/phi_core/loopform_builder.rs b/src/mir/phi_core/loopform_builder.rs index 06ff676c..f5290e9f 100644 --- a/src/mir/phi_core/loopform_builder.rs +++ b/src/mir/phi_core/loopform_builder.rs @@ -956,12 +956,7 @@ pub fn build_exit_phis_for_control( // Phase 69-2: LocalScopeInspectorBox 完全削除 // variable_definitions は LoopScopeShape に移行済み(Phase 48-4) - loopform.build_exit_phis( - ops, - exit_id, - branch_source_block, - exit_snapshots, - ) + loopform.build_exit_phis(ops, exit_id, branch_source_block, exit_snapshots) } #[cfg(test)] diff --git a/src/mir/verification.rs b/src/mir/verification.rs index 986501a3..3f5a982c 100644 --- a/src/mir/verification.rs +++ b/src/mir/verification.rs @@ -31,6 +31,11 @@ impl MirVerifier { pub fn verify_module(&mut self, module: &MirModule) -> Result<(), Vec> { self.errors.clear(); + // Stage‑B/selfhost 専用: dev verify を一時緩和するためのトグル + if !crate::config::env::stageb_dev_verify_enabled() { + return Ok(()); + } + for (_name, function) in &module.functions { if let Err(mut func_errors) = self.verify_function(function) { // Dev-only trace: BreakFinderBox / LoopSSA 周辺のSSAバグを詳細に観測する。 diff --git a/src/runner/child_env.rs b/src/runner/child_env.rs index 3c143e0e..cfb3cea6 100644 --- a/src/runner/child_env.rs +++ b/src/runner/child_env.rs @@ -30,8 +30,10 @@ pub fn apply_core_wrapper_env(cmd: &mut std::process::Command) { // Remove noisy or recursive toggles cmd.env_remove("NYASH_USE_NY_COMPILER"); cmd.env_remove("NYASH_CLI_VERBOSE"); - // Enforce quiet JSON capture - cmd.env("NYASH_JSON_ONLY", "1"); + // Enforce quiet JSON capture (allow override for debug) + if std::env::var("NYASH_JSON_ONLY").is_err() { + cmd.env("NYASH_JSON_ONLY", "1"); + } // Restrict environment to avoid plugin/using drift cmd.env("NYASH_DISABLE_PLUGINS", "1"); cmd.env("NYASH_SKIP_TOML_ENV", "1"); diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs index 3a778a73..03043504 100644 --- a/src/runner/dispatch.rs +++ b/src/runner/dispatch.rs @@ -197,6 +197,16 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { } // Backend selection + if std::env::var("NYASH_EMIT_MIR_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!( + "[dispatch] backend={} file={} path=backend-select", + groups.backend.backend, filename + ); + } match groups.backend.backend.as_str() { "mir" => { crate::cli_v!( diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 338d45c0..410b2952 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -92,6 +92,19 @@ impl NyashRunner { return; } let groups = self.config.as_groups(); + + // CLI mode trace: show backend/file/args when emit-mir trace is enabled + if std::env::var("NYASH_EMIT_MIR_TRACE") + .ok() + .as_deref() + == Some("1") + { + let backend = &groups.backend.backend; + let file = groups.input.file.as_deref().unwrap_or(""); + let args = std::env::args().skip(1).collect::>().join(" "); + eprintln!("[cli] mode={} file={} args={}", backend, file, args); + } + let skip_stage1_stub = groups.emit.hako_emit_program_json || groups.emit.hako_emit_mir_json; if !skip_stage1_stub { if let Some(code) = self.maybe_run_stage1_cli_stub(&groups) { diff --git a/src/runner/modes/common_util/plugin_guard.rs b/src/runner/modes/common_util/plugin_guard.rs index 21b66454..d51456f3 100644 --- a/src/runner/modes/common_util/plugin_guard.rs +++ b/src/runner/modes/common_util/plugin_guard.rs @@ -80,6 +80,7 @@ pub fn check_and_report(strict: bool, quiet_pipe: bool, label: &str) { "[plugin/missing] {} providers not loaded: {:?}", label, missing ); + eprintln!("[plugin/missing] hint: set NYASH_DEBUG_PLUGIN=1 or NYASH_CLI_VERBOSE=1 to see plugin init errors"); emit_hints_for(&missing); if quiet_pipe { // In quiet JSON mode, avoid noisy stdout; hints are on stderr already. diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index 0285354a..1e698a58 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -14,6 +14,16 @@ impl NyashRunner { // Quiet mode for child pipelines (e.g., selfhost compiler JSON emit) let quiet_pipe = crate::config::env::env_bool("NYASH_JSON_ONLY"); + if std::env::var("NYASH_EMIT_MIR_TRACE").ok().as_deref() == Some("1") { + eprintln!( + "[runner/vm] entry file={} quiet_pipe={} mode=stage-b?{}", + filename, + quiet_pipe, + std::env::var("HAKO_STAGEB_TRACE") + .ok() + .unwrap_or_else(|| "0".to_string()) + ); + } // Enforce plugin-first policy for VM on this branch (deterministic): // - Initialize plugin host if not yet loaded @@ -170,7 +180,10 @@ impl NyashRunner { } if trace && crate::config::env::parser_stage3_enabled() { - eprintln!("[vm] Stage-3: enabled (NYASH_FEATURES/legacy env) for {}", filename); + eprintln!( + "[vm] Stage-3: enabled (NYASH_FEATURES/legacy env) for {}", + filename + ); } // Fail‑Fast (opt‑in): Hako 構文を Nyash VM 経路で実行しない @@ -513,10 +526,25 @@ impl NyashRunner { // Routing logic is centralized in join_ir_vm_bridge_dispatch module try_run_joinir_vm_bridge(&module_vm, quiet_pipe); + if std::env::var("NYASH_EMIT_MIR_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!("[runner/vm] calling execute_module"); + } match vm.execute_module(&module_vm) { Ok(ret) => { use crate::box_trait::{BoolBox, IntegerBox}; + if std::env::var("NYASH_EMIT_MIR_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!("[runner/vm] vm_result={}", ret.to_string_box().value); + } + // Extract exit code from return value let exit_code = if let Some(ib) = ret.as_any().downcast_ref::() { ib.value as i32 @@ -541,11 +569,25 @@ impl NyashRunner { if !quiet_pipe { println!("RC: {}", exit_code); } + if std::env::var("NYASH_EMIT_MIR_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!("[runner/vm] exit_code={}", exit_code); + } // Exit with the return value as exit code process::exit(exit_code); } Err(e) => { + if std::env::var("NYASH_EMIT_MIR_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!("[runner/vm] vm_error={}", e); + } eprintln!("❌ [rust-vm] VM error: {}", e); process::exit(1); } diff --git a/src/runner_plugin_init.rs b/src/runner_plugin_init.rs index e1dce5b9..531b938e 100644 --- a/src/runner_plugin_init.rs +++ b/src/runner_plugin_init.rs @@ -40,80 +40,96 @@ pub fn init_bid_plugins() { } let cfg_path = resolve_plugin_toml(); - if let Ok(()) = init_global_plugin_host(&cfg_path) { - if plugin_debug || cli_verbose { - eprintln!("🔌 plugin host initialized from {}", cfg_path); - // Show which plugin loader backend compiled in (enabled/stub) - println!( - "[plugin-loader] backend={}", - crate::runtime::plugin_loader_v2::backend_kind() - ); - } - let host = get_global_plugin_host(); - let host = host.read().unwrap(); - if let Some(config) = host.config_ref() { - let registry = get_global_registry(); - for (lib_name, lib_def) in &config.libraries { - for box_name in &lib_def.boxes { - if plugin_debug { - eprintln!(" 📦 Registering plugin provider for {}", box_name); - } - registry.apply_plugin_config(&PluginConfig { - plugins: [(box_name.clone(), lib_name.clone())].into(), - }); - } - } + match init_global_plugin_host(&cfg_path) { + Ok(()) => { if plugin_debug || cli_verbose { - eprintln!("✅ plugin host fully configured"); + eprintln!("[plugin/init] plugin host initialized from {}", cfg_path); + // Show which plugin loader backend compiled in (enabled/stub) + println!( + "[plugin-loader] backend={}", + crate::runtime::plugin_loader_v2::backend_kind() + ); } - } - - // Optional autoload for [using.*] kind="dylib" packages - if std::env::var("NYASH_USING_DYLIB_AUTOLOAD").ok().as_deref() == Some("1") - && std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") - { - if plugin_debug || cli_verbose { - eprintln!("[using.dylib/autoload] scanning nyash.toml packages …"); - } - let mut using_paths: Vec = Vec::new(); - let mut pending_modules: std::vec::Vec<(String, String)> = Vec::new(); - let mut aliases: std::collections::HashMap = - std::collections::HashMap::new(); - let mut packages: std::collections::HashMap = - std::collections::HashMap::new(); - let _ = crate::using::resolver::populate_from_toml( - &mut using_paths, - &mut pending_modules, - &mut aliases, - &mut packages, - ); - for (name, pkg) in packages.iter() { - if let crate::using::spec::PackageKind::Dylib = pkg.kind { - // Build library name from file stem (best-effort) - let lib_name = std::path::Path::new(&pkg.path) - .file_name() - .and_then(|s| s.to_str()) - .unwrap_or(name) - .to_string(); - let host = get_global_plugin_host(); - let res = host - .read() - .unwrap() - .load_library_direct(&lib_name, &pkg.path, &[]); - if let Err(e) = res { - if plugin_debug || cli_verbose { - eprintln!("[using.dylib/autoload] failed '{}': {}", lib_name, e); + let host = get_global_plugin_host(); + let host = host.read().unwrap(); + if let Some(config) = host.config_ref() { + let registry = get_global_registry(); + for (lib_name, lib_def) in &config.libraries { + for box_name in &lib_def.boxes { + if plugin_debug { + eprintln!(" 📦 Registering plugin provider for {}", box_name); + } + registry.apply_plugin_config(&PluginConfig { + plugins: [(box_name.clone(), lib_name.clone())].into(), + }); + } + } + if plugin_debug || cli_verbose { + eprintln!("[plugin/init] ✅ plugin host fully configured"); + } + } + + // Optional autoload for [using.*] kind="dylib" packages + if std::env::var("NYASH_USING_DYLIB_AUTOLOAD").ok().as_deref() == Some("1") + && std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") + { + if plugin_debug || cli_verbose { + eprintln!("[using.dylib/autoload] scanning nyash.toml packages …"); + } + let mut using_paths: Vec = Vec::new(); + let mut pending_modules: std::vec::Vec<(String, String)> = Vec::new(); + let mut aliases: std::collections::HashMap = + std::collections::HashMap::new(); + let mut packages: std::collections::HashMap< + String, + crate::using::spec::UsingPackage, + > = std::collections::HashMap::new(); + let _ = crate::using::resolver::populate_from_toml( + &mut using_paths, + &mut pending_modules, + &mut aliases, + &mut packages, + ); + for (name, pkg) in packages.iter() { + if let crate::using::spec::PackageKind::Dylib = pkg.kind { + // Build library name from file stem (best-effort) + let lib_name = std::path::Path::new(&pkg.path) + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or(name) + .to_string(); + let host = get_global_plugin_host(); + let res = + host.read() + .unwrap() + .load_library_direct(&lib_name, &pkg.path, &[]); + if let Err(e) = res { + if plugin_debug || cli_verbose { + eprintln!("[using.dylib/autoload] failed '{}': {}", lib_name, e); + } + } else if plugin_debug || cli_verbose { + eprintln!( + "[using.dylib/autoload] loaded '{}' from {}", + lib_name, pkg.path + ); } - } else if plugin_debug || cli_verbose { - eprintln!( - "[using.dylib/autoload] loaded '{}' from {}", - lib_name, pkg.path - ); } } } } - } else if plugin_debug || cli_verbose { - eprintln!("⚠️ Failed to load plugin config (hakorune.toml/nyash.toml) - plugins disabled"); + Err(e) => { + if plugin_debug || cli_verbose { + eprintln!( + "[plugin/init] failed to initialize from {}: {}", + cfg_path, e + ); + } else { + eprintln!( + "[plugin/init] plugins disabled (config={}): {}", + cfg_path, e + ); + } + return; + } } } diff --git a/src/runtime/plugin_loader_unified.rs b/src/runtime/plugin_loader_unified.rs index cbd3dd66..c4ef8938 100644 --- a/src/runtime/plugin_loader_unified.rs +++ b/src/runtime/plugin_loader_unified.rs @@ -422,7 +422,40 @@ pub fn get_global_plugin_host() -> Arc> { pub fn init_global_plugin_host(config_path: &str) -> BidResult<()> { let host = get_global_plugin_host(); - host.write().unwrap().load_libraries(config_path)?; - host.read().unwrap().register_boxes()?; + { + let mut h = host.write().unwrap(); + let disabled = std::env::var("NYASH_DISABLE_PLUGINS") + .ok() + .as_deref() + .map(|v| v == "1" || v.eq_ignore_ascii_case("true") || v.eq_ignore_ascii_case("on")) + .unwrap_or(false); + if disabled { + eprintln!("[plugin/init] plugins disabled by NYASH_DISABLE_PLUGINS=1"); + return Err(BidError::PluginError); + } + + if !std::path::Path::new(config_path).exists() { + eprintln!( + "[plugin/init] plugins disabled (config={}): config file not found", + config_path + ); + return Err(BidError::PluginError); + } + + h.load_libraries(config_path).map_err(|e| { + eprintln!( + "[plugin/init] load_libraries({}) failed: {}", + config_path, e + ); + BidError::PluginError + })?; + h.register_boxes().map_err(|e| { + eprintln!( + "[plugin/init] register_boxes({}) failed: {}", + config_path, e + ); + BidError::PluginError + })?; + } Ok(()) } diff --git a/src/runtime/plugin_loader_v2/enabled/loader/config.rs b/src/runtime/plugin_loader_v2/enabled/loader/config.rs index 92c2897b..f8f7c04b 100644 --- a/src/runtime/plugin_loader_v2/enabled/loader/config.rs +++ b/src/runtime/plugin_loader_v2/enabled/loader/config.rs @@ -7,8 +7,10 @@ pub(super) fn load_config(loader: &mut PluginLoaderV2, config_path: &str) -> Bid .unwrap_or_else(|_| config_path.to_string()); loader.config_path = Some(canonical.clone()); loader.config = Some( - crate::config::nyash_toml_v2::NyashConfigV2::from_file(&canonical) - .map_err(|_| BidError::PluginError)?, + crate::config::nyash_toml_v2::NyashConfigV2::from_file(&canonical).map_err(|e| { + eprintln!("[plugin/init] failed to parse {}: {}", canonical, e); + BidError::PluginError + })?, ); if let Some(cfg) = loader.config.as_ref() { let mut labels: Vec = Vec::new(); diff --git a/src/runtime/plugin_loader_v2/enabled/loader/library.rs b/src/runtime/plugin_loader_v2/enabled/loader/library.rs index 773a8b00..0161b567 100644 --- a/src/runtime/plugin_loader_v2/enabled/loader/library.rs +++ b/src/runtime/plugin_loader_v2/enabled/loader/library.rs @@ -51,7 +51,15 @@ pub(super) fn load_plugin( lib_path.display() ); } - let lib = unsafe { Library::new(&lib_path) }.map_err(|_| BidError::PluginError)?; + let lib = unsafe { Library::new(&lib_path) }.map_err(|e| { + eprintln!( + "[plugin/init] dlopen failed for {} ({}): {}", + lib_name, + lib_path.display(), + e + ); + BidError::PluginError + })?; let lib_arc = Arc::new(lib); unsafe { diff --git a/src/tests/helpers/joinir_env.rs b/src/tests/helpers/joinir_env.rs new file mode 100644 index 00000000..bcf8b19d --- /dev/null +++ b/src/tests/helpers/joinir_env.rs @@ -0,0 +1,21 @@ +//! JoinIR テスト用の軽量 ENV ヘルパー +//! +//! Core/Dev のフラグを明示的にセット/クリアすることで、テスト間の競合を避ける。 + +/// Core ON (joinir_core_enabled = true) にする。 +pub fn set_core_on() { + std::env::set_var("NYASH_JOINIR_CORE", "1"); +} + +/// Core OFF (joinir_core_enabled = false) にする。 +pub fn set_core_off() { + std::env::set_var("NYASH_JOINIR_CORE", "0"); +} + +/// IfSelect/Dev 系のフラグをすべてクリアする。 +pub fn clear_joinir_flags() { + std::env::remove_var("NYASH_JOINIR_CORE"); + std::env::remove_var("HAKO_JOINIR_IF_SELECT"); + std::env::remove_var("HAKO_JOINIR_IF_SELECT_DRYRUN"); + std::env::remove_var("NYASH_JOINIR_EXPERIMENT"); +} diff --git a/src/tests/helpers/mod.rs b/src/tests/helpers/mod.rs index 0b4f500c..73da4da6 100644 --- a/src/tests/helpers/mod.rs +++ b/src/tests/helpers/mod.rs @@ -1,3 +1,4 @@ //! Phase 34-7.5: テストヘルパーモジュール pub mod joinir_frontend; +pub mod joinir_env; diff --git a/src/tests/joinir/mainline_phase49.rs b/src/tests/joinir/mainline_phase49.rs index a7032301..4abb1f5d 100644 --- a/src/tests/joinir/mainline_phase49.rs +++ b/src/tests/joinir/mainline_phase49.rs @@ -18,11 +18,13 @@ use crate::ast::ASTNode; use crate::mir::MirCompiler; use crate::parser::NyashParser; +use crate::tests::helpers::joinir_env::clear_joinir_flags; /// Phase 49-3: JoinIR Frontend mainline パイプラインが /// print_tokens 関数のコンパイル時にクラッシュしないことを確認 #[test] fn phase49_joinir_mainline_pipeline_smoke() { + clear_joinir_flags(); // Phase 49 mainline route は dev フラグで制御 std::env::set_var("HAKO_JOINIR_PRINT_TOKENS_MAIN", "1"); std::env::set_var("NYASH_JOINIR_MAINLINE_DEBUG", "1"); @@ -74,7 +76,7 @@ static box Main { ); // クリーンアップ - std::env::remove_var("HAKO_JOINIR_PRINT_TOKENS_MAIN"); + clear_joinir_flags(); std::env::remove_var("NYASH_JOINIR_MAINLINE_DEBUG"); std::env::remove_var("NYASH_FEATURES"); std::env::remove_var("NYASH_DISABLE_PLUGINS"); @@ -83,6 +85,7 @@ static box Main { /// Phase 49-3: dev フラグ OFF 時は従来経路を使用することを確認 #[test] fn phase49_joinir_mainline_fallback_without_flag() { + clear_joinir_flags(); // dev フラグ OFF std::env::remove_var("HAKO_JOINIR_PRINT_TOKENS_MAIN"); std::env::set_var("NYASH_FEATURES", "stage3"); @@ -125,6 +128,7 @@ static box Main { ); // クリーンアップ + clear_joinir_flags(); std::env::remove_var("NYASH_FEATURES"); std::env::remove_var("NYASH_DISABLE_PLUGINS"); } @@ -135,6 +139,7 @@ static box Main { /// 両方が正常に完了することを確認する。 #[test] fn phase49_joinir_mainline_ab_comparison() { + clear_joinir_flags(); let src = r#" box JsonTokenizer { tokens: ArrayBox @@ -206,7 +211,7 @@ static box Main { // Future: Add execution comparison // クリーンアップ - std::env::remove_var("HAKO_JOINIR_PRINT_TOKENS_MAIN"); + clear_joinir_flags(); std::env::remove_var("NYASH_FEATURES"); std::env::remove_var("NYASH_DISABLE_PLUGINS"); } @@ -219,6 +224,7 @@ static box Main { /// ArrayExtBox.filter 関数のコンパイル時にクラッシュしないことを確認 #[test] fn phase49_joinir_array_filter_smoke() { + clear_joinir_flags(); // Phase 49-4 mainline route は dev フラグで制御 std::env::set_var("HAKO_JOINIR_ARRAY_FILTER_MAIN", "1"); std::env::set_var("NYASH_JOINIR_MAINLINE_DEBUG", "1"); @@ -264,7 +270,7 @@ static box Main { ); // クリーンアップ - std::env::remove_var("HAKO_JOINIR_ARRAY_FILTER_MAIN"); + clear_joinir_flags(); std::env::remove_var("NYASH_JOINIR_MAINLINE_DEBUG"); std::env::remove_var("NYASH_FEATURES"); std::env::remove_var("NYASH_DISABLE_PLUGINS"); @@ -273,6 +279,7 @@ static box Main { /// Phase 49-4: dev フラグ OFF 時は従来経路を使用することを確認 #[test] fn phase49_joinir_array_filter_fallback() { + clear_joinir_flags(); // dev フラグ OFF std::env::remove_var("HAKO_JOINIR_ARRAY_FILTER_MAIN"); std::env::set_var("NYASH_FEATURES", "stage3"); @@ -315,6 +322,7 @@ static box Main { ); // クリーンアップ + clear_joinir_flags(); std::env::remove_var("NYASH_FEATURES"); std::env::remove_var("NYASH_DISABLE_PLUGINS"); } @@ -323,6 +331,7 @@ static box Main { /// ArrayExtBox.filter版 #[test] fn phase49_joinir_array_filter_ab_comparison() { + clear_joinir_flags(); let src = r#" static box ArrayExtBox { filter(arr, pred) { @@ -388,7 +397,7 @@ static box Main { ); // クリーンアップ - std::env::remove_var("HAKO_JOINIR_ARRAY_FILTER_MAIN"); + clear_joinir_flags(); std::env::remove_var("NYASH_FEATURES"); std::env::remove_var("NYASH_DISABLE_PLUGINS"); } diff --git a/src/tests/mir_funcscanner_parse_params_trim_min.rs b/src/tests/mir_funcscanner_parse_params_trim_min.rs index b4601690..cbb4ac66 100644 --- a/src/tests/mir_funcscanner_parse_params_trim_min.rs +++ b/src/tests/mir_funcscanner_parse_params_trim_min.rs @@ -47,6 +47,24 @@ fn mir_funcscanner_parse_params_trim_min_verify_and_vm() { compiled.module.functions.len() ); + // Optional MIR dump for targeted functions when NYASH_MIR_TEST_DUMP=1 + if std::env::var("NYASH_MIR_TEST_DUMP").ok().as_deref() == Some("1") { + use crate::mir::MirPrinter; + let printer = MirPrinter::new(); + for name in [ + "FuncScannerBox.parse_params/1", + "FuncScannerBox.trim/1", + "main", + ] { + if let Some(func) = compiled.module.functions.get(name) { + let dump = printer.print_function(func); + eprintln!("----- MIR DUMP: {} -----\n{}", name, dump); + } else { + eprintln!("[parse-params-trim/min] WARN: function not found: {}", name); + } + } + } + // MIR verify: ここで Undefined / dominator エラーが出るかを見る。 let mut verifier = MirVerifier::new(); if let Err(errors) = verifier.verify_module(&compiled.module) { diff --git a/src/tests/mir_joinir_if_select.rs b/src/tests/mir_joinir_if_select.rs index db7c1b58..5af017c1 100644 --- a/src/tests/mir_joinir_if_select.rs +++ b/src/tests/mir_joinir_if_select.rs @@ -8,6 +8,20 @@ mod tests { use crate::mir::join_ir::JoinInst; use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, ValueId}; use std::collections::BTreeMap; + use std::env; + + fn strict_if_env_guard() -> impl Drop { + env::set_var("NYASH_JOINIR_CORE", "1"); + env::set_var("NYASH_JOINIR_STRICT", "1"); + struct Guard; + impl Drop for Guard { + fn drop(&mut self) { + let _ = env::remove_var("NYASH_JOINIR_CORE"); + let _ = env::remove_var("NYASH_JOINIR_STRICT"); + } + } + Guard + } /// Helper to create a simple if/else function matching the "simple" pattern fn create_simple_pattern_mir() -> MirFunction { @@ -128,6 +142,11 @@ mod tests { /// 順番に: simple/local/disabled/wrong_name を確認する。 #[test] fn test_if_select_pattern_matching() { + use crate::tests::helpers::joinir_env::{clear_joinir_flags, set_core_off}; + + // 環境を明示的にリセット + clear_joinir_flags(); + // ==== 1. Simple pattern (env ON) ==== std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); @@ -185,13 +204,17 @@ mod tests { } // ==== 3. Disabled by default (env OFF) ==== + set_core_off(); std::env::remove_var("HAKO_JOINIR_IF_SELECT"); let func = create_simple_pattern_mir(); let entry_block = func.entry_block; let result = try_lower_if_to_joinir(&func, entry_block, false, None); // Phase 61-1: Pure If - assert!(result.is_none(), "Expected None when IfSelect toggle is not set"); + assert!( + result.is_none(), + "Expected None when IfSelect toggle is not set" + ); eprintln!("✅ If/Select lowering correctly disabled by default"); @@ -211,7 +234,7 @@ mod tests { eprintln!("✅ Function name filter working correctly"); // Clean up - std::env::remove_var("HAKO_JOINIR_IF_SELECT"); + clear_joinir_flags(); } // ============================================================================ @@ -291,6 +314,7 @@ mod tests { #[test] fn test_if_select_simple_with_verify() { + let _env = strict_if_env_guard(); use crate::mir::join_ir::verify::verify_select_minimal; // Create simple pattern JoinIR @@ -309,6 +333,7 @@ mod tests { #[test] fn test_if_select_local_with_verify() { + let _env = strict_if_env_guard(); use crate::mir::join_ir::verify::verify_select_minimal; // Create local pattern JoinIR @@ -327,6 +352,7 @@ mod tests { #[test] fn test_if_select_verify_rejects_multiple_selects() { + let _env = strict_if_env_guard(); use crate::mir::join_ir::verify::verify_select_minimal; // Create JoinIR with 2 Select instructions (invalid) @@ -357,6 +383,7 @@ mod tests { #[test] fn test_if_select_verify_checks_invariants() { + let _env = strict_if_env_guard(); use crate::mir::join_ir::verify::verify_select_minimal; // Create valid JoinIR @@ -517,6 +544,7 @@ mod tests { fn test_if_merge_simple_pattern() { use crate::mir::join_ir::JoinInst; + let _env = strict_if_env_guard(); std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); let func = create_if_merge_simple_pattern_mir(); @@ -558,6 +586,7 @@ mod tests { fn test_if_merge_multiple_pattern() { use crate::mir::join_ir::JoinInst; + let _env = strict_if_env_guard(); std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); let func = create_if_merge_multiple_pattern_mir(); @@ -653,6 +682,7 @@ mod tests { fn test_type_hint_propagation_simple() { use crate::mir::MirType; + let _env = strict_if_env_guard(); std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); let func = create_simple_pattern_mir_with_const(); @@ -690,6 +720,7 @@ mod tests { fn test_p1_ab_type_inference() { use crate::mir::MirType; + let _env = strict_if_env_guard(); std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); // P1 Simple pattern で Select 生成 @@ -729,6 +760,7 @@ mod tests { use crate::mir::join_ir::lowering::if_merge::IfMergeLowerer; use crate::mir::MirType; + let _env = strict_if_env_guard(); std::env::set_var("NYASH_JOINIR_IF_MERGE", "1"); // P2 IfMerge Simple pattern で IfMerge 生成 diff --git a/src/tests/mir_joinir_stage1_using_resolver_min.rs b/src/tests/mir_joinir_stage1_using_resolver_min.rs index 39036dc4..9040667a 100644 --- a/src/tests/mir_joinir_stage1_using_resolver_min.rs +++ b/src/tests/mir_joinir_stage1_using_resolver_min.rs @@ -27,6 +27,11 @@ use crate::mir::{MirCompiler, ValueId}; use crate::parser::NyashParser; use std::collections::BTreeMap; +fn ensure_joinir_strict_env() { + std::env::set_var("NYASH_JOINIR_CORE", "1"); + std::env::set_var("NYASH_JOINIR_STRICT", "1"); +} + #[test] #[ignore] // 手動実行用(Stage-1 minimal) fn mir_joinir_stage1_using_resolver_auto_lowering() { @@ -108,6 +113,7 @@ fn mir_joinir_stage1_using_resolver_auto_lowering() { fn mir_joinir_stage1_using_resolver_type_sanity() { // Phase 27.12: 型定義の基本的なサニティチェック(常時実行) // stage1_using_resolver 用の JoinFunction が作成できることを確認 + ensure_joinir_strict_env(); let resolve_id = JoinFuncId::new(20); let resolve_func = JoinFunction::new( @@ -126,6 +132,7 @@ fn mir_joinir_stage1_using_resolver_type_sanity() { fn mir_joinir_stage1_using_resolver_empty_module_returns_none() { // Phase 27.13: 空の MIR モジュールでは None を返すことを確認 // Stage1UsingResolverBox.resolve_for_source/1 関数が存在しない場合のフォールバック動作 + ensure_joinir_strict_env(); // 最小限の MIR モジュールを作成 use crate::mir::MirModule; diff --git a/src/tests/mir_stage1_staticcompiler_receiver.rs b/src/tests/mir_stage1_staticcompiler_receiver.rs index 5ec0541e..e38e02ac 100644 --- a/src/tests/mir_stage1_staticcompiler_receiver.rs +++ b/src/tests/mir_stage1_staticcompiler_receiver.rs @@ -35,6 +35,11 @@ fn ensure_stage3_env() { std::env::set_var("HAKO_ENABLE_USING", "1"); } +fn ensure_joinir_strict_env() { + std::env::set_var("NYASH_JOINIR_CORE", "1"); + std::env::set_var("NYASH_JOINIR_STRICT", "1"); +} + /// StringHelpers.skip_ws + 最小 Main のテスト用フィクスチャ。 /// Stage‑1 CLI 全体を読み込まずに、StringHelpers 内部の `.length()` 正規化だけを確認する。 fn stage1_staticcompiler_fixture_src() -> String { @@ -62,6 +67,7 @@ static box Main { #[test] fn mir_stage1_staticcompiler_receiver_compiles_and_verifies() { ensure_stage3_env(); + ensure_joinir_strict_env(); let src = stage1_staticcompiler_fixture_src(); let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok"); @@ -81,6 +87,7 @@ fn mir_stage1_staticcompiler_receiver_compiles_and_verifies() { #[test] fn mir_stage1_staticcompiler_receiver_exec_succeeds() { ensure_stage3_env(); + ensure_joinir_strict_env(); std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); // Plugin依存を排除 let src = stage1_staticcompiler_fixture_src(); @@ -113,6 +120,7 @@ fn mir_stage1_staticcompiler_receiver_exec_succeeds() { #[test] fn mir_stage1_staticcompiler_receiver_normalizes_to_stringbox() { ensure_stage3_env(); + ensure_joinir_strict_env(); let src = stage1_staticcompiler_fixture_src(); let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok"); diff --git a/src/tests/phase67_generic_type_resolver.rs b/src/tests/phase67_generic_type_resolver.rs index 8ad51aa2..81afcaf5 100644 --- a/src/tests/phase67_generic_type_resolver.rs +++ b/src/tests/phase67_generic_type_resolver.rs @@ -41,7 +41,9 @@ fn phase67_type_hint_policy_p3c_integration() { // 一般関数は P3-C 候補 assert!(TypeHintPolicy::is_p3c_target("ArrayProcessor.process/1")); - assert!(TypeHintPolicy::is_p3c_target("GenericTypeGetTest.array_get/0")); + assert!(TypeHintPolicy::is_p3c_target( + "GenericTypeGetTest.array_get/0" + )); // is_target と is_p3c_target は排他的 let p3c_func = "GenericTypeGetTest.array_get/0"; @@ -128,7 +130,8 @@ fn phase67_ab_test_resolve_from_phi_equivalence() { inputs: vec![(then_bb, v2), (else_bb, v3)], type_hint: None, // P3-C: type_hint なし }); - f.get_block_mut(merge_bb).unwrap().terminator = Some(MirInstruction::Return { value: Some(v4) }); + f.get_block_mut(merge_bb).unwrap().terminator = + Some(MirInstruction::Return { value: Some(v4) }); // 型情報を設定 let mut types: BTreeMap = BTreeMap::new(); @@ -139,6 +142,13 @@ fn phase67_ab_test_resolve_from_phi_equivalence() { let result_a = crate::mir::phi_core::if_phi::infer_type_from_phi(&f, v4, &types); let result_b = GenericTypeResolver::resolve_from_phi(&f, v4, &types); - assert_eq!(result_a, result_b, "A/B test: both routes should return the same type"); - assert_eq!(result_a, Some(MirType::Integer), "Type should be inferred as Integer"); + assert_eq!( + result_a, result_b, + "A/B test: both routes should return the same type" + ); + assert_eq!( + result_a, + Some(MirType::Integer), + "Type should be inferred as Integer" + ); } diff --git a/tools/hakorune_emit_mir.sh b/tools/hakorune_emit_mir.sh index 0a50145f..5431de65 100644 --- a/tools/hakorune_emit_mir.sh +++ b/tools/hakorune_emit_mir.sh @@ -24,6 +24,13 @@ else ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" fi +RAW_KEEP="${NYASH_EMIT_MIR_KEEP_RAW:-0}" +RAW_DIR="${NYASH_EMIT_MIR_RAW_DIR:-$ROOT/logs/emit_mir}" +if [ "$RAW_KEEP" = "1" ]; then + mkdir -p "$RAW_DIR" 2>/dev/null || RAW_KEEP=0 +fi +timestamp_now() { date +%Y%m%d_%H%M%S; } + # Resolve nyash/hakorune binary via test_runner helper (ensures consistent env) if [ ! -f "$IN" ]; then echo "[FAIL] input not found: $IN" >&2 @@ -154,6 +161,39 @@ PY fi export HAKO_MIRBUILDER_IMPORTS="$IMPORTS_JSON" +# Run direct emit with optional raw capture (label helps identify caller) +run_direct_emit() { + local label="$1" out_path="$2" in_path="$3" + local rc=0 + if [ "$RAW_KEEP" = "1" ]; then + local tmp_out + tmp_out=$(mktemp --suffix .emit_direct_raw) + "$NYASH_BIN" --emit-mir-json "$out_path" "$in_path" >"$tmp_out" 2>&1 || rc=$? + local ts=$(timestamp_now) + local log_path="$RAW_DIR/direct_emit_${label}_${ts}_$$.log" + { + local raw_len=$(wc -c < "$tmp_out" | tr -d ' ') + echo "[emit/raw] label=$label rc=$rc raw_len=$raw_len" + cat "$tmp_out" + } > "$log_path" 2>/dev/null || true + rm -f "$tmp_out" 2>/dev/null || true + else + "$NYASH_BIN" --emit-mir-json "$out_path" "$in_path" >/dev/null 2>&1 || rc=$? + fi + if [ $rc -ne 0 ]; then + # Quick stderr summary to avoid opening the raw file + local raw_len=0 + if [ -f "${tmp_out:-}" ]; then + raw_len=$(wc -c < "${tmp_out:-}" | tr -d ' ') + echo "[emit/direct] rc=$rc raw_len=$raw_len (head 100 lines)" >&2 + head -n 100 "${tmp_out:-}" >&2 || true + else + echo "[emit/direct] rc=$rc (no tmp_out captured)" >&2 + fi + fi + return $rc +} + # Check if FORCE jsonfrag mode is requested (bypasses Stage-B entirely) if [ "${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-0}" = "1" ]; then # Extract limit from code using grep/awk @@ -338,14 +378,34 @@ if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then echo "[emit:trace] Stage-B: Starting parse of input (${code_len} chars)..." >&2 fi +stageb_raw_log="" +# Decide Stage-B entry (direct compiler_stageb or compiler.hako --stage-b) +stageb_target="$ROOT/lang/src/compiler/entry/compiler.hako" +stageb_extra=(--stage-b --stage3) +# Legacy: allow direct compiler_stageb.hako when explicitly requested +if [ "${NYASH_EMIT_USE_COMPILER:-1}" = "0" ]; then + stageb_target="$ROOT/lang/src/compiler/entry/compiler_stageb.hako" + stageb_extra=() +fi +# Optional: log which entry we picked +if [ "${NYASH_EMIT_MIR_TRACE:-0}" = "1" ]; then + echo "[emit/trace] stageb target=$stageb_target extra=${stageb_extra[*]:-}" >&2 +fi # Run Stage-B with temp file (avoid subshell CODE variable expansion) PROG_JSON_RAW=$(cd "$ROOT" && \ - NYASH_JSON_ONLY=1 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ + NYASH_JSON_ONLY=${NYASH_JSON_ONLY:-1} NYASH_DISABLE_NY_COMPILER=${NYASH_DISABLE_NY_COMPILER:-1} HAKO_DISABLE_NY_COMPILER=${HAKO_DISABLE_NY_COMPILER:-1} \ HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ NYASH_ENABLE_USING=${NYASH_ENABLE_USING:-1} HAKO_ENABLE_USING=${HAKO_ENABLE_USING:-1} \ - "$NYASH_BIN" --backend vm "$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- --source "$(cat "$CODE_TMP")" 2>&1) + "$NYASH_BIN" --backend vm "$stageb_target" -- "${stageb_extra[@]}" --source "$(cat "$CODE_TMP")" 2>&1) rc=$? +stageb_rc=$rc + +# Optional raw trace to stderr for debugging extraction/filter問題 +if [ "${NYASH_EMIT_MIR_TRACE:-0}" = "1" ]; then + echo "[emit/trace] Stage-B raw len=${#PROG_JSON_RAW} rc=${stageb_rc}" >&2 + printf '%s\n' "$PROG_JSON_RAW" >&2 +fi if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then echo "[emit:trace] Stage-B: Raw output length=${#PROG_JSON_RAW} chars, rc=$rc" >&2 @@ -354,6 +414,22 @@ fi # Extract Program JSON from raw output PROG_JSON_OUT=$(extract_program_json "$PROG_JSON_RAW" 2>/dev/null || true) extract_rc=$? +stageb_hit=$(printf '%s' "$PROG_JSON_OUT" | grep -c '"kind":"Program"' || true) +stageb_raw_len=${#PROG_JSON_RAW} +echo "[emit/stageb] rc_stageb=${stageb_rc} extract_rc=${extract_rc} raw_len=${stageb_raw_len} program_hits=${stageb_hit}" >&2 +if [ "$RAW_KEEP" = "1" ]; then + ts=$(timestamp_now) + stageb_raw_log="$RAW_DIR/stageb_emit_${ts}_$$.log" + { + echo "[emit/raw] cmd=${stageb_target##*/} rc_stageb=${stageb_rc} extract_rc=${extract_rc} raw_len=${stageb_raw_len} program_hits=${stageb_hit}" + echo "[emit/raw] src=$IN" + echo "[emit/raw] --- stdout+stderr ---" + printf '%s' "$PROG_JSON_RAW" + } > "$stageb_raw_log" 2>/dev/null || true + if [ $extract_rc -eq 0 ] && [ -n "$PROG_JSON_OUT" ]; then + printf '%s' "$PROG_JSON_OUT" > "$RAW_DIR/stageb_emit_${ts}_$$.program.json" 2>/dev/null || true + fi +fi if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then if [ $extract_rc -eq 0 ] && [ -n "$PROG_JSON_OUT" ]; then @@ -372,26 +448,35 @@ if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then fi # Update rc to reflect extraction result +stageb_ok=1 if [ $extract_rc -ne 0 ] || [ -z "$PROG_JSON_OUT" ]; then rc=1 + stageb_ok=0 fi set -e # If Stage-B fails, skip to direct MIR emit paths (provider/legacy) -if [ $rc -ne 0 ] || [ -z "$PROG_JSON_OUT" ]; then +if [ $rc -ne 0 ] || [ -z "$PROG_JSON_OUT" ] || [ "$stageb_hit" -eq 0 ]; then # Diagnose common Stage‑B errors before falling back diagnose_stageb_failure "$PROG_JSON_RAW" # Stage-B not available - fall back to legacy CLI path directly # Skip the intermediate Program(JSON) step and emit MIR directly + echo "[emit/stageb] failed_or_empty stageb_ok=${stageb_ok} rc_stageb=${stageb_rc} extract_rc=${extract_rc} hits=${stageb_hit}" >&2 + if [ "${NYASH_EMIT_MIR_TRACE:-0}" = "1" ]; then + echo "[emit/stageb] raw_len=${stageb_raw_len}" >&2 + fi + direct_ok=0 if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \ HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \ HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \ NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \ NYASH_MIR_UNIFIED_CALL=${NYASH_MIR_UNIFIED_CALL:-1} \ - "$NYASH_BIN" --emit-mir-json "$OUT" "$IN" >/dev/null 2>&1; then + run_direct_emit "fallback" "$OUT" "$IN"; then + direct_ok=1 echo "[OK] MIR JSON written (direct-emit): $OUT" exit 0 fi + echo "[emit/direct] failed rc_stageb=${stageb_rc} extract_rc=${extract_rc} direct_ok=${direct_ok} stageb_hits=${stageb_hit}" >&2 echo "[FAIL] Stage-B and direct MIR emit both failed" >&2 exit 1 fi @@ -400,15 +485,18 @@ fi if ! printf '%s' "$PROG_JSON_OUT" | grep -q '"kind"\s*:\s*"Program"'; then # Invalid Program JSON - fall back to direct emit(事前に簡単な診断を出す) diagnose_stageb_failure "$PROG_JSON_RAW" + direct_ok=0 if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \ HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \ HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \ NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \ NYASH_MIR_UNIFIED_CALL=${NYASH_MIR_UNIFIED_CALL:-1} \ - "$NYASH_BIN" --emit-mir-json "$OUT" "$IN" >/dev/null 2>&1; then + run_direct_emit "invalid_program" "$OUT" "$IN"; then + direct_ok=1 echo "[OK] MIR JSON written (direct-emit-fallback): $OUT" exit 0 fi + echo "[emit/direct] invalid_program rc_stageb=${stageb_rc} extract_rc=${extract_rc} direct_ok=${direct_ok} stageb_hits=${stageb_hit}" >&2 echo "[FAIL] Stage‑B output invalid and direct emit failed" >&2 exit 1 fi diff --git a/tools/selfhost/selfhost_build.sh b/tools/selfhost/selfhost_build.sh index b8a1916b..a4782b7a 100644 --- a/tools/selfhost/selfhost_build.sh +++ b/tools/selfhost/selfhost_build.sh @@ -21,6 +21,12 @@ if [ -z "${BIN}" ]; then elif [ -x "$ROOT/target/release/nyash" ]; then BIN="$ROOT/target/release/nyash"; else echo "[selfhost] error: NYASH_BIN not set and no binary found under target/release" >&2; exit 2; fi fi +RAW_KEEP="${NYASH_SELFHOST_KEEP_RAW:-0}" +RAW_DIR="${NYASH_SELFHOST_RAW_DIR:-$ROOT/logs/selfhost}" +if [ "$RAW_KEEP" = "1" ]; then + mkdir -p "$RAW_DIR" 2>/dev/null || RAW_KEEP=0 +fi +timestamp_now() { date +%Y%m%d_%H%M%S; } IN="" JSON_OUT="" @@ -29,6 +35,20 @@ EXE_OUT="" DO_RUN=0 KEEP_TMP=0 +apply_selfhost_env() { + export NYASH_FEATURES="${NYASH_FEATURES:-stage3}" + export NYASH_PARSER_ALLOW_SEMICOLON=1 + export NYASH_ALLOW_USING_FILE=0 + export HAKO_ALLOW_USING_FILE=0 + export NYASH_USING_AST=1 + export NYASH_VARMAP_GUARD_STRICT=0 + export NYASH_BLOCK_SCHEDULE_VERIFY=0 + export NYASH_QUIET=0 HAKO_QUIET=0 NYASH_CLI_VERBOSE=0 + # Ensure core plugins (Console/Array/Map/String/Integer) are discoverable + export NYASH_PLUGIN_PATH="${NYASH_PLUGIN_PATH:-$ROOT/target/release}" + export NYASH_PLUGIN_PATHS="${NYASH_PLUGIN_PATHS:-$NYASH_PLUGIN_PATH}" +} + while [ $# -gt 0 ]; do case "$1" in --in) IN="$2"; shift 2;; @@ -48,8 +68,12 @@ tmp_json="${JSON_OUT:-/tmp/hako_stageb_$$.json}" # Emit Program(JSON v0; prefer BuildBox for emit-only when HAKO_USE_BUILDBOX=1) RAW="/tmp/hako_stageb_raw_$$.txt" +stageb_rc=0 SRC_CONTENT="$(cat "$IN")" +stageb_cmd_desc="" if [ "${HAKO_USE_BUILDBOX:-0}" = "1" ] && [ "$DO_RUN" = "0" ] && [ -z "$EXE_OUT" ]; then + apply_selfhost_env + stageb_cmd_desc="BuildBox.emit_program_json_v0 via compiler build_box" WRAP="/tmp/hako_buildbox_wrap_$$.hako" cat > "$WRAP" <<'HAKO' include "lang/src/compiler/build/build_box.hako" @@ -62,31 +86,47 @@ static box Main { method main(args) { HAKO ( export HAKO_SRC="$SRC_CONTENT" - export NYASH_QUIET=0 HAKO_QUIET=0 NYASH_CLI_VERBOSE=0 cd "$ROOT" && "$BIN" --backend vm "$WRAP" - ) > "$RAW" 2>&1 || true + ) > "$RAW" 2>&1 || stageb_rc=$? rm -f "$WRAP" 2>/dev/null || true else + apply_selfhost_env + stageb_cmd_desc="compiler.hako --stage-b --stage3" ( - export NYASH_PARSER_ALLOW_SEMICOLON=1 - export NYASH_ALLOW_USING_FILE=0 - export HAKO_ALLOW_USING_FILE=0 - export NYASH_USING_AST=1 - export NYASH_FEATURES="${NYASH_FEATURES:-stage3}" - export NYASH_VARMAP_GUARD_STRICT=0 - export NYASH_BLOCK_SCHEDULE_VERIFY=0 - export NYASH_QUIET=0 HAKO_QUIET=0 NYASH_CLI_VERBOSE=0 cd "$ROOT" && \ "$BIN" --backend vm \ - "$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- \ - --source "$SRC_CONTENT" - ) > "$RAW" 2>&1 || true + "$ROOT/lang/src/compiler/entry/compiler.hako" -- \ + --stage-b --stage3 --source "$SRC_CONTENT" + ) > "$RAW" 2>&1 || stageb_rc=$? fi -if ! awk '(/"version":0/ && /"kind":"Program"/){print;found=1;exit} END{exit(found?0:1)}' "$RAW" > "$tmp_json"; then +extract_ok=0 +if awk '(/"version":0/ && /"kind":"Program"/){print;found=1;exit} END{exit(found?0:1)}' "$RAW" > "$tmp_json"; then + extract_ok=1 +fi + +if [ "$RAW_KEEP" = "1" ]; then + ts="$(timestamp_now)" + raw_log="$RAW_DIR/stageb_${ts}_$$.log" + { + echo "[selfhost/raw] cmd: ${stageb_cmd_desc:-unknown}" + echo "[selfhost/raw] rc_stageb=${stageb_rc} extract_ok=${extract_ok}" + echo "[selfhost/raw] src=${IN}" + echo "[selfhost/raw] --- stdout+stderr ---" + cat "$RAW" + } > "$raw_log" 2>/dev/null || true + if [ "$extract_ok" = "1" ] && [ -s "$tmp_json" ]; then + cp "$tmp_json" "$RAW_DIR/stageb_${ts}_$$.json" 2>/dev/null || true + fi +fi + +if [ "$extract_ok" != "1" ]; then echo "[selfhost] Stage‑B emit failed" >&2 tail -n 120 "$RAW" >&2 || true - rm -f "$RAW" 2>/dev/null || true + if [ "$RAW_KEEP" = "1" ] && [ -n "${raw_log:-}" ]; then + echo "[selfhost/debug] RAW log: $raw_log" >&2 + fi + if [ "$KEEP_TMP" != "1" ]; then rm -f "$RAW" 2>/dev/null || true; fi exit 1 fi rm -f "$RAW" 2>/dev/null || true diff --git a/tools/smokes/v2/README.md b/tools/smokes/v2/README.md index 99c90674..8889324d 100644 --- a/tools/smokes/v2/README.md +++ b/tools/smokes/v2/README.md @@ -20,6 +20,7 @@ Notes - Avoid running heavy integration smokes in CI by default. Use `--profile quick`. - When a test depends on external tools (e.g., LLVM), prefer `[SKIP:]` over failure. - Stage‑B/selfhost canaries(`stage1_launcher_*`, `phase251*` など)は Stage‑3 デフォルト環境で安定しないため、quick プロファイルでは `[SKIP:stageb]` として扱い、必要に応じて別プロファイル(integration/full)で個別に実行する。 +- Selfhost quick カバレッジは最小 1 本(`core/selfhost_minimal.sh`)に絞り、Stage‑3 + JoinIR 前提で Stage‑B→VM を通るかだけを確認する。 - S3 backend 向けの長尺テスト群も quick 向きではないため、timeout を短く保ちたい場合は `[SKIP:slow]` にして別途ローカルで回すことを推奨する。 Quick tips diff --git a/tools/smokes/v2/lib/test_runner.sh b/tools/smokes/v2/lib/test_runner.sh index 228b76e4..87c6fdef 100644 --- a/tools/smokes/v2/lib/test_runner.sh +++ b/tools/smokes/v2/lib/test_runner.sh @@ -20,6 +20,8 @@ fi # Stage-3 is default: prefer feature flag instead of legacy parser envs. export NYASH_FEATURES="${NYASH_FEATURES:-stage3}" +# JoinIR Core は smokes 実行時のみ既定ON(明示設定があればそれを優先) +export NYASH_JOINIR_CORE="${NYASH_JOINIR_CORE:-1}" # Debug convenience: HAKO_DEBUG=1 enables execution trace and log passthrough if [ "${HAKO_DEBUG:-0}" = "1" ]; then @@ -95,6 +97,7 @@ log_error() { | grep -v "^\\[DEBUG/" \ | grep -v "^\\[ssa-undef-debug\\]" \ | grep -v '^\[PluginBoxFactory\]' \ + | grep -v '^\[plugin/init\]' \ | grep -v '^\[using.dylib/autoload\]' \ | grep -v "^\[vm\] Stage-3" \ | grep -v "^\[DEBUG\]" \ diff --git a/tools/smokes/v2/profiles/quick/core/phase2100/s3_backend_selector_crate_exe_vm_parity_return42_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2100/s3_backend_selector_crate_exe_vm_parity_return42_canary_vm.sh index 299d54b3..25f0798b 100644 --- a/tools/smokes/v2/profiles/quick/core/phase2100/s3_backend_selector_crate_exe_vm_parity_return42_canary_vm.sh +++ b/tools/smokes/v2/profiles/quick/core/phase2100/s3_backend_selector_crate_exe_vm_parity_return42_canary_vm.sh @@ -4,6 +4,10 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/../../../../../../.." && pwd)" source "$ROOT/tools/smokes/v2/lib/test_runner.sh" || true +# Stage-B emit が不安定なため quick ではスキップ(s3 parity は Stage-B ラインで扱う) +echo "[SKIP] s3_backend_selector_crate_exe_vm_parity_return42_canary_vm (Stage-B emit 不安定のため quick ではスキップ)" >&2 +exit 0 + # Ensure tools are built and environment is consistent for EXE timeout "${HAKO_BUILD_TIMEOUT:-10}" bash -c "cd \"$ROOT\" && cargo build -q --release -p nyash-llvm-compiler >/dev/null" || true timeout "${HAKO_BUILD_TIMEOUT:-10}" bash -c "cd \"$ROOT/crates/nyash_kernel\" && cargo build -q --release >/dev/null" || true diff --git a/tools/smokes/v2/profiles/quick/core/selfhost_minimal.sh b/tools/smokes/v2/profiles/quick/core/selfhost_minimal.sh new file mode 100644 index 00000000..21e363af --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/selfhost_minimal.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# selfhost_minimal.sh — Minimal selfhost Stage‑B→VM path using stage1_run_min.hako + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then + ROOT="$ROOT_GIT" +else + ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)" +fi + +BIN="${NYASH_BIN:-$ROOT/target/release/nyash}" +SELFHOST="$ROOT/tools/selfhost/selfhost_build.sh" +TARGET="$ROOT/apps/tests/stage1_run_min.hako" + +warn() { echo -e "[WARN] $*" >&2; } +info() { echo -e "[INFO] $*" >&2; } +fail() { echo -e "[FAIL] $*" >&2; exit 1; } +pass() { echo -e "[PASS] $*" >&2; } + +if [ ! -x "$BIN" ]; then + warn "[SKIP] nyash binary not found at $BIN (build release first)" + exit 0 +fi + +if [ ! -x "$SELFHOST" ]; then + warn "[SKIP] selfhost_build.sh missing at $SELFHOST" + exit 0 +fi + +if [ ! -f "$TARGET" ]; then + warn "[SKIP] target fixture not found: $TARGET" + exit 0 +fi + +info "Running minimal selfhost path via selfhost_build.sh" +set +e +NYASH_FEATURES="${NYASH_FEATURES:-stage3}" \ +NYASH_USE_NY_COMPILER="${NYASH_USE_NY_COMPILER:-1}" \ +NYASH_NY_COMPILER_EMIT_ONLY="${NYASH_NY_COMPILER_EMIT_ONLY:-1}" \ + "$SELFHOST" --in "$TARGET" --run +rc=$? +set -e + +if [ $rc -ne 0 ]; then + fail "selfhost_minimal failed (rc=$rc)" +fi + +pass "selfhost_minimal passed (stage1_run_min.hako)" +exit 0 diff --git a/tools/smokes/v2/profiles/quick/core/stageb_min_emit.sh b/tools/smokes/v2/profiles/quick/core/stageb_min_emit.sh new file mode 100644 index 00000000..1acaf784 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/stageb_min_emit.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")" && git rev-parse --show-toplevel 2>/dev/null || true)" +if [ -z "$ROOT" ]; then + ROOT="$(cd "$(dirname "$0")/../../../../../../.." && pwd)" +fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" || true + +INPUT="$ROOT/apps/tests/emit_boxcall_length_canary_vm.hako" +if [ ! -f "$INPUT" ]; then + echo "[SKIP] stageb_min_emit: input not found: $INPUT" >&2 + exit 0 +fi + +TMP_JSON=$(mktemp --suffix .json) +trap 'rm -f "$TMP_JSON" 2>/dev/null || true' EXIT + +# Stage‑B emit (RAW 保存は dev 用) +if ! NYASH_EMIT_MIR_KEEP_RAW=1 NYASH_EMIT_USE_COMPILER=1 NYASH_EMIT_MIR_TRACE="${NYASH_EMIT_MIR_TRACE:-0}" \ + NYASH_FEATURES="${NYASH_FEATURES:-stage3}" \ + bash "$ROOT/tools/hakorune_emit_mir.sh" "$INPUT" "$TMP_JSON" >/dev/null 2>&1; then + echo "[FAIL] stageb_min_emit: failed to emit MIR JSON" >&2 + exit 1 +fi + +if [ ! -s "$TMP_JSON" ]; then + echo "[FAIL] stageb_min_emit: JSON output missing or empty" >&2 + exit 1 +fi + +echo "[PASS] stageb_min_emit" +exit 0 diff --git a/tools/smokes/v2/run.sh b/tools/smokes/v2/run.sh index 31778668..a7e13dc9 100644 --- a/tools/smokes/v2/run.sh +++ b/tools/smokes/v2/run.sh @@ -186,6 +186,11 @@ setup_environment() { # プロファイル専用設定 export SMOKES_CURRENT_PROFILE="$PROFILE" + # Phase 80: quick プロファイルは JoinIR Core を既定ONで回してみる(明示指定があれば尊重) + if [ "$PROFILE" = "quick" ] && [ -z "${NYASH_JOINIR_CORE+x}" ]; then + export NYASH_JOINIR_CORE=1 + log_info "JoinIR Core default ON for quick profile (NYASH_JOINIR_CORE=1)" + fi # コマンドライン引数の環境変数設定 if [ -n "$TIMEOUT" ]; then