use std::collections::HashMap;
use std::fmt::Write;
use super::{Backend, CodegenError, CodegenResult, Target};
use crate::codegen::ir::*;
use crate::codegen::{GeneratedCode, OutputFormat};
#[repr(u16)]
#[derive(Debug, Clone, Copy)]
pub enum WasiErrno {
Success = 0,
Toobig = 1,
Acces = 2,
Addrinuse = 3,
Addrnotavail = 4,
Afnosupport = 5,
Again = 6,
Already = 7,
Badf = 8,
Badmsg = 9,
Busy = 10,
Canceled = 11,
Child = 12,
Connaborted = 13,
Connrefused = 14,
Connreset = 15,
Deadlk = 16,
Destaddrreq = 17,
Dom = 18,
Dquot = 19,
Exist = 20,
Fault = 21,
Fbig = 22,
Hostunreach = 23,
Idrm = 24,
Ilseq = 25,
Inprogress = 26,
Intr = 27,
Inval = 28,
Io = 29,
Isconn = 30,
Isdir = 31,
Loop = 32,
Mfile = 33,
Mlink = 34,
Msgsize = 35,
Multihop = 36,
Nametoolong = 37,
Netdown = 38,
Netreset = 39,
Netunreach = 40,
Nfile = 41,
Nobufs = 42,
Nodev = 43,
Noent = 44,
Noexec = 45,
Nolck = 46,
Nolink = 47,
Nomem = 48,
Nomsg = 49,
Noprotoopt = 50,
Nospc = 51,
Nosys = 52,
Notconn = 53,
Notdir = 54,
Notempty = 55,
Notrecoverable = 56,
Notsock = 57,
Notsup = 58,
Notty = 59,
Nxio = 60,
Overflow = 61,
Ownerdead = 62,
Perm = 63,
Pipe = 64,
Proto = 65,
Protonosupport = 66,
Prototype = 67,
Range = 68,
Rofs = 69,
Spipe = 70,
Srch = 71,
Stale = 72,
Timedout = 73,
Txtbsy = 74,
Xdev = 75,
Notcapable = 76,
}
#[derive(Debug, Clone, Copy)]
pub struct WasiRights(pub u64);
impl WasiRights {
pub const FD_DATASYNC: u64 = 1 << 0;
pub const FD_READ: u64 = 1 << 1;
pub const FD_SEEK: u64 = 1 << 2;
pub const FD_FDSTAT_SET_FLAGS: u64 = 1 << 3;
pub const FD_SYNC: u64 = 1 << 4;
pub const FD_TELL: u64 = 1 << 5;
pub const FD_WRITE: u64 = 1 << 6;
pub const FD_ADVISE: u64 = 1 << 7;
pub const FD_ALLOCATE: u64 = 1 << 8;
pub const PATH_CREATE_DIRECTORY: u64 = 1 << 9;
pub const PATH_CREATE_FILE: u64 = 1 << 10;
pub const PATH_LINK_SOURCE: u64 = 1 << 11;
pub const PATH_LINK_TARGET: u64 = 1 << 12;
pub const PATH_OPEN: u64 = 1 << 13;
pub const FD_READDIR: u64 = 1 << 14;
pub const PATH_READLINK: u64 = 1 << 15;
pub const PATH_RENAME_SOURCE: u64 = 1 << 16;
pub const PATH_RENAME_TARGET: u64 = 1 << 17;
pub const PATH_FILESTAT_GET: u64 = 1 << 18;
pub const PATH_FILESTAT_SET_SIZE: u64 = 1 << 19;
pub const PATH_FILESTAT_SET_TIMES: u64 = 1 << 20;
pub const FD_FILESTAT_GET: u64 = 1 << 21;
pub const FD_FILESTAT_SET_SIZE: u64 = 1 << 22;
pub const FD_FILESTAT_SET_TIMES: u64 = 1 << 23;
pub const PATH_SYMLINK: u64 = 1 << 24;
pub const PATH_REMOVE_DIRECTORY: u64 = 1 << 25;
pub const PATH_UNLINK_FILE: u64 = 1 << 26;
pub const POLL_FD_READWRITE: u64 = 1 << 27;
pub const SOCK_SHUTDOWN: u64 = 1 << 28;
pub const SOCK_ACCEPT: u64 = 1 << 29;
}
#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub enum WasiClockId {
Realtime = 0,
Monotonic = 1,
ProcessCputimeId = 2,
ThreadCputimeId = 3,
}
pub struct WasmBackend {
output: String,
wasi: bool,
memory_pages: u32,
max_memory_pages: Option<u32>,
indent: u32,
local_counter: u32,
block_counter: u32,
local_names: HashMap<LocalId, String>,
block_labels: HashMap<BlockId, String>,
heap_base: u32,
bulk_memory: bool,
simd: bool,
threads: bool,
}
impl WasmBackend {
pub fn new() -> Self {
Self {
output: String::new(),
wasi: false,
memory_pages: 1,
max_memory_pages: None,
indent: 0,
local_counter: 0,
block_counter: 0,
local_names: HashMap::new(),
block_labels: HashMap::new(),
heap_base: 65536, bulk_memory: false,
simd: false,
threads: false,
}
}
pub fn with_wasi() -> Self {
let mut backend = Self::new();
backend.wasi = true;
backend.memory_pages = 4; backend
}
pub fn with_memory(mut self, pages: u32) -> Self {
self.memory_pages = pages;
self
}
pub fn with_max_memory(mut self, pages: u32) -> Self {
self.max_memory_pages = Some(pages);
self
}
pub fn with_bulk_memory(mut self) -> Self {
self.bulk_memory = true;
self
}
pub fn with_simd(mut self) -> Self {
self.simd = true;
self
}
pub fn with_threads(mut self) -> Self {
self.threads = true;
self
}
fn reset_function_state(&mut self) {
self.local_counter = 0;
self.block_counter = 0;
self.local_names.clear();
self.block_labels.clear();
}
fn emit_line(&mut self, s: &str) {
for _ in 0..self.indent {
self.output.push_str(" ");
}
self.output.push_str(s);
self.output.push('\n');
}
fn emit(&mut self, s: &str) {
for _ in 0..self.indent {
self.output.push_str(" ");
}
self.output.push_str(s);
}
fn fresh_local(&mut self) -> String {
let name = format!("$__tmp{}", self.local_counter);
self.local_counter += 1;
name
}
fn fresh_block_label(&mut self) -> String {
let label = format!("$block{}", self.block_counter);
self.block_counter += 1;
label
}
fn emit_type(&self, ty: &MirType) -> &'static str {
match ty {
MirType::Void => "i32", MirType::Bool => "i32",
MirType::Int(size, _) => match size {
IntSize::I8 | IntSize::I16 | IntSize::I32 => "i32",
IntSize::I64 | IntSize::I128 | IntSize::ISize => "i64",
},
MirType::Float(size) => match size {
FloatSize::F32 => "f32",
FloatSize::F64 => "f64",
},
MirType::Ptr(_) => "i32", MirType::FnPtr(_) => "i32", MirType::Array(_, _) => "i32", MirType::Slice(_) => "i32", MirType::Struct(_) => "i32", MirType::Never => "i32", MirType::Vector(_, _) => "v128", MirType::Texture2D(_) | MirType::Sampler | MirType::SampledImage(_) => "i32",
MirType::TraitObject(_) => "i32", MirType::Vec(_) => "i32", MirType::Tuple(_) => "i32", MirType::Map(_, _) => "i32", }
}
fn type_size(&self, ty: &MirType) -> u32 {
match ty {
MirType::Void => 0,
MirType::Bool => 1,
MirType::Int(size, _) => match size {
IntSize::I8 => 1,
IntSize::I16 => 2,
IntSize::I32 | IntSize::ISize => 4, IntSize::I64 => 8,
IntSize::I128 => 16,
},
MirType::Float(size) => match size {
FloatSize::F32 => 4,
FloatSize::F64 => 8,
},
MirType::Ptr(_) | MirType::FnPtr(_) => 4,
MirType::Array(elem, count) => self.type_size(elem) * (*count as u32),
MirType::Slice(_) => 8, MirType::Struct(_) => 4, MirType::Never => 0,
MirType::Vector(elem, lanes) => self.type_size(elem) * lanes,
MirType::Texture2D(_) | MirType::Sampler | MirType::SampledImage(_) => 4,
MirType::TraitObject(_) => 8, MirType::Vec(_) => 4, MirType::Tuple(elems) => elems.iter().map(|e| self.type_size(e)).sum(),
MirType::Map(_, _) => 4, }
}
fn gen_module_header(&mut self, module: &MirModule) {
self.emit_line(";; Generated by QuantaLang Compiler");
self.emit_line(&format!(";; Module: {}", module.name));
self.emit_line(";; Target: WebAssembly");
if self.wasi {
self.emit_line(";; WASI: snapshot_preview1");
}
self.output.push('\n');
self.emit_line("(module");
self.indent += 1;
}
fn gen_wasi_imports(&mut self) {
self.emit_line(";; =======================================================");
self.emit_line(";; WASI Imports (wasi_snapshot_preview1)");
self.emit_line(";; =======================================================");
self.output.push('\n');
self.emit_line(";; Process Control");
self.emit_line("(import \"wasi_snapshot_preview1\" \"proc_exit\" (func $__wasi_proc_exit (param i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"args_get\" (func $__wasi_args_get (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"args_sizes_get\" (func $__wasi_args_sizes_get (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"environ_get\" (func $__wasi_environ_get (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"environ_sizes_get\" (func $__wasi_environ_sizes_get (param i32 i32) (result i32)))");
self.output.push('\n');
self.emit_line(";; Clock");
self.emit_line("(import \"wasi_snapshot_preview1\" \"clock_res_get\" (func $__wasi_clock_res_get (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"clock_time_get\" (func $__wasi_clock_time_get (param i32 i64 i32) (result i32)))");
self.output.push('\n');
self.emit_line(";; File Descriptors");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_advise\" (func $__wasi_fd_advise (param i32 i64 i64 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_allocate\" (func $__wasi_fd_allocate (param i32 i64 i64) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_close\" (func $__wasi_fd_close (param i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_datasync\" (func $__wasi_fd_datasync (param i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_fdstat_get\" (func $__wasi_fd_fdstat_get (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_fdstat_set_flags\" (func $__wasi_fd_fdstat_set_flags (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_fdstat_set_rights\" (func $__wasi_fd_fdstat_set_rights (param i32 i64 i64) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_filestat_get\" (func $__wasi_fd_filestat_get (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_filestat_set_size\" (func $__wasi_fd_filestat_set_size (param i32 i64) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_filestat_set_times\" (func $__wasi_fd_filestat_set_times (param i32 i64 i64 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_pread\" (func $__wasi_fd_pread (param i32 i32 i32 i64 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_prestat_get\" (func $__wasi_fd_prestat_get (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_prestat_dir_name\" (func $__wasi_fd_prestat_dir_name (param i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_pwrite\" (func $__wasi_fd_pwrite (param i32 i32 i32 i64 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_read\" (func $__wasi_fd_read (param i32 i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_readdir\" (func $__wasi_fd_readdir (param i32 i32 i32 i64 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_renumber\" (func $__wasi_fd_renumber (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_seek\" (func $__wasi_fd_seek (param i32 i64 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_sync\" (func $__wasi_fd_sync (param i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_tell\" (func $__wasi_fd_tell (param i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"fd_write\" (func $__wasi_fd_write (param i32 i32 i32 i32) (result i32)))");
self.output.push('\n');
self.emit_line(";; Path Operations");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_create_directory\" (func $__wasi_path_create_directory (param i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_filestat_get\" (func $__wasi_path_filestat_get (param i32 i32 i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_filestat_set_times\" (func $__wasi_path_filestat_set_times (param i32 i32 i32 i32 i64 i64 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_link\" (func $__wasi_path_link (param i32 i32 i32 i32 i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_open\" (func $__wasi_path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_readlink\" (func $__wasi_path_readlink (param i32 i32 i32 i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_remove_directory\" (func $__wasi_path_remove_directory (param i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_rename\" (func $__wasi_path_rename (param i32 i32 i32 i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_symlink\" (func $__wasi_path_symlink (param i32 i32 i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"path_unlink_file\" (func $__wasi_path_unlink_file (param i32 i32 i32) (result i32)))");
self.output.push('\n');
self.emit_line(";; Polling");
self.emit_line("(import \"wasi_snapshot_preview1\" \"poll_oneoff\" (func $__wasi_poll_oneoff (param i32 i32 i32 i32) (result i32)))");
self.output.push('\n');
self.emit_line(";; Random");
self.emit_line("(import \"wasi_snapshot_preview1\" \"random_get\" (func $__wasi_random_get (param i32 i32) (result i32)))");
self.output.push('\n');
self.emit_line(";; Scheduling");
self.emit_line("(import \"wasi_snapshot_preview1\" \"sched_yield\" (func $__wasi_sched_yield (result i32)))");
self.output.push('\n');
self.emit_line(";; Sockets (optional)");
self.emit_line("(import \"wasi_snapshot_preview1\" \"sock_accept\" (func $__wasi_sock_accept (param i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"sock_recv\" (func $__wasi_sock_recv (param i32 i32 i32 i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"sock_send\" (func $__wasi_sock_send (param i32 i32 i32 i32 i32) (result i32)))");
self.emit_line("(import \"wasi_snapshot_preview1\" \"sock_shutdown\" (func $__wasi_sock_shutdown (param i32 i32) (result i32)))");
self.output.push('\n');
}
fn gen_memory(&mut self) {
self.emit_line(";; =======================================================");
self.emit_line(";; Memory");
self.emit_line(";; =======================================================");
if let Some(max) = self.max_memory_pages {
self.emit_line(&format!(
"(memory (export \"memory\") {} {})",
self.memory_pages, max
));
} else {
self.emit_line(&format!(
"(memory (export \"memory\") {})",
self.memory_pages
));
}
self.emit_line(&format!(
"(global $__heap_base (mut i32) (i32.const {}))",
self.heap_base
));
self.emit_line("(global $__stack_pointer (mut i32) (i32.const 65536))");
self.output.push('\n');
}
fn gen_data_section(&mut self, module: &MirModule) {
if module.strings.is_empty() {
return;
}
self.emit_line(";; =======================================================");
self.emit_line(";; Data Section");
self.emit_line(";; =======================================================");
let mut offset = 0u32;
for (i, s) in module.strings.iter().enumerate() {
let escaped = self.escape_string(s);
self.emit_line(&format!(
"(data (i32.const {}) \"{}\\00\")",
offset, escaped
));
self.emit_line(&format!("(global $__str{} i32 (i32.const {}))", i, offset));
offset += s.len() as u32 + 1; }
self.output.push('\n');
}
fn escape_string(&self, s: &str) -> String {
let mut result = String::new();
for c in s.chars() {
match c {
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
c if c.is_ascii_graphic() || c == ' ' => result.push(c),
c => {
for b in c.to_string().as_bytes() {
write!(&mut result, "\\{:02x}", b).unwrap();
}
}
}
}
result
}
fn gen_globals(&mut self, module: &MirModule) {
if module.globals.is_empty() {
return;
}
self.emit_line(";; =======================================================");
self.emit_line(";; Globals");
self.emit_line(";; =======================================================");
for global in &module.globals {
let wasm_type = self.emit_type(&global.ty);
let default_val = match &global.ty {
MirType::Float(FloatSize::F32) => "f32.const 0.0",
MirType::Float(FloatSize::F64) => "f64.const 0.0",
MirType::Int(IntSize::I64, _) | MirType::Int(IntSize::I128, _) => "i64.const 0",
_ => "i32.const 0",
};
if global.is_mut {
self.emit_line(&format!(
"(global ${} (mut {}) ({}))",
global.name, wasm_type, default_val
));
} else {
self.emit_line(&format!(
"(global ${} {} ({}))",
global.name, wasm_type, default_val
));
}
}
self.output.push('\n');
}
fn gen_table(&mut self, module: &MirModule) {
let func_count = module.functions.len();
if func_count > 0 {
self.emit_line(";; =======================================================");
self.emit_line(";; Function Table");
self.emit_line(";; =======================================================");
self.emit_line(&format!(
"(table (export \"__indirect_function_table\") {} funcref)",
func_count
));
let mut elem_str = String::from("(elem (i32.const 0)");
for func in &module.functions {
if !func.is_declaration() {
elem_str.push_str(&format!(" ${}", func.name));
}
}
elem_str.push(')');
self.emit_line(&elem_str);
self.output.push('\n');
}
}
fn gen_types(&mut self, module: &MirModule) {
self.emit_line(";; =======================================================");
self.emit_line(";; Type Declarations");
self.emit_line(";; =======================================================");
let mut sig_map: HashMap<String, usize> = HashMap::new();
let mut type_idx = 0;
for func in &module.functions {
let sig_str = self.signature_string(&func.sig);
if !sig_map.contains_key(&sig_str) {
sig_map.insert(sig_str.clone(), type_idx);
let mut type_def = format!("(type $t{} (func", type_idx);
if !func.sig.params.is_empty() {
type_def.push_str(" (param");
for param in &func.sig.params {
type_def.push_str(&format!(" {}", self.emit_type(param)));
}
type_def.push(')');
}
if func.sig.ret != MirType::Void {
type_def.push_str(&format!(" (result {})", self.emit_type(&func.sig.ret)));
}
type_def.push_str("))");
self.emit_line(&type_def);
type_idx += 1;
}
}
self.output.push('\n');
}
fn signature_string(&self, sig: &MirFnSig) -> String {
let mut s = String::new();
for p in &sig.params {
s.push_str(self.emit_type(p));
s.push(',');
}
s.push_str("->");
s.push_str(self.emit_type(&sig.ret));
s
}
fn gen_function(&mut self, func: &MirFunction) -> CodegenResult<()> {
self.reset_function_state();
if func.is_declaration() {
return Ok(());
}
let mut header = format!("(func ${}", func.name);
if func.is_public {
header.push_str(&format!(" (export \"{}\")", func.name));
}
for (i, param) in func.sig.params.iter().enumerate() {
let name = format!("$arg{}", i);
header.push_str(&format!(" (param {} {})", name, self.emit_type(param)));
}
if func.sig.ret != MirType::Void {
header.push_str(&format!(" (result {})", self.emit_type(&func.sig.ret)));
}
self.emit_line(&header);
self.indent += 1;
self.gen_locals(func)?;
self.gen_function_body(func)?;
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
Ok(())
}
fn gen_locals(&mut self, func: &MirFunction) -> CodegenResult<()> {
for local in &func.locals {
if local.is_param {
let name = format!("$arg{}", local.id.0);
self.local_names.insert(local.id, name);
} else {
let name = if let Some(n) = &local.name {
format!("$local_{}", n)
} else {
format!("$local{}", local.id.0)
};
self.local_names.insert(local.id, name.clone());
self.emit_line(&format!("(local {} {})", name, self.emit_type(&local.ty)));
}
}
let has_switch = func.blocks.as_ref().map_or(false, |blocks| {
blocks
.iter()
.any(|b| matches!(&b.terminator, Some(MirTerminator::Switch { .. })))
});
if has_switch {
self.emit_line("(local $__switch_val i32)");
}
Ok(())
}
fn gen_function_body(&mut self, func: &MirFunction) -> CodegenResult<()> {
let blocks = match &func.blocks {
Some(blocks) => blocks,
None => return Ok(()),
};
if blocks.is_empty() {
if func.sig.ret != MirType::Void {
self.gen_default_value(&func.sig.ret);
}
return Ok(());
}
for block in blocks {
let label = format!("$bb{}", block.id.0);
self.block_labels.insert(block.id, label);
}
let num_blocks = blocks.len();
if num_blocks == 1 {
let block = &blocks[0];
let label = self
.block_labels
.get(&block.id)
.cloned()
.unwrap_or_default();
self.emit_line(&format!(";; Block {}", label));
for stmt in &block.stmts {
self.gen_stmt(stmt, func)?;
}
if let Some(term) = &block.terminator {
self.gen_terminator(term, func, true)?;
}
return Ok(());
}
self.emit_line("(block $fn_exit");
self.indent += 1;
self.emit_line("(loop $loop_top");
self.indent += 1;
for i in (1..num_blocks).rev() {
let label = self
.block_labels
.get(&BlockId(i as u32))
.cloned()
.unwrap_or_default();
self.emit_line(&format!("(block {}", label));
self.indent += 1;
}
for (i, block) in blocks.iter().enumerate() {
let is_last = i == num_blocks - 1;
let label = self
.block_labels
.get(&block.id)
.cloned()
.unwrap_or_default();
self.emit_line(&format!(";; Block {}", label));
for stmt in &block.stmts {
self.gen_stmt(stmt, func)?;
}
if let Some(term) = &block.terminator {
self.gen_terminator(term, func, is_last)?;
}
if i < num_blocks - 1 {
self.indent -= 1;
let next_label = self
.block_labels
.get(&BlockId((i + 1) as u32))
.cloned()
.unwrap_or_default();
self.emit_line(&format!(") ;; end {}", next_label));
}
}
self.emit_line("br $loop_top");
self.indent -= 1;
self.emit_line(") ;; end $loop_top");
self.indent -= 1;
self.emit_line(") ;; end $fn_exit");
Ok(())
}
fn gen_stmt(&mut self, stmt: &MirStmt, func: &MirFunction) -> CodegenResult<()> {
match &stmt.kind {
MirStmtKind::Assign { dest, value } => {
self.gen_rvalue(value, func)?;
let dest_name = self
.local_names
.get(dest)
.ok_or_else(|| CodegenError::Internal(format!("Unknown local: {:?}", dest)))?
.clone();
self.emit_line(&format!("local.set {}", dest_name));
}
MirStmtKind::DerefAssign { ptr, value } => {
let ptr_name = self
.local_names
.get(ptr)
.ok_or_else(|| CodegenError::Internal(format!("Unknown local: {:?}", ptr)))?
.clone();
self.gen_rvalue(value, func)?;
self.emit_line(&format!("local.get {}", ptr_name));
self.emit_line("i64.store");
}
MirStmtKind::FieldDerefAssign {
ptr,
field_name: _,
value,
} => {
let ptr_name = self
.local_names
.get(ptr)
.ok_or_else(|| CodegenError::Internal(format!("Unknown local: {:?}", ptr)))?
.clone();
self.gen_rvalue(value, func)?;
self.emit_line(&format!("local.get {}", ptr_name));
self.emit_line("i64.store");
}
MirStmtKind::FieldAssign {
base,
field_name: _,
value,
} => {
let base_name = self
.local_names
.get(base)
.ok_or_else(|| CodegenError::Internal(format!("Unknown local: {:?}", base)))?
.clone();
self.gen_rvalue(value, func)?;
self.emit_line(&format!("local.get {}", base_name));
self.emit_line("i64.store");
}
MirStmtKind::StorageLive(_local) => {
}
MirStmtKind::StorageDead(_local) => {
}
MirStmtKind::Nop => {
}
}
Ok(())
}
fn gen_rvalue(&mut self, rvalue: &MirRValue, func: &MirFunction) -> CodegenResult<()> {
match rvalue {
MirRValue::Use(value) => {
self.gen_value(value, func)?;
}
MirRValue::BinaryOp { op, left, right } => {
self.gen_value(left, func)?;
self.gen_value(right, func)?;
let ty = self.infer_value_type(left, func)?;
let instr = self.wasm_binop(*op, &ty);
self.emit_line(&instr);
}
MirRValue::UnaryOp { op, operand } => {
let ty = self.infer_value_type(operand, func)?;
match op {
UnaryOp::Neg => {
if ty.is_float() {
self.gen_value(operand, func)?;
let wasm_ty = self.emit_type(&ty);
self.emit_line(&format!("{}.neg", wasm_ty));
} else {
let wasm_ty = self.emit_type(&ty);
self.emit_line(&format!("{}.const 0", wasm_ty));
self.gen_value(operand, func)?;
self.emit_line(&format!("{}.sub", wasm_ty));
}
}
UnaryOp::Not => {
self.gen_value(operand, func)?;
if ty == MirType::Bool {
self.emit_line("i32.const 1");
self.emit_line("i32.xor");
} else {
let wasm_ty = self.emit_type(&ty);
self.emit_line(&format!("{}.const -1", wasm_ty));
self.emit_line(&format!("{}.xor", wasm_ty));
}
}
}
}
MirRValue::Ref { place, .. } | MirRValue::AddressOf { place, .. } => {
self.gen_place_addr(place, func)?;
}
MirRValue::Cast { kind, value, ty } => {
self.gen_value(value, func)?;
let from_ty = self.infer_value_type(value, func)?;
self.gen_cast(*kind, &from_ty, ty)?;
}
MirRValue::Aggregate { kind: _, operands } => {
let size = operands.len() as u32 * 4; self.emit_line(&format!("global.get $__heap_base"));
self.emit_line(&format!("global.get $__heap_base"));
self.emit_line(&format!("i32.const {}", size));
self.emit_line("i32.add");
self.emit_line("global.set $__heap_base");
}
MirRValue::Repeat { value, count } => {
let ty = self.infer_value_type(value, func)?;
let elem_size = self.type_size(&ty);
let total_size = elem_size * (*count as u32);
self.emit_line("global.get $__heap_base");
self.emit_line(&format!("global.get $__heap_base"));
self.emit_line(&format!("i32.const {}", total_size));
self.emit_line("i32.add");
self.emit_line("global.set $__heap_base");
}
MirRValue::Discriminant(place) => {
self.gen_place_addr(place, func)?;
self.emit_line("i32.load");
}
MirRValue::Len(place) => {
self.gen_place_addr(place, func)?;
self.emit_line("i32.const 4");
self.emit_line("i32.add");
self.emit_line("i32.load");
}
MirRValue::NullaryOp(op, ty) => {
match op {
NullaryOp::SizeOf => {
let size = self.type_size(ty);
self.emit_line(&format!("i32.const {}", size));
}
NullaryOp::AlignOf => {
let align = std::cmp::min(self.type_size(ty), 8);
self.emit_line(&format!("i32.const {}", align));
}
}
}
MirRValue::FieldAccess {
base,
field_name,
field_ty,
} => {
self.emit_line(&format!(";; field access .{}", field_name));
self.gen_value(base, func)?;
let field_offset = field_name
.bytes()
.fold(0u32, |acc, b| acc.wrapping_add(b as u32))
% 64;
let elem_size = self.type_size(field_ty);
self.emit_line(&format!(
"i32.const {} ;; offset of .{}",
field_offset * elem_size,
field_name
));
self.emit_line("i32.add");
let load_instr = match self.emit_type(field_ty) {
"f32" => "f32.load",
"f64" => "f64.load",
"i64" => "i64.load",
_ => "i32.load",
};
self.emit_line(load_instr);
}
MirRValue::VariantField {
base,
variant_name,
field_index,
field_ty,
} => {
self.emit_line(&format!(
";; variant field {}.{}",
variant_name, field_index
));
self.gen_value(base, func)?;
let elem_size = self.type_size(field_ty);
let offset = 4 + (*field_index) * elem_size;
self.emit_line(&format!("i32.const {}", offset));
self.emit_line("i32.add");
let load_instr = match self.emit_type(field_ty) {
"f32" => "f32.load",
"f64" => "f64.load",
"i64" => "i64.load",
_ => "i32.load",
};
self.emit_line(load_instr);
}
MirRValue::IndexAccess {
base,
index,
elem_ty,
} => {
self.gen_value(base, func)?;
self.gen_value(index, func)?;
let elem_size = self.type_size(elem_ty);
self.emit_line(&format!("i32.const {} ;; element size", elem_size));
self.emit_line("i32.mul");
self.emit_line("i32.add");
let load_instr = match self.emit_type(elem_ty) {
"f32" => "f32.load",
"f64" => "f64.load",
"i64" => "i64.load",
_ => "i32.load",
};
self.emit_line(load_instr);
}
MirRValue::Deref { ptr, pointee_ty } => {
self.gen_value(ptr, func)?;
let load_instr = match self.emit_type(pointee_ty) {
"f32" => "f32.load",
"f64" => "f64.load",
"i64" => "i64.load",
_ => "i32.load",
};
self.emit_line(load_instr);
}
MirRValue::TextureSample { .. } => {
self.emit_line(";; texture_sample: GPU-only, not supported in WASM");
self.emit_line("i32.const 0");
}
}
Ok(())
}
fn gen_value(&mut self, value: &MirValue, _func: &MirFunction) -> CodegenResult<()> {
match value {
MirValue::Local(id) => {
let name = self
.local_names
.get(id)
.ok_or_else(|| CodegenError::Internal(format!("Unknown local: {:?}", id)))?
.clone();
self.emit_line(&format!("local.get {}", name));
}
MirValue::Const(c) => {
self.gen_const(c)?;
}
MirValue::Global(name) => {
self.emit_line(&format!("global.get ${}", name));
}
MirValue::Function(name) => {
self.emit_line(&format!("i32.const 0 ;; func ${}", name));
}
}
Ok(())
}
fn gen_const(&mut self, c: &MirConst) -> CodegenResult<()> {
match c {
MirConst::Bool(b) => {
self.emit_line(&format!("i32.const {}", if *b { 1 } else { 0 }));
}
MirConst::Int(v, ty) => {
let wasm_ty = self.emit_type(ty);
self.emit_line(&format!("{}.const {}", wasm_ty, v));
}
MirConst::Uint(v, ty) => {
let wasm_ty = self.emit_type(ty);
self.emit_line(&format!("{}.const {}", wasm_ty, v));
}
MirConst::Float(v, ty) => {
let wasm_ty = self.emit_type(ty);
self.emit_line(&format!("{}.const {}", wasm_ty, v));
}
MirConst::Str(idx) => {
self.emit_line(&format!("global.get $__str{}", idx));
}
MirConst::ByteStr(_) => {
self.emit_line("i32.const 0 ;; bytestr");
}
MirConst::Null(_) => {
self.emit_line("i32.const 0");
}
MirConst::Unit => {
self.emit_line("i32.const 0");
}
MirConst::Zeroed(_) => {
self.emit_line("i32.const 0");
}
MirConst::Undef(_) => {
self.emit_line("i32.const 0 ;; undef");
}
MirConst::Struct(_, _) => {
self.emit_line("i32.const 0 ;; struct const stub");
}
}
Ok(())
}
fn gen_place_addr(&mut self, place: &MirPlace, _func: &MirFunction) -> CodegenResult<()> {
let name = self
.local_names
.get(&place.local)
.ok_or_else(|| CodegenError::Internal(format!("Unknown local: {:?}", place.local)))?
.clone();
self.emit_line(&format!("local.get {}", name));
for proj in &place.projections {
match proj {
PlaceProjection::Deref => {
self.emit_line("i32.load");
}
PlaceProjection::Field(idx, _ty) => {
self.emit_line(&format!("i32.const {}", idx * 4));
self.emit_line("i32.add");
}
PlaceProjection::Index(idx_local) => {
let idx_name = self
.local_names
.get(idx_local)
.ok_or_else(|| {
CodegenError::Internal(format!("Unknown local: {:?}", idx_local))
})?
.clone();
self.emit_line(&format!("local.get {}", idx_name));
self.emit_line("i32.const 4");
self.emit_line("i32.mul");
self.emit_line("i32.add");
}
PlaceProjection::ConstantIndex { offset, from_end } => {
if *from_end {
self.emit_line(&format!("i32.const {}", offset * 4));
self.emit_line("i32.sub");
} else {
self.emit_line(&format!("i32.const {}", offset * 4));
self.emit_line("i32.add");
}
}
PlaceProjection::Subslice { from, .. } => {
self.emit_line(&format!("i32.const {}", from * 4));
self.emit_line("i32.add");
}
PlaceProjection::Downcast(_) => {
self.emit_line("i32.const 4");
self.emit_line("i32.add");
}
}
}
Ok(())
}
fn emit_br_to(&mut self, target: BlockId) {
if target.0 == 0 {
self.emit_line("br $loop_top ;; back to bb0");
} else {
let label = self
.block_labels
.get(&target)
.cloned()
.unwrap_or_else(|| format!("$bb{}", target.0));
self.emit_line(&format!("br {} ;; goto bb{}", label, target.0));
}
}
fn gen_terminator(
&mut self,
term: &MirTerminator,
func: &MirFunction,
_is_last: bool,
) -> CodegenResult<()> {
match term {
MirTerminator::Goto(target) => {
self.emit_br_to(*target);
}
MirTerminator::If {
cond,
then_block,
else_block,
} => {
self.gen_value(cond, func)?;
self.emit_line("(if");
self.indent += 1;
self.emit_line("(then");
self.indent += 1;
self.emit_br_to(*then_block);
self.indent -= 1;
self.emit_line(")");
self.emit_line("(else");
self.indent += 1;
self.emit_br_to(*else_block);
self.indent -= 1;
self.emit_line(")");
self.indent -= 1;
self.emit_line(")");
}
MirTerminator::Switch {
value,
targets,
default,
} => {
self.emit_line(";; switch");
self.gen_value(value, func)?;
self.emit_line("local.set $__switch_val");
for (const_val, target) in targets {
let val_str = match const_val {
MirConst::Int(v, _) => v.to_string(),
MirConst::Uint(v, _) => v.to_string(),
_ => "0".to_string(),
};
self.emit_line("local.get $__switch_val");
self.emit_line(&format!("i32.const {}", val_str));
self.emit_line("i32.eq");
self.emit_line("(if");
self.indent += 1;
self.emit_line("(then");
self.indent += 1;
self.emit_br_to(*target);
self.indent -= 1;
self.emit_line(")");
self.indent -= 1;
self.emit_line(")");
}
self.emit_br_to(*default);
}
MirTerminator::Call {
func: callee,
args,
dest,
target,
..
} => {
for arg in args {
self.gen_value(arg, func)?;
}
match callee {
MirValue::Function(name) => {
self.emit_line(&format!("call ${}", name));
}
_ => {
self.gen_value(callee, func)?;
self.emit_line("call_indirect");
}
}
if let Some(dest_local) = dest {
let dest_name = self
.local_names
.get(dest_local)
.ok_or_else(|| {
CodegenError::Internal(format!("Unknown local: {:?}", dest_local))
})?
.clone();
self.emit_line(&format!("local.set {}", dest_name));
}
if let Some(target) = target {
self.emit_br_to(*target);
}
}
MirTerminator::Return(value) => {
if let Some(val) = value {
self.gen_value(val, func)?;
}
self.emit_line("return");
}
MirTerminator::Unreachable => {
self.emit_line("unreachable");
}
MirTerminator::Drop { target, .. } => {
self.emit_line(";; drop (no-op)");
self.emit_br_to(*target);
}
MirTerminator::Assert {
cond,
expected,
msg,
target,
..
} => {
self.gen_value(cond, func)?;
if !*expected {
self.emit_line("i32.eqz");
}
self.emit_line("(if");
self.indent += 1;
self.emit_line("(then)");
self.emit_line(&format!("(else ;; assert failed: {}", msg));
self.emit_line(" unreachable");
self.emit_line(")");
self.indent -= 1;
self.emit_line(")");
self.emit_br_to(*target);
}
MirTerminator::Resume => {
self.emit_line(";; resume unwinding");
self.emit_line("unreachable");
}
MirTerminator::Abort => {
if self.wasi {
self.emit_line("i32.const 1");
self.emit_line("call $__wasi_proc_exit");
}
self.emit_line("unreachable");
}
}
Ok(())
}
fn gen_cast(&mut self, kind: CastKind, from: &MirType, to: &MirType) -> CodegenResult<()> {
let from_wasm = self.emit_type(from);
let to_wasm = self.emit_type(to);
match kind {
CastKind::IntToInt => {
match (from_wasm, to_wasm) {
("i32", "i64") => {
if from.is_signed() {
self.emit_line("i64.extend_i32_s");
} else {
self.emit_line("i64.extend_i32_u");
}
}
("i64", "i32") => {
self.emit_line("i32.wrap_i64");
}
_ => {} }
}
CastKind::FloatToFloat => match (from_wasm, to_wasm) {
("f32", "f64") => {
self.emit_line("f64.promote_f32");
}
("f64", "f32") => {
self.emit_line("f32.demote_f64");
}
_ => {}
},
CastKind::IntToFloat => {
let convert = if from.is_signed() {
format!(
"{}.convert_i{}_s",
to_wasm,
if from_wasm == "i64" { "64" } else { "32" }
)
} else {
format!(
"{}.convert_i{}_u",
to_wasm,
if from_wasm == "i64" { "64" } else { "32" }
)
};
self.emit_line(&convert);
}
CastKind::FloatToInt => {
let trunc = if to.is_signed() {
format!("{}.trunc_{}_s", to_wasm, from_wasm)
} else {
format!("{}.trunc_{}_u", to_wasm, from_wasm)
};
self.emit_line(&trunc);
}
CastKind::PtrToInt | CastKind::IntToPtr | CastKind::PtrToPtr | CastKind::FnToPtr => {
}
CastKind::Transmute => {
match (from_wasm, to_wasm) {
("f32", "i32") => self.emit_line("i32.reinterpret_f32"),
("i32", "f32") => self.emit_line("f32.reinterpret_i32"),
("f64", "i64") => self.emit_line("i64.reinterpret_f64"),
("i64", "f64") => self.emit_line("f64.reinterpret_i64"),
_ => {}
}
}
}
Ok(())
}
fn wasm_binop(&self, op: BinOp, ty: &MirType) -> String {
let wasm_ty = self.emit_type(ty);
let is_float = ty.is_float();
let is_signed = ty.is_signed();
match op {
BinOp::Add => format!("{}.add", wasm_ty),
BinOp::Sub => format!("{}.sub", wasm_ty),
BinOp::Mul => format!("{}.mul", wasm_ty),
BinOp::Div => {
if is_float {
format!("{}.div", wasm_ty)
} else if is_signed {
format!("{}.div_s", wasm_ty)
} else {
format!("{}.div_u", wasm_ty)
}
}
BinOp::Rem => {
if is_float {
format!(";; TODO: {}.rem", wasm_ty)
} else if is_signed {
format!("{}.rem_s", wasm_ty)
} else {
format!("{}.rem_u", wasm_ty)
}
}
BinOp::BitAnd => format!("{}.and", wasm_ty),
BinOp::BitOr => format!("{}.or", wasm_ty),
BinOp::BitXor => format!("{}.xor", wasm_ty),
BinOp::Shl => format!("{}.shl", wasm_ty),
BinOp::Shr => {
if is_signed {
format!("{}.shr_s", wasm_ty)
} else {
format!("{}.shr_u", wasm_ty)
}
}
BinOp::Eq => {
if is_float {
format!("{}.eq", wasm_ty)
} else {
format!("{}.eq", wasm_ty)
}
}
BinOp::Ne => {
if is_float {
format!("{}.ne", wasm_ty)
} else {
format!("{}.ne", wasm_ty)
}
}
BinOp::Lt => {
if is_float {
format!("{}.lt", wasm_ty)
} else if is_signed {
format!("{}.lt_s", wasm_ty)
} else {
format!("{}.lt_u", wasm_ty)
}
}
BinOp::Le => {
if is_float {
format!("{}.le", wasm_ty)
} else if is_signed {
format!("{}.le_s", wasm_ty)
} else {
format!("{}.le_u", wasm_ty)
}
}
BinOp::Gt => {
if is_float {
format!("{}.gt", wasm_ty)
} else if is_signed {
format!("{}.gt_s", wasm_ty)
} else {
format!("{}.gt_u", wasm_ty)
}
}
BinOp::Ge => {
if is_float {
format!("{}.ge", wasm_ty)
} else if is_signed {
format!("{}.ge_s", wasm_ty)
} else {
format!("{}.ge_u", wasm_ty)
}
}
BinOp::AddChecked | BinOp::AddWrapping | BinOp::AddSaturating => {
format!("{}.add", wasm_ty)
}
BinOp::SubChecked | BinOp::SubWrapping | BinOp::SubSaturating => {
format!("{}.sub", wasm_ty)
}
BinOp::MulChecked | BinOp::MulWrapping => format!("{}.mul", wasm_ty),
BinOp::Pow => {
format!("call $__quanta_pow_{}", wasm_ty)
}
}
}
fn infer_value_type(&self, value: &MirValue, func: &MirFunction) -> CodegenResult<MirType> {
match value {
MirValue::Local(id) => func
.locals
.iter()
.find(|l| l.id == *id)
.map(|l| l.ty.clone())
.ok_or_else(|| CodegenError::Internal(format!("Unknown local: {:?}", id))),
MirValue::Const(c) => match c {
MirConst::Bool(_) => Ok(MirType::Bool),
MirConst::Int(_, ty) => Ok(ty.clone()),
MirConst::Uint(_, ty) => Ok(ty.clone()),
MirConst::Float(_, ty) => Ok(ty.clone()),
MirConst::Str(_) => Ok(MirType::Ptr(Box::new(MirType::Int(IntSize::I8, false)))),
MirConst::ByteStr(_) => {
Ok(MirType::Ptr(Box::new(MirType::Int(IntSize::I8, false))))
}
MirConst::Null(ty) => Ok(ty.clone()),
MirConst::Unit => Ok(MirType::Void),
MirConst::Zeroed(ty) => Ok(ty.clone()),
MirConst::Undef(ty) => Ok(ty.clone()),
MirConst::Struct(name, _) => Ok(MirType::Struct(name.clone())),
},
MirValue::Global(_) => Ok(MirType::i32()),
MirValue::Function(_) => Ok(MirType::i32()),
}
}
fn gen_default_value(&mut self, ty: &MirType) {
match ty {
MirType::Float(FloatSize::F32) => self.emit_line("f32.const 0.0"),
MirType::Float(FloatSize::F64) => self.emit_line("f64.const 0.0"),
MirType::Int(IntSize::I64, _) | MirType::Int(IntSize::I128, _) => {
self.emit_line("i64.const 0")
}
_ => self.emit_line("i32.const 0"),
}
}
fn gen_wasi_start(&mut self, module: &MirModule) {
let has_main = module.functions.iter().any(|f| f.name.as_ref() == "main");
if has_main {
self.emit_line(";; =======================================================");
self.emit_line(";; WASI Entry Point");
self.emit_line(";; =======================================================");
self.emit_line("(func (export \"_start\")");
self.indent += 1;
self.emit_line("call $main");
self.emit_line("drop ;; ignore return value");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
}
}
fn gen_wasi_helpers(&mut self) {
self.emit_line(";; =======================================================");
self.emit_line(";; WASI Helper Functions");
self.emit_line(";; =======================================================");
self.output.push('\n');
self.emit_line("(func $__quanta_print (param $ptr i32) (param $len i32)");
self.indent += 1;
self.emit_line("(local $iov i32)");
self.emit_line("(local $written i32)");
self.emit_line(";; Build iovec at heap");
self.emit_line("global.get $__heap_base");
self.emit_line("local.set $iov");
self.emit_line(";; iov.buf = ptr");
self.emit_line("local.get $iov");
self.emit_line("local.get $ptr");
self.emit_line("i32.store");
self.emit_line(";; iov.len = len");
self.emit_line("local.get $iov");
self.emit_line("i32.const 4");
self.emit_line("i32.add");
self.emit_line("local.get $len");
self.emit_line("i32.store");
self.emit_line(";; fd_write(stdout=1, iov, 1, &written)");
self.emit_line("i32.const 1 ;; stdout");
self.emit_line("local.get $iov");
self.emit_line("i32.const 1 ;; iovs_len");
self.emit_line("local.get $iov");
self.emit_line("i32.const 8");
self.emit_line("i32.add");
self.emit_line("call $__wasi_fd_write");
self.emit_line("drop");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
self.emit_line("(func $__quanta_println (param $ptr i32) (param $len i32)");
self.indent += 1;
self.emit_line("local.get $ptr");
self.emit_line("local.get $len");
self.emit_line("call $__quanta_print");
self.emit_line(";; Print newline");
self.emit_line("i32.const 1024 ;; newline address");
self.emit_line("i32.const 1");
self.emit_line("call $__quanta_print");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
self.emit_line("(data (i32.const 1024) \"\\n\")");
self.output.push('\n');
self.emit_line(";; Simple bump allocator");
self.emit_line("(func $__quanta_alloc (param $size i32) (result i32)");
self.indent += 1;
self.emit_line("(local $ptr i32)");
self.emit_line(";; Get current heap pointer");
self.emit_line("global.get $__heap_base");
self.emit_line("local.set $ptr");
self.emit_line(";; Align size to 8 bytes");
self.emit_line("local.get $size");
self.emit_line("i32.const 7");
self.emit_line("i32.add");
self.emit_line("i32.const -8 ;; 0xFFFFFFF8");
self.emit_line("i32.and");
self.emit_line(";; Bump heap pointer");
self.emit_line("global.get $__heap_base");
self.emit_line("i32.add");
self.emit_line("global.set $__heap_base");
self.emit_line(";; Return old pointer");
self.emit_line("local.get $ptr");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
self.emit_line("(func $__quanta_free (param $ptr i32)");
self.indent += 1;
self.emit_line(";; No-op for bump allocator");
self.emit_line("nop");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
if self.bulk_memory {
self.emit_line(
"(func $__quanta_memcpy (param $dest i32) (param $src i32) (param $len i32)",
);
self.indent += 1;
self.emit_line("local.get $dest");
self.emit_line("local.get $src");
self.emit_line("local.get $len");
self.emit_line("memory.copy");
self.indent -= 1;
self.emit_line(")");
} else {
self.emit_line(
"(func $__quanta_memcpy (param $dest i32) (param $src i32) (param $len i32)",
);
self.indent += 1;
self.emit_line("(local $i i32)");
self.emit_line("(block $done");
self.indent += 1;
self.emit_line("(loop $loop");
self.indent += 1;
self.emit_line("local.get $i");
self.emit_line("local.get $len");
self.emit_line("i32.ge_u");
self.emit_line("br_if $done");
self.emit_line(";; dest[i] = src[i]");
self.emit_line("local.get $dest");
self.emit_line("local.get $i");
self.emit_line("i32.add");
self.emit_line("local.get $src");
self.emit_line("local.get $i");
self.emit_line("i32.add");
self.emit_line("i32.load8_u");
self.emit_line("i32.store8");
self.emit_line(";; i++");
self.emit_line("local.get $i");
self.emit_line("i32.const 1");
self.emit_line("i32.add");
self.emit_line("local.set $i");
self.emit_line("br $loop");
self.indent -= 1;
self.emit_line(")");
self.indent -= 1;
self.emit_line(")");
self.indent -= 1;
self.emit_line(")");
}
self.output.push('\n');
if self.bulk_memory {
self.emit_line(
"(func $__quanta_memset (param $dest i32) (param $val i32) (param $len i32)",
);
self.indent += 1;
self.emit_line("local.get $dest");
self.emit_line("local.get $val");
self.emit_line("local.get $len");
self.emit_line("memory.fill");
self.indent -= 1;
self.emit_line(")");
} else {
self.emit_line(
"(func $__quanta_memset (param $dest i32) (param $val i32) (param $len i32)",
);
self.indent += 1;
self.emit_line("(local $i i32)");
self.emit_line("(block $done");
self.indent += 1;
self.emit_line("(loop $loop");
self.indent += 1;
self.emit_line("local.get $i");
self.emit_line("local.get $len");
self.emit_line("i32.ge_u");
self.emit_line("br_if $done");
self.emit_line(";; dest[i] = val");
self.emit_line("local.get $dest");
self.emit_line("local.get $i");
self.emit_line("i32.add");
self.emit_line("local.get $val");
self.emit_line("i32.store8");
self.emit_line(";; i++");
self.emit_line("local.get $i");
self.emit_line("i32.const 1");
self.emit_line("i32.add");
self.emit_line("local.set $i");
self.emit_line("br $loop");
self.indent -= 1;
self.emit_line(")");
self.indent -= 1;
self.emit_line(")");
self.indent -= 1;
self.emit_line(")");
}
self.output.push('\n');
self.emit_line("(func $__quanta_exit (param $code i32)");
self.indent += 1;
self.emit_line("local.get $code");
self.emit_line("call $__wasi_proc_exit");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
self.emit_line("(func $__quanta_abort");
self.indent += 1;
self.emit_line("i32.const 134 ;; SIGABRT");
self.emit_line("call $__wasi_proc_exit");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
self.emit_line("(func $__quanta_args_count (result i32)");
self.indent += 1;
self.emit_line("(local $argc i32)");
self.emit_line("(local $argv_buf_size i32)");
self.emit_line(";; Get args sizes");
self.emit_line("global.get $__heap_base ;; &argc");
self.emit_line("global.get $__heap_base");
self.emit_line("i32.const 4");
self.emit_line("i32.add ;; &argv_buf_size");
self.emit_line("call $__wasi_args_sizes_get");
self.emit_line("drop");
self.emit_line(";; Return argc");
self.emit_line("global.get $__heap_base");
self.emit_line("i32.load");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
self.emit_line("(func $__quanta_time_now (result i64)");
self.indent += 1;
self.emit_line("(local $time_ptr i32)");
self.emit_line(";; Allocate 8 bytes for timestamp");
self.emit_line("global.get $__heap_base");
self.emit_line("local.set $time_ptr");
self.emit_line(";; Get realtime clock");
self.emit_line("i32.const 0 ;; CLOCK_REALTIME");
self.emit_line("i64.const 1 ;; precision: 1ns");
self.emit_line("local.get $time_ptr");
self.emit_line("call $__wasi_clock_time_get");
self.emit_line("drop");
self.emit_line(";; Return timestamp");
self.emit_line("local.get $time_ptr");
self.emit_line("i64.load");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
self.emit_line("(func $__quanta_random (param $buf i32) (param $len i32) (result i32)");
self.indent += 1;
self.emit_line("local.get $buf");
self.emit_line("local.get $len");
self.emit_line("call $__wasi_random_get");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
self.emit_line("(func $__quanta_eprint (param $ptr i32) (param $len i32)");
self.indent += 1;
self.emit_line("(local $iov i32)");
self.emit_line("(local $written i32)");
self.emit_line("global.get $__heap_base");
self.emit_line("local.set $iov");
self.emit_line("local.get $iov");
self.emit_line("local.get $ptr");
self.emit_line("i32.store");
self.emit_line("local.get $iov");
self.emit_line("i32.const 4");
self.emit_line("i32.add");
self.emit_line("local.get $len");
self.emit_line("i32.store");
self.emit_line("i32.const 2 ;; stderr");
self.emit_line("local.get $iov");
self.emit_line("i32.const 1");
self.emit_line("local.get $iov");
self.emit_line("i32.const 8");
self.emit_line("i32.add");
self.emit_line("call $__wasi_fd_write");
self.emit_line("drop");
self.indent -= 1;
self.emit_line(")");
self.output.push('\n');
}
}
impl Default for WasmBackend {
fn default() -> Self {
Self::new()
}
}
impl Backend for WasmBackend {
fn generate(&mut self, mir: &MirModule) -> CodegenResult<GeneratedCode> {
self.output.clear();
self.gen_module_header(mir);
if self.wasi {
self.gen_wasi_imports();
}
self.gen_memory();
self.gen_types(mir);
self.gen_data_section(mir);
self.gen_globals(mir);
self.gen_table(mir);
if self.wasi {
self.gen_wasi_helpers();
}
self.emit_line(";; =======================================================");
self.emit_line(";; Functions");
self.emit_line(";; =======================================================");
self.output.push('\n');
for func in &mir.functions {
self.gen_function(func)?;
}
if self.wasi {
self.gen_wasi_start(mir);
}
self.indent -= 1;
self.emit_line(")");
Ok(GeneratedCode::new(
OutputFormat::Wat,
self.output.as_bytes().to_vec(),
))
}
fn target(&self) -> Target {
Target::Wasm
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_simple_module() -> MirModule {
let mut module = MirModule::new("test");
let sig = MirFnSig::new(vec![MirType::i32(), MirType::i32()], MirType::i32());
let mut func = MirFunction::new("add", sig);
func.is_public = true;
func.add_local(MirLocal {
id: LocalId(0),
name: Some("a".into()),
ty: MirType::i32(),
is_mut: false,
is_param: true,
annotations: Vec::new(),
});
func.add_local(MirLocal {
id: LocalId(1),
name: Some("b".into()),
ty: MirType::i32(),
is_mut: false,
is_param: true,
annotations: Vec::new(),
});
func.add_local(MirLocal {
id: LocalId(2),
name: Some("result".into()),
ty: MirType::i32(),
is_mut: true,
is_param: false,
annotations: Vec::new(),
});
let mut block = MirBlock::new(BlockId::ENTRY);
block.push_stmt(MirStmt::assign(
LocalId(2),
MirRValue::BinaryOp {
op: BinOp::Add,
left: MirValue::Local(LocalId(0)),
right: MirValue::Local(LocalId(1)),
},
));
block.set_terminator(MirTerminator::Return(Some(MirValue::Local(LocalId(2)))));
func.add_block(block);
module.add_function(func);
module
}
#[test]
fn test_wasm_backend_new() {
let backend = WasmBackend::new();
assert!(!backend.wasi);
assert_eq!(backend.memory_pages, 1);
}
#[test]
fn test_wasm_backend_with_wasi() {
let backend = WasmBackend::with_wasi();
assert!(backend.wasi);
assert_eq!(backend.memory_pages, 4);
}
#[test]
fn test_wasm_backend_with_options() {
let backend = WasmBackend::new()
.with_memory(8)
.with_max_memory(64)
.with_bulk_memory()
.with_simd();
assert_eq!(backend.memory_pages, 8);
assert_eq!(backend.max_memory_pages, Some(64));
assert!(backend.bulk_memory);
assert!(backend.simd);
}
#[test]
fn test_emit_type() {
let backend = WasmBackend::new();
assert_eq!(backend.emit_type(&MirType::Bool), "i32");
assert_eq!(backend.emit_type(&MirType::i32()), "i32");
assert_eq!(backend.emit_type(&MirType::i64()), "i64");
assert_eq!(backend.emit_type(&MirType::f32()), "f32");
assert_eq!(backend.emit_type(&MirType::f64()), "f64");
assert_eq!(
backend.emit_type(&MirType::Ptr(Box::new(MirType::i32()))),
"i32"
);
}
#[test]
fn test_type_size() {
let backend = WasmBackend::new();
assert_eq!(backend.type_size(&MirType::Bool), 1);
assert_eq!(backend.type_size(&MirType::i8()), 1);
assert_eq!(backend.type_size(&MirType::i16()), 2);
assert_eq!(backend.type_size(&MirType::i32()), 4);
assert_eq!(backend.type_size(&MirType::i64()), 8);
assert_eq!(backend.type_size(&MirType::f32()), 4);
assert_eq!(backend.type_size(&MirType::f64()), 8);
}
#[test]
fn test_wasm_binop() {
let backend = WasmBackend::new();
assert_eq!(backend.wasm_binop(BinOp::Add, &MirType::i32()), "i32.add");
assert_eq!(backend.wasm_binop(BinOp::Sub, &MirType::i64()), "i64.sub");
assert_eq!(backend.wasm_binop(BinOp::Mul, &MirType::f32()), "f32.mul");
assert_eq!(backend.wasm_binop(BinOp::Div, &MirType::i32()), "i32.div_s");
assert_eq!(backend.wasm_binop(BinOp::Div, &MirType::u32()), "i32.div_u");
assert_eq!(backend.wasm_binop(BinOp::Div, &MirType::f64()), "f64.div");
}
#[test]
fn test_escape_string() {
let backend = WasmBackend::new();
assert_eq!(backend.escape_string("hello"), "hello");
assert_eq!(backend.escape_string("hello\nworld"), "hello\\nworld");
assert_eq!(backend.escape_string("tab\there"), "tab\\there");
assert_eq!(backend.escape_string("quote\"here"), "quote\\\"here");
}
#[test]
fn test_generate_simple_module() {
let module = create_simple_module();
let mut backend = WasmBackend::new();
let result = backend.generate(&module);
assert!(result.is_ok());
let code = result.unwrap();
let output = code.as_string().unwrap();
assert!(output.contains("(module"));
assert!(output.contains("(func $add"));
assert!(output.contains("(export \"add\")"));
assert!(output.contains("(memory"));
}
#[test]
fn test_generate_wasi_module() {
let module = create_simple_module();
let mut backend = WasmBackend::with_wasi();
let result = backend.generate(&module);
assert!(result.is_ok());
let code = result.unwrap();
let output = code.as_string().unwrap();
assert!(output.contains("wasi_snapshot_preview1"));
assert!(output.contains("$__wasi_fd_write"));
assert!(output.contains("$__wasi_proc_exit"));
}
#[test]
fn test_backend_target() {
let backend = WasmBackend::new();
assert_eq!(backend.target(), Target::Wasm);
}
#[test]
fn test_wasi_helpers() {
let mut module = MirModule::new("test");
let sig = MirFnSig::new(vec![], MirType::i32());
let mut func = MirFunction::new("main", sig);
func.is_public = true;
let mut block = MirBlock::new(BlockId::ENTRY);
block.set_terminator(MirTerminator::Return(Some(MirValue::Const(MirConst::Int(
0,
MirType::i32(),
)))));
func.add_block(block);
module.add_function(func);
let mut backend = WasmBackend::with_wasi();
let result = backend.generate(&module);
assert!(result.is_ok());
let code = result.unwrap();
let output = code.as_string().unwrap();
assert!(output.contains("$__quanta_print"));
assert!(output.contains("$__quanta_println"));
assert!(output.contains("(export \"_start\")"));
}
}