Files
hakorune/src/runner/modes/llvm.rs
nyash-codex 35cd93a37a Phase 33-2: JoinInst::Select implementation + minimal If JoinIR lowering
Implementation:
- Add JoinInst::Select variant to JoinIR schema
- Implement Select execution in JoinIR Runner (Bool/Int cond support)
- Add Select handling in JoinIR→MIR Bridge (4-block structure)
- Create test cases (joinir_if_select_simple/local.hako)
- Add dev toggle NYASH_JOINIR_IF_SELECT=1
- Create lowering infrastructure (if_select.rs, stub for Phase 33-3)

Tests:
- 3/3 unit tests pass (test_select_true/false/int_cond)
- Integration tests pass (RC: 0)
- A/B execution verified (existing if_phi vs JoinIR Select)

Files changed:
- New: apps/tests/joinir_if_select_{simple,local}.hako
- New: src/mir/join_ir/lowering/if_select.rs
- Modified: src/mir/join_ir/{mod,json,runner,vm_bridge}.rs
- Modified: src/config/env.rs (joinir_if_select_enabled)
- Modified: docs/reference/environment-variables.md

Phase 33-3 ready: MIR pattern recognition + auto-lowering pending

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 02:58:38 +09:00

397 lines
17 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::super::NyashRunner;
use nyash_rust::mir::passes::method_id_inject::inject_method_ids;
use nyash_rust::{
mir::{MirCompiler, MirInstruction},
parser::NyashParser,
};
use std::{fs, process};
impl NyashRunner {
/// Execute LLVM mode (split)
pub(crate) fn execute_llvm_mode(&self, filename: &str) {
// Initialize plugin host so method_id injection can resolve plugin calls
crate::runner_plugin_init::init_bid_plugins();
// Friendly plugin guard (nonstrict): unify diagnostics across modes
crate::runner::modes::common_util::plugin_guard::check_and_report(
false,
crate::config::env::env_bool("NYASH_JSON_ONLY"),
"llvm",
);
// Read the file
let code = match fs::read_to_string(filename) {
Ok(content) => content,
Err(e) => {
eprintln!("❌ Error reading file {}: {}", filename, e);
process::exit(1);
}
};
// Using handling (AST prelude merge like common/vm paths)
let use_ast = crate::config::env::using_ast_enabled();
let mut code_ref: &str = &code;
let cleaned_code_owned;
let mut prelude_asts: Vec<nyash_rust::ast::ASTNode> = Vec::new();
if crate::config::env::enable_using() {
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(
self, &code, filename,
) {
Ok((clean, paths)) => {
cleaned_code_owned = clean;
code_ref = &cleaned_code_owned;
if !paths.is_empty() && !use_ast {
eprintln!("❌ using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines.");
std::process::exit(1);
}
if use_ast && !paths.is_empty() {
match crate::runner::modes::common_util::resolve::parse_preludes_to_asts(
self, &paths,
) {
Ok(v) => prelude_asts = v,
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
}
}
}
}
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
}
}
// Pre-expand '@name[:T] = expr' sugar at line-head (same as common path)
let preexpanded_owned =
crate::runner::modes::common_util::resolve::preexpand_at_local(code_ref);
code_ref = &preexpanded_owned;
// Parse to AST (main)
let main_ast = match NyashParser::parse_from_string(code_ref) {
Ok(ast) => ast,
Err(e) => {
crate::runner::modes::common_util::diag::print_parse_error_with_context(
filename, code_ref, &e,
);
// Enhanced context: list merged prelude files if any (from text-merge path)
let preludes =
crate::runner::modes::common_util::resolve::clone_last_merged_preludes();
if !preludes.is_empty() {
eprintln!("[parse/context] merged prelude files ({}):", preludes.len());
let show = std::cmp::min(16, preludes.len());
for p in preludes.iter().take(show) {
eprintln!(" - {}", p);
}
if preludes.len() > show {
eprintln!(" ... ({} more)", preludes.len() - show);
}
}
process::exit(1);
}
};
// Merge preludes + main when enabled
let ast = if use_ast && !prelude_asts.is_empty() {
crate::runner::modes::common_util::resolve::merge_prelude_asts_with_main(
prelude_asts,
&main_ast,
)
} else {
main_ast
};
// Macro expansion (env-gated) after merge
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
let ast = crate::runner::modes::macro_child::normalize_core_pass(&ast);
// Compile to MIR
let mut mir_compiler = MirCompiler::new();
let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
&mut mir_compiler,
ast,
Some(filename),
) {
Ok(result) => result,
Err(e) => {
eprintln!("❌ MIR compilation error: {}", e);
process::exit(1);
}
};
println!("📊 MIR Module compiled successfully!");
println!("📊 Functions: {}", compile_result.module.functions.len());
// Inject method_id for BoxCall/PluginInvoke where resolvable (by-id path)
#[allow(unused_mut)]
let mut module = compile_result.module.clone();
let injected = inject_method_ids(&mut module);
if injected > 0 {
crate::cli_v!("[LLVM] method_id injected: {} places", injected);
}
// Phase 32 L-4.3a: JoinIR LLVM experiment hook
// When NYASH_JOINIR_EXPERIMENT=1 and NYASH_JOINIR_LLVM_EXPERIMENT=1,
// try to lower MIR → JoinIR → MIR' for Main.skip/1 to fix PHI issues.
// JoinIR-converted functions are merged back into the original module.
#[cfg(feature = "llvm-harness")]
let module = if crate::config::env::joinir_experiment_enabled()
&& crate::config::env::joinir_llvm_experiment_enabled()
&& crate::config::env::llvm_use_harness()
{
use nyash_rust::mir::join_ir::lower_skip_ws_to_joinir;
use nyash_rust::mir::join_ir_vm_bridge::convert_joinir_to_mir;
eprintln!("[joinir/llvm] Attempting JoinIR path for LLVM execution");
// Try to lower Main.skip/1 to JoinIR
if module.functions.contains_key("Main.skip/1") {
match lower_skip_ws_to_joinir(&module) {
Some(join_module) => {
eprintln!(
"[joinir/llvm] ✅ Lowered to JoinIR ({} functions)",
join_module.functions.len()
);
// Convert JoinIR back to MIR' (with normalized PHI)
match convert_joinir_to_mir(&join_module) {
Ok(mir_from_joinir) => {
eprintln!(
"[joinir/llvm] ✅ Converted to MIR' ({} functions)",
mir_from_joinir.functions.len()
);
// Merge JoinIR functions into original module
// Strategy: Remove Main.skip/1 (PHI-problematic) and rename join_func_0 to Main.skip/1
let mut merged = module.clone();
// Remove the original PHI-problematic Main.skip/1
if merged.functions.remove("Main.skip/1").is_some() {
eprintln!("[joinir/llvm] Removed original Main.skip/1 (PHI-problematic)");
}
for (name, func) in mir_from_joinir.functions {
// Rename join_func_0 → Main.skip/1 to maintain call compatibility
let target_name = if name == "join_func_0" {
eprintln!("[joinir/llvm] Renaming {} → Main.skip/1", name);
"Main.skip/1".to_string()
} else {
eprintln!("[joinir/llvm] Adding JoinIR function: {}", name);
name
};
merged.functions.insert(target_name, func);
}
eprintln!(
"[joinir/llvm] ✅ Merged module ({} functions)",
merged.functions.len()
);
merged
}
Err(e) => {
eprintln!("[joinir/llvm] ❌ JoinIR→MIR conversion failed: {:?}", e);
eprintln!("[joinir/llvm] Falling back to original MIR");
module
}
}
}
None => {
eprintln!("[joinir/llvm] ❌ JoinIR lowering returned None");
eprintln!("[joinir/llvm] Falling back to original MIR");
module
}
}
} else {
eprintln!("[joinir/llvm] Main.skip/1 not found, using original MIR");
module
}
} else {
module
};
// Dev/Test helper: allow executing via PyVM harness when requested
if std::env::var("SMOKES_USE_PYVM").ok().as_deref() == Some("1") {
match super::common_util::pyvm::run_pyvm_harness_lib(&module, "llvm-ast") {
Ok(code) => {
std::process::exit(code);
}
Err(e) => {
eprintln!("❌ PyVM harness error: {}", e);
std::process::exit(1);
}
}
}
// If explicit object path is requested, emit object only
if let Ok(_out_path) = std::env::var("NYASH_LLVM_OBJ_OUT") {
#[cfg(feature = "llvm-harness")]
{
// Harness path (optional): if NYASH_LLVM_USE_HARNESS=1, try Python/llvmlite first.
if crate::config::env::llvm_use_harness() {
if let Err(e) = crate::runner::modes::common_util::exec::llvmlite_emit_object(
&module, &_out_path, 20_000,
) {
eprintln!("{}", e);
process::exit(1);
}
return;
}
// Verify object presence and size (>0)
match std::fs::metadata(&_out_path) {
Ok(meta) => {
if meta.len() == 0 {
eprintln!("❌ harness object is empty: {}", _out_path);
process::exit(1);
}
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!(
"[LLVM] object emitted: {} ({} bytes)",
_out_path,
meta.len()
);
}
}
Err(e) => {
eprintln!(
"❌ harness output not found after emit: {} ({})",
_out_path, e
);
process::exit(1);
}
}
return;
}
#[cfg(all(not(feature = "llvm-harness"), feature = "llvm-inkwell-legacy"))]
{
use nyash_rust::backend::llvm_compile_to_object;
// Ensure parent directory exists for the object file
if let Some(parent) = std::path::Path::new(&_out_path).parent() {
let _ = std::fs::create_dir_all(parent);
}
crate::cli_v!(
"[Runner/LLVM] emitting object to {} (cwd={})",
_out_path,
std::env::current_dir()
.map(|p| p.display().to_string())
.unwrap_or_default()
);
if let Err(e) = llvm_compile_to_object(&module, &_out_path) {
eprintln!("❌ LLVM object emit error: {}", e);
process::exit(1);
}
match std::fs::metadata(&_out_path) {
Ok(meta) if meta.len() > 0 => {
crate::cli_v!(
"[LLVM] object emitted: {} ({} bytes)",
_out_path,
meta.len()
);
}
_ => {
eprintln!("❌ LLVM object not found or empty: {}", _out_path);
process::exit(1);
}
}
return;
}
#[cfg(all(not(feature = "llvm-harness"), not(feature = "llvm-inkwell-legacy")))]
{
eprintln!("❌ LLVM backend not available (object emit).");
process::exit(1);
}
}
// Execute via LLVM backend (harness preferred)
#[cfg(feature = "llvm-harness")]
{
if crate::config::env::llvm_use_harness() {
// Prefer producing a native executable via ny-llvmc, then execute it
let exe_out = "tmp/nyash_llvm_run";
let libs = std::env::var("NYASH_LLVM_EXE_LIBS").ok();
match crate::runner::modes::common_util::exec::ny_llvmc_emit_exe_lib(
&module,
exe_out,
None,
libs.as_deref(),
) {
Ok(()) => {
match crate::runner::modes::common_util::exec::run_executable(
exe_out,
&[],
20_000,
) {
Ok((code, _timed_out, stdout_text)) => {
// Forward program stdout so parity tests can compare outputs
if !stdout_text.is_empty() {
print!("{}", stdout_text);
}
println!("✅ LLVM (harness) execution completed (exit={})", code);
std::process::exit(code);
}
Err(e) => {
eprintln!("❌ run executable error: {}", e);
std::process::exit(1);
}
}
}
Err(e) => {
eprintln!("❌ ny-llvmc emit-exe error: {}", e);
eprintln!(
" Hint: build ny-llvmc: cargo build -p nyash-llvm-compiler --release"
);
std::process::exit(1);
}
}
}
}
// Execute via LLVM backend (mock or real)
#[cfg(feature = "llvm-inkwell-legacy")]
{
use nyash_rust::backend::llvm_compile_and_execute;
let temp_path = "nyash_llvm_temp";
match llvm_compile_and_execute(&module, temp_path) {
Ok(result) => {
if let Some(int_result) = result.as_any().downcast_ref::<IntegerBox>() {
let exit_code = int_result.value;
println!("✅ LLVM execution completed!");
println!("📊 Exit code: {}", exit_code);
process::exit(exit_code as i32);
} else {
println!("✅ LLVM execution completed (non-integer result)!");
println!("📊 Result: {}", result.to_string_box().value);
}
}
Err(e) => {
eprintln!("❌ LLVM execution error: {}", e);
process::exit(1);
}
}
}
#[cfg(all(not(feature = "llvm-inkwell-legacy")))]
{
println!("🔧 Mock LLVM Backend Execution:");
println!(" Build with --features llvm-inkwell-legacy for Rust/inkwell backend, or set NYASH_LLVM_OBJ_OUT and NYASH_LLVM_USE_HARNESS=1 for harness.");
// NamingBox SSOT: Select entry (arity-aware, Main.main → main fallback)
let entry =
crate::runner::modes::common_util::entry_selection::select_entry_function(&module);
if let Some(main_func) = module.functions.get(&entry) {
for (_bid, block) in &main_func.blocks {
for inst in &block.instructions {
match inst {
MirInstruction::Return { value: Some(_) } => {
println!("✅ Mock exit code: 42");
process::exit(42);
}
MirInstruction::Return { value: None } => {
println!("✅ Mock exit code: 0");
process::exit(0);
}
_ => {}
}
}
}
}
println!("✅ Mock exit code: 0");
process::exit(0);
}
}
}
// emit_mir_json_for_harness moved to crate::runner::mir_json_emit