gen_dev: add ability to pass arguments to functions

This commit is contained in:
Brendan Hansknecht 2021-02-12 19:32:34 -08:00
parent 01a86aaa9c
commit 95be1a1b6d
5 changed files with 590 additions and 81 deletions

View File

@ -1,6 +1,9 @@
use crate::generic64::{Assembler, CallConv, RegTrait};
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage};
use crate::Relocation;
use bumpalo::collections::Vec;
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use roc_mono::layout::Layout;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[allow(dead_code)]
@ -138,10 +141,13 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
buf: &mut Vec<'_, u8>,
saved_regs: &[AArch64GeneralReg],
requested_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<i32, String> {
// Full size is upcast to i64 to make sure we don't overflow here.
let full_stack_size = requested_stack_size
.checked_add(8 * saved_regs.len() as i32 + 8) // The extra 8 is space to store the frame pointer.
.ok_or("Ran out of stack space")?
.checked_add(fn_call_stack_size)
.ok_or("Ran out of stack space")?;
let alignment = if full_stack_size <= 0 {
0
@ -155,6 +161,11 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
};
if let Some(aligned_stack_size) = full_stack_size.checked_add(offset as i32) {
if aligned_stack_size > 0 {
AArch64Assembler::mov_reg64_reg64(
buf,
AArch64GeneralReg::FP,
AArch64GeneralReg::ZRSP,
);
AArch64Assembler::sub_reg64_reg64_imm32(
buf,
AArch64GeneralReg::ZRSP,
@ -168,9 +179,11 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GeneralReg::LR);
offset -= 8;
AArch64Assembler::mov_stack32_reg64(buf, offset, AArch64GeneralReg::FP);
offset = aligned_stack_size - fn_call_stack_size;
for reg in saved_regs {
offset -= 8;
AArch64Assembler::mov_stack32_reg64(buf, offset, *reg);
AArch64Assembler::mov_base32_reg64(buf, offset, *reg);
}
Ok(aligned_stack_size)
} else {
@ -186,6 +199,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
buf: &mut Vec<'_, u8>,
saved_regs: &[AArch64GeneralReg],
aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String> {
if aligned_stack_size > 0 {
// All the following stores could be optimized by using `STP` to store pairs.
@ -194,9 +208,11 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
AArch64Assembler::mov_reg64_stack32(buf, AArch64GeneralReg::LR, offset);
offset -= 8;
AArch64Assembler::mov_reg64_stack32(buf, AArch64GeneralReg::FP, offset);
offset = aligned_stack_size - fn_call_stack_size;
for reg in saved_regs {
offset -= 8;
AArch64Assembler::mov_reg64_stack32(buf, *reg, offset);
AArch64Assembler::mov_reg64_base32(buf, *reg, offset);
}
AArch64Assembler::add_reg64_reg64_imm32(
buf,
@ -207,6 +223,25 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
}
Ok(())
}
#[inline(always)]
fn load_args<'a>(
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)],
) -> Result<(), String> {
Err("Loading args not yet implemented for AArch64".to_string())
}
#[inline(always)]
fn store_args<'a>(
_buf: &mut Vec<'a, u8>,
_symbol_map: &MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [Symbol],
_arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>,
) -> Result<u32, String> {
Err("Storing args not yet implemented for AArch64".to_string())
}
}
impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {

View File

@ -36,12 +36,31 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
buf: &mut Vec<'a, u8>,
general_saved_regs: &[GeneralReg],
requested_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<i32, String>;
fn cleanup_stack<'a>(
buf: &mut Vec<'a, u8>,
general_saved_regs: &[GeneralReg],
aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String>;
// load_args updates the symbol map to know where every arg is stored.
fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
) -> Result<(), String>;
// store_args stores the args in registers and on the stack for function calling.
// It returns the amount of stack space needed to temporarily store the args.
fn store_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>,
) -> Result<u32, String>;
}
/// Assembler contains calls to the backend assembly generator.
@ -76,6 +95,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn mov_freg64_freg64(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
// base32 is similar to stack based instructions but they reference the base/frame pointer.
fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
@ -104,7 +124,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
#[derive(Clone, Debug, PartialEq)]
#[allow(dead_code)]
enum SymbolStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
pub enum SymbolStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
// These may need layout, but I am not sure.
// I think whenever a symbol would be used, we specify layout anyways.
GeneralReg(GeneralReg),
@ -149,7 +169,9 @@ pub struct Backend64Bit<
general_used_callee_saved_regs: MutSet<GeneralReg>,
float_used_callee_saved_regs: MutSet<FloatReg>,
stack_size: i32,
stack_size: u32,
// The ammount of stack space needed to pass args for function calling.
fn_call_stack_size: u32,
}
impl<
@ -178,6 +200,7 @@ impl<
float_used_regs: bumpalo::vec![in env.arena],
float_used_callee_saved_regs: MutSet::default(),
stack_size: 0,
fn_call_stack_size: 0,
})
}
@ -187,6 +210,7 @@ impl<
fn reset(&mut self) {
self.stack_size = 0;
self.fn_call_stack_size = 0;
self.last_seen_map.clear();
self.free_map.clear();
self.symbols_map.clear();
@ -225,14 +249,24 @@ impl<
// Setup stack.
let mut used_regs = bumpalo::vec![in self.env.arena];
used_regs.extend(&self.general_used_callee_saved_regs);
let aligned_stack_size = CC::setup_stack(&mut out, &used_regs, self.stack_size)?;
let aligned_stack_size = CC::setup_stack(
&mut out,
&used_regs,
self.stack_size as i32,
self.fn_call_stack_size as i32,
)?;
let setup_offset = out.len();
// Add function body.
out.extend(&self.buf);
// Cleanup stack.
CC::cleanup_stack(&mut out, &used_regs, aligned_stack_size)?;
CC::cleanup_stack(
&mut out,
&used_regs,
aligned_stack_size,
self.fn_call_stack_size as i32,
)?;
ASM::ret(&mut out);
// Update relocs to include stack setup offset.
@ -255,12 +289,31 @@ impl<
Ok((out.into_bump_slice(), out_relocs.into_bump_slice()))
}
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String> {
CC::load_args(&mut self.symbols_map, args)?;
// Update used and free regs.
for (sym, storage) in &self.symbols_map {
match storage {
SymbolStorage::GeneralReg(reg) | SymbolStorage::BaseAndGeneralReg(reg, _) => {
self.general_free_regs.retain(|r| *r != *reg);
self.general_used_regs.push((*reg, *sym));
}
SymbolStorage::FloatReg(reg) | SymbolStorage::BaseAndFloatReg(reg, _) => {
self.float_free_regs.retain(|r| *r != *reg);
self.float_used_regs.push((*reg, *sym));
}
SymbolStorage::Base(_) => {}
}
}
Ok(())
}
fn build_fn_call(
&mut self,
dst: &Symbol,
fn_name: String,
_args: &'a [Symbol],
_arg_layouts: &[Layout<'a>],
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
// Save used caller saved regs.
@ -288,7 +341,14 @@ impl<
}
// Put values in param regs or on top of the stack.
// TODO: deal with arg passing. This will be call conv specific.
let tmp_stack_size = CC::store_args(
&mut self.buf,
&self.symbols_map,
args,
arg_layouts,
ret_layout,
)?;
self.fn_call_stack_size = std::cmp::max(self.fn_call_stack_size, tmp_stack_size);
// Call function and generate reloc.
ASM::call(&mut self.buf, &mut self.relocs, fn_name);
@ -479,17 +539,6 @@ impl<
.insert(*sym, SymbolStorage::GeneralReg(reg));
Ok(reg)
}
Some(SymbolStorage::FloatReg(_reg)) => {
Err("Cannot load floating point symbol into GeneralReg".to_string())
}
Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => {
self.symbols_map
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
Ok(reg)
}
Some(SymbolStorage::BaseAndFloatReg(_reg, _offset)) => {
Err("Cannot load floating point symbol into GeneralReg".to_string())
}
Some(SymbolStorage::Base(offset)) => {
let reg = self.claim_general_reg(sym)?;
self.symbols_map
@ -497,6 +546,14 @@ impl<
ASM::mov_reg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg)
}
Some(SymbolStorage::BaseAndGeneralReg(reg, offset)) => {
self.symbols_map
.insert(*sym, SymbolStorage::BaseAndGeneralReg(reg, offset));
Ok(reg)
}
Some(SymbolStorage::FloatReg(_)) | Some(SymbolStorage::BaseAndFloatReg(_, _)) => {
Err("Cannot load floating point symbol into GeneralReg".to_string())
}
None => Err(format!("Unknown symbol: {}", sym)),
}
}
@ -504,21 +561,10 @@ impl<
fn load_to_float_reg(&mut self, sym: &Symbol) -> Result<FloatReg, String> {
let val = self.symbols_map.remove(sym);
match val {
Some(SymbolStorage::GeneralReg(_reg)) => {
Err("Cannot load integer point symbol into FloatReg".to_string())
}
Some(SymbolStorage::FloatReg(reg)) => {
self.symbols_map.insert(*sym, SymbolStorage::FloatReg(reg));
Ok(reg)
}
Some(SymbolStorage::BaseAndGeneralReg(_reg, _offset)) => {
Err("Cannot load integer point symbol into FloatReg".to_string())
}
Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => {
self.symbols_map
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
Ok(reg)
}
Some(SymbolStorage::Base(offset)) => {
let reg = self.claim_float_reg(sym)?;
self.symbols_map
@ -526,6 +572,14 @@ impl<
ASM::mov_freg64_base32(&mut self.buf, reg, offset as i32);
Ok(reg)
}
Some(SymbolStorage::BaseAndFloatReg(reg, offset)) => {
self.symbols_map
.insert(*sym, SymbolStorage::BaseAndFloatReg(reg, offset));
Ok(reg)
}
Some(SymbolStorage::GeneralReg(_)) | Some(SymbolStorage::BaseAndGeneralReg(_, _)) => {
Err("Cannot load integer point symbol into FloatReg".to_string())
}
None => Err(format!("Unknown symbol: {}", sym)),
}
}
@ -534,17 +588,23 @@ impl<
let val = self.symbols_map.remove(sym);
match val {
Some(SymbolStorage::GeneralReg(reg)) => {
let offset = self.increase_stack_size(8)?;
// For base addresssing, use the negative offset.
ASM::mov_base32_reg64(&mut self.buf, -offset, reg);
self.symbols_map.insert(*sym, SymbolStorage::Base(-offset));
let offset = self.increase_stack_size(8)? as i32;
// For base addresssing, use the negative offset - 8.
ASM::mov_base32_reg64(&mut self.buf, -offset - 8, reg);
self.symbols_map
.insert(*sym, SymbolStorage::Base(-offset - 8));
Ok(())
}
Some(SymbolStorage::FloatReg(reg)) => {
let offset = self.increase_stack_size(8)?;
let offset = self.increase_stack_size(8)? as i32;
// For base addresssing, use the negative offset.
ASM::mov_base32_freg64(&mut self.buf, -offset, reg);
self.symbols_map.insert(*sym, SymbolStorage::Base(-offset));
ASM::mov_base32_freg64(&mut self.buf, -offset - 8, reg);
self.symbols_map
.insert(*sym, SymbolStorage::Base(-offset - 8));
Ok(())
}
Some(SymbolStorage::Base(offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset));
Ok(())
}
Some(SymbolStorage::BaseAndGeneralReg(_, offset)) => {
@ -555,21 +615,22 @@ impl<
self.symbols_map.insert(*sym, SymbolStorage::Base(offset));
Ok(())
}
Some(SymbolStorage::Base(offset)) => {
self.symbols_map.insert(*sym, SymbolStorage::Base(offset));
Ok(())
}
None => Err(format!("Unknown symbol: {}", sym)),
}
}
/// increase_stack_size increase the current stack size and returns the offset of the stack.
fn increase_stack_size(&mut self, amount: i32) -> Result<i32, String> {
fn increase_stack_size(&mut self, amount: u32) -> Result<u32, String> {
debug_assert!(amount > 0);
let offset = self.stack_size;
if let Some(new_size) = self.stack_size.checked_add(amount) {
self.stack_size = new_size;
Ok(offset)
// Since stack size is u32, but the max offset is i32, if we pass i32 max, we have overflowed.
if new_size > i32::MAX as u32 {
Err("Ran out of stack space".to_string())
} else {
self.stack_size = new_size;
Ok(offset)
}
} else {
Err("Ran out of stack space".to_string())
}

View File

@ -1,6 +1,9 @@
use crate::generic64::{Assembler, CallConv, RegTrait};
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage};
use crate::Relocation;
use bumpalo::collections::Vec;
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout};
// 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.
@ -145,8 +148,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
buf: &mut Vec<'a, u8>,
general_saved_regs: &[X86_64GeneralReg],
requested_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<i32, String> {
x86_64_generic_setup_stack(buf, general_saved_regs, requested_stack_size)
x86_64_generic_setup_stack(
buf,
general_saved_regs,
requested_stack_size,
fn_call_stack_size,
)
}
#[inline(always)]
@ -154,8 +163,201 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
buf: &mut Vec<'a, u8>,
general_saved_regs: &[X86_64GeneralReg],
aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String> {
x86_64_generic_cleanup_stack(buf, general_saved_regs, aligned_stack_size)
x86_64_generic_cleanup_stack(
buf,
general_saved_regs,
aligned_stack_size,
fn_call_stack_size,
)
}
#[inline(always)]
fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
) -> Result<(), String> {
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
let mut general_i = 0;
let mut float_i = 0;
for (layout, sym) in args.iter() {
match layout {
Layout::Builtin(Builtin::Int64) => {
if general_i < Self::GENERAL_PARAM_REGS.len() {
symbol_map.insert(
*sym,
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
);
general_i += 1;
} else {
base_offset += 8;
symbol_map.insert(*sym, SymbolStorage::Base(base_offset));
}
}
Layout::Builtin(Builtin::Float64) => {
if float_i < Self::FLOAT_PARAM_REGS.len() {
symbol_map.insert(
*sym,
SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[float_i]),
);
float_i += 1;
} else {
base_offset += 8;
symbol_map.insert(*sym, SymbolStorage::Base(base_offset));
}
}
x => {
return Err(format!(
"Loading args with layout {:?} not yet implementd",
x
));
}
}
}
Ok(())
}
#[inline(always)]
fn store_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<u32, String> {
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut general_i = 0;
let mut float_i = 0;
// For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg.
match ret_layout {
Layout::Builtin(Builtin::Int64) => {}
Layout::Builtin(Builtin::Float64) => {}
x => {
return Err(format!(
"recieving return type, {:?}, is not yet implemented",
x
));
}
}
for (i, layout) in arg_layouts.iter().enumerate() {
match layout {
Layout::Builtin(Builtin::Int64) => {
if general_i < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg.
let dst = Self::GENERAL_PARAM_REGS[general_i];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::GeneralReg(reg)
| SymbolStorage::BaseAndGeneralReg(reg, _) => {
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
}
SymbolStorage::Base(offset) => {
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
}
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg(_, _) => {
return Err(
"Cannot load floating point symbol into GeneralReg".to_string()
)
}
}
general_i += 1;
} else {
// Load the value to the stack.
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::GeneralReg(reg)
| SymbolStorage::BaseAndGeneralReg(reg, _) => {
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
}
SymbolStorage::Base(offset) => {
// Use RAX as a tmp reg because it will be free before function calls.
X86_64Assembler::mov_reg64_base32(
buf,
X86_64GeneralReg::RAX,
*offset,
);
X86_64Assembler::mov_stack32_reg64(
buf,
stack_offset,
X86_64GeneralReg::RAX,
);
}
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg(_, _) => {
return Err(
"Cannot load floating point symbol into GeneralReg".to_string()
)
}
}
stack_offset += 8;
}
}
Layout::Builtin(Builtin::Float64) => {
if float_i < Self::FLOAT_PARAM_REGS.len() {
// Load the value to the param reg.
let dst = Self::FLOAT_PARAM_REGS[float_i];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::FloatReg(reg)
| SymbolStorage::BaseAndFloatReg(reg, _) => {
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
}
SymbolStorage::Base(offset) => {
X86_64Assembler::mov_freg64_base32(buf, dst, *offset);
}
SymbolStorage::GeneralReg(_)
| SymbolStorage::BaseAndGeneralReg(_, _) => {
return Err("Cannot load general symbol into FloatReg".to_string())
}
}
float_i += 1;
} else {
// Load the value to the stack.
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::FloatReg(reg)
| SymbolStorage::BaseAndFloatReg(reg, _) => {
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
}
SymbolStorage::Base(offset) => {
// Use XMM0 as a tmp reg because it will be free before function calls.
X86_64Assembler::mov_freg64_base32(
buf,
X86_64FloatReg::XMM0,
*offset,
);
X86_64Assembler::mov_stack32_freg64(
buf,
stack_offset,
X86_64FloatReg::XMM0,
);
}
SymbolStorage::GeneralReg(_)
| SymbolStorage::BaseAndGeneralReg(_, _) => {
return Err("Cannot load general symbol into FloatReg".to_string())
}
}
stack_offset += 8;
}
}
x => {
return Err(format!(
"calling with arg type, {:?}, is not yet implemented",
x
));
}
}
}
Ok(stack_offset as u32)
}
}
@ -256,8 +458,9 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
buf: &mut Vec<'a, u8>,
saved_regs: &[X86_64GeneralReg],
requested_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<i32, String> {
x86_64_generic_setup_stack(buf, saved_regs, requested_stack_size)
x86_64_generic_setup_stack(buf, saved_regs, requested_stack_size, fn_call_stack_size)
}
#[inline(always)]
@ -265,8 +468,190 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
buf: &mut Vec<'a, u8>,
saved_regs: &[X86_64GeneralReg],
aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String> {
x86_64_generic_cleanup_stack(buf, saved_regs, aligned_stack_size)
x86_64_generic_cleanup_stack(buf, saved_regs, aligned_stack_size, fn_call_stack_size)
}
#[inline(always)]
fn load_args<'a>(
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
) -> Result<(), String> {
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
for (i, (layout, sym)) in args.iter().enumerate() {
if i < Self::GENERAL_PARAM_REGS.len() {
match layout {
Layout::Builtin(Builtin::Int64) => {
symbol_map
.insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]));
}
Layout::Builtin(Builtin::Float64) => {
symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i]));
}
x => {
return Err(format!(
"Loading args with layout {:?} not yet implementd",
x
));
}
}
} else {
base_offset += match layout {
Layout::Builtin(Builtin::Int64) => 8,
Layout::Builtin(Builtin::Float64) => 8,
x => {
return Err(format!(
"Loading args with layout {:?} not yet implemented",
x
));
}
};
symbol_map.insert(*sym, SymbolStorage::Base(base_offset));
}
}
Ok(())
}
#[inline(always)]
fn store_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<u32, String> {
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut reg_i = 0;
// For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg.
match ret_layout {
Layout::Builtin(Builtin::Int64) => {}
Layout::Builtin(Builtin::Float64) => {}
x => {
return Err(format!(
"recieving return type, {:?}, is not yet implemented",
x
));
}
}
for (i, layout) in arg_layouts.iter().enumerate() {
match layout {
Layout::Builtin(Builtin::Int64) => {
if i < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg.
let dst = Self::GENERAL_PARAM_REGS[reg_i];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::GeneralReg(reg)
| SymbolStorage::BaseAndGeneralReg(reg, _) => {
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
}
SymbolStorage::Base(offset) => {
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
}
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg(_, _) => {
return Err(
"Cannot load floating point symbol into GeneralReg".to_string()
)
}
}
reg_i += 1;
} else {
// Load the value to the stack.
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::GeneralReg(reg)
| SymbolStorage::BaseAndGeneralReg(reg, _) => {
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
}
SymbolStorage::Base(offset) => {
// Use RAX as a tmp reg because it will be free before function calls.
X86_64Assembler::mov_reg64_base32(
buf,
X86_64GeneralReg::RAX,
*offset,
);
X86_64Assembler::mov_stack32_reg64(
buf,
stack_offset,
X86_64GeneralReg::RAX,
);
}
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg(_, _) => {
return Err(
"Cannot load floating point symbol into GeneralReg".to_string()
)
}
}
stack_offset += 8;
}
}
Layout::Builtin(Builtin::Float64) => {
if i < Self::FLOAT_PARAM_REGS.len() {
// Load the value to the param reg.
let dst = Self::FLOAT_PARAM_REGS[reg_i];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::FloatReg(reg)
| SymbolStorage::BaseAndFloatReg(reg, _) => {
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
}
SymbolStorage::Base(offset) => {
X86_64Assembler::mov_freg64_base32(buf, dst, *offset);
}
SymbolStorage::GeneralReg(_)
| SymbolStorage::BaseAndGeneralReg(_, _) => {
return Err("Cannot load general symbol into FloatReg".to_string())
}
}
reg_i += 1;
} else {
// Load the value to the stack.
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::FloatReg(reg)
| SymbolStorage::BaseAndFloatReg(reg, _) => {
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
}
SymbolStorage::Base(offset) => {
// Use XMM0 as a tmp reg because it will be free before function calls.
X86_64Assembler::mov_freg64_base32(
buf,
X86_64FloatReg::XMM0,
*offset,
);
X86_64Assembler::mov_stack32_freg64(
buf,
stack_offset,
X86_64FloatReg::XMM0,
);
}
SymbolStorage::GeneralReg(_)
| SymbolStorage::BaseAndGeneralReg(_, _) => {
return Err("Cannot load general symbol into FloatReg".to_string())
}
}
stack_offset += 8;
}
}
x => {
return Err(format!(
"calling with arg type, {:?}, is not yet implemented",
x
));
}
}
}
Ok(stack_offset as u32)
}
}
@ -275,12 +660,15 @@ fn x86_64_generic_setup_stack<'a>(
buf: &mut Vec<'a, u8>,
saved_regs: &[X86_64GeneralReg],
requested_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<i32, String> {
X86_64Assembler::push_reg64(buf, X86_64GeneralReg::RBP);
X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RBP, X86_64GeneralReg::RSP);
let full_stack_size = requested_stack_size
.checked_add(8 * saved_regs.len() as i32)
.ok_or("Ran out of stack space")?
.checked_add(fn_call_stack_size)
.ok_or("Ran out of stack space")?;
let alignment = if full_stack_size <= 0 {
0
@ -301,11 +689,11 @@ fn x86_64_generic_setup_stack<'a>(
aligned_stack_size,
);
// Put values at the top of the stack to avoid conflicts with previously saved variables.
let mut offset = aligned_stack_size;
// Put values at the top of the stack to avoid conflicts with previously saved variables.
let mut offset = aligned_stack_size - fn_call_stack_size;
for reg in saved_regs {
offset -= 8;
X86_64Assembler::mov_base32_reg64(buf, -offset, *reg);
offset -= 8;
}
Ok(aligned_stack_size)
} else {
@ -321,12 +709,13 @@ fn x86_64_generic_cleanup_stack<'a>(
buf: &mut Vec<'a, u8>,
saved_regs: &[X86_64GeneralReg],
aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String> {
if aligned_stack_size > 0 {
let mut offset = aligned_stack_size;
let mut offset = aligned_stack_size - fn_call_stack_size;
for reg in saved_regs {
offset -= 8;
X86_64Assembler::mov_reg64_base32(buf, *reg, -offset);
offset -= 8;
}
X86_64Assembler::add_reg64_reg64_imm32(
buf,
@ -335,7 +724,7 @@ fn x86_64_generic_cleanup_stack<'a>(
aligned_stack_size,
);
}
X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RSP, X86_64GeneralReg::RBP);
//X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RSP, X86_64GeneralReg::RBP);
X86_64Assembler::pop_reg64(buf, X86_64GeneralReg::RBP);
Ok(())
}
@ -657,7 +1046,9 @@ fn mov_reg64_imm64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, imm: i64) {
/// `MOV r/m64,r64` -> Move r64 to r/m64.
#[inline(always)]
fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
binop_reg64_reg64(0x89, buf, dst, src);
if dst != src {
binop_reg64_reg64(0x89, buf, dst, src);
}
}
/// `MOV r64,r/m64` -> Move r/m64 to r64. where m64 references the base pionter.
@ -996,26 +1387,27 @@ mod tests {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, src), expected) in &[
((X86_64GeneralReg::RAX, X86_64GeneralReg::RAX), vec![]),
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::RAX),
[0x48, 0x89, 0xC0],
(X86_64GeneralReg::RAX, X86_64GeneralReg::RCX),
vec![0x48, 0x89, 0xC8],
),
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::R15),
[0x4C, 0x89, 0xF8],
vec![0x4C, 0x89, 0xF8],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::RAX),
[0x49, 0x89, 0xC7],
vec![0x49, 0x89, 0xC7],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::R15),
[0x4D, 0x89, 0xFF],
(X86_64GeneralReg::R15, X86_64GeneralReg::R14),
vec![0x4D, 0x89, 0xF7],
),
] {
buf.clear();
mov_reg64_reg64(&mut buf, *dst, *src);
assert_eq!(expected, &buf[..]);
assert_eq!(&expected[..], &buf[..]);
}
}

