feat(stage1): Phase 25.1 - Stage1 CLI デバッグ改善 + 環境変数削減計画

-  Stage1 CLI デバッグログ追加
  - lang/src/runner/stage1_cli.hako: STAGE1_CLI_DEBUG対応
  - 各関数でentry/exit/状態ログ出力
  - SSAバグ調査を容易化

-  Rust bridge 実装
  - src/runner/stage1_bridge.rs: 子プロセス起動・環境変数配線
  - NYASH_ENTRY設定、モジュールリスト生成

-  デバッグヘルパースクリプト
  - tools/stage1_debug.sh: 環境変数自動診断・詳細ログ
  - tools/stage1_minimal.sh: 推奨5変数のみで実行

-  環境変数削減計画(25個→5個)
  - docs/development/proposals/env-var-reduction-report.md
    - 使用箇所マトリックス、依存関係グラフ
    - 6段階削減ロードマップ(80%削減目標)
  - docs/development/proposals/stage1-architecture-improvement.md
    - 他言語事例調査(Rust/Go/Nim)
    - アーキテクチャ統一案、実装ロードマップ

-  LoopForm v2 設計ドキュメント
  - docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md

🎯 成果: Stage1起動の複雑さを可視化、80%削減計画確立
This commit is contained in:
nyash-codex
2025-11-21 06:22:02 +09:00
parent 6865f4acfa
commit 51359574d9
7 changed files with 2278 additions and 0 deletions

271
src/runner/stage1_bridge.rs Normal file
View File

