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