2025-08-14 13:16:12 +00:00
|
|
|
/*!
|
|
|
|
|
* Execution Runner Module - Nyash File and Mode Execution Coordinator
|
2025-09-17 07:43:07 +09:00
|
|
|
*
|
2025-08-14 13:16:12 +00:00
|
|
|
* This module handles all execution logic, backend selection, and mode coordination,
|
|
|
|
|
* separated from CLI parsing and the main entry point.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-08-19 05:36:01 +09:00
|
|
|
use nyash_rust::cli::CliConfig;
|
2025-09-16 06:13:44 +09:00
|
|
|
// prune heavy unused imports here; modules import what they need locally
|
|
|
|
|
// pruned unused runtime imports in this module
|
2025-08-18 09:14:39 +00:00
|
|
|
|
2025-08-19 16:48:45 +09:00
|
|
|
#[cfg(feature = "wasm-backend")]
|
2025-09-17 07:43:07 +09:00
|
|
|
use nyash_rust::backend::{aot::AotBackend, wasm::WasmBackend};
|
2025-08-19 16:48:45 +09:00
|
|
|
|
2025-09-14 00:44:28 +09:00
|
|
|
#[cfg(feature = "llvm-inkwell-legacy")]
|
2025-09-17 07:43:07 +09:00
|
|
|
use nyash_rust::backend::llvm_compile_and_execute;
|
2025-08-14 13:16:12 +00:00
|
|
|
use std::{fs, process};
|
2025-09-17 07:43:07 +09:00
|
|
|
mod box_index;
|
|
|
|
|
mod build;
|
|
|
|
|
mod cli_directives;
|
2025-09-03 01:37:38 +09:00
|
|
|
mod demos;
|
2025-09-17 07:43:07 +09:00
|
|
|
mod dispatch;
|
2025-09-05 13:29:17 +09:00
|
|
|
mod json_v0_bridge;
|
2025-09-14 04:51:33 +09:00
|
|
|
mod mir_json_emit;
|
2025-09-22 07:54:25 +09:00
|
|
|
pub mod modes;
|
2025-09-16 00:01:31 +09:00
|
|
|
mod pipe_io;
|
|
|
|
|
mod pipeline;
|
2025-09-22 07:54:25 +09:00
|
|
|
mod jit_direct;
|
2025-09-16 00:01:31 +09:00
|
|
|
mod selfhost;
|
2025-09-17 07:43:07 +09:00
|
|
|
mod tasks;
|
|
|
|
|
mod trace;
|
2025-09-22 21:52:39 +09:00
|
|
|
mod plugins;
|
2025-08-14 13:16:12 +00:00
|
|
|
|
2025-08-19 05:16:40 +09:00
|
|
|
// v2 plugin system imports
|
2025-08-26 04:34:14 +09:00
|
|
|
use nyash_rust::runner_plugin_init;
|
2025-09-17 07:43:07 +09:00
|
|
|
use nyash_rust::runtime;
|
2025-09-16 03:54:44 +09:00
|
|
|
// use std::path::PathBuf; // not used in current runner
|
2025-08-18 11:07:03 +09:00
|
|
|
|
2025-09-08 04:35:50 +09:00
|
|
|
/// Resolve a using target according to priority: modules > relative > using-paths
|
|
|
|
|
/// Returns Ok(resolved_path_or_token). On strict mode, ambiguous matches cause error.
|
2025-09-22 21:52:39 +09:00
|
|
|
// use pipeline::resolve_using_target; // resolved within helpers; avoid unused warning
|
2025-09-08 04:35:50 +09:00
|
|
|
|
2025-08-14 13:16:12 +00:00
|
|
|
/// Main execution coordinator
|
|
|
|
|
pub struct NyashRunner {
|
|
|
|
|
config: CliConfig,
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 23:44:34 +09:00
|
|
|
/// Minimal task runner: read nyash.toml [env] and [tasks], run the named task via shell
|
2025-09-16 01:54:00 +09:00
|
|
|
use tasks::run_named_task;
|
2025-09-01 23:44:34 +09:00
|
|
|
|
2025-09-22 21:52:39 +09:00
|
|
|
#[cfg(not(feature = "jit-direct-only"))]
|
2025-08-14 13:16:12 +00:00
|
|
|
impl NyashRunner {
|
|
|
|
|
/// Create a new runner with the given configuration
|
|
|
|
|
pub fn new(config: CliConfig) -> Self {
|
|
|
|
|
Self { config }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Run Nyash based on the configuration
|
|
|
|
|
pub fn run(&self) {
|
2025-09-22 21:52:39 +09:00
|
|
|
// New behavior-preserving delegator
|
|
|
|
|
self.run_refactored();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// init_bid_plugins moved to runner_plugin_init.rs
|
|
|
|
|
|
|
|
|
|
/// Execute file-based mode with backend selection
|
|
|
|
|
pub(crate) fn run_file(&self, filename: &str) {
|
|
|
|
|
dispatch::execute_file_with_backend(self, filename);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Minimal AOT build pipeline driven by nyash.toml (mvp)
|
|
|
|
|
fn run_build_mvp(&self, cfg_path: &str) -> Result<(), String> {
|
|
|
|
|
build::run_build_mvp_impl(self, cfg_path)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(not(feature = "jit-direct-only"))]
|
|
|
|
|
impl NyashRunner {
|
|
|
|
|
/// New behavior-preserving refactor of run(): structured into smaller helpers
|
|
|
|
|
fn run_refactored(&self) {
|
|
|
|
|
// Early: macro child
|
2025-09-19 22:27:59 +09:00
|
|
|
if let Some(ref macro_file) = self.config.macro_expand_child {
|
|
|
|
|
crate::runner::modes::macro_child::run_macro_child(macro_file);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-09-19 15:11:57 +09:00
|
|
|
let groups = self.config.as_groups();
|
2025-09-22 21:52:39 +09:00
|
|
|
// Early: build
|
2025-09-19 15:11:57 +09:00
|
|
|
if let Some(cfg_path) = groups.build.path.clone() {
|
2025-09-06 16:53:12 +09:00
|
|
|
if let Err(e) = self.run_build_mvp(&cfg_path) {
|
|
|
|
|
eprintln!("❌ build error: {}", e);
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
// Preprocess usings and directives (includes dep-tree log)
|
|
|
|
|
self.preprocess_usings_and_directives(&groups);
|
|
|
|
|
// JSON v0 bridge
|
|
|
|
|
if self.try_run_json_v0_pipe() { return; }
|
|
|
|
|
// Named task
|
|
|
|
|
if let Some(task) = groups.run_task.clone() {
|
|
|
|
|
if let Err(e) = run_named_task(&task) {
|
|
|
|
|
eprintln!("❌ Task error: {}", e);
|
|
|
|
|
process::exit(1);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Common env + runtime/plugins
|
|
|
|
|
self.apply_common_env(&groups);
|
|
|
|
|
self.init_runtime_and_plugins(&groups);
|
|
|
|
|
// Backend config + policy
|
|
|
|
|
self.configure_backend(&groups);
|
|
|
|
|
self.enforce_runtime_jit_policy(&groups);
|
|
|
|
|
// Benchmark
|
|
|
|
|
if self.maybe_run_benchmark(&groups) { return; }
|
|
|
|
|
// Dispatch
|
|
|
|
|
self.dispatch_entry(&groups);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- Helpers (extracted from original run) ----
|
|
|
|
|
|
|
|
|
|
fn preprocess_usings_and_directives(&self, groups: &crate::cli::CliGroups) {
|
|
|
|
|
use pipeline::resolve_using_target;
|
|
|
|
|
// Initialize UsingContext (defaults + nyash.toml + env)
|
2025-09-16 00:01:31 +09:00
|
|
|
let mut using_ctx = self.init_using_context();
|
2025-09-22 21:52:39 +09:00
|
|
|
// Collect CLI --using SPEC into (target, alias)
|
2025-09-16 14:57:05 +09:00
|
|
|
let mut pending_using: Vec<(String, Option<String>)> = Vec::new();
|
2025-09-19 15:11:57 +09:00
|
|
|
for spec in &groups.input.cli_usings {
|
2025-09-16 14:57:05 +09:00
|
|
|
let s = spec.trim();
|
2025-09-22 21:52:39 +09:00
|
|
|
if s.is_empty() { continue; }
|
2025-09-16 14:57:05 +09:00
|
|
|
let (target, alias) = if let Some(pos) = s.find(" as ") {
|
2025-09-22 21:52:39 +09:00
|
|
|
(s[..pos].trim().to_string(), Some(s[pos + 4..].trim().to_string()))
|
|
|
|
|
} else { (s.to_string(), None) };
|
|
|
|
|
let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash");
|
2025-09-16 14:57:05 +09:00
|
|
|
if is_path {
|
|
|
|
|
let path = target.trim_matches('"').to_string();
|
|
|
|
|
let name = alias.clone().unwrap_or_else(|| {
|
2025-09-22 21:52:39 +09:00
|
|
|
std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string()
|
2025-09-16 14:57:05 +09:00
|
|
|
});
|
|
|
|
|
pending_using.push((name, Some(path)));
|
|
|
|
|
} else {
|
|
|
|
|
pending_using.push((target, alias));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
// Apply pending modules (from context) to registry as StringBox
|
2025-09-16 00:01:31 +09:00
|
|
|
for (ns, path) in using_ctx.pending_modules.iter() {
|
2025-09-08 11:20:13 +09:00
|
|
|
let sb = crate::box_trait::StringBox::new(path.clone());
|
|
|
|
|
crate::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
// Optional dependency tree bridge (log-only)
|
2025-09-08 04:35:50 +09:00
|
|
|
if let Ok(dep_path) = std::env::var("NYASH_DEPS_JSON") {
|
|
|
|
|
match std::fs::read_to_string(&dep_path) {
|
|
|
|
|
Ok(s) => {
|
|
|
|
|
let bytes = s.as_bytes().len();
|
|
|
|
|
let mut root_info = String::new();
|
|
|
|
|
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&s) {
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some(r) = v.get("root_path").and_then(|x| x.as_str()) {
|
|
|
|
|
root_info = format!(" root='{}'", r);
|
|
|
|
|
}
|
2025-09-08 04:35:50 +09:00
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
crate::cli_v!("[deps] loaded {} bytes from{} {}", bytes, if root_info.is_empty() { "" } else { ":" }, root_info);
|
2025-09-08 04:35:50 +09:00
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
Err(e) => { crate::cli_v!("[deps] read error: {}", e); }
|
2025-09-17 20:33:19 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
// If a file is provided, apply script-level directives and late using/env merges
|
2025-09-19 15:11:57 +09:00
|
|
|
if let Some(ref filename) = groups.input.file {
|
2025-08-29 21:39:47 +09:00
|
|
|
if let Ok(code) = fs::read_to_string(filename) {
|
2025-09-22 21:52:39 +09:00
|
|
|
// Apply directives and lint
|
|
|
|
|
let strict_fields = std::env::var("NYASH_FIELDS_TOP_STRICT").ok().as_deref() == Some("1");
|
|
|
|
|
if let Err(e) = cli_directives::apply_cli_directives_from_source(&code, strict_fields, groups.debug.cli_verbose) {
|
2025-09-17 05:56:33 +09:00
|
|
|
eprintln!("❌ Lint/Directive error: {}", e);
|
2025-09-16 14:57:05 +09:00
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
// Late env overrides (paths/modules)
|
2025-09-08 04:35:50 +09:00
|
|
|
if let Ok(paths) = std::env::var("NYASH_USING_PATH") {
|
2025-09-22 21:52:39 +09:00
|
|
|
for p in paths.split(':') { let p = p.trim(); if !p.is_empty() { using_ctx.using_paths.push(p.to_string()); } }
|
2025-09-08 04:35:50 +09:00
|
|
|
}
|
|
|
|
|
if let Ok(mods) = std::env::var("NYASH_MODULES") {
|
|
|
|
|
for ent in mods.split(',') {
|
2025-09-17 07:43:07 +09:00
|
|
|
if let Some((k, v)) = ent.split_once('=') {
|
2025-09-22 21:52:39 +09:00
|
|
|
let k = k.trim(); let v = v.trim();
|
|
|
|
|
if !k.is_empty() && !v.is_empty() { using_ctx.pending_modules.push((k.to_string(), v.to_string())); }
|
2025-09-08 04:35:50 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
// Re-apply pending modules in case env added more (idempotent)
|
2025-09-16 00:01:31 +09:00
|
|
|
for (ns, path) in using_ctx.pending_modules.iter() {
|
2025-09-08 04:35:50 +09:00
|
|
|
let sb = nyash_rust::box_trait::StringBox::new(path.clone());
|
|
|
|
|
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
// Resolve CLI --using entries against context and register values (with aliasing)
|
2025-09-08 04:35:50 +09:00
|
|
|
let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1");
|
2025-09-19 02:07:38 +09:00
|
|
|
let verbose = crate::config::env::cli_verbose();
|
2025-09-08 04:35:50 +09:00
|
|
|
let ctx = std::path::Path::new(filename).parent();
|
|
|
|
|
for (ns, alias) in pending_using.iter() {
|
2025-09-24 21:45:27 +09:00
|
|
|
let value = match resolve_using_target(ns, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, &using_ctx.packages, ctx, strict, verbose) {
|
2025-09-08 04:35:50 +09:00
|
|
|
Ok(v) => v,
|
2025-09-22 21:52:39 +09:00
|
|
|
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
2025-09-08 04:35:50 +09:00
|
|
|
};
|
|
|
|
|
let sb = nyash_rust::box_trait::StringBox::new(value.clone());
|
|
|
|
|
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
|
|
|
|
if let Some(a) = alias {
|
|
|
|
|
let sb2 = nyash_rust::box_trait::StringBox::new(value);
|
|
|
|
|
nyash_rust::runtime::modules_registry::set(a.clone(), Box::new(sb2));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-29 21:39:47 +09:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
}
|
2025-08-29 21:39:47 +09:00
|
|
|
|
2025-09-22 21:52:39 +09:00
|
|
|
/// Apply early environment toggles that affect CLI behavior and VM stats.
|
|
|
|
|
/// Side effects: sets `NYASH_CLI_VERBOSE`, `NYASH_GC_MODE` when specified by CLI groups.
|
|
|
|
|
fn apply_common_env(&self, groups: &crate::cli::CliGroups) {
|
|
|
|
|
if groups.debug.cli_verbose { std::env::set_var("NYASH_CLI_VERBOSE", "1"); }
|
|
|
|
|
if let Some(ref m) = groups.gc_mode { if !m.trim().is_empty() { std::env::set_var("NYASH_GC_MODE", m); } }
|
|
|
|
|
}
|
2025-09-05 13:29:17 +09:00
|
|
|
|
2025-09-22 21:52:39 +09:00
|
|
|
// init_runtime_and_plugins moved to runner/plugins.rs
|
2025-08-23 03:40:17 +09:00
|
|
|
|
2025-09-22 21:52:39 +09:00
|
|
|
/// Configure backend knobs (VM/JIT) from CLI flags and env, merging with runtime capabilities.
|
|
|
|
|
/// Side effects: writes environment variables for flags and applies JIT config via nyash_rust::jit::config.
|
|
|
|
|
fn configure_backend(&self, groups: &crate::cli::CliGroups) {
|
|
|
|
|
if groups.backend.vm_stats { std::env::set_var("NYASH_VM_STATS", "1"); }
|
|
|
|
|
if groups.backend.vm_stats_json { std::env::set_var("NYASH_VM_STATS_JSON", "1"); }
|
2025-08-28 09:26:58 +09:00
|
|
|
{
|
2025-09-22 21:52:39 +09:00
|
|
|
if groups.backend.jit.events { std::env::set_var("NYASH_JIT_EVENTS", "1"); }
|
|
|
|
|
if groups.backend.jit.events_compile { std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1"); }
|
|
|
|
|
if groups.backend.jit.events_runtime { std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1"); }
|
|
|
|
|
if let Some(ref p) = groups.backend.jit.events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); }
|
2025-08-28 09:26:58 +09:00
|
|
|
let mut jc = nyash_rust::jit::config::JitConfig::from_env();
|
2025-09-19 14:29:02 +09:00
|
|
|
jc.exec |= groups.backend.jit.exec;
|
|
|
|
|
jc.stats |= groups.backend.jit.stats;
|
|
|
|
|
jc.stats_json |= groups.backend.jit.stats_json;
|
|
|
|
|
jc.dump |= groups.backend.jit.dump;
|
2025-09-22 21:52:39 +09:00
|
|
|
if groups.backend.jit.threshold.is_some() { jc.threshold = groups.backend.jit.threshold; }
|
2025-09-19 14:29:02 +09:00
|
|
|
jc.phi_min |= groups.backend.jit.phi_min;
|
|
|
|
|
jc.hostcall |= groups.backend.jit.hostcall;
|
|
|
|
|
jc.handle_debug |= groups.backend.jit.handle_debug;
|
|
|
|
|
jc.native_f64 |= groups.backend.jit.native_f64;
|
|
|
|
|
jc.native_bool |= groups.backend.jit.native_bool;
|
2025-08-29 02:05:39 +09:00
|
|
|
let events_on = std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|
|
|
|
|
|| std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1")
|
|
|
|
|
|| std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1");
|
2025-09-22 21:52:39 +09:00
|
|
|
if events_on && jc.threshold.is_none() { jc.threshold = Some(1); }
|
|
|
|
|
if groups.backend.jit.only { std::env::set_var("NYASH_JIT_ONLY", "1"); }
|
2025-08-28 09:26:58 +09:00
|
|
|
let caps = nyash_rust::jit::config::probe_capabilities();
|
|
|
|
|
jc = nyash_rust::jit::config::apply_runtime_caps(jc, caps);
|
2025-09-22 21:52:39 +09:00
|
|
|
if let Some(path) = &groups.emit.emit_cfg { std::env::set_var("NYASH_JIT_DOT", path); jc.dump = true; }
|
2025-08-28 09:26:58 +09:00
|
|
|
jc.apply_env();
|
|
|
|
|
nyash_rust::jit::config::set_current(jc.clone());
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
if std::env::var("NYASH_JIT_STRICT").ok().as_deref() == Some("1") {
|
|
|
|
|
if std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().is_none() { std::env::set_var("NYASH_JIT_ARGS_HANDLE_ONLY", "1"); }
|
|
|
|
|
if std::env::var("NYASH_JIT_ONLY").ok().is_none() { std::env::set_var("NYASH_JIT_ONLY", "1"); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Enforce runtime policy for JIT execution when AOT object output is absent.
|
|
|
|
|
/// Side effects: may set `NYASH_JIT_EXEC=0` when policy requires.
|
|
|
|
|
fn enforce_runtime_jit_policy(&self, groups: &crate::cli::CliGroups) {
|
2025-09-19 15:11:57 +09:00
|
|
|
if !groups.compile_native && !groups.backend.jit.direct {
|
2025-08-29 23:11:21 +09:00
|
|
|
let aot_obj = std::env::var("NYASH_AOT_OBJECT_OUT").ok();
|
|
|
|
|
if aot_obj.is_none() || aot_obj.as_deref() == Some("") {
|
|
|
|
|
std::env::set_var("NYASH_JIT_EXEC", "0");
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Optionally run the benchmark suite and exit, depending on CLI flags.
|
|
|
|
|
/// Returns true when a benchmark run occurred.
|
|
|
|
|
fn maybe_run_benchmark(&self, groups: &crate::cli::CliGroups) -> bool {
|
2025-09-19 15:11:57 +09:00
|
|
|
if groups.benchmark {
|
2025-08-14 13:16:12 +00:00
|
|
|
println!("📊 Nyash Performance Benchmark Suite");
|
|
|
|
|
println!("====================================");
|
2025-09-19 15:11:57 +09:00
|
|
|
println!("Running {} iterations per test...", groups.iterations);
|
2025-08-14 13:16:12 +00:00
|
|
|
println!();
|
2025-09-17 16:11:01 +09:00
|
|
|
#[cfg(feature = "vm-legacy")]
|
2025-09-22 21:52:39 +09:00
|
|
|
{ self.execute_benchmark_mode(); return true; }
|
2025-09-17 16:11:01 +09:00
|
|
|
#[cfg(not(feature = "vm-legacy"))]
|
2025-09-22 21:52:39 +09:00
|
|
|
{ eprintln!("❌ Benchmark mode requires VM backend. Rebuild with --features vm-legacy."); std::process::exit(1); }
|
2025-08-14 13:16:12 +00:00
|
|
|
}
|
2025-09-22 21:52:39 +09:00
|
|
|
false
|
|
|
|
|
}
|
2025-08-14 13:16:12 +00:00
|
|
|
|
2025-09-22 21:52:39 +09:00
|
|
|
/// Final dispatch to selected execution mode (file/JIT-direct or demos).
|
|
|
|
|
fn dispatch_entry(&self, groups: &crate::cli::CliGroups) {
|
2025-09-19 15:11:57 +09:00
|
|
|
if let Some(ref filename) = groups.input.file {
|
2025-09-22 21:52:39 +09:00
|
|
|
if groups.backend.jit.direct { self.run_file_jit_direct(filename); return; }
|
2025-08-26 04:42:52 +09:00
|
|
|
self.run_file(filename);
|
2025-09-22 21:52:39 +09:00
|
|
|
} else { demos::run_all_demos(); }
|
2025-09-06 16:53:12 +09:00
|
|
|
}
|
2025-08-14 13:16:12 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-28 09:26:58 +09:00
|
|
|
impl NyashRunner {
|
|
|
|
|
/// Run a file through independent JIT engine (no VM execute loop)
|
2025-09-23 02:26:33 +09:00
|
|
|
/// ARCHIVED: JIT/Cranelift functionality disabled for Phase 15
|
2025-08-28 09:26:58 +09:00
|
|
|
fn run_file_jit_direct(&self, filename: &str) {
|
2025-09-23 02:26:33 +09:00
|
|
|
eprintln!("❌ JIT-direct mode is archived for Phase 15. JIT/Cranelift moved to archive/jit-cranelift/");
|
|
|
|
|
eprintln!(" Use VM backend instead: nyash {}", filename);
|
|
|
|
|
eprintln!(" Or use LLVM backend: nyash --backend llvm {}", filename);
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
|
|
|
|
|
// Original JIT implementation archived - commented out for Phase 15
|
|
|
|
|
/*
|
2025-09-17 07:43:07 +09:00
|
|
|
use nyash_rust::{mir::MirCompiler, parser::NyashParser};
|
2025-08-28 09:26:58 +09:00
|
|
|
use std::fs;
|
|
|
|
|
// Small helper for unified error output (text or JSON)
|
|
|
|
|
let emit_err = |phase: &str, code: &str, msg: &str| {
|
|
|
|
|
if std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1")
|
2025-09-17 07:43:07 +09:00
|
|
|
|| std::env::var("NYASH_JIT_ERROR_JSON").ok().as_deref() == Some("1")
|
|
|
|
|
{
|
2025-08-28 09:26:58 +09:00
|
|
|
let payload = serde_json::json!({
|
|
|
|
|
"kind": "jit_direct_error",
|
|
|
|
|
"phase": phase,
|
|
|
|
|
"code": code,
|
|
|
|
|
"message": msg,
|
|
|
|
|
"file": filename,
|
|
|
|
|
});
|
|
|
|
|
println!("{}", payload.to_string());
|
|
|
|
|
} else {
|
|
|
|
|
eprintln!("[JIT-direct][{}][{}] {}", phase, code, msg);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// Require cranelift feature at runtime by attempting compile; if unavailable compile_function returns None
|
|
|
|
|
let code = match fs::read_to_string(filename) {
|
|
|
|
|
Ok(s) => s,
|
2025-09-17 07:43:07 +09:00
|
|
|
Err(e) => {
|
|
|
|
|
emit_err("read_file", "IO", &format!("{}", e));
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
};
|
|
|
|
|
let ast = match NyashParser::parse_from_string(&code) {
|
2025-09-17 07:43:07 +09:00
|
|
|
Ok(a) => a,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
emit_err("parse", "SYNTAX", &format!("{}", e));
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
};
|
|
|
|
|
let mut mc = MirCompiler::new();
|
2025-09-17 07:43:07 +09:00
|
|
|
let cr = match mc.compile(ast) {
|
|
|
|
|
Ok(m) => m,
|
|
|
|
|
Err(e) => {
|
|
|
|
|
emit_err("mir", "MIR_COMPILE", &format!("{}", e));
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let func = match cr.module.functions.get("main") {
|
|
|
|
|
Some(f) => f,
|
|
|
|
|
None => {
|
|
|
|
|
emit_err("mir", "NO_MAIN", "No main function found");
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-08-28 09:26:58 +09:00
|
|
|
|
|
|
|
|
// Guard: refuse write-effects in jit-direct when policy.read_only
|
|
|
|
|
{
|
|
|
|
|
use nyash_rust::mir::effect::Effect;
|
|
|
|
|
let policy = nyash_rust::jit::policy::current();
|
|
|
|
|
let mut writes = 0usize;
|
|
|
|
|
for (_bbid, bb) in func.blocks.iter() {
|
|
|
|
|
for inst in bb.instructions.iter() {
|
|
|
|
|
let mask = inst.effects();
|
|
|
|
|
if mask.contains(Effect::WriteHeap) {
|
|
|
|
|
writes += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(term) = &bb.terminator {
|
2025-09-17 07:43:07 +09:00
|
|
|
if term.effects().contains(Effect::WriteHeap) {
|
|
|
|
|
writes += 1;
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if policy.read_only && writes > 0 {
|
2025-09-17 07:43:07 +09:00
|
|
|
emit_err(
|
|
|
|
|
"policy",
|
|
|
|
|
"WRITE_EFFECTS",
|
|
|
|
|
&format!(
|
|
|
|
|
"write-effects detected ({} ops). jit-direct is read-only at this stage.",
|
|
|
|
|
writes
|
|
|
|
|
),
|
|
|
|
|
);
|
2025-08-28 09:26:58 +09:00
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-01 23:44:34 +09:00
|
|
|
// jit-direct 安定化: 分岐合流(PHI)は明示ブロック引数で配線
|
|
|
|
|
{
|
|
|
|
|
let mut cfg = nyash_rust::jit::config::current();
|
|
|
|
|
cfg.phi_min = true; // enable multi-PHI arg passing/join
|
|
|
|
|
nyash_rust::jit::config::set_current(cfg);
|
|
|
|
|
}
|
|
|
|
|
// Prepare minimal runtime hooks so JIT externs (checkpoint/await) can reach GC/scheduler
|
|
|
|
|
{
|
|
|
|
|
let rt = nyash_rust::runtime::NyashRuntime::new();
|
|
|
|
|
nyash_rust::runtime::global_hooks::set_from_runtime(&rt);
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
let mut engine = nyash_rust::jit::engine::JitEngine::new();
|
|
|
|
|
match engine.compile_function("main", func) {
|
|
|
|
|
Some(h) => {
|
|
|
|
|
// Optional event: compile
|
2025-09-17 07:43:07 +09:00
|
|
|
nyash_rust::jit::events::emit(
|
|
|
|
|
"compile",
|
|
|
|
|
&func.signature.name,
|
|
|
|
|
Some(h),
|
|
|
|
|
None,
|
|
|
|
|
serde_json::json!({}),
|
|
|
|
|
);
|
2025-08-28 09:26:58 +09:00
|
|
|
// Parse JIT args from env: NYASH_JIT_ARGS (comma-separated), with optional type prefixes
|
|
|
|
|
// Formats per arg: i:123, f:3.14, b:true/false, h:42 (handle), or bare numbers (int), true/false (bool)
|
|
|
|
|
let mut jit_args: Vec<nyash_rust::jit::abi::JitValue> = Vec::new();
|
|
|
|
|
if let Ok(s) = std::env::var("NYASH_JIT_ARGS") {
|
|
|
|
|
for raw in s.split(',') {
|
|
|
|
|
let t = raw.trim();
|
2025-09-17 07:43:07 +09:00
|
|
|
if t.is_empty() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
let v = if let Some(rest) = t.strip_prefix("i:") {
|
2025-09-17 07:43:07 +09:00
|
|
|
rest.parse::<i64>()
|
|
|
|
|
.ok()
|
|
|
|
|
.map(nyash_rust::jit::abi::JitValue::I64)
|
2025-08-28 09:26:58 +09:00
|
|
|
} else if let Some(rest) = t.strip_prefix("f:") {
|
2025-09-17 07:43:07 +09:00
|
|
|
rest.parse::<f64>()
|
|
|
|
|
.ok()
|
|
|
|
|
.map(nyash_rust::jit::abi::JitValue::F64)
|
2025-08-28 09:26:58 +09:00
|
|
|
} else if let Some(rest) = t.strip_prefix("b:") {
|
2025-09-17 07:43:07 +09:00
|
|
|
let b = matches!(rest, "1" | "true" | "True" | "TRUE");
|
2025-08-28 09:26:58 +09:00
|
|
|
Some(nyash_rust::jit::abi::JitValue::Bool(b))
|
|
|
|
|
} else if let Some(rest) = t.strip_prefix("h:") {
|
2025-09-17 07:43:07 +09:00
|
|
|
rest.parse::<u64>()
|
|
|
|
|
.ok()
|
|
|
|
|
.map(nyash_rust::jit::abi::JitValue::Handle)
|
|
|
|
|
} else if t.eq_ignore_ascii_case("true") || t == "1" {
|
|
|
|
|
Some(nyash_rust::jit::abi::JitValue::Bool(true))
|
|
|
|
|
} else if t.eq_ignore_ascii_case("false") || t == "0" {
|
|
|
|
|
Some(nyash_rust::jit::abi::JitValue::Bool(false))
|
|
|
|
|
} else if let Ok(iv) = t.parse::<i64>() {
|
|
|
|
|
Some(nyash_rust::jit::abi::JitValue::I64(iv))
|
|
|
|
|
} else if let Ok(fv) = t.parse::<f64>() {
|
|
|
|
|
Some(nyash_rust::jit::abi::JitValue::F64(fv))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
if let Some(jv) = v {
|
|
|
|
|
jit_args.push(jv);
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Coerce args to expected MIR types
|
|
|
|
|
use nyash_rust::mir::MirType;
|
|
|
|
|
let expected = &func.signature.params;
|
|
|
|
|
if expected.len() != jit_args.len() {
|
2025-09-17 07:43:07 +09:00
|
|
|
emit_err(
|
|
|
|
|
"args",
|
|
|
|
|
"COUNT_MISMATCH",
|
|
|
|
|
&format!("expected={}, passed={}", expected.len(), jit_args.len()),
|
|
|
|
|
);
|
2025-08-28 09:26:58 +09:00
|
|
|
eprintln!("Hint: set NYASH_JIT_ARGS as comma-separated values, e.g., i:42,f:3.14,b:true");
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
2025-09-17 07:43:07 +09:00
|
|
|
let mut coerced: Vec<nyash_rust::jit::abi::JitValue> =
|
|
|
|
|
Vec::with_capacity(jit_args.len());
|
2025-08-28 09:26:58 +09:00
|
|
|
for (i, (exp, got)) in expected.iter().zip(jit_args.iter()).enumerate() {
|
|
|
|
|
let cv = match exp {
|
|
|
|
|
MirType::Integer => match got {
|
2025-09-17 07:43:07 +09:00
|
|
|
nyash_rust::jit::abi::JitValue::I64(v) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::I64(*v)
|
|
|
|
|
}
|
|
|
|
|
nyash_rust::jit::abi::JitValue::F64(f) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::I64(*f as i64)
|
|
|
|
|
}
|
|
|
|
|
nyash_rust::jit::abi::JitValue::Bool(b) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::I64(if *b { 1 } else { 0 })
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
emit_err(
|
|
|
|
|
"args",
|
|
|
|
|
"TYPE_MISMATCH",
|
|
|
|
|
&format!("param#{} expects Integer", i),
|
|
|
|
|
);
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
},
|
|
|
|
|
MirType::Float => match got {
|
2025-09-17 07:43:07 +09:00
|
|
|
nyash_rust::jit::abi::JitValue::F64(f) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::F64(*f)
|
|
|
|
|
}
|
|
|
|
|
nyash_rust::jit::abi::JitValue::I64(v) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::F64(*v as f64)
|
|
|
|
|
}
|
|
|
|
|
nyash_rust::jit::abi::JitValue::Bool(b) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::F64(if *b { 1.0 } else { 0.0 })
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
emit_err(
|
|
|
|
|
"args",
|
|
|
|
|
"TYPE_MISMATCH",
|
|
|
|
|
&format!("param#{} expects Float", i),
|
|
|
|
|
);
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
},
|
|
|
|
|
MirType::Bool => match got {
|
2025-09-17 07:43:07 +09:00
|
|
|
nyash_rust::jit::abi::JitValue::Bool(b) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::Bool(*b)
|
|
|
|
|
}
|
|
|
|
|
nyash_rust::jit::abi::JitValue::I64(v) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::Bool(*v != 0)
|
|
|
|
|
}
|
|
|
|
|
nyash_rust::jit::abi::JitValue::F64(f) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::Bool(*f != 0.0)
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
emit_err(
|
|
|
|
|
"args",
|
|
|
|
|
"TYPE_MISMATCH",
|
|
|
|
|
&format!("param#{} expects Bool", i),
|
|
|
|
|
);
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
},
|
2025-09-17 07:43:07 +09:00
|
|
|
MirType::String
|
|
|
|
|
| MirType::Box(_)
|
|
|
|
|
| MirType::Array(_)
|
|
|
|
|
| MirType::Future(_) => match got {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::Handle(h) => {
|
|
|
|
|
nyash_rust::jit::abi::JitValue::Handle(*h)
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
emit_err(
|
|
|
|
|
"args",
|
|
|
|
|
"TYPE_MISMATCH",
|
|
|
|
|
&format!("param#{} expects handle (h:<id>)", i),
|
|
|
|
|
);
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
},
|
|
|
|
|
MirType::Void | MirType::Unknown => {
|
|
|
|
|
// Keep as-is
|
|
|
|
|
*got
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
coerced.push(cv);
|
|
|
|
|
}
|
|
|
|
|
nyash_rust::jit::rt::set_current_jit_args(&coerced);
|
|
|
|
|
let t0 = std::time::Instant::now();
|
|
|
|
|
let out = engine.execute_handle(h, &coerced);
|
|
|
|
|
match out {
|
|
|
|
|
Some(v) => {
|
|
|
|
|
let ms = t0.elapsed().as_millis();
|
2025-09-17 07:43:07 +09:00
|
|
|
nyash_rust::jit::events::emit(
|
|
|
|
|
"execute",
|
|
|
|
|
&func.signature.name,
|
|
|
|
|
Some(h),
|
|
|
|
|
Some(ms),
|
|
|
|
|
serde_json::json!({}),
|
|
|
|
|
);
|
2025-08-28 09:26:58 +09:00
|
|
|
// Normalize result according to MIR return type for friendly output
|
|
|
|
|
use nyash_rust::mir::MirType;
|
|
|
|
|
let ret_ty = &func.signature.return_type;
|
|
|
|
|
let vmv = match (ret_ty, v) {
|
2025-09-17 07:43:07 +09:00
|
|
|
(MirType::Bool, nyash_rust::jit::abi::JitValue::I64(i)) => {
|
|
|
|
|
nyash_rust::backend::vm::VMValue::Bool(i != 0)
|
|
|
|
|
}
|
|
|
|
|
(MirType::Bool, nyash_rust::jit::abi::JitValue::Bool(b)) => {
|
|
|
|
|
nyash_rust::backend::vm::VMValue::Bool(b)
|
|
|
|
|
}
|
|
|
|
|
(MirType::Float, nyash_rust::jit::abi::JitValue::F64(f)) => {
|
|
|
|
|
nyash_rust::backend::vm::VMValue::Float(f)
|
|
|
|
|
}
|
|
|
|
|
(MirType::Float, nyash_rust::jit::abi::JitValue::I64(i)) => {
|
|
|
|
|
nyash_rust::backend::vm::VMValue::Float(i as f64)
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
// Default adapter for other combos
|
|
|
|
|
_ => nyash_rust::jit::abi::adapter::from_jit_value(v),
|
|
|
|
|
};
|
|
|
|
|
println!("✅ JIT-direct execution completed successfully!");
|
|
|
|
|
// Pretty print with expected type tag
|
|
|
|
|
let (ety, sval) = match (ret_ty, &vmv) {
|
2025-09-17 07:43:07 +09:00
|
|
|
(MirType::Bool, nyash_rust::backend::vm::VMValue::Bool(b)) => {
|
|
|
|
|
("Bool", b.to_string())
|
|
|
|
|
}
|
|
|
|
|
(MirType::Float, nyash_rust::backend::vm::VMValue::Float(f)) => {
|
|
|
|
|
("Float", format!("{}", f))
|
|
|
|
|
}
|
|
|
|
|
(MirType::Integer, nyash_rust::backend::vm::VMValue::Integer(i)) => {
|
|
|
|
|
("Integer", i.to_string())
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
// Fallbacks
|
2025-09-17 07:43:07 +09:00
|
|
|
(_, nyash_rust::backend::vm::VMValue::Integer(i)) => {
|
|
|
|
|
("Integer", i.to_string())
|
|
|
|
|
}
|
|
|
|
|
(_, nyash_rust::backend::vm::VMValue::Float(f)) => {
|
|
|
|
|
("Float", format!("{}", f))
|
|
|
|
|
}
|
|
|
|
|
(_, nyash_rust::backend::vm::VMValue::Bool(b)) => {
|
|
|
|
|
("Bool", b.to_string())
|
|
|
|
|
}
|
|
|
|
|
(_, nyash_rust::backend::vm::VMValue::String(s)) => {
|
|
|
|
|
("String", s.clone())
|
|
|
|
|
}
|
|
|
|
|
(_, nyash_rust::backend::vm::VMValue::BoxRef(arc)) => {
|
|
|
|
|
("BoxRef", arc.type_name().to_string())
|
|
|
|
|
}
|
|
|
|
|
(_, nyash_rust::backend::vm::VMValue::Future(_)) => {
|
|
|
|
|
("Future", "<future>".to_string())
|
|
|
|
|
}
|
|
|
|
|
(_, nyash_rust::backend::vm::VMValue::Void) => {
|
|
|
|
|
("Void", "void".to_string())
|
|
|
|
|
}
|
2025-08-28 09:26:58 +09:00
|
|
|
};
|
|
|
|
|
println!("ResultType(MIR): {}", ety);
|
|
|
|
|
println!("Result: {}", sval);
|
|
|
|
|
// Optional JSON stats
|
|
|
|
|
if std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1") {
|
|
|
|
|
let cfg = nyash_rust::jit::config::current();
|
|
|
|
|
let caps = nyash_rust::jit::config::probe_capabilities();
|
|
|
|
|
let (phi_t, phi_b1, ret_b) = engine.last_lower_stats();
|
2025-09-17 07:43:07 +09:00
|
|
|
let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig {
|
|
|
|
|
"b1_bool"
|
|
|
|
|
} else {
|
|
|
|
|
"i64_bool"
|
|
|
|
|
};
|
2025-08-28 09:26:58 +09:00
|
|
|
let payload = serde_json::json!({
|
|
|
|
|
"version": 1,
|
|
|
|
|
"function": func.signature.name,
|
|
|
|
|
"abi_mode": abi_mode,
|
|
|
|
|
"abi_b1_enabled": cfg.native_bool_abi,
|
|
|
|
|
"abi_b1_supported": caps.supports_b1_sig,
|
|
|
|
|
"b1_norm_count": nyash_rust::jit::rt::b1_norm_get(),
|
|
|
|
|
"ret_bool_hint_count": nyash_rust::jit::rt::ret_bool_hint_get(),
|
|
|
|
|
"phi_total_slots": phi_t,
|
|
|
|
|
"phi_b1_slots": phi_b1,
|
|
|
|
|
"ret_bool_hint_used": ret_b,
|
|
|
|
|
});
|
|
|
|
|
println!("{}", payload.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => {
|
2025-09-17 07:43:07 +09:00
|
|
|
nyash_rust::jit::events::emit(
|
|
|
|
|
"fallback",
|
|
|
|
|
&func.signature.name,
|
|
|
|
|
Some(h),
|
|
|
|
|
None,
|
|
|
|
|
serde_json::json!({"reason":"trap_or_missing"}),
|
|
|
|
|
);
|
|
|
|
|
emit_err(
|
|
|
|
|
"execute",
|
|
|
|
|
"TRAP_OR_MISSING",
|
|
|
|
|
"execution failed (trap or missing handle)",
|
|
|
|
|
);
|
2025-08-28 09:26:58 +09:00
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => {
|
2025-09-17 07:43:07 +09:00
|
|
|
emit_err(
|
|
|
|
|
"compile",
|
|
|
|
|
"UNAVAILABLE",
|
|
|
|
|
"Build with --features cranelift-jit",
|
|
|
|
|
);
|
2025-08-28 09:26:58 +09:00
|
|
|
std::process::exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-23 02:26:33 +09:00
|
|
|
*/ // End of archived JIT implementation
|
2025-08-28 09:26:58 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-14 13:16:12 +00:00
|
|
|
// Demo functions (moved from main.rs)
|
2025-09-03 01:37:38 +09:00
|
|
|
// moved to demos.rs
|
2025-08-14 13:16:12 +00:00
|
|
|
|
2025-09-03 01:37:38 +09:00
|
|
|
// moved to demos.rs
|
2025-08-14 13:16:12 +00:00
|
|
|
|
2025-09-03 01:37:38 +09:00
|
|
|
// moved to demos.rs
|
2025-08-14 13:16:12 +00:00
|
|
|
|
2025-09-03 01:37:38 +09:00
|
|
|
// moved to demos.rs
|
2025-08-14 13:16:12 +00:00
|
|
|
|
2025-09-03 01:37:38 +09:00
|
|
|
// moved to demos.rs
|
2025-08-14 13:16:12 +00:00
|
|
|
|
2025-09-03 01:37:38 +09:00
|
|
|
// moved to demos.rs
|