@ -0,0 +1,271 @@
/*!
* Stage1 CLI bridge — delegate to Hako Stage1 stub when explicitly enabled.
*
* - Entry: NYASH_USE_STAGE1_CLI=1 (default OFF).
* - Toggle guard for child recursion: NYASH_STAGE1_CLI_CHILD=1 (set by bridge).
* - Entry path override: STAGE1_CLI_ENTRY or HAKORUNE_STAGE1_ENTRY (optional).
* - Mode toggles:
* - STAGE1_EMIT_PROGRAM_JSON=1 : emit program-json <source.hako>
* - STAGE1_EMIT_MIR_JSON=1 : emit mir-json (<source.hako> or STAGE1_PROGRAM_JSON)
* - STAGE1_BACKEND={vm|llvm|pyvm} hint for run path (default: CLI backend)
*
* Notes
* - This bridge aims to keep Rust Stage0 thin: it only invokes the Stage1 stub
* (lang/src/runner/stage1_cli.hako) with script args and exits with the stub's code.
* - When toggles are unset or this is a child invocation, the bridge is a no-op.
*/
use super::NyashRunner;
use crate::cli::CliGroups;
use serde_json;
use std::{path::Path, process};
impl NyashRunner {
fn collect_modules_list() -> Option<String> {
use std::collections::HashSet;
use std::fs;
use std::path::PathBuf;
let path = PathBuf::from("nyash.toml");
if !path.exists() {
return None;
}
let content = fs::read_to_string(&path).ok()?;
let mut entries: Vec<String> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
let mut in_modules = false;
for raw in content.lines() {
let line = raw.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.starts_with('[') {
in_modules = line == "[modules]";
continue;
}
if !in_modules {
continue;
}
if let Some((k, v_raw)) = line.split_once('=') {
let key = k.trim().trim_matches('"');
let mut v = v_raw.trim();
if let Some((head, _comment)) = v.split_once('#') {
v = head.trim();
}
if v.starts_with('"') && v.ends_with('"') && v.len() >= 2 {
v = &v[1..v.len().saturating_sub(1)];
}
if !key.is_empty() && !v.is_empty() {
if seen.insert(key.to_string()) {
entries.push(format!("{key}={v}"));
}
}
}
}
// Add a few well-known aliases required by Stage1 CLI if they are absent in nyash.toml.
for (k, v) in [
(
"lang.compiler.entry.using_resolver_box",
"lang/src/compiler/entry/using_resolver_box.hako",
),
(
"selfhost.shared.host_bridge.codegen_bridge",
"lang/src/shared/host_bridge/codegen_bridge_box.hako",
),
] {
if seen.insert(k.to_string()) {
entries.push(format!("{k}={v}"));
}
}
if entries.is_empty() {
None
} else {
Some(entries.join("|||"))
}
}
/// If enabled, run the Stage1 CLI stub as a child process and return its exit code.
/// Returns None when the bridge is not engaged.
pub(crate) fn maybe_run_stage1_cli_stub(&self, groups: &CliGroups) -> Option<i32> {
if std::env::var("NYASH_STAGE1_CLI_CHILD").ok().as_deref() == Some("1") {
return None;
}
if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() != Some("1") {
return None;
}
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1")
|| std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2")
{
eprintln!("[stage1-bridge/debug] NYASH_USE_STAGE1_CLI=1 detected");
}
let entry = std::env::var("STAGE1_CLI_ENTRY")
.or_else(|_| std::env::var("HAKORUNE_STAGE1_ENTRY"))
.unwrap_or_else(|_| "lang/src/runner/stage1_cli.hako".to_string());
if !Path::new(&entry).exists() {
eprintln!("[stage1-cli] entry not found: {}", entry);
return Some(97);
}
let source = groups
.input
.file
.as_ref()
.cloned()
.or_else(|| std::env::var("STAGE1_SOURCE").ok())
.or_else(|| std::env::var("STAGE1_INPUT").ok());
let emit_program = std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1");
let emit_mir = std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1");
let mut stage1_args: Vec<String> = Vec::new();
let mut stage1_env_script_args: Option<String> = None;
let mut stage1_source_env: Option<String> = None;
let mut stage1_progjson_env: Option<String> = None;
if emit_program {
let src = source.as_ref().cloned().unwrap_or_else(|| {
eprintln!("[stage1-cli] STAGE1_EMIT_PROGRAM_JSON=1 but no input file provided");
process::exit(97);
});
stage1_args.push("emit".into());
stage1_args.push("program-json".into());
stage1_args.push(src);
stage1_source_env = stage1_args.last().cloned();
} else if emit_mir {
if let Ok(pjson) = std::env::var("STAGE1_PROGRAM_JSON") {
stage1_args.push("emit".into());
stage1_args.push("mir-json".into());
stage1_args.push("--from-program-json".into());
stage1_args.push(pjson);
stage1_progjson_env = stage1_args.last().cloned();
} else {
let src = source.as_ref().cloned().unwrap_or_else(|| {
eprintln!("[stage1-cli] STAGE1_EMIT_MIR_JSON=1 but no input file provided");
process::exit(97);
});
stage1_args.push("emit".into());
stage1_args.push("mir-json".into());
stage1_args.push(src);
stage1_source_env = stage1_args.last().cloned();
}
} else {
let src = source.as_ref().cloned().unwrap_or_else(|| {
eprintln!("[stage1-cli] NYASH_USE_STAGE1_CLI=1 requires an input file to run");
process::exit(97);
});
stage1_args.push("run".into());
let backend = std::env::var("STAGE1_BACKEND")
.ok()
.unwrap_or_else(|| groups.backend.backend.clone());
stage1_args.push("--backend".into());
stage1_args.push(backend);
stage1_args.push(src);
stage1_source_env = stage1_args.last().cloned();
}
// Forward script args provided to the parent process (via -- arg1 arg2 ...)
if let Ok(json) = std::env::var("NYASH_SCRIPT_ARGS_JSON") {
if let Ok(mut extras) = serde_json::from_str::<Vec<String>>(&json) {
stage1_args.append(&mut extras);
}
}
// Also pass args via env to guarantee argv is well-defined in the stub.
if std::env::var("NYASH_SCRIPT_ARGS_JSON").is_err() {
if let Ok(json) = serde_json::to_string(&stage1_args) {
stage1_env_script_args = Some(json);
}
}
let exe = std::env::current_exe().unwrap_or_else(|_| {
// Fallback to release binary path when current_exe is unavailable
std::path::PathBuf::from("target/release/nyash")
});
let mut cmd = std::process::Command::new(exe);
let entry_fn =
std::env::var("NYASH_ENTRY").unwrap_or_else(|_| "Stage1CliMain.main/1".to_string());
cmd.arg(&entry).arg("--");
for a in &stage1_args {
cmd.arg(a);
}
if let Some(json) = stage1_env_script_args.as_ref() {
cmd.env("NYASH_SCRIPT_ARGS_JSON", json);
}
if let Some(src) = stage1_source_env.as_ref() {
cmd.env("STAGE1_SOURCE", src);
}
if let Some(pjson) = stage1_progjson_env.as_ref() {
cmd.env("STAGE1_PROGRAM_JSON", pjson);
}
// Pass source text inline to avoid FileBox dependency when possible.
if stage1_source_env.is_none() {
if let Some(src_path) = source.as_ref() {
if let Ok(text) = std::fs::read_to_string(&src_path) {
cmd.env("STAGE1_SOURCE_TEXT", text);
}
}
} else if let Some(src_path) = stage1_source_env.as_ref() {
if let Ok(text) = std::fs::read_to_string(src_path) {
cmd.env("STAGE1_SOURCE_TEXT", text);
}
}
cmd.env("NYASH_STAGE1_CLI_CHILD", "1");
if std::env::var("NYASH_NYRT_SILENT_RESULT").is_err() {
cmd.env("NYASH_NYRT_SILENT_RESULT", "1");
}
if std::env::var("NYASH_DISABLE_PLUGINS").is_err() {
cmd.env("NYASH_DISABLE_PLUGINS", "0");
}
if std::env::var("NYASH_FILEBOX_MODE").is_err() {
cmd.env("NYASH_FILEBOX_MODE", "auto");
}
if std::env::var("NYASH_BOX_FACTORY_POLICY").is_err() {
cmd.env("NYASH_BOX_FACTORY_POLICY", "builtin_first");
}
if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() {
cmd.env("HAKO_STAGEB_APPLY_USINGS", "1");
}
if std::env::var("NYASH_ENABLE_USING").is_err() {
cmd.env("NYASH_ENABLE_USING", "1");
}
if std::env::var("HAKO_ENABLE_USING").is_err() {
cmd.env("HAKO_ENABLE_USING", "1");
}
if std::env::var("NYASH_PARSER_STAGE3").is_err() {
cmd.env("NYASH_PARSER_STAGE3", "1");
}
if std::env::var("HAKO_PARSER_STAGE3").is_err() {
cmd.env("HAKO_PARSER_STAGE3", "1");
}
if std::env::var("HAKO_STAGEB_MODULES_LIST").is_err() {
if let Some(mods) = Self::collect_modules_list() {
cmd.env("HAKO_STAGEB_MODULES_LIST", mods);
}
}
if std::env::var("NYASH_ENTRY").is_err() {
cmd.env("NYASH_ENTRY", &entry_fn);
}
if std::env::var("STAGE1_BACKEND").is_err() {
if let Some(be) = stage1_args
.windows(2)
.find(|w| w[0] == "--backend")
.map(|w| w[1].clone())
{
cmd.env("STAGE1_BACKEND", be);
}
}
crate::cli_v!(
"[stage1-cli] delegating to stub: {} -- {}",
entry,
stage1_args.join(" ")
);
let status = match cmd.status() {
Ok(s) => s,
Err(e) => {
eprintln!("[stage1-cli] failed to spawn stub: {}", e);
return Some(97);
}
};
Some(status.code().unwrap_or(1))
}
}