holey-bytes/lang/src/backend/hbvm.rs

937 lines
31 KiB
Rust
Raw Normal View History

2024-10-27 08:29:14 -05:00
use {
super::{AssemblySpec, Backend},
2024-10-27 08:29:14 -05:00
crate::{
lexer::TokenKind,
nodes::{Kind, Nid, Nodes, MEM},
2024-11-13 08:25:27 -06:00
parser,
ty::{self, Loc, Module, Offset, Size, Types},
utils::{EntSlice, EntVec},
2024-10-27 08:29:14 -05:00
},
2024-11-07 01:52:41 -06:00
alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec},
core::{assert_matches::debug_assert_matches, mem, ops::Range},
2024-10-27 08:29:14 -05:00
hbbytecode::{self as instrs, *},
reg::Reg,
2024-10-27 08:29:14 -05:00
};
2024-11-16 04:46:59 -06:00
mod regalloc;
2024-11-07 01:52:41 -06:00
mod reg {
pub const STACK_PTR: Reg = 254;
pub const ZERO: Reg = 0;
pub const RET: Reg = 1;
pub const RET_ADDR: Reg = 31;
pub type Reg = u8;
}
fn write_reloc(doce: &mut [u8], offset: usize, value: i64, size: u16) {
let value = value.to_ne_bytes();
doce[offset..offset + size as usize].copy_from_slice(&value[..size as usize]);
}
#[derive(Clone, Copy)]
struct TypedReloc {
target: ty::Id,
reloc: Reloc,
}
// TODO: make into bit struct (width: u2, sub_offset: u3, offset: u27)
#[derive(Clone, Copy, Debug)]
struct Reloc {
offset: Offset,
sub_offset: 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 FuncDt {
offset: Offset,
// TODO: change to indices into common vec
relocs: Vec<TypedReloc>,
code: Vec<u8>,
}
2024-10-27 08:29:14 -05:00
impl Default for FuncDt {
fn default() -> Self {
Self { offset: u32::MAX, relocs: Default::default(), code: Default::default() }
2024-10-27 08:29:14 -05:00
}
}
2024-10-27 08:29:14 -05:00
struct GlobalDt {
offset: Offset,
}
2024-10-27 08:29:14 -05:00
impl Default for GlobalDt {
fn default() -> Self {
Self { offset: u32::MAX }
}
}
2024-10-27 08:29:14 -05:00
#[derive(Default)]
struct Assembler {
frontier: Vec<ty::Id>,
globals: Vec<ty::Global>,
funcs: Vec<ty::Func>,
}
2024-10-27 08:29:14 -05:00
#[derive(Default)]
pub struct HbvmBackend {
2024-11-08 03:25:34 -06:00
funcs: EntVec<ty::Func, FuncDt>,
globals: EntVec<ty::Global, GlobalDt>,
asm: Assembler,
2024-11-16 04:46:59 -06:00
ralloc: regalloc::Res,
ret_relocs: Vec<Reloc>,
relocs: Vec<TypedReloc>,
jump_relocs: Vec<(Nid, Reloc)>,
code: Vec<u8>,
offsets: Vec<Offset>,
2024-10-27 08:29:14 -05:00
}
impl HbvmBackend {
2024-10-27 08:29:14 -05:00
fn emit(&mut self, instr: (usize, [u8; instrs::MAX_SIZE])) {
emit(&mut self.code, instr);
}
}
impl Backend for HbvmBackend {
fn assemble_bin(&mut self, entry: ty::Func, types: &Types, to: &mut Vec<u8>) {
to.extend([0u8; HEADER_SIZE]);
binary_prelude(to);
let AssemblySpec { code_length, data_length, entry } =
self.assemble_reachable(entry, types, to);
let exe = AbleOsExecutableHeader {
magic_number: [0x15, 0x91, 0xD2],
executable_version: 0,
code_length,
data_length,
debug_length: 0,
config_length: 0,
metadata_length: 0,
};
2024-11-07 01:52:41 -06:00
Reloc::new(HEADER_SIZE, 3, 4).apply_jump(to, entry, 0);
unsafe { *to.as_mut_ptr().cast::<AbleOsExecutableHeader>() = exe }
}
fn assemble_reachable(
&mut self,
from: ty::Func,
types: &Types,
to: &mut Vec<u8>,
) -> AssemblySpec {
debug_assert!(self.asm.frontier.is_empty());
debug_assert!(self.asm.funcs.is_empty());
debug_assert!(self.asm.globals.is_empty());
2024-11-08 03:25:34 -06:00
self.globals.shadow(types.ins.globals.len());
self.asm.frontier.push(from.into());
while let Some(itm) = self.asm.frontier.pop() {
match itm.expand() {
ty::Kind::Func(func) => {
2024-11-08 03:25:34 -06:00
let fuc = &mut self.funcs[func];
2024-11-07 01:52:41 -06:00
debug_assert!(!fuc.code.is_empty());
if fuc.offset != u32::MAX {
continue;
}
fuc.offset = 0;
self.asm.funcs.push(func);
self.asm.frontier.extend(fuc.relocs.iter().map(|r| r.target));
}
ty::Kind::Global(glob) => {
2024-11-08 03:25:34 -06:00
let glb = &mut self.globals[glob];
if glb.offset != u32::MAX {
continue;
}
glb.offset = 0;
self.asm.globals.push(glob);
}
_ => unreachable!(),
}
}
let init_len = to.len();
for &func in &self.asm.funcs {
2024-11-08 03:25:34 -06:00
let fuc = &mut self.funcs[func];
fuc.offset = to.len() as _;
debug_assert!(!fuc.code.is_empty());
to.extend(&fuc.code);
}
let code_length = to.len() - init_len;
for global in self.asm.globals.drain(..) {
2024-11-08 03:25:34 -06:00
self.globals[global].offset = to.len() as _;
to.extend(&types.ins.globals[global].data);
}
let data_length = to.len() - code_length - init_len;
2024-10-27 08:29:14 -05:00
for func in self.asm.funcs.drain(..) {
2024-11-08 03:25:34 -06:00
let fuc = &self.funcs[func];
for rel in &fuc.relocs {
let offset = match rel.target.expand() {
2024-11-08 03:25:34 -06:00
ty::Kind::Func(fun) => self.funcs[fun].offset,
ty::Kind::Global(glo) => self.globals[glo].offset,
_ => unreachable!(),
};
rel.reloc.apply_jump(to, offset, fuc.offset);
}
}
AssemblySpec {
code_length: code_length as _,
data_length: data_length as _,
2024-11-08 03:25:34 -06:00
entry: self.funcs[from].offset,
}
2024-10-27 08:29:14 -05:00
}
fn disasm<'a>(
&'a self,
mut sluce: &[u8],
eca_handler: &mut dyn FnMut(&mut &[u8]),
types: &'a Types,
files: &'a EntSlice<Module, parser::Ast>,
output: &mut String,
) -> Result<(), hbbytecode::DisasmError<'a>> {
use hbbytecode::DisasmItem;
let functions = types
.ins
.funcs
.iter()
2024-11-08 03:25:34 -06:00
.zip(self.funcs.iter())
.filter(|(_, f)| f.offset != u32::MAX)
.map(|(f, fd)| {
2024-11-08 03:25:34 -06:00
let name = if f.file != Module::default() {
let file = &files[f.file];
file.ident_str(f.name)
} else {
"target_fn"
};
(fd.offset, (name, fd.code.len() as u32, DisasmItem::Func))
})
.chain(
types
.ins
.globals
.iter()
2024-11-08 03:25:34 -06:00
.zip(self.globals.iter())
.filter(|(_, g)| g.offset != u32::MAX)
.map(|(g, gd)| {
2024-11-08 03:25:34 -06:00
let name = if g.file == Module::default() {
core::str::from_utf8(&g.data).unwrap_or("invalid utf-8")
} else {
let file = &files[g.file];
file.ident_str(g.name)
};
(gd.offset, (name, g.data.len() as Size, DisasmItem::Global))
}),
)
.collect::<BTreeMap<_, _>>();
hbbytecode::disasm(&mut sluce, &functions, output, eca_handler)
}
fn emit_ct_body(
&mut self,
id: ty::Func,
nodes: &Nodes,
tys: &Types,
files: &EntSlice<Module, parser::Ast>,
) {
self.emit_body(id, nodes, tys, files);
2024-11-08 03:25:34 -06:00
let fd = &mut self.funcs[id];
fd.code.truncate(fd.code.len() - instrs::jala(0, 0, 0).0);
emit(&mut fd.code, instrs::tx());
2024-10-27 08:29:14 -05:00
}
fn emit_body(
&mut self,
id: ty::Func,
nodes: &Nodes,
tys: &Types,
files: &EntSlice<Module, parser::Ast>,
) {
let sig = tys.ins.funcs[id].sig;
2024-10-27 08:29:14 -05:00
debug_assert!(self.code.is_empty());
self.offsets.clear();
self.offsets.resize(nodes.len(), Offset::MAX);
2024-10-27 08:29:14 -05:00
let mut stack_size = 0;
'_compute_stack: {
let mems = &nodes[MEM].outputs;
2024-10-27 08:29:14 -05:00
for &stck in mems.iter() {
if !matches!(nodes[stck].kind, Kind::Stck | Kind::Arg) {
2024-10-27 08:29:14 -05:00
debug_assert_matches!(
nodes[stck].kind,
Kind::Phi
2024-11-17 11:15:58 -06:00
| Kind::Return { .. }
| Kind::Load
| Kind::Call { .. }
| Kind::Stre
| Kind::Join
2024-10-27 08:29:14 -05:00
);
continue;
}
stack_size += tys.size_of(nodes[stck].ty);
self.offsets[stck as usize] = stack_size;
2024-10-27 08:29:14 -05:00
}
for &stck in mems.iter() {
if !matches!(nodes[stck].kind, Kind::Stck | Kind::Arg) {
2024-10-27 08:29:14 -05:00
continue;
}
self.offsets[stck as usize] = stack_size - self.offsets[stck as usize];
2024-10-27 08:29:14 -05:00
}
}
2024-11-16 04:46:59 -06:00
let (saved, tail) = self.emit_body_code(nodes, sig, tys, files);
2024-10-27 08:29:14 -05:00
if let Some(last_ret) = self.ret_relocs.last()
&& last_ret.offset as usize == self.code.len() - 5
&& self
.jump_relocs
.last()
.map_or(true, |&(r, _)| self.offsets[r as usize] as usize != self.code.len())
2024-10-27 08:29:14 -05:00
{
self.code.truncate(self.code.len() - 5);
self.ret_relocs.pop();
}
for (nd, rel) in self.jump_relocs.drain(..) {
let offset = self.offsets[nd as usize];
2024-10-27 08:29:14 -05:00
rel.apply_jump(&mut self.code, offset, 0);
}
let end = self.code.len();
for ret_rel in self.ret_relocs.drain(..) {
ret_rel.apply_jump(&mut self.code, end as _, 0);
}
let mut stripped_prelude_size = 0;
'_close_function: {
let pushed = (saved as i64 + !tail as i64) * 8;
let stack = stack_size as i64;
let add_len = instrs::addi64(0, 0, 0).0;
let st_len = instrs::st(0, 0, 0, 0).0;
2024-10-27 08:29:14 -05:00
match (pushed, stack) {
(0, 0) => {
stripped_prelude_size = add_len + st_len;
2024-10-27 08:29:14 -05:00
self.code.drain(0..stripped_prelude_size);
break '_close_function;
}
(0, stack) => {
write_reloc(&mut self.code, 3, -stack, 8);
stripped_prelude_size = st_len;
let end = add_len + st_len;
self.code.drain(add_len..end);
2024-10-27 08:29:14 -05:00
self.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, stack as _));
break '_close_function;
}
_ => {}
}
write_reloc(&mut self.code, 3, -(pushed + stack), 8);
write_reloc(&mut self.code, 3 + 8 + 3, stack, 8);
write_reloc(&mut self.code, 3 + 8 + 3 + 8, pushed, 2);
self.emit(instrs::ld(
reg::RET_ADDR + tail as u8,
reg::STACK_PTR,
stack as _,
pushed as _,
));
self.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, (pushed + stack) as _));
}
self.relocs.iter_mut().for_each(|r| r.reloc.offset -= stripped_prelude_size as u32);
2024-11-03 03:15:03 -06:00
if sig.ret != ty::Id::NEVER {
self.emit(instrs::jala(reg::ZERO, reg::RET_ADDR, 0));
}
2024-11-08 03:25:34 -06:00
self.funcs.shadow(tys.ins.funcs.len());
self.funcs[id].code = mem::take(&mut self.code);
self.funcs[id].relocs = mem::take(&mut self.relocs);
debug_assert_eq!(self.ret_relocs.len(), 0);
debug_assert_eq!(self.relocs.len(), 0);
debug_assert_eq!(self.jump_relocs.len(), 0);
debug_assert_eq!(self.code.len(), 0);
2024-10-27 08:29:14 -05:00
}
}
2024-11-07 01:52:41 -06:00
impl Nodes {
fn cond_op(&self, cnd: Nid) -> CondRet {
let Kind::BinOp { op } = self[cnd].kind else { return None };
2024-11-16 04:46:59 -06:00
if self.is_unlocked(cnd) {
return None;
}
op.cond_op(self[self[cnd].inputs[1]].ty)
}
fn strip_offset(&self, region: Nid) -> (Nid, Offset) {
2024-11-14 13:25:52 -06:00
if matches!(self[region].kind, Kind::BinOp { op: TokenKind::Add | TokenKind::Sub })
2024-11-16 04:46:59 -06:00
&& self.is_locked(region)
2024-11-14 13:25:52 -06:00
&& let Kind::CInt { value } = self[self[region].inputs[2]].kind
{
(self[region].inputs[1], value as _)
} else {
(region, 0)
}
}
2024-11-07 01:52:41 -06:00
fn is_never_used(&self, nid: Nid, tys: &Types) -> bool {
let node = &self[nid];
2024-10-27 08:29:14 -05:00
match node.kind {
2024-11-13 03:28:16 -06:00
Kind::CInt { value: 0 } => false,
Kind::CInt { value: 1.. } => node.outputs.iter().all(|&o| {
2024-11-07 01:52:41 -06:00
matches!(self[o].kind, Kind::BinOp { op }
if op.imm_binop(self[o].ty).is_some()
&& self.is_const(self[o].inputs[2])
&& op.cond_op(self[o].ty).is_none())
}),
Kind::BinOp { op: TokenKind::Mul } if node.ty.is_float() => {
node.outputs.iter().all(|&n| {
self[n].kind == Kind::BinOp { op: TokenKind::Add } && self[n].inputs[1] == nid
})
}
2024-11-07 01:52:41 -06:00
Kind::BinOp { op: TokenKind::Add | TokenKind::Sub } => {
(self.is_locked(node.inputs[1]) && !self[node.inputs[1]].ty.is_float())
2024-11-07 01:52:41 -06:00
|| (self.is_const(node.inputs[2])
&& node.outputs.iter().all(|&n| self.uses_direct_offset_of(n, nid, tys)))
2024-11-07 01:52:41 -06:00
}
Kind::BinOp { op } => {
2024-11-15 16:18:40 -06:00
op.cond_op(self[node.inputs[1]].ty).is_some()
2024-11-07 01:52:41 -06:00
&& node.outputs.iter().all(|&n| self[n].kind == Kind::If)
}
Kind::Stck if tys.size_of(node.ty) == 0 => true,
Kind::Stck | Kind::Arg => node.outputs.iter().all(|&n| {
self.uses_direct_offset_of(n, nid, tys)
2024-11-07 01:52:41 -06:00
|| (matches!(self[n].kind, Kind::BinOp { op: TokenKind::Add })
&& self.is_never_used(n, tys))
}),
Kind::Load { .. } => node.ty.loc(tys) == Loc::Stack,
_ => false,
2024-10-27 08:29:14 -05:00
}
}
fn uses_direct_offset_of(&self, user: Nid, target: Nid, tys: &Types) -> bool {
let node = &self[user];
((node.kind == Kind::Stre && node.inputs[2] == target)
|| (node.kind == Kind::Load && node.inputs[1] == target))
&& (node.ty.loc(tys) == Loc::Reg
// this means the struct is actually loaded into a register so no BMC needed
|| (node.kind == Kind::Load
&& !matches!(tys.parama(node.ty).0, Some(PLoc::Ref(..)))
&& node.outputs.iter().all(|&o| matches!(self[o].kind, Kind::Call { .. } | Kind::Return { .. }))))
}
2024-11-13 08:25:27 -06:00
}
impl HbvmBackend {
fn extend(
&mut self,
base: ty::Id,
dest: ty::Id,
reg: Reg,
tys: &Types,
files: &EntSlice<Module, parser::Ast>,
) {
if reg == 0 {
return;
}
2024-11-13 08:25:27 -06:00
let (bsize, dsize) = (tys.size_of(base), tys.size_of(dest));
debug_assert!(bsize <= 8, "{}", ty::Display::new(tys, files, base));
debug_assert!(dsize <= 8, "{}", ty::Display::new(tys, files, dest));
if bsize == dsize {
return Default::default();
}
self.emit(match (base.is_signed(), dest.is_signed()) {
(true, true) => {
let op = [instrs::sxt8, instrs::sxt16, instrs::sxt32][bsize.ilog2() as usize];
op(reg, reg)
}
_ => {
let mask = (1u64 << (bsize * 8)) - 1;
instrs::andi(reg, reg, mask)
}
});
}
2024-10-27 08:29:14 -05:00
}
type CondRet = Option<(fn(u8, u8, i16) -> EncodedInstr, bool)>;
2024-10-27 08:29:14 -05:00
impl TokenKind {
fn cmp_against(self) -> Option<u64> {
2024-10-29 07:36:12 -05:00
Some(match self {
Self::Le | Self::Gt => 1,
Self::Ne | Self::Eq => 0,
Self::Ge | Self::Lt => (-1i64) as _,
2024-10-29 07:36:12 -05:00
_ => return None,
})
}
fn float_cmp(self, ty: ty::Id) -> Option<fn(u8, u8, u8) -> EncodedInstr> {
2024-10-29 07:36:12 -05:00
if !ty.is_float() {
return None;
}
let size = ty.simple_size().unwrap();
let ops = match self {
Self::Gt => [instrs::fcmpgt32, instrs::fcmpgt64],
Self::Lt => [instrs::fcmplt32, instrs::fcmplt64],
2024-10-29 07:36:12 -05:00
_ => return None,
};
Some(ops[size.ilog2() as usize - 2])
}
fn cond_op(self, ty: ty::Id) -> CondRet {
2024-10-29 07:36:12 -05:00
let signed = ty.is_signed();
2024-10-27 08:29:14 -05:00
Some((
match self {
Self::Eq => instrs::jne,
Self::Ne => instrs::jeq,
_ if ty.is_float() => return None,
2024-10-27 08:29:14 -05:00
Self::Le if signed => instrs::jgts,
Self::Le => instrs::jgtu,
Self::Lt if signed => instrs::jlts,
Self::Lt => instrs::jltu,
Self::Ge if signed => instrs::jlts,
Self::Ge => instrs::jltu,
Self::Gt if signed => instrs::jgts,
Self::Gt => instrs::jgtu,
_ => return None,
},
matches!(self, Self::Lt | Self::Gt),
2024-10-27 08:29:14 -05:00
))
}
2024-10-29 07:36:12 -05:00
fn binop(self, ty: ty::Id) -> Option<fn(u8, u8, u8) -> EncodedInstr> {
2024-11-15 16:18:40 -06:00
let size = ty.simple_size().unwrap_or_else(|| panic!("{:?}", ty.expand()));
2024-10-29 07:36:12 -05:00
if ty.is_integer() || ty == ty::Id::BOOL || ty.is_pointer() {
macro_rules! div { ($($op:ident),*) => {[$(|a, b, c| $op(a, 0, b, c)),*]}; }
macro_rules! rem { ($($op:ident),*) => {[$(|a, b, c| $op(0, a, b, c)),*]}; }
let signed = ty.is_signed();
let ops = match self {
Self::Add => [add8, add16, add32, add64],
Self::Sub => [sub8, sub16, sub32, sub64],
Self::Mul => [mul8, mul16, mul32, mul64],
Self::Div if signed => div!(dirs8, dirs16, dirs32, dirs64),
Self::Div => div!(diru8, diru16, diru32, diru64),
Self::Mod if signed => rem!(dirs8, dirs16, dirs32, dirs64),
Self::Mod => rem!(diru8, diru16, diru32, diru64),
Self::Band => return Some(and),
Self::Bor => return Some(or),
Self::Xor => return Some(xor),
Self::Shl => [slu8, slu16, slu32, slu64],
Self::Shr if signed => [srs8, srs16, srs32, srs64],
Self::Shr => [sru8, sru16, sru32, sru64],
_ => return None,
};
2024-10-27 08:29:14 -05:00
2024-10-29 07:36:12 -05:00
Some(ops[size.ilog2() as usize])
} else {
debug_assert!(ty.is_float(), "{self} {ty:?}");
let ops = match self {
Self::Add => [fadd32, fadd64],
Self::Sub => [fsub32, fsub64],
Self::Mul => [fmul32, fmul64],
Self::Div => [fdiv32, fdiv64],
_ => return None,
};
Some(ops[size.ilog2() as usize - 2])
}
2024-10-27 08:29:14 -05:00
}
2024-10-29 07:36:12 -05:00
fn imm_binop(self, ty: ty::Id) -> Option<fn(u8, u8, u64) -> EncodedInstr> {
2024-10-27 08:29:14 -05:00
macro_rules! def_op {
($name:ident |$a:ident, $b:ident, $c:ident| $($tt:tt)*) => {
macro_rules! $name {
($$($$op:ident),*) => {
[$$(
|$a, $b, $c: u64| $$op($($tt)*),
)*]
}
}
};
}
2024-10-29 07:36:12 -05:00
if ty.is_float() {
return None;
}
2024-10-27 08:29:14 -05:00
def_op!(basic_op | a, b, c | a, b, c as _);
def_op!(sub_op | a, b, c | a, b, c.wrapping_neg() as _);
2024-10-29 07:36:12 -05:00
let signed = ty.is_signed();
2024-10-27 08:29:14 -05:00
let ops = match self {
Self::Add => basic_op!(addi8, addi16, addi32, addi64),
Self::Sub => sub_op!(addi8, addi16, addi32, addi64),
Self::Mul => basic_op!(muli8, muli16, muli32, muli64),
Self::Band => return Some(andi),
Self::Bor => return Some(ori),
Self::Xor => return Some(xori),
2024-11-10 02:17:43 -06:00
Self::Shr if signed => basic_op!(srsi8, srsi16, srsi32, srsi64),
2024-10-27 08:29:14 -05:00
Self::Shr => basic_op!(srui8, srui16, srui32, srui64),
Self::Shl => basic_op!(slui8, slui16, slui32, slui64),
_ => return None,
};
2024-10-29 07:36:12 -05:00
let size = ty.simple_size().unwrap();
2024-10-27 08:29:14 -05:00
Some(ops[size.ilog2() as usize])
}
fn unop(&self, dst: ty::Id, src: ty::Id) -> Option<fn(u8, u8) -> EncodedInstr> {
2024-11-12 12:02:29 -06:00
let src_idx =
src.simple_size().unwrap_or_else(|| panic!("{:?}", src.expand())).ilog2() as usize;
2024-10-27 08:29:14 -05:00
Some(match self {
2024-11-11 16:02:02 -06:00
Self::Sub => [
|a, b| sub8(a, reg::ZERO, b),
|a, b| sub16(a, reg::ZERO, b),
|a, b| sub32(a, reg::ZERO, b),
|a, b| sub64(a, reg::ZERO, b),
][src_idx],
2024-11-11 15:36:20 -06:00
Self::Not => instrs::not,
2024-10-29 08:24:31 -05:00
Self::Float if dst.is_float() && src.is_integer() => {
2024-11-11 15:14:54 -06:00
debug_assert_matches!(
(dst.simple_size(), src.simple_size()),
(Some(4 | 8), Some(8))
);
[instrs::itf32, instrs::itf64][dst.simple_size().unwrap().ilog2() as usize - 2]
2024-10-29 08:24:31 -05:00
}
Self::Number if src.is_float() && dst.is_integer() => {
[|a, b| instrs::fti32(a, b, 1), |a, b| instrs::fti64(a, b, 1)][src_idx - 2]
2024-10-29 08:24:31 -05:00
}
2024-11-12 13:42:04 -06:00
Self::Number if src.is_signed() && (dst.is_integer() || dst.is_pointer()) => {
2024-11-12 12:02:29 -06:00
[instrs::sxt8, instrs::sxt16, instrs::sxt32][src_idx]
}
2024-11-12 13:42:04 -06:00
Self::Number
if (src.is_unsigned() || src == ty::Id::BOOL)
&& (dst.is_integer() || dst.is_pointer()) =>
{
[
|a, b| instrs::andi(a, b, 0xff),
|a, b| instrs::andi(a, b, 0xffff),
|a, b| instrs::andi(a, b, 0xffffffff),
][src_idx]
}
2024-10-29 08:24:31 -05:00
Self::Float if dst.is_float() && src.is_float() => {
[instrs::fc32t64, |a, b| instrs::fc64t32(a, b, 1)][src_idx - 2]
2024-10-29 08:24:31 -05:00
}
2024-10-27 08:29:14 -05:00
_ => return None,
})
}
}
#[derive(Clone, Copy, Debug)]
enum PLoc {
Reg(Reg, u16),
WideReg(Reg, u16),
Ref(Reg, u32),
}
struct ParamAlloc(Range<Reg>);
impl ParamAlloc {
pub fn next(&mut self, ty: ty::Id, tys: &Types) -> Option<PLoc> {
Some(match tys.size_of(ty) {
0 => return None,
size @ 1..=8 => PLoc::Reg(self.0.next().unwrap(), size as _),
size @ 9..=16 => PLoc::WideReg(self.0.next_chunk::<2>().unwrap()[0], size as _),
size @ 17.. => PLoc::Ref(self.0.next().unwrap(), size),
})
}
}
impl Types {
fn parama(&self, ret: ty::Id) -> (Option<PLoc>, ParamAlloc) {
let mut iter = ParamAlloc(1..12);
let ret = iter.next(ret, self);
iter.0.start += ret.is_none() as u8;
(ret, iter)
}
}
2024-10-27 08:29:14 -05:00
type EncodedInstr = (usize, [u8; instrs::MAX_SIZE]);
fn emit(out: &mut Vec<u8>, (len, instr): EncodedInstr) {
out.extend_from_slice(&instr[..len]);
}
fn binary_prelude(to: &mut Vec<u8>) {
2024-10-27 08:29:14 -05:00
emit(to, instrs::jal(reg::RET_ADDR, reg::ZERO, 0));
emit(to, instrs::tx());
}
#[derive(Default)]
pub struct LoggedMem {
pub mem: hbvm::mem::HostMemory,
op_buf: Vec<hbbytecode::Oper>,
disp_buf: String,
prev_instr: Option<hbbytecode::Instr>,
}
impl LoggedMem {
unsafe fn display_instr<T>(&mut self, instr: hbbytecode::Instr, addr: hbvm::mem::Address) {
let novm: *const hbvm::Vm<Self, 0> = core::ptr::null();
let offset = core::ptr::addr_of!((*novm).memory) as usize;
let regs = unsafe {
&*core::ptr::addr_of!(
(*(((self as *mut _ as *mut u8).sub(offset)) as *const hbvm::Vm<Self, 0>))
.registers
)
};
let mut bytes = core::slice::from_raw_parts(
(addr.get() - 1) as *const u8,
core::mem::size_of::<T>() + 1,
);
use core::fmt::Write;
hbbytecode::parse_args(&mut bytes, instr, &mut self.op_buf).unwrap();
debug_assert!(bytes.is_empty());
self.disp_buf.clear();
write!(self.disp_buf, "{:<10}", format!("{instr:?}")).unwrap();
for (i, op) in self.op_buf.drain(..).enumerate() {
if i != 0 {
write!(self.disp_buf, ", ").unwrap();
}
write!(self.disp_buf, "{op:?}").unwrap();
if let hbbytecode::Oper::R(r) = op {
write!(self.disp_buf, "({})", regs[r as usize].0).unwrap()
}
}
log::trace!("read-typed: {:x}: {}", addr.get(), self.disp_buf);
}
}
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::trace!(
"load: {:x} {}",
addr.get(),
AsHex(core::slice::from_raw_parts(addr.get() as *const u8, count))
);
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::trace!(
"store: {:x} {}",
addr.get(),
AsHex(core::slice::from_raw_parts(source, count))
);
self.mem.store(addr, source, count)
}
unsafe fn prog_read<T: Copy + 'static>(&mut self, addr: hbvm::mem::Address) -> T {
if log::log_enabled!(log::Level::Trace) {
if core::any::TypeId::of::<u8>() == core::any::TypeId::of::<T>() {
if let Some(instr) = self.prev_instr {
self.display_instr::<()>(instr, addr);
}
self.prev_instr = hbbytecode::Instr::try_from(*(addr.get() as *const u8)).ok();
} else {
let instr = self.prev_instr.take().unwrap();
self.display_instr::<T>(instr, addr);
}
}
self.mem.prog_read(addr)
}
}
struct AsHex<'a>(&'a [u8]);
impl core::fmt::Display for AsHex<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for &b in self.0 {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
const VM_STACK_SIZE: usize = 1024 * 64;
pub struct Comptime {
pub vm: hbvm::Vm<LoggedMem, { 1024 * 10 }>,
stack: Box<[u8; VM_STACK_SIZE]>,
pub code: Vec<u8>,
depth: usize,
2024-10-27 08:29:14 -05:00
}
impl Comptime {
pub fn run(&mut self, ret_loc: &mut [u8], offset: u32) -> u64 {
self.vm.write_reg(reg::RET, ret_loc.as_mut_ptr() as u64);
let prev_pc = self.push_pc(offset);
loop {
match self.vm.run().expect("TODO") {
hbvm::VmRunOk::End => break,
hbvm::VmRunOk::Timer => todo!(),
hbvm::VmRunOk::Ecall => todo!(),
hbvm::VmRunOk::Breakpoint => todo!(),
}
}
self.pop_pc(prev_pc);
if let len @ 1..=8 = ret_loc.len() {
ret_loc.copy_from_slice(&self.vm.read_reg(reg::RET).0.to_ne_bytes()[..len])
}
self.vm.read_reg(reg::RET).0
}
pub fn reset(&mut self) {
let ptr = unsafe { self.stack.as_mut_ptr().cast::<u8>().add(VM_STACK_SIZE) as u64 };
self.vm.registers.fill(hbvm::value::Value(0));
self.vm.write_reg(reg::STACK_PTR, ptr);
self.vm.pc = hbvm::mem::Address::new(self.code.as_ptr() as u64 + HEADER_SIZE as u64);
}
fn push_pc(&mut self, offset: Offset) -> hbvm::mem::Address {
let entry = &mut self.code[offset as usize] as *mut _ as _;
core::mem::replace(&mut self.vm.pc, hbvm::mem::Address::new(entry))
- self.code.as_ptr() as usize
}
fn pop_pc(&mut self, prev_pc: hbvm::mem::Address) {
self.vm.pc = prev_pc + self.code.as_ptr() as usize;
}
pub fn clear(&mut self) {
self.code.clear();
}
#[must_use]
pub fn active(&self) -> bool {
self.depth != 0
}
pub fn activate(&mut self) {
self.depth += 1;
}
pub fn deactivate(&mut self) {
self.depth -= 1;
}
2024-10-27 08:29:14 -05:00
}
impl Default for Comptime {
fn default() -> Self {
let mut stack = Box::<[u8; VM_STACK_SIZE]>::new_uninit();
let mut vm = hbvm::Vm::default();
let ptr = unsafe { stack.as_mut_ptr().cast::<u8>().add(VM_STACK_SIZE) as u64 };
vm.write_reg(reg::STACK_PTR, ptr);
Self { vm, stack: unsafe { stack.assume_init() }, code: Default::default(), depth: 0 }
2024-10-27 08:29:14 -05:00
}
}
const HEADER_SIZE: usize = core::mem::size_of::<AbleOsExecutableHeader>();
#[repr(packed)]
#[expect(dead_code)]
pub struct AbleOsExecutableHeader {
magic_number: [u8; 3],
executable_version: u32,
code_length: u64,
data_length: u64,
debug_length: u64,
config_length: u64,
metadata_length: u64,
}
#[cfg(test)]
pub fn test_run_vm(out: &[u8], output: &mut String) {
use core::{ffi::CStr, fmt::Write};
2024-10-27 08:29:14 -05:00
let mut stack = [0_u64; 1024 * 20];
let mut vm = unsafe {
hbvm::Vm::<_, { 1024 * 100 }>::new(
LoggedMem::default(),
hbvm::mem::Address::new(out.as_ptr() as u64).wrapping_add(HEADER_SIZE),
)
};
vm.write_reg(reg::STACK_PTR, unsafe { stack.as_mut_ptr().add(stack.len()) } as u64);
let stat = loop {
match vm.run() {
Ok(hbvm::VmRunOk::End) => break Ok(()),
Ok(hbvm::VmRunOk::Ecall) => match vm.read_reg(2).0 {
37 => writeln!(
output,
"{}",
unsafe { CStr::from_ptr(vm.read_reg(3).0 as _) }.to_str().unwrap()
)
.unwrap(),
2024-10-27 08:29:14 -05:00
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 = core::alloc::Layout::from_size_align(size, align).unwrap();
let ptr = unsafe { alloc::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 = core::alloc::Layout::from_size_align(size, align).unwrap();
unsafe { alloc::alloc::dealloc(ptr as *mut u8, layout) };
}
3 => vm.write_reg(1, 42),
2024-11-04 05:38:47 -06:00
8 => {}
2024-11-07 03:43:29 -06:00
unknown => writeln!(output, "unknown ecall: {unknown:?}").unwrap(),
2024-10-27 08:29:14 -05:00
},
Ok(hbvm::VmRunOk::Timer) => {
writeln!(output, "timed out").unwrap();
break Ok(());
}
Ok(ev) => writeln!(output, "ev: {:?}", ev).unwrap(),
Err(e) => break Err(e),
}
};
writeln!(output, "code size: {}", out.len() - HEADER_SIZE).unwrap();
writeln!(output, "ret: {:?}", vm.read_reg(1).0).unwrap();
writeln!(output, "status: {:?}", stat).unwrap();
}