holey-bytes/hblang/src/codegen.rs
2024-05-19 18:20:42 +02:00

2123 lines
68 KiB
Rust

use std::{
cell::{Cell, RefCell},
ops::Range,
};
use hbvm::Vm;
use crate::{
ident::{self, Ident},
parser::ExprRef,
};
use {
crate::{
instrs, lexer, log,
parser::{self},
},
std::rc::Rc,
};
use {lexer::TokenKind as T, parser::Expr as E};
type LabelId = u32;
type Reg = u8;
type MaskElem = u64;
type Type = u32;
type GlobalId = u32;
fn align_up(value: u64, align: u64) -> u64 {
(value + align - 1) & !(align - 1)
}
enum Signature {
Global(Type),
Function(Box<[Type]>, Type),
}
struct ItemId {
file: parser::FileId,
expr: parser::ExprRef,
}
#[derive(Debug, PartialEq, Eq)]
struct LinReg(Reg, Rc<RefCell<RegAlloc>>);
#[cfg(debug_assertions)]
impl Drop for LinReg {
fn drop(&mut self) {
self.1.borrow_mut().free(self.0)
}
}
struct Stack {
offset: u64,
size: u64,
alloc: Cell<Option<Rc<RefCell<StackAlloc>>>>,
}
impl std::fmt::Debug for Stack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Stack")
.field("offset", &self.offset)
.field("size", &self.size)
.finish()
}
}
impl PartialEq for Stack {
fn eq(&self, other: &Self) -> bool {
self.offset == other.offset && self.size == other.size
}
}
impl Eq for Stack {}
impl Stack {
fn leak(&self) {
self.alloc.set(None);
}
}
impl Drop for Stack {
fn drop(&mut self) {
if let Some(f) = self.alloc.get_mut().as_mut() {
f.borrow_mut().free(self.offset, self.size)
}
}
}
#[derive(Default, PartialEq, Eq, Debug)]
struct StackAlloc {
ranges: Vec<Range<u64>>,
height: u64,
}
impl StackAlloc {
fn alloc(&mut self, size: u64) -> (u64, u64) {
if let Some((index, range)) = self
.ranges
.iter_mut()
.enumerate()
.filter(|(_, range)| range.end - range.start >= size)
.min_by_key(|(_, range)| range.end - range.start)
{
let offset = range.start;
range.start += size;
if range.start == range.end {
self.ranges.swap_remove(index);
}
return (offset, size);
}
let offset = self.height;
self.height += size;
(offset, size)
}
fn free(&mut self, offset: u64, size: u64) {
let range = offset..offset + size;
// FIXME: we do more wor then we need to, rather we keep the sequence sorted and only scan
// element before and after the modified range
self.ranges.push(range);
self.ranges.sort_by_key(|range| range.start);
self.ranges.dedup_by(|b, a| {
if a.end == b.start {
a.end = b.end;
true
} else {
false
}
});
}
fn clear(&mut self) {
assert!(self.ranges.len() <= 1, "{:?}", self.ranges);
self.ranges.clear();
self.height = 0;
}
}
#[derive(Default, Debug)]
enum Ctx {
#[default]
None,
Inferred(Type),
Dest(Value),
DestUntyped(Loc),
}
impl Ctx {
fn ty(&self) -> Option<Type> {
Some(match self {
Self::Inferred(ty) => *ty,
Self::Dest(Value { ty, .. }) => *ty,
_ => return None,
})
}
fn loc(self) -> Option<Loc> {
Some(match self {
Self::Dest(Value { loc, .. }) => loc,
Self::DestUntyped(loc, ..) => loc,
_ => return None,
})
}
}
pub mod bt {
use super::*;
macro_rules! builtin_type {
($($name:ident;)*) => {$(
pub const $name: Type = ${index(0)};
)*};
}
builtin_type! {
UNDECLARED;
NEVER;
VOID;
BOOL;
U8;
U16;
U32;
UINT;
I8;
I16;
I32;
INT;
}
pub fn is_signed(ty: Type) -> bool {
(I8..=INT).contains(&ty)
}
pub fn is_unsigned(ty: Type) -> bool {
(U8..=UINT).contains(&ty)
}
pub fn strip_pointer(ty: Type) -> Type {
match TypeKind::from_ty(ty) {
TypeKind::Pointer(_) => INT,
_ => ty,
}
}
pub fn is_pointer(ty: Type) -> bool {
matches!(TypeKind::from_ty(ty), TypeKind::Pointer(_))
}
pub fn try_upcast(oa: Type, ob: Type) -> Option<Type> {
let (oa, ob) = (oa.min(ob), oa.max(ob));
let (a, b) = (strip_pointer(oa), strip_pointer(ob));
Some(match () {
_ if oa == ob => oa,
_ if is_signed(a) && is_signed(b) || is_unsigned(a) && is_unsigned(b) => ob,
_ if is_unsigned(a) && is_signed(b) && a - U8 < b - I8 => ob,
_ => return None,
})
}
}
#[derive(Debug)]
enum TypeKind {
Builtin(Type),
Struct(Type),
Pointer(Type),
}
impl TypeKind {
const FLAG_BITS: u32 = 2;
const FLAG_OFFSET: u32 = std::mem::size_of::<Type>() as u32 * 8 - Self::FLAG_BITS;
const INDEX_MASK: u32 = (1 << (32 - Self::FLAG_BITS)) - 1;
fn from_ty(ty: Type) -> Self {
let (flag, index) = (ty >> Self::FLAG_OFFSET, ty & Self::INDEX_MASK);
match flag {
0 => Self::Builtin(index),
1 => Self::Pointer(index),
2 => Self::Struct(index),
_ => unreachable!(),
}
}
const fn encode(self) -> Type {
let (index, flag) = match self {
Self::Builtin(index) => (index, 0),
Self::Pointer(index) => (index, 1),
Self::Struct(index) => (index, 2),
};
(flag << Self::FLAG_OFFSET) | index
}
}
const STACK_PTR: Reg = 254;
const ZERO: Reg = 0;
const RET_ADDR: Reg = 31;
const ELEM_WIDTH: usize = std::mem::size_of::<MaskElem>() * 8;
struct Frame {
label: LabelId,
prev_relocs: usize,
offset: u32,
}
struct Reloc {
id: Result<LabelId, GlobalId>,
offset: u32,
instr_offset: u16,
size: u16,
}
struct StackReloc {
offset: u32,
size: u16,
}
#[derive(Default)]
pub struct CodeBlock {
code: Vec<u8>,
relocs: Vec<Reloc>,
}
impl CodeBlock {
pub fn extend(&mut self, bytes: &[u8]) {
self.code.extend_from_slice(bytes);
}
pub fn offset(&mut self, id: LabelId, instr_offset: u16, size: u16) {
self.relocs.push(Reloc {
id: Ok(id),
offset: self.code.len() as u32,
instr_offset,
size,
});
}
fn encode(&mut self, (len, instr): (usize, [u8; instrs::MAX_SIZE])) {
let name = instrs::NAMES[instr[0] as usize];
log::dbg!(
"{:08x}: {}: {}",
self.code.len(),
name,
instr
.iter()
.take(len)
.skip(1)
.map(|b| format!("{:02x}", b))
.collect::<String>()
);
self.code.extend_from_slice(&instr[..len]);
}
fn push(&mut self, value: Reg, size: usize) {
self.subi64(STACK_PTR, STACK_PTR, size as _);
self.encode(instrs::st(value, STACK_PTR, 0, size as _));
}
fn pop(&mut self, value: Reg, size: usize) {
self.encode(instrs::ld(value, STACK_PTR, 0, size as _));
self.addi64(STACK_PTR, STACK_PTR, size as _);
}
fn short_cut_bin_op(&mut self, dest: Reg, src: Reg, imm: u64) -> bool {
if imm == 0 && dest != src {
self.encode(instrs::cp(dest, src));
}
imm != 0
}
fn subi64(&mut self, dest: Reg, src: Reg, imm: u64) {
self.addi64(dest, src, imm.wrapping_neg());
}
fn addi64(&mut self, dest: Reg, src: Reg, imm: u64) {
if self.short_cut_bin_op(dest, src, imm) {
self.encode(instrs::addi64(dest, src, imm));
}
}
fn call(&mut self, func: LabelId) {
self.offset(func, 3, 4);
self.encode(instrs::jal(RET_ADDR, ZERO, 0));
}
fn ret(&mut self) {
self.encode(instrs::jala(ZERO, RET_ADDR, 0));
}
fn prelude(&mut self, entry: LabelId) {
self.call(entry);
self.encode(instrs::tx());
}
fn relocate(&mut self, labels: &[FnLabel], globals: &[Global], shift: i64) {
for reloc in self.relocs.drain(..) {
let offset = match reloc.id {
Ok(id) => labels[id as usize].offset,
Err(id) => globals[id as usize].offset,
};
let offset = if reloc.size == 8 && reloc.id.is_ok() {
reloc.offset as i64
} else {
offset as i64 - reloc.offset as i64
} + shift;
let dest = &mut self.code[reloc.offset as usize + reloc.instr_offset as usize..]
[..reloc.size as usize];
debug_assert!(dest.iter().all(|&b| b == 0));
match reloc.size {
2 => dest.copy_from_slice(&(offset as i16).to_le_bytes()),
4 => dest.copy_from_slice(&(offset as i32).to_le_bytes()),
8 => dest.copy_from_slice(&offset.to_le_bytes()),
_ => unreachable!(),
};
}
}
fn append(&mut self, data: &mut CodeBlock, code_offset: usize, reloc_offset: usize) {
for reloc in &mut data.relocs[reloc_offset..] {
reloc.offset += self.code.len() as u32;
reloc.offset -= code_offset as u32;
}
self.relocs.extend(data.relocs.drain(reloc_offset..));
self.code.extend(data.code.drain(code_offset..));
}
}
#[derive(Default, PartialEq, Eq)]
pub struct RegAlloc {
free: Vec<Reg>,
max_used: Reg,
}
impl std::fmt::Debug for RegAlloc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RegAlloc").finish()
}
}
impl RegAlloc {
fn init_callee(&mut self) {
self.free.clear();
self.free.extend((32..=253).rev());
self.max_used = RET_ADDR;
}
fn allocate(&mut self) -> Reg {
let reg = self.free.pop().expect("TODO: we need to spill");
self.max_used = self.max_used.max(reg);
reg
}
fn free(&mut self, reg: Reg) {
self.free.push(reg);
}
fn pushed_size(&self) -> usize {
(self.max_used as usize - RET_ADDR as usize + 1) * 8
}
}
#[derive(Clone)]
struct FnLabel {
offset: u32,
name: Ident,
args: Rc<[Type]>,
ret: Type,
}
struct Variable {
id: Ident,
value: Value,
}
struct RetReloc {
offset: u32,
instr_offset: u16,
size: u16,
}
struct Loop {
var_count: usize,
offset: u32,
relocs: Vec<RetReloc>,
}
struct Struct {
name: Rc<str>,
id: Ident,
fields: Rc<[(Rc<str>, Type)]>,
}
struct TypeDisplay<'a> {
codegen: &'a Codegen,
ty: Type,
}
impl<'a> TypeDisplay<'a> {
fn new(codegen: &'a Codegen, ty: Type) -> Self {
Self { codegen, ty }
}
fn rety(&self, ty: Type) -> Self {
Self::new(self.codegen, ty)
}
}
impl<'a> std::fmt::Display for TypeDisplay<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use TypeKind as TK;
let str = match TK::from_ty(self.ty) {
TK::Builtin(bt::UNDECLARED) => "undeclared",
TK::Builtin(bt::VOID) => "void",
TK::Builtin(bt::NEVER) => "never",
TK::Builtin(bt::INT) => "int",
TK::Builtin(bt::I32) => "i32",
TK::Builtin(bt::I16) => "i16",
TK::Builtin(bt::I8) => "i8",
TK::Builtin(bt::UINT) => "uint",
TK::Builtin(bt::U32) => "u32",
TK::Builtin(bt::U16) => "u16",
TK::Builtin(bt::U8) => "u8",
TK::Builtin(bt::BOOL) => "bool",
TK::Builtin(_) => unreachable!(),
TK::Pointer(ty) => {
return write!(f, "*{}", self.rety(self.codegen.pointers[ty as usize]))
}
TK::Struct(idx) => return write!(f, "{}", self.codegen.records[idx as usize].name),
};
f.write_str(str)
}
}
struct Global {
id: Ident,
code: u32,
offset: u32,
dep: GlobalId,
ty: Type,
}
#[derive(Default)]
pub struct Codegen {
cf: parser::Ast,
cf_id: parser::FileId,
ret: Type,
ret_reg: Option<Reg>,
cur_global: GlobalId,
main: Option<LabelId>,
to_generate: Vec<ItemId>,
gpa: Rc<RefCell<RegAlloc>>,
sa: Rc<RefCell<StackAlloc>>,
ret_relocs: Vec<RetReloc>,
loops: Vec<Loop>,
code: CodeBlock,
data: CodeBlock,
temp: CodeBlock,
labels: Vec<FnLabel>,
globals: Vec<Global>,
vars: Vec<Variable>,
records: Vec<Struct>,
pointers: Vec<Type>,
pub files: Vec<parser::Ast>,
vm: Vm<LoggedMem, 0>,
}
impl Codegen {
pub fn generate(&mut self) {
self.cur_global = GlobalId::MAX;
self.find_and_declare(0, Err("main"));
while let Some(item) = self.to_generate.pop() {
self.generate_item(item);
}
}
fn generate_item(&mut self, item: ItemId) {
let ast = self.files[item.file as usize].clone();
let expr = item.expr.get(&ast).unwrap();
self.cf = ast.clone();
self.cf_id = item.file;
match expr {
E::BinOp {
left: E::Ident { name, id, .. },
op: T::Decl,
right: E::Closure { body, args, .. },
} => {
log::dbg!("fn: {}", name);
let frame = self.define_fn_label(*id);
if *name == "main" {
self.main = Some(frame.label);
}
let fn_label = self.labels[frame.label as usize].clone();
self.gpa.borrow_mut().init_callee();
log::dbg!("fn-args");
let mut parama = self.param_alloc(fn_label.ret);
for (arg, &ty) in args.iter().zip(fn_label.args.iter()) {
let sym = parser::find_symbol(&self.cf.symbols, arg.id);
let loc = self.load_arg(sym.flags, ty, &mut parama);
self.vars.push(Variable {
id: arg.id,
value: Value { ty, loc },
});
}
if self.size_of(fn_label.ret) > 16 {
let reg = self.gpa.borrow_mut().allocate();
self.code.encode(instrs::cp(reg, 1));
self.ret_reg = Some(reg);
} else {
self.ret_reg = None;
}
self.ret = fn_label.ret;
log::dbg!("fn-body");
if self.expr(body).is_some() {
self.report(body.pos(), "expected all paths in the fucntion to return");
}
self.vars.clear();
log::dbg!("fn-prelude, stack: {:x}", self.sa.borrow().height);
log::dbg!("fn-relocs");
self.write_fn_prelude(frame);
log::dbg!("fn-ret");
self.reloc_rets();
self.ret();
self.sa.borrow_mut().clear();
}
value => todo!(),
}
}
fn align_of(&self, ty: Type) -> u64 {
use TypeKind as TK;
match TypeKind::from_ty(ty) {
TK::Struct(t) => self.records[t as usize]
.fields
.iter()
.map(|&(_, ty)| self.align_of(ty))
.max()
.unwrap(),
_ => self.size_of(ty).max(1),
}
}
fn size_of(&self, ty: Type) -> u64 {
use TypeKind as TK;
match TK::from_ty(ty) {
TK::Pointer(_) => 8,
TK::Builtin(bt::VOID) => 0,
TK::Builtin(bt::NEVER) => unreachable!(),
TK::Builtin(bt::INT | bt::UINT) => 8,
TK::Builtin(bt::I32 | bt::U32) => 4,
TK::Builtin(bt::I16 | bt::U16) => 2,
TK::Builtin(bt::I8 | bt::U8 | bt::BOOL) => 1,
TK::Builtin(e) => unreachable!("{:?}", e),
TK::Struct(ty) => {
let mut offset = 0;
let record = &self.records[ty as usize];
for &(_, ty) in record.fields.iter() {
let align = self.align_of(ty);
offset = align_up(offset, align);
offset += self.size_of(ty);
}
offset
}
}
}
fn display_ty(&self, ty: Type) -> TypeDisplay {
TypeDisplay::new(self, ty)
}
fn unwrap_struct(&self, ty: Type, pos: parser::Pos, context: impl std::fmt::Display) -> Type {
match TypeKind::from_ty(ty) {
TypeKind::Struct(idx) => idx,
_ => self.report(
pos,
format_args!("expected struct, got {} ({context})", self.display_ty(ty)),
),
}
}
fn offset_of(&self, pos: parser::Pos, ty: Type, field: Result<&str, usize>) -> (u64, Type) {
let idx = self.unwrap_struct(ty, pos, "field access");
let record = &self.records[idx as usize];
let mut offset = 0;
for (i, (name, ty)) in record.fields.iter().enumerate() {
if Ok(name.as_ref()) == field || Err(i) == field {
return (offset, *ty);
}
let align = self.align_of(*ty);
offset = align_up(offset, align);
offset += self.size_of(*ty);
}
match field {
Ok(i) => self.report(pos, format_args!("field not found: {i}")),
Err(field) => self.report(pos, format_args!("field not found: {field}")),
}
}
fn alloc_reg(&mut self) -> LinReg {
LinReg(self.gpa.borrow_mut().allocate(), self.gpa.clone())
}
fn alloc_stack(&mut self, size: u64) -> Rc<Stack> {
let (offset, size) = self.sa.borrow_mut().alloc(size);
Stack {
offset,
size,
alloc: Cell::new(Some(self.sa.clone())),
}
.into()
}
fn loc_to_reg(&mut self, loc: Loc, size: u64) -> LinReg {
match loc {
Loc::RegRef(rr) => {
let reg = self.alloc_reg();
self.code.encode(instrs::cp(reg.0, rr));
reg
}
Loc::Reg(reg) => reg,
Loc::Deref(dreg, .., offset) => {
let reg = self.alloc_reg();
self.code
.encode(instrs::ld(reg.0, dreg.0, offset, size as _));
reg
}
Loc::DerefRef(dreg, .., offset) => {
let reg = self.alloc_reg();
self.code.encode(instrs::ld(reg.0, dreg, offset, size as _));
reg
}
Loc::Imm(imm) => {
let reg = self.alloc_reg();
self.code.encode(instrs::li64(reg.0, imm));
reg
}
Loc::Stack(stack, off) => {
let reg = self.alloc_reg();
self.load_stack(reg.0, stack.offset + off, size as _);
reg
}
}
}
fn store_stack(&mut self, reg: Reg, offset: u64, size: u16) {
self.code.encode(instrs::st(reg, STACK_PTR, offset, size));
}
fn load_stack(&mut self, reg: Reg, offset: u64, size: u16) {
self.code.encode(instrs::ld(reg, STACK_PTR, offset, size));
}
fn reloc_rets(&mut self) {
let len = self.code.code.len() as i32;
for reloc in self.ret_relocs.drain(..) {
let dest = &mut self.code.code[reloc.offset as usize + reloc.instr_offset as usize..]
[..reloc.size as usize];
debug_assert!(dest.iter().all(|&b| b == 0));
let offset = len - reloc.offset as i32;
dest.copy_from_slice(&offset.to_ne_bytes());
}
}
fn ty(&mut self, expr: &parser::Expr) -> Type {
match *expr {
E::Ident { id, .. } if ident::is_null(id) => id,
E::UnOp {
op: T::Mul, val, ..
} => {
let ty = self.ty(val);
self.alloc_pointer(ty)
}
E::Ident { id, .. } => {
let index = match self.records.iter().position(|r| r.id == id) {
Some(index) => index as Type,
None => self.find_and_declare(0, Ok(id)),
};
TypeKind::Struct(index).encode()
}
expr => unimplemented!("type: {:#?}", expr),
}
}
fn expr(&mut self, expr: &parser::Expr) -> Option<Value> {
self.expr_ctx(expr, Ctx::default())
}
fn expr_ctx(&mut self, expr: &parser::Expr, mut ctx: Ctx) -> Option<Value> {
use instrs as i;
let value = match *expr {
E::Directive {
name: "eca",
args: [ret_ty, args @ ..],
..
} => {
let ty = self.ty(ret_ty);
let mut parama = self.param_alloc(ty);
let mut values = Vec::with_capacity(args.len());
for arg in args {
let arg = self.expr(arg)?;
self.pass_arg(&arg, &mut parama);
values.push(arg.loc);
}
drop(values);
let loc = self.alloc_ret_loc(ty, ctx);
self.code.encode(i::eca());
self.post_process_ret_loc(ty, &loc);
return Some(Value { ty, loc });
}
E::Directive {
name: "sizeof",
args: [ty],
..
} => {
let ty = self.ty(ty);
let loc = Loc::Imm(self.size_of(ty));
return Some(Value { ty: bt::UINT, loc });
}
E::Directive {
name: "alignof",
args: [ty],
..
} => {
let ty = self.ty(ty);
let loc = Loc::Imm(self.align_of(ty));
return Some(Value { ty: bt::UINT, loc });
}
E::Directive {
name: "intcast",
args: [val],
..
} => {
let Some(ty) = ctx.ty() else {
self.report(
expr.pos(),
"type to cast to is unknown, use `@as(<type>, <expr>)`",
);
};
let mut val = self.expr(val)?;
let from_size = self.size_of(val.ty);
let to_size = self.size_of(ty);
if from_size < to_size && bt::is_signed(val.ty) {
let reg = self.loc_to_reg(val.loc, from_size);
let op = [i::sxt8, i::sxt16, i::sxt32][from_size.ilog2() as usize];
self.code.encode(op(reg.0, reg.0));
val.loc = Loc::Reg(reg);
}
Some(Value { ty, loc: val.loc })
}
E::Directive {
name: "bitcast",
args: [val],
..
} => {
let Some(ty) = ctx.ty() else {
self.report(
expr.pos(),
"type to cast to is unknown, use `@as(<type>, <expr>)`",
);
};
let size = self.size_of(ty);
ctx = match ctx {
Ctx::Dest(Value { loc, .. }) | Ctx::DestUntyped(loc, ..) => {
Ctx::DestUntyped(loc)
}
_ => Ctx::None,
};
let val = self.expr_ctx(val, ctx)?;
if self.size_of(val.ty) != size {
self.report(
expr.pos(),
format_args!(
"cannot bitcast {} to {} (different sizes: {} != {size})",
self.display_ty(val.ty),
self.display_ty(ty),
self.size_of(val.ty),
),
);
}
// TODO: maybe check align
return Some(Value { ty, loc: val.loc });
}
E::Directive {
name: "as",
args: [ty, val],
..
} => {
let ty = self.ty(ty);
let ctx = match ctx {
Ctx::Dest(dest) => Ctx::Dest(dest),
Ctx::DestUntyped(loc) => Ctx::Dest(Value { ty, loc }),
_ => Ctx::Inferred(ty),
};
return self.expr_ctx(val, ctx);
}
E::Bool { value, .. } => Some(Value {
ty: bt::BOOL,
loc: Loc::Imm(value as u64),
}),
E::Ctor {
pos, ty, fields, ..
} => {
let Some(ty) = ty.map(|ty| self.ty(ty)).or(ctx.ty()) else {
self.report(pos, "expected type, (it cannot be inferred)");
};
let size = self.size_of(ty);
let loc = match ctx.loc() {
Some(loc) => loc,
_ => Loc::Stack(self.alloc_stack(size), 0),
};
let stuct = self.unwrap_struct(ty, pos, "struct literal");
let field_count = self.records[stuct as usize].fields.len();
if field_count != fields.len() {
self.report(
pos,
format_args!("expected {} fields, got {}", field_count, fields.len()),
);
}
for (i, (name, field)) in fields.iter().enumerate() {
let (offset, ty) = self.offset_of(field.pos(), ty, name.ok_or(i));
let loc = loc.offset_ref(offset);
self.expr_ctx(field, Ctx::Dest(Value { ty, loc }))?;
}
return Some(Value { ty, loc });
}
E::Field { target, field } => {
let mut tal = self.expr(target)?;
if let TypeKind::Pointer(ty) = TypeKind::from_ty(tal.ty) {
tal.ty = self.pointers[ty as usize];
tal.loc = match tal.loc {
Loc::Reg(r) => Loc::Deref(r, None, 0),
Loc::RegRef(r) => Loc::DerefRef(r, None, 0),
l => {
let ptr = self.loc_to_reg(l, 8);
Loc::Deref(ptr, None, 0)
}
};
}
let (offset, ty) = self.offset_of(target.pos(), tal.ty, Ok(field));
let loc = tal.loc.offset(offset);
Some(Value { ty, loc })
}
E::UnOp {
op: T::Band,
val,
pos,
} => {
let val = self.expr(val)?;
let loc = match val.loc {
Loc::Deref(r, stack, off) => {
if let Some(stack) = stack {
stack.leak()
}
self.code.addi64(r.0, r.0, off);
Loc::Reg(r)
}
Loc::DerefRef(r, stack, off) => {
if let Some(stack) = stack {
stack.leak()
}
let reg = self.alloc_reg();
self.code.addi64(reg.0, r, off);
Loc::Reg(reg)
}
Loc::Stack(stack, off) => {
stack.leak();
let reg = self.alloc_reg();
self.code
.encode(i::addi64(reg.0, STACK_PTR, stack.offset + off));
Loc::Reg(reg)
}
l => self.report(
pos,
format_args!("cant take pointer of {} ({:?})", self.display_ty(val.ty), l),
),
};
Some(Value {
ty: self.alloc_pointer(val.ty),
loc,
})
}
E::UnOp {
op: T::Mul,
val,
pos,
} => {
let val = self.expr(val)?;
match TypeKind::from_ty(val.ty) {
TypeKind::Pointer(ty) => Some(Value {
ty: self.pointers[ty as usize],
loc: Loc::Deref(self.loc_to_reg(val.loc, self.size_of(val.ty)), None, 0),
}),
_ => self.report(
pos,
format_args!("expected pointer, got {}", self.display_ty(val.ty)),
),
}
}
E::BinOp {
left: E::Ident { id, .. },
op: T::Decl,
right,
} => {
let val = self.expr(right)?;
let loc = self.make_loc_owned(val.loc, val.ty);
let sym = parser::find_symbol(&self.cf.symbols, *id);
let loc = match loc {
Loc::Reg(r) if sym.flags & parser::REFERENCED != 0 => {
let size = self.size_of(val.ty);
let stack = self.alloc_stack(size);
self.store_stack(r.0, stack.offset, size as _);
Loc::Stack(stack, 0)
}
l => l,
};
self.vars.push(Variable {
id: *id,
value: Value { ty: val.ty, loc },
});
Some(Value::VOID)
}
E::Call {
func: &E::Ident { id, .. },
args,
} => {
let func = match self.get_label(id) {
Some(func) => func,
None => self.find_and_declare(0, Ok(id)),
};
let fn_label = self.labels[func as usize].clone();
let mut parama = self.param_alloc(fn_label.ret);
let mut values = Vec::with_capacity(args.len());
for (earg, &ty) in args.iter().zip(fn_label.args.iter()) {
let arg = self.expr_ctx(earg, Ctx::Inferred(ty))?;
_ = self.assert_ty(earg.pos(), ty, arg.ty);
self.pass_arg(&arg, &mut parama);
values.push(arg.loc);
}
drop(values);
let loc = self.alloc_ret_loc(fn_label.ret, ctx);
self.code.call(func);
self.post_process_ret_loc(fn_label.ret, &loc);
return Some(Value {
ty: fn_label.ret,
loc,
});
}
E::Ident { id, index, .. }
if let Some((var_index, var)) =
self.vars.iter_mut().enumerate().find(|(_, v)| v.id == id) =>
{
let sym = parser::find_symbol(&self.cf.symbols, id);
let loc = match parser::ident_flag_index(sym.flags) == index
&& !self.loops.last().is_some_and(|l| l.var_count > var_index)
{
true => std::mem::replace(&mut var.value.loc, Loc::Imm(0)),
false => var.value.loc.take_ref(),
};
Some(Value {
ty: var.value.ty,
loc,
})
}
E::Ident { id, .. } => {
let id = match self.globals.iter().position(|g| g.id == id) {
Some(id) => id as GlobalId,
None => self.find_and_declare(0, Ok(id)),
};
let ptr = self.alloc_reg();
let global = &mut self.globals[id as usize];
if self.cur_global != GlobalId::MAX {
global.dep = global.dep.max(id);
}
self.code.relocs.push(Reloc {
id: Err(id),
offset: self.code.code.len() as u32,
instr_offset: 3,
size: 4,
});
self.code.encode(i::lra(ptr.0, 0, 0));
Some(Value {
ty: global.ty,
loc: Loc::Deref(ptr, None, 0),
})
}
E::Return { val, .. } => {
if let Some(val) = val {
let size = self.size_of(self.ret);
let loc = match size {
0 => Loc::Imm(0),
..=16 => Loc::RegRef(1),
_ => Loc::DerefRef(1, None, 0),
};
self.expr_ctx(val, Ctx::Dest(Value { loc, ty: self.ret }))?;
}
self.ret_relocs.push(RetReloc {
offset: self.code.code.len() as u32,
instr_offset: 1,
size: 4,
});
self.code.encode(i::jmp(0));
None
}
E::Block { stmts, .. } => {
for stmt in stmts {
self.expr(stmt)?;
}
Some(Value::VOID)
}
E::Number { value, .. } => Some(Value {
ty: ctx.ty().map(bt::strip_pointer).unwrap_or(bt::INT),
loc: Loc::Imm(value),
}),
E::If {
cond, then, else_, ..
} => 'b: {
log::dbg!("if-cond");
let cond = self.expr_ctx(cond, Ctx::Inferred(bt::BOOL))?;
let reg = self.loc_to_reg(cond.loc, 1);
let jump_offset = self.code.code.len() as u32;
self.code.encode(i::jeq(reg.0, 0, 0));
log::dbg!("if-then");
let then_unreachable = self.expr(then).is_none();
let mut else_unreachable = false;
let mut jump = self.code.code.len() as i16 - jump_offset as i16;
if let Some(else_) = else_ {
log::dbg!("if-else");
let else_jump_offset = self.code.code.len() as u32;
if !then_unreachable {
self.code.encode(i::jmp(0));
jump = self.code.code.len() as i16 - jump_offset as i16;
}
else_unreachable = self.expr(else_).is_none();
if !then_unreachable {
let jump = self.code.code.len() as i32 - else_jump_offset as i32;
log::dbg!("if-else-jump: {}", jump);
self.code.code[else_jump_offset as usize + 1..][..4]
.copy_from_slice(&jump.to_ne_bytes());
}
}
log::dbg!("if-then-jump: {}", jump);
self.code.code[jump_offset as usize + 3..][..2]
.copy_from_slice(&jump.to_ne_bytes());
if then_unreachable && else_unreachable {
break 'b None;
}
Some(Value::VOID)
}
E::Loop { body, .. } => 'a: {
log::dbg!("loop");
let loop_start = self.code.code.len() as u32;
self.loops.push(Loop {
var_count: self.vars.len() as _,
offset: loop_start,
relocs: Default::default(),
});
let body_unreachable = self.expr(body).is_none();
log::dbg!("loop-end");
if !body_unreachable {
let loop_end = self.code.code.len();
self.code
.encode(i::jmp(loop_start as i32 - loop_end as i32));
}
let loop_end = self.code.code.len() as u32;
let loop_ = self.loops.pop().unwrap();
let is_unreachable = loop_.relocs.is_empty();
for reloc in loop_.relocs {
let dest = &mut self.code.code
[reloc.offset as usize + reloc.instr_offset as usize..]
[..reloc.size as usize];
let offset = loop_end as i32 - reloc.offset as i32;
dest.copy_from_slice(&offset.to_ne_bytes());
}
self.vars.drain(loop_.var_count..);
if is_unreachable {
log::dbg!("infinite loop");
break 'a None;
}
Some(Value::VOID)
}
E::Break { .. } => {
let loop_ = self.loops.last_mut().unwrap();
let offset = self.code.code.len() as u32;
self.code.encode(i::jmp(0));
loop_.relocs.push(RetReloc {
offset,
instr_offset: 1,
size: 4,
});
None
}
E::Continue { .. } => {
let loop_ = self.loops.last().unwrap();
let offset = self.code.code.len() as u32;
self.code
.encode(i::jmp(loop_.offset as i32 - offset as i32));
None
}
E::BinOp {
left,
op: op @ (T::And | T::Or),
right,
} => {
let lhs = self.expr_ctx(left, Ctx::Inferred(bt::BOOL))?;
let lhs = self.loc_to_reg(lhs.loc, 1);
let jump_offset = self.code.code.len() as u32 + 3;
let op = if op == T::And { i::jeq } else { i::jne };
self.code.encode(op(lhs.0, 0, 0));
if let Some(rhs) = self.expr_ctx(right, Ctx::Inferred(bt::BOOL)) {
let rhs = self.loc_to_reg(rhs.loc, 1);
self.code.encode(i::cp(lhs.0, rhs.0));
}
let jump = self.code.code.len() as i16 - jump_offset as i16;
self.code.code[jump_offset as usize..][..2].copy_from_slice(&jump.to_ne_bytes());
Some(Value {
ty: bt::BOOL,
loc: Loc::Reg(lhs),
})
}
E::BinOp { left, op, right } => 'ops: {
let left = self.expr(left)?;
if op == T::Assign {
self.expr_ctx(right, Ctx::Dest(left)).unwrap();
return Some(Value::VOID);
}
if let TypeKind::Struct(_) = TypeKind::from_ty(left.ty) {
let right = self.expr_ctx(right, Ctx::Inferred(left.ty))?;
_ = self.assert_ty(expr.pos(), left.ty, right.ty);
return self.struct_op(op, left.ty, ctx, left.loc, right.loc);
}
let lsize = self.size_of(left.ty);
let ty = ctx.ty().unwrap_or(left.ty);
let (lhs, loc) = match std::mem::take(&mut ctx).loc() {
Some(Loc::RegRef(reg)) if Loc::RegRef(reg) == left.loc && reg != 1 => {
(reg, Loc::RegRef(reg))
}
Some(loc) => {
debug_assert!(!matches!(loc, Loc::Reg(LinReg(RET_ADDR, ..))));
ctx = Ctx::Dest(Value { ty, loc });
let reg = self.loc_to_reg(left.loc, lsize);
(reg.0, Loc::Reg(reg))
}
None => {
let reg = self.loc_to_reg(left.loc, lsize);
(reg.0, Loc::Reg(reg))
}
};
let right = self.expr_ctx(right, Ctx::Inferred(left.ty))?;
let rsize = self.size_of(right.ty);
let ty = self.assert_ty(expr.pos(), left.ty, right.ty);
let size = self.size_of(ty);
let signed = bt::is_signed(ty);
if let Loc::Imm(mut imm) = right.loc
&& let Some(oper) = Self::imm_math_op(op, signed, size)
{
if matches!(op, T::Add | T::Sub)
&& let TypeKind::Pointer(ty) = TypeKind::from_ty(ty)
{
let size = self.size_of(self.pointers[ty as usize]);
imm *= size;
}
self.code.encode(oper(lhs, lhs, imm));
break 'ops Some(Value { ty, loc });
}
let rhs = self.loc_to_reg(right.loc, rsize);
if matches!(op, T::Add | T::Sub) {
let min_size = lsize.min(rsize);
if bt::is_signed(ty) && min_size < size {
let operand = if lsize < rsize { lhs } else { rhs.0 };
let op = [i::sxt8, i::sxt16, i::sxt32][min_size.ilog2() as usize];
self.code.encode(op(operand, operand));
}
if bt::is_pointer(left.ty) ^ bt::is_pointer(right.ty) {
let (offset, ty) = if bt::is_pointer(left.ty) {
(rhs.0, left.ty)
} else {
(lhs, right.ty)
};
let TypeKind::Pointer(ty) = TypeKind::from_ty(ty) else {
unreachable!()
};
let size = self.size_of(self.pointers[ty as usize]);
self.code.encode(i::muli64(offset, offset, size as _));
}
}
if let Some(op) = Self::math_op(op, signed, size) {
self.code.encode(op(lhs, lhs, rhs.0));
break 'ops Some(Value { ty, loc });
}
'cmp: {
let against = match op {
T::Le | T::Gt => 1,
T::Ne | T::Eq => 0,
T::Ge | T::Lt => (-1i64) as _,
_ => break 'cmp,
};
let op_fn = if signed { i::cmps } else { i::cmpu };
self.code.encode(op_fn(lhs, lhs, rhs.0));
self.code.encode(i::cmpui(lhs, lhs, against));
if matches!(op, T::Eq | T::Lt | T::Gt) {
self.code.encode(i::not(lhs, lhs));
}
break 'ops Some(Value { ty: bt::BOOL, loc });
}
unimplemented!("{:#?}", op)
}
ast => unimplemented!("{:#?}", ast),
}?;
match ctx {
Ctx::Dest(dest) => {
_ = self.assert_ty(expr.pos(), dest.ty, value.ty);
self.assign(dest.ty, dest.loc, value.loc)?;
Some(Value {
ty: dest.ty,
loc: Loc::Imm(0),
})
}
Ctx::DestUntyped(loc) => {
// Wo dont check since bitcast does
self.assign(value.ty, loc, value.loc);
Some(Value {
ty: value.ty,
loc: Loc::Imm(0),
})
}
_ => Some(value),
}
}
fn math_op(
op: T,
signed: bool,
size: u64,
) -> Option<fn(u8, u8, u8) -> (usize, [u8; instrs::MAX_SIZE])> {
use instrs as i;
macro_rules! div { ($($op:ident),*) => {[$(|a, b, c| i::$op(a, ZERO, b, c)),*]}; }
macro_rules! rem { ($($op:ident),*) => {[$(|a, b, c| i::$op(ZERO, a, b, c)),*]}; }
let ops = match op {
T::Add => [i::add8, i::add16, i::add32, i::add64],
T::Sub => [i::sub8, i::sub16, i::sub32, i::sub64],
T::Mul => [i::mul8, i::mul16, i::mul32, i::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(i::and),
T::Bor => return Some(i::or),
T::Xor => return Some(i::xor),
T::Shl => [i::slu8, i::slu16, i::slu32, i::slu64],
T::Shr if signed => [i::srs8, i::srs16, i::srs32, i::srs64],
T::Shr => [i::sru8, i::sru16, i::sru32, i::sru64],
_ => return None,
};
Some(ops[size.ilog2() as usize])
}
fn imm_math_op(
op: T,
signed: bool,
size: u64,
) -> Option<fn(u8, u8, u64) -> (usize, [u8; instrs::MAX_SIZE])> {
use instrs as i;
macro_rules! def_op {
($name:ident |$a:ident, $b:ident, $c:ident| $($tt:tt)*) => {
macro_rules! $name {
($$($$op:ident),*) => {
[$$(
|$a, $b, $c: u64| i::$$op($($tt)*),
)*]
}
}
};
}
def_op!(basic_op | a, b, c | a, b, c as _);
def_op!(sub_op | a, b, c | b, a, c.wrapping_neg() as _);
let ops = match op {
T::Add => basic_op!(addi8, addi16, addi32, addi64),
T::Sub => sub_op!(addi8, addi16, addi32, addi64),
T::Mul => basic_op!(muli8, muli16, muli32, muli64),
T::Band => return Some(i::andi),
T::Bor => return Some(i::ori),
T::Xor => return Some(i::xori),
T::Shr if signed => basic_op!(srui8, srui16, srui32, srui64),
T::Shr => basic_op!(srui8, srui16, srui32, srui64),
T::Shl => basic_op!(slui8, slui16, slui32, slui64),
_ => return None,
};
Some(ops[size.ilog2() as usize])
}
fn struct_op(&mut self, op: T, ty: Type, ctx: Ctx, left: Loc, right: Loc) -> Option<Value> {
if let TypeKind::Struct(stuct) = TypeKind::from_ty(ty) {
let dst = match ctx {
Ctx::Dest(dest) => dest.loc,
_ => Loc::Stack(self.alloc_stack(self.size_of(ty)), 0),
};
let mut offset = 0;
for &(_, ty) in self.records[stuct as usize].fields.clone().iter() {
let align = self.align_of(ty);
offset = align_up(offset, align);
let size = self.size_of(ty);
let ctx = Ctx::Dest(Value::new(ty, dst.offset_ref(offset)));
let left = left.offset_ref(offset);
let right = right.offset_ref(offset);
self.struct_op(op, ty, ctx, left, right)?;
offset += size;
}
return Some(Value { ty, loc: dst });
}
let size = self.size_of(ty);
let signed = bt::is_signed(ty);
let (lhs, owned) = self.loc_to_reg_ref(&left, size);
if let Loc::Imm(imm) = right
&& let Some(op) = Self::imm_math_op(op, signed, size)
{
self.code.encode(op(lhs, lhs, imm));
return if let Ctx::Dest(dest) = ctx {
self.assign(dest.ty, dest.loc, owned.map_or(Loc::RegRef(lhs), Loc::Reg));
Some(Value::VOID)
} else {
Some(Value::new(ty, owned.map_or(Loc::RegRef(lhs), Loc::Reg)))
};
}
let rhs = self.loc_to_reg(right, size);
if let Some(op) = Self::math_op(op, signed, size) {
self.code.encode(op(lhs, lhs, rhs.0));
return if let Ctx::Dest(dest) = ctx {
self.assign(dest.ty, dest.loc, owned.map_or(Loc::RegRef(lhs), Loc::Reg));
Some(Value::VOID)
} else {
Some(Value::new(ty, owned.map_or(Loc::RegRef(lhs), Loc::Reg)))
};
}
unimplemented!("{:#?}", op)
}
fn loc_to_reg_ref(&mut self, loc: &Loc, size: u64) -> (u8, Option<LinReg>) {
match *loc {
Loc::RegRef(reg) => (reg, None),
Loc::Reg(LinReg(reg, ..)) => (reg, None),
Loc::Deref(LinReg(reg, ..), .., off) | Loc::DerefRef(reg, .., off) => {
let new = self.alloc_reg();
self.code.encode(instrs::ld(new.0, reg, off, size as _));
(new.0, Some(new))
}
Loc::Stack(ref stack, off) => {
let new = self.alloc_reg();
self.load_stack(new.0, stack.offset + off, size as _);
(new.0, Some(new))
}
Loc::Imm(imm) => {
let new = self.alloc_reg();
self.code.encode(instrs::li64(new.0, imm));
(new.0, Some(new))
}
}
}
fn ensure_sign_extended(&mut self, val: Value, ty: Type) -> Value {
let size = self.size_of(ty);
let lsize = self.size_of(val.ty);
if lsize < size {
let reg = self.loc_to_reg(val.loc, lsize);
let op = [instrs::sxt8, instrs::sxt16, instrs::sxt32][lsize.ilog2() as usize];
self.code.encode(op(reg.0, reg.0));
Value {
ty,
loc: Loc::Reg(reg),
}
} else {
val
}
}
fn assign_opaque(&mut self, size: u64, right: Loc, left: Loc) -> Option<Value> {
if left == right {
return Some(Value::VOID);
}
match size {
0 => {}
..=8 => {
let lhs = self.loc_to_reg(left, size);
match right {
Loc::RegRef(reg) => self.code.encode(instrs::cp(reg, lhs.0)),
Loc::Deref(reg, .., off) => {
self.code.encode(instrs::st(lhs.0, reg.0, off, size as _));
}
Loc::DerefRef(reg, .., off) => {
self.code.encode(instrs::st(lhs.0, reg, off, size as _));
}
Loc::Stack(stack, off) => {
self.store_stack(lhs.0, stack.offset + off, size as _);
}
l => unimplemented!("{:?}", l),
}
}
..=16 if matches!(right, Loc::RegRef(1)) => {
let (lhs, loff) = left.ref_to_ptr();
self.code.encode(instrs::st(1, lhs, loff, 16));
}
..=u64::MAX => {
let rhs = self.to_ptr(right);
let lhs = self.to_ptr(left);
self.code
.encode(instrs::bmc(lhs.0, rhs.0, size.try_into().unwrap()));
}
}
Some(Value::VOID)
}
fn assign(&mut self, ty: Type, right: Loc, left: Loc) -> Option<Value> {
self.assign_opaque(self.size_of(ty), right, left)
}
fn to_ptr(&mut self, loc: Loc) -> LinReg {
match loc {
Loc::Deref(reg, .., off) => {
self.code.addi64(reg.0, reg.0, off);
reg
}
Loc::DerefRef(reg, .., off) => {
let new = self.alloc_reg();
self.code.addi64(new.0, reg, off);
new
}
Loc::Stack(stack, off) => {
let reg = self.alloc_reg();
self.code.addi64(reg.0, STACK_PTR, stack.offset + off);
reg
}
l => unreachable!("{:?}", l),
}
}
fn ensure_owned(&mut self, loc: Loc, ty: Type) -> Loc {
match loc {
Loc::RegRef(reg) => {
let new = self.alloc_reg();
self.code.encode(instrs::cp(new.0, reg));
Loc::Reg(new)
}
l => {
let size = self.size_of(ty);
let stack = self.alloc_stack(size);
self.assign(ty, Loc::DerefRef(STACK_PTR, None, stack.offset), l);
Loc::Stack(stack, 0)
}
}
}
fn find_and_declare(&mut self, file: parser::FileId, name: Result<Ident, &str>) -> LabelId {
let f = self.files[file as usize].clone();
let expr = f.find_decl(name).expect("TODO: error");
match expr {
E::BinOp {
left: &E::Ident { id, .. },
op: T::Decl,
right: E::Closure { args, ret, .. },
} => {
let args = args.iter().map(|arg| self.ty(&arg.ty)).collect::<Vec<_>>();
let ret = self.ty(ret);
self.to_generate.push(ItemId {
file,
expr: ExprRef::new(expr),
});
self.declare_fn_label(id, args.into(), ret)
}
E::BinOp {
left: &E::Ident { id, name, .. },
op: T::Decl,
right: E::Struct { fields, .. },
} => {
let fields = fields
.iter()
.map(|&(name, ty)| (name.into(), self.ty(&ty)))
.collect();
self.records.push(Struct {
id,
fields,
name: name.into(),
});
self.records.len() as u32 - 1
}
E::BinOp {
left: &E::Ident { id, .. },
op: T::Decl,
right,
} => {
let gid = self.globals.len() as GlobalId;
let prev_in_global = std::mem::replace(&mut self.cur_global, gid);
let prev_gpa = std::mem::replace(&mut *self.gpa.borrow_mut(), Default::default());
let prev_sa = std::mem::replace(&mut *self.sa.borrow_mut(), Default::default());
let offset = self.code.code.len();
let reloc_count = self.code.relocs.len();
self.globals.push(Global {
id,
ty: bt::UNDECLARED,
code: 0,
dep: 0,
offset: u32::MAX,
});
self.gpa.borrow_mut().init_callee();
let ret = self.gpa.borrow_mut().allocate();
// TODO: detect is constant does not call anything
self.code.encode(instrs::cp(ret, 1));
let ret = self
.expr_ctx(right, Ctx::DestUntyped(Loc::DerefRef(ret, None, 0)))
.expect("TODO: unreachable constant/global");
self.code.encode(instrs::tx());
self.globals[gid as usize].ty = ret.ty;
self.globals[gid as usize].code = self.data.code.len() as u32;
self.data.append(&mut self.code, offset, reloc_count);
*self.sa.borrow_mut() = prev_sa;
*self.gpa.borrow_mut() = prev_gpa;
self.cur_global = prev_in_global;
gid
}
e => unimplemented!("{e:#?}"),
}
}
fn declare_fn_label(&mut self, name: Ident, args: Rc<[Type]>, ret: Type) -> LabelId {
self.labels.push(FnLabel {
offset: 0,
name,
args,
ret,
});
self.labels.len() as u32 - 1
}
fn define_fn_label(&mut self, name: Ident) -> Frame {
let offset = self.code.code.len() as u32;
let label = self.get_label(name).unwrap();
self.labels[label as usize].offset = offset;
Frame {
label,
prev_relocs: self.code.relocs.len(),
offset,
}
}
fn get_label(&self, name: Ident) -> Option<LabelId> {
self.labels
.iter()
.position(|l| l.name == name)
.map(|l| l as _)
}
fn write_fn_prelude(&mut self, frame: Frame) {
self.temp.push(RET_ADDR, self.gpa.borrow().pushed_size());
self.temp
.subi64(STACK_PTR, STACK_PTR, self.sa.borrow().height);
for reloc in &mut self.code.relocs[frame.prev_relocs..] {
reloc.offset += self.temp.code.len() as u32;
}
for reloc in &mut self.ret_relocs {
reloc.offset += self.temp.code.len() as u32;
}
self.code.code.splice(
frame.offset as usize..frame.offset as usize,
self.temp.code.drain(..),
);
}
fn ret(&mut self) {
self.code.encode(instrs::addi64(
STACK_PTR,
STACK_PTR,
self.sa.borrow().height,
));
self.code.pop(RET_ADDR, self.gpa.borrow().pushed_size());
self.code.ret();
}
pub fn dump(mut self, out: &mut impl std::io::Write) -> std::io::Result<()> {
self.temp.prelude(self.main.unwrap());
self.temp
.relocate(&self.labels, &self.globals, self.temp.code.len() as i64);
let mut globals = std::mem::take(&mut self.globals);
for global in globals.iter_mut() {
let size = self.size_of(global.ty);
global.offset = self.code.code.len() as u32;
self.code.code.extend(std::iter::repeat(0).take(size as _));
}
self.globals = globals;
let prev_len = self.code.code.len();
self.code.append(&mut self.data, 0, 0);
self.code.relocate(&self.labels, &self.globals, 0);
{
let mut var_order = self
.globals
.iter()
.map(|g| g.dep)
.zip(0u32..)
.collect::<Vec<_>>();
var_order.sort_unstable();
let stack_size = 1024 * 1024 * 2;
let mut stack = Vec::<u8>::with_capacity(stack_size);
for (_, glob_id) in var_order.into_iter().rev() {
let global = &self.globals[glob_id as usize];
self.vm.pc = hbvm::mem::Address::new(
&mut self.code.code[global.code as usize + prev_len] as *mut _ as u64,
);
self.vm
.write_reg(254, unsafe { stack.as_mut_ptr().add(stack_size) } as u64);
self.vm.write_reg(
1,
&mut self.code.code[global.offset as usize] as *mut _ as u64,
);
self.vm.run().unwrap();
}
}
self.code.code.truncate(prev_len);
out.write_all(&self.temp.code)?;
out.write_all(&self.code.code)
}
fn alloc_pointer(&mut self, ty: Type) -> Type {
let ty = self
.pointers
.iter()
.position(|&p| p == ty)
.unwrap_or_else(|| {
self.pointers.push(ty);
self.pointers.len() - 1
});
TypeKind::Pointer(ty as Type).encode()
}
fn make_loc_owned(&mut self, loc: Loc, ty: Type) -> Loc {
match loc {
Loc::RegRef(rreg) => {
let reg = self.alloc_reg();
self.code.encode(instrs::cp(reg.0, rreg));
Loc::Reg(reg)
}
Loc::Imm(imm) => {
let reg = self.alloc_reg();
self.code.encode(instrs::li64(reg.0, imm));
Loc::Reg(reg)
}
l @ (Loc::DerefRef(..) | Loc::Deref(..)) => {
let size = self.size_of(ty);
let stack = self.alloc_stack(size);
self.assign(ty, Loc::DerefRef(STACK_PTR, None, stack.offset), l);
Loc::Stack(stack, 0)
}
l => l,
}
}
fn pass_arg(&mut self, value: &Value, parama: &mut Range<u8>) {
let size = self.size_of(value.ty);
let p = parama.next().unwrap() as Reg;
if size > 16 {
let (ptr, off) = value.loc.ref_to_ptr();
self.code.addi64(p, ptr, off);
return;
}
match value.loc {
Loc::Reg(LinReg(reg, ..)) | Loc::RegRef(reg) => self.code.encode(instrs::cp(p, reg)),
Loc::Deref(LinReg(reg, ..), .., off) | Loc::DerefRef(reg, .., off) => {
self.code.encode(instrs::ld(p, reg, off, size as _));
}
Loc::Imm(imm) => self.code.encode(instrs::li64(p, imm)),
Loc::Stack(ref stack, off) => {
self.load_stack(p, stack.offset + off, size as _);
if size > 8 {
parama.next().unwrap();
}
}
}
}
fn load_arg(&mut self, flags: parser::IdentFlags, ty: Type, parama: &mut Range<u8>) -> Loc {
let size = self.size_of(ty);
match size {
0 => Loc::Imm(0),
..=8 if flags & parser::REFERENCED == 0 => {
let reg = self.alloc_reg();
self.code.encode(instrs::cp(reg.0, parama.next().unwrap()));
Loc::Reg(reg)
}
..=8 => {
let stack = self.alloc_stack(size as _);
self.store_stack(parama.next().unwrap(), stack.offset, size as _);
Loc::Stack(stack, 0)
}
..=16 => {
let stack = self.alloc_stack(size);
self.store_stack(parama.next().unwrap(), stack.offset, size as _);
parama.next().unwrap();
Loc::Stack(stack, 0)
}
_ if flags & (parser::MUTABLE | parser::REFERENCED) == 0 => {
let ptr = parama.next().unwrap();
let reg = self.alloc_reg();
self.code.encode(instrs::cp(reg.0, ptr));
Loc::Deref(reg, None, 0)
}
_ => {
let ptr = parama.next().unwrap();
let stack = self.alloc_stack(size);
self.assign(
ty,
Loc::DerefRef(STACK_PTR, None, stack.offset),
Loc::DerefRef(ptr, None, 0),
);
Loc::Stack(stack, 0)
}
}
}
#[must_use]
fn assert_ty(&self, pos: parser::Pos, ty: Type, expected: Type) -> Type {
if let Some(res) = bt::try_upcast(ty, expected) {
res
} else {
let ty = self.display_ty(ty);
let expected = self.display_ty(expected);
self.report(pos, format_args!("expected {ty}, got {expected}"));
}
}
fn report(&self, pos: parser::Pos, msg: impl std::fmt::Display) -> ! {
let (line, col) = self.cf.nlines.line_col(pos);
println!("{}:{}:{}: {}", self.cf.path, line, col, msg);
unreachable!();
}
fn alloc_ret_loc(&mut self, ret: Type, ctx: Ctx) -> Loc {
let size = self.size_of(ret);
match size {
0 => Loc::Imm(0),
..=8 => Loc::RegRef(1),
..=16 => match ctx {
Ctx::Dest(dest) => dest.loc,
_ => Loc::Stack(self.alloc_stack(size), 0),
},
..=u64::MAX => {
let val = match ctx {
Ctx::Dest(dest) => dest.loc,
_ => Loc::Stack(self.alloc_stack(size), 0),
};
let (ptr, off) = val.ref_to_ptr();
self.code.encode(instrs::cp(1, ptr));
self.code.addi64(1, ptr, off);
val
}
}
}
fn post_process_ret_loc(&mut self, ty: Type, loc: &Loc) {
let size = self.size_of(ty);
match size {
0 => {}
..=8 => {}
..=16 => {
if let Loc::Stack(ref stack, off) = loc {
self.store_stack(1, stack.offset + off, size as _);
} else {
unreachable!()
}
}
..=u64::MAX => {}
}
}
fn param_alloc(&self, ret: Type) -> Range<u8> {
2 + (9..=16).contains(&self.size_of(ret)) as u8..12
}
}
#[derive(Debug)]
pub struct Value {
ty: Type,
loc: Loc,
}
impl Value {
const VOID: Self = Self {
ty: bt::VOID,
loc: Loc::Imm(0),
};
fn new(ty: Type, loc: Loc) -> Self {
Self { ty, loc }
}
}
#[derive(Debug, PartialEq, Eq)]
enum Loc {
Reg(LinReg),
RegRef(Reg),
Deref(LinReg, Option<Rc<Stack>>, u64),
DerefRef(Reg, Option<Rc<Stack>>, u64),
Imm(u64),
Stack(Rc<Stack>, u64),
}
impl Loc {
fn take_ref(&self) -> Loc {
match *self {
Self::Reg(LinReg(reg, ..), ..) | Self::RegRef(reg) => Self::RegRef(reg),
Self::Deref(LinReg(reg, ..), ref st, off) | Self::DerefRef(reg, ref st, off) => {
Self::DerefRef(reg, st.clone(), off)
}
Self::Stack(ref stack, off) => {
Self::DerefRef(STACK_PTR, Some(stack.clone()), stack.offset + off)
}
ref un => unreachable!("{:?}", un),
}
}
fn ref_to_ptr(&self) -> (Reg, u64) {
match *self {
Loc::Deref(LinReg(reg, ..), _, off) => (reg, off),
Loc::DerefRef(reg, _, off) => (reg, off),
Loc::Stack(ref stack, off) => (STACK_PTR, stack.offset + off),
ref l => panic!("expected stack location, got {:?}", l),
}
}
fn offset_ref(&self, offset: u64) -> Loc {
self.take_ref().offset(offset)
}
fn offset(self, offset: u64) -> Loc {
match self {
Loc::Deref(r, stack, off) => Loc::Deref(r, stack, off + offset),
Loc::DerefRef(r, stack, off) => Loc::DerefRef(r, stack, off + offset),
Loc::Stack(s, off) => Loc::Stack(s, off + offset),
l => unreachable!("{:?}", l),
}
}
}
#[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::dbg!(
"load: {:x} {:?}",
addr.get(),
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::dbg!(
"store: {:x} {:?}",
addr.get(),
core::slice::from_raw_parts(source, count)
);
self.mem.store(addr, source, count)
}
unsafe fn prog_read<T: Copy>(&mut self, addr: hbvm::mem::Address) -> T {
log::dbg!(
"read-typed: {:x} {} {:?}",
addr.get(),
std::any::type_name::<T>(),
if core::mem::size_of::<T>() == 1 {
instrs::NAMES[std::ptr::read(addr.get() as *const u8) as usize].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)]
mod tests {
use super::parser;
fn generate(input: &'static str, output: &mut String) {
let path = "test";
let mut codegen = super::Codegen::default();
codegen.files = vec![parser::Ast::new(path, input, &parser::no_loader)];
codegen.generate();
let mut out = Vec::new();
codegen.dump(&mut out).unwrap();
use std::fmt::Write;
let mut stack = [0_u64; 128];
let mut vm = unsafe {
hbvm::Vm::<_, 0>::new(
hbvm::mem::HostMemory,
hbvm::mem::Address::new(out.as_ptr() as u64),
)
};
vm.write_reg(
super::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(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:
example => include_str!("../examples/main_fn.hb");
arithmetic => include_str!("../examples/arithmetic.hb");
variables => include_str!("../examples/variables.hb");
functions => include_str!("../examples/functions.hb");
if_statements => include_str!("../examples/if_statement.hb");
loops => include_str!("../examples/loops.hb");
fb_driver => include_str!("../examples/fb_driver.hb");
pointers => include_str!("../examples/pointers.hb");
structs => include_str!("../examples/structs.hb");
different_types => include_str!("../examples/different_types.hb");
struct_operators => include_str!("../examples/struct_operators.hb");
directives => include_str!("../examples/directives.hb");
global_variables => include_str!("../examples/global_variables.hb");
}
}