View File

@ -70,10 +70,14 @@ where
/// finalize is run at the end of build_proc when all internal code is finalized.
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String>;
// load_args is used to let the backend know what the args are.
// The backend should track these args so it can use them as needed.
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), 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.
self.load_args(&proc.args)?;
// let start = std::time::Instant::now();
self.scan_ast(&proc.body);
self.create_free_map();
@ -167,6 +171,8 @@ where
let fn_name = LayoutIds::default()
.get(*func_sym, layout)
.to_symbol_string(*func_sym, &self.env().interns);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(arguments)?;
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
}
x => Err(format!("the function, {:?}, is not yet implemented", x)),

View File

@ -168,6 +168,36 @@ mod gen_num {
);
}
#[test]
fn gen_wrap_add_nums() {
assert_evals_to!(
indoc!(
r#"
add2 = \num1, num2 -> num1 + num2
add2 4 5
"#
),
9,
i64
);
}
#[test]
fn gen_wrap_add_nums_force_stack() {
assert_evals_to!(
indoc!(
r#"
add9 = \num1, num2, num3, num4, num5, num6, num7, num8, num9 -> num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9
add9 1 2 3 4 5 6 7 8 9
"#
),
45,
i64
);
}
/*
#[test]
fn f64_sqrt() {
@ -250,21 +280,6 @@ mod gen_num {
);
}
#[test]
fn gen_wrap_add_nums() {
assert_evals_to!(
indoc!(
r#"
add2 = \num1, num2 -> num1 + num2
add2 4 5
"#
),
9,
i64
);
}
#[test]
fn gen_div_f64() {
// FIXME this works with normal types, but fails when checking uniqueness types