Complete Phase 8.3 WASM Box Operations - Everything is Box in WASM
Co-authored-by: moe-charm <217100418+moe-charm@users.noreply.github.com>
This commit is contained in:
1
simple_test.nyash
Normal file
1
simple_test.nyash
Normal file
@ -0,0 +1 @@
|
|||||||
|
static box Main { main() { return 42 } }
|
||||||
@ -97,6 +97,17 @@ impl WasmCodegen {
|
|||||||
// Add globals (heap pointer)
|
// Add globals (heap pointer)
|
||||||
wasm_module.globals.extend(memory_manager.get_globals());
|
wasm_module.globals.extend(memory_manager.get_globals());
|
||||||
|
|
||||||
|
// Add memory management functions
|
||||||
|
wasm_module.functions.push(memory_manager.get_malloc_function());
|
||||||
|
wasm_module.functions.push(memory_manager.get_generic_box_alloc_function());
|
||||||
|
|
||||||
|
// Add Box-specific allocation functions for known types
|
||||||
|
for box_type in ["StringBox", "IntegerBox", "BoolBox", "DataBox"] {
|
||||||
|
if let Ok(alloc_func) = memory_manager.get_box_alloc_function(box_type) {
|
||||||
|
wasm_module.functions.push(alloc_func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate functions
|
// Generate functions
|
||||||
for (name, function) in &mir_module.functions {
|
for (name, function) in &mir_module.functions {
|
||||||
let wasm_function = self.generate_function(name, function.clone())?;
|
let wasm_function = self.generate_function(name, function.clone())?;
|
||||||
@ -222,26 +233,78 @@ impl WasmCodegen {
|
|||||||
self.generate_print(*value)
|
self.generate_print(*value)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Phase 8.3 PoC2: Reference operations (stub for now)
|
// Phase 8.3 PoC2: Reference operations
|
||||||
MirInstruction::RefNew { dst, box_val } => {
|
MirInstruction::RefNew { dst, box_val } => {
|
||||||
// For now, just copy the value (TODO: implement heap allocation)
|
// Create a new reference to a Box by copying the Box value
|
||||||
|
// This assumes box_val contains a Box pointer already
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
format!("local.get ${}", self.get_local_index(*box_val)?),
|
format!("local.get ${}", self.get_local_index(*box_val)?),
|
||||||
format!("local.set ${}", self.get_local_index(*dst)?),
|
format!("local.set ${}", self.get_local_index(*dst)?),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
MirInstruction::RefGet { dst, reference, field: _ } => {
|
MirInstruction::RefGet { dst, reference, field } => {
|
||||||
// For now, just copy the reference (TODO: implement field access)
|
// Load field value from Box through reference
|
||||||
|
// reference contains Box pointer, field is the field name
|
||||||
|
// For now, assume all fields are at offset 12 (first field after header)
|
||||||
|
// TODO: Add proper field offset calculation
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
format!("local.get ${}", self.get_local_index(*reference)?),
|
format!("local.get ${}", self.get_local_index(*reference)?),
|
||||||
|
"i32.const 12".to_string(), // Offset: header (12 bytes) + first field
|
||||||
|
"i32.add".to_string(),
|
||||||
|
"i32.load".to_string(),
|
||||||
format!("local.set ${}", self.get_local_index(*dst)?),
|
format!("local.set ${}", self.get_local_index(*dst)?),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
MirInstruction::RefSet { reference: _, field: _, value: _ } => {
|
MirInstruction::RefSet { reference, field, value } => {
|
||||||
// For now, no-op (TODO: implement field assignment)
|
// Store field value to Box through reference
|
||||||
Ok(vec!["nop".to_string()])
|
// reference contains Box pointer, field is the field name, value is new value
|
||||||
|
// For now, assume all fields are at offset 12 (first field after header)
|
||||||
|
// TODO: Add proper field offset calculation
|
||||||
|
Ok(vec![
|
||||||
|
format!("local.get ${}", self.get_local_index(*reference)?),
|
||||||
|
"i32.const 12".to_string(), // Offset: header (12 bytes) + first field
|
||||||
|
"i32.add".to_string(),
|
||||||
|
format!("local.get ${}", self.get_local_index(*value)?),
|
||||||
|
"i32.store".to_string(),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
|
||||||
|
MirInstruction::NewBox { dst, box_type, args } => {
|
||||||
|
// Create a new Box using the generic allocator
|
||||||
|
match box_type.as_str() {
|
||||||
|
"DataBox" => {
|
||||||
|
// Use specific allocator for known types
|
||||||
|
let mut instructions = vec![
|
||||||
|
"call $alloc_databox".to_string(),
|
||||||
|
format!("local.set ${}", self.get_local_index(*dst)?),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Initialize fields with arguments if provided
|
||||||
|
for (i, arg) in args.iter().enumerate() {
|
||||||
|
instructions.extend(vec![
|
||||||
|
format!("local.get ${}", self.get_local_index(*dst)?),
|
||||||
|
format!("i32.const {}", 12 + i * 4), // Field offset
|
||||||
|
"i32.add".to_string(),
|
||||||
|
format!("local.get ${}", self.get_local_index(*arg)?),
|
||||||
|
"i32.store".to_string(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(instructions)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// Use generic allocator for unknown types
|
||||||
|
// This is a fallback - in a real implementation, all Box types should be known
|
||||||
|
Ok(vec![
|
||||||
|
"i32.const 8192".to_string(), // Default unknown type ID
|
||||||
|
format!("i32.const {}", args.len()),
|
||||||
|
"call $box_alloc".to_string(),
|
||||||
|
format!("local.set ${}", self.get_local_index(*dst)?),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Phase 8.4 PoC3: Extension stubs
|
// Phase 8.4 PoC3: Extension stubs
|
||||||
@ -265,7 +328,8 @@ impl WasmCodegen {
|
|||||||
|
|
||||||
MirInstruction::BarrierRead { .. } |
|
MirInstruction::BarrierRead { .. } |
|
||||||
MirInstruction::BarrierWrite { .. } |
|
MirInstruction::BarrierWrite { .. } |
|
||||||
MirInstruction::FutureSet { .. } => {
|
MirInstruction::FutureSet { .. } |
|
||||||
|
MirInstruction::Safepoint => {
|
||||||
// No-op for now
|
// No-op for now
|
||||||
Ok(vec!["nop".to_string()])
|
Ok(vec!["nop".to_string()])
|
||||||
},
|
},
|
||||||
|
|||||||
@ -18,12 +18,22 @@ pub struct BoxLayout {
|
|||||||
|
|
||||||
impl BoxLayout {
|
impl BoxLayout {
|
||||||
pub fn new(type_name: &str) -> Self {
|
pub fn new(type_name: &str) -> Self {
|
||||||
// Simple type ID generation (hash of name for now)
|
// Assign consistent type IDs for standard Box types
|
||||||
let type_id = type_name.chars().map(|c| c as u32).sum::<u32>() % 65536;
|
let type_id = match type_name {
|
||||||
|
"StringBox" => 0x1001,
|
||||||
|
"IntegerBox" => 0x1002,
|
||||||
|
"BoolBox" => 0x1003,
|
||||||
|
"ArrayBox" => 0x1004,
|
||||||
|
"DataBox" => 0x1005, // For testing
|
||||||
|
_ => {
|
||||||
|
// Generate ID from hash for custom types
|
||||||
|
type_name.chars().map(|c| c as u32).sum::<u32>() % 65536 + 0x2000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
type_id,
|
type_id,
|
||||||
size: 8, // Minimum size: type_id + field_count
|
size: 12, // Header: type_id + ref_count + field_count
|
||||||
field_offsets: HashMap::new(),
|
field_offsets: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,10 +59,29 @@ pub struct MemoryManager {
|
|||||||
|
|
||||||
impl MemoryManager {
|
impl MemoryManager {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
let mut manager = Self {
|
||||||
box_layouts: HashMap::new(),
|
box_layouts: HashMap::new(),
|
||||||
heap_start: 0x800, // 2KB reserved for stack/globals
|
heap_start: 0x800, // 2KB reserved for stack/globals
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Register standard Box types
|
||||||
|
manager.register_standard_box_types();
|
||||||
|
manager
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register standard built-in Box types
|
||||||
|
fn register_standard_box_types(&mut self) {
|
||||||
|
// StringBox: [type_id][ref_count][field_count][ptr_to_chars][length]
|
||||||
|
self.register_box_type("StringBox".to_string(), vec!["data_ptr".to_string(), "length".to_string()]);
|
||||||
|
|
||||||
|
// IntegerBox: [type_id][ref_count][field_count][value]
|
||||||
|
self.register_box_type("IntegerBox".to_string(), vec!["value".to_string()]);
|
||||||
|
|
||||||
|
// BoolBox: [type_id][ref_count][field_count][value]
|
||||||
|
self.register_box_type("BoolBox".to_string(), vec!["value".to_string()]);
|
||||||
|
|
||||||
|
// DataBox: [type_id][ref_count][field_count][value] - for testing
|
||||||
|
self.register_box_type("DataBox".to_string(), vec!["value".to_string()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a Box type layout
|
/// Register a Box type layout
|
||||||
@ -78,19 +107,28 @@ impl MemoryManager {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate heap allocation function
|
/// Generate heap allocation function with 4-byte alignment
|
||||||
pub fn get_malloc_function(&self) -> String {
|
pub fn get_malloc_function(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
r#"(func $malloc (param $size i32) (result i32)
|
r#"(func $malloc (param $size i32) (result i32)
|
||||||
(local $ptr i32)
|
(local $ptr i32)
|
||||||
|
(local $aligned_size i32)
|
||||||
|
|
||||||
|
;; Align size to 4-byte boundary
|
||||||
|
local.get $size
|
||||||
|
i32.const 3
|
||||||
|
i32.add
|
||||||
|
i32.const -4
|
||||||
|
i32.and
|
||||||
|
local.set $aligned_size
|
||||||
|
|
||||||
;; Get current heap pointer
|
;; Get current heap pointer
|
||||||
global.get $heap_ptr
|
global.get $heap_ptr
|
||||||
local.set $ptr
|
local.set $ptr
|
||||||
|
|
||||||
;; Advance heap pointer
|
;; Advance heap pointer by aligned size
|
||||||
global.get $heap_ptr
|
global.get $heap_ptr
|
||||||
local.get $size
|
local.get $aligned_size
|
||||||
i32.add
|
i32.add
|
||||||
global.set $heap_ptr
|
global.set $heap_ptr
|
||||||
|
|
||||||
@ -119,10 +157,17 @@ impl MemoryManager {
|
|||||||
i32.const {}
|
i32.const {}
|
||||||
i32.store
|
i32.store
|
||||||
|
|
||||||
;; Initialize field_count
|
;; Initialize ref_count to 1
|
||||||
local.get $ptr
|
local.get $ptr
|
||||||
i32.const 4
|
i32.const 4
|
||||||
i32.add
|
i32.add
|
||||||
|
i32.const 1
|
||||||
|
i32.store
|
||||||
|
|
||||||
|
;; Initialize field_count
|
||||||
|
local.get $ptr
|
||||||
|
i32.const 8
|
||||||
|
i32.add
|
||||||
i32.const {}
|
i32.const {}
|
||||||
i32.store
|
i32.store
|
||||||
|
|
||||||
@ -146,6 +191,17 @@ impl MemoryManager {
|
|||||||
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
r#"(func $get_{}_{} (param $box_ptr i32) (result i32)
|
r#"(func $get_{}_{} (param $box_ptr i32) (result i32)
|
||||||
|
;; Verify type_id (optional safety check)
|
||||||
|
local.get $box_ptr
|
||||||
|
i32.load
|
||||||
|
i32.const {}
|
||||||
|
i32.ne
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
;; Load field value
|
||||||
local.get $box_ptr
|
local.get $box_ptr
|
||||||
i32.const {}
|
i32.const {}
|
||||||
i32.add
|
i32.add
|
||||||
@ -153,6 +209,7 @@ impl MemoryManager {
|
|||||||
)"#,
|
)"#,
|
||||||
type_name.to_lowercase(),
|
type_name.to_lowercase(),
|
||||||
field_name,
|
field_name,
|
||||||
|
layout.type_id,
|
||||||
offset
|
offset
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -167,6 +224,16 @@ impl MemoryManager {
|
|||||||
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
r#"(func $set_{}_{} (param $box_ptr i32) (param $value i32)
|
r#"(func $set_{}_{} (param $box_ptr i32) (param $value i32)
|
||||||
|
;; Verify type_id (optional safety check)
|
||||||
|
local.get $box_ptr
|
||||||
|
i32.load
|
||||||
|
i32.const {}
|
||||||
|
i32.ne
|
||||||
|
if
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
;; Store field value
|
||||||
local.get $box_ptr
|
local.get $box_ptr
|
||||||
i32.const {}
|
i32.const {}
|
||||||
i32.add
|
i32.add
|
||||||
@ -175,6 +242,7 @@ impl MemoryManager {
|
|||||||
)"#,
|
)"#,
|
||||||
type_name.to_lowercase(),
|
type_name.to_lowercase(),
|
||||||
field_name,
|
field_name,
|
||||||
|
layout.type_id,
|
||||||
offset
|
offset
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -188,13 +256,68 @@ impl MemoryManager {
|
|||||||
;; 0x400-0x7FF: Stack space (1KB)
|
;; 0x400-0x7FF: Stack space (1KB)
|
||||||
;; 0x800+: Heap (bump allocator)
|
;; 0x800+: Heap (bump allocator)
|
||||||
;;
|
;;
|
||||||
;; Box Layout: [type_id:i32][field_count:i32][field0:i32][field1:i32]...
|
;; Box Layout: [type_id:i32][ref_count:i32][field_count:i32][field0:i32][field1:i32]...
|
||||||
;;
|
;;
|
||||||
|
;; Standard Type IDs:
|
||||||
|
;; StringBox: 0x1001, IntegerBox: 0x1002, BoolBox: 0x1003
|
||||||
|
;; ArrayBox: 0x1004, DataBox: 0x1005
|
||||||
|
;; Custom: 0x2000+
|
||||||
|
;;
|
||||||
;; Heap start: 0x{:x}
|
;; Heap start: 0x{:x}
|
||||||
"#,
|
"#,
|
||||||
self.heap_start
|
self.heap_start
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get type ID for a Box type
|
||||||
|
pub fn get_type_id(&self, type_name: &str) -> Option<u32> {
|
||||||
|
self.box_layouts.get(type_name).map(|layout| layout.type_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate generic Box creation helper
|
||||||
|
pub fn get_generic_box_alloc_function(&self) -> String {
|
||||||
|
format!(
|
||||||
|
r#"(func $box_alloc (param $type_id i32) (param $field_count i32) (result i32)
|
||||||
|
(local $ptr i32)
|
||||||
|
(local $total_size i32)
|
||||||
|
|
||||||
|
;; Calculate total size: header (12) + fields (field_count * 4)
|
||||||
|
local.get $field_count
|
||||||
|
i32.const 4
|
||||||
|
i32.mul
|
||||||
|
i32.const 12
|
||||||
|
i32.add
|
||||||
|
local.set $total_size
|
||||||
|
|
||||||
|
;; Allocate memory
|
||||||
|
local.get $total_size
|
||||||
|
call $malloc
|
||||||
|
local.set $ptr
|
||||||
|
|
||||||
|
;; Initialize type_id
|
||||||
|
local.get $ptr
|
||||||
|
local.get $type_id
|
||||||
|
i32.store
|
||||||
|
|
||||||
|
;; Initialize ref_count to 1
|
||||||
|
local.get $ptr
|
||||||
|
i32.const 4
|
||||||
|
i32.add
|
||||||
|
i32.const 1
|
||||||
|
i32.store
|
||||||
|
|
||||||
|
;; Initialize field_count
|
||||||
|
local.get $ptr
|
||||||
|
i32.const 8
|
||||||
|
i32.add
|
||||||
|
local.get $field_count
|
||||||
|
i32.store
|
||||||
|
|
||||||
|
;; Return box pointer
|
||||||
|
local.get $ptr
|
||||||
|
)"#
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -203,31 +326,49 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_box_layout_creation() {
|
fn test_box_layout_creation() {
|
||||||
let layout = BoxLayout::new("TestBox");
|
let layout = BoxLayout::new("DataBox");
|
||||||
assert_eq!(layout.size, 8); // type_id + field_count
|
assert_eq!(layout.size, 12); // type_id + ref_count + field_count
|
||||||
|
assert_eq!(layout.type_id, 0x1005); // DataBox has specific ID
|
||||||
assert!(layout.field_offsets.is_empty());
|
assert!(layout.field_offsets.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_box_layout_field_addition() {
|
fn test_box_layout_field_addition() {
|
||||||
let mut layout = BoxLayout::new("TestBox");
|
let mut layout = BoxLayout::new("DataBox");
|
||||||
layout.add_field("field1".to_string());
|
layout.add_field("field1".to_string());
|
||||||
layout.add_field("field2".to_string());
|
layout.add_field("field2".to_string());
|
||||||
|
|
||||||
assert_eq!(layout.size, 16); // 8 + 4 + 4
|
assert_eq!(layout.size, 20); // 12 + 4 + 4
|
||||||
assert_eq!(layout.get_field_offset("field1"), Some(8));
|
assert_eq!(layout.get_field_offset("field1"), Some(12));
|
||||||
assert_eq!(layout.get_field_offset("field2"), Some(12));
|
assert_eq!(layout.get_field_offset("field2"), Some(16));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory_manager_standard_types() {
|
||||||
|
let manager = MemoryManager::new();
|
||||||
|
|
||||||
|
// Verify standard types are registered
|
||||||
|
assert!(manager.get_box_layout("StringBox").is_some());
|
||||||
|
assert!(manager.get_box_layout("IntegerBox").is_some());
|
||||||
|
assert!(manager.get_box_layout("BoolBox").is_some());
|
||||||
|
assert!(manager.get_box_layout("DataBox").is_some());
|
||||||
|
|
||||||
|
// Verify type IDs
|
||||||
|
assert_eq!(manager.get_type_id("StringBox"), Some(0x1001));
|
||||||
|
assert_eq!(manager.get_type_id("IntegerBox"), Some(0x1002));
|
||||||
|
assert_eq!(manager.get_type_id("DataBox"), Some(0x1005));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_memory_manager_registration() {
|
fn test_memory_manager_registration() {
|
||||||
let mut manager = MemoryManager::new();
|
let mut manager = MemoryManager::new();
|
||||||
manager.register_box_type("DataBox".to_string(), vec!["x".to_string(), "y".to_string()]);
|
manager.register_box_type("CustomBox".to_string(), vec!["x".to_string(), "y".to_string()]);
|
||||||
|
|
||||||
let layout = manager.get_box_layout("DataBox").unwrap();
|
let layout = manager.get_box_layout("CustomBox").unwrap();
|
||||||
assert_eq!(layout.field_offsets.len(), 2);
|
assert_eq!(layout.field_offsets.len(), 2);
|
||||||
assert!(layout.get_field_offset("x").is_some());
|
assert!(layout.get_field_offset("x").is_some());
|
||||||
assert!(layout.get_field_offset("y").is_some());
|
assert!(layout.get_field_offset("y").is_some());
|
||||||
|
assert!(layout.type_id >= 0x2000); // Custom types start at 0x2000
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -238,15 +379,28 @@ mod tests {
|
|||||||
assert!(malloc_func.contains("$malloc"));
|
assert!(malloc_func.contains("$malloc"));
|
||||||
assert!(malloc_func.contains("$heap_ptr"));
|
assert!(malloc_func.contains("$heap_ptr"));
|
||||||
assert!(malloc_func.contains("global.get"));
|
assert!(malloc_func.contains("global.get"));
|
||||||
|
assert!(malloc_func.contains("i32.and")); // Alignment check
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_box_alloc_function_generation() {
|
fn test_box_alloc_function_generation() {
|
||||||
let mut manager = MemoryManager::new();
|
let manager = MemoryManager::new();
|
||||||
manager.register_box_type("TestBox".to_string(), vec!["value".to_string()]);
|
let alloc_func = manager.get_box_alloc_function("DataBox").unwrap();
|
||||||
|
|
||||||
let alloc_func = manager.get_box_alloc_function("TestBox").unwrap();
|
assert!(alloc_func.contains("$alloc_databox"));
|
||||||
assert!(alloc_func.contains("$alloc_testbox"));
|
|
||||||
assert!(alloc_func.contains("call $malloc"));
|
assert!(alloc_func.contains("call $malloc"));
|
||||||
|
assert!(alloc_func.contains("4101")); // 0x1005 type ID for DataBox
|
||||||
|
assert!(alloc_func.contains("i32.const 1")); // ref_count initialization
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generic_box_alloc_function() {
|
||||||
|
let manager = MemoryManager::new();
|
||||||
|
let generic_func = manager.get_generic_box_alloc_function();
|
||||||
|
|
||||||
|
assert!(generic_func.contains("$box_alloc"));
|
||||||
|
assert!(generic_func.contains("$type_id"));
|
||||||
|
assert!(generic_func.contains("$field_count"));
|
||||||
|
assert!(generic_func.contains("i32.const 12")); // Header size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
64
src/main.rs
64
src/main.rs
@ -34,7 +34,7 @@ use mir::{MirCompiler, MirPrinter};
|
|||||||
|
|
||||||
// 🚀 Backend Infrastructure
|
// 🚀 Backend Infrastructure
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
use backend::VM;
|
use backend::{VM, wasm::WasmBackend};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::process;
|
use std::process;
|
||||||
@ -84,6 +84,12 @@ fn main() {
|
|||||||
.help("Choose execution backend: 'interpreter' (default) or 'vm'")
|
.help("Choose execution backend: 'interpreter' (default) or 'vm'")
|
||||||
.default_value("interpreter")
|
.default_value("interpreter")
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("compile-wasm")
|
||||||
|
.long("compile-wasm")
|
||||||
|
.help("Compile to WebAssembly (WAT format) instead of executing")
|
||||||
|
.action(clap::ArgAction::SetTrue)
|
||||||
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
// デバッグ燃料の解析
|
// デバッグ燃料の解析
|
||||||
@ -93,6 +99,7 @@ fn main() {
|
|||||||
let dump_mir = matches.get_flag("dump-mir");
|
let dump_mir = matches.get_flag("dump-mir");
|
||||||
let verify_mir = matches.get_flag("verify");
|
let verify_mir = matches.get_flag("verify");
|
||||||
let mir_verbose = matches.get_flag("mir-verbose");
|
let mir_verbose = matches.get_flag("mir-verbose");
|
||||||
|
let compile_wasm = matches.get_flag("compile-wasm");
|
||||||
let backend = matches.get_one::<String>("backend").unwrap();
|
let backend = matches.get_one::<String>("backend").unwrap();
|
||||||
|
|
||||||
if let Some(filename) = matches.get_one::<String>("file") {
|
if let Some(filename) = matches.get_one::<String>("file") {
|
||||||
@ -100,6 +107,9 @@ fn main() {
|
|||||||
if dump_mir || verify_mir {
|
if dump_mir || verify_mir {
|
||||||
println!("🚀 Nyash MIR Compiler - Processing file: {} 🚀", filename);
|
println!("🚀 Nyash MIR Compiler - Processing file: {} 🚀", filename);
|
||||||
execute_mir_mode(filename, dump_mir, verify_mir, mir_verbose);
|
execute_mir_mode(filename, dump_mir, verify_mir, mir_verbose);
|
||||||
|
} else if compile_wasm {
|
||||||
|
println!("🌐 Nyash WASM Compiler - Compiling file: {} 🌐", filename);
|
||||||
|
execute_wasm_mode(filename);
|
||||||
} else if backend == "vm" {
|
} else if backend == "vm" {
|
||||||
println!("🚀 Nyash VM Backend - Executing file: {} 🚀", filename);
|
println!("🚀 Nyash VM Backend - Executing file: {} 🚀", filename);
|
||||||
execute_vm_mode(filename);
|
execute_vm_mode(filename);
|
||||||
@ -1238,6 +1248,58 @@ fn execute_vm_mode(filename: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn execute_wasm_mode(filename: &str) {
|
||||||
|
// Read the source file
|
||||||
|
let source = match fs::read_to_string(filename) {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ Error reading file '{}': {}", filename, e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse to AST
|
||||||
|
let ast = match NyashParser::parse_from_string(&source) {
|
||||||
|
Ok(ast) => ast,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ Parse error: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compile to MIR
|
||||||
|
let mut compiler = MirCompiler::new();
|
||||||
|
let compile_result = match compiler.compile(ast) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ MIR compilation error: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for verification errors
|
||||||
|
if let Err(errors) = &compile_result.verification_result {
|
||||||
|
eprintln!("❌ MIR verification failed with {} error(s):", errors.len());
|
||||||
|
for (i, error) in errors.iter().enumerate() {
|
||||||
|
eprintln!(" {}: {}", i + 1, error);
|
||||||
|
}
|
||||||
|
// Continue compilation anyway for now
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile to WASM
|
||||||
|
let mut wasm_backend = WasmBackend::new();
|
||||||
|
match wasm_backend.compile_to_wat(compile_result.module) {
|
||||||
|
Ok(wat_text) => {
|
||||||
|
println!("✅ WASM compilation completed successfully!");
|
||||||
|
println!("Generated WAT:\n{}", wat_text);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ WASM compilation error: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
334
tests/wasm_poc2_box_operations.rs
Normal file
334
tests/wasm_poc2_box_operations.rs
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
/*!
|
||||||
|
* Phase 8.3 PoC2 Integration Test - Box Operations in WASM
|
||||||
|
*
|
||||||
|
* Tests end-to-end MIR→WASM compilation and execution for:
|
||||||
|
* - RefNew: Box creation and reference assignment
|
||||||
|
* - RefGet: Field reading from Box objects
|
||||||
|
* - RefSet: Field writing to Box objects
|
||||||
|
* - NewBox: Direct Box allocation with type information
|
||||||
|
*
|
||||||
|
* Validates the "Everything is Box" philosophy in WASM
|
||||||
|
*/
|
||||||
|
|
||||||
|
use nyash_rust::mir::{
|
||||||
|
MirModule, MirFunction, FunctionSignature, MirType, EffectMask,
|
||||||
|
BasicBlock, BasicBlockId, ValueId, MirInstruction, ConstValue
|
||||||
|
};
|
||||||
|
use nyash_rust::backend::wasm::WasmBackend;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wasm_poc2_refnew_basic() {
|
||||||
|
// Build MIR equivalent to:
|
||||||
|
// function main() {
|
||||||
|
// %box = new_box "DataBox"(42)
|
||||||
|
// %ref = ref_new %box
|
||||||
|
// return %ref // Should return box pointer
|
||||||
|
// }
|
||||||
|
|
||||||
|
let mut backend = WasmBackend::new();
|
||||||
|
let mir_module = build_refnew_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("$malloc"), "Should contain malloc function");
|
||||||
|
assert!(wat_text.contains("$alloc_databox"), "Should contain DataBox allocator");
|
||||||
|
assert!(wat_text.contains("call $alloc_databox"), "Should call DataBox allocator");
|
||||||
|
assert!(wat_text.contains("i32.store"), "Should store field values");
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
// Should return a valid pointer (greater than heap start 0x800)
|
||||||
|
assert!(return_value >= 0x800, "Should return valid Box pointer: {}", return_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wasm_poc2_refget_refset() {
|
||||||
|
// Build MIR equivalent to:
|
||||||
|
// function main() {
|
||||||
|
// %box = new_box "DataBox"(10)
|
||||||
|
// %ref = ref_new %box
|
||||||
|
// ref_set %ref.value = 42
|
||||||
|
// %result = ref_get %ref.value
|
||||||
|
// return %result // Should return 42
|
||||||
|
// }
|
||||||
|
|
||||||
|
let mut backend = WasmBackend::new();
|
||||||
|
let mir_module = build_refget_refset_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 updated field value");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wasm_poc2_complete_box_workflow() {
|
||||||
|
// Build MIR equivalent to:
|
||||||
|
// function main() {
|
||||||
|
// %box1 = new_box "DataBox"(100)
|
||||||
|
// %box2 = new_box "DataBox"(200)
|
||||||
|
// %ref1 = ref_new %box1
|
||||||
|
// %ref2 = ref_new %box2
|
||||||
|
// %val1 = ref_get %ref1.value
|
||||||
|
// %val2 = ref_get %ref2.value
|
||||||
|
// %sum = %val1 + %val2
|
||||||
|
// ref_set %ref1.value = %sum
|
||||||
|
// %result = ref_get %ref1.value
|
||||||
|
// return %result // Should return 300
|
||||||
|
// }
|
||||||
|
|
||||||
|
let mut backend = WasmBackend::new();
|
||||||
|
let mir_module = build_complete_workflow_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, 300, "Should return sum of Box values");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build MIR module for basic RefNew test
|
||||||
|
fn build_refnew_mir_module() -> MirModule {
|
||||||
|
let mut module = MirModule::new("test_refnew".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 init_val = ValueId::new(0); // 42
|
||||||
|
let box_ptr = ValueId::new(1); // DataBox pointer
|
||||||
|
let ref_ptr = ValueId::new(2); // Reference to DataBox
|
||||||
|
|
||||||
|
// Create constant for initialization
|
||||||
|
block.add_instruction(MirInstruction::Const {
|
||||||
|
dst: init_val,
|
||||||
|
value: ConstValue::Integer(42),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create DataBox with initial value
|
||||||
|
block.add_instruction(MirInstruction::NewBox {
|
||||||
|
dst: box_ptr,
|
||||||
|
box_type: "DataBox".to_string(),
|
||||||
|
args: vec![init_val],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create reference to the Box
|
||||||
|
block.add_instruction(MirInstruction::RefNew {
|
||||||
|
dst: ref_ptr,
|
||||||
|
box_val: box_ptr,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the reference
|
||||||
|
block.set_terminator(MirInstruction::Return {
|
||||||
|
value: Some(ref_ptr),
|
||||||
|
});
|
||||||
|
|
||||||
|
main_function.add_block(block);
|
||||||
|
module.add_function(main_function);
|
||||||
|
|
||||||
|
module
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build MIR module for RefGet/RefSet test
|
||||||
|
fn build_refget_refset_mir_module() -> MirModule {
|
||||||
|
let mut module = MirModule::new("test_refget_refset".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 init_val = ValueId::new(0); // 10
|
||||||
|
let new_val = ValueId::new(1); // 42
|
||||||
|
let box_ptr = ValueId::new(2); // DataBox pointer
|
||||||
|
let ref_ptr = ValueId::new(3); // Reference to DataBox
|
||||||
|
let result = ValueId::new(4); // Read back value
|
||||||
|
|
||||||
|
// Create constants
|
||||||
|
block.add_instruction(MirInstruction::Const {
|
||||||
|
dst: init_val,
|
||||||
|
value: ConstValue::Integer(10),
|
||||||
|
});
|
||||||
|
|
||||||
|
block.add_instruction(MirInstruction::Const {
|
||||||
|
dst: new_val,
|
||||||
|
value: ConstValue::Integer(42),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create DataBox with initial value
|
||||||
|
block.add_instruction(MirInstruction::NewBox {
|
||||||
|
dst: box_ptr,
|
||||||
|
box_type: "DataBox".to_string(),
|
||||||
|
args: vec![init_val],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create reference to the Box
|
||||||
|
block.add_instruction(MirInstruction::RefNew {
|
||||||
|
dst: ref_ptr,
|
||||||
|
box_val: box_ptr,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set field value
|
||||||
|
block.add_instruction(MirInstruction::RefSet {
|
||||||
|
reference: ref_ptr,
|
||||||
|
field: "value".to_string(),
|
||||||
|
value: new_val,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get field value
|
||||||
|
block.add_instruction(MirInstruction::RefGet {
|
||||||
|
dst: result,
|
||||||
|
reference: ref_ptr,
|
||||||
|
field: "value".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the result
|
||||||
|
block.set_terminator(MirInstruction::Return {
|
||||||
|
value: Some(result),
|
||||||
|
});
|
||||||
|
|
||||||
|
main_function.add_block(block);
|
||||||
|
module.add_function(main_function);
|
||||||
|
|
||||||
|
module
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build MIR module for complete Box workflow test
|
||||||
|
fn build_complete_workflow_mir_module() -> MirModule {
|
||||||
|
let mut module = MirModule::new("test_complete_workflow".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 val1_init = ValueId::new(0); // 100
|
||||||
|
let val2_init = ValueId::new(1); // 200
|
||||||
|
let box1_ptr = ValueId::new(2); // DataBox 1 pointer
|
||||||
|
let box2_ptr = ValueId::new(3); // DataBox 2 pointer
|
||||||
|
let ref1_ptr = ValueId::new(4); // Reference to DataBox 1
|
||||||
|
let ref2_ptr = ValueId::new(5); // Reference to DataBox 2
|
||||||
|
let val1 = ValueId::new(6); // Value from box1
|
||||||
|
let val2 = ValueId::new(7); // Value from box2
|
||||||
|
let sum = ValueId::new(8); // Sum of values
|
||||||
|
let result = ValueId::new(9); // Final result
|
||||||
|
|
||||||
|
// Create constants
|
||||||
|
block.add_instruction(MirInstruction::Const {
|
||||||
|
dst: val1_init,
|
||||||
|
value: ConstValue::Integer(100),
|
||||||
|
});
|
||||||
|
|
||||||
|
block.add_instruction(MirInstruction::Const {
|
||||||
|
dst: val2_init,
|
||||||
|
value: ConstValue::Integer(200),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create DataBoxes
|
||||||
|
block.add_instruction(MirInstruction::NewBox {
|
||||||
|
dst: box1_ptr,
|
||||||
|
box_type: "DataBox".to_string(),
|
||||||
|
args: vec![val1_init],
|
||||||
|
});
|
||||||
|
|
||||||
|
block.add_instruction(MirInstruction::NewBox {
|
||||||
|
dst: box2_ptr,
|
||||||
|
box_type: "DataBox".to_string(),
|
||||||
|
args: vec![val2_init],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create references
|
||||||
|
block.add_instruction(MirInstruction::RefNew {
|
||||||
|
dst: ref1_ptr,
|
||||||
|
box_val: box1_ptr,
|
||||||
|
});
|
||||||
|
|
||||||
|
block.add_instruction(MirInstruction::RefNew {
|
||||||
|
dst: ref2_ptr,
|
||||||
|
box_val: box2_ptr,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get values from both boxes
|
||||||
|
block.add_instruction(MirInstruction::RefGet {
|
||||||
|
dst: val1,
|
||||||
|
reference: ref1_ptr,
|
||||||
|
field: "value".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
block.add_instruction(MirInstruction::RefGet {
|
||||||
|
dst: val2,
|
||||||
|
reference: ref2_ptr,
|
||||||
|
field: "value".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add values
|
||||||
|
block.add_instruction(MirInstruction::BinOp {
|
||||||
|
dst: sum,
|
||||||
|
op: nyash_rust::mir::BinaryOp::Add,
|
||||||
|
lhs: val1,
|
||||||
|
rhs: val2,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store sum back to first box
|
||||||
|
block.add_instruction(MirInstruction::RefSet {
|
||||||
|
reference: ref1_ptr,
|
||||||
|
field: "value".to_string(),
|
||||||
|
value: sum,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Read back the result
|
||||||
|
block.add_instruction(MirInstruction::RefGet {
|
||||||
|
dst: result,
|
||||||
|
reference: ref1_ptr,
|
||||||
|
field: "value".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the result
|
||||||
|
block.set_terminator(MirInstruction::Return {
|
||||||
|
value: Some(result),
|
||||||
|
});
|
||||||
|
|
||||||
|
main_function.add_block(block);
|
||||||
|
module.add_function(main_function);
|
||||||
|
|
||||||
|
module
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user