Merge pull request #52 from moe-charm/copilot/fix-51

Phase 8: MIR→WASM Codegen - Complete foundation and basic arithmetic operations
This commit is contained in:
moe-charm
2025-08-14 06:00:31 +09:00
committed by GitHub
7 changed files with 1248 additions and 1 deletions

View File

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

View File

@ -3,5 +3,7 @@
*/
pub mod vm;
pub mod wasm;
pub use vm::{VM, VMError, VMValue};
pub use vm::{VM, VMError, VMValue};
pub use wasm::{WasmBackend, WasmError};

391
src/backend/wasm/codegen.rs Normal file
View File

@ -0,0 +1,391 @@
/*!
* 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<String>,
pub memory: String,
pub globals: Vec<String>,
pub functions: Vec<String>,
pub exports: Vec<String>,
}
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 imports first (must come before other definitions in WASM)
for import in &self.imports {
wat.push_str(&format!(" {}\n", import));
}
// Add memory declaration
if !self.memory.is_empty() {
wat.push_str(&format!(" {}\n", self.memory));
}
// 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<ValueId, u32>,
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<WasmModule, WasmError> {
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<String, WasmError> {
// 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 {
// Declare individual local variables for each ValueId
for i in 0..local_count {
function_body.push_str(&format!(" (local ${} i32)", i));
}
}
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<u32, WasmError> {
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<Vec<String>, 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();
// Process regular instructions
for mir_instruction in &block.instructions {
let wasm_instructions = self.generate_instruction(mir_instruction)?;
instructions.extend(wasm_instructions);
}
// Process terminator instruction
if let Some(ref terminator) = block.terminator {
let wasm_instructions = self.generate_instruction(terminator)?;
instructions.extend(wasm_instructions);
}
Ok(instructions)
}
/// Generate WASM instructions for a single MIR instruction
fn generate_instruction(&self, instruction: &MirInstruction) -> Result<Vec<String>, 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<Vec<String>, 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<Vec<String>, 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<Vec<String>, 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<Vec<String>, 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<Vec<String>, 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<u32, WasmError> {
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
}
}

252
src/backend/wasm/memory.rs Normal file
View File

@ -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<String, u32>,
}
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::<u32>() % 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<u32> {
self.field_offsets.get(field_name).copied()
}
}
/// WASM memory manager
pub struct MemoryManager {
/// Known Box layouts
box_layouts: HashMap<String, BoxLayout>,
/// 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<String>) {
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<String> {
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<String, WasmError> {
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<String, WasmError> {
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<String, WasmError> {
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"));
}
}

131
src/backend/wasm/mod.rs Normal file
View File

@ -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<Vec<u8>, 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<String, WasmError> {
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<i32, WasmError> {
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());
}
}

221
src/backend/wasm/runtime.rs Normal file
View File

@ -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<ImportFunction>,
}
/// External function import definition
#[derive(Debug, Clone)]
pub struct ImportFunction {
pub module: String,
pub name: String,
pub params: Vec<String>,
pub result: Option<String>,
}
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<String> {
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<String>, result: Option<String>) {
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<String, Vec<&ImportFunction>> = 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<String, WasmError> {
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!"));
}
}

View File

@ -0,0 +1,246 @@
/*!
* Phase 8.2 PoC1 Integration Test - Basic WASM Arithmetic Operations
*
* Tests end-to-end MIR→WASM compilation and execution for:
* - Constant loading
* - Binary arithmetic (addition, subtraction, multiplication)
* - Print output
* - Return values
*/
use nyash_rust::mir::{
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue, BinaryOp
};
use nyash_rust::backend::wasm::WasmBackend;
#[test]
fn test_wasm_poc1_basic_arithmetic() {
// Build MIR equivalent to:
// function main() {
// %a = const 42
// %b = const 8
// %result = %a + %b
// print %result
// return %result
// }
let mut backend = WasmBackend::new();
let mir_module = build_arithmetic_mir_module();
// Generate WAT text for debugging
let wat_result = backend.compile_to_wat(mir_module.clone());
assert!(wat_result.is_ok(), "WAT generation should succeed");
let wat_text = wat_result.unwrap();
// Verify WAT contains expected elements
assert!(wat_text.contains("(module"), "Should contain module declaration");
assert!(wat_text.contains("memory"), "Should contain memory declaration");
assert!(wat_text.contains("import"), "Should contain imports");
assert!(wat_text.contains("$main"), "Should contain main function");
assert!(wat_text.contains("i32.const 42"), "Should contain constant 42");
assert!(wat_text.contains("i32.const 8"), "Should contain constant 8");
assert!(wat_text.contains("i32.add"), "Should contain addition operation");
assert!(wat_text.contains("call $print"), "Should contain print call");
assert!(wat_text.contains("return"), "Should contain return instruction");
// Compile to WASM binary and execute
let wasm_result = backend.compile_module(mir_module);
if let Err(e) = &wasm_result {
println!("WASM compilation error: {}", e);
}
assert!(wasm_result.is_ok(), "WASM compilation should succeed");
let wasm_bytes = wasm_result.unwrap();
assert!(!wasm_bytes.is_empty(), "WASM bytes should not be empty");
// Execute with wasmtime
let execution_result = backend.execute_wasm(&wasm_bytes);
assert!(execution_result.is_ok(), "WASM execution should succeed");
let return_value = execution_result.unwrap();
assert_eq!(return_value, 50, "Should return 42 + 8 = 50");
}
#[test]
fn test_wasm_poc1_multiplication() {
// Test: 6 * 7 = 42
let mut backend = WasmBackend::new();
let mir_module = build_multiplication_mir_module();
let wasm_result = backend.compile_module(mir_module);
assert!(wasm_result.is_ok(), "WASM compilation should succeed");
let return_value = backend.execute_wasm(&wasm_result.unwrap()).unwrap();
assert_eq!(return_value, 42, "Should return 6 * 7 = 42");
}
#[test]
fn test_wasm_poc1_subtraction() {
// Test: 50 - 8 = 42
let mut backend = WasmBackend::new();
let mir_module = build_subtraction_mir_module();
let wasm_result = backend.compile_module(mir_module);
assert!(wasm_result.is_ok(), "WASM compilation should succeed");
let return_value = backend.execute_wasm(&wasm_result.unwrap()).unwrap();
assert_eq!(return_value, 42, "Should return 50 - 8 = 42");
}
/// Build MIR module for: 42 + 8
fn build_arithmetic_mir_module() -> MirModule {
let mut module = MirModule::new("test_arithmetic".to_string());
// Create main function signature
let main_signature = FunctionSignature {
name: "main".to_string(),
params: vec![],
return_type: MirType::Integer,
effects: EffectMask::PURE,
};
// Create entry block
let entry_block = BasicBlockId::new(0);
let mut main_function = MirFunction::new(main_signature, entry_block);
// Create basic block
let mut block = BasicBlock::new(entry_block);
// Generate value IDs
let val_a = ValueId::new(0); // 42
let val_b = ValueId::new(1); // 8
let result = ValueId::new(2); // 42 + 8
// Add instructions
block.add_instruction(MirInstruction::Const {
dst: val_a,
value: ConstValue::Integer(42),
});
block.add_instruction(MirInstruction::Const {
dst: val_b,
value: ConstValue::Integer(8),
});
block.add_instruction(MirInstruction::BinOp {
dst: result,
op: BinaryOp::Add,
lhs: val_a,
rhs: val_b,
});
block.add_instruction(MirInstruction::Print {
value: result,
effects: EffectMask::IO,
});
// Set terminator instruction (Return must be a terminator, not regular instruction)
block.set_terminator(MirInstruction::Return {
value: Some(result),
});
// Debug: Print number of instructions and terminator
println!("Instructions: {}, Has terminator: {}", block.instructions.len(), block.terminator.is_some());
// Add block to function
main_function.add_block(block);
// Add function to module
module.add_function(main_function);
module
}
/// Build MIR module for: 6 * 7
fn build_multiplication_mir_module() -> MirModule {
let mut module = MirModule::new("test_multiplication".to_string());
let main_signature = FunctionSignature {
name: "main".to_string(),
params: vec![],
return_type: MirType::Integer,
effects: EffectMask::PURE,
};
let entry_block = BasicBlockId::new(0);
let mut main_function = MirFunction::new(main_signature, entry_block);
let mut block = BasicBlock::new(entry_block);
let val_a = ValueId::new(0); // 6
let val_b = ValueId::new(1); // 7
let result = ValueId::new(2); // 6 * 7
block.add_instruction(MirInstruction::Const {
dst: val_a,
value: ConstValue::Integer(6),
});
block.add_instruction(MirInstruction::Const {
dst: val_b,
value: ConstValue::Integer(7),
});
block.add_instruction(MirInstruction::BinOp {
dst: result,
op: BinaryOp::Mul,
lhs: val_a,
rhs: val_b,
});
block.set_terminator(MirInstruction::Return {
value: Some(result),
});
main_function.add_block(block);
module.add_function(main_function);
module
}
/// Build MIR module for: 50 - 8
fn build_subtraction_mir_module() -> MirModule {
let mut module = MirModule::new("test_subtraction".to_string());
let main_signature = FunctionSignature {
name: "main".to_string(),
params: vec![],
return_type: MirType::Integer,
effects: EffectMask::PURE,
};
let entry_block = BasicBlockId::new(0);
let mut main_function = MirFunction::new(main_signature, entry_block);
let mut block = BasicBlock::new(entry_block);
let val_a = ValueId::new(0); // 50
let val_b = ValueId::new(1); // 8
let result = ValueId::new(2); // 50 - 8
block.add_instruction(MirInstruction::Const {
dst: val_a,
value: ConstValue::Integer(50),
});
block.add_instruction(MirInstruction::Const {
dst: val_b,
value: ConstValue::Integer(8),
});
block.add_instruction(MirInstruction::BinOp {
dst: result,
op: BinaryOp::Sub,
lhs: val_a,
rhs: val_b,
});
block.set_terminator(MirInstruction::Return {
value: Some(result),
});
main_function.add_block(block);
module.add_function(main_function);
module
}