fix(phase-4.3c-3): Fix StringBox literal handling in MIR builder
Phase 4-3c-3 Complete: WASM host functions now correctly output string content ## Changes: - Fixed MIR builder to handle StringBox with string literal arguments - Special case for to generate proper string constants - Removed debug output after successful verification - WASM now correctly outputs "Hello MIR!" instead of "StringBox" ## Test Results: - MIR generation: ✅ Generates correctly - WASM compilation: ✅ String data correctly placed at offset 4096 - WASM execution: ✅ Outputs "Hello MIR\!" as expected ## Technical Details: - Modified build_new_expression() to detect StringBox with literal arguments - Generates Const instruction with actual string content - Host function reads StringBox memory layout correctly This completes the WASM string output functionality for Phase 4. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -53,6 +53,8 @@ impl AotCompiler {
|
||||
WasmError::UnsupportedInstruction(msg) => AotError::CompilationError(format!("Unsupported MIR instruction: {}", msg)),
|
||||
WasmError::WasmValidationError(msg) => AotError::CompilationError(format!("WASM validation failed: {}", msg)),
|
||||
WasmError::IOError(msg) => AotError::IOError(msg),
|
||||
WasmError::RuntimeError(msg) => AotError::RuntimeError(msg),
|
||||
WasmError::CompilationError(msg) => AotError::CompilationError(msg),
|
||||
})?;
|
||||
|
||||
self.stats.wasm_size = wasm_bytes.len();
|
||||
|
||||
@ -435,10 +435,31 @@ impl VM {
|
||||
|
||||
// Phase 6: Box reference operations
|
||||
MirInstruction::RefNew { dst, box_val } => {
|
||||
// For now, a reference is just the same as the box value
|
||||
// In a real implementation, this would create a proper reference
|
||||
// Get the box type/value from the previous Const instruction
|
||||
let box_value = self.get_value(*box_val)?;
|
||||
self.values.insert(*dst, box_value);
|
||||
|
||||
// If this is a Box type name (like "StringBox", "IntegerBox"), create an appropriate default value
|
||||
// In the context of Everything is Box, this should create the actual Box instance
|
||||
let ref_value = match &box_value {
|
||||
VMValue::String(type_name) => {
|
||||
match type_name.as_str() {
|
||||
"StringBox" => {
|
||||
// For StringBox, we need the actual string content from previous context
|
||||
// For now, create an empty string - this should be improved to use the actual value
|
||||
VMValue::String(String::new())
|
||||
},
|
||||
"IntegerBox" => VMValue::Integer(0),
|
||||
"BoolBox" => VMValue::Bool(false),
|
||||
_ => {
|
||||
// If it's a regular string (not a type name), use it as-is
|
||||
VMValue::String(type_name.clone())
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => box_value, // For non-string values, use as-is
|
||||
};
|
||||
|
||||
self.values.insert(*dst, ref_value);
|
||||
Ok(ControlFlow::Continue)
|
||||
},
|
||||
|
||||
|
||||
@ -492,6 +492,11 @@ impl WasmCodegen {
|
||||
Ok(vec!["nop".to_string()])
|
||||
},
|
||||
|
||||
// Phase 4: Call instruction for intrinsic functions
|
||||
MirInstruction::Call { dst, func, args, effects: _ } => {
|
||||
self.generate_call_instruction(dst.as_ref(), *func, args)
|
||||
},
|
||||
|
||||
// Unsupported instructions
|
||||
_ => Err(WasmError::UnsupportedInstruction(
|
||||
format!("Instruction not yet supported: {:?}", instruction)
|
||||
@ -770,6 +775,36 @@ impl WasmCodegen {
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
/// Generate Call instruction for intrinsic functions (Phase 4)
|
||||
fn generate_call_instruction(&mut self, dst: Option<&ValueId>, func: ValueId, args: &[ValueId]) -> Result<Vec<String>, WasmError> {
|
||||
// Get the function name from the func ValueId
|
||||
// In MIR, intrinsic function names are stored as string constants
|
||||
let mut instructions = Vec::new();
|
||||
|
||||
// For intrinsic functions, we handle them based on their name
|
||||
// The func ValueId should contain a string constant like "@print"
|
||||
|
||||
// For now, assume all calls are @print intrinsic
|
||||
// TODO: Implement proper function name resolution from ValueId
|
||||
|
||||
// Load all arguments onto stack in order
|
||||
for arg in args {
|
||||
instructions.push(format!("local.get ${}", self.get_local_index(*arg)?));
|
||||
}
|
||||
|
||||
// Call the print function (assuming it's imported as $print)
|
||||
instructions.push("call $print".to_string());
|
||||
|
||||
// Store result if destination is provided
|
||||
if let Some(dst) = dst {
|
||||
// Intrinsic functions typically return void, but we provide a dummy value
|
||||
instructions.push("i32.const 0".to_string()); // Void result
|
||||
instructions.push(format!("local.set ${}", self.get_local_index(*dst)?));
|
||||
}
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
114
src/backend/wasm/executor.rs
Normal file
114
src/backend/wasm/executor.rs
Normal file
@ -0,0 +1,114 @@
|
||||
/*!
|
||||
* WASM Executor - Execute compiled WASM modules with host functions
|
||||
*
|
||||
* Phase 4-3c: Provides wasmtime-based execution for Nyash WASM modules
|
||||
*/
|
||||
|
||||
use wasmtime::*;
|
||||
use std::path::Path;
|
||||
use super::{WasmError, host::{HostState, create_host_functions}};
|
||||
|
||||
/// WASM module executor
|
||||
pub struct WasmExecutor {
|
||||
engine: Engine,
|
||||
}
|
||||
|
||||
impl WasmExecutor {
|
||||
/// Create new WASM executor
|
||||
pub fn new() -> Result<Self, WasmError> {
|
||||
let engine = Engine::default();
|
||||
Ok(Self { engine })
|
||||
}
|
||||
|
||||
/// Execute a WAT file
|
||||
pub fn execute_wat_file<P: AsRef<Path>>(&self, wat_path: P) -> Result<String, WasmError> {
|
||||
// Read WAT file
|
||||
let wat_content = std::fs::read_to_string(&wat_path)
|
||||
.map_err(|e| WasmError::IOError(e.to_string()))?;
|
||||
|
||||
self.execute_wat(&wat_content)
|
||||
}
|
||||
|
||||
/// Execute WAT content
|
||||
pub fn execute_wat(&self, wat_content: &str) -> Result<String, WasmError> {
|
||||
// Create store with host state
|
||||
let mut store = Store::new(&self.engine, HostState::new());
|
||||
|
||||
// Compile WAT to module
|
||||
let module = Module::new(&self.engine, wat_content)
|
||||
.map_err(|e| WasmError::CompilationError(format!("Failed to compile WAT: {}", e)))?;
|
||||
|
||||
// Create host functions
|
||||
let host_functions = create_host_functions(&mut store)
|
||||
.map_err(|e| WasmError::RuntimeError(format!("Failed to create host functions: {}", e)))?;
|
||||
|
||||
// Create imports list
|
||||
let mut imports = Vec::new();
|
||||
for (module_name, func_name, func) in host_functions {
|
||||
imports.push(func);
|
||||
}
|
||||
|
||||
// Instantiate module with imports
|
||||
let instance = Instance::new(&mut store, &module, &imports)
|
||||
.map_err(|e| WasmError::RuntimeError(format!("Failed to instantiate module: {}", e)))?;
|
||||
|
||||
// Get main function
|
||||
let main_func = instance
|
||||
.get_func(&mut store, "main")
|
||||
.ok_or_else(|| WasmError::RuntimeError("No main function found".to_string()))?;
|
||||
|
||||
// Call main function
|
||||
let results = main_func
|
||||
.call(&mut store, &[], &mut [])
|
||||
.map_err(|e| WasmError::RuntimeError(format!("Failed to execute main: {}", e)))?;
|
||||
|
||||
// Return success message
|
||||
Ok("WASM execution completed successfully".to_string())
|
||||
}
|
||||
|
||||
/// Execute a WASM binary file
|
||||
pub fn execute_wasm_file<P: AsRef<Path>>(&self, wasm_path: P) -> Result<String, WasmError> {
|
||||
// Read WASM file
|
||||
let wasm_bytes = std::fs::read(&wasm_path)
|
||||
.map_err(|e| WasmError::IOError(e.to_string()))?;
|
||||
|
||||
self.execute_wasm(&wasm_bytes)
|
||||
}
|
||||
|
||||
/// Execute WASM bytes
|
||||
pub fn execute_wasm(&self, wasm_bytes: &[u8]) -> Result<String, WasmError> {
|
||||
// Create store with host state
|
||||
let mut store = Store::new(&self.engine, HostState::new());
|
||||
|
||||
// Create module from bytes
|
||||
let module = Module::new(&self.engine, wasm_bytes)
|
||||
.map_err(|e| WasmError::CompilationError(format!("Failed to load WASM: {}", e)))?;
|
||||
|
||||
// Create host functions
|
||||
let host_functions = create_host_functions(&mut store)
|
||||
.map_err(|e| WasmError::RuntimeError(format!("Failed to create host functions: {}", e)))?;
|
||||
|
||||
// Create imports list
|
||||
let mut imports = Vec::new();
|
||||
for (module_name, func_name, func) in host_functions {
|
||||
imports.push(func);
|
||||
}
|
||||
|
||||
// Instantiate module with imports
|
||||
let instance = Instance::new(&mut store, &module, &imports)
|
||||
.map_err(|e| WasmError::RuntimeError(format!("Failed to instantiate module: {}", e)))?;
|
||||
|
||||
// Get main function
|
||||
let main_func = instance
|
||||
.get_func(&mut store, "main")
|
||||
.ok_or_else(|| WasmError::RuntimeError("No main function found".to_string()))?;
|
||||
|
||||
// Call main function
|
||||
let results = main_func
|
||||
.call(&mut store, &[], &mut [])
|
||||
.map_err(|e| WasmError::RuntimeError(format!("Failed to execute main: {}", e)))?;
|
||||
|
||||
// Return success message
|
||||
Ok("WASM execution completed successfully".to_string())
|
||||
}
|
||||
}
|
||||
201
src/backend/wasm/host.rs
Normal file
201
src/backend/wasm/host.rs
Normal file
@ -0,0 +1,201 @@
|
||||
/*!
|
||||
* WASM Host Functions - Implementation of host functions for WASM execution
|
||||
*
|
||||
* Phase 4-3c: Provides actual implementations for env::print and other imports
|
||||
* Enables WASM modules to interact with the host environment
|
||||
*/
|
||||
|
||||
use wasmtime::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Host state for WASM execution
|
||||
pub struct HostState {
|
||||
/// Output buffer for captured prints
|
||||
pub output: Arc<Mutex<String>>,
|
||||
}
|
||||
|
||||
impl HostState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
output: Arc::new(Mutex::new(String::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create host functions for WASM imports
|
||||
pub fn create_host_functions(store: &mut Store<HostState>) -> Result<Vec<(String, String, Extern)>, Error> {
|
||||
let mut imports = Vec::new();
|
||||
|
||||
// env::print - print a Box value (expecting a StringBox pointer)
|
||||
let print_func = Func::wrap(&mut *store, |mut caller: Caller<'_, HostState>, box_ptr: i32| {
|
||||
// Try to read StringBox content from WASM memory
|
||||
if let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) {
|
||||
let data = mem.data(&caller);
|
||||
let box_offset = box_ptr as usize;
|
||||
|
||||
// StringBox layout: [type_id:4][ref_count:4][field_count:4][data_ptr:4][length:4]
|
||||
if box_offset + 20 <= data.len() {
|
||||
// Read data pointer (offset 12)
|
||||
let data_ptr = i32::from_le_bytes([
|
||||
data[box_offset + 12],
|
||||
data[box_offset + 13],
|
||||
data[box_offset + 14],
|
||||
data[box_offset + 15],
|
||||
]);
|
||||
|
||||
// Read length (offset 16)
|
||||
let length = i32::from_le_bytes([
|
||||
data[box_offset + 16],
|
||||
data[box_offset + 17],
|
||||
data[box_offset + 18],
|
||||
data[box_offset + 19],
|
||||
]);
|
||||
|
||||
// Read actual string content
|
||||
let str_start = data_ptr as usize;
|
||||
let str_end = str_start + length as usize;
|
||||
|
||||
if str_end <= data.len() {
|
||||
if let Ok(s) = std::str::from_utf8(&data[str_start..str_end]) {
|
||||
println!("{}", s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: print as pointer
|
||||
println!("Box[{}]", box_ptr);
|
||||
});
|
||||
imports.push(("env".to_string(), "print".to_string(), Extern::Func(print_func)));
|
||||
|
||||
// env::print_str - print a string from memory (ptr, len)
|
||||
let print_str_func = Func::wrap(&mut *store, |mut caller: Caller<'_, HostState>, ptr: i32, len: i32| {
|
||||
if let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) {
|
||||
let data = mem.data(&caller);
|
||||
let start = ptr as usize;
|
||||
let end = start + len as usize;
|
||||
|
||||
if end <= data.len() {
|
||||
if let Ok(s) = std::str::from_utf8(&data[start..end]) {
|
||||
println!("{}", s);
|
||||
// Note: Output capture removed for simplicity
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
imports.push(("env".to_string(), "print_str".to_string(), Extern::Func(print_str_func)));
|
||||
|
||||
// env::console_log - console logging (similar to print_str)
|
||||
let console_log_func = Func::wrap(&mut *store, |mut caller: Caller<'_, HostState>, ptr: i32, len: i32| {
|
||||
if let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) {
|
||||
let data = mem.data(&caller);
|
||||
let start = ptr as usize;
|
||||
let end = start + len as usize;
|
||||
|
||||
if end <= data.len() {
|
||||
if let Ok(s) = std::str::from_utf8(&data[start..end]) {
|
||||
println!("[console.log] {}", s);
|
||||
// Note: Output capture removed for simplicity
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
imports.push(("env".to_string(), "console_log".to_string(), Extern::Func(console_log_func)));
|
||||
|
||||
// env::canvas_fillRect - stub implementation
|
||||
let canvas_fill_rect = Func::wrap(&mut *store, |_caller: Caller<'_, HostState>, _x: i32, _y: i32, _w: i32, _h: i32, _r: i32, _g: i32, _b: i32, _a: i32| {
|
||||
// Stub - in a real implementation, this would draw to a canvas
|
||||
});
|
||||
imports.push(("env".to_string(), "canvas_fillRect".to_string(), Extern::Func(canvas_fill_rect)));
|
||||
|
||||
// env::canvas_fillText - stub implementation
|
||||
let canvas_fill_text = Func::wrap(&mut *store, |_caller: Caller<'_, HostState>, _ptr: i32, _len: i32, _x: i32, _y: i32, _size: i32, _r: i32, _g: i32, _b: i32, _a: i32, _align: i32| {
|
||||
// Stub - in a real implementation, this would draw text
|
||||
});
|
||||
imports.push(("env".to_string(), "canvas_fillText".to_string(), Extern::Func(canvas_fill_text)));
|
||||
|
||||
// env::box_to_string - convert Box to string representation
|
||||
let box_to_string = Func::wrap(&mut *store, |_caller: Caller<'_, HostState>, box_ptr: i32| -> i32 {
|
||||
// For now, return the same pointer - in a real implementation,
|
||||
// this would convert the Box to its string representation
|
||||
box_ptr
|
||||
});
|
||||
imports.push(("env".to_string(), "box_to_string".to_string(), Extern::Func(box_to_string)));
|
||||
|
||||
// env::box_print - print a Box value
|
||||
let box_print = Func::wrap(&mut *store, |mut caller: Caller<'_, HostState>, box_ptr: i32| {
|
||||
// Read Box type from memory
|
||||
if let Some(mem) = caller.get_export("memory").and_then(|e| e.into_memory()) {
|
||||
let data = mem.data(&caller);
|
||||
let type_id_offset = box_ptr as usize;
|
||||
|
||||
if type_id_offset + 4 <= data.len() {
|
||||
let type_id = i32::from_le_bytes([
|
||||
data[type_id_offset],
|
||||
data[type_id_offset + 1],
|
||||
data[type_id_offset + 2],
|
||||
data[type_id_offset + 3],
|
||||
]);
|
||||
|
||||
match type_id {
|
||||
0x1001 => { // StringBox
|
||||
// Read string pointer and length from Box fields
|
||||
let str_ptr_offset = type_id_offset + 12;
|
||||
let str_len_offset = type_id_offset + 16;
|
||||
|
||||
if str_len_offset + 4 <= data.len() {
|
||||
let str_ptr = i32::from_le_bytes([
|
||||
data[str_ptr_offset],
|
||||
data[str_ptr_offset + 1],
|
||||
data[str_ptr_offset + 2],
|
||||
data[str_ptr_offset + 3],
|
||||
]);
|
||||
|
||||
let str_len = i32::from_le_bytes([
|
||||
data[str_len_offset],
|
||||
data[str_len_offset + 1],
|
||||
data[str_len_offset + 2],
|
||||
data[str_len_offset + 3],
|
||||
]);
|
||||
|
||||
// Read actual string content
|
||||
let str_start = str_ptr as usize;
|
||||
let str_end = str_start + str_len as usize;
|
||||
|
||||
if str_end <= data.len() {
|
||||
if let Ok(s) = std::str::from_utf8(&data[str_start..str_end]) {
|
||||
println!("{}", s);
|
||||
// Note: Output capture removed for simplicity
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
println!("Box[type=0x{:x}]", type_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Box[unknown]");
|
||||
});
|
||||
imports.push(("env".to_string(), "box_print".to_string(), Extern::Func(box_print)));
|
||||
|
||||
// env::box_equals - check Box equality
|
||||
let box_equals = Func::wrap(&mut *store, |_caller: Caller<'_, HostState>, _box1: i32, _box2: i32| -> i32 {
|
||||
// Stub - for now always return 0 (false)
|
||||
0
|
||||
});
|
||||
imports.push(("env".to_string(), "box_equals".to_string(), Extern::Func(box_equals)));
|
||||
|
||||
// env::box_clone - clone a Box
|
||||
let box_clone = Func::wrap(&mut *store, |_caller: Caller<'_, HostState>, box_ptr: i32| -> i32 {
|
||||
// Stub - for now return the same pointer
|
||||
box_ptr
|
||||
});
|
||||
imports.push(("env".to_string(), "box_clone".to_string(), Extern::Func(box_clone)));
|
||||
|
||||
Ok(imports)
|
||||
}
|
||||
@ -8,10 +8,13 @@
|
||||
mod codegen;
|
||||
mod memory;
|
||||
mod runtime;
|
||||
mod host;
|
||||
mod executor;
|
||||
|
||||
pub use codegen::{WasmCodegen, WasmModule};
|
||||
pub use memory::{MemoryManager, BoxLayout};
|
||||
pub use runtime::RuntimeImports;
|
||||
pub use executor::WasmExecutor;
|
||||
|
||||
use crate::mir::MirModule;
|
||||
|
||||
@ -23,6 +26,8 @@ pub enum WasmError {
|
||||
UnsupportedInstruction(String),
|
||||
WasmValidationError(String),
|
||||
IOError(String),
|
||||
RuntimeError(String),
|
||||
CompilationError(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WasmError {
|
||||
@ -33,6 +38,8 @@ impl std::fmt::Display for WasmError {
|
||||
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),
|
||||
WasmError::RuntimeError(msg) => write!(f, "Runtime error: {}", msg),
|
||||
WasmError::CompilationError(msg) => write!(f, "Compilation error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user