mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Mixed refactoring
Add error return value as opposed to using panic! and unimplemented! Reorder some functions Return relocations Scan ast for variable lifetimes Probably put too much into this cl, but it would be annoying to change at this piont.
This commit is contained in:
parent
44d6c3bc02
commit
4e973b9236
4 changed files with 247 additions and 51 deletions
|
@ -59,9 +59,10 @@ pub fn build_module<'a>(
|
|||
}
|
||||
|
||||
// Build procedures.
|
||||
let mut backend: X86_64Backend = Backend::new(env, target);
|
||||
let mut backend: X86_64Backend = Backend::new(env, target)?;
|
||||
for (proc_id, proc) in procs {
|
||||
let proc_data = backend.build_proc(proc);
|
||||
let (proc_data, _relocations) = backend.build_proc(proc)?;
|
||||
// TODO: handle relocations.
|
||||
output.add_symbol_data(proc_id, text, proc_data, 16);
|
||||
}
|
||||
Ok(output)
|
||||
|
|
|
@ -15,7 +15,7 @@ use bumpalo::Bump;
|
|||
use object::write::Object;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::ir::{Expr, Literal, Proc, Stmt};
|
||||
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
||||
use roc_mono::layout::Layout;
|
||||
use target_lexicon::{BinaryFormat, Triple};
|
||||
|
||||
|
@ -44,61 +44,226 @@ pub fn build_module<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
trait Backend<'a> {
|
||||
// These relocations likely will need a length.
|
||||
// They may even need more definition, but this should be at least good enough for how we will use elf.
|
||||
enum Relocation<'a> {
|
||||
LocalData { offset: u64, data: &'a [u8] },
|
||||
LinkedFunction { offset: u64, name: &'a str },
|
||||
LinkedData { offset: u64, name: &'a str },
|
||||
}
|
||||
|
||||
trait Backend<'a>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// new creates a new backend that will output to the specific Object.
|
||||
fn new(env: &'a Env, target: &Triple) -> Self;
|
||||
fn new(env: &'a Env, target: &Triple) -> Result<Self, String>;
|
||||
|
||||
fn env(&self) -> &'a Env<'a>;
|
||||
|
||||
/// reset resets any registers or other values that may be occupied at the end of a procedure.
|
||||
fn reset(&mut self);
|
||||
|
||||
/// build_proc creates a procedure and outputs it to the wrapped object writer.
|
||||
/// This will need to return the list of relocations because they have to be added differently based on file format.
|
||||
/// Also, assembly will of course be generated by individual calls on backend like may setup_stack.
|
||||
fn build_proc(&mut self, proc: Proc<'a>) -> &'a [u8] {
|
||||
self.reset();
|
||||
// TODO: let the backend know of all the arguments.
|
||||
self.build_stmt(&proc.body);
|
||||
self.finalize()
|
||||
}
|
||||
/// last_seen_map gets the map from symbol to when it is last seen in the function.
|
||||
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>>;
|
||||
|
||||
/// set_symbol_to_lit sets a symbol to be equal to a literal.
|
||||
/// When the symbol is used, the literal should be loaded.
|
||||
fn set_symbol_to_lit(&mut self, sym: &Symbol, lit: &Literal<'a>);
|
||||
|
||||
/// free_symbol frees any registers or stack space used to hold a symbol.
|
||||
fn free_symbol(&mut self, sym: &Symbol);
|
||||
|
||||
/// return_symbol moves a symbol to the correct return location for the backend.
|
||||
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String>;
|
||||
|
||||
/// finalize does any setup and cleanup that should happen around the procedure.
|
||||
/// finalize does setup because things like stack size and jump locations are not know until the function is written.
|
||||
/// For example, this can store the frame pionter and setup stack space.
|
||||
/// finalize is run at the end of build_proc when all internal code is finalized.
|
||||
fn finalize(&mut self) -> &'a [u8];
|
||||
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String>;
|
||||
|
||||
/// build_proc creates a procedure and outputs it to the wrapped object writer.
|
||||
/// This will need to return the list of relocations because they have to be added differently based on file format.
|
||||
/// Also, assembly will of course be generated by individual calls on backend like may setup_stack.
|
||||
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
|
||||
self.reset();
|
||||
// TODO: let the backend know of all the arguments.
|
||||
self.calculate_last_seen(&proc.body);
|
||||
//println!("{:?}", self.last_seen_map());
|
||||
self.build_stmt(&proc.body);
|
||||
self.finalize()
|
||||
}
|
||||
|
||||
/// build_stmt builds a statement and outputs at the end of the buffer.
|
||||
fn build_stmt(&mut self, stmt: &Stmt<'a>) {
|
||||
fn build_stmt(&mut self, stmt: &Stmt<'a>) -> Result<(), String> {
|
||||
match stmt {
|
||||
Stmt::Let(sym, expr, layout, following) => {
|
||||
self.build_expr(sym, expr, layout);
|
||||
self.maybe_free_symbol(sym, stmt);
|
||||
self.build_stmt(following);
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Ret(sym) => {
|
||||
self.return_symbol(sym);
|
||||
self.maybe_free_symbol(sym, stmt);
|
||||
Ok(())
|
||||
}
|
||||
x => unimplemented!("the statement, {:?}, is not yet implemented", x),
|
||||
x => Err(format!("the statement, {:?}, is not yet implemented", x)),
|
||||
}
|
||||
}
|
||||
|
||||
/// build_expr builds the expressions for the specified symbol.
|
||||
/// The builder must keep track of the symbol because it may be refered to later.
|
||||
/// In many cases values can be lazy loaded, like literals.
|
||||
fn build_expr(&mut self, sym: &Symbol, expr: &Expr<'a>, layout: &Layout<'a>) {
|
||||
fn build_expr(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
expr: &Expr<'a>,
|
||||
_layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
match expr {
|
||||
Expr::Literal(lit) => {
|
||||
self.set_symbol_to_lit(sym, lit, layout);
|
||||
self.set_symbol_to_lit(sym, lit);
|
||||
Ok(())
|
||||
}
|
||||
x => unimplemented!("the expression, {:?}, is not yet implemented", x),
|
||||
x => Err(format!("the expression, {:?}, is not yet implemented", x)),
|
||||
}
|
||||
}
|
||||
|
||||
/// set_symbol_to_lit sets a symbol to be equal to a literal.
|
||||
/// When the symbol is used, the literal should be loaded.
|
||||
fn set_symbol_to_lit(&mut self, sym: &Symbol, lit: &Literal<'a>, layout: &Layout<'a>);
|
||||
/// maybe_free will check if the symbol is last seen in the current state. If so, it will free the symbol resources, like registers.
|
||||
fn maybe_free_symbol(&mut self, sym: &Symbol, stmt: &Stmt<'a>) {
|
||||
match self.last_seen_map().get(sym) {
|
||||
Some(laststmt) if *laststmt == stmt as *const Stmt<'a> => {
|
||||
//println!("Freeing symbol: {:?}", sym);
|
||||
self.free_symbol(sym);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// return_symbol moves a symbol to the correct return location for the backend.
|
||||
fn return_symbol(&mut self, sym: &Symbol);
|
||||
/// set_last_seen sets the statement a symbol was last seen in.
|
||||
fn set_last_seen(&mut self, sym: Symbol, stmt: &Stmt<'a>) {
|
||||
self.last_seen_map().insert(sym, stmt);
|
||||
}
|
||||
|
||||
/// calculate_last_seen runs through the ast and fill the last seen map.
|
||||
/// This must iterate through the ast in the same way that build_stmt does. i.e. then before else.
|
||||
fn calculate_last_seen(&mut self, stmt: &Stmt<'a>) {
|
||||
match stmt {
|
||||
Stmt::Let(sym, expr, _, following) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
match expr {
|
||||
Expr::Literal(_) => {}
|
||||
Expr::FunctionPointer(sym, _) => self.set_last_seen(*sym, stmt),
|
||||
Expr::FunctionCall {
|
||||
call_type, args, ..
|
||||
} => {
|
||||
for sym in *args {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
match call_type {
|
||||
CallType::ByName(_sym) => {
|
||||
// Do nothing, by name is not a variable with lifetime.
|
||||
}
|
||||
CallType::ByPointer(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::RunLowLevel(_, args) => {
|
||||
for sym in *args {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::ForeignCall { arguments, .. } => {
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Tag { arguments, .. } => {
|
||||
for sym in *arguments {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Struct(syms) => {
|
||||
for sym in *syms {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::AccessAtIndex { structure, .. } => {
|
||||
self.set_last_seen(*structure, stmt);
|
||||
}
|
||||
Expr::Array { elems, .. } => {
|
||||
for sym in *elems {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Reuse { .. } => {
|
||||
// Not sure what this is used for so leaving it blank for now.
|
||||
}
|
||||
Expr::Reset(_sym) => {
|
||||
// Not sure what this is used for so leaving it blank for now.
|
||||
}
|
||||
Expr::EmptyArray => {}
|
||||
Expr::RuntimeErrorFunction(_) => {}
|
||||
}
|
||||
self.calculate_last_seen(following);
|
||||
}
|
||||
Stmt::Switch {
|
||||
cond_symbol,
|
||||
branches,
|
||||
default_branch,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*cond_symbol, stmt);
|
||||
for (_, branch) in *branches {
|
||||
self.calculate_last_seen(branch);
|
||||
}
|
||||
self.calculate_last_seen(default_branch);
|
||||
}
|
||||
Stmt::Cond {
|
||||
cond_symbol,
|
||||
branching_symbol,
|
||||
pass,
|
||||
fail,
|
||||
..
|
||||
} => {
|
||||
self.set_last_seen(*cond_symbol, stmt);
|
||||
self.set_last_seen(*branching_symbol, stmt);
|
||||
self.calculate_last_seen(pass);
|
||||
self.calculate_last_seen(fail);
|
||||
}
|
||||
Stmt::Ret(sym) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
Stmt::Inc(sym, following) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.calculate_last_seen(following);
|
||||
}
|
||||
Stmt::Dec(sym, following) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
self.calculate_last_seen(following);
|
||||
}
|
||||
Stmt::Join {
|
||||
parameters,
|
||||
continuation,
|
||||
remainder,
|
||||
..
|
||||
} => {
|
||||
for param in *parameters {
|
||||
self.set_last_seen(param.symbol, stmt);
|
||||
}
|
||||
self.calculate_last_seen(continuation);
|
||||
self.calculate_last_seen(remainder);
|
||||
}
|
||||
Stmt::Jump(JoinPointId(sym), symbols) => {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
for sym in *symbols {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Stmt::RuntimeError(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use bumpalo::collections::Vec;
|
|||
|
||||
// Not sure exactly how I want to represent registers.
|
||||
// If we want max speed, we would likely make them structs that impl the same trait to avoid ifs.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub enum Register {
|
||||
RAX,
|
||||
RCX,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::{Backend, Env};
|
||||
use crate::{Backend, Env, Relocation};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_collections::all::{ImSet, MutMap};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::Literal;
|
||||
use roc_mono::ir::{Literal, Stmt};
|
||||
use roc_mono::layout::Layout;
|
||||
use target_lexicon::{CallingConvention, Triple};
|
||||
|
||||
|
@ -11,6 +11,13 @@ use asm::Register;
|
|||
|
||||
const RETURN_REG: Register = Register::RAX;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum SymbolStorage<'a> {
|
||||
Literal(Literal<'a>),
|
||||
Register(Register, Layout<'a>),
|
||||
Stack(u32, Layout<'a>),
|
||||
}
|
||||
|
||||
pub struct X86_64Backend<'a> {
|
||||
env: &'a Env<'a>,
|
||||
buf: Vec<'a, u8>,
|
||||
|
@ -19,8 +26,9 @@ pub struct X86_64Backend<'a> {
|
|||
/// If that is the case, we can skip emitting the frame pointer and updating the stack.
|
||||
leaf_proc: bool,
|
||||
|
||||
last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
|
||||
// This will need to hold info a symbol is held in a register or on the stack as well.
|
||||
symbols_map: MutMap<Symbol, (Literal<'a>, Layout<'a>)>,
|
||||
symbols_map: MutMap<Symbol, SymbolStorage<'a>>,
|
||||
// This is gonna need to include a lot of data. Right now I can think of quite a few.
|
||||
// Registers order by priority with info of what data is stored in them.
|
||||
// Scope with knows were all variables are currently stored.X86_64Backend
|
||||
|
@ -40,15 +48,19 @@ pub struct X86_64Backend<'a> {
|
|||
callee_saved_regs: ImSet<Register>,
|
||||
shadow_space_size: u8,
|
||||
red_zone_size: u8,
|
||||
|
||||
// not sure how big this should be u16 is 64k. I hope no function uses that much stack.
|
||||
stack_size: u16,
|
||||
}
|
||||
|
||||
impl<'a> Backend<'a> for X86_64Backend<'a> {
|
||||
fn new(env: &'a Env, target: &Triple) -> Self {
|
||||
fn new(env: &'a Env, target: &Triple) -> Result<Self, String> {
|
||||
match target.default_calling_convention() {
|
||||
Ok(CallingConvention::SystemV) => X86_64Backend {
|
||||
Ok(CallingConvention::SystemV) => Ok(X86_64Backend {
|
||||
env,
|
||||
leaf_proc: true,
|
||||
buf: bumpalo::vec!(in env.arena),
|
||||
last_seen_map: MutMap::default(),
|
||||
symbols_map: MutMap::default(),
|
||||
gp_param_regs: &[
|
||||
Register::RDI,
|
||||
|
@ -81,11 +93,13 @@ impl<'a> Backend<'a> for X86_64Backend<'a> {
|
|||
]),
|
||||
shadow_space_size: 0,
|
||||
red_zone_size: 128,
|
||||
},
|
||||
Ok(CallingConvention::WindowsFastcall) => X86_64Backend {
|
||||
stack_size: 0,
|
||||
}),
|
||||
Ok(CallingConvention::WindowsFastcall) => Ok(X86_64Backend {
|
||||
env,
|
||||
leaf_proc: true,
|
||||
buf: bumpalo::vec!(in env.arena),
|
||||
last_seen_map: MutMap::default(),
|
||||
symbols_map: MutMap::default(),
|
||||
gp_param_regs: &[Register::RCX, Register::RDX, Register::R8, Register::R9],
|
||||
caller_saved_regs: ImSet::from(vec![
|
||||
|
@ -110,8 +124,9 @@ impl<'a> Backend<'a> for X86_64Backend<'a> {
|
|||
]),
|
||||
shadow_space_size: 32,
|
||||
red_zone_size: 0,
|
||||
},
|
||||
x => panic!("unsupported backend: {:?}", x),
|
||||
stack_size: 0,
|
||||
}),
|
||||
x => Err(format!("unsupported backend: {:?}", x)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,48 +139,63 @@ impl<'a> Backend<'a> for X86_64Backend<'a> {
|
|||
self.buf.clear();
|
||||
}
|
||||
|
||||
fn finalize(&mut self) -> &'a [u8] {
|
||||
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>> {
|
||||
&mut self.last_seen_map
|
||||
}
|
||||
|
||||
fn set_symbol_to_lit(&mut self, sym: &Symbol, lit: &Literal<'a>) {
|
||||
self.symbols_map
|
||||
.insert(*sym, SymbolStorage::Literal(lit.clone()));
|
||||
}
|
||||
|
||||
fn free_symbol(&mut self, sym: &Symbol) {
|
||||
self.symbols_map.remove(sym);
|
||||
}
|
||||
|
||||
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String> {
|
||||
self.load_symbol(RETURN_REG, sym)
|
||||
}
|
||||
|
||||
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String> {
|
||||
// TODO: handle allocating and cleaning up data on the stack.
|
||||
let mut out = bumpalo::vec![in self.env.arena];
|
||||
if !self.leaf_proc {
|
||||
if self.requires_stack_modification() {
|
||||
asm::push_register64bit(&mut out, Register::RBP);
|
||||
asm::mov_register64bit_register64bit(&mut out, Register::RBP, Register::RSP);
|
||||
}
|
||||
out.extend(&self.buf);
|
||||
|
||||
if !self.leaf_proc {
|
||||
if self.requires_stack_modification() {
|
||||
asm::pop_register64bit(&mut out, Register::RBP);
|
||||
}
|
||||
asm::ret_near(&mut out);
|
||||
|
||||
out.into_bump_slice()
|
||||
}
|
||||
|
||||
fn set_symbol_to_lit(&mut self, sym: &Symbol, lit: &Literal<'a>, layout: &Layout<'a>) {
|
||||
self.symbols_map.insert(*sym, (lit.clone(), layout.clone()));
|
||||
}
|
||||
|
||||
fn return_symbol(&mut self, sym: &Symbol) {
|
||||
self.load_symbol(RETURN_REG, sym);
|
||||
Ok((out.into_bump_slice(), &[]))
|
||||
}
|
||||
}
|
||||
|
||||
/// This impl block is for ir related instructions that need backend specific information.
|
||||
/// For example, loading a symbol for doing a computation.
|
||||
impl<'a> X86_64Backend<'a> {
|
||||
fn load_symbol(&mut self, dst: Register, sym: &Symbol) {
|
||||
fn requires_stack_modification(&self) -> bool {
|
||||
!self.leaf_proc
|
||||
|| self.stack_size < self.shadow_space_size as u16 + self.red_zone_size as u16
|
||||
}
|
||||
|
||||
fn load_symbol(&mut self, dst: Register, sym: &Symbol) -> Result<(), String> {
|
||||
let val = self.symbols_map.get(sym);
|
||||
match val {
|
||||
Some((Literal::Int(x), _)) => {
|
||||
Some(SymbolStorage::Literal(Literal::Int(x))) => {
|
||||
let val = *x;
|
||||
if val <= i32::MAX as i64 && val >= i32::MIN as i64 {
|
||||
asm::mov_register64bit_immediate32bit(&mut self.buf, dst, val as i32);
|
||||
} else {
|
||||
asm::mov_register64bit_immediate64bit(&mut self.buf, dst, val);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Some(x) => unimplemented!("symbol, {:?}, is not yet implemented", x),
|
||||
None => panic!("Unknown return symbol: {}", sym),
|
||||
Some(x) => Err(format!("symbol, {:?}, is not yet implemented", x)),
|
||||
None => Err(format!("Unknown return symbol: {}", sym)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue