2024-05-09 16:41:59 -05:00
|
|
|
use {crate::parser, std::fmt::Write};
|
2024-05-09 11:16:01 -05:00
|
|
|
|
2024-05-10 08:29:11 -05:00
|
|
|
type Reg = u8;
|
|
|
|
type MaskElem = u64;
|
|
|
|
|
|
|
|
const STACK_PTR: Reg = 254;
|
|
|
|
const ZERO: Reg = 0;
|
|
|
|
const RET_ADDR: Reg = 31;
|
|
|
|
const ELEM_WIDTH: usize = std::mem::size_of::<MaskElem>() * 8;
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct RegAlloc {
|
|
|
|
free: Vec<Reg>,
|
|
|
|
// TODO:use 256 bit mask instead
|
|
|
|
used: Vec<std::cmp::Reverse<Reg>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RegAlloc {
|
|
|
|
fn callee_general_purpose() -> Self {
|
|
|
|
Self {
|
|
|
|
free: (32..=253).collect(),
|
|
|
|
used: Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn allocate(&mut self) -> Reg {
|
|
|
|
let reg = self.free.pop().expect("TODO: we need to spill");
|
|
|
|
if self.used.binary_search(&std::cmp::Reverse(reg)).is_err() {
|
|
|
|
self.used.push(std::cmp::Reverse(reg));
|
|
|
|
}
|
|
|
|
reg
|
|
|
|
}
|
|
|
|
|
|
|
|
fn free(&mut self, reg: Reg) {
|
|
|
|
self.free.push(reg);
|
|
|
|
}
|
|
|
|
}
|
2024-05-09 11:16:01 -05:00
|
|
|
|
2024-05-09 16:41:59 -05:00
|
|
|
pub struct Codegen<'a> {
|
2024-05-10 08:29:11 -05:00
|
|
|
path: &'a std::path::Path,
|
|
|
|
gpa: RegAlloc,
|
|
|
|
code: String,
|
|
|
|
data: String,
|
|
|
|
prelude_buf: String,
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
|
|
|
|
2024-05-09 16:41:59 -05:00
|
|
|
impl<'a> Codegen<'a> {
|
2024-05-10 08:29:11 -05:00
|
|
|
pub fn new() -> Self {
|
2024-05-09 11:16:01 -05:00
|
|
|
Self {
|
2024-05-10 08:29:11 -05:00
|
|
|
path: std::path::Path::new(""),
|
|
|
|
gpa: RegAlloc::callee_general_purpose(),
|
|
|
|
code: String::new(),
|
|
|
|
data: String::new(),
|
|
|
|
prelude_buf: String::new(),
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-10 08:29:11 -05:00
|
|
|
pub fn file(&mut self, path: &'a std::path::Path, exprs: &[parser::Expr]) -> std::fmt::Result {
|
|
|
|
self.path = path;
|
2024-05-09 16:41:59 -05:00
|
|
|
for expr in exprs {
|
|
|
|
self.expr(expr)?;
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
2024-05-09 16:41:59 -05:00
|
|
|
Ok(())
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
|
|
|
|
2024-05-09 16:41:59 -05:00
|
|
|
fn expr(&mut self, expr: &parser::Expr) -> std::fmt::Result {
|
|
|
|
use parser::Expr as E;
|
2024-05-10 08:29:11 -05:00
|
|
|
match *expr {
|
2024-05-09 16:41:59 -05:00
|
|
|
E::Decl {
|
|
|
|
name,
|
|
|
|
val:
|
|
|
|
E::Closure {
|
|
|
|
ret: E::Ident { name: "void" },
|
|
|
|
body,
|
|
|
|
},
|
|
|
|
} => {
|
|
|
|
writeln!(self.code, "{name}:")?;
|
2024-05-10 08:29:11 -05:00
|
|
|
let fn_start = self.code.len();
|
|
|
|
self.expr(body)?;
|
|
|
|
self.write_fn_prelude(fn_start)
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
2024-05-09 16:41:59 -05:00
|
|
|
E::Return { val: None } => self.ret(),
|
|
|
|
E::Block { stmts } => {
|
|
|
|
for stmt in stmts {
|
|
|
|
self.expr(stmt)?;
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
2024-05-09 16:41:59 -05:00
|
|
|
Ok(())
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
2024-05-09 16:41:59 -05:00
|
|
|
ast => unimplemented!("{:?}", ast),
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-10 08:29:11 -05:00
|
|
|
fn write_fn_prelude(&mut self, fn_start: usize) -> std::fmt::Result {
|
|
|
|
self.prelude_buf.clear();
|
|
|
|
// TODO: avoid clone here
|
|
|
|
for reg in self.gpa.used.clone().iter() {
|
|
|
|
stack_push(&mut self.prelude_buf, reg.0, 8)?;
|
|
|
|
}
|
2024-05-09 11:16:01 -05:00
|
|
|
|
2024-05-10 08:29:11 -05:00
|
|
|
self.code.insert_str(fn_start, &self.prelude_buf);
|
|
|
|
self.gpa = RegAlloc::callee_general_purpose();
|
2024-05-09 11:16:01 -05:00
|
|
|
|
2024-05-10 08:29:11 -05:00
|
|
|
Ok(())
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
|
|
|
|
2024-05-09 16:41:59 -05:00
|
|
|
fn ret(&mut self) -> std::fmt::Result {
|
2024-05-10 08:29:11 -05:00
|
|
|
for reg in self.gpa.used.clone().iter().rev() {
|
|
|
|
stack_pop(&mut self.code, reg.0, 8)?;
|
|
|
|
}
|
|
|
|
ret(&mut self.code)
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
|
|
|
|
2024-05-10 08:29:11 -05:00
|
|
|
pub fn dump(self, mut out: impl std::fmt::Write) -> std::fmt::Result {
|
|
|
|
prelude(&mut out)?;
|
2024-05-09 16:41:59 -05:00
|
|
|
writeln!(out, "{}", self.code)?;
|
|
|
|
writeln!(out, "{}", self.data)
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-10 08:29:11 -05:00
|
|
|
fn stack_push(out: &mut impl std::fmt::Write, value: Reg, size: usize) -> std::fmt::Result {
|
|
|
|
writeln!(out, " st r{value}, r{STACK_PTR}, r{ZERO}, {size}")?;
|
|
|
|
writeln!(
|
|
|
|
out,
|
|
|
|
" addi{} r{STACK_PTR}, r{STACK_PTR}, {size}",
|
|
|
|
size * 8
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn stack_pop(out: &mut impl std::fmt::Write, value: Reg, size: usize) -> std::fmt::Result {
|
|
|
|
writeln!(
|
|
|
|
out,
|
|
|
|
" subi{} r{STACK_PTR}, r{STACK_PTR}, {size}",
|
|
|
|
size * 8
|
|
|
|
)?;
|
|
|
|
writeln!(out, " ld r{value}, r{STACK_PTR}, r{ZERO}, {size}")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn call(out: &mut impl std::fmt::Write, func: &str) -> std::fmt::Result {
|
|
|
|
stack_push(out, RET_ADDR, 8)?;
|
|
|
|
jump_label(out, func)?;
|
|
|
|
stack_pop(out, RET_ADDR, 8)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ret(out: &mut impl std::fmt::Write) -> std::fmt::Result {
|
|
|
|
writeln!(out, " jala r{ZERO}, r{RET_ADDR}, 0")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn jump_label(out: &mut impl std::fmt::Write, label: &str) -> std::fmt::Result {
|
|
|
|
writeln!(out, " jal r{RET_ADDR}, r{ZERO}, {label}")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn prelude(out: &mut impl std::fmt::Write) -> std::fmt::Result {
|
|
|
|
writeln!(out, "start:")?;
|
|
|
|
writeln!(out, " jal r{RET_ADDR}, r{ZERO}, main")?;
|
|
|
|
writeln!(out, " tx")
|
|
|
|
}
|
|
|
|
|
2024-05-09 16:41:59 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2024-05-10 08:29:11 -05:00
|
|
|
use std::io::Write;
|
|
|
|
|
|
|
|
struct TestMem;
|
|
|
|
|
|
|
|
impl hbvm::mem::Memory for TestMem {
|
|
|
|
#[inline]
|
|
|
|
unsafe fn load(
|
|
|
|
&mut self,
|
|
|
|
addr: hbvm::mem::Address,
|
|
|
|
target: *mut u8,
|
|
|
|
count: usize,
|
|
|
|
) -> Result<(), hbvm::mem::LoadError> {
|
|
|
|
unsafe { core::ptr::copy(addr.get() as *const u8, target, count) }
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
unsafe fn store(
|
|
|
|
&mut self,
|
|
|
|
addr: hbvm::mem::Address,
|
|
|
|
source: *const u8,
|
|
|
|
count: usize,
|
|
|
|
) -> Result<(), hbvm::mem::StoreError> {
|
|
|
|
unsafe { core::ptr::copy(source, addr.get() as *mut u8, count) }
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
unsafe fn prog_read<T: Copy>(&mut self, addr: hbvm::mem::Address) -> T {
|
|
|
|
unsafe { core::ptr::read(addr.get() as *const T) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-09 16:41:59 -05:00
|
|
|
fn generate(input: &'static str, output: &mut String) {
|
2024-05-10 08:29:11 -05:00
|
|
|
let path = std::path::Path::new("test");
|
|
|
|
let arena = crate::parser::Arena::default();
|
|
|
|
let mut buffer = Vec::new();
|
|
|
|
let mut parser = super::parser::Parser::new(input, path, &arena, &mut buffer);
|
2024-05-09 16:41:59 -05:00
|
|
|
let exprs = parser.file();
|
2024-05-10 08:29:11 -05:00
|
|
|
let mut codegen = super::Codegen::new();
|
|
|
|
codegen.file(path, &exprs).unwrap();
|
|
|
|
codegen.dump(&mut *output).unwrap();
|
|
|
|
|
|
|
|
let mut proc = std::process::Command::new("/usr/bin/hbas")
|
|
|
|
.stdin(std::process::Stdio::piped())
|
|
|
|
.stdout(std::process::Stdio::piped())
|
|
|
|
.spawn()
|
|
|
|
.unwrap();
|
|
|
|
proc.stdin
|
|
|
|
.as_mut()
|
|
|
|
.unwrap()
|
|
|
|
.write_all(output.as_bytes())
|
|
|
|
.unwrap();
|
|
|
|
let out = proc.wait_with_output().unwrap();
|
|
|
|
|
|
|
|
if !out.status.success() {
|
|
|
|
panic!(
|
|
|
|
"hbas failed with status: {}\n{}",
|
|
|
|
out.status,
|
|
|
|
String::from_utf8_lossy(&out.stderr)
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
use std::fmt::Write;
|
|
|
|
|
|
|
|
let mut stack = [0_u64; 1024];
|
|
|
|
|
|
|
|
for b in &out.stdout {
|
|
|
|
writeln!(output, "{:02x}", b).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut vm = unsafe {
|
|
|
|
hbvm::Vm::<TestMem, 0>::new(
|
|
|
|
TestMem,
|
|
|
|
hbvm::mem::Address::new(out.stdout.as_ptr() as u64),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
vm.write_reg(super::STACK_PTR, stack.as_mut_ptr() as u64);
|
|
|
|
|
|
|
|
let stat = loop {
|
|
|
|
match vm.run() {
|
|
|
|
Ok(hbvm::VmRunOk::End) => break Ok(()),
|
|
|
|
Ok(ev) => writeln!(output, "ev: {:?}", ev).unwrap(),
|
|
|
|
Err(e) => break Err(e),
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
writeln!(output, "ret: {:?}", vm.read_reg(0)).unwrap();
|
|
|
|
writeln!(output, "status: {:?}", stat).unwrap();
|
|
|
|
}
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
|
|
|
|
2024-05-09 16:41:59 -05:00
|
|
|
crate::run_tests! { generate:
|
|
|
|
example => include_str!("../examples/main_fn.hb");
|
2024-05-09 11:16:01 -05:00
|
|
|
}
|
|
|
|
}
|