making functions example pass

This commit is contained in:
mlokr 2024-09-04 23:46:32 +02:00
parent 7d8a7c142a
commit 4c95c767b1
7 changed files with 606 additions and 73 deletions

View file

@ -8,7 +8,7 @@ use {
parser::{self, find_symbol, idfl, CtorField, Expr, ExprRef, FileId, Pos}, parser::{self, find_symbol, idfl, CtorField, Expr, ExprRef, FileId, Pos},
HashMap, HashMap,
}, },
std::{fmt::Display, ops::Range, rc::Rc, usize}, std::{fmt::Display, ops::Range, rc::Rc},
}; };
type Offset = u32; type Offset = u32;

View file

@ -81,6 +81,7 @@ mod log {
Wrn, Wrn,
Inf, Inf,
Dbg, Dbg,
Trc,
} }
pub const LOG_LEVEL: Level = match option_env!("LOG_LEVEL") { pub const LOG_LEVEL: Level = match option_env!("LOG_LEVEL") {
@ -89,6 +90,7 @@ mod log {
b'w' => Level::Wrn, b'w' => Level::Wrn,
b'i' => Level::Inf, b'i' => Level::Inf,
b'd' => Level::Dbg, b'd' => Level::Dbg,
b't' => Level::Trc,
_ => panic!("Invalid log level."), _ => panic!("Invalid log level."),
}, },
None => { None => {
@ -118,9 +120,10 @@ mod log {
macro_rules! wrn { ($($arg:tt)*) => { $crate::log::log!($crate::log::Level::Wrn, $($arg)*) }; } macro_rules! wrn { ($($arg:tt)*) => { $crate::log::log!($crate::log::Level::Wrn, $($arg)*) }; }
macro_rules! inf { ($($arg:tt)*) => { $crate::log::log!($crate::log::Level::Inf, $($arg)*) }; } macro_rules! inf { ($($arg:tt)*) => { $crate::log::log!($crate::log::Level::Inf, $($arg)*) }; }
macro_rules! dbg { ($($arg:tt)*) => { $crate::log::log!($crate::log::Level::Dbg, $($arg)*) }; } macro_rules! dbg { ($($arg:tt)*) => { $crate::log::log!($crate::log::Level::Dbg, $($arg)*) }; }
macro_rules! trc { ($($arg:tt)*) => { $crate::log::log!($crate::log::Level::Trc, $($arg)*) }; }
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use {dbg, err, inf, log, wrn}; pub(crate) use {dbg, err, inf, log, trc, wrn};
} }
#[inline] #[inline]
@ -615,6 +618,7 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Vec<Ast>> {
} }
type HashMap<K, V> = std::collections::HashMap<K, V, std::hash::BuildHasherDefault<FnvHasher>>; type HashMap<K, V> = std::collections::HashMap<K, V, std::hash::BuildHasherDefault<FnvHasher>>;
type _HashSet<K> = std::collections::HashSet<K, std::hash::BuildHasherDefault<FnvHasher>>;
struct FnvHasher(u64); struct FnvHasher(u64);
@ -654,7 +658,19 @@ pub fn run_test(
} }
let mut output = String::new(); let mut output = String::new();
test(ident, input, &mut output); {
struct DumpOut<'a>(&'a mut String);
impl Drop for DumpOut<'_> {
fn drop(&mut self) {
if std::thread::panicking() {
println!("{}", self.0);
}
}
}
let dump = DumpOut(&mut output);
test(ident, input, dump.0);
}
let mut root = PathBuf::from( let mut root = PathBuf::from(
std::env::var("PT_TEST_ROOT") std::env::var("PT_TEST_ROOT")

View file

@ -59,7 +59,7 @@ mod reg {
impl Drop for Id { impl Drop for Id {
fn drop(&mut self) { fn drop(&mut self) {
if !std::thread::panicking() && self.1 { if !std::thread::panicking() && self.1 {
unreachable!("reg id leaked: {:?}", self.0); panic!("reg id leaked: {:?}", self.0);
} }
} }
} }
@ -450,6 +450,7 @@ impl Nodes {
fn clear(&mut self) { fn clear(&mut self) {
self.values.clear(); self.values.clear();
self.lookup.clear();
self.free = u32::MAX; self.free = u32::MAX;
} }
@ -462,15 +463,15 @@ impl Nodes {
let mut inputs = [NILL; MAX_INPUTS]; let mut inputs = [NILL; MAX_INPUTS];
inputs[..inps.len()].copy_from_slice(&inps); inputs[..inps.len()].copy_from_slice(&inps);
if let Some(&id) = self.lookup.get(&(kind, inputs)) { if let Some(&id) = self.lookup.get(&(kind.clone(), inputs)) {
debug_assert_eq!(self[id].kind, kind); debug_assert_eq!(&self[id].kind, &kind);
debug_assert_eq!(self[id].inputs, inputs); debug_assert_eq!(self[id].inputs, inputs);
return id; return id;
} }
let id = self.add(Node { let id = self.add(Node {
inputs, inputs,
kind, kind: kind.clone(),
loc: Default::default(), loc: Default::default(),
depth: u32::MAX, depth: u32::MAX,
lock_rc: 0, lock_rc: 0,
@ -511,7 +512,7 @@ impl Nodes {
self[inp].outputs.swap_remove(index); self[inp].outputs.swap_remove(index);
self.remove(inp); self.remove(inp);
} }
let res = self.lookup.remove(&(self[target].kind, self[target].inputs)); let res = self.lookup.remove(&(self[target].kind.clone(), self[target].inputs));
debug_assert_eq!(res, Some(target)); debug_assert_eq!(res, Some(target));
self.remove_low(target); self.remove_low(target);
} }
@ -524,6 +525,7 @@ impl Nodes {
Kind::Return => {} Kind::Return => {}
Kind::Tuple { .. } => {} Kind::Tuple { .. } => {}
Kind::ConstInt { .. } => {} Kind::ConstInt { .. } => {}
Kind::Call { .. } => {}
} }
None None
} }
@ -547,8 +549,8 @@ impl Nodes {
} }
} }
if let (Kind::ConstInt { value: a }, Kind::ConstInt { value: b }) = if let (&Kind::ConstInt { value: a }, &Kind::ConstInt { value: b }) =
(self[lhs].kind, self[rhs].kind) (&self[lhs].kind, &self[rhs].kind)
{ {
return Some(self.new_node( return Some(self.new_node(
self[target].ty, self[target].ty,
@ -613,7 +615,7 @@ impl Nodes {
} }
if changed { if changed {
return Some(self.new_node(self[target].ty, self[target].kind, [lhs, rhs])); return Some(self.new_node(self[target].ty, self[target].kind.clone(), [lhs, rhs]));
} }
None None
@ -641,12 +643,13 @@ impl Nodes {
} }
fn modify_input(&mut self, target: Nid, inp_index: usize, with: Nid) -> Nid { fn modify_input(&mut self, target: Nid, inp_index: usize, with: Nid) -> Nid {
let out = self.lookup.remove(&(self[target].kind, self[target].inputs)); let out = self.lookup.remove(&(self[target].kind.clone(), self[target].inputs));
debug_assert!(out == Some(target)); debug_assert!(out == Some(target));
debug_assert_ne!(self[target].inputs[inp_index], with); debug_assert_ne!(self[target].inputs[inp_index], with);
self[target].inputs[inp_index] = with; self[target].inputs[inp_index] = with;
if let Err(other) = self.lookup.try_insert((self[target].kind, self[target].inputs), target) if let Err(other) =
self.lookup.try_insert((self[target].kind.clone(), self[target].inputs), target)
{ {
let rpl = *other.entry.get(); let rpl = *other.entry.get();
self.replace(target, rpl); self.replace(target, rpl);
@ -722,13 +725,46 @@ impl Nodes {
self.fmt(f, o, rcs)?; self.fmt(f, o, rcs)?;
} }
} }
Kind::Call { func, ref args } => {
if is_ready() {
write!(f, "{}: call {}(", node, func)?;
for (i, &value) in args.iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
self.fmt(f, value, rcs)?;
}
writeln!(f, ")")?;
for &o in &self[node].outputs {
if self.is_cfg(o) {
self.fmt(f, o, rcs)?;
}
}
} else {
write!(f, "call{node}")?;
}
}
} }
Ok(()) Ok(())
} }
fn is_cfg(&self, o: Nid) -> bool { fn is_cfg(&self, o: Nid) -> bool {
matches!(self[o].kind, Kind::Start | Kind::End | Kind::Return | Kind::Tuple { .. }) matches!(
self[o].kind,
Kind::Start | Kind::End | Kind::Return | Kind::Tuple { .. } | Kind::Call { .. }
)
}
fn check_final_integrity(&self) {
for slot in &self.values {
match slot {
PoolSlot::Value(node) => {
debug_assert_eq!(node.lock_rc, 0);
}
PoolSlot::Next(_) => {}
}
}
} }
} }
@ -758,7 +794,7 @@ enum PoolSlot {
Next(u32), Next(u32),
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(u8)] #[repr(u8)]
pub enum Kind { pub enum Kind {
Start, Start,
@ -767,6 +803,7 @@ pub enum Kind {
ConstInt { value: i64 }, ConstInt { value: i64 },
Tuple { index: u32 }, Tuple { index: u32 },
BinOp { op: lexer::TokenKind }, BinOp { op: lexer::TokenKind },
Call { func: ty::Func, args: Vec<Nid> },
} }
impl Kind { impl Kind {
@ -864,6 +901,12 @@ impl ItemCtx {
fn emit(&mut self, instr: (usize, [u8; instrs::MAX_SIZE])) { fn emit(&mut self, instr: (usize, [u8; instrs::MAX_SIZE])) {
emit(&mut self.code, instr); emit(&mut self.code, instr);
} }
fn free_loc(&mut self, loc: impl Into<Option<Loc>>) {
if let Some(loc) = loc.into() {
self.regs.free(loc.reg);
}
}
} }
fn emit(out: &mut Vec<u8>, (len, instr): (usize, [u8; instrs::MAX_SIZE])) { fn emit(out: &mut Vec<u8>, (len, instr): (usize, [u8; instrs::MAX_SIZE])) {
@ -922,6 +965,8 @@ struct TypedReloc {
} }
struct Global { struct Global {
file: FileId,
name: Ident,
ty: ty::Id, ty: ty::Id,
offset: Offset, offset: Offset,
data: Vec<u8>, data: Vec<u8>,
@ -929,7 +974,13 @@ struct Global {
impl Default for Global { impl Default for Global {
fn default() -> Self { fn default() -> Self {
Self { ty: Default::default(), offset: u32::MAX, data: Default::default() } Self {
ty: Default::default(),
offset: u32::MAX,
data: Default::default(),
file: 0,
name: 0,
}
} }
} }
@ -941,6 +992,25 @@ struct Reloc {
width: u8, width: u8,
} }
impl Reloc {
fn new(offset: usize, sub_offset: u8, width: u8) -> Self {
Self { offset: offset as u32, sub_offset, width }
}
fn apply_jump(mut self, code: &mut [u8], to: u32, from: u32) -> i64 {
self.offset += from;
let offset = to as i64 - self.offset as i64;
self.write_offset(code, offset);
offset
}
fn write_offset(&self, code: &mut [u8], offset: i64) {
let bytes = offset.to_ne_bytes();
let slice = &mut code[self.offset as usize + self.sub_offset as usize..];
slice[..self.width as usize].copy_from_slice(&bytes[..self.width as usize]);
}
}
struct Field { struct Field {
name: Rc<str>, name: Rc<str>,
ty: ty::Id, ty: ty::Id,
@ -1131,6 +1201,11 @@ impl Ctx {
struct Loc { struct Loc {
reg: reg::Id, reg: reg::Id,
} }
impl Loc {
fn as_ref(&self) -> Loc {
Loc { reg: self.reg.as_ref() }
}
}
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct GenCtx { struct GenCtx {
@ -1165,7 +1240,82 @@ impl Codegen {
self.complete_call_graph_low(); self.complete_call_graph_low();
} }
pub fn dump_reachable(&mut self, from: ty::Func, to: &mut Vec<u8>) {} fn assemble(&mut self, to: &mut Vec<u8>) {
emit(to, instrs::jal(reg::RET_ADDR, reg::ZERO, 0));
emit(to, instrs::tx());
self.dump_reachable(0, to);
Reloc::new(0, 3, 4).apply_jump(to, self.tys.funcs[0].offset, 0);
}
fn dump_reachable(&mut self, from: ty::Func, to: &mut Vec<u8>) {
let mut frontier = vec![ty::Kind::Func(from).compress()];
while let Some(itm) = frontier.pop() {
match itm.expand() {
ty::Kind::Func(func) => {
let fuc = &mut self.tys.funcs[func as usize];
if task::unpack(fuc.offset).is_ok() {
continue;
}
fuc.offset = to.len() as _;
to.extend(&fuc.code);
frontier.extend(fuc.relocs.iter().map(|r| r.target));
}
ty::Kind::Global(glob) => {
let glb = &mut self.tys.globals[glob as usize];
if task::unpack(glb.offset).is_ok() {
continue;
}
glb.offset = to.len() as _;
to.extend(&glb.data);
}
_ => unreachable!(),
}
}
for fuc in &self.tys.funcs {
if task::unpack(fuc.offset).is_err() {
continue;
}
for rel in &fuc.relocs {
let offset = match rel.target.expand() {
ty::Kind::Func(fun) => self.tys.funcs[fun as usize].offset,
ty::Kind::Global(glo) => self.tys.globals[glo as usize].offset,
_ => unreachable!(),
};
rel.reloc.apply_jump(to, offset, fuc.offset);
}
}
}
pub fn disasm(
&mut self,
mut sluce: &[u8],
output: &mut impl std::io::Write,
) -> std::io::Result<()> {
use crate::DisasmItem;
let functions = self
.tys
.funcs
.iter()
.filter(|f| task::unpack(f.offset).is_ok())
.map(|f| {
let file = &self.files[f.file as usize];
let Expr::BinOp { left: &Expr::Ident { name, .. }, .. } = f.expr.get(file).unwrap()
else {
unreachable!()
};
(f.offset, (name, f.code.len() as u32, DisasmItem::Func))
})
.chain(self.tys.globals.iter().filter(|g| task::unpack(g.offset).is_ok()).map(|g| {
let file = &self.files[g.file as usize];
(g.offset, (file.ident_str(g.name), self.tys.size_of(g.ty), DisasmItem::Global))
}))
.collect::<HashMap<_, _>>();
crate::disasm(&mut sluce, &functions, output, |_| {})
}
fn make_func_reachable(&mut self, func: ty::Func) { fn make_func_reachable(&mut self, func: ty::Func) {
let fuc = &mut self.tys.funcs[func as usize]; let fuc = &mut self.tys.funcs[func as usize];
@ -1189,19 +1339,35 @@ impl Codegen {
} }
fn expr_ctx(&mut self, expr: &Expr, ctx: Ctx) -> Option<Nid> { fn expr_ctx(&mut self, expr: &Expr, ctx: Ctx) -> Option<Nid> {
let msg = "i know nothing about this name gal which is vired \
because we parsed succesfully";
match *expr { match *expr {
Expr::Comment { .. } => Some(NILL), Expr::Comment { .. } => Some(NILL),
Expr::Ident { pos, id, .. } => { Expr::Ident { pos, id, .. } => Some(
let msg = "i know nothing about this name gal which is vired\ self.ci
because we parsed succesfully"; .vars
Some( .iter()
self.ci .find(|v| v.id == id)
.vars .unwrap_or_else(|| self.report(pos, msg))
.iter() .value,
.find(|v| v.id == id) ),
.unwrap_or_else(|| self.report(pos, msg)) Expr::BinOp { left: &Expr::Ident { id, .. }, op: TokenKind::Decl, right } => {
.value, let value = self.expr(right)?;
) self.ci.nodes.lock(value);
self.ci.vars.push(Variable { id, value });
Some(NILL)
}
Expr::BinOp { left: &Expr::Ident { id, pos, .. }, op: TokenKind::Assign, right } => {
let value = self.expr(right)?;
self.ci.nodes.lock(value);
let Some(var) = self.ci.vars.iter_mut().find(|v| v.id == id) else {
self.report(pos, msg);
};
let prev = std::mem::replace(&mut var.value, value);
self.ci.nodes.unlock_free(prev);
Some(NILL)
} }
Expr::BinOp { left, op, right } => { Expr::BinOp { left, op, right } => {
let lhs = self.expr_ctx(left, ctx)?; let lhs = self.expr_ctx(left, ctx)?;
@ -1214,6 +1380,54 @@ impl Codegen {
self.ci.nodes.new_node(ty::bin_ret(ty, op), Kind::BinOp { op }, [lhs, rhs]); self.ci.nodes.new_node(ty::bin_ret(ty, op), Kind::BinOp { op }, [lhs, rhs]);
Some(id) Some(id)
} }
Expr::Call { func: &Expr::Ident { pos, id, name, .. }, args, .. } => {
let func = self.find_or_declare(pos, self.ci.file, Some(id), name);
let ty::Kind::Func(func) = func else {
self.report(
pos,
format_args!(
"compiler cant (yet) call '{}'",
self.ty_display(func.compress())
),
);
};
let fuc = &self.tys.funcs[func as usize];
let sig = fuc.sig.expect("TODO: generic functions");
let ast = self.files[fuc.file as usize].clone();
let Expr::BinOp { right: &Expr::Closure { args: cargs, .. }, .. } =
fuc.expr.get(&ast).unwrap()
else {
unreachable!()
};
if args.len() != cargs.len() {
let s = if cargs.len() == 1 { "" } else { "s" };
self.report(
pos,
format_args!(
"expected {} function argumenr{s}, got {}",
cargs.len(),
args.len()
),
);
}
let mut inps = vec![];
for ((arg, _carg), tyx) in args.iter().zip(cargs).zip(sig.args.range()) {
let ty = self.tys.args[tyx];
let value = self.expr_ctx(arg, Ctx::default().with_ty(ty))?;
_ = self.assert_ty(arg.pos(), self.tof(value), ty, true);
inps.push(value);
}
self.ci.cfg =
self.ci
.nodes
.new_node(sig.ret, Kind::Call { func, args: inps.clone() }, [self.ci.cfg]);
self.ci.nodes.add_deps(self.ci.cfg, &inps);
Some(self.ci.cfg)
}
Expr::Return { pos, val } => { Expr::Return { pos, val } => {
let ty = if let Some(val) = val { let ty = if let Some(val) = val {
let value = self.expr_ctx(val, Ctx { ty: self.ci.ret })?; let value = self.expr_ctx(val, Ctx { ty: self.ci.ret })?;
@ -1249,7 +1463,7 @@ impl Codegen {
ret ret
} }
Expr::Number { value, .. } => Some(self.ci.nodes.new_node( Expr::Number { value, .. } => Some(self.ci.nodes.new_node(
ctx.ty.unwrap_or(ty::UINT.into()), ctx.ty.unwrap_or(ty::INT.into()),
Kind::ConstInt { value }, Kind::ConstInt { value },
[], [],
)), )),
@ -1325,6 +1539,13 @@ impl Codegen {
self.ci.nodes.unlock(var.value); self.ci.nodes.unlock(var.value);
} }
#[cfg(debug_assertions)]
{
self.ci.nodes.check_final_integrity();
}
log::trc!("{}", self.ci.nodes);
'_open_function: { '_open_function: {
self.ci.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, 0)); self.ci.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, 0));
self.ci.emit(instrs::st(reg::RET_ADDR, reg::STACK_PTR, 0, 0)); self.ci.emit(instrs::st(reg::RET_ADDR, reg::STACK_PTR, 0, 0));
@ -1332,16 +1553,22 @@ impl Codegen {
self.ci.regs.init(); self.ci.regs.init();
let mut params = self.tys.parama(sig.ret); '_copy_args: {
for var in self.ci.vars.drain(..) { let mut params = self.tys.parama(sig.ret);
match self.tys.size_of(self.ci.nodes[var.value].ty) { for var in self.ci.vars.drain(..) {
0 => {} if self.ci.nodes[var.value].outputs.is_empty() {
1..=8 => { continue;
let reg = self.ci.regs.allocate(); }
emit(&mut self.ci.code, instrs::cp(reg.get(), params.next()));
self.ci.nodes[var.value].loc = Loc { reg }; match self.tys.size_of(self.ci.nodes[var.value].ty) {
0 => {}
1..=8 => {
let reg = self.ci.regs.allocate();
emit(&mut self.ci.code, instrs::cp(reg.get(), params.next()));
self.ci.nodes[var.value].loc = Loc { reg };
}
s => todo!("{s}"),
} }
s => todo!("{s}"),
} }
} }
@ -1357,36 +1584,87 @@ impl Codegen {
self.ci.emit(instrs::ld(reg::RET_ADDR, reg::STACK_PTR, stack as _, pushed as _)); self.ci.emit(instrs::ld(reg::RET_ADDR, reg::STACK_PTR, stack as _, pushed as _));
self.ci.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, (pushed + stack) as _)); self.ci.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, (pushed + stack) as _));
self.ci.emit(instrs::jala(reg::ZERO, reg::RET_ADDR, 0));
} }
self.tys.funcs[id as usize].code.append(&mut self.ci.code); self.tys.funcs[id as usize].code.append(&mut self.ci.code);
self.tys.funcs[id as usize].relocs.append(&mut self.ci.relocs); self.tys.funcs[id as usize].relocs.append(&mut self.ci.relocs);
self.ci.nodes.clear();
self.pool.cis.push(std::mem::replace(&mut self.ci, prev_ci)); self.pool.cis.push(std::mem::replace(&mut self.ci, prev_ci));
} }
fn emit_control(&mut self, ctrl: Nid) { fn emit_control(&mut self, mut ctrl: Nid) {
match self.ci.nodes[ctrl].kind { loop {
Kind::Start => unreachable!(), match self.ci.nodes[ctrl].kind.clone() {
Kind::End => unreachable!(), Kind::Start => unreachable!(),
Kind::Return => { Kind::End => unreachable!(),
let ret_loc = match self.tys.size_of(self.ci.ret.expect("TODO")) { Kind::Return => {
0 => Loc::default(), let ret_loc = match self.tys.size_of(self.ci.ret.expect("TODO")) {
1..=8 => Loc { reg: 1u8.into() }, 0 => Loc::default(),
s => todo!("{s}"), 1..=8 => Loc { reg: 1u8.into() },
}; s => todo!("{s}"),
};
self.emit_expr(self.ci.nodes[ctrl].inputs[1], GenCtx::default().with_loc(ret_loc)); let dst = self.emit_expr(
self.ci.nodes[ctrl].inputs[1],
GenCtx::default().with_loc(ret_loc),
);
self.ci.free_loc(dst);
break;
}
Kind::ConstInt { .. } => unreachable!(),
Kind::Tuple { index } => {
debug_assert!(index == 0);
ctrl = self.ci.nodes[ctrl].outputs[0];
}
Kind::BinOp { .. } => unreachable!(),
Kind::Call { func, args } => {
let ret = self.tof(ctrl);
let mut parama = self.tys.parama(ret);
for &arg in args.iter() {
let dst = match self.tys.size_of(self.tof(arg)) {
0 => continue,
1..=8 => Loc { reg: parama.next().into() },
s => todo!("{s}"),
};
let dst = self.emit_expr(arg, GenCtx::default().with_loc(dst));
self.ci.free_loc(dst);
}
let ret_loc = match self.tys.size_of(ret) {
0 => Loc::default(),
1..=8 => Loc { reg: 1u8.into() },
s => todo!("{s}"),
};
let reloc = Reloc::new(self.ci.code.len(), 3, 4);
self.ci
.relocs
.push(TypedReloc { target: ty::Kind::Func(func).compress(), reloc });
self.ci.emit(instrs::jal(reg::RET_ADDR, reg::ZERO, 0));
self.make_func_reachable(func);
// TODO: allocate return register
let loc = Loc { reg: self.ci.regs.allocate() };
self.ci.emit(instrs::cp(loc.reg.get(), ret_loc.reg.get()));
self.ci.nodes[ctrl].loc = loc;
self.ci.nodes[ctrl].lock_rc += 1;
ctrl = *self.ci.nodes[ctrl]
.outputs
.iter()
.find(|&&o| self.ci.nodes.is_cfg(o))
.unwrap();
}
} }
Kind::ConstInt { value } => unreachable!(),
Kind::Tuple { index } => {
debug_assert!(index == 0);
self.emit_control(self.ci.nodes[ctrl].outputs[0]);
}
Kind::BinOp { op } => unreachable!(),
} }
} }
fn emit_expr(&mut self, expr: Nid, ctx: GenCtx) { #[must_use = "dont forget to drop the location"]
fn emit_expr(&mut self, expr: Nid, ctx: GenCtx) -> Option<Loc> {
match self.ci.nodes[expr].kind { match self.ci.nodes[expr].kind {
Kind::Start => unreachable!(), Kind::Start => unreachable!(),
Kind::End => unreachable!(), Kind::End => unreachable!(),
@ -1400,9 +1678,75 @@ impl Codegen {
self.ci.nodes[expr].loc = Loc { reg }; self.ci.nodes[expr].loc = Loc { reg };
} }
} }
Kind::Tuple { index } => unreachable!(), Kind::Tuple { index } => {
Kind::BinOp { op } => unreachable!(), debug_assert!(index != 0);
}
Kind::BinOp { op } => {
let ty = self.tof(expr);
let [left, right, ..] = self.ci.nodes[expr].inputs;
let mut ldst = self.emit_expr(left, GenCtx::default());
let mut rdst = self.emit_expr(right, GenCtx::default());
let op = Self::math_op(op, ty.is_signed(), self.tys.size_of(ty))
.expect("TODO: what now?");
let loc = ctx
.loc
.or_else(|| ldst.take())
.or_else(|| rdst.take())
.unwrap_or_else(|| Loc { reg: self.ci.regs.allocate() });
self.ci.free_loc(ldst);
self.ci.free_loc(rdst);
self.ci.emit(op(
loc.reg.get(),
self.ci.nodes[left].loc.reg.get(),
self.ci.nodes[right].loc.reg.get(),
));
self.ci.nodes[expr].loc = loc;
}
Kind::Call { .. } => {}
} }
self.ci.nodes[expr].lock_rc += 1;
if self.ci.nodes[expr].lock_rc as usize == self.ci.nodes[expr].outputs.len() {
let re = self.ci.nodes[expr].loc.as_ref();
return Some(std::mem::replace(&mut self.ci.nodes[expr].loc, re));
}
None
}
#[allow(clippy::type_complexity)]
fn math_op(
op: TokenKind,
signed: bool,
size: u32,
) -> Option<fn(u8, u8, u8) -> (usize, [u8; instrs::MAX_SIZE])> {
use {instrs::*, TokenKind as T};
macro_rules! div { ($($op:ident),*) => {[$(|a, b, c| $op(a, reg::ZERO, b, c)),*]}; }
macro_rules! rem { ($($op:ident),*) => {[$(|a, b, c| $op(reg::ZERO, a, b, c)),*]}; }
let ops = match op {
T::Add => [add8, add16, add32, add64],
T::Sub => [sub8, sub16, sub32, sub64],
T::Mul => [mul8, mul16, mul32, mul64],
T::Div if signed => div!(dirs8, dirs16, dirs32, dirs64),
T::Div => div!(diru8, diru16, diru32, diru64),
T::Mod if signed => rem!(dirs8, dirs16, dirs32, dirs64),
T::Mod => rem!(diru8, diru16, diru32, diru64),
T::Band => return Some(and),
T::Bor => return Some(or),
T::Xor => return Some(xor),
T::Shl => [slu8, slu16, slu32, slu64],
T::Shr if signed => [srs8, srs16, srs32, srs64],
T::Shr => [sru8, sru16, sru32, sru64],
_ => return None,
};
Some(ops[size.ilog2() as usize])
} }
// TODO: sometimes its better to do this in bulk // TODO: sometimes its better to do this in bulk
@ -1420,7 +1764,7 @@ impl Codegen {
name: Option<Ident>, name: Option<Ident>,
lit_name: &str, lit_name: &str,
) -> ty::Kind { ) -> ty::Kind {
log::dbg!("find_or_declare: {lit_name} {file}"); log::trc!("find_or_declare: {lit_name} {file}");
let f = self.files[file as usize].clone(); let f = self.files[file as usize].clone();
let Some((expr, ident)) = f.find_decl(name.ok_or(lit_name)) else { let Some((expr, ident)) = f.find_decl(name.ok_or(lit_name)) else {
@ -1566,10 +1910,76 @@ impl Codegen {
} }
} }
#[derive(Default)]
pub struct LoggedMem {
pub mem: hbvm::mem::HostMemory,
}
impl hbvm::mem::Memory for LoggedMem {
unsafe fn load(
&mut self,
addr: hbvm::mem::Address,
target: *mut u8,
count: usize,
) -> Result<(), hbvm::mem::LoadError> {
log::trc!(
"load: {:x} {:?}",
addr.get(),
core::slice::from_raw_parts(addr.get() as *const u8, count)
.iter()
.rev()
.map(|&b| format!("{b:02x}"))
.collect::<String>()
);
self.mem.load(addr, target, count)
}
unsafe fn store(
&mut self,
addr: hbvm::mem::Address,
source: *const u8,
count: usize,
) -> Result<(), hbvm::mem::StoreError> {
log::trc!(
"store: {:x} {:?}",
addr.get(),
core::slice::from_raw_parts(source, count)
.iter()
.rev()
.map(|&b| format!("{b:02x}"))
.collect::<String>()
);
self.mem.store(addr, source, count)
}
unsafe fn prog_read<T: Copy>(&mut self, addr: hbvm::mem::Address) -> T {
log::trc!(
"read-typed: {:x} {} {:?}",
addr.get(),
std::any::type_name::<T>(),
if core::mem::size_of::<T>() == 1
&& let Some(nm) =
instrs::NAMES.get(std::ptr::read(addr.get() as *const u8) as usize)
{
nm.to_string()
} else {
core::slice::from_raw_parts(addr.get() as *const u8, core::mem::size_of::<T>())
.iter()
.map(|&b| format!("{:02x}", b))
.collect::<String>()
}
);
self.mem.prog_read(addr)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use { use {
crate::parser::{self, FileId}, crate::{
parser::{self, FileId},
son::LoggedMem,
},
std::io, std::io,
}; };
@ -1632,16 +2042,68 @@ mod tests {
codegen.generate(); codegen.generate();
use std::fmt::Write; let mut out = Vec::new();
codegen.assemble(&mut out);
write!(output, "{}", codegen.ci.nodes).unwrap(); let mut buf = Vec::<u8>::new();
let err = codegen.disasm(&out, &mut buf);
output.push_str(String::from_utf8(buf).unwrap().as_str());
if let Err(e) = err {
writeln!(output, "!!! asm is invalid: {e}").unwrap();
return;
}
let mut stack = [0_u64; 128];
let mut vm = unsafe {
hbvm::Vm::<_, 0>::new(
LoggedMem::default(),
hbvm::mem::Address::new(out.as_ptr() as u64),
)
};
vm.write_reg(super::reg::STACK_PTR, unsafe { stack.as_mut_ptr().add(stack.len()) } as u64);
use std::fmt::Write;
let stat = loop {
match vm.run() {
Ok(hbvm::VmRunOk::End) => break Ok(()),
Ok(hbvm::VmRunOk::Ecall) => match vm.read_reg(2).0 {
1 => writeln!(output, "ev: Ecall").unwrap(), // compatibility with a test
69 => {
let [size, align] = [vm.read_reg(3).0 as usize, vm.read_reg(4).0 as usize];
let layout = std::alloc::Layout::from_size_align(size, align).unwrap();
let ptr = unsafe { std::alloc::alloc(layout) };
vm.write_reg(1, ptr as u64);
}
96 => {
let [ptr, size, align] = [
vm.read_reg(3).0 as usize,
vm.read_reg(4).0 as usize,
vm.read_reg(5).0 as usize,
];
let layout = std::alloc::Layout::from_size_align(size, align).unwrap();
unsafe { std::alloc::dealloc(ptr as *mut u8, layout) };
}
3 => vm.write_reg(1, 42),
unknown => unreachable!("unknown ecall: {unknown:?}"),
},
Ok(ev) => writeln!(output, "ev: {:?}", ev).unwrap(),
Err(e) => break Err(e),
}
};
writeln!(output, "code size: {}", out.len()).unwrap();
writeln!(output, "ret: {:?}", vm.read_reg(1).0).unwrap();
writeln!(output, "status: {:?}", stat).unwrap();
} }
crate::run_tests! { generate: crate::run_tests! { generate:
arithmetic => README; arithmetic => README;
const_folding_with_arg => README; const_folding_with_arg => README;
//variables => README; variables => README;
//functions => README; functions => README;
//comments => README; //comments => README;
//if_statements => README; //if_statements => README;
//loops => README; //loops => README;

View file

@ -1,4 +1,10 @@
0: Start main:
2: Tuple { index: 0 } ADDI64 r254, r254, -8d
4: return [2] 1 ST r31, r254, 0a, 8h
1: End LI64 r1, 1d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 77
ret: 1
status: Ok(())

View file

@ -1,5 +1,10 @@
0: Start main:
2: Tuple { index: 0 } ADDI64 r254, r254, -8d
7: return [2] 0 ST r31, r254, 0a, 8h
1: End LI64 r1, 0d
Start.1 LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 77
ret: 0
status: Ok(())

View file

@ -0,0 +1,34 @@
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LI64 r2, 10d
JAL r31, r0, :add_one
CP r32, r1
LI64 r2, 20d
JAL r31, r0, :add_two
CP r33, r1
ADD64 r1, r33, r32
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
add_one:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r32, r2
LI64 r33, 1d
ADD64 r1, r32, r33
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
add_two:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
CP r32, r2
LI64 r33, 2d
ADD64 r1, r32, r33
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 263
ret: 33
status: Ok(())

View file

@ -0,0 +1,10 @@
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r1, 0d
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 77
ret: 0
status: Ok(())