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:
Moe Charm
2025-08-17 13:49:35 +09:00
parent bb3f2e8032
commit 3df87fb1ce
41 changed files with 4444 additions and 68 deletions

View File

@ -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();

View File

@ -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)
},

View File

@ -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)]

View 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
View 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)
}

View File

@ -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),
}
}
}