From df66ea3ecb76f9563d0c09d5f438373dae3fc622 Mon Sep 17 00:00:00 2001 From: Selfhosting Dev Date: Thu, 18 Sep 2025 04:23:11 +0900 Subject: [PATCH] cli: add --emit-exe (+ --emit-exe-nyrt/--emit-exe-libs) to build native exe via ny-llvmc; docs updated --- docs/LLVM_HARNESS.md | 5 ++++- docs/guides/selfhost-pilot.md | 1 + src/cli.rs | 28 +++++++++++++++++++++++ src/runner/dispatch.rs | 42 +++++++++++++++++++++++++++++++++++ src/runner/modes/mir.rs | 40 +++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 1 deletion(-) diff --git a/docs/LLVM_HARNESS.md b/docs/LLVM_HARNESS.md index 4db4339e..5f3dc275 100644 --- a/docs/LLVM_HARNESS.md +++ b/docs/LLVM_HARNESS.md @@ -37,9 +37,12 @@ Wiring(Rust 側) 2) `python3 tools/llvmlite_harness.py --in --out ` を起動 3) 成功後は通常のリンク手順(NyRT とリンク) -Tools(統合フロー) +Tools / CLI(統合フロー) - crate 直結の EXE 出力: `NYASH_LLVM_COMPILER=crate NYASH_LLVM_EMIT=exe tools/build_llvm.sh apps/tests/ternary_basic.nyash -o app` - 環境変数 `NYASH_LLVM_NYRT` で NyRT の場所を、`NYASH_LLVM_LIBS` で追加フラグを指定できる。 + - CLI から直接 EXE 出力(新規): + - `./target/release/nyash --emit-exe tmp/app --backend mir apps/tests/ternary_basic.nyash` + - 追加オプション: `--emit-exe-nyrt ` / `--emit-exe-libs ""` Scope(Phase 15) - 最小命令: Const/BinOp/Compare/Phi/Branch/Jump/Return diff --git a/docs/guides/selfhost-pilot.md b/docs/guides/selfhost-pilot.md index 5019f647..682d3308 100644 --- a/docs/guides/selfhost-pilot.md +++ b/docs/guides/selfhost-pilot.md @@ -20,6 +20,7 @@ CI Workflows 2) EXE 生成: `./target/release/ny-llvmc --in tmp/app.json --emit exe --nyrt target/release --out tmp/app` 3) 実行: `./tmp/app`(戻り値が exit code) - ワンコマンドスモーク: `bash tools/crate_exe_smoke.sh apps/tests/ternary_basic.nyash` + - CLI で直接 EXE 出力: `./target/release/nyash --emit-exe tmp/app --backend mir apps/tests/ternary_basic.nyash` - Installs LLVM 18 + llvmlite, then runs `tools/exe_first_smoke.sh`. Useful Env Flags diff --git a/src/cli.rs b/src/cli.rs index 4b9f22ad..024dbc2e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -71,6 +71,10 @@ pub struct CliConfig { pub cli_usings: Vec, // Emit MIR JSON to a file and exit (bridge mode) pub emit_mir_json: Option, + // Emit native executable via ny-llvmc (crate) and exit + pub emit_exe: Option, + pub emit_exe_nyrt: Option, + pub emit_exe_libs: Option, } impl CliConfig { @@ -139,6 +143,24 @@ impl CliConfig { .value_name("FILE") .help("Emit MIR JSON v0 to file (validation-friendly) and exit") ) + .arg( + Arg::new("emit-exe") + .long("emit-exe") + .value_name("FILE") + .help("Emit native executable via ny-llvmc (crate) and exit") + ) + .arg( + Arg::new("emit-exe-nyrt") + .long("emit-exe-nyrt") + .value_name("DIR") + .help("Directory containing libnyrt.a (used with --emit-exe)") + ) + .arg( + Arg::new("emit-exe-libs") + .long("emit-exe-libs") + .value_name("FLAGS") + .help("Extra linker flags for ny-llvmc when emitting executable") + ) .arg( Arg::new("stage3") .long("stage3") @@ -491,6 +513,9 @@ impl CliConfig { .map(|v| v.cloned().collect()) .unwrap_or_else(|| Vec::new()), emit_mir_json: matches.get_one::("emit-mir-json").cloned(), + emit_exe: matches.get_one::("emit-exe").cloned(), + emit_exe_nyrt: matches.get_one::("emit-exe-nyrt").cloned(), + emit_exe_libs: matches.get_one::("emit-exe-libs").cloned(), } } } @@ -546,6 +571,9 @@ impl Default for CliConfig { build_target: None, cli_usings: Vec::new(), emit_mir_json: None, + emit_exe: None, + emit_exe_nyrt: None, + emit_exe_libs: None, } } } diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs index 2851758c..8c09b2e9 100644 --- a/src/runner/dispatch.rs +++ b/src/runner/dispatch.rs @@ -181,6 +181,48 @@ impl NyashRunner { println!("MIR JSON written: {}", p.display()); std::process::exit(0); } + // If CLI requested EXE emit, generate JSON then invoke ny-llvmc to link NyRT and exit. + if let Some(exe_out) = self.config.emit_exe.as_ref() { + let tmp_dir = std::path::Path::new("tmp"); + let _ = std::fs::create_dir_all(tmp_dir); + let json_path = tmp_dir.join("nyash_cli_emit.json"); + if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(module, &json_path) { + eprintln!("❌ MIR JSON emit error: {}", e); + std::process::exit(1); + } + // Resolve ny-llvmc + let ny_llvmc = std::env::var("NYASH_NY_LLVM_COMPILER") + .ok() + .and_then(|s| if !s.is_empty() { Some(std::path::PathBuf::from(s)) } else { None }) + .or_else(|| which::which("ny-llvmc").ok()) + .unwrap_or_else(|| std::path::PathBuf::from("target/release/ny-llvmc")); + // Build command + let mut cmd = std::process::Command::new(ny_llvmc); + cmd.arg("--in").arg(&json_path) + .arg("--emit").arg("exe") + .arg("--out").arg(exe_out); + if let Some(dir) = self.config.emit_exe_nyrt.as_ref() { + cmd.arg("--nyrt").arg(dir); + } else { + // default hint + cmd.arg("--nyrt").arg("target/release"); + } + if let Some(flags) = self.config.emit_exe_libs.as_ref() { + if !flags.trim().is_empty() { + cmd.arg("--libs").arg(flags); + } + } + let status = cmd.status().unwrap_or_else(|e| { + eprintln!("❌ failed to spawn ny-llvmc: {}", e); + std::process::exit(1); + }); + if !status.success() { + eprintln!("❌ ny-llvmc failed with status: {:?}", status.code()); + std::process::exit(1); + } + println!("EXE written: {}", exe_out); + std::process::exit(0); + } use crate::backend::MirInterpreter; use crate::box_trait::{BoolBox, IntegerBox, StringBox}; use crate::boxes::FloatBox; diff --git a/src/runner/modes/mir.rs b/src/runner/modes/mir.rs index 0cf4b2f0..9e6c8001 100644 --- a/src/runner/modes/mir.rs +++ b/src/runner/modes/mir.rs @@ -75,5 +75,45 @@ impl NyashRunner { println!("MIR JSON written: {}", p.display()); std::process::exit(0); } + + // Emit native executable via ny-llvmc (crate) and exit + if let Some(exe_out) = self.config.emit_exe.as_ref() { + let tmp_dir = std::path::Path::new("tmp"); + let _ = std::fs::create_dir_all(tmp_dir); + let json_path = tmp_dir.join("nyash_cli_emit.json"); + if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness(&compile_result.module, &json_path) { + eprintln!("❌ MIR JSON emit error: {}", e); + std::process::exit(1); + } + let ny_llvmc = std::env::var("NYASH_NY_LLVM_COMPILER") + .ok() + .and_then(|s| if !s.is_empty() { Some(std::path::PathBuf::from(s)) } else { None }) + .or_else(|| which::which("ny-llvmc").ok()) + .unwrap_or_else(|| std::path::PathBuf::from("target/release/ny-llvmc")); + let mut cmd = std::process::Command::new(ny_llvmc); + cmd.arg("--in").arg(&json_path) + .arg("--emit").arg("exe") + .arg("--out").arg(exe_out); + if let Some(dir) = self.config.emit_exe_nyrt.as_ref() { + cmd.arg("--nyrt").arg(dir); + } else { + cmd.arg("--nyrt").arg("target/release"); + } + if let Some(flags) = self.config.emit_exe_libs.as_ref() { + if !flags.trim().is_empty() { + cmd.arg("--libs").arg(flags); + } + } + let status = cmd.status().unwrap_or_else(|e| { + eprintln!("❌ failed to spawn ny-llvmc: {}", e); + std::process::exit(1); + }); + if !status.success() { + eprintln!("❌ ny-llvmc failed with status: {:?}", status.code()); + std::process::exit(1); + } + println!("EXE written: {}", exe_out); + std::process::exit(0); + } } }