2025-11-13 16:40:58 +09:00
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
|
|
impl MirInterpreter {
|
|
|
|
|
|
pub(super) fn execute_global_function(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
func_name: &str,
|
|
|
|
|
|
args: &[ValueId],
|
|
|
|
|
|
) -> Result<VMValue, VMError> {
|
2025-11-21 09:01:43 +09:00
|
|
|
|
// NamingBox: static box 名の正規化(main._nop/0 → Main._nop/0 など)
|
2025-11-22 01:21:38 +09:00
|
|
|
|
let mut canonical = crate::mir::naming::normalize_static_global_name(func_name);
|
|
|
|
|
|
|
|
|
|
|
|
// 🎯 Phase 21.7++: If function name doesn't have arity, add it from args.len()
|
|
|
|
|
|
// MIR functions are stored as "BoxName.method/arity" but calls may come without arity
|
|
|
|
|
|
if !canonical.contains('/') {
|
|
|
|
|
|
canonical = format!("{}/{}", canonical, args.len());
|
|
|
|
|
|
}
|
2025-11-21 09:01:43 +09:00
|
|
|
|
|
|
|
|
|
|
// Normalize arity suffix for extern-like dispatch, but keep canonical/original name
|
2025-11-13 16:40:58 +09:00
|
|
|
|
// for module-local function table lookup (functions may carry arity suffix).
|
2025-11-21 09:01:43 +09:00
|
|
|
|
let base = super::super::utils::normalize_arity_suffix(&canonical);
|
|
|
|
|
|
|
2025-11-22 01:21:38 +09:00
|
|
|
|
// 🔍 Debug: Check function lookup
|
|
|
|
|
|
if std::env::var("NYASH_DEBUG_FUNCTION_LOOKUP").ok().as_deref() == Some("1") {
|
|
|
|
|
|
eprintln!("[DEBUG/vm] Looking up function: '{}'", func_name);
|
|
|
|
|
|
eprintln!("[DEBUG/vm] canonical: '{}'", canonical);
|
|
|
|
|
|
eprintln!("[DEBUG/vm] base: '{}'", base);
|
|
|
|
|
|
eprintln!("[DEBUG/vm] Available functions: {}", self.functions.len());
|
|
|
|
|
|
if !self.functions.contains_key(&canonical) {
|
|
|
|
|
|
eprintln!("[DEBUG/vm] ❌ '{}' NOT found in functions", canonical);
|
|
|
|
|
|
// List functions starting with same prefix
|
|
|
|
|
|
let prefix = if let Some(idx) = canonical.find('.') {
|
|
|
|
|
|
&canonical[..idx]
|
|
|
|
|
|
} else {
|
|
|
|
|
|
&canonical
|
|
|
|
|
|
};
|
|
|
|
|
|
let matching: Vec<_> = self.functions.keys()
|
|
|
|
|
|
.filter(|k| k.starts_with(prefix))
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
if !matching.is_empty() {
|
|
|
|
|
|
eprintln!("[DEBUG/vm] Similar functions:");
|
|
|
|
|
|
for k in matching.iter().take(10) {
|
|
|
|
|
|
eprintln!("[DEBUG/vm] - {}", k);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
eprintln!("[DEBUG/vm] ✅ '{}' found", canonical);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 09:01:43 +09:00
|
|
|
|
// Module-local/global function: execute by function table if present.
|
|
|
|
|
|
// まず canonical 名で探す(Main._nop/0 など)。Phase 25.x 時点では
|
|
|
|
|
|
// レガシー名での再探索は廃止し、NamingBox 側の正規化に一本化する。
|
|
|
|
|
|
if let Some(func) = self.functions.get(&canonical).cloned() {
|
2025-11-13 16:40:58 +09:00
|
|
|
|
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
|
2025-11-21 06:25:17 +09:00
|
|
|
|
for a in args {
|
|
|
|
|
|
argv.push(self.reg_load(*a)?);
|
|
|
|
|
|
}
|
2025-11-13 16:40:58 +09:00
|
|
|
|
return self.exec_function_inner(&func, Some(&argv));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
match base {
|
|
|
|
|
|
// Console-like globals
|
|
|
|
|
|
"print" | "nyash.builtin.print" => {
|
|
|
|
|
|
// Reuse extern handler for consistency with other console names
|
|
|
|
|
|
return self.execute_extern_function("print", args);
|
|
|
|
|
|
}
|
|
|
|
|
|
"error" => {
|
|
|
|
|
|
if let Some(arg_id) = args.get(0) {
|
|
|
|
|
|
let val = self.reg_load(*arg_id)?;
|
|
|
|
|
|
eprintln!("Error: {}", val.to_string());
|
|
|
|
|
|
}
|
|
|
|
|
|
return Ok(VMValue::Void);
|
|
|
|
|
|
}
|
|
|
|
|
|
"panic" => {
|
|
|
|
|
|
return self.execute_extern_function("panic", args);
|
|
|
|
|
|
}
|
|
|
|
|
|
"exit" => {
|
|
|
|
|
|
return self.execute_extern_function("exit", args);
|
|
|
|
|
|
}
|
|
|
|
|
|
"env.get" => {
|
|
|
|
|
|
// Route env.get global to extern handler
|
|
|
|
|
|
return self.execute_extern_function("env.get", args);
|
|
|
|
|
|
}
|
|
|
|
|
|
"hostbridge.extern_invoke" => {
|
|
|
|
|
|
// SSOT: delegate to extern dispatcher (provider)
|
|
|
|
|
|
return self.execute_extern_function("hostbridge.extern_invoke", args);
|
|
|
|
|
|
}
|
|
|
|
|
|
// LLVM harness providers (direct)
|
|
|
|
|
|
"env.mirbuilder.emit" | "env.mirbuilder.emit/1" => {
|
|
|
|
|
|
if let Some(a0) = args.get(0) {
|
|
|
|
|
|
let s = self.reg_load(*a0)?.to_string();
|
|
|
|
|
|
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
|
|
|
|
|
|
Ok(out) => Ok(VMValue::String(out)),
|
|
|
|
|
|
Err(e) => Err(self.err_with_context("env.mirbuilder.emit", &e.to_string())),
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Err(self.err_invalid("env.mirbuilder.emit expects 1 arg"))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
"env.codegen.emit_object" | "env.codegen.emit_object/1" => {
|
|
|
|
|
|
if let Some(a0) = args.get(0) {
|
|
|
|
|
|
let s = self.reg_load(*a0)?.to_string();
|
|
|
|
|
|
let opts = crate::host_providers::llvm_codegen::Opts {
|
|
|
|
|
|
out: None,
|
2025-11-21 06:25:17 +09:00
|
|
|
|
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
|
|
|
|
|
|
.ok()
|
|
|
|
|
|
.map(std::path::PathBuf::from),
|
|
|
|
|
|
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL")
|
|
|
|
|
|
.ok()
|
|
|
|
|
|
.or(Some("0".to_string())),
|
2025-11-13 16:40:58 +09:00
|
|
|
|
timeout_ms: None,
|
|
|
|
|
|
};
|
|
|
|
|
|
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
|
|
|
|
|
|
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
|
2025-11-21 06:25:17 +09:00
|
|
|
|
Err(e) => {
|
|
|
|
|
|
Err(self.err_with_context("env.codegen.emit_object", &e.to_string()))
|
|
|
|
|
|
}
|
2025-11-13 16:40:58 +09:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Err(self.err_invalid("env.codegen.emit_object expects 1 arg"))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
"env.codegen.link_object" | "env.codegen.link_object/3" => {
|
|
|
|
|
|
// C-API route only; args[2] is expected to be an ArrayBox [obj_path, exe_out?]
|
2025-11-21 06:25:17 +09:00
|
|
|
|
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|
|
|
|
|
|
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
|
|
|
|
|
|
.ok()
|
|
|
|
|
|
.as_deref()
|
|
|
|
|
|
!= Some("1")
|
|
|
|
|
|
{
|
2025-11-13 16:40:58 +09:00
|
|
|
|
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
|
|
|
|
|
|
}
|
2025-11-21 06:25:17 +09:00
|
|
|
|
if args.len() < 3 {
|
|
|
|
|
|
return Err(self.err_arg_count("env.codegen.link_object", 3, args.len()));
|
|
|
|
|
|
}
|
2025-11-13 16:40:58 +09:00
|
|
|
|
let v = self.reg_load(args[2])?;
|
|
|
|
|
|
let (obj_path, exe_out) = match v {
|
|
|
|
|
|
VMValue::BoxRef(b) => {
|
2025-11-21 06:25:17 +09:00
|
|
|
|
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
|
|
|
|
|
|
{
|
|
|
|
|
|
let idx0: Box<dyn crate::box_trait::NyashBox> =
|
|
|
|
|
|
Box::new(crate::box_trait::IntegerBox::new(0));
|
2025-11-13 16:40:58 +09:00
|
|
|
|
let elem0 = ab.get(idx0).to_string_box().value;
|
|
|
|
|
|
let mut exe: Option<String> = None;
|
2025-11-21 06:25:17 +09:00
|
|
|
|
let idx1: Box<dyn crate::box_trait::NyashBox> =
|
|
|
|
|
|
Box::new(crate::box_trait::IntegerBox::new(1));
|
2025-11-13 16:40:58 +09:00
|
|
|
|
let e1 = ab.get(idx1).to_string_box().value;
|
2025-11-21 06:25:17 +09:00
|
|
|
|
if !e1.is_empty() {
|
|
|
|
|
|
exe = Some(e1);
|
|
|
|
|
|
}
|
2025-11-13 16:40:58 +09:00
|
|
|
|
(elem0, exe)
|
2025-11-21 06:25:17 +09:00
|
|
|
|
} else {
|
|
|
|
|
|
(b.to_string_box().value, None)
|
|
|
|
|
|
}
|
2025-11-13 16:40:58 +09:00
|
|
|
|
}
|
|
|
|
|
|
_ => (v.to_string(), None),
|
|
|
|
|
|
};
|
|
|
|
|
|
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
|
|
|
|
|
let obj = std::path::PathBuf::from(obj_path);
|
2025-11-21 06:25:17 +09:00
|
|
|
|
let exe = exe_out
|
|
|
|
|
|
.map(std::path::PathBuf::from)
|
|
|
|
|
|
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
|
|
|
|
|
match crate::host_providers::llvm_codegen::link_object_capi(
|
|
|
|
|
|
&obj,
|
|
|
|
|
|
&exe,
|
|
|
|
|
|
extra.as_deref(),
|
|
|
|
|
|
) {
|
2025-11-13 16:40:58 +09:00
|
|
|
|
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
2025-11-21 06:25:17 +09:00
|
|
|
|
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())),
|
2025-11-13 16:40:58 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
"nyash.builtin.error" => {
|
|
|
|
|
|
if let Some(arg_id) = args.get(0) {
|
|
|
|
|
|
let val = self.reg_load(*arg_id)?;
|
|
|
|
|
|
eprintln!("Error: {}", val.to_string());
|
|
|
|
|
|
}
|
|
|
|
|
|
Ok(VMValue::Void)
|
|
|
|
|
|
}
|
2025-11-21 09:16:27 +09:00
|
|
|
|
_ => {
|
2025-11-22 02:13:10 +09:00
|
|
|
|
// ⚠️ Phase 0.2: User-friendly "Did you mean?" suggestions
|
|
|
|
|
|
let prefix = if let Some(idx) = canonical.find('.') {
|
|
|
|
|
|
&canonical[..idx]
|
|
|
|
|
|
} else {
|
|
|
|
|
|
&canonical
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let similar: Vec<_> = self.functions.keys()
|
|
|
|
|
|
.filter(|k| k.starts_with(prefix))
|
|
|
|
|
|
.take(5)
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
|
|
let mut err_msg = format!("Function not found: {}", func_name);
|
|
|
|
|
|
|
|
|
|
|
|
if !similar.is_empty() {
|
|
|
|
|
|
err_msg.push_str("\n\n💡 Did you mean:");
|
|
|
|
|
|
for s in similar {
|
|
|
|
|
|
err_msg.push_str(&format!("\n - {}", s));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err_msg.push_str("\n\n🔍 Debug: NYASH_DEBUG_FUNCTION_LOOKUP=1 for full lookup trace");
|
|
|
|
|
|
|
2025-11-21 09:16:27 +09:00
|
|
|
|
// NamingBox SSOT: ここで canonical に失敗したら素直に Unknown とする。
|
|
|
|
|
|
// レガシーフォールバック(functions.get(func_name) 再探索)は Phase 25.x で廃止済み。
|
2025-11-22 02:13:10 +09:00
|
|
|
|
Err(self.err_with_context("global function", &err_msg))
|
2025-11-21 09:16:27 +09:00
|
|
|
|
}
|
2025-11-13 16:40:58 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|