Files
hakorune/src/backend/aot/executable.rs

302 lines
9.3 KiB
Rust

/*!
* Executable Builder - Creates standalone native executables
*
* Embeds precompiled WASM modules into self-contained executables
*/
use super::{AotConfig, AotError};
use std::fs;
use std::path::Path;
/// Builder for creating standalone executable files
pub struct ExecutableBuilder<'a> {
config: &'a AotConfig,
precompiled_module: Option<Vec<u8>>,
runtime_template: &'static str,
}
impl<'a> ExecutableBuilder<'a> {
/// Create a new executable builder
pub fn new(config: &'a AotConfig) -> Self {
Self {
config,
precompiled_module: None,
runtime_template: RUNTIME_TEMPLATE,
}
}
/// Embed precompiled module data
pub fn embed_precompiled_module(&mut self, module_data: Vec<u8>) -> Result<(), AotError> {
self.precompiled_module = Some(module_data);
Ok(())
}
/// Create the standalone executable
pub fn create_executable<P: AsRef<Path>>(&self, output_path: P) -> Result<(), AotError> {
let module_data = self.precompiled_module.as_ref().ok_or_else(|| {
AotError::CompilationError("No precompiled module embedded".to_string())
})?;
// Generate the runtime code with embedded module
let runtime_code = self.generate_runtime_code(module_data)?;
// Write to temporary Rust source file
let temp_dir = std::env::temp_dir();
let temp_main = temp_dir.join("nyash_aot_main.rs");
let temp_cargo = temp_dir.join("Cargo.toml");
fs::write(&temp_main, runtime_code)?;
fs::write(&temp_cargo, self.generate_cargo_toml())?;
// Compile with Rust compiler
self.compile_rust_executable(&temp_dir, output_path)?;
// Clean up temporary files
let _ = fs::remove_file(&temp_main);
let _ = fs::remove_file(&temp_cargo);
Ok(())
}
/// Generate the runtime code with embedded module
fn generate_runtime_code(&self, module_data: &[u8]) -> Result<String, AotError> {
let module_bytes = self.format_module_bytes(module_data);
let compatibility_key = self.config.compatibility_key();
let runtime_code = self
.runtime_template
.replace("{{MODULE_BYTES}}", &module_bytes)
.replace("{{COMPATIBILITY_KEY}}", &compatibility_key)
.replace(
"{{OPTIMIZATION_LEVEL}}",
&self.config.optimization_level().to_string(),
)
.replace("{{TARGET_ARCH}}", self.config.target_arch())
.replace("{{WASMTIME_VERSION}}", "18.0");
Ok(runtime_code)
}
/// Format module bytes as Rust byte array literal
fn format_module_bytes(&self, data: &[u8]) -> String {
let mut result = String::with_capacity(data.len() * 6);
result.push_str("&[\n ");
for (i, byte) in data.iter().enumerate() {
if i > 0 && i % 16 == 0 {
result.push_str("\n ");
}
result.push_str(&format!("0x{:02x}, ", byte));
}
result.push_str("\n]");
result
}
/// Generate Cargo.toml for the executable
fn generate_cargo_toml(&self) -> String {
format!(
r#"[package]
name = "nyash-aot-executable"
version = "0.1.0"
edition = "2021"
[dependencies]
wasmtime = "18.0"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
strip = true
[[bin]]
name = "nyash-aot-executable"
path = "nyash_aot_main.rs"
"#
)
}
/// Compile the Rust executable
fn compile_rust_executable<P: AsRef<Path>, Q: AsRef<Path>>(
&self,
temp_dir: P,
output_path: Q,
) -> Result<(), AotError> {
let temp_dir = temp_dir.as_ref();
let output_path = output_path.as_ref();
// Use cargo to compile
let mut cmd = std::process::Command::new("cargo");
cmd.current_dir(temp_dir)
.args(&["build", "--release", "--bin", "nyash-aot-executable"]);
let output = cmd
.output()
.map_err(|e| AotError::CompilationError(format!("Failed to run cargo: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(AotError::CompilationError(format!(
"Cargo build failed: {}",
stderr
)));
}
// Copy the compiled executable to the desired location
let compiled_exe = temp_dir.join("target/release/nyash-aot-executable");
let compiled_exe = if cfg!(windows) {
compiled_exe.with_extension("exe")
} else {
compiled_exe
};
if !compiled_exe.exists() {
return Err(AotError::CompilationError(
"Compiled executable not found".to_string(),
));
}
fs::copy(&compiled_exe, output_path)
.map_err(|e| AotError::IOError(format!("Failed to copy executable: {}", e)))?;
Ok(())
}
}
/// Runtime template for generated executables
const RUNTIME_TEMPLATE: &str = r#"/*!
* Nyash AOT Runtime - Generated executable
*
* This file is automatically generated by the Nyash AOT compiler.
* It contains a precompiled WebAssembly module and minimal runtime.
*/
use wasmtime::{Engine, Module, Instance, Store, Config, OptLevel, Strategy};
use std::process;
// Embedded precompiled module (generated by AOT compiler)
const MODULE_DATA: &[u8] = {{MODULE_BYTES}};
// Compilation metadata
const COMPATIBILITY_KEY: &str = "{{COMPATIBILITY_KEY}}";
const OPTIMIZATION_LEVEL: &str = "{{OPTIMIZATION_LEVEL}}";
const TARGET_ARCH: &str = "{{TARGET_ARCH}}";
const WASMTIME_VERSION: &str = "{{WASMTIME_VERSION}}";
fn main() {
if let Err(e) = run_aot_module() {
eprintln!("❌ AOT execution error: {}", e);
process::exit(1);
}
}
fn run_aot_module() -> Result<(), Box<dyn std::error::Error>> {
// Create optimized wasmtime configuration
let mut config = Config::new();
config.strategy(Strategy::Cranelift);
config.cranelift_opt_level(OptLevel::Speed);
// Enable features used during compilation
config.wasm_simd(true);
config.wasm_bulk_memory(true);
config.wasm_multi_memory(true);
// Create engine with the configuration
let engine = Engine::new(&config)?;
// Deserialize the precompiled module
let module = unsafe {
Module::deserialize(&engine, MODULE_DATA)?
};
// Create store and instance
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[])?;
// Look for the main function
let main_func = instance
.get_typed_func::<(), i32>(&mut store, "main")
.or_else(|_| instance.get_typed_func::<(), i32>(&mut store, "_start"))
.or_else(|_| instance.get_typed_func::<(), i32>(&mut store, "run"))
.map_err(|_| "No main function found in module")?;
// Execute the function
let result = main_func.call(&mut store, ())?;
println!("✅ AOT execution completed successfully!");
println!("📊 Metadata:");
println!(" Compatibility: {}", COMPATIBILITY_KEY);
println!(" Optimization: {}", OPTIMIZATION_LEVEL);
println!(" Target: {}", TARGET_ARCH);
println!(" Wasmtime: {}", WASMTIME_VERSION);
println!(" Result: {}", result);
process::exit(result);
}
"#;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_executable_builder_creation() {
let config = AotConfig::new().expect("Failed to create config");
let _builder = ExecutableBuilder::new(&config);
// Should not panic
assert!(true);
}
#[test]
fn test_embed_module() {
let config = AotConfig::new().expect("Failed to create config");
let mut builder = ExecutableBuilder::new(&config);
let test_data = vec![1, 2, 3, 4, 5];
builder
.embed_precompiled_module(test_data)
.expect("Failed to embed module");
assert!(builder.precompiled_module.is_some());
}
#[test]
fn test_format_module_bytes() {
let config = AotConfig::new().expect("Failed to create config");
let builder = ExecutableBuilder::new(&config);
let test_data = vec![0x00, 0x61, 0x73, 0x6d];
let formatted = builder.format_module_bytes(&test_data);
assert!(formatted.contains("0x00"));
assert!(formatted.contains("0x61"));
assert!(formatted.contains("0x73"));
assert!(formatted.contains("0x6d"));
}
#[test]
fn test_cargo_toml_generation() {
let config = AotConfig::new().expect("Failed to create config");
let builder = ExecutableBuilder::new(&config);
let cargo_toml = builder.generate_cargo_toml();
assert!(cargo_toml.contains("nyash-aot-executable"));
assert!(cargo_toml.contains("wasmtime"));
assert!(cargo_toml.contains("opt-level = 3"));
}
#[test]
fn test_runtime_code_generation() {
let config = AotConfig::new().expect("Failed to create config");
let builder = ExecutableBuilder::new(&config);
let test_data = vec![0x00, 0x61, 0x73, 0x6d];
let runtime_code = builder
.generate_runtime_code(&test_data)
.expect("Failed to generate runtime");
assert!(runtime_code.contains("MODULE_DATA"));
assert!(runtime_code.contains("0x00"));
assert!(runtime_code.contains("18.0")); // Wasmtime version
}
}