302 lines
9.3 KiB
Rust
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
|
|
}
|
|
}
|