mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
Refactor x86_64 backend and add first assembly methods
This commit is contained in:
parent
a8986087f9
commit
0fcb1df344
3 changed files with 130 additions and 40 deletions
|
@ -11,7 +11,7 @@
|
||||||
// re-enable this when working on performance optimizations than have it block PRs.
|
// re-enable this when working on performance optimizations than have it block PRs.
|
||||||
#![allow(clippy::large_enum_variant)]
|
#![allow(clippy::large_enum_variant)]
|
||||||
|
|
||||||
use bumpalo::{collections::Vec, Bump};
|
use bumpalo::Bump;
|
||||||
use object::write::Object;
|
use object::write::Object;
|
||||||
use roc_collections::all::{MutMap, MutSet};
|
use roc_collections::all::{MutMap, MutSet};
|
||||||
use roc_module::symbol::{Interns, Symbol};
|
use roc_module::symbol::{Interns, Symbol};
|
||||||
|
@ -59,26 +59,25 @@ trait Backend<'a> {
|
||||||
fn build_proc(&mut self, proc: Proc<'a>) -> &'a [u8] {
|
fn build_proc(&mut self, proc: Proc<'a>) -> &'a [u8] {
|
||||||
self.reset();
|
self.reset();
|
||||||
// TODO: let the backend know of all the arguments.
|
// TODO: let the backend know of all the arguments.
|
||||||
let mut buf = bumpalo::vec!(in self.env().arena);
|
self.build_stmt(&proc.body);
|
||||||
self.build_stmt(&mut buf, &proc.body);
|
self.finalize()
|
||||||
self.wrap_proc(buf).into_bump_slice()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// wrap_proc does any setup and cleanup that should happen around the procedure.
|
/// 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.
|
/// For example, this can store the frame pionter and setup stack space.
|
||||||
/// wrap_proc is run at the end of build_proc when all internal code is finalized.
|
/// finalize is run at the end of build_proc when all internal code is finalized.
|
||||||
/// wrap_proc returns a Vec because it is expected to prepend data.
|
fn finalize(&mut self) -> &'a [u8];
|
||||||
fn wrap_proc(&mut self, buf: Vec<'a, u8>) -> Vec<'a, u8>;
|
|
||||||
|
|
||||||
/// build_stmt builds a statement and outputs at the end of the buffer.
|
/// build_stmt builds a statement and outputs at the end of the buffer.
|
||||||
fn build_stmt(&mut self, buf: &mut Vec<'a, u8>, stmt: &Stmt<'a>) {
|
fn build_stmt(&mut self, stmt: &Stmt<'a>) {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Let(sym, expr, layout, following) => {
|
Stmt::Let(sym, expr, layout, following) => {
|
||||||
self.build_expr(buf, sym, expr, layout);
|
self.build_expr(sym, expr, layout);
|
||||||
self.build_stmt(buf, following);
|
self.build_stmt(following);
|
||||||
}
|
}
|
||||||
Stmt::Ret(sym) => {
|
Stmt::Ret(sym) => {
|
||||||
self.return_symbol(buf, sym);
|
self.return_symbol(sym);
|
||||||
}
|
}
|
||||||
x => unimplemented!("the statement, {:?}, is not yet implemented", x),
|
x => unimplemented!("the statement, {:?}, is not yet implemented", x),
|
||||||
}
|
}
|
||||||
|
@ -87,13 +86,7 @@ trait Backend<'a> {
|
||||||
/// build_expr builds the expressions for the specified symbol.
|
/// build_expr builds the expressions for the specified symbol.
|
||||||
/// The builder must keep track of the symbol because it may be refered to later.
|
/// 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.
|
/// In many cases values can be lazy loaded, like literals.
|
||||||
fn build_expr(
|
fn build_expr(&mut self, sym: &Symbol, expr: &Expr<'a>, layout: &Layout<'a>) {
|
||||||
&mut self,
|
|
||||||
_buf: &mut Vec<'a, u8>,
|
|
||||||
sym: &Symbol,
|
|
||||||
expr: &Expr<'a>,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
) {
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Literal(lit) => {
|
Expr::Literal(lit) => {
|
||||||
self.set_symbol_to_lit(sym, lit, layout);
|
self.set_symbol_to_lit(sym, lit, layout);
|
||||||
|
@ -107,5 +100,5 @@ trait Backend<'a> {
|
||||||
fn set_symbol_to_lit(&mut self, sym: &Symbol, lit: &Literal<'a>, layout: &Layout<'a>);
|
fn set_symbol_to_lit(&mut self, sym: &Symbol, lit: &Literal<'a>, layout: &Layout<'a>);
|
||||||
|
|
||||||
/// return_symbol moves a symbol to the correct return location for the backend.
|
/// return_symbol moves a symbol to the correct return location for the backend.
|
||||||
fn return_symbol(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol);
|
fn return_symbol(&mut self, sym: &Symbol);
|
||||||
}
|
}
|
||||||
|
|
70
compiler/gen_dev/src/x86_64/asm.rs
Normal file
70
compiler/gen_dev/src/x86_64/asm.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
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)]
|
||||||
|
pub enum Register {
|
||||||
|
RAX,
|
||||||
|
RCX,
|
||||||
|
RDX,
|
||||||
|
RBX,
|
||||||
|
RSP,
|
||||||
|
RBP,
|
||||||
|
RSI,
|
||||||
|
RDI,
|
||||||
|
R8,
|
||||||
|
R9,
|
||||||
|
R10,
|
||||||
|
R11,
|
||||||
|
R12,
|
||||||
|
R13,
|
||||||
|
R14,
|
||||||
|
R15,
|
||||||
|
}
|
||||||
|
|
||||||
|
const REX_W: u8 = 0x48;
|
||||||
|
|
||||||
|
fn add_rm_extension(reg: Register, byte: u8) -> u8 {
|
||||||
|
if reg as u8 > 7 {
|
||||||
|
byte + 1
|
||||||
|
} else {
|
||||||
|
byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_reg_extension(reg: Register, byte: u8) -> u8 {
|
||||||
|
if reg as u8 > 7 {
|
||||||
|
byte + 4
|
||||||
|
} else {
|
||||||
|
byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Below here are the functions for all of the assembly instructions.
|
||||||
|
/// Their names are based on the instruction and operators combined.
|
||||||
|
/// Please call buf.reserve if you push or extend more than once.
|
||||||
|
/// Also, please keep these in alphanumeric order.
|
||||||
|
|
||||||
|
pub fn mov_register64bit_immediate32bit<'a>(buf: &mut Vec<'a, u8>, dst: Register, imm: i32) {
|
||||||
|
let rex = add_rm_extension(dst, REX_W);
|
||||||
|
let dst_mod = dst as u8 % 8;
|
||||||
|
buf.reserve(7);
|
||||||
|
buf.extend(&[rex, 0xC7, 0xC0 + dst_mod]);
|
||||||
|
buf.extend(&imm.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mov_register64bit_immediate64bit<'a>(buf: &mut Vec<'a, u8>, dst: Register, imm: i64) {
|
||||||
|
let rex = add_rm_extension(dst, REX_W);
|
||||||
|
let dst_mod = dst as u8 % 8;
|
||||||
|
buf.reserve(10);
|
||||||
|
buf.extend(&[rex, 0xB8 + dst_mod]);
|
||||||
|
buf.extend(&imm.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mov_register64bit_register64bit<'a>(buf: &mut Vec<'a, u8>, dst: Register, src: Register) {
|
||||||
|
let rex = add_rm_extension(dst, REX_W);
|
||||||
|
let rex = add_reg_extension(src, rex);
|
||||||
|
let dst_mod = dst as u8 % 8;
|
||||||
|
let src_mod = (src as u8 % 8) << 3;
|
||||||
|
buf.extend(&[rex, 0x89, 0xC0 + dst_mod + src_mod]);
|
||||||
|
}
|
|
@ -5,8 +5,19 @@ use roc_module::symbol::Symbol;
|
||||||
use roc_mono::ir::Literal;
|
use roc_mono::ir::Literal;
|
||||||
use roc_mono::layout::Layout;
|
use roc_mono::layout::Layout;
|
||||||
|
|
||||||
|
mod asm;
|
||||||
|
|
||||||
|
use asm::Register;
|
||||||
|
|
||||||
|
const RETURN_REG: Register = Register::RAX;
|
||||||
|
|
||||||
pub struct X86_64Backend<'a> {
|
pub struct X86_64Backend<'a> {
|
||||||
env: &'a Env<'a>,
|
env: &'a Env<'a>,
|
||||||
|
buf: Vec<'a, u8>,
|
||||||
|
|
||||||
|
/// leaf_proc is true if the only calls this function makes are tail calls.
|
||||||
|
/// If that is the case, we can skip emitting the frame pointer and updating the stack.
|
||||||
|
leaf_proc: bool,
|
||||||
|
|
||||||
// This will need to hold info a symbol is held in a register or on the stack as well.
|
// 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, (Literal<'a>, Layout<'a>)>,
|
||||||
|
@ -27,6 +38,8 @@ impl<'a> Backend<'a> for X86_64Backend<'a> {
|
||||||
fn new(env: &'a Env) -> Self {
|
fn new(env: &'a Env) -> Self {
|
||||||
X86_64Backend {
|
X86_64Backend {
|
||||||
env,
|
env,
|
||||||
|
leaf_proc: true,
|
||||||
|
buf: bumpalo::vec!(in env.arena),
|
||||||
symbols_map: MutMap::default(),
|
symbols_map: MutMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,40 +50,54 @@ impl<'a> Backend<'a> for X86_64Backend<'a> {
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.symbols_map.clear();
|
self.symbols_map.clear();
|
||||||
|
self.buf.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_proc(&mut self, body: Vec<'a, u8>) -> Vec<'a, u8> {
|
fn finalize(&mut self) -> &'a [u8] {
|
||||||
// push rbp (0x55)
|
|
||||||
// mov rbp, rsp (0x48, 0x89, 0xE5)
|
|
||||||
let mut out = bumpalo::vec![in self.env.arena; 0x55, 0x48, 0x89, 0xE5];
|
|
||||||
out.reserve(body.len() + 2);
|
|
||||||
|
|
||||||
// TODO: handle allocating and cleaning up data on the stack.
|
// TODO: handle allocating and cleaning up data on the stack.
|
||||||
|
let mut out = bumpalo::vec![in self.env.arena];
|
||||||
|
if !self.leaf_proc {
|
||||||
|
// push rbp (0x55)
|
||||||
|
// mov rbp, rsp (0x48, 0x89, 0xE5)
|
||||||
|
out.extend(&[0x55]);
|
||||||
|
asm::mov_register64bit_register64bit(&mut out, Register::RBP, Register::RSP)
|
||||||
|
}
|
||||||
|
out.extend(&self.buf);
|
||||||
|
|
||||||
out.extend(body);
|
if !self.leaf_proc {
|
||||||
|
// pop rbp
|
||||||
// pop rbp
|
out.push(0x5D);
|
||||||
out.push(0x5D);
|
}
|
||||||
// ret
|
// ret
|
||||||
out.push(0xC3);
|
out.push(0xC3);
|
||||||
|
|
||||||
out
|
out.into_bump_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_symbol_to_lit(&mut self, sym: &Symbol, lit: &Literal<'a>, layout: &Layout<'a>) {
|
fn set_symbol_to_lit(&mut self, sym: &Symbol, lit: &Literal<'a>, layout: &Layout<'a>) {
|
||||||
self.symbols_map
|
self.symbols_map.insert(*sym, (lit.clone(), layout.clone()));
|
||||||
.insert(sym.clone(), (lit.clone(), layout.clone()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn return_symbol(&mut self, body: &mut Vec<'a, u8>, sym: &Symbol) {
|
fn return_symbol(&mut self, sym: &Symbol) {
|
||||||
//let body = bumpalo::vec![in env.arena; 0xb8, 0x06, 0x00, 0x00, 0x00];
|
self.load_symbol(RETURN_REG, sym);
|
||||||
match self.symbols_map.get(sym) {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) {
|
||||||
|
let val = self.symbols_map.get(sym);
|
||||||
|
match val {
|
||||||
Some((Literal::Int(x), _)) => {
|
Some((Literal::Int(x), _)) => {
|
||||||
// movabs rax, ...
|
let val = *x;
|
||||||
body.extend(&[0x48, 0xB8]);
|
if val <= i32::MAX as i64 && val >= i32::MIN as i64 {
|
||||||
body.extend(&x.to_le_bytes());
|
asm::mov_register64bit_immediate32bit(&mut self.buf, dst, val as i32);
|
||||||
|
} else {
|
||||||
|
asm::mov_register64bit_immediate64bit(&mut self.buf, dst, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(x) => unimplemented!("return value, {:?}, is not yet implemented", x),
|
Some(x) => unimplemented!("symbol, {:?}, is not yet implemented", x),
|
||||||
None => panic!("Unknown return symbol: {}", sym),
|
None => panic!("Unknown return symbol: {}", sym),
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue