use crate::compiler::context::{ClassInfo, CompilationContext, COLLECTION_HEAP_BASE};
use crate::compiler::function::{compile_function, resolve_return_type};
use crate::ir::{IRBody, IRConstant, IRExpr, IRModule, IRStatement, IRType, STRING_LEN_PREFIX};
use std::collections::HashMap;
use wasm_encoder::{
BlockType, CodeSection, ConstExpr, DataSection, ExportSection, Function, FunctionSection,
GlobalSection, GlobalType, Instruction, MemorySection, MemoryType, Module, TypeSection,
ValType,
};
fn ir_type_to_wasm_type(ir_type: &IRType) -> ValType {
match ir_type {
IRType::Float => ValType::F64,
IRType::Int | IRType::Bool | IRType::String => ValType::I32,
IRType::Class(_) => ValType::I32, IRType::List(_) | IRType::Dict(_, _) | IRType::Tuple(_) => ValType::I32, IRType::Optional(_) | IRType::Union(_) => ValType::I32, _ => ValType::I32,
}
}
fn is_self_ref(expr: &IRExpr) -> bool {
matches!(expr, IRExpr::Variable(n) | IRExpr::Param(n) if n == "self")
}
fn infer_field_value_type(value: &IRExpr, params: &HashMap<String, IRType>) -> IRType {
match value {
IRExpr::Const(IRConstant::Float(_)) => IRType::Float,
IRExpr::Const(IRConstant::Int(_)) => IRType::Int,
IRExpr::Const(IRConstant::Bool(_)) => IRType::Bool,
IRExpr::Variable(name) | IRExpr::Param(name) => {
params.get(name).cloned().unwrap_or(IRType::Unknown)
}
IRExpr::BinaryOp { left, right, .. } => {
let lt = infer_field_value_type(left, params);
let rt = infer_field_value_type(right, params);
if lt == IRType::Float || rt == IRType::Float {
IRType::Float
} else {
lt
}
}
IRExpr::UnaryOp { operand, .. } => infer_field_value_type(operand, params),
_ => IRType::Unknown,
}
}
fn collect_self_fields(
body: &IRBody,
params: &HashMap<String, IRType>,
out: &mut Vec<(String, IRType)>,
) {
for stmt in &body.statements {
match stmt {
IRStatement::AttributeAssign {
object,
attribute,
value,
} if is_self_ref(object) => {
out.push((attribute.clone(), infer_field_value_type(value, params)));
}
IRStatement::AttributeAugAssign {
object,
attribute,
value,
..
} if is_self_ref(object) => {
out.push((attribute.clone(), infer_field_value_type(value, params)));
}
IRStatement::If {
then_body,
else_body,
..
} => {
collect_self_fields(then_body, params, out);
if let Some(else_body) = else_body {
collect_self_fields(else_body, params, out);
}
}
IRStatement::While { body, .. } => collect_self_fields(body, params, out),
IRStatement::For { body, .. } => collect_self_fields(body, params, out),
_ => {}
}
}
}
fn build_alloc_function() -> Function {
let mut f = Function::new([(4u32, ValType::I32)]);
f.instruction(&Instruction::LocalGet(0));
f.instruction(&Instruction::I32Const(7));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::I32Const(!7));
f.instruction(&Instruction::I32And);
f.instruction(&Instruction::LocalSet(1));
f.instruction(&Instruction::GlobalGet(0));
f.instruction(&Instruction::LocalSet(2));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::LocalGet(1));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::LocalSet(3));
f.instruction(&Instruction::MemorySize(0));
f.instruction(&Instruction::I32Const(16));
f.instruction(&Instruction::I32Shl);
f.instruction(&Instruction::LocalSet(4));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32GtU);
f.instruction(&Instruction::If(BlockType::Empty));
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::LocalGet(4));
f.instruction(&Instruction::I32Sub);
f.instruction(&Instruction::I32Const(65535));
f.instruction(&Instruction::I32Add);
f.instruction(&Instruction::I32Const(16));
f.instruction(&Instruction::I32ShrU);
f.instruction(&Instruction::MemoryGrow(0));
f.instruction(&Instruction::Drop); f.instruction(&Instruction::End);
f.instruction(&Instruction::LocalGet(3));
f.instruction(&Instruction::GlobalSet(0));
f.instruction(&Instruction::LocalGet(2));
f.instruction(&Instruction::End);
f
}
pub fn compile_ir_module(ir_module: &IRModule) -> Vec<u8> {
let mut module = Module::new();
let mut ctx = CompilationContext::new();
let memory_layout = ir_module.memory_layout.clone();
for var in &ir_module.variables {
ctx.add_module_var(&var.name, var.var_type.clone(), var.value.clone());
}
let mut resolved_returns: HashMap<String, IRType> = HashMap::new();
for _ in 0..2 {
for func in &ir_module.functions {
let rt = resolve_return_type(func, &resolved_returns);
resolved_returns.insert(func.name.clone(), rt);
}
for cls in &ir_module.classes {
for method in &cls.methods {
let rt = resolve_return_type(method, &resolved_returns);
resolved_returns.insert(format!("{}::{}", cls.name, method.name), rt);
}
}
}
let module_return = |func: &crate::ir::IRFunction| -> IRType {
resolved_returns
.get(&func.name)
.cloned()
.unwrap_or_else(|| func.return_type.clone())
};
let method_return = |class: &str, method: &crate::ir::IRFunction| -> IRType {
resolved_returns
.get(&format!("{class}::{}", method.name))
.cloned()
.unwrap_or_else(|| method.return_type.clone())
};
let mut types = TypeSection::new();
let mut total_function_count = ir_module.functions.len();
for cls in &ir_module.classes {
total_function_count += cls.methods.len();
}
ctx.alloc_func_index = total_function_count as u32;
for func in &ir_module.functions {
let params: Vec<ValType> = func
.params
.iter()
.map(|param| ir_type_to_wasm_type(¶m.param_type))
.collect();
let results = vec![ir_type_to_wasm_type(&module_return(func))];
types.ty().function(params, results);
}
for cls in &ir_module.classes {
for method in &cls.methods {
let params: Vec<ValType> = method
.params
.iter()
.map(|param| ir_type_to_wasm_type(¶m.param_type))
.collect();
let results = vec![ir_type_to_wasm_type(&method_return(&cls.name, method))];
types.ty().function(params, results);
}
}
types.ty().function([ValType::I32], [ValType::I32]);
module.section(&types);
let mut functions = FunctionSection::new();
for _ in 0..total_function_count {
functions.function(functions.len());
}
functions.function(total_function_count as u32); module.section(&functions);
let mut exports = ExportSection::new();
let mut func_idx = 0u32;
for func in &ir_module.functions {
let param_types = func.params.iter().map(|p| p.param_type.clone()).collect();
ctx.add_function(&func.name, func_idx, param_types, module_return(func));
exports.export(&func.name, wasm_encoder::ExportKind::Func, func_idx);
func_idx += 1;
}
for cls in &ir_module.classes {
let mut class_info = ClassInfo {
name: cls.name.clone(),
methods: HashMap::new(),
field_offsets: HashMap::new(),
field_types: HashMap::new(),
class_var_values: HashMap::new(),
instance_size: 0,
};
let mut current_offset = 4u64;
let add_field = |info: &mut ClassInfo, name: &str, ty: IRType, current_offset: &mut u64| {
if !info.field_offsets.contains_key(name) {
info.field_offsets.insert(name.to_string(), *current_offset);
*current_offset += 8;
}
let entry = info
.field_types
.entry(name.to_string())
.or_insert(IRType::Unknown);
if matches!(entry, IRType::Unknown) {
*entry = ty;
}
};
for var in &cls.class_vars {
let ty = var
.var_type
.clone()
.unwrap_or_else(|| infer_field_value_type(&var.value, &HashMap::new()));
add_field(&mut class_info, &var.name, ty, &mut current_offset);
class_info
.class_var_values
.insert(var.name.clone(), var.value.clone());
}
for method in &cls.methods {
let params: HashMap<String, IRType> = method
.params
.iter()
.map(|p| (p.name.clone(), p.param_type.clone()))
.collect();
let mut fields = Vec::new();
collect_self_fields(&method.body, ¶ms, &mut fields);
for (name, ty) in fields {
add_field(&mut class_info, &name, ty, &mut current_offset);
}
}
class_info.instance_size = current_offset as u32;
for method in &cls.methods {
let param_types = method.params.iter().map(|p| p.param_type.clone()).collect();
let qualified_name = format!("{}::{}", cls.name, method.name);
ctx.add_function(
&qualified_name,
func_idx,
param_types,
method_return(&cls.name, method),
);
class_info.methods.insert(method.name.clone(), func_idx);
exports.export(&qualified_name, wasm_encoder::ExportKind::Func, func_idx);
func_idx += 1;
}
ctx.add_class(class_info);
}
exports.export("memory", wasm_encoder::ExportKind::Memory, 0);
let mut data = DataSection::new();
if !memory_layout.string_offsets.is_empty() {
let mut offsets: Vec<(&String, u32)> = memory_layout
.string_offsets
.iter()
.map(|(s, &offset)| (s, offset))
.collect();
offsets.sort_by_key(|(_s, offset)| *offset);
let mut all_strings = Vec::new();
for (s, _) in offsets {
all_strings.extend_from_slice(&(s.len() as u32).to_le_bytes()); all_strings.extend_from_slice(s.as_bytes());
all_strings.push(0); }
data.active(0, &ConstExpr::i32_const(0), all_strings);
}
if !memory_layout.bytes_offsets.is_empty() {
let mut offsets: Vec<(&Vec<u8>, u32)> = memory_layout
.bytes_offsets
.iter()
.map(|(b, &offset)| (b, offset))
.collect();
offsets.sort_by_key(|(_b, offset)| *offset);
let base = offsets[0].1 - STRING_LEN_PREFIX;
let mut all_bytes = Vec::new();
for (b, _) in offsets {
all_bytes.extend_from_slice(&(b.len() as u32).to_le_bytes()); all_bytes.extend_from_slice(b);
}
data.active(0, &ConstExpr::i32_const(base as i32), all_bytes);
}
let mut codes = CodeSection::new();
for func_ir in &ir_module.functions {
let return_type = module_return(func_ir);
let compiled_func = compile_function(func_ir, &mut ctx, &memory_layout, &return_type);
codes.function(&compiled_func);
}
for cls in &ir_module.classes {
for method in &cls.methods {
let return_type = method_return(&cls.name, method);
let compiled_method = compile_function(method, &mut ctx, &memory_layout, &return_type);
codes.function(&compiled_method);
}
}
codes.function(&build_alloc_function());
let heap_end = COLLECTION_HEAP_BASE + ctx.collection_alloc_offset.get();
let runtime_heap_base = (heap_end + 7) & !7;
let min_pages = (((runtime_heap_base as u64) + 65535) / 65536).max(2);
let mut memories = MemorySection::new();
memories.memory(MemoryType {
minimum: min_pages,
maximum: None,
memory64: false,
shared: false,
page_size_log2: None,
});
let mut globals = GlobalSection::new();
globals.global(
GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
&ConstExpr::i32_const(runtime_heap_base as i32),
);
module.section(&memories);
module.section(&globals);
module.section(&exports);
module.section(&codes);
module.section(&data);
module.finish()
}