diff --git a/Cargo.toml b/Cargo.toml index 0c643b52..14bbc765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,10 @@ wasm-bindgen = "0.2" console_error_panic_hook = "0.1" js-sys = "0.3" +# WASM backend dependencies (Phase 8) +wabt = "0.10" +wasmtime = "18.0" + # GUI フレームワーク - only when gui feature is enabled egui = { version = "0.29", optional = true } eframe = { version = "0.29", default-features = false, features = ["default_fonts", "glow"], optional = true } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 76ce1642..db212ebc 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -3,5 +3,7 @@ */ pub mod vm; +pub mod wasm; -pub use vm::{VM, VMError, VMValue}; \ No newline at end of file +pub use vm::{VM, VMError, VMValue}; +pub use wasm::{WasmBackend, WasmError}; \ No newline at end of file diff --git a/src/backend/wasm/codegen.rs b/src/backend/wasm/codegen.rs new file mode 100644 index 00000000..43f2c5fa --- /dev/null +++ b/src/backend/wasm/codegen.rs @@ -0,0 +1,381 @@ +/*! + * WASM Code Generation - Core MIR to WASM instruction conversion + * + * Phase 8.2 PoC1: Basic operations (arithmetic, control flow, print) + * Phase 8.3 PoC2: Reference operations (RefNew/RefGet/RefSet) + */ + +use crate::mir::{MirModule, MirFunction, MirInstruction, ConstValue, BinaryOp, CompareOp, UnaryOp, ValueId, BasicBlockId}; +use super::{WasmError, MemoryManager, RuntimeImports}; +use std::collections::HashMap; + +/// WASM module representation for WAT generation +pub struct WasmModule { + pub imports: Vec, + pub memory: String, + pub globals: Vec, + pub functions: Vec, + pub exports: Vec, +} + +impl WasmModule { + pub fn new() -> Self { + Self { + imports: Vec::new(), + memory: String::new(), + globals: Vec::new(), + functions: Vec::new(), + exports: Vec::new(), + } + } + + /// Generate WAT text format + pub fn to_wat(&self) -> String { + let mut wat = String::new(); + wat.push_str("(module\n"); + + // Add memory declaration first + if !self.memory.is_empty() { + wat.push_str(&format!(" {}\n", self.memory)); + } + + // Add imports + for import in &self.imports { + wat.push_str(&format!(" {}\n", import)); + } + + // Add globals + for global in &self.globals { + wat.push_str(&format!(" {}\n", global)); + } + + // Add functions + for function in &self.functions { + wat.push_str(&format!(" {}\n", function)); + } + + // Add exports + for export in &self.exports { + wat.push_str(&format!(" {}\n", export)); + } + + wat.push_str(")\n"); + wat + } +} + +/// WASM code generator +pub struct WasmCodegen { + /// Current function context for local variable management + current_locals: HashMap, + next_local_index: u32, +} + +impl WasmCodegen { + pub fn new() -> Self { + Self { + current_locals: HashMap::new(), + next_local_index: 0, + } + } + + /// Generate WASM module from MIR module + pub fn generate_module( + &mut self, + mir_module: MirModule, + memory_manager: &MemoryManager, + runtime: &RuntimeImports + ) -> Result { + let mut wasm_module = WasmModule::new(); + + // Add memory declaration (64KB initial) + wasm_module.memory = "(memory (export \"memory\") 1)".to_string(); + + // Add runtime imports (env.print for debugging) + wasm_module.imports.extend(runtime.get_imports()); + + // Add globals (heap pointer) + wasm_module.globals.extend(memory_manager.get_globals()); + + // Generate functions + for (name, function) in &mir_module.functions { + let wasm_function = self.generate_function(name, function.clone())?; + wasm_module.functions.push(wasm_function); + } + + // Add main function export if it exists + if mir_module.functions.contains_key("main") { + wasm_module.exports.push("(export \"main\" (func $main))".to_string()); + } + + Ok(wasm_module) + } + + /// Generate WASM function from MIR function + fn generate_function(&mut self, name: &str, mir_function: MirFunction) -> Result { + // Reset local variable tracking for this function + self.current_locals.clear(); + self.next_local_index = 0; + + let mut function_body = String::new(); + function_body.push_str(&format!("(func ${})", name)); + + // Add return type if not void + match mir_function.signature.return_type { + crate::mir::MirType::Integer => function_body.push_str(" (result i32)"), + crate::mir::MirType::Bool => function_body.push_str(" (result i32)"), + crate::mir::MirType::Void => {}, // No return type + _ => return Err(WasmError::UnsupportedInstruction( + format!("Unsupported return type: {:?}", mir_function.signature.return_type) + )), + } + + // Collect all local variables needed + let local_count = self.count_locals(&mir_function)?; + if local_count > 0 { + function_body.push_str(&format!(" (local $locals i32)")); + } + + function_body.push('\n'); + + // Generate body from entry block + let entry_instructions = self.generate_basic_block(&mir_function, mir_function.entry_block)?; + for instruction in entry_instructions { + function_body.push_str(&format!(" {}\n", instruction)); + } + + function_body.push_str(" )"); + Ok(function_body) + } + + /// Count local variables needed for the function + fn count_locals(&mut self, mir_function: &MirFunction) -> Result { + let mut max_value_id = 0; + + for (_, block) in &mir_function.blocks { + for instruction in &block.instructions { + if let Some(value_id) = instruction.dst_value() { + max_value_id = max_value_id.max(value_id.as_u32()); + } + for used_value in instruction.used_values() { + max_value_id = max_value_id.max(used_value.as_u32()); + } + } + } + + // Assign local indices to value IDs + for i in 0..=max_value_id { + let value_id = ValueId::new(i); + self.current_locals.insert(value_id, self.next_local_index); + self.next_local_index += 1; + } + + Ok(self.next_local_index) + } + + /// Generate WASM instructions for a basic block + fn generate_basic_block(&self, mir_function: &MirFunction, block_id: BasicBlockId) -> Result, WasmError> { + let block = mir_function.blocks.get(&block_id) + .ok_or_else(|| WasmError::CodegenError(format!("Basic block {:?} not found", block_id)))?; + + let mut instructions = Vec::new(); + + for mir_instruction in &block.instructions { + let wasm_instructions = self.generate_instruction(mir_instruction)?; + instructions.extend(wasm_instructions); + } + + Ok(instructions) + } + + /// Generate WASM instructions for a single MIR instruction + fn generate_instruction(&self, instruction: &MirInstruction) -> Result, WasmError> { + match instruction { + // Phase 8.2 PoC1: Basic operations + MirInstruction::Const { dst, value } => { + self.generate_const(*dst, value) + }, + + MirInstruction::BinOp { dst, op, lhs, rhs } => { + self.generate_binop(*dst, *op, *lhs, *rhs) + }, + + MirInstruction::Compare { dst, op, lhs, rhs } => { + self.generate_compare(*dst, *op, *lhs, *rhs) + }, + + MirInstruction::Return { value } => { + self.generate_return(value.as_ref()) + }, + + MirInstruction::Print { value, .. } => { + self.generate_print(*value) + }, + + // Phase 8.3 PoC2: Reference operations (stub for now) + MirInstruction::RefNew { dst, box_val } => { + // For now, just copy the value (TODO: implement heap allocation) + Ok(vec![ + format!("local.get ${}", self.get_local_index(*box_val)?), + format!("local.set ${}", self.get_local_index(*dst)?), + ]) + }, + + MirInstruction::RefGet { dst, reference, field: _ } => { + // For now, just copy the reference (TODO: implement field access) + Ok(vec![ + format!("local.get ${}", self.get_local_index(*reference)?), + format!("local.set ${}", self.get_local_index(*dst)?), + ]) + }, + + MirInstruction::RefSet { reference: _, field: _, value: _ } => { + // For now, no-op (TODO: implement field assignment) + Ok(vec!["nop".to_string()]) + }, + + // Phase 8.4 PoC3: Extension stubs + MirInstruction::WeakNew { dst, box_val } | + MirInstruction::FutureNew { dst, value: box_val } => { + // Treat as regular reference for now + Ok(vec![ + format!("local.get ${}", self.get_local_index(*box_val)?), + format!("local.set ${}", self.get_local_index(*dst)?), + ]) + }, + + MirInstruction::WeakLoad { dst, weak_ref } | + MirInstruction::Await { dst, future: weak_ref } => { + // Always succeed for now + Ok(vec![ + format!("local.get ${}", self.get_local_index(*weak_ref)?), + format!("local.set ${}", self.get_local_index(*dst)?), + ]) + }, + + MirInstruction::BarrierRead { .. } | + MirInstruction::BarrierWrite { .. } | + MirInstruction::FutureSet { .. } => { + // No-op for now + Ok(vec!["nop".to_string()]) + }, + + // Unsupported instructions + _ => Err(WasmError::UnsupportedInstruction( + format!("Instruction not yet supported: {:?}", instruction) + )), + } + } + + /// Generate constant loading + fn generate_const(&self, dst: ValueId, value: &ConstValue) -> Result, WasmError> { + let const_instruction = match value { + ConstValue::Integer(n) => format!("i32.const {}", n), + ConstValue::Bool(b) => format!("i32.const {}", if *b { 1 } else { 0 }), + ConstValue::Void => "i32.const 0".to_string(), + _ => return Err(WasmError::UnsupportedInstruction( + format!("Unsupported constant type: {:?}", value) + )), + }; + + Ok(vec![ + const_instruction, + format!("local.set ${}", self.get_local_index(dst)?), + ]) + } + + /// Generate binary operation + fn generate_binop(&self, dst: ValueId, op: BinaryOp, lhs: ValueId, rhs: ValueId) -> Result, WasmError> { + let wasm_op = match op { + BinaryOp::Add => "i32.add", + BinaryOp::Sub => "i32.sub", + BinaryOp::Mul => "i32.mul", + BinaryOp::Div => "i32.div_s", + BinaryOp::And => "i32.and", + BinaryOp::Or => "i32.or", + _ => return Err(WasmError::UnsupportedInstruction( + format!("Unsupported binary operation: {:?}", op) + )), + }; + + Ok(vec![ + format!("local.get ${}", self.get_local_index(lhs)?), + format!("local.get ${}", self.get_local_index(rhs)?), + wasm_op.to_string(), + format!("local.set ${}", self.get_local_index(dst)?), + ]) + } + + /// Generate comparison operation + fn generate_compare(&self, dst: ValueId, op: CompareOp, lhs: ValueId, rhs: ValueId) -> Result, WasmError> { + let wasm_op = match op { + CompareOp::Eq => "i32.eq", + CompareOp::Ne => "i32.ne", + CompareOp::Lt => "i32.lt_s", + CompareOp::Le => "i32.le_s", + CompareOp::Gt => "i32.gt_s", + CompareOp::Ge => "i32.ge_s", + }; + + Ok(vec![ + format!("local.get ${}", self.get_local_index(lhs)?), + format!("local.get ${}", self.get_local_index(rhs)?), + wasm_op.to_string(), + format!("local.set ${}", self.get_local_index(dst)?), + ]) + } + + /// Generate return instruction + fn generate_return(&self, value: Option<&ValueId>) -> Result, WasmError> { + if let Some(value_id) = value { + Ok(vec![ + format!("local.get ${}", self.get_local_index(*value_id)?), + "return".to_string(), + ]) + } else { + Ok(vec!["return".to_string()]) + } + } + + /// Generate print instruction (calls env.print import) + fn generate_print(&self, value: ValueId) -> Result, WasmError> { + Ok(vec![ + format!("local.get ${}", self.get_local_index(value)?), + "call $print".to_string(), + ]) + } + + /// Get WASM local variable index for ValueId + fn get_local_index(&self, value_id: ValueId) -> Result { + self.current_locals.get(&value_id) + .copied() + .ok_or_else(|| WasmError::CodegenError(format!("Local variable not found for ValueId: {:?}", value_id))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mir::{MirModule, MirFunction, FunctionSignature, MirType, EffectMask, BasicBlock, BasicBlockId, ValueId}; + + #[test] + fn test_wasm_module_wat_generation() { + let mut module = WasmModule::new(); + module.memory = "(memory (export \"memory\") 1)".to_string(); + module.imports.push("(import \"env\" \"print\" (func $print (param i32)))".to_string()); + + let wat = module.to_wat(); + assert!(wat.contains("(module")); + assert!(wat.contains("memory")); + assert!(wat.contains("import")); + } + + #[test] + fn test_constant_generation() { + let codegen = WasmCodegen::new(); + let dst = ValueId::new(0); + + let result = codegen.generate_const(dst, &ConstValue::Integer(42)); + assert!(result.is_err()); // Should fail without local mapping + } +} \ No newline at end of file diff --git a/src/backend/wasm/memory.rs b/src/backend/wasm/memory.rs new file mode 100644 index 00000000..00e66817 --- /dev/null +++ b/src/backend/wasm/memory.rs @@ -0,0 +1,252 @@ +/*! + * WASM Memory Management - Box layout and heap allocation + * + * Phase 8.3 PoC2: Implements bump allocator and Box memory layout + * Memory Layout: 0x000-0x3FF (reserved), 0x400-0x7FF (stack), 0x800+ (heap) + */ + +use super::WasmError; +use std::collections::HashMap; + +/// Box memory layout definition +#[derive(Debug, Clone)] +pub struct BoxLayout { + pub type_id: u32, + pub size: u32, + pub field_offsets: HashMap, +} + +impl BoxLayout { + pub fn new(type_name: &str) -> Self { + // Simple type ID generation (hash of name for now) + let type_id = type_name.chars().map(|c| c as u32).sum::() % 65536; + + Self { + type_id, + size: 8, // Minimum size: type_id + field_count + field_offsets: HashMap::new(), + } + } + + pub fn add_field(&mut self, field_name: String) { + let offset = self.size; + self.field_offsets.insert(field_name, offset); + self.size += 4; // Each field is 4 bytes (i32) + } + + pub fn get_field_offset(&self, field_name: &str) -> Option { + self.field_offsets.get(field_name).copied() + } +} + +/// WASM memory manager +pub struct MemoryManager { + /// Known Box layouts + box_layouts: HashMap, + /// Current heap pointer (starts at 0x800) + heap_start: u32, +} + +impl MemoryManager { + pub fn new() -> Self { + Self { + box_layouts: HashMap::new(), + heap_start: 0x800, // 2KB reserved for stack/globals + } + } + + /// Register a Box type layout + pub fn register_box_type(&mut self, type_name: String, fields: Vec) { + let mut layout = BoxLayout::new(&type_name); + + for field in fields { + layout.add_field(field); + } + + self.box_layouts.insert(type_name, layout); + } + + /// Get Box layout by type name + pub fn get_box_layout(&self, type_name: &str) -> Option<&BoxLayout> { + self.box_layouts.get(type_name) + } + + /// Generate WASM globals for heap management + pub fn get_globals(&self) -> Vec { + vec![ + format!("(global $heap_ptr (mut i32) (i32.const {}))", self.heap_start), + ] + } + + /// Generate heap allocation function + pub fn get_malloc_function(&self) -> String { + format!( + r#"(func $malloc (param $size i32) (result i32) + (local $ptr i32) + + ;; Get current heap pointer + global.get $heap_ptr + local.set $ptr + + ;; Advance heap pointer + global.get $heap_ptr + local.get $size + i32.add + global.set $heap_ptr + + ;; Return allocated pointer + local.get $ptr + )"# + ) + } + + /// Generate Box allocation function for specific type + pub fn get_box_alloc_function(&self, type_name: &str) -> Result { + let layout = self.get_box_layout(type_name) + .ok_or_else(|| WasmError::MemoryError(format!("Unknown box type: {}", type_name)))?; + + Ok(format!( + r#"(func $alloc_{} (result i32) + (local $ptr i32) + + ;; Allocate memory for box + i32.const {} + call $malloc + local.set $ptr + + ;; Initialize type_id + local.get $ptr + i32.const {} + i32.store + + ;; Initialize field_count + local.get $ptr + i32.const 4 + i32.add + i32.const {} + i32.store + + ;; Return box pointer + local.get $ptr + )"#, + type_name.to_lowercase(), + layout.size, + layout.type_id, + layout.field_offsets.len() + )) + } + + /// Generate field getter function + pub fn get_field_get_function(&self, type_name: &str, field_name: &str) -> Result { + let layout = self.get_box_layout(type_name) + .ok_or_else(|| WasmError::MemoryError(format!("Unknown box type: {}", type_name)))?; + + let offset = layout.get_field_offset(field_name) + .ok_or_else(|| WasmError::MemoryError(format!("Unknown field: {}.{}", type_name, field_name)))?; + + Ok(format!( + r#"(func $get_{}_{} (param $box_ptr i32) (result i32) + local.get $box_ptr + i32.const {} + i32.add + i32.load + )"#, + type_name.to_lowercase(), + field_name, + offset + )) + } + + /// Generate field setter function + pub fn get_field_set_function(&self, type_name: &str, field_name: &str) -> Result { + let layout = self.get_box_layout(type_name) + .ok_or_else(|| WasmError::MemoryError(format!("Unknown box type: {}", type_name)))?; + + let offset = layout.get_field_offset(field_name) + .ok_or_else(|| WasmError::MemoryError(format!("Unknown field: {}.{}", type_name, field_name)))?; + + Ok(format!( + r#"(func $set_{}_{} (param $box_ptr i32) (param $value i32) + local.get $box_ptr + i32.const {} + i32.add + local.get $value + i32.store + )"#, + type_name.to_lowercase(), + field_name, + offset + )) + } + + /// Get memory layout constants for documentation + pub fn get_memory_layout_info(&self) -> String { + format!( + r#" +;; Memory Layout: +;; 0x000-0x3FF: Reserved/globals (1KB) +;; 0x400-0x7FF: Stack space (1KB) +;; 0x800+: Heap (bump allocator) +;; +;; Box Layout: [type_id:i32][field_count:i32][field0:i32][field1:i32]... +;; +;; Heap start: 0x{:x} +"#, + self.heap_start + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_box_layout_creation() { + let layout = BoxLayout::new("TestBox"); + assert_eq!(layout.size, 8); // type_id + field_count + assert!(layout.field_offsets.is_empty()); + } + + #[test] + fn test_box_layout_field_addition() { + let mut layout = BoxLayout::new("TestBox"); + layout.add_field("field1".to_string()); + layout.add_field("field2".to_string()); + + assert_eq!(layout.size, 16); // 8 + 4 + 4 + assert_eq!(layout.get_field_offset("field1"), Some(8)); + assert_eq!(layout.get_field_offset("field2"), Some(12)); + } + + #[test] + fn test_memory_manager_registration() { + let mut manager = MemoryManager::new(); + manager.register_box_type("DataBox".to_string(), vec!["x".to_string(), "y".to_string()]); + + let layout = manager.get_box_layout("DataBox").unwrap(); + assert_eq!(layout.field_offsets.len(), 2); + assert!(layout.get_field_offset("x").is_some()); + assert!(layout.get_field_offset("y").is_some()); + } + + #[test] + fn test_malloc_function_generation() { + let manager = MemoryManager::new(); + let malloc_func = manager.get_malloc_function(); + + assert!(malloc_func.contains("$malloc")); + assert!(malloc_func.contains("$heap_ptr")); + assert!(malloc_func.contains("global.get")); + } + + #[test] + fn test_box_alloc_function_generation() { + let mut manager = MemoryManager::new(); + manager.register_box_type("TestBox".to_string(), vec!["value".to_string()]); + + let alloc_func = manager.get_box_alloc_function("TestBox").unwrap(); + assert!(alloc_func.contains("$alloc_testbox")); + assert!(alloc_func.contains("call $malloc")); + } +} \ No newline at end of file diff --git a/src/backend/wasm/mod.rs b/src/backend/wasm/mod.rs new file mode 100644 index 00000000..3d3799ef --- /dev/null +++ b/src/backend/wasm/mod.rs @@ -0,0 +1,131 @@ +/*! + * WASM Backend - Phase 8 Implementation + * + * Converts MIR instructions to WebAssembly for sandboxed execution + * Targets browser execution and wasmtime runtime + */ + +mod codegen; +mod memory; +mod runtime; + +pub use codegen::{WasmCodegen, WasmModule}; +pub use memory::{MemoryManager, BoxLayout}; +pub use runtime::RuntimeImports; + +use crate::mir::{MirModule, MirFunction}; +use std::collections::HashMap; + +/// WASM compilation error +#[derive(Debug)] +pub enum WasmError { + CodegenError(String), + MemoryError(String), + UnsupportedInstruction(String), + WasmValidationError(String), + IOError(String), +} + +impl std::fmt::Display for WasmError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WasmError::CodegenError(msg) => write!(f, "Codegen error: {}", msg), + WasmError::MemoryError(msg) => write!(f, "Memory error: {}", msg), + WasmError::UnsupportedInstruction(msg) => write!(f, "Unsupported instruction: {}", msg), + WasmError::WasmValidationError(msg) => write!(f, "WASM validation error: {}", msg), + WasmError::IOError(msg) => write!(f, "IO error: {}", msg), + } + } +} + +impl std::error::Error for WasmError {} + +/// Main WASM backend compiler +pub struct WasmBackend { + codegen: WasmCodegen, + memory_manager: MemoryManager, + runtime: RuntimeImports, +} + +impl WasmBackend { + /// Create a new WASM backend + pub fn new() -> Self { + Self { + codegen: WasmCodegen::new(), + memory_manager: MemoryManager::new(), + runtime: RuntimeImports::new(), + } + } + + /// Compile MIR module to WASM bytes + pub fn compile_module(&mut self, mir_module: MirModule) -> Result, WasmError> { + // Generate WAT (WebAssembly Text) first for debugging + let wat_text = self.compile_to_wat(mir_module)?; + + // Convert WAT to WASM binary using wabt + wabt::wat2wasm(&wat_text) + .map_err(|e| WasmError::WasmValidationError(format!("WAT to WASM conversion failed: {}", e))) + } + + /// Compile MIR module to WAT text format (for debugging) + pub fn compile_to_wat(&mut self, mir_module: MirModule) -> Result { + let wasm_module = self.codegen.generate_module(mir_module, &self.memory_manager, &self.runtime)?; + Ok(wasm_module.to_wat()) + } + + /// Execute WASM bytes using wasmtime (for testing) + pub fn execute_wasm(&self, wasm_bytes: &[u8]) -> Result { + let engine = wasmtime::Engine::default(); + let module = wasmtime::Module::new(&engine, wasm_bytes) + .map_err(|e| WasmError::WasmValidationError(format!("Module creation failed: {}", e)))?; + + let mut store = wasmtime::Store::new(&engine, ()); + + // Create print function import + let print_func = wasmtime::Func::wrap(&mut store, |value: i32| { + println!("{}", value); + }); + + let imports = [print_func.into()]; + let instance = wasmtime::Instance::new(&mut store, &module, &imports) + .map_err(|e| WasmError::WasmValidationError(format!("Instance creation failed: {}", e)))?; + + // Call main function + let main_func = instance.get_typed_func::<(), i32>(&mut store, "main") + .map_err(|e| WasmError::WasmValidationError(format!("Main function not found: {}", e)))?; + + let result = main_func.call(&mut store, ()) + .map_err(|e| WasmError::WasmValidationError(format!("Execution failed: {}", e)))?; + + Ok(result) + } +} + +impl Default for WasmBackend { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mir::MirModule; + + #[test] + fn test_backend_creation() { + let _backend = WasmBackend::new(); + // Should not panic + assert!(true); + } + + #[test] + fn test_empty_module_compilation() { + let mut backend = WasmBackend::new(); + let module = MirModule::new("test".to_string()); + + // Should handle empty module gracefully + let result = backend.compile_to_wat(module); + assert!(result.is_ok()); + } +} \ No newline at end of file diff --git a/src/backend/wasm/runtime.rs b/src/backend/wasm/runtime.rs new file mode 100644 index 00000000..5ebeb2e6 --- /dev/null +++ b/src/backend/wasm/runtime.rs @@ -0,0 +1,221 @@ +/*! + * WASM Runtime Imports - External function imports for WASM modules + * + * Phase 8.2 PoC1: Implements env.print import for debugging + * Future: Additional runtime functions (file I/O, network, etc.) + */ + +use super::WasmError; + +/// Runtime import definitions for WASM modules +pub struct RuntimeImports { + /// Available imports + imports: Vec, +} + +/// External function import definition +#[derive(Debug, Clone)] +pub struct ImportFunction { + pub module: String, + pub name: String, + pub params: Vec, + pub result: Option, +} + +impl RuntimeImports { + pub fn new() -> Self { + let mut runtime = Self { + imports: Vec::new(), + }; + + // Add standard runtime imports + runtime.add_standard_imports(); + runtime + } + + /// Add standard runtime function imports + fn add_standard_imports(&mut self) { + // env.print for debugging output + self.imports.push(ImportFunction { + module: "env".to_string(), + name: "print".to_string(), + params: vec!["i32".to_string()], + result: None, + }); + + // Future: env.print_string for string output + // Future: env.file_read, env.file_write for file I/O + // Future: env.http_request for network access + } + + /// Get all import declarations in WAT format + pub fn get_imports(&self) -> Vec { + self.imports.iter().map(|import| { + let params = if import.params.is_empty() { + String::new() + } else { + format!("(param {})", import.params.join(" ")) + }; + + let result = if let Some(ref result_type) = import.result { + format!("(result {})", result_type) + } else { + String::new() + }; + + format!( + "(import \"{}\" \"{}\" (func ${} {} {}))", + import.module, + import.name, + import.name, + params, + result + ) + }).collect() + } + + /// Add custom import function + pub fn add_import(&mut self, module: String, name: String, params: Vec, result: Option) { + self.imports.push(ImportFunction { + module, + name, + params, + result, + }); + } + + /// Check if an import is available + pub fn has_import(&self, name: &str) -> bool { + self.imports.iter().any(|import| import.name == name) + } + + /// Get import function by name + pub fn get_import(&self, name: &str) -> Option<&ImportFunction> { + self.imports.iter().find(|import| import.name == name) + } + + /// Generate JavaScript import object for browser execution + pub fn get_js_import_object(&self) -> String { + let mut js = String::new(); + js.push_str("const importObject = {\n"); + + // Group by module + let mut modules: std::collections::HashMap> = std::collections::HashMap::new(); + for import in &self.imports { + modules.entry(import.module.clone()).or_default().push(import); + } + + for (module_name, functions) in modules { + js.push_str(&format!(" {}: {{\n", module_name)); + + for function in functions { + match function.name.as_str() { + "print" => { + js.push_str(" print: (value) => console.log(value),\n"); + }, + _ => { + js.push_str(&format!(" {}: () => {{ throw new Error('Not implemented: {}'); }},\n", + function.name, function.name)); + } + } + } + + js.push_str(" },\n"); + } + + js.push_str("};\n"); + js + } + + /// Generate Rust wasmtime import bindings + pub fn get_wasmtime_imports(&self) -> Result { + let mut rust_code = String::new(); + rust_code.push_str("// Wasmtime import bindings\n"); + rust_code.push_str("let mut imports = Vec::new();\n\n"); + + for import in &self.imports { + match import.name.as_str() { + "print" => { + rust_code.push_str(r#" +let print_func = wasmtime::Func::wrap(&mut store, |value: i32| { + println!("{}", value); +}); +imports.push(print_func.into()); +"#); + }, + _ => { + rust_code.push_str(&format!(r#" +// TODO: Implement {} import +let {}_func = wasmtime::Func::wrap(&mut store, || {{ + panic!("Not implemented: {}") +}}); +imports.push({}_func.into()); +"#, import.name, import.name, import.name, import.name)); + } + } + } + + Ok(rust_code) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_runtime_imports_creation() { + let runtime = RuntimeImports::new(); + assert!(!runtime.imports.is_empty()); + assert!(runtime.has_import("print")); + } + + #[test] + fn test_import_wat_generation() { + let runtime = RuntimeImports::new(); + let imports = runtime.get_imports(); + + assert!(!imports.is_empty()); + assert!(imports[0].contains("import")); + assert!(imports[0].contains("env")); + assert!(imports[0].contains("print")); + } + + #[test] + fn test_custom_import_addition() { + let mut runtime = RuntimeImports::new(); + runtime.add_import( + "custom".to_string(), + "test_func".to_string(), + vec!["i32".to_string(), "i32".to_string()], + Some("i32".to_string()) + ); + + assert!(runtime.has_import("test_func")); + let import = runtime.get_import("test_func").unwrap(); + assert_eq!(import.module, "custom"); + assert_eq!(import.params.len(), 2); + assert!(import.result.is_some()); + } + + #[test] + fn test_js_import_object_generation() { + let runtime = RuntimeImports::new(); + let js = runtime.get_js_import_object(); + + assert!(js.contains("importObject")); + assert!(js.contains("env")); + assert!(js.contains("print")); + assert!(js.contains("console.log")); + } + + #[test] + fn test_wasmtime_imports_generation() { + let runtime = RuntimeImports::new(); + let rust_code = runtime.get_wasmtime_imports().unwrap(); + + assert!(rust_code.contains("wasmtime::Func::wrap")); + assert!(rust_code.contains("print_func")); + assert!(rust_code.contains("println!")); + } +} \ No newline at end of file