JoinIR/SSA/Stage-3: sync CURRENT_TASK and dev env

This commit is contained in:
nyash-codex
2025-12-01 11:10:46 +09:00
parent a3d5bacc55
commit 8633224061
52 changed files with 974 additions and 256 deletions

View File

@ -17,12 +17,14 @@
- 制御構造と PHI の意味論は **JoinIRLoopScopeShape/IfPhiContext 等の薄い箱)** に一本化する。 - 制御構造と PHI の意味論は **JoinIRLoopScopeShape/IfPhiContext 等の薄い箱)** に一本化する。
- 実行の SSOT は VM / LLVM ラインとし、JoinIR→MIR→VM/LLVM は「構造 SSOT → 実行 SSOT」への変換として扱う。 - 実行の SSOT は VM / LLVM ラインとし、JoinIR→MIR→VM/LLVM は「構造 SSOT → 実行 SSOT」への変換として扱う。
- 既存の PHI 箱if_phi.rs / PhiBuilderBox / conservative.rs / Trio 等は、JoinIR 側のカバレッジが十分になったところから順に削っていく。 - 既存の 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+** - **これからPhase 69+**
- wasm/Web デモライン: JoinIR ベースの軽量デモ実装。 - wasm/Web デモライン: JoinIR ベースの軽量デモ実装。
- 最適化ライン: JoinIR の最適化パスと LLVM/ny-llvmc 統合。 - 最適化ライン: JoinIR の最適化パスと LLVM/ny-llvmc 統合。
- Trio 削除ライン: 完了Phase 70、LoopScopeShape SSOT - 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/ 移設も今後の小フェーズとして残しておく。 - 69-5: conservative.rs の docs/ 移設も今後の小フェーズとして残しておく。
- 追加完了 (Phase 70): - 追加完了 (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+ ## 2. 次の一手Phase 69+
@ -107,6 +109,15 @@
- GenericTypeResolver 経由で全 P3-C ケースをカバー - GenericTypeResolver 経由で全 P3-C ケースをカバー
- `infer_type_from_phi` 本体削除と if_phi.rs 大掃除 - `infer_type_from_phi` 本体削除と if_phi.rs 大掃除
- **Phase 71: SelfHosting 再ブートストラップdocs 起点 / SSA デバッグモードで一時停止中)**
- `docs/private/roadmap2/phases/phase-71-selfhost-reboot/README.md` で代表パス 1 本Stage3 + 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 で成功確認済みだが、StageB/SSA/JoinIR/dev verify の複合経路で Program(JSON v0) emit 前に失敗し、`StageB emit failed` で selfhost_minimal が落ちている。
- RAW 観測フラグ追加済み: `NYASH_SELFHOST_KEEP_RAW` / `NYASH_EMIT_MIR_KEEP_RAW` で StageB 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` は緑、StageB RAW から ParserBox.* の `ssa-undef-debug` は消えたが、emit 失敗は継続dev verify/birth 警告は残存)。
- dev verify 緩和トグル(`NYASH_STAGEB_DEV_VERIFY`)を実装済み。代表パスで ON/OFF を比較したが、OFF にしても emit は復活せず、SSA/StageB 本体側の欠損が疑われる。
- quick プロファイルでは JoinIR/VM 系は緑維持を目標としつつ、selfhost_minimal / stageb_min_emit は「SSA ラインの観測窓」として赤許容。StageB/SSA 起因の赤は Phase 71-SSA 側でハンドルする。
- **Phase 72: JoinIR dev フラグ棚卸し**docs + env ポリシー整備済み、配線寄せ残) - **Phase 72: JoinIR dev フラグ棚卸し**docs + env ポリシー整備済み、配線寄せ残)
- `config::env::joinir_core_enabled()` / `joinir_dev_enabled()` を追加し、Core/DevOnly/Deprecated の区分を整理 - `config::env::joinir_core_enabled()` / `joinir_dev_enabled()` を追加し、Core/DevOnly/Deprecated の区分を整理
- docs/private/roadmap2/phases/phase-72-joinir-dev-flags/README.md に一覧表を追加済み - docs/private/roadmap2/phases/phase-72-joinir-dev-flags/README.md に一覧表を追加済み
@ -142,6 +153,17 @@
- `MirFunction.blocks: HashMap``BTreeMap` で非決定的テスト解消 - `MirFunction.blocks: HashMap``BTreeMap` で非決定的テスト解消
- Phase 25.1 同様のパターン適用 - Phase 25.1 同様のパターン適用
- Phase 71-SSA: StageB / 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/Loopmir_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 / Stage1/StageB 代表)では、
JoinIR lowering / VM ブリッジ失敗を即エラー扱いとし、レガシー経路は「未対応関数専用」に縮退させる。
--- ---
## 3. 旧フェーズ(過去ログへのポインタ) ## 3. 旧フェーズ(過去ログへのポインタ)

View File

@ -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()
}
}

View File

@ -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 } local flags = { emit: 0, ret: null, source: null, stage_b: 0, prefer_cfg: 1, stage3: 0, v1_compat: 0 }
if args == null { return flags } 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 i = 0
local n = args.length() local n = args.length()
loop(i < n) { loop(i < n) {
local token = "" + args.get(i) local token = "" + args.get(i)
if trace_on == 1 {
print("[compiler/flags] arg[" + ("" + i) + "]=" + token)
}
if token == "--min-json" { if token == "--min-json" {
flags.emit = 1 flags.emit = 1
} else if token == "--stage-b" { } else if token == "--stage-b" {
@ -62,6 +69,14 @@ static box Main {
} }
i = i + 1 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 return flags
} }
@ -478,12 +493,30 @@ static box Main {
} }
main(args) { 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 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 { if flags.stage_b == 1 {
// Phase 28.2 Quick Win 3: Direct call to SSOT // StageB 経路は SSOT: StageBDriverBox.main をそのまま叩く(末尾で print(ast_json) 済み)
local json = StageBDriverBox.compile(flags.source, flags.prefer_cfg, flags.stage3, flags.v1_compat) // stdout / stderr 両方にタグを出して、実行経路を確実に観測する
print(json) print("[compiler/main] enter stage-b args=" + ("" + args))
return 0 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 { if flags.emit == 1 {
local json = me._compile_source_to_json_v0(flags.source) local json = me._compile_source_to_json_v0(flags.source)

View File

@ -1236,6 +1236,9 @@ static box StageBDriverBox {
} }
main(args) { main(args) {
// Dev trace to confirm entry dispatch
print("[stageb/main] enter")
// ============================================================================ // ============================================================================
// Phase 25.1c: Guaranteed marker for entry point confirmation (dev-only) // 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") local depth = env.get("HAKO_STAGEB_DRIVER_DEPTH")
if depth != null && ("" + depth) != "0" { 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 return -1
} }
env.set("HAKO_STAGEB_DRIVER_DEPTH", "1") env.set("HAKO_STAGEB_DRIVER_DEPTH", "1")
} }
// Depth guard cleared → main 続行できることを明示
print("[stageb/main] depth ok")
// Dev-only: direct FuncScanner harness // Dev-only: direct FuncScanner harness
{ {
local test_flag = env.get("HAKO_STAGEB_FUNCSCAN_TEST") local test_flag = env.get("HAKO_STAGEB_FUNCSCAN_TEST")
if test_flag != null && ("" + test_flag) == "1" { if test_flag != null && ("" + test_flag) == "1" {
print("[stageb/info] FUNC_SCAN_TEST=1, skipping emit")
StageBFuncScannerBox.test_fib_scan() StageBFuncScannerBox.test_fib_scan()
// Clear depth guard before returning // Clear depth guard before returning
env.set("HAKO_STAGEB_DRIVER_DEPTH", "0") env.set("HAKO_STAGEB_DRIVER_DEPTH", "0")
@ -1293,6 +1299,9 @@ static box StageBDriverBox {
// local externs_json = p.get_externs_json() // local externs_json = p.get_externs_json()
local body_src = StageBBodyExtractorBox.build_body_src(src, args) 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 local l2 = 0
if body_src != null { l2 = ("" + body_src).length() } 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(ast_json)
print("[stageb/main] after ast_json")
{ {
local tracer = new StageBTraceBox() local tracer = new StageBTraceBox()
tracer.log("StageBDriverBox.main:exit rc=0") tracer.log("StageBDriverBox.main:exit rc=0")

View File

@ -416,30 +416,34 @@ static box FuncScannerBox {
// FuncScannerBox._trim を使用static helper パターン) // FuncScannerBox._trim を使用static helper パターン)
// 戻り値: ArrayBoxトリム済みパラメータ名のリスト // 戻り値: ArrayBoxトリム済みパラメータ名のリスト
method parse_params(params_str) { method parse_params(params_str) {
// NOTE: keep the control flow simple to reduce SSA/PHI complexity. // NOTE: SSA/PHI が崩れにくいよう、1 ループ+単純状態でスキャンする。
// skip_whitespace/trim are already welltested helpers, so we reuse them here.
if params_str == null { return new ArrayBox() } if params_str == null { return new ArrayBox() }
// 状態変数は params / cur / i のみを更新する。
local params = new ArrayBox() local params = new ArrayBox()
local pstr = "" + params_str local s = "" + params_str
local n = pstr.length() local n = s.length()
local pos = 0 local cur = ""
local i = 0
loop(pos < n) { // Scan characters plus a sentinel comma at the end to reuse the same branch.
pos = FuncScannerBox.skip_whitespace(pstr, pos) loop(i <= n) {
if pos >= n { break } // 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). if ch == "," {
local next = pos // push current token if non-empty after trim, then reset
loop(next < n) { // Use static helper to avoid capturing instance state (SSA-friendly).
if pstr.substring(next, next + 1) == "," { break } local tok = FuncScannerBox._trim(cur)
next = next + 1 if tok.length() > 0 { params.push(tok) }
cur = ""
i = i + 1
continue
} else {
cur = cur + ch
} }
i = i + 1
// Trim and collect the parameter name.
local pname = FuncScannerBox.trim(pstr.substring(pos, next))
if pname.length() > 0 { params.push(pname) }
pos = next + 1
} }
return params return params
@ -532,6 +536,26 @@ static box FuncScannerBox {
// Static helper: 前後空白削除(外部 API // Static helper: 前後空白削除(外部 API
method _trim(s) { 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 ""
} }
} }

View File

@ -2,6 +2,7 @@
// ParserCommonUtilsBox — shared utility functions for parser boxes // ParserCommonUtilsBox — shared utility functions for parser boxes
// Responsibility: Provide common string/character operations used across parser components // Responsibility: Provide common string/character operations used across parser components
// Notes: Pure utility functions; no state, no dependencies // Notes: Pure utility functions; no state, no dependencies
using lang.compiler.parser.scan.parser_string_utils_box as ParserStringUtilsBox
static box ParserCommonUtilsBox { static box ParserCommonUtilsBox {
// ===== 数値・文字列変換 ===== // ===== 数値・文字列変換 =====
@ -47,12 +48,8 @@ static box ParserCommonUtilsBox {
} }
trim(s) { trim(s) {
local i = 0 // Delegate to string_utils to keep SSA/semantic consistency.
local n = s.length() return ParserStringUtilsBox.trim(s)
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)
} }
esc_json(s) { esc_json(s) {
@ -69,4 +66,3 @@ static box ParserCommonUtilsBox {
return out return out
} }
} }

View File

@ -71,16 +71,18 @@ static box ParserStringUtilsBox {
if s == null { return "" } if s == null { return "" }
local str = "" + s local str = "" + s
local n = str.length() local n = str.length()
local b = 0
loop(b < n) { // Leading whitespace: reuse shared skip_ws to keep semantics aligned with StageB.
local ch = str.substring(b, b + 1) local b = StringHelpers.skip_ws(str, 0)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { b = b + 1 } else { break } if b >= n { return "" }
}
// Trailing whitespace: walk backwards until a non-space is found.
local e = n local e = n
loop(e > b) { loop(e > b) {
local ch = str.substring(e - 1, e) 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) } if e > b { return str.substring(b, e) }
return "" return ""
} }

View File

@ -57,6 +57,9 @@ path = "lang/src/shared/json/stringify.hako"
[using.sh_core] [using.sh_core]
path = "lang/src/shared/common/string_helpers.hako" path = "lang/src/shared/common/string_helpers.hako"
[plugin_paths]
search_paths = ["target/release"]
[modules] [modules]
# Core shared helpers (needed by parser/compiler in Stage-B) # Core shared helpers (needed by parser/compiler in Stage-B)
"sh_core" = "lang/src/shared/common/string_helpers.hako" "sh_core" = "lang/src/shared/common/string_helpers.hako"

View File

@ -1,5 +1,7 @@
use super::*; use super::*;
use crate::mir::basic_block::BasicBlock; use crate::mir::basic_block::BasicBlock;
use std::fs::OpenOptions;
use std::io::Write;
use std::mem; use std::mem;
impl MirInterpreter { impl MirInterpreter {
@ -66,6 +68,16 @@ impl MirInterpreter {
}) })
.unwrap_or(1_000_000); .unwrap_or(1_000_000);
let mut steps: u64 = 0; let mut steps: u64 = 0;
let trace_log_path: Option<String> = 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 { loop {
steps += 1; steps += 1;
@ -94,6 +106,23 @@ impl MirInterpreter {
.get(&cur) .get(&cur)
.ok_or_else(|| VMError::InvalidBasicBlock(format!("bb {:?} not found", 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() { if Self::trace_enabled() {
eprintln!( eprintln!(
"[vm-trace] enter bb={:?} pred={:?} fn={}", "[vm-trace] enter bb={:?} pred={:?} fn={}",

View File

@ -225,16 +225,19 @@ impl MirInterpreter {
// Try candidates in order // Try candidates in order
let mut chosen: Option<&nyash_rust::mir::MirFunction> = None; let mut chosen: Option<&nyash_rust::mir::MirFunction> = None;
let mut chosen_name: Option<String> = None;
for c in &candidates { for c in &candidates {
// exact // exact
if let Some(f) = module.functions.get(c) { if let Some(f) = module.functions.get(c) {
chosen = Some(f); chosen = Some(f);
chosen_name = Some(c.clone());
break; break;
} }
// if contains '/': try name before '/' // if contains '/': try name before '/'
if let Some((head, _)) = c.split_once('/') { if let Some((head, _)) = c.split_once('/') {
if let Some(f) = module.functions.get(head) { if let Some(f) = module.functions.get(head) {
chosen = Some(f); chosen = Some(f);
chosen_name = Some(head.to_string());
break; break;
} }
} }
@ -242,6 +245,7 @@ impl MirInterpreter {
if c.ends_with(".main") { if c.ends_with(".main") {
if let Some(f) = module.functions.get("main") { if let Some(f) = module.functions.get("main") {
chosen = Some(f); chosen = Some(f);
chosen_name = Some("main".to_string());
break; 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) // Prepare arguments if the entry takes parameters (pass script args as ArrayBox)
let ret = if func.signature.params.len() == 0 { let ret = if func.signature.params.len() == 0 {
self.execute_function(func)? self.execute_function(func)?

View File

@ -150,6 +150,12 @@ pub fn verify_ret_purity() -> bool {
env_bool("NYASH_VERIFY_RET_PURITY") 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) ---- // ---- LLVM harness toggle (llvmlite) ----
pub fn llvm_use_harness() -> bool { pub fn llvm_use_harness() -> bool {
// Phase 15: デフォルトONLLVMバックエンドはPythonハーネス使用 // Phase 15: デフォルトONLLVMバックエンドはPythonハーネス使用
@ -195,7 +201,11 @@ fn nyash_features_list() -> Option<Vec<String>> {
} }
}) })
.collect(); .collect();
if list.is_empty() { None } else { Some(list) } if list.is_empty() {
None
} else {
Some(list)
}
} }
fn feature_stage3_enabled() -> bool { 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_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. /// JoinIR VM bridge debug output. Enables verbose logging of JoinIR→MIR conversion.
/// Set NYASH_JOINIR_VM_BRIDGE_DEBUG=1 to enable. /// Set NYASH_JOINIR_VM_BRIDGE_DEBUG=1 to enable.
pub fn joinir_vm_bridge_debug() -> bool { pub fn joinir_vm_bridge_debug() -> bool {
@ -266,6 +282,10 @@ pub fn joinir_llvm_experiment_enabled() -> bool {
/// Phase 33: JoinIR If Select 実験の有効化 /// Phase 33: JoinIR If Select 実験の有効化
/// Primary: HAKO_JOINIR_IF_SELECT (Phase 33-8+). /// Primary: HAKO_JOINIR_IF_SELECT (Phase 33-8+).
pub fn joinir_if_select_enabled() -> bool { pub fn joinir_if_select_enabled() -> bool {
// Core ON なら既定で有効化JoinIR 本線化を優先)
if joinir_core_enabled() {
return true;
}
// Primary: HAKO_JOINIR_IF_SELECT // Primary: HAKO_JOINIR_IF_SELECT
if let Some(v) = env_flag("HAKO_JOINIR_IF_SELECT") { if let Some(v) = env_flag("HAKO_JOINIR_IF_SELECT") {
return v; return v;

View File

@ -36,37 +36,15 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[
]; ];
pub fn lookup_keyword(word: &str) -> Option<&'static str> { pub fn lookup_keyword(word: &str) -> Option<&'static str> {
for (k, t) in KEYWORDS { for (k, t) in KEYWORDS {
if *k == word { return Some(*t); } if *k == word {
return Some(*t);
}
} }
None None
} }
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[ pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
"box", "box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait",
"global", "include", "local", "outbox", "try", "throw", "using", "from",
"function",
"static",
"if",
"loop",
"break",
"return",
"print",
"nowait",
"include",
"local",
"outbox",
"try",
"throw",
"using",
"from",
]; ];
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[ pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"];
"add",
"sub",
"mul",
"div",
"and",
"or",
"eq",
"ne",
];

View File

@ -26,10 +26,12 @@ impl super::MirBuilder {
/// ///
/// This is the unified entry point for all loop lowering. Specific functions /// This is the unified entry point for all loop lowering. Specific functions
/// are routed through JoinIR Frontend instead of the traditional LoopBuilder path /// 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 /// - Core ON (`joinir_core_enabled()`): print_tokens / ArrayExt.filter はまず JoinIR Frontend を試す
/// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2 /// - 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. /// Note: Arity does NOT include implicit `me` receiver.
pub(super) fn cf_loop( pub(super) fn cf_loop(
@ -37,7 +39,7 @@ impl super::MirBuilder {
condition: ASTNode, condition: ASTNode,
body: Vec<ASTNode>, body: Vec<ASTNode>,
) -> Result<ValueId, String> { ) -> Result<ValueId, String> {
// 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)? { if let Some(result) = self.try_cf_loop_joinir(&condition, &body)? {
return Ok(result); return Ok(result);
} }
@ -77,20 +79,31 @@ impl super::MirBuilder {
.map(|f| f.signature.name.clone()) .map(|f| f.signature.name.clone())
.unwrap_or_default(); .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 // 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() { let is_target = match func_name.as_str() {
"JsonTokenizer.print_tokens/0" => { "JsonTokenizer.print_tokens/0" => {
std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN") if core_on {
.ok() true
.as_deref() } else {
== Some("1") std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN")
.ok()
.as_deref()
== Some("1")
}
} }
"ArrayExtBox.filter/2" => { "ArrayExtBox.filter/2" => {
std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN") if core_on {
.ok() true
.as_deref() } else {
== Some("1") std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN")
.ok()
.as_deref()
== Some("1")
}
} }
_ => false, _ => false,
}; };

View File

@ -2,6 +2,7 @@ use super::{
BasicBlockId, EffectMask, FunctionSignature, MirInstruction, MirModule, MirType, ValueId, BasicBlockId, EffectMask, FunctionSignature, MirInstruction, MirModule, MirType, ValueId,
}; };
use crate::ast::ASTNode; use crate::ast::ASTNode;
use crate::config;
// Lifecycle routines extracted from builder.rs // Lifecycle routines extracted from builder.rs
fn has_main_static(ast: &ASTNode) -> bool { fn has_main_static(ast: &ASTNode) -> bool {
@ -299,7 +300,8 @@ impl super::MirBuilder {
None None
}; };
// Phase 67: P3-C 対象なら GenericTypeResolver を優先使用 // 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( if let Some(mt) = GenericTypeResolver::resolve_from_phi(
&function, &function,
*v, *v,
@ -342,11 +344,9 @@ impl super::MirBuilder {
}; };
// Phase 67: P3-C 対象なら GenericTypeResolver を優先使用 // 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( if let Some(mt) =
&function, GenericTypeResolver::resolve_from_phi(&function, *v, &self.value_types)
*v, {
&self.value_types,
) {
if std::env::var("NYASH_P3C_DEBUG").is_ok() { if std::env::var("NYASH_P3C_DEBUG").is_ok() {
eprintln!( eprintln!(
"[lifecycle/p3c] {} type inferred via GenericTypeResolver: {:?}", "[lifecycle/p3c] {} type inferred via GenericTypeResolver: {:?}",
@ -373,7 +373,9 @@ impl super::MirBuilder {
} }
} }
// Dev-only verify: NewBox → birth() invariant (warn if missing) // Dev-only verify: NewBox → birth() invariant (warn if missing)
// StageB 用トグル: NYASH_STAGEB_DEV_VERIFY=0 のときは StageBDriverBox だけ警告をスキップする。
if crate::config::env::using_is_dev() { if crate::config::env::using_is_dev() {
let stageb_dev_verify_on = config::env::stageb_dev_verify_enabled();
let mut warn_count = 0usize; let mut warn_count = 0usize;
for (_bid, bb) in function.blocks.iter() { for (_bid, bb) in function.blocks.iter() {
let insns = &bb.instructions; let insns = &bb.instructions;
@ -385,6 +387,11 @@ impl super::MirBuilder {
args, args,
} = &insns[idx] } = &insns[idx]
{ {
// StageB dev verify OFF のときは StageBDriverBox の birth 警告のみスキップ
if !stageb_dev_verify_on && box_type == "StageBDriverBox" {
idx += 1;
continue;
}
// Skip StringBox (literal optimization path) // Skip StringBox (literal optimization path)
if box_type != "StringBox" { if box_type != "StringBox" {
let expect_tail = format!("{}.birth/{}", box_type, args.len()); let expect_tail = format!("{}.birth/{}", box_type, args.len());

View File

@ -154,10 +154,7 @@ mod tests {
// P3-C 対象 // P3-C 対象
assert!(GenericTypeResolver::is_generic_method(&array_type, "get")); assert!(GenericTypeResolver::is_generic_method(&array_type, "get"));
assert!(GenericTypeResolver::is_generic_method(&array_type, "pop")); assert!(GenericTypeResolver::is_generic_method(&array_type, "pop"));
assert!(GenericTypeResolver::is_generic_method( assert!(GenericTypeResolver::is_generic_method(&array_type, "first"));
&array_type,
"first"
));
assert!(GenericTypeResolver::is_generic_method(&array_type, "last")); assert!(GenericTypeResolver::is_generic_method(&array_type, "last"));
// P3-A/P3-B 対象(非 P3-C // P3-A/P3-B 対象(非 P3-C
@ -194,9 +191,7 @@ mod tests {
fn test_is_p3c_candidate() { fn test_is_p3c_candidate() {
// 全関数が P3-C 候補P1/P2/P3-A/B 以外) // 全関数が P3-C 候補P1/P2/P3-A/B 以外)
assert!(GenericTypeResolver::is_p3c_candidate("Main.main/0")); assert!(GenericTypeResolver::is_p3c_candidate("Main.main/0"));
assert!(GenericTypeResolver::is_p3c_candidate( assert!(GenericTypeResolver::is_p3c_candidate("FuncScanner.parse/1"));
"FuncScanner.parse/1"
));
// 空文字列は false // 空文字列は false
assert!(!GenericTypeResolver::is_p3c_candidate("")); assert!(!GenericTypeResolver::is_p3c_candidate(""));

View File

@ -170,8 +170,16 @@ pub(crate) fn intake_loop_form(
// Phase 70-1: Trio 分類を削除し、pinned_hint/carrier_hint をそのまま返す // Phase 70-1: Trio 分類を削除し、pinned_hint/carrier_hint をそのまま返す
// 実際の分類は LoopScopeShape::from_loop_form() 内部で実施される(二重分類問題解消) // 実際の分類は LoopScopeShape::from_loop_form() 内部で実施される(二重分類問題解消)
let ordered_pinned: Vec<String> = pinned_hint.into_iter().collect::<BTreeSet<_>>().into_iter().collect(); let ordered_pinned: Vec<String> = pinned_hint
let ordered_carriers: Vec<String> = carrier_hint.into_iter().collect::<BTreeSet<_>>().into_iter().collect(); .into_iter()
.collect::<BTreeSet<_>>()
.into_iter()
.collect();
let ordered_carriers: Vec<String> = carrier_hint
.into_iter()
.collect::<BTreeSet<_>>()
.into_iter()
.collect();
if ordered_pinned.is_empty() || ordered_carriers.is_empty() { if ordered_pinned.is_empty() || ordered_carriers.is_empty() {
return None; return None;

View File

@ -58,10 +58,7 @@ impl LoopScopeShape {
Some(result) Some(result)
} }
fn build_from_intake( fn build_from_intake(loop_form: &LoopForm, intake: &LoopFormIntake) -> Option<Self> {
loop_form: &LoopForm,
intake: &LoopFormIntake,
) -> Option<Self> {
let layout = block_layout(loop_form); let layout = block_layout(loop_form);
if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() { if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() {
@ -80,13 +77,8 @@ impl LoopScopeShape {
let carriers: BTreeSet<String> = intake.carrier_ordered.iter().cloned().collect(); let carriers: BTreeSet<String> = intake.carrier_ordered.iter().cloned().collect();
let variable_definitions = collect_variable_definitions(intake, &layout); let variable_definitions = collect_variable_definitions(intake, &layout);
let (body_locals, exit_live) = classify_body_and_exit( let (body_locals, exit_live) =
intake, classify_body_and_exit(intake, &pinned, &carriers, &variable_definitions, &layout);
&pinned,
&carriers,
&variable_definitions,
&layout,
);
let progress_carrier = carriers.iter().next().cloned(); let progress_carrier = carriers.iter().next().cloned();
@ -150,10 +142,7 @@ fn collect_variable_definitions(
for (bb, snap) in &intake.exit_snapshots { for (bb, snap) in &intake.exit_snapshots {
for var_name in snap.keys() { for var_name in snap.keys() {
var_defs var_defs.entry(var_name.clone()).or_default().insert(*bb);
.entry(var_name.clone())
.or_default()
.insert(*bb);
} }
} }

View File

@ -18,14 +18,16 @@ pub enum LoopVarClass {
impl LoopVarClass { impl LoopVarClass {
#[cfg(test)] #[cfg(test)]
pub fn needs_exit_phi(self) -> bool { 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)] #[cfg(test)]
pub fn needs_header_phi(self) -> bool { pub fn needs_header_phi(self) -> bool {
matches!(self, LoopVarClass::Pinned | LoopVarClass::Carrier) matches!(self, LoopVarClass::Pinned | LoopVarClass::Carrier)
} }
} }
/// ループ変数スコープの統合ビュー /// ループ変数スコープの統合ビュー

View File

@ -151,9 +151,7 @@ mod tests {
pinned: vec!["s".to_string()].into_iter().collect(), pinned: vec!["s".to_string()].into_iter().collect(),
carriers: vec!["i".to_string()].into_iter().collect(), carriers: vec!["i".to_string()].into_iter().collect(),
body_locals: BTreeSet::new(), body_locals: BTreeSet::new(),
exit_live: vec!["s".to_string(), "i".to_string()] exit_live: vec!["s".to_string(), "i".to_string()].into_iter().collect(),
.into_iter()
.collect(),
progress_carrier: Some("i".to_string()), progress_carrier: Some("i".to_string()),
variable_definitions: BTreeMap::new(), variable_definitions: BTreeMap::new(),
} }

View File

@ -1,6 +1,6 @@
use super::shape::LoopVarClass;
use super::*; use super::*;
use crate::mir::join_ir::lowering::loop_form_intake::LoopFormIntake; use crate::mir::join_ir::lowering::loop_form_intake::LoopFormIntake;
use super::shape::LoopVarClass;
use crate::mir::{BasicBlockId, MirQuery, ValueId}; use crate::mir::{BasicBlockId, MirQuery, ValueId};
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
@ -362,9 +362,13 @@ fn test_is_available_in_all_phase48_5_future() {
); );
variable_definitions.insert( variable_definitions.insert(
"i".to_string(), "i".to_string(),
vec![BasicBlockId::new(2), BasicBlockId::new(3), BasicBlockId::new(4)] vec![
.into_iter() BasicBlockId::new(2),
.collect(), BasicBlockId::new(3),
BasicBlockId::new(4),
]
.into_iter()
.collect(),
); );
let scope = LoopScopeShape { let scope = LoopScopeShape {
@ -391,7 +395,11 @@ fn test_is_available_in_all_phase48_5_future() {
// i は block 2, 3, 4 で定義 → すべて要求しても true // i は block 2, 3, 4 で定義 → すべて要求しても true
assert!(scope.is_available_in_all( assert!(scope.is_available_in_all(
"i", "i",
&[BasicBlockId::new(2), BasicBlockId::new(3), BasicBlockId::new(4)] &[
BasicBlockId::new(2),
BasicBlockId::new(3),
BasicBlockId::new(4)
]
)); ));
// unknown は variable_definitions にない → false // unknown は variable_definitions にない → false
@ -469,7 +477,11 @@ fn test_variable_definitions_partial_availability() {
// s は全 exit で利用可能 // s は全 exit で利用可能
assert!(scope.is_available_in_all( assert!(scope.is_available_in_all(
"s", "s",
&[BasicBlockId::new(20), BasicBlockId::new(21), BasicBlockId::new(22)] &[
BasicBlockId::new(20),
BasicBlockId::new(21),
BasicBlockId::new(22)
]
)); ));
// y は exit1 でのみ利用可能 // y は exit1 でのみ利用可能

View File

@ -85,6 +85,10 @@ impl LoopToJoinLowerer {
loop_form: &LoopForm, loop_form: &LoopForm,
func_name: Option<&str>, func_name: Option<&str>,
) -> Option<JoinModule> { ) -> Option<JoinModule> {
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 { if self.debug {
eprintln!( eprintln!(
"[LoopToJoinLowerer] lower() called for {:?}", "[LoopToJoinLowerer] lower() called for {:?}",
@ -137,6 +141,12 @@ impl LoopToJoinLowerer {
func_name.unwrap_or("<unknown>") func_name.unwrap_or("<unknown>")
); );
} }
if strict_on && is_minimal_target {
panic!(
"[joinir/loop] strict mode: view-based check failed for {}",
func_name.unwrap_or("<unknown>")
);
}
return None; return None;
} }
@ -151,6 +161,12 @@ impl LoopToJoinLowerer {
func_name.unwrap_or("<unknown>") func_name.unwrap_or("<unknown>")
); );
} }
if strict_on && is_minimal_target {
panic!(
"[joinir/loop] strict mode: name filter rejected {}",
func_name.unwrap_or("<unknown>")
);
}
return None; return None;
} }
} else if self.debug { } else if self.debug {
@ -161,7 +177,14 @@ impl LoopToJoinLowerer {
} }
// Step 5: パターンに応じた lowering を実行 // 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("<unknown>")
);
}
out
} }
/// Phase 29 L-5.3: Progress carrier の安全性をチェック /// Phase 29 L-5.3: Progress carrier の安全性をチェック

View File

@ -20,6 +20,7 @@ pub mod exit_args_resolver;
pub mod funcscanner_append_defs; pub mod funcscanner_append_defs;
pub mod funcscanner_trim; pub mod funcscanner_trim;
pub mod generic_case_a; 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_dry_runner; // Phase 33-10.0
pub mod if_merge; // Phase 33-7 pub mod if_merge; // Phase 33-7
pub mod if_phi_context; // Phase 61-1 pub mod if_phi_context; // Phase 61-1
@ -35,7 +36,6 @@ pub mod stageb_body;
pub mod stageb_funcscanner; pub mod stageb_funcscanner;
pub mod type_hint_policy; // Phase 65.5: 型ヒントポリシー箱化 pub mod type_hint_policy; // Phase 65.5: 型ヒントポリシー箱化
pub mod type_inference; // Phase 65-2-A pub mod type_inference; // Phase 65-2-A
pub mod generic_type_resolver; // Phase 66: P3-C ジェネリック型推論箱
pub mod value_id_ranges; pub mod value_id_ranges;
// Re-export public lowering functions // Re-export public lowering functions
@ -148,6 +148,8 @@ pub fn try_lower_if_to_joinir(
if !crate::config::env::joinir_if_select_enabled() { if !crate::config::env::joinir_if_select_enabled() {
return None; 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責務分離 // Phase 33-9.1: Loop専任関数の除外Loop/If責務分離
// Loop lowering対象関数はIf loweringの対象外にすることで、 // Loop lowering対象関数はIf loweringの対象外にすることで、
@ -179,15 +181,21 @@ pub fn try_lower_if_to_joinir(
"Stage1JsonScannerBox.value_start_after_key_pos/2" "Stage1JsonScannerBox.value_start_after_key_pos/2"
); );
if !is_allowed { // Phase 80: Core ON のときは許可リストを「JoinIRをまず試す」対象とみなす。
if debug_level >= 2 { // Core OFF のときは従来どおり whitelist + env に頼る。
eprintln!( if !is_allowed || !core_on {
"[try_lower_if_to_joinir] skipping non-allowed function: {}", // Core OFF かつ許可外なら従来のガードでスキップ
func.signature.name 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 { if debug_level >= 1 {
eprintln!( eprintln!(
@ -232,6 +240,12 @@ pub fn try_lower_if_to_joinir(
func.signature.name func.signature.name
); );
} }
if strict_allowed {
panic!(
"[joinir/if] strict mode: pattern not matched for {}",
func.signature.name
);
}
return None; 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 result
} }

View File

@ -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::join_ir_vm_bridge::run_joinir_via_vm;
use crate::mir::MirModule; use crate::mir::MirModule;
use std::process; use std::process;
use crate::config::env::{joinir_dev_enabled, joinir_strict_enabled};
/// Main.skip/1 用 JoinIR ブリッジExec: JoinIR→VM 実行まで対応) /// 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()); let input = std::env::var("NYASH_JOINIR_INPUT").unwrap_or_else(|_| " abc".to_string());
eprintln!("[joinir/vm_bridge] Input: {:?}", input); 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)]) { match run_joinir_via_vm(&join_module, JoinFuncId::new(0), &[JoinValue::Str(input)]) {
Ok(result) => { Ok(result) => {
let exit_code = match &result { let exit_code = match &result {
@ -35,10 +40,23 @@ pub(crate) fn try_run_skip_ws(module: &MirModule, quiet_pipe: bool) -> bool {
_ => 0, _ => 0,
}; };
eprintln!("[joinir/vm_bridge] ✅ JoinIR result: {:?}", result); eprintln!("[joinir/vm_bridge] ✅ JoinIR result: {:?}", result);
if !quiet_pipe { if dev_bridge {
println!("RC: {}", exit_code); // 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) => { Err(e) => {
eprintln!("[joinir/vm_bridge] ❌ JoinIR execution failed: {:?}", 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()); let input = std::env::var("NYASH_JOINIR_INPUT").unwrap_or_else(|_| " abc ".to_string());
eprintln!("[joinir/vm_bridge] Input: {:?}", input); 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)]) { match run_joinir_via_vm(&join_module, JoinFuncId::new(0), &[JoinValue::Str(input)]) {
Ok(result) => { Ok(result) => {
eprintln!("[joinir/vm_bridge] ✅ JoinIR trim result: {:?}", result); eprintln!("[joinir/vm_bridge] ✅ JoinIR trim result: {:?}", result);
if !quiet_pipe { if dev_bridge {
match &result { if !quiet_pipe {
JoinValue::Str(s) => println!("{}", s), match &result {
_ => println!("{:?}", 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) => { Err(e) => {
eprintln!("[joinir/vm_bridge] ❌ JoinIR trim failed: {:?}", e); eprintln!("[joinir/vm_bridge] ❌ JoinIR trim failed: {:?}", e);

View File

@ -40,6 +40,7 @@ use crate::mir::MirModule;
/// Exec実行または LowerOnly検証のみのパスに分岐する。 /// Exec実行または LowerOnly検証のみのパスに分岐する。
pub fn try_run_joinir_vm_bridge(module: &MirModule, quiet_pipe: bool) -> bool { pub fn try_run_joinir_vm_bridge(module: &MirModule, quiet_pipe: bool) -> bool {
let flags = JoinIrEnvFlags::from_env(); let flags = JoinIrEnvFlags::from_env();
let strict = crate::config::env::joinir_strict_enabled();
// Phase 32 L-4: テーブルから対象関数を探す // Phase 32 L-4: テーブルから対象関数を探す
let Some(target) = find_joinir_target(module) else { 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: テーブル駆動ディスパッチ // Phase 32 L-4: テーブル駆動ディスパッチ
// 関数名でルーティング(将来は lowering テーブルベースに差し替え予定) // 関数名でルーティング(将来は lowering テーブルベースに差し替え予定)
match target.func_name { let handled = match target.func_name {
"Main.skip/1" => try_run_skip_ws(module, quiet_pipe), "Main.skip/1" => try_run_skip_ws(module, quiet_pipe),
"FuncScannerBox.trim/1" => try_run_trim(module, quiet_pipe), "FuncScannerBox.trim/1" => try_run_trim(module, quiet_pipe),
"Stage1UsingResolverBox.resolve_for_source/5" => try_run_stage1_usingresolver(module), "Stage1UsingResolverBox.resolve_for_source/5" => try_run_stage1_usingresolver(module),
"StageBBodyExtractorBox.build_body_src/2" => try_run_stageb_body(module), "StageBBodyExtractorBox.build_body_src/2" => try_run_stageb_body(module),
"StageBFuncScannerBox.scan_all_boxes/1" => try_run_stageb_funcscanner(module), "StageBFuncScannerBox.scan_all_boxes/1" => try_run_stageb_funcscanner(module),
_ => false, _ => 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
} }

View File

@ -123,9 +123,7 @@ impl LoopSnapshotMergeBox {
if debug { if debug {
eprintln!( eprintln!(
"[Option C] var '{}': {:?} needs_exit_phi={}", "[Option C] var '{}': {:?} needs_exit_phi={}",
var_name, var_name, class, needs_exit_phi
class,
needs_exit_phi
); );
if let Some(defining_blocks) = definitions.get(&var_name) { if let Some(defining_blocks) = definitions.get(&var_name) {
eprintln!("[Option C] defining_blocks: {:?}", defining_blocks); eprintln!("[Option C] defining_blocks: {:?}", defining_blocks);

View File

@ -956,12 +956,7 @@ pub fn build_exit_phis_for_control<O: LoopFormOps>(
// Phase 69-2: LocalScopeInspectorBox 完全削除 // Phase 69-2: LocalScopeInspectorBox 完全削除
// variable_definitions は LoopScopeShape に移行済みPhase 48-4 // variable_definitions は LoopScopeShape に移行済みPhase 48-4
loopform.build_exit_phis( loopform.build_exit_phis(ops, exit_id, branch_source_block, exit_snapshots)
ops,
exit_id,
branch_source_block,
exit_snapshots,
)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -31,6 +31,11 @@ impl MirVerifier {
pub fn verify_module(&mut self, module: &MirModule) -> Result<(), Vec<VerificationError>> { pub fn verify_module(&mut self, module: &MirModule) -> Result<(), Vec<VerificationError>> {
self.errors.clear(); self.errors.clear();
// StageB/selfhost 専用: dev verify を一時緩和するためのトグル
if !crate::config::env::stageb_dev_verify_enabled() {
return Ok(());
}
for (_name, function) in &module.functions { for (_name, function) in &module.functions {
if let Err(mut func_errors) = self.verify_function(function) { if let Err(mut func_errors) = self.verify_function(function) {
// Dev-only trace: BreakFinderBox / LoopSSA 周辺のSSAバグを詳細に観測する。 // Dev-only trace: BreakFinderBox / LoopSSA 周辺のSSAバグを詳細に観測する。

View File

@ -30,8 +30,10 @@ pub fn apply_core_wrapper_env(cmd: &mut std::process::Command) {
// Remove noisy or recursive toggles // Remove noisy or recursive toggles
cmd.env_remove("NYASH_USE_NY_COMPILER"); cmd.env_remove("NYASH_USE_NY_COMPILER");
cmd.env_remove("NYASH_CLI_VERBOSE"); cmd.env_remove("NYASH_CLI_VERBOSE");
// Enforce quiet JSON capture // Enforce quiet JSON capture (allow override for debug)
cmd.env("NYASH_JSON_ONLY", "1"); if std::env::var("NYASH_JSON_ONLY").is_err() {
cmd.env("NYASH_JSON_ONLY", "1");
}
// Restrict environment to avoid plugin/using drift // Restrict environment to avoid plugin/using drift
cmd.env("NYASH_DISABLE_PLUGINS", "1"); cmd.env("NYASH_DISABLE_PLUGINS", "1");
cmd.env("NYASH_SKIP_TOML_ENV", "1"); cmd.env("NYASH_SKIP_TOML_ENV", "1");

View File

@ -197,6 +197,16 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
} }
// Backend selection // 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() { match groups.backend.backend.as_str() {
"mir" => { "mir" => {
crate::cli_v!( crate::cli_v!(

View File

@ -92,6 +92,19 @@ impl NyashRunner {
return; return;
} }
let groups = self.config.as_groups(); 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("<none>");
let args = std::env::args().skip(1).collect::<Vec<_>>().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; let skip_stage1_stub = groups.emit.hako_emit_program_json || groups.emit.hako_emit_mir_json;
if !skip_stage1_stub { if !skip_stage1_stub {
if let Some(code) = self.maybe_run_stage1_cli_stub(&groups) { if let Some(code) = self.maybe_run_stage1_cli_stub(&groups) {

View File

@ -80,6 +80,7 @@ pub fn check_and_report(strict: bool, quiet_pipe: bool, label: &str) {
"[plugin/missing] {} providers not loaded: {:?}", "[plugin/missing] {} providers not loaded: {:?}",
label, missing 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); emit_hints_for(&missing);
if quiet_pipe { if quiet_pipe {
// In quiet JSON mode, avoid noisy stdout; hints are on stderr already. // In quiet JSON mode, avoid noisy stdout; hints are on stderr already.

View File

@ -14,6 +14,16 @@ impl NyashRunner {
// Quiet mode for child pipelines (e.g., selfhost compiler JSON emit) // Quiet mode for child pipelines (e.g., selfhost compiler JSON emit)
let quiet_pipe = crate::config::env::env_bool("NYASH_JSON_ONLY"); 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): // Enforce plugin-first policy for VM on this branch (deterministic):
// - Initialize plugin host if not yet loaded // - Initialize plugin host if not yet loaded
@ -170,7 +180,10 @@ impl NyashRunner {
} }
if trace && crate::config::env::parser_stage3_enabled() { 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
);
} }
// FailFast (optin): Hako 構文を Nyash VM 経路で実行しない // FailFast (optin): Hako 構文を Nyash VM 経路で実行しない
@ -513,10 +526,25 @@ impl NyashRunner {
// Routing logic is centralized in join_ir_vm_bridge_dispatch module // Routing logic is centralized in join_ir_vm_bridge_dispatch module
try_run_joinir_vm_bridge(&module_vm, quiet_pipe); 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) { match vm.execute_module(&module_vm) {
Ok(ret) => { Ok(ret) => {
use crate::box_trait::{BoolBox, IntegerBox}; 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 // Extract exit code from return value
let exit_code = if let Some(ib) = ret.as_any().downcast_ref::<IntegerBox>() { let exit_code = if let Some(ib) = ret.as_any().downcast_ref::<IntegerBox>() {
ib.value as i32 ib.value as i32
@ -541,11 +569,25 @@ impl NyashRunner {
if !quiet_pipe { if !quiet_pipe {
println!("RC: {}", exit_code); 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 // Exit with the return value as exit code
process::exit(exit_code); process::exit(exit_code);
} }
Err(e) => { 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); eprintln!("❌ [rust-vm] VM error: {}", e);
process::exit(1); process::exit(1);
} }

View File

@ -40,80 +40,96 @@ pub fn init_bid_plugins() {
} }
let cfg_path = resolve_plugin_toml(); let cfg_path = resolve_plugin_toml();
if let Ok(()) = init_global_plugin_host(&cfg_path) { match init_global_plugin_host(&cfg_path) {
if plugin_debug || cli_verbose { Ok(()) => {
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(),
});
}
}
if plugin_debug || cli_verbose { 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()
);
} }
} let host = get_global_plugin_host();
let host = host.read().unwrap();
// Optional autoload for [using.*] kind="dylib" packages if let Some(config) = host.config_ref() {
if std::env::var("NYASH_USING_DYLIB_AUTOLOAD").ok().as_deref() == Some("1") let registry = get_global_registry();
&& std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") for (lib_name, lib_def) in &config.libraries {
{ for box_name in &lib_def.boxes {
if plugin_debug || cli_verbose { if plugin_debug {
eprintln!("[using.dylib/autoload] scanning nyash.toml packages …"); eprintln!(" 📦 Registering plugin provider for {}", box_name);
} }
let mut using_paths: Vec<String> = Vec::new(); registry.apply_plugin_config(&PluginConfig {
let mut pending_modules: std::vec::Vec<(String, String)> = Vec::new(); plugins: [(box_name.clone(), lib_name.clone())].into(),
let mut aliases: std::collections::HashMap<String, String> = });
std::collections::HashMap::new(); }
let mut packages: std::collections::HashMap<String, crate::using::spec::UsingPackage> = }
std::collections::HashMap::new(); if plugin_debug || cli_verbose {
let _ = crate::using::resolver::populate_from_toml( eprintln!("[plugin/init] ✅ plugin host fully configured");
&mut using_paths, }
&mut pending_modules, }
&mut aliases,
&mut packages, // Optional autoload for [using.*] kind="dylib" packages
); if std::env::var("NYASH_USING_DYLIB_AUTOLOAD").ok().as_deref() == Some("1")
for (name, pkg) in packages.iter() { && std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1")
if let crate::using::spec::PackageKind::Dylib = pkg.kind { {
// Build library name from file stem (best-effort) if plugin_debug || cli_verbose {
let lib_name = std::path::Path::new(&pkg.path) eprintln!("[using.dylib/autoload] scanning nyash.toml packages …");
.file_name() }
.and_then(|s| s.to_str()) let mut using_paths: Vec<String> = Vec::new();
.unwrap_or(name) let mut pending_modules: std::vec::Vec<(String, String)> = Vec::new();
.to_string(); let mut aliases: std::collections::HashMap<String, String> =
let host = get_global_plugin_host(); std::collections::HashMap::new();
let res = host let mut packages: std::collections::HashMap<
.read() String,
.unwrap() crate::using::spec::UsingPackage,
.load_library_direct(&lib_name, &pkg.path, &[]); > = std::collections::HashMap::new();
if let Err(e) = res { let _ = crate::using::resolver::populate_from_toml(
if plugin_debug || cli_verbose { &mut using_paths,
eprintln!("[using.dylib/autoload] failed '{}': {}", lib_name, e); &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 { Err(e) => {
eprintln!("⚠️ Failed to load plugin config (hakorune.toml/nyash.toml) - plugins disabled"); 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;
}
} }
} }

View File

@ -422,7 +422,40 @@ pub fn get_global_plugin_host() -> Arc<RwLock<PluginHost>> {
pub fn init_global_plugin_host(config_path: &str) -> BidResult<()> { pub fn init_global_plugin_host(config_path: &str) -> BidResult<()> {
let host = get_global_plugin_host(); 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(()) Ok(())
} }

View File

@ -7,8 +7,10 @@ pub(super) fn load_config(loader: &mut PluginLoaderV2, config_path: &str) -> Bid
.unwrap_or_else(|_| config_path.to_string()); .unwrap_or_else(|_| config_path.to_string());
loader.config_path = Some(canonical.clone()); loader.config_path = Some(canonical.clone());
loader.config = Some( loader.config = Some(
crate::config::nyash_toml_v2::NyashConfigV2::from_file(&canonical) crate::config::nyash_toml_v2::NyashConfigV2::from_file(&canonical).map_err(|e| {
.map_err(|_| BidError::PluginError)?, eprintln!("[plugin/init] failed to parse {}: {}", canonical, e);
BidError::PluginError
})?,
); );
if let Some(cfg) = loader.config.as_ref() { if let Some(cfg) = loader.config.as_ref() {
let mut labels: Vec<String> = Vec::new(); let mut labels: Vec<String> = Vec::new();

View File

@ -51,7 +51,15 @@ pub(super) fn load_plugin(
lib_path.display() 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); let lib_arc = Arc::new(lib);
unsafe { unsafe {

View File

@ -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");
}

View File

@ -1,3 +1,4 @@
//! Phase 34-7.5: テストヘルパーモジュール //! Phase 34-7.5: テストヘルパーモジュール
pub mod joinir_frontend; pub mod joinir_frontend;
pub mod joinir_env;

View File

@ -18,11 +18,13 @@
use crate::ast::ASTNode; use crate::ast::ASTNode;
use crate::mir::MirCompiler; use crate::mir::MirCompiler;
use crate::parser::NyashParser; use crate::parser::NyashParser;
use crate::tests::helpers::joinir_env::clear_joinir_flags;
/// Phase 49-3: JoinIR Frontend mainline パイプラインが /// Phase 49-3: JoinIR Frontend mainline パイプラインが
/// print_tokens 関数のコンパイル時にクラッシュしないことを確認 /// print_tokens 関数のコンパイル時にクラッシュしないことを確認
#[test] #[test]
fn phase49_joinir_mainline_pipeline_smoke() { fn phase49_joinir_mainline_pipeline_smoke() {
clear_joinir_flags();
// Phase 49 mainline route は dev フラグで制御 // Phase 49 mainline route は dev フラグで制御
std::env::set_var("HAKO_JOINIR_PRINT_TOKENS_MAIN", "1"); std::env::set_var("HAKO_JOINIR_PRINT_TOKENS_MAIN", "1");
std::env::set_var("NYASH_JOINIR_MAINLINE_DEBUG", "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_JOINIR_MAINLINE_DEBUG");
std::env::remove_var("NYASH_FEATURES"); std::env::remove_var("NYASH_FEATURES");
std::env::remove_var("NYASH_DISABLE_PLUGINS"); std::env::remove_var("NYASH_DISABLE_PLUGINS");
@ -83,6 +85,7 @@ static box Main {
/// Phase 49-3: dev フラグ OFF 時は従来経路を使用することを確認 /// Phase 49-3: dev フラグ OFF 時は従来経路を使用することを確認
#[test] #[test]
fn phase49_joinir_mainline_fallback_without_flag() { fn phase49_joinir_mainline_fallback_without_flag() {
clear_joinir_flags();
// dev フラグ OFF // dev フラグ OFF
std::env::remove_var("HAKO_JOINIR_PRINT_TOKENS_MAIN"); std::env::remove_var("HAKO_JOINIR_PRINT_TOKENS_MAIN");
std::env::set_var("NYASH_FEATURES", "stage3"); 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_FEATURES");
std::env::remove_var("NYASH_DISABLE_PLUGINS"); std::env::remove_var("NYASH_DISABLE_PLUGINS");
} }
@ -135,6 +139,7 @@ static box Main {
/// 両方が正常に完了することを確認する。 /// 両方が正常に完了することを確認する。
#[test] #[test]
fn phase49_joinir_mainline_ab_comparison() { fn phase49_joinir_mainline_ab_comparison() {
clear_joinir_flags();
let src = r#" let src = r#"
box JsonTokenizer { box JsonTokenizer {
tokens: ArrayBox tokens: ArrayBox
@ -206,7 +211,7 @@ static box Main {
// Future: Add execution comparison // 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_FEATURES");
std::env::remove_var("NYASH_DISABLE_PLUGINS"); std::env::remove_var("NYASH_DISABLE_PLUGINS");
} }
@ -219,6 +224,7 @@ static box Main {
/// ArrayExtBox.filter 関数のコンパイル時にクラッシュしないことを確認 /// ArrayExtBox.filter 関数のコンパイル時にクラッシュしないことを確認
#[test] #[test]
fn phase49_joinir_array_filter_smoke() { fn phase49_joinir_array_filter_smoke() {
clear_joinir_flags();
// Phase 49-4 mainline route は dev フラグで制御 // Phase 49-4 mainline route は dev フラグで制御
std::env::set_var("HAKO_JOINIR_ARRAY_FILTER_MAIN", "1"); std::env::set_var("HAKO_JOINIR_ARRAY_FILTER_MAIN", "1");
std::env::set_var("NYASH_JOINIR_MAINLINE_DEBUG", "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_JOINIR_MAINLINE_DEBUG");
std::env::remove_var("NYASH_FEATURES"); std::env::remove_var("NYASH_FEATURES");
std::env::remove_var("NYASH_DISABLE_PLUGINS"); std::env::remove_var("NYASH_DISABLE_PLUGINS");
@ -273,6 +279,7 @@ static box Main {
/// Phase 49-4: dev フラグ OFF 時は従来経路を使用することを確認 /// Phase 49-4: dev フラグ OFF 時は従来経路を使用することを確認
#[test] #[test]
fn phase49_joinir_array_filter_fallback() { fn phase49_joinir_array_filter_fallback() {
clear_joinir_flags();
// dev フラグ OFF // dev フラグ OFF
std::env::remove_var("HAKO_JOINIR_ARRAY_FILTER_MAIN"); std::env::remove_var("HAKO_JOINIR_ARRAY_FILTER_MAIN");
std::env::set_var("NYASH_FEATURES", "stage3"); 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_FEATURES");
std::env::remove_var("NYASH_DISABLE_PLUGINS"); std::env::remove_var("NYASH_DISABLE_PLUGINS");
} }
@ -323,6 +331,7 @@ static box Main {
/// ArrayExtBox.filter版 /// ArrayExtBox.filter版
#[test] #[test]
fn phase49_joinir_array_filter_ab_comparison() { fn phase49_joinir_array_filter_ab_comparison() {
clear_joinir_flags();
let src = r#" let src = r#"
static box ArrayExtBox { static box ArrayExtBox {
filter(arr, pred) { 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_FEATURES");
std::env::remove_var("NYASH_DISABLE_PLUGINS"); std::env::remove_var("NYASH_DISABLE_PLUGINS");
} }

View File

@ -47,6 +47,24 @@ fn mir_funcscanner_parse_params_trim_min_verify_and_vm() {
compiled.module.functions.len() 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 エラーが出るかを見る。 // MIR verify: ここで Undefined / dominator エラーが出るかを見る。
let mut verifier = MirVerifier::new(); let mut verifier = MirVerifier::new();
if let Err(errors) = verifier.verify_module(&compiled.module) { if let Err(errors) = verifier.verify_module(&compiled.module) {

View File

@ -8,6 +8,20 @@ mod tests {
use crate::mir::join_ir::JoinInst; use crate::mir::join_ir::JoinInst;
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, ValueId}; use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, ValueId};
use std::collections::BTreeMap; 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 /// Helper to create a simple if/else function matching the "simple" pattern
fn create_simple_pattern_mir() -> MirFunction { fn create_simple_pattern_mir() -> MirFunction {
@ -128,6 +142,11 @@ mod tests {
/// 順番に: simple/local/disabled/wrong_name を確認する。 /// 順番に: simple/local/disabled/wrong_name を確認する。
#[test] #[test]
fn test_if_select_pattern_matching() { 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) ==== // ==== 1. Simple pattern (env ON) ====
std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); std::env::set_var("HAKO_JOINIR_IF_SELECT", "1");
@ -185,13 +204,17 @@ mod tests {
} }
// ==== 3. Disabled by default (env OFF) ==== // ==== 3. Disabled by default (env OFF) ====
set_core_off();
std::env::remove_var("HAKO_JOINIR_IF_SELECT"); std::env::remove_var("HAKO_JOINIR_IF_SELECT");
let func = create_simple_pattern_mir(); let func = create_simple_pattern_mir();
let entry_block = func.entry_block; let entry_block = func.entry_block;
let result = try_lower_if_to_joinir(&func, entry_block, false, None); // Phase 61-1: Pure If 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"); eprintln!("✅ If/Select lowering correctly disabled by default");
@ -211,7 +234,7 @@ mod tests {
eprintln!("✅ Function name filter working correctly"); eprintln!("✅ Function name filter working correctly");
// Clean up // Clean up
std::env::remove_var("HAKO_JOINIR_IF_SELECT"); clear_joinir_flags();
} }
// ============================================================================ // ============================================================================
@ -291,6 +314,7 @@ mod tests {
#[test] #[test]
fn test_if_select_simple_with_verify() { fn test_if_select_simple_with_verify() {
let _env = strict_if_env_guard();
use crate::mir::join_ir::verify::verify_select_minimal; use crate::mir::join_ir::verify::verify_select_minimal;
// Create simple pattern JoinIR // Create simple pattern JoinIR
@ -309,6 +333,7 @@ mod tests {
#[test] #[test]
fn test_if_select_local_with_verify() { fn test_if_select_local_with_verify() {
let _env = strict_if_env_guard();
use crate::mir::join_ir::verify::verify_select_minimal; use crate::mir::join_ir::verify::verify_select_minimal;
// Create local pattern JoinIR // Create local pattern JoinIR
@ -327,6 +352,7 @@ mod tests {
#[test] #[test]
fn test_if_select_verify_rejects_multiple_selects() { fn test_if_select_verify_rejects_multiple_selects() {
let _env = strict_if_env_guard();
use crate::mir::join_ir::verify::verify_select_minimal; use crate::mir::join_ir::verify::verify_select_minimal;
// Create JoinIR with 2 Select instructions (invalid) // Create JoinIR with 2 Select instructions (invalid)
@ -357,6 +383,7 @@ mod tests {
#[test] #[test]
fn test_if_select_verify_checks_invariants() { fn test_if_select_verify_checks_invariants() {
let _env = strict_if_env_guard();
use crate::mir::join_ir::verify::verify_select_minimal; use crate::mir::join_ir::verify::verify_select_minimal;
// Create valid JoinIR // Create valid JoinIR
@ -517,6 +544,7 @@ mod tests {
fn test_if_merge_simple_pattern() { fn test_if_merge_simple_pattern() {
use crate::mir::join_ir::JoinInst; use crate::mir::join_ir::JoinInst;
let _env = strict_if_env_guard();
std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); std::env::set_var("HAKO_JOINIR_IF_SELECT", "1");
let func = create_if_merge_simple_pattern_mir(); let func = create_if_merge_simple_pattern_mir();
@ -558,6 +586,7 @@ mod tests {
fn test_if_merge_multiple_pattern() { fn test_if_merge_multiple_pattern() {
use crate::mir::join_ir::JoinInst; use crate::mir::join_ir::JoinInst;
let _env = strict_if_env_guard();
std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); std::env::set_var("HAKO_JOINIR_IF_SELECT", "1");
let func = create_if_merge_multiple_pattern_mir(); let func = create_if_merge_multiple_pattern_mir();
@ -653,6 +682,7 @@ mod tests {
fn test_type_hint_propagation_simple() { fn test_type_hint_propagation_simple() {
use crate::mir::MirType; use crate::mir::MirType;
let _env = strict_if_env_guard();
std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); std::env::set_var("HAKO_JOINIR_IF_SELECT", "1");
let func = create_simple_pattern_mir_with_const(); let func = create_simple_pattern_mir_with_const();
@ -690,6 +720,7 @@ mod tests {
fn test_p1_ab_type_inference() { fn test_p1_ab_type_inference() {
use crate::mir::MirType; use crate::mir::MirType;
let _env = strict_if_env_guard();
std::env::set_var("HAKO_JOINIR_IF_SELECT", "1"); std::env::set_var("HAKO_JOINIR_IF_SELECT", "1");
// P1 Simple pattern で Select 生成 // P1 Simple pattern で Select 生成
@ -729,6 +760,7 @@ mod tests {
use crate::mir::join_ir::lowering::if_merge::IfMergeLowerer; use crate::mir::join_ir::lowering::if_merge::IfMergeLowerer;
use crate::mir::MirType; use crate::mir::MirType;
let _env = strict_if_env_guard();
std::env::set_var("NYASH_JOINIR_IF_MERGE", "1"); std::env::set_var("NYASH_JOINIR_IF_MERGE", "1");
// P2 IfMerge Simple pattern で IfMerge 生成 // P2 IfMerge Simple pattern で IfMerge 生成

View File

@ -27,6 +27,11 @@ use crate::mir::{MirCompiler, ValueId};
use crate::parser::NyashParser; use crate::parser::NyashParser;
use std::collections::BTreeMap; 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] #[test]
#[ignore] // 手動実行用Stage-1 minimal #[ignore] // 手動実行用Stage-1 minimal
fn mir_joinir_stage1_using_resolver_auto_lowering() { 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() { fn mir_joinir_stage1_using_resolver_type_sanity() {
// Phase 27.12: 型定義の基本的なサニティチェック(常時実行) // Phase 27.12: 型定義の基本的なサニティチェック(常時実行)
// stage1_using_resolver 用の JoinFunction が作成できることを確認 // stage1_using_resolver 用の JoinFunction が作成できることを確認
ensure_joinir_strict_env();
let resolve_id = JoinFuncId::new(20); let resolve_id = JoinFuncId::new(20);
let resolve_func = JoinFunction::new( 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() { fn mir_joinir_stage1_using_resolver_empty_module_returns_none() {
// Phase 27.13: 空の MIR モジュールでは None を返すことを確認 // Phase 27.13: 空の MIR モジュールでは None を返すことを確認
// Stage1UsingResolverBox.resolve_for_source/1 関数が存在しない場合のフォールバック動作 // Stage1UsingResolverBox.resolve_for_source/1 関数が存在しない場合のフォールバック動作
ensure_joinir_strict_env();
// 最小限の MIR モジュールを作成 // 最小限の MIR モジュールを作成
use crate::mir::MirModule; use crate::mir::MirModule;

View File

@ -35,6 +35,11 @@ fn ensure_stage3_env() {
std::env::set_var("HAKO_ENABLE_USING", "1"); 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 のテスト用フィクスチャ。 /// StringHelpers.skip_ws + 最小 Main のテスト用フィクスチャ。
/// Stage1 CLI 全体を読み込まずに、StringHelpers 内部の `.length()` 正規化だけを確認する。 /// Stage1 CLI 全体を読み込まずに、StringHelpers 内部の `.length()` 正規化だけを確認する。
fn stage1_staticcompiler_fixture_src() -> String { fn stage1_staticcompiler_fixture_src() -> String {
@ -62,6 +67,7 @@ static box Main {
#[test] #[test]
fn mir_stage1_staticcompiler_receiver_compiles_and_verifies() { fn mir_stage1_staticcompiler_receiver_compiles_and_verifies() {
ensure_stage3_env(); ensure_stage3_env();
ensure_joinir_strict_env();
let src = stage1_staticcompiler_fixture_src(); let src = stage1_staticcompiler_fixture_src();
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok"); let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok");
@ -81,6 +87,7 @@ fn mir_stage1_staticcompiler_receiver_compiles_and_verifies() {
#[test] #[test]
fn mir_stage1_staticcompiler_receiver_exec_succeeds() { fn mir_stage1_staticcompiler_receiver_exec_succeeds() {
ensure_stage3_env(); ensure_stage3_env();
ensure_joinir_strict_env();
std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); // Plugin依存を排除 std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); // Plugin依存を排除
let src = stage1_staticcompiler_fixture_src(); let src = stage1_staticcompiler_fixture_src();
@ -113,6 +120,7 @@ fn mir_stage1_staticcompiler_receiver_exec_succeeds() {
#[test] #[test]
fn mir_stage1_staticcompiler_receiver_normalizes_to_stringbox() { fn mir_stage1_staticcompiler_receiver_normalizes_to_stringbox() {
ensure_stage3_env(); ensure_stage3_env();
ensure_joinir_strict_env();
let src = stage1_staticcompiler_fixture_src(); let src = stage1_staticcompiler_fixture_src();
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok"); let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok");

View File

@ -41,7 +41,9 @@ fn phase67_type_hint_policy_p3c_integration() {
// 一般関数は P3-C 候補 // 一般関数は P3-C 候補
assert!(TypeHintPolicy::is_p3c_target("ArrayProcessor.process/1")); 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 は排他的 // is_target と is_p3c_target は排他的
let p3c_func = "GenericTypeGetTest.array_get/0"; 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)], inputs: vec![(then_bb, v2), (else_bb, v3)],
type_hint: None, // P3-C: type_hint なし 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<ValueId, MirType> = BTreeMap::new(); let mut types: BTreeMap<ValueId, MirType> = 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_a = crate::mir::phi_core::if_phi::infer_type_from_phi(&f, v4, &types);
let result_b = GenericTypeResolver::resolve_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!(
assert_eq!(result_a, Some(MirType::Integer), "Type should be inferred as Integer"); 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"
);
} }

View File

@ -24,6 +24,13 @@ else
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
fi 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) # Resolve nyash/hakorune binary via test_runner helper (ensures consistent env)
if [ ! -f "$IN" ]; then if [ ! -f "$IN" ]; then
echo "[FAIL] input not found: $IN" >&2 echo "[FAIL] input not found: $IN" >&2
@ -154,6 +161,39 @@ PY
fi fi
export HAKO_MIRBUILDER_IMPORTS="$IMPORTS_JSON" 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) # Check if FORCE jsonfrag mode is requested (bypasses Stage-B entirely)
if [ "${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-0}" = "1" ]; then if [ "${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-0}" = "1" ]; then
# Extract limit from code using grep/awk # 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 echo "[emit:trace] Stage-B: Starting parse of input (${code_len} chars)..." >&2
fi 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[*]:-<none>}" >&2
fi
# Run Stage-B with temp file (avoid subshell CODE variable expansion) # Run Stage-B with temp file (avoid subshell CODE variable expansion)
PROG_JSON_RAW=$(cd "$ROOT" && \ 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}" \ HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=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_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=$? 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 if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[emit:trace] Stage-B: Raw output length=${#PROG_JSON_RAW} chars, rc=$rc" >&2 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 # Extract Program JSON from raw output
PROG_JSON_OUT=$(extract_program_json "$PROG_JSON_RAW" 2>/dev/null || true) PROG_JSON_OUT=$(extract_program_json "$PROG_JSON_RAW" 2>/dev/null || true)
extract_rc=$? 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 [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
if [ $extract_rc -eq 0 ] && [ -n "$PROG_JSON_OUT" ]; then if [ $extract_rc -eq 0 ] && [ -n "$PROG_JSON_OUT" ]; then
@ -372,26 +448,35 @@ if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
fi fi
# Update rc to reflect extraction result # Update rc to reflect extraction result
stageb_ok=1
if [ $extract_rc -ne 0 ] || [ -z "$PROG_JSON_OUT" ]; then if [ $extract_rc -ne 0 ] || [ -z "$PROG_JSON_OUT" ]; then
rc=1 rc=1
stageb_ok=0
fi fi
set -e set -e
# If Stage-B fails, skip to direct MIR emit paths (provider/legacy) # 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 StageB errors before falling back # Diagnose common StageB errors before falling back
diagnose_stageb_failure "$PROG_JSON_RAW" diagnose_stageb_failure "$PROG_JSON_RAW"
# Stage-B not available - fall back to legacy CLI path directly # Stage-B not available - fall back to legacy CLI path directly
# Skip the intermediate Program(JSON) step and emit MIR 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}" \ if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \
HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \ HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \
HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \ HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \
NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \ NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \
NYASH_MIR_UNIFIED_CALL=${NYASH_MIR_UNIFIED_CALL:-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" echo "[OK] MIR JSON written (direct-emit): $OUT"
exit 0 exit 0
fi 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 echo "[FAIL] Stage-B and direct MIR emit both failed" >&2
exit 1 exit 1
fi fi
@ -400,15 +485,18 @@ fi
if ! printf '%s' "$PROG_JSON_OUT" | grep -q '"kind"\s*:\s*"Program"'; then if ! printf '%s' "$PROG_JSON_OUT" | grep -q '"kind"\s*:\s*"Program"'; then
# Invalid Program JSON - fall back to direct emit事前に簡単な診断を出す # Invalid Program JSON - fall back to direct emit事前に簡単な診断を出す
diagnose_stageb_failure "$PROG_JSON_RAW" diagnose_stageb_failure "$PROG_JSON_RAW"
direct_ok=0
if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \ if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-1}" \
HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \ HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \
HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \ HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \
NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \ NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \
NYASH_MIR_UNIFIED_CALL=${NYASH_MIR_UNIFIED_CALL:-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" echo "[OK] MIR JSON written (direct-emit-fallback): $OUT"
exit 0 exit 0
fi 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] StageB output invalid and direct emit failed" >&2 echo "[FAIL] StageB output invalid and direct emit failed" >&2
exit 1 exit 1
fi fi

View File

@ -21,6 +21,12 @@ if [ -z "${BIN}" ]; then
elif [ -x "$ROOT/target/release/nyash" ]; then BIN="$ROOT/target/release/nyash"; 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 else echo "[selfhost] error: NYASH_BIN not set and no binary found under target/release" >&2; exit 2; fi
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="" IN=""
JSON_OUT="" JSON_OUT=""
@ -29,6 +35,20 @@ EXE_OUT=""
DO_RUN=0 DO_RUN=0
KEEP_TMP=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 while [ $# -gt 0 ]; do
case "$1" in case "$1" in
--in) IN="$2"; shift 2;; --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) # Emit Program(JSON v0; prefer BuildBox for emit-only when HAKO_USE_BUILDBOX=1)
RAW="/tmp/hako_stageb_raw_$$.txt" RAW="/tmp/hako_stageb_raw_$$.txt"
stageb_rc=0
SRC_CONTENT="$(cat "$IN")" SRC_CONTENT="$(cat "$IN")"
stageb_cmd_desc=""
if [ "${HAKO_USE_BUILDBOX:-0}" = "1" ] && [ "$DO_RUN" = "0" ] && [ -z "$EXE_OUT" ]; then 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" WRAP="/tmp/hako_buildbox_wrap_$$.hako"
cat > "$WRAP" <<'HAKO' cat > "$WRAP" <<'HAKO'
include "lang/src/compiler/build/build_box.hako" include "lang/src/compiler/build/build_box.hako"
@ -62,31 +86,47 @@ static box Main { method main(args) {
HAKO HAKO
( (
export HAKO_SRC="$SRC_CONTENT" export HAKO_SRC="$SRC_CONTENT"
export NYASH_QUIET=0 HAKO_QUIET=0 NYASH_CLI_VERBOSE=0
cd "$ROOT" && "$BIN" --backend vm "$WRAP" cd "$ROOT" && "$BIN" --backend vm "$WRAP"
) > "$RAW" 2>&1 || true ) > "$RAW" 2>&1 || stageb_rc=$?
rm -f "$WRAP" 2>/dev/null || true rm -f "$WRAP" 2>/dev/null || true
else 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" && \ cd "$ROOT" && \
"$BIN" --backend vm \ "$BIN" --backend vm \
"$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- \ "$ROOT/lang/src/compiler/entry/compiler.hako" -- \
--source "$SRC_CONTENT" --stage-b --stage3 --source "$SRC_CONTENT"
) > "$RAW" 2>&1 || true ) > "$RAW" 2>&1 || stageb_rc=$?
fi 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] StageB emit failed" >&2 echo "[selfhost] StageB emit failed" >&2
tail -n 120 "$RAW" >&2 || true 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 exit 1
fi fi
rm -f "$RAW" 2>/dev/null || true rm -f "$RAW" 2>/dev/null || true

View File

@ -20,6 +20,7 @@ Notes
- Avoid running heavy integration smokes in CI by default. Use `--profile quick`. - Avoid running heavy integration smokes in CI by default. Use `--profile quick`.
- When a test depends on external tools (e.g., LLVM), prefer `[SKIP:<reason>]` over failure. - When a test depends on external tools (e.g., LLVM), prefer `[SKIP:<reason>]` over failure.
- StageB/selfhost canaries`stage1_launcher_*`, `phase251*` など)は Stage3 デフォルト環境で安定しないため、quick プロファイルでは `[SKIP:stageb]` として扱い、必要に応じて別プロファイルintegration/fullで個別に実行する。 - StageB/selfhost canaries`stage1_launcher_*`, `phase251*` など)は Stage3 デフォルト環境で安定しないため、quick プロファイルでは `[SKIP:stageb]` として扱い、必要に応じて別プロファイルintegration/fullで個別に実行する。
- Selfhost quick カバレッジは最小 1 本(`core/selfhost_minimal.sh`に絞り、Stage3 + JoinIR 前提で StageB→VM を通るかだけを確認する。
- S3 backend 向けの長尺テスト群も quick 向きではないため、timeout を短く保ちたい場合は `[SKIP:slow]` にして別途ローカルで回すことを推奨する。 - S3 backend 向けの長尺テスト群も quick 向きではないため、timeout を短く保ちたい場合は `[SKIP:slow]` にして別途ローカルで回すことを推奨する。
Quick tips Quick tips

View File

@ -20,6 +20,8 @@ fi
# Stage-3 is default: prefer feature flag instead of legacy parser envs. # Stage-3 is default: prefer feature flag instead of legacy parser envs.
export NYASH_FEATURES="${NYASH_FEATURES:-stage3}" 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 # Debug convenience: HAKO_DEBUG=1 enables execution trace and log passthrough
if [ "${HAKO_DEBUG:-0}" = "1" ]; then if [ "${HAKO_DEBUG:-0}" = "1" ]; then
@ -95,6 +97,7 @@ log_error() {
| grep -v "^\\[DEBUG/" \ | grep -v "^\\[DEBUG/" \
| grep -v "^\\[ssa-undef-debug\\]" \ | grep -v "^\\[ssa-undef-debug\\]" \
| grep -v '^\[PluginBoxFactory\]' \ | grep -v '^\[PluginBoxFactory\]' \
| grep -v '^\[plugin/init\]' \
| grep -v '^\[using.dylib/autoload\]' \ | grep -v '^\[using.dylib/autoload\]' \
| grep -v "^\[vm\] Stage-3" \ | grep -v "^\[vm\] Stage-3" \
| grep -v "^\[DEBUG\]" \ | grep -v "^\[DEBUG\]" \

View File

@ -4,6 +4,10 @@ set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../../../../../../.." && pwd)" ROOT="$(cd "$(dirname "$0")/../../../../../../.." && pwd)"
source "$ROOT/tools/smokes/v2/lib/test_runner.sh" || true 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 # 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\" && 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 timeout "${HAKO_BUILD_TIMEOUT:-10}" bash -c "cd \"$ROOT/crates/nyash_kernel\" && cargo build -q --release >/dev/null" || true

View File

@ -0,0 +1,51 @@
#!/bin/bash
# selfhost_minimal.sh — Minimal selfhost StageB→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

View File

@ -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
# StageB 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

View File

@ -186,6 +186,11 @@ setup_environment() {
# プロファイル専用設定 # プロファイル専用設定
export SMOKES_CURRENT_PROFILE="$PROFILE" 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 if [ -n "$TIMEOUT" ]; then