mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00

This change will be needed to deal with passing function arguments. Without this change, we would need to do multiple passes due to function arguments being on top of the stack and conflicting with variables.
476 lines
18 KiB
Rust
476 lines
18 KiB
Rust
#![warn(clippy::all, clippy::dbg_macro)]
|
|
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
|
#![allow(clippy::large_enum_variant)]
|
|
|
|
use bumpalo::{collections::Vec, Bump};
|
|
use roc_collections::all::{MutMap, MutSet};
|
|
use roc_module::ident::{ModuleName, TagName};
|
|
use roc_module::low_level::LowLevel;
|
|
use roc_module::symbol::{Interns, Symbol};
|
|
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
|
use roc_mono::layout::{Builtin, Layout, LayoutIds};
|
|
use target_lexicon::Triple;
|
|
|
|
mod generic64;
|
|
mod object_builder;
|
|
pub use object_builder::build_module;
|
|
mod run_roc;
|
|
|
|
pub struct Env<'a> {
|
|
pub arena: &'a Bump,
|
|
pub interns: Interns,
|
|
pub exposed_to_host: MutSet<Symbol>,
|
|
pub lazy_literals: bool,
|
|
}
|
|
|
|
// INLINED_SYMBOLS is a set of all of the functions we automatically inline if seen.
|
|
const INLINED_SYMBOLS: [Symbol; 4] = [
|
|
Symbol::NUM_ABS,
|
|
Symbol::NUM_ADD,
|
|
Symbol::NUM_SUB,
|
|
Symbol::BOOL_EQ,
|
|
];
|
|
|
|
// 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.
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)]
|
|
pub enum Relocation {
|
|
LocalData {
|
|
offset: u64,
|
|
// This should probably technically be a bumpalo::Vec.
|
|
// The problem is that it currently is built in a place that can't access the arena.
|
|
data: std::vec::Vec<u8>,
|
|
},
|
|
LinkedFunction {
|
|
offset: u64,
|
|
name: String,
|
|
},
|
|
LinkedData {
|
|
offset: u64,
|
|
name: String,
|
|
},
|
|
}
|
|
|
|
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) -> 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);
|
|
|
|
/// 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) -> Result<(&'a [u8], &[Relocation]), String>;
|
|
|
|
/// build_proc creates a procedure and outputs it to the wrapped object writer.
|
|
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
|
|
self.reset();
|
|
// TODO: let the backend know of all the arguments.
|
|
// let start = std::time::Instant::now();
|
|
self.scan_ast(&proc.body);
|
|
self.create_free_map();
|
|
// let duration = start.elapsed();
|
|
// println!("Time to calculate lifetimes: {:?}", duration);
|
|
// 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>) -> Result<(), String> {
|
|
match stmt {
|
|
Stmt::Let(sym, expr, layout, following) => {
|
|
self.build_expr(sym, expr, layout)?;
|
|
self.free_symbols(stmt);
|
|
self.build_stmt(following)?;
|
|
Ok(())
|
|
}
|
|
Stmt::Ret(sym) => {
|
|
self.load_literal_symbols(&[*sym])?;
|
|
self.return_symbol(sym)?;
|
|
self.free_symbols(stmt);
|
|
Ok(())
|
|
}
|
|
Stmt::Invoke {
|
|
symbol,
|
|
layout,
|
|
call,
|
|
pass,
|
|
fail: _,
|
|
} => {
|
|
// for now, treat invoke as a normal call
|
|
|
|
let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass);
|
|
self.build_stmt(&stmt)
|
|
}
|
|
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.
|
|
fn build_expr(
|
|
&mut self,
|
|
sym: &Symbol,
|
|
expr: &Expr<'a>,
|
|
layout: &Layout<'a>,
|
|
) -> Result<(), String> {
|
|
match expr {
|
|
Expr::Literal(lit) => {
|
|
if self.env().lazy_literals {
|
|
self.literal_map().insert(*sym, lit.clone());
|
|
} else {
|
|
self.load_literal(sym, lit)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Expr::Call(roc_mono::ir::Call {
|
|
call_type,
|
|
arguments,
|
|
}) => {
|
|
match call_type {
|
|
CallType::ByName {
|
|
name: func_sym,
|
|
arg_layouts,
|
|
ret_layout,
|
|
..
|
|
} => {
|
|
match *func_sym {
|
|
Symbol::NUM_ABS => {
|
|
// Instead of calling the function, just inline it.
|
|
self.build_run_low_level(sym, &LowLevel::NumAbs, arguments, layout)
|
|
}
|
|
Symbol::NUM_ADD => {
|
|
// Instead of calling the function, just inline it.
|
|
self.build_run_low_level(sym, &LowLevel::NumAdd, arguments, layout)
|
|
}
|
|
Symbol::NUM_SUB => {
|
|
// Instead of calling the function, just inline it.
|
|
self.build_run_low_level(sym, &LowLevel::NumSub, arguments, layout)
|
|
}
|
|
Symbol::BOOL_EQ => {
|
|
// Instead of calling the function, just inline it.
|
|
self.build_run_low_level(sym, &LowLevel::Eq, arguments, layout)
|
|
}
|
|
x if x
|
|
.module_string(&self.env().interns)
|
|
.starts_with(ModuleName::APP) =>
|
|
{
|
|
let fn_name = LayoutIds::default()
|
|
.get(*func_sym, layout)
|
|
.to_symbol_string(*func_sym, &self.env().interns);
|
|
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
|
|
}
|
|
x => Err(format!("the function, {:?}, is not yet implemented", x)),
|
|
}
|
|
}
|
|
|
|
CallType::LowLevel { op: lowlevel } => {
|
|
self.build_run_low_level(sym, lowlevel, arguments, layout)
|
|
}
|
|
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
|
|
}
|
|
}
|
|
x => Err(format!("the expression, {:?}, is not yet implemented", x)),
|
|
}
|
|
}
|
|
|
|
/// build_run_low_level builds the low level opertation and outputs to the specified symbol.
|
|
/// The builder must keep track of the symbol because it may be refered to later.
|
|
fn build_run_low_level(
|
|
&mut self,
|
|
sym: &Symbol,
|
|
lowlevel: &LowLevel,
|
|
args: &'a [Symbol],
|
|
layout: &Layout<'a>,
|
|
) -> Result<(), String> {
|
|
// Now that the arguments are needed, load them if they are literals.
|
|
self.load_literal_symbols(args)?;
|
|
match lowlevel {
|
|
LowLevel::NumAbs => {
|
|
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
|
|
match layout {
|
|
Layout::Builtin(Builtin::Int64) => self.build_num_abs_i64(sym, &args[0]),
|
|
x => Err(format!("layout, {:?}, not implemented yet", x)),
|
|
}
|
|
}
|
|
LowLevel::NumAdd => {
|
|
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
|
|
match layout {
|
|
Layout::Builtin(Builtin::Int64) => {
|
|
self.build_num_add_i64(sym, &args[0], &args[1])
|
|
}
|
|
Layout::Builtin(Builtin::Float64) => {
|
|
self.build_num_add_f64(sym, &args[0], &args[1])
|
|
}
|
|
x => Err(format!("layout, {:?}, not implemented yet", x)),
|
|
}
|
|
}
|
|
LowLevel::NumSub => {
|
|
// TODO: when this is expanded to floats. deal with typecasting here, and then call correct low level method.
|
|
match layout {
|
|
Layout::Builtin(Builtin::Int64) => {
|
|
self.build_num_sub_i64(sym, &args[0], &args[1])
|
|
}
|
|
x => Err(format!("layout, {:?}, not implemented yet", x)),
|
|
}
|
|
}
|
|
LowLevel::Eq => match layout {
|
|
Layout::Builtin(Builtin::Int1) => self.build_eq_i64(sym, &args[0], &args[1]),
|
|
// Should we panic?
|
|
x => Err(format!("wrong layout, {:?}, for LowLevel::Eq", x)),
|
|
},
|
|
|
|
x => Err(format!("low level, {:?}. is not yet implemented", x)),
|
|
}
|
|
}
|
|
|
|
/// build_fn_call creates a call site for a function.
|
|
/// This includes dealing with things like saving regs and propagating the returned value.
|
|
fn build_fn_call(
|
|
&mut self,
|
|
dst: &Symbol,
|
|
fn_name: String,
|
|
args: &'a [Symbol],
|
|
arg_layouts: &[Layout<'a>],
|
|
ret_layout: &Layout<'a>,
|
|
) -> Result<(), String>;
|
|
|
|
/// build_num_abs_i64 stores the absolute value of src into dst.
|
|
/// It only deals with inputs and outputs of i64 type.
|
|
fn build_num_abs_i64(&mut self, dst: &Symbol, src: &Symbol) -> Result<(), String>;
|
|
|
|
/// build_num_add_i64 stores the sum of src1 and src2 into dst.
|
|
/// It only deals with inputs and outputs of i64 type.
|
|
fn build_num_add_i64(
|
|
&mut self,
|
|
dst: &Symbol,
|
|
src1: &Symbol,
|
|
src2: &Symbol,
|
|
) -> Result<(), String>;
|
|
|
|
/// build_num_add_f64 stores the sum of src1 and src2 into dst.
|
|
/// It only deals with inputs and outputs of f64 type.
|
|
fn build_num_add_f64(
|
|
&mut self,
|
|
dst: &Symbol,
|
|
src1: &Symbol,
|
|
src2: &Symbol,
|
|
) -> Result<(), String>;
|
|
|
|
/// build_num_sub_i64 stores the `src1 - src2` difference into dst.
|
|
/// It only deals with inputs and outputs of i64 type.
|
|
fn build_num_sub_i64(
|
|
&mut self,
|
|
dst: &Symbol,
|
|
src1: &Symbol,
|
|
src2: &Symbol,
|
|
) -> Result<(), String>;
|
|
|
|
/// build_eq_i64 stores the result of `src1 == src2` into dst.
|
|
/// It only deals with inputs and outputs of i64 type.
|
|
fn build_eq_i64(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol) -> Result<(), String>;
|
|
|
|
/// literal_map gets the map from symbol to literal, used for lazy loading and literal folding.
|
|
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
|
|
|
|
fn load_literal_symbols(&mut self, syms: &[Symbol]) -> Result<(), String> {
|
|
if self.env().lazy_literals {
|
|
for sym in syms {
|
|
if let Some(lit) = self.literal_map().remove(sym) {
|
|
self.load_literal(sym, &lit)?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// load_literal sets a symbol to be equal to a literal.
|
|
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>;
|
|
|
|
/// return_symbol moves a symbol to the correct return location for the backend.
|
|
fn return_symbol(&mut self, sym: &Symbol) -> Result<(), String>;
|
|
|
|
/// free_symbols will free all symbols for the given statement.
|
|
fn free_symbols(&mut self, stmt: &Stmt<'a>) {
|
|
if let Some(syms) = self.free_map().remove(&(stmt as *const Stmt<'a>)) {
|
|
for sym in syms {
|
|
//println!("Freeing symbol: {:?}", sym);
|
|
self.free_symbol(&sym);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// free_symbol frees any registers or stack space used to hold a symbol.
|
|
fn free_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);
|
|
}
|
|
|
|
/// 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>>;
|
|
|
|
fn create_free_map(&mut self) {
|
|
let mut free_map = MutMap::default();
|
|
let arena = self.env().arena;
|
|
for (sym, stmt) in self.last_seen_map() {
|
|
let vals = free_map
|
|
.entry(*stmt)
|
|
.or_insert_with(|| bumpalo::vec![in arena]);
|
|
vals.push(*sym);
|
|
}
|
|
self.set_free_map(free_map);
|
|
}
|
|
|
|
/// free_map gets the map statement to the symbols that are free after they run.
|
|
fn free_map(&mut self) -> &mut MutMap<*const Stmt<'a>, Vec<'a, Symbol>>;
|
|
|
|
/// set_free_map sets the free map to the given map.
|
|
fn set_free_map(&mut self, map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>);
|
|
|
|
/// scan_ast 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 scan_ast(&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::Call(call) => self.scan_ast_call(call, 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 {
|
|
symbol,
|
|
arguments,
|
|
tag_name,
|
|
..
|
|
} => {
|
|
self.set_last_seen(*symbol, stmt);
|
|
match tag_name {
|
|
TagName::Closure(sym) => {
|
|
self.set_last_seen(*sym, stmt);
|
|
}
|
|
TagName::Private(sym) => {
|
|
self.set_last_seen(*sym, stmt);
|
|
}
|
|
TagName::Global(_) => {}
|
|
}
|
|
for sym in *arguments {
|
|
self.set_last_seen(*sym, stmt);
|
|
}
|
|
}
|
|
Expr::Reset(sym) => {
|
|
self.set_last_seen(*sym, stmt);
|
|
}
|
|
Expr::EmptyArray => {}
|
|
Expr::RuntimeErrorFunction(_) => {}
|
|
}
|
|
self.scan_ast(following);
|
|
}
|
|
|
|
Stmt::Invoke {
|
|
symbol,
|
|
layout,
|
|
call,
|
|
pass,
|
|
fail: _,
|
|
} => {
|
|
// for now, treat invoke as a normal call
|
|
|
|
let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass);
|
|
self.scan_ast(&stmt);
|
|
}
|
|
|
|
Stmt::Switch {
|
|
cond_symbol,
|
|
branches,
|
|
default_branch,
|
|
..
|
|
} => {
|
|
self.set_last_seen(*cond_symbol, stmt);
|
|
for (_, _, branch) in *branches {
|
|
self.scan_ast(branch);
|
|
}
|
|
self.scan_ast(default_branch.1);
|
|
}
|
|
Stmt::Ret(sym) => {
|
|
self.set_last_seen(*sym, stmt);
|
|
}
|
|
Stmt::Rethrow => {}
|
|
Stmt::Refcounting(modify, following) => {
|
|
let sym = modify.get_symbol();
|
|
|
|
self.set_last_seen(sym, stmt);
|
|
self.scan_ast(following);
|
|
}
|
|
Stmt::Join {
|
|
parameters,
|
|
continuation,
|
|
remainder,
|
|
..
|
|
} => {
|
|
for param in *parameters {
|
|
self.set_last_seen(param.symbol, stmt);
|
|
}
|
|
self.scan_ast(continuation);
|
|
self.scan_ast(remainder);
|
|
}
|
|
Stmt::Jump(JoinPointId(sym), symbols) => {
|
|
self.set_last_seen(*sym, stmt);
|
|
for sym in *symbols {
|
|
self.set_last_seen(*sym, stmt);
|
|
}
|
|
}
|
|
Stmt::RuntimeError(_) => {}
|
|
}
|
|
}
|
|
|
|
fn scan_ast_call(&mut self, call: &roc_mono::ir::Call, stmt: &roc_mono::ir::Stmt<'a>) {
|
|
let roc_mono::ir::Call {
|
|
call_type,
|
|
arguments,
|
|
} = call;
|
|
|
|
for sym in *arguments {
|
|
self.set_last_seen(*sym, stmt);
|
|
}
|
|
|
|
match call_type {
|
|
CallType::ByName { .. } => {}
|
|
CallType::ByPointer { name: sym, .. } => {
|
|
self.set_last_seen(*sym, stmt);
|
|
}
|
|
CallType::LowLevel { .. } => {}
|
|
CallType::Foreign { .. } => {}
|
|
}
|
|
}
|
|
}
|