Phase 8.1 Complete: WASM backend foundation implementation
Co-authored-by: moe-charm <217100418+moe-charm@users.noreply.github.com>
This commit is contained in:
@ -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 }
|
||||
|
||||
@ -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};
|
||||
381
src/backend/wasm/codegen.rs
Normal file
381
src/backend/wasm/codegen.rs
Normal file
@ -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<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 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<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 {
|
||||
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<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();
|
||||
|
||||
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<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
252
src/backend/wasm/memory.rs
Normal 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
131
src/backend/wasm/mod.rs
Normal 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
221
src/backend/wasm/runtime.rs
Normal 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!"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user