Merge branch 'main' of github.com:roc-lang/roc into rust-1-62-1

This commit is contained in:
Anton-4 2022-09-10 18:17:33 +02:00
commit f6324edc10
No known key found for this signature in database
GPG key ID: A13F4A6E21141925
226 changed files with 10311 additions and 3781 deletions

View file

@ -440,7 +440,84 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
todo!("register multiplication for AArch64");
todo!("register signed multiplication for AArch64");
}
fn umul_reg64_reg64_reg64<'a, ASM, CC>(
_buf: &mut Vec<'a, u8>,
_storage_manager: &mut StorageManager<'a, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) where
ASM: Assembler<AArch64GeneralReg, AArch64FloatReg>,
CC: CallConv<AArch64GeneralReg, AArch64FloatReg, ASM>,
{
todo!("register unsigned multiplication for AArch64");
}
fn idiv_reg64_reg64_reg64<'a, ASM, CC>(
_buf: &mut Vec<'a, u8>,
_storage_manager: &mut StorageManager<'a, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) where
ASM: Assembler<AArch64GeneralReg, AArch64FloatReg>,
CC: CallConv<AArch64GeneralReg, AArch64FloatReg, ASM>,
{
todo!("register signed division for AArch64");
}
fn udiv_reg64_reg64_reg64<'a, ASM, CC>(
_buf: &mut Vec<'a, u8>,
_storage_manager: &mut StorageManager<'a, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) where
ASM: Assembler<AArch64GeneralReg, AArch64FloatReg>,
CC: CallConv<AArch64GeneralReg, AArch64FloatReg, ASM>,
{
todo!("register unsigned division for AArch64");
}
#[inline(always)]
fn mul_freg32_freg32_freg32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64FloatReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
) {
todo!("multiplication for floats for AArch64");
}
#[inline(always)]
fn mul_freg64_freg64_freg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64FloatReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
) {
todo!("multiplication for floats for AArch64");
}
#[inline(always)]
fn div_freg32_freg32_freg32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64FloatReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
) {
todo!("division for floats for AArch64");
}
#[inline(always)]
fn div_freg64_freg64_freg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64FloatReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
) {
todo!("division for floats for AArch64");
}
#[inline(always)]
@ -749,10 +826,41 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
todo!("registers greater than or equal for AArch64");
}
fn set_if_overflow(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg) {
todo!("set if overflow for AArch64");
}
#[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) {
ret_reg64(buf, AArch64GeneralReg::LR)
}
fn and_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
todo!("bitwise and for AArch64")
}
fn or_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
todo!("bitwise or for AArch64")
}
fn xor_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
todo!("bitwise xor for AArch64")
}
}
impl AArch64Assembler {}

View file

@ -21,7 +21,7 @@ mod disassembler_test_macro;
pub(crate) mod storage;
pub(crate) mod x86_64;
use storage::StorageManager;
use storage::{RegStorage, StorageManager};
const REFCOUNT_ONE: u64 = i64::MIN as u64;
// TODO: on all number functions double check and deal with over/underflow.
@ -143,6 +143,27 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
src2: GeneralReg,
);
fn and_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn or_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn xor_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String);
/// Jumps by an offset of offset bytes unconditionally.
@ -210,12 +231,64 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn mul_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn div_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn div_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn imul_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn umul_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, CC>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
) where
ASM: Assembler<GeneralReg, FloatReg>,
CC: CallConv<GeneralReg, FloatReg, ASM>;
fn idiv_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, CC>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
) where
ASM: Assembler<GeneralReg, FloatReg>,
CC: CallConv<GeneralReg, FloatReg, ASM>;
fn udiv_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, CC>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
) where
ASM: Assembler<GeneralReg, FloatReg>,
CC: CallConv<GeneralReg, FloatReg, ASM>;
fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32);
fn sub_reg64_reg64_reg64(
@ -268,6 +341,8 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
src2: GeneralReg,
);
fn set_if_overflow(buf: &mut Vec<'_, u8>, dst: GeneralReg);
fn ret(buf: &mut Vec<'_, u8>);
}
@ -288,6 +363,7 @@ pub struct Backend64Bit<
// They are likely to be small enough that it is faster to use a vec and linearly scan it or keep it sorted and binary search.
phantom_asm: PhantomData<ASM>,
phantom_cc: PhantomData<CC>,
target_info: TargetInfo,
env: &'a Env<'a>,
interns: &'a mut Interns,
helper_proc_gen: CodeGenHelp<'a>,
@ -322,9 +398,15 @@ pub fn new_backend_64bit<
Backend64Bit {
phantom_asm: PhantomData,
phantom_cc: PhantomData,
target_info,
env,
interns,
helper_proc_gen: CodeGenHelp::new(env.arena, target_info, env.module_id),
helper_proc_gen: CodeGenHelp::new(
env.arena,
env.layout_interner,
target_info,
env.module_id,
),
helper_proc_symbols: bumpalo::vec![in env.arena],
proc_name: None,
is_self_recursive: None,
@ -339,6 +421,19 @@ pub fn new_backend_64bit<
}
}
macro_rules! quadword_and_smaller {
() => {
IntWidth::I64
| IntWidth::U64
| IntWidth::I32
| IntWidth::U32
| IntWidth::I16
| IntWidth::U16
| IntWidth::I8
| IntWidth::U8
};
}
impl<
'a,
GeneralReg: RegTrait,
@ -699,16 +794,7 @@ impl<
fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) {
match layout {
Layout::Builtin(Builtin::Int(
IntWidth::I64
| IntWidth::U64
| IntWidth::I32
| IntWidth::U32
| IntWidth::I16
| IntWidth::U16
| IntWidth::I8
| IntWidth::U8,
)) => {
Layout::Builtin(Builtin::Int(quadword_and_smaller!())) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -734,9 +820,62 @@ impl<
}
}
fn build_num_add_checked(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
num_layout: &Layout<'a>,
return_layout: &Layout<'a>,
) {
use Builtin::Int;
let buf = &mut self.buf;
let struct_size = return_layout.stack_size(self.env.layout_interner, self.target_info);
let base_offset = self.storage_manager.claim_stack_area(dst, struct_size);
match num_layout {
Layout::Builtin(Int(IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8)) => {
let dst_reg = self
.storage_manager
.claim_general_reg(buf, &Symbol::DEV_TMP);
let overflow_reg = self
.storage_manager
.claim_general_reg(buf, &Symbol::DEV_TMP2);
let src1_reg = self.storage_manager.load_to_general_reg(buf, src1);
let src2_reg = self.storage_manager.load_to_general_reg(buf, src2);
ASM::add_reg64_reg64_reg64(buf, dst_reg, src1_reg, src2_reg);
ASM::set_if_overflow(buf, overflow_reg);
ASM::mov_base32_reg64(buf, base_offset, dst_reg);
ASM::mov_base32_reg64(buf, base_offset + 8, overflow_reg);
self.free_symbol(&Symbol::DEV_TMP);
self.free_symbol(&Symbol::DEV_TMP2);
}
Layout::Builtin(Int(IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8)) => {
todo!("addChecked for unsigned integers")
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
todo!("addChecked for f64")
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
todo!("addChecked for f32")
}
x => todo!("NumAdd: layout, {:?}", x),
}
}
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) {
use Builtin::Int;
match layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
Layout::Builtin(Int(IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -746,10 +885,95 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
Layout::Builtin(Int(IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src1);
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src2);
ASM::umul_reg64_reg64_reg64(
&mut self.buf,
&mut self.storage_manager,
dst_reg,
src1_reg,
src2_reg,
);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::mul_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::mul_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
x => todo!("NumMul: layout, {:?}", x),
}
}
fn build_num_div(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) {
match layout {
Layout::Builtin(Builtin::Int(
IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8,
)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src1);
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src2);
ASM::idiv_reg64_reg64_reg64(
&mut self.buf,
&mut self.storage_manager,
dst_reg,
src1_reg,
src2_reg,
);
}
Layout::Builtin(Builtin::Int(
IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8,
)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src1);
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src2);
ASM::udiv_reg64_reg64_reg64(
&mut self.buf,
&mut self.storage_manager,
dst_reg,
src1_reg,
src2_reg,
);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::div_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::div_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
x => todo!("NumDiv: layout, {:?}", x),
}
}
fn build_num_neg(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>) {
match layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
@ -945,7 +1169,8 @@ impl<
let index_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, index);
let ret_stack_size = ret_layout.stack_size(self.storage_manager.target_info());
let ret_stack_size =
ret_layout.stack_size(self.env.layout_interner, self.storage_manager.target_info());
// TODO: This can be optimized with smarter instructions.
// Also can probably be moved into storage manager at least partly.
self.storage_manager.with_tmp_general_reg(
@ -987,7 +1212,8 @@ impl<
let elem_layout = arg_layouts[2];
let u32_layout = &Layout::Builtin(Builtin::Int(IntWidth::U32));
let list_alignment = list_layout.alignment_bytes(self.storage_manager.target_info());
let list_alignment = list_layout
.alignment_bytes(self.env.layout_interner, self.storage_manager.target_info());
self.load_literal(
&Symbol::DEV_TMP,
u32_layout,
@ -1006,7 +1232,8 @@ impl<
ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset);
// Load the elements size.
let elem_stack_size = elem_layout.stack_size(self.storage_manager.target_info());
let elem_stack_size =
elem_layout.stack_size(self.env.layout_interner, self.storage_manager.target_info());
self.load_literal(
&Symbol::DEV_TMP3,
u64_layout,
@ -1016,7 +1243,7 @@ impl<
// Setup the return location.
let base_offset = self.storage_manager.claim_stack_area(
dst,
ret_layout.stack_size(self.storage_manager.target_info()),
ret_layout.stack_size(self.env.layout_interner, self.storage_manager.target_info()),
);
let ret_fields = if let Layout::Struct { field_layouts, .. } = ret_layout {
@ -1033,13 +1260,19 @@ impl<
let (out_list_offset, out_elem_offset) = if ret_fields[0] == elem_layout {
(
base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32,
base_offset
+ ret_fields[0]
.stack_size(self.env.layout_interner, self.storage_manager.target_info())
as i32,
base_offset,
)
} else {
(
base_offset,
base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32,
base_offset
+ ret_fields[0]
.stack_size(self.env.layout_interner, self.storage_manager.target_info())
as i32,
)
};
@ -1120,10 +1353,15 @@ impl<
// This requires at least 8 for the refcount alignment.
let allocation_alignment = std::cmp::max(
8,
elem_layout.allocation_alignment_bytes(self.storage_manager.target_info()) as u64,
elem_layout.allocation_alignment_bytes(
self.env.layout_interner,
self.storage_manager.target_info(),
) as u64,
);
let elem_size = elem_layout.stack_size(self.storage_manager.target_info()) as u64;
let elem_size = elem_layout
.stack_size(self.env.layout_interner, self.storage_manager.target_info())
as u64;
let allocation_size = elem_size * elems.len() as u64 + allocation_alignment /* add space for refcount */;
let u64_layout = Layout::Builtin(Builtin::Int(IntWidth::U64));
self.load_literal(
@ -1383,6 +1621,66 @@ impl<
offset,
});
}
fn build_int_bitwise_and(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
int_width: IntWidth,
) {
let buf = &mut self.buf;
match int_width {
IntWidth::U128 | IntWidth::I128 => todo!(),
_ => {
let dst_reg = self.storage_manager.claim_general_reg(buf, dst);
let src1_reg = self.storage_manager.load_to_general_reg(buf, src1);
let src2_reg = self.storage_manager.load_to_general_reg(buf, src2);
ASM::and_reg64_reg64_reg64(buf, dst_reg, src1_reg, src2_reg);
}
}
}
fn build_int_bitwise_or(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
int_width: IntWidth,
) {
let buf = &mut self.buf;
match int_width {
IntWidth::U128 | IntWidth::I128 => todo!(),
_ => {
let dst_reg = self.storage_manager.claim_general_reg(buf, dst);
let src1_reg = self.storage_manager.load_to_general_reg(buf, src1);
let src2_reg = self.storage_manager.load_to_general_reg(buf, src2);
ASM::or_reg64_reg64_reg64(buf, dst_reg, src1_reg, src2_reg);
}
}
}
fn build_int_bitwise_xor(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
int_width: IntWidth,
) {
let buf = &mut self.buf;
match int_width {
IntWidth::U128 | IntWidth::I128 => todo!(),
_ => {
let dst_reg = self.storage_manager.claim_general_reg(buf, dst);
let src1_reg = self.storage_manager.load_to_general_reg(buf, src1);
let src2_reg = self.storage_manager.load_to_general_reg(buf, src2);
ASM::xor_reg64_reg64_reg64(buf, dst_reg, src1_reg, src2_reg);
}
}
}
}
/// This impl block is for ir related instructions that need backend specific information.

View file

@ -22,7 +22,7 @@ use StackStorage::*;
use Storage::*;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum RegStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
pub enum RegStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
General(GeneralReg),
Float(FloatReg),
}
@ -86,7 +86,7 @@ pub struct StorageManager<
> {
phantom_cc: PhantomData<CC>,
phantom_asm: PhantomData<ASM>,
env: &'a Env<'a>,
pub(crate) env: &'a Env<'a>,
target_info: TargetInfo,
// Data about where each symbol is stored.
symbol_storage_map: MutMap<Symbol, Storage<GeneralReg, FloatReg>>,
@ -541,12 +541,12 @@ impl<
let (base_offset, size) = (*base_offset, *size);
let mut data_offset = base_offset;
for layout in field_layouts.iter().take(index as usize) {
let field_size = layout.stack_size(self.target_info);
let field_size = layout.stack_size(self.env.layout_interner, self.target_info);
data_offset += field_size as i32;
}
debug_assert!(data_offset < base_offset + size as i32);
let layout = field_layouts[index as usize];
let size = layout.stack_size(self.target_info);
let size = layout.stack_size(self.env.layout_interner, self.target_info);
self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert(
*sym,
@ -591,8 +591,8 @@ impl<
UnionLayout::NonRecursive(_) => {
let (union_offset, _) = self.stack_offset_and_size(structure);
let (data_size, data_alignment) =
union_layout.data_size_and_alignment(self.target_info);
let (data_size, data_alignment) = union_layout
.data_size_and_alignment(self.env.layout_interner, self.target_info);
let id_offset = data_size - data_alignment;
let discriminant = union_layout.discriminant();
@ -635,7 +635,7 @@ impl<
layout: &Layout<'a>,
fields: &'a [Symbol],
) {
let struct_size = layout.stack_size(self.target_info);
let struct_size = layout.stack_size(self.env.layout_interner, self.target_info);
if struct_size == 0 {
self.symbol_storage_map.insert(*sym, NoData);
return;
@ -646,7 +646,8 @@ impl<
let mut current_offset = base_offset;
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout);
let field_size = field_layout.stack_size(self.target_info);
let field_size =
field_layout.stack_size(self.env.layout_interner, self.target_info);
current_offset += field_size as i32;
}
} else {
@ -667,8 +668,8 @@ impl<
) {
match union_layout {
UnionLayout::NonRecursive(field_layouts) => {
let (data_size, data_alignment) =
union_layout.data_size_and_alignment(self.target_info);
let (data_size, data_alignment) = union_layout
.data_size_and_alignment(self.env.layout_interner, self.target_info);
let id_offset = data_size - data_alignment;
if data_alignment < 8 || data_alignment % 8 != 0 {
todo!("small/unaligned tagging");
@ -679,7 +680,8 @@ impl<
fields.iter().zip(field_layouts[tag_id as usize].iter())
{
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout);
let field_size = field_layout.stack_size(self.target_info);
let field_size =
field_layout.stack_size(self.env.layout_interner, self.target_info);
current_offset += field_size as i32;
}
self.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| {
@ -733,16 +735,19 @@ impl<
let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, to_offset, reg);
}
_ if layout.stack_size(self.target_info) == 0 => {}
_ if layout.stack_size(self.env.layout_interner, self.target_info) == 0 => {}
// TODO: Verify this is always true.
// The dev backend does not deal with refcounting and does not care about if data is safe to memcpy.
// It is just temporarily storing the value due to needing to free registers.
// Later, it will be reloaded and stored in refcounted as needed.
_ if layout.stack_size(self.target_info) > 8 => {
_ if layout.stack_size(self.env.layout_interner, self.target_info) > 8 => {
let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert!(from_offset % 8 == 0);
debug_assert!(size % 8 == 0);
debug_assert_eq!(size, layout.stack_size(self.target_info));
debug_assert_eq!(
size,
layout.stack_size(self.env.layout_interner, self.target_info)
);
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, reg, from_offset + i);
@ -756,7 +761,7 @@ impl<
#[allow(dead_code)]
/// Ensures that a register is free. If it is not free, data will be moved to make it free.
fn ensure_reg_free(
pub fn ensure_reg_free(
&mut self,
buf: &mut Vec<'a, u8>,
wanted_reg: RegStorage<GeneralReg, FloatReg>,
@ -1016,7 +1021,7 @@ impl<
.insert(*symbol, Rc::new((base_offset, 8)));
}
_ => {
let stack_size = layout.stack_size(self.target_info);
let stack_size = layout.stack_size(self.env.layout_interner, self.target_info);
if stack_size == 0 {
self.symbol_storage_map.insert(*symbol, NoData);
} else {

View file

@ -7,7 +7,7 @@ use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout};
use roc_mono::layout::{Builtin, Layout, STLayoutInterner};
use roc_target::TargetInfo;
const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64();
@ -266,12 +266,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer.
let mut general_i = 0;
let mut float_i = 0;
if X86_64SystemV::returns_via_arg_pointer(ret_layout) {
if X86_64SystemV::returns_via_arg_pointer(storage_manager.env.layout_interner, ret_layout) {
storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[0]);
general_i += 1;
}
for (layout, sym) in args.iter() {
let stack_size = layout.stack_size(TARGET_INFO);
let stack_size = layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO);
match layout {
single_register_integers!() => {
if general_i < Self::GENERAL_PARAM_REGS.len() {
@ -324,10 +324,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut general_i = 0;
let mut float_i = 0;
if Self::returns_via_arg_pointer(ret_layout) {
if Self::returns_via_arg_pointer(storage_manager.env.layout_interner, ret_layout) {
// Save space on the stack for the result we will be return.
let base_offset =
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
let base_offset = storage_manager.claim_stack_area(
dst,
ret_layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO),
);
// Set the first reg to the address base + offset.
let ret_reg = Self::GENERAL_PARAM_REGS[general_i];
general_i += 1;
@ -386,8 +388,8 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
tmp_stack_offset += 8;
}
}
x if x.stack_size(TARGET_INFO) == 0 => {}
x if x.stack_size(TARGET_INFO) > 16 => {
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) > 16 => {
// TODO: Double check this.
// Just copy onto the stack.
// Use return reg as buffer because it will be empty right now.
@ -431,8 +433,8 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
single_register_layouts!() => {
internal_error!("single register layouts are not complex symbols");
}
x if x.stack_size(TARGET_INFO) == 0 => {}
x if !Self::returns_via_arg_pointer(x) => {
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
x if !Self::returns_via_arg_pointer(storage_manager.env.layout_interner, x) => {
let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
debug_assert_eq!(base_offset % 8, 0);
if size <= 8 {
@ -487,9 +489,9 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
single_register_layouts!() => {
internal_error!("single register layouts are not complex symbols");
}
x if x.stack_size(TARGET_INFO) == 0 => {}
x if !Self::returns_via_arg_pointer(x) => {
let size = layout.stack_size(TARGET_INFO);
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
x if !Self::returns_via_arg_pointer(storage_manager.env.layout_interner, x) => {
let size = layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO);
let offset = storage_manager.claim_stack_area(sym, size);
if size <= 8 {
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
@ -516,10 +518,13 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
}
impl X86_64SystemV {
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
fn returns_via_arg_pointer<'a>(
interner: &STLayoutInterner<'a>,
ret_layout: &Layout<'a>,
) -> bool {
// TODO: This will need to be more complex/extended to fully support the calling convention.
// details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
ret_layout.stack_size(TARGET_INFO) > 16
ret_layout.stack_size(interner, TARGET_INFO) > 16
}
}
@ -667,7 +672,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
) {
let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer.
let mut i = 0;
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout) {
if X86_64WindowsFastcall::returns_via_arg_pointer(
storage_manager.env.layout_interner,
ret_layout,
) {
storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[i]);
i += 1;
}
@ -682,7 +690,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[i]);
i += 1;
}
x if x.stack_size(TARGET_INFO) == 0 => {}
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
x => {
todo!("Loading args with layout {:?}", x);
}
@ -717,9 +725,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
ret_layout: &Layout<'a>,
) {
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
if Self::returns_via_arg_pointer(ret_layout) {
if Self::returns_via_arg_pointer(storage_manager.env.layout_interner, ret_layout) {
// Save space on the stack for the arg we will return.
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
storage_manager.claim_stack_area(
dst,
ret_layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO),
);
todo!("claim first parama reg for the address");
}
for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() {
@ -768,7 +779,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
tmp_stack_offset += 8;
}
}
x if x.stack_size(TARGET_INFO) == 0 => {}
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
x => {
todo!("calling with arg type, {:?}", x);
}
@ -809,10 +820,13 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
}
impl X86_64WindowsFastcall {
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
fn returns_via_arg_pointer<'a>(
interner: &STLayoutInterner<'a>,
ret_layout: &Layout<'a>,
) -> bool {
// TODO: This is not fully correct there are some exceptions for "vector" types.
// details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values
ret_layout.stack_size(TARGET_INFO) > 8
ret_layout.stack_size(interner, TARGET_INFO) > 8
}
}
@ -902,6 +916,22 @@ fn x86_64_generic_cleanup_stack<'a>(
X86_64Assembler::pop_reg64(buf, X86_64GeneralReg::RBP);
}
type Reg64 = X86_64GeneralReg;
fn binop_move_src_to_dst_reg64<F>(buf: &mut Vec<'_, u8>, f: F, dst: Reg64, src1: Reg64, src2: Reg64)
where
F: FnOnce(&mut Vec<'_, u8>, X86_64GeneralReg, X86_64GeneralReg),
{
if dst == src1 {
f(buf, dst, src2);
} else if dst == src2 {
f(buf, dst, src1);
} else {
mov_reg64_reg64(buf, dst, src1);
f(buf, dst, src2);
}
}
impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
// These functions should map to the raw assembly functions below.
// In some cases, that means you can just directly call one of the direct assembly functions.
@ -940,22 +970,12 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
mov_reg64_reg64(buf, dst, src1);
add_reg64_imm32(buf, dst, imm32);
}
#[inline(always)]
fn add_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) {
if dst == src1 {
add_reg64_reg64(buf, dst, src2);
} else if dst == src2 {
add_reg64_reg64(buf, dst, src1);
} else {
mov_reg64_reg64(buf, dst, src1);
add_reg64_reg64(buf, dst, src2);
}
fn add_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) {
binop_move_src_to_dst_reg64(buf, add_reg64_reg64, dst, src1, src2)
}
#[inline(always)]
fn add_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
@ -1009,6 +1029,130 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
imul_reg64_reg64(buf, dst, src2);
}
fn umul_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, X86_64GeneralReg, X86_64FloatReg, ASM, CC>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) where
ASM: Assembler<X86_64GeneralReg, X86_64FloatReg>,
CC: CallConv<X86_64GeneralReg, X86_64FloatReg, ASM>,
{
use crate::generic64::RegStorage;
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RAX));
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RDX));
mov_reg64_reg64(buf, X86_64GeneralReg::RAX, src1);
mul_reg64_reg64(buf, src2);
mov_reg64_reg64(buf, dst, X86_64GeneralReg::RAX);
}
fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
mulss_freg32_freg32(buf, dst, src2);
} else if dst == src2 {
mulss_freg32_freg32(buf, dst, src1);
} else {
movss_freg32_freg32(buf, dst, src1);
mulss_freg32_freg32(buf, dst, src2);
}
}
#[inline(always)]
fn mul_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
mulsd_freg64_freg64(buf, dst, src2);
} else if dst == src2 {
mulsd_freg64_freg64(buf, dst, src1);
} else {
movsd_freg64_freg64(buf, dst, src1);
mulsd_freg64_freg64(buf, dst, src2);
}
}
fn div_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
divss_freg32_freg32(buf, dst, src2);
} else if dst == src2 {
divss_freg32_freg32(buf, dst, src1);
} else {
movsd_freg64_freg64(buf, dst, src1);
divss_freg32_freg32(buf, dst, src2);
}
}
fn div_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
divsd_freg64_freg64(buf, dst, src2);
} else if dst == src2 {
divsd_freg64_freg64(buf, dst, src1);
} else {
movsd_freg64_freg64(buf, dst, src1);
divsd_freg64_freg64(buf, dst, src2);
}
}
fn idiv_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, X86_64GeneralReg, X86_64FloatReg, ASM, CC>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) where
ASM: Assembler<X86_64GeneralReg, X86_64FloatReg>,
CC: CallConv<X86_64GeneralReg, X86_64FloatReg, ASM>,
{
use crate::generic64::RegStorage;
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RAX));
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RDX));
mov_reg64_reg64(buf, X86_64GeneralReg::RAX, src1);
idiv_reg64_reg64(buf, src2);
mov_reg64_reg64(buf, dst, X86_64GeneralReg::RAX);
}
fn udiv_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, X86_64GeneralReg, X86_64FloatReg, ASM, CC>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) where
ASM: Assembler<X86_64GeneralReg, X86_64FloatReg>,
CC: CallConv<X86_64GeneralReg, X86_64FloatReg, ASM>,
{
use crate::generic64::RegStorage;
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RAX));
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RDX));
mov_reg64_reg64(buf, X86_64GeneralReg::RAX, src1);
udiv_reg64_reg64(buf, src2);
mov_reg64_reg64(buf, dst, X86_64GeneralReg::RAX);
}
#[inline(always)]
fn jmp_imm32(buf: &mut Vec<'_, u8>, offset: i32) -> usize {
jmp_imm32(buf, offset);
@ -1115,31 +1259,20 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
#[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("sign extending 4 byte values");
} else if size == 2 {
todo!("sign extending 2 byte values");
} else if size == 1 {
todo!("sign extending 1 byte values");
} else {
internal_error!("Invalid size for sign extension: {}", size);
match size {
8 => Self::mov_reg64_base32(buf, dst, offset),
4 | 2 | 1 => todo!("sign extending {size} byte values"),
_ => internal_error!("Invalid size for sign extension: {size}"),
}
}
#[inline(always)]
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
if size == 8 {
Self::mov_reg64_base32(buf, dst, offset);
} else if size == 4 {
todo!("zero extending 4 byte values");
} else if size == 2 {
todo!("zero extending 2 byte values");
} else if size == 1 {
movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset);
} else {
internal_error!("Invalid size for zero extension: {}", size);
match size {
8 => Self::mov_reg64_base32(buf, dst, offset),
4 | 2 => todo!("zero extending {size} byte values"),
1 => movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset),
_ => internal_error!("Invalid size for zero extension: {size}"),
}
}
@ -1266,6 +1399,22 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
fn ret(buf: &mut Vec<'_, u8>) {
ret(buf);
}
fn set_if_overflow(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) {
seto_reg64(buf, dst);
}
fn and_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) {
binop_move_src_to_dst_reg64(buf, and_reg64_reg64, dst, src1, src2)
}
fn or_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) {
binop_move_src_to_dst_reg64(buf, or_reg64_reg64, dst, src1, src2)
}
fn xor_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) {
binop_move_src_to_dst_reg64(buf, xor_reg64_reg64, dst, src1, src2)
}
}
impl X86_64Assembler {
@ -1280,12 +1429,25 @@ impl X86_64Assembler {
}
}
const REX: u8 = 0x40;
const REX_W: u8 = REX | 0x8;
// see https://wiki.osdev.org/X86-64_Instruction_Encoding#Encoding
/// If set, 64-bit operand size is used
const REX_PREFIX_W: u8 = 0b1000;
/// Extension to the MODRM.reg
const REX_PREFIX_R: u8 = 0b0100;
#[allow(unused)]
/// Extension to the SIB.index field
const REX_PREFIX_X: u8 = 0b0010;
/// Extension to the MODRM.rm
const REX_PREFIX_B: u8 = 0b0001;
/// Wide REX
const REX_W: u8 = REX | REX_PREFIX_W;
#[inline(always)]
fn add_rm_extension<T: RegTrait>(reg: T, byte: u8) -> u8 {
if reg.value() > 7 {
byte | 1
byte | REX_PREFIX_B
} else {
byte
}
@ -1299,7 +1461,7 @@ fn add_opcode_extension(reg: X86_64GeneralReg, byte: u8) -> u8 {
#[inline(always)]
fn add_reg_extension<T: RegTrait>(reg: T, byte: u8) -> u8 {
if reg.value() > 7 {
byte | 4
byte | REX_PREFIX_R
} else {
byte
}
@ -1356,6 +1518,27 @@ fn add_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene
binop_reg64_reg64(0x01, buf, dst, src);
}
/// `AND r/m64,r64` -> Bitwise logical and r64 to r/m64.
#[inline(always)]
fn and_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
// NOTE: src and dst are flipped by design
binop_reg64_reg64(0x23, buf, src, dst);
}
/// `OR r/m64,r64` -> Bitwise logical or r64 to r/m64.
#[inline(always)]
fn or_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
// NOTE: src and dst are flipped by design
binop_reg64_reg64(0x0B, buf, src, dst);
}
/// `XOR r/m64,r64` -> Bitwise logical exclusive or r64 to r/m64.
#[inline(always)]
fn xor_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
// NOTE: src and dst are flipped by design
binop_reg64_reg64(0x33, buf, src, dst);
}
/// `ADDSD xmm1,xmm2/m64` -> Add the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn addsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
@ -1396,6 +1579,86 @@ fn addss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64Fl
}
}
/// `MULSD xmm1,xmm2/m64` -> Multiply the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn mulsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend(&[
0xF2,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x59,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend(&[0xF2, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
/// `DIVSS xmm1,xmm2/m64` -> Divide the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn divss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend(&[
0xF3,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x5E,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend(&[0xF3, 0x0F, 0x5E, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
/// `DIVSD xmm1,xmm2/m64` -> Divide the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn divsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend(&[
0xF2,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x5E,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend(&[0xF2, 0x0F, 0x5E, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
/// `ADDSS xmm1,xmm2/m64` -> Add the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn mulss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend(&[
0xF3,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x59,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend(&[0xF3, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
#[inline(always)]
fn andpd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
@ -1465,6 +1728,60 @@ fn imul_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gen
extended_binop_reg64_reg64(0x0F, 0xAF, buf, src, dst);
}
/// `MUL r/m64` -> Unsigned Multiply r/m64 to r64.
#[inline(always)]
fn mul_reg64_reg64(buf: &mut Vec<'_, u8>, src: X86_64GeneralReg) {
let mut rex = REX_W;
rex = add_reg_extension(src, rex);
if src.value() > 7 {
rex |= REX_PREFIX_B;
}
buf.extend(&[rex, 0xF7, 0b1110_0000 | (src as u8 % 8)]);
}
/// `IDIV r/m64` -> Signed divide RDX:RAX by r/m64, with result stored in RAX ← Quotient, RDX ← Remainder.
#[inline(always)]
fn idiv_reg64_reg64(buf: &mut Vec<'_, u8>, src: X86_64GeneralReg) {
let mut rex = REX_W;
rex = add_reg_extension(src, rex);
if src.value() > 7 {
rex |= REX_PREFIX_B;
}
// The CQO instruction can be used to produce a double quadword dividend
// from a quadword before a quadword division.
//
// The CQO instruction (available in 64-bit mode only) copies the sign (bit 63)
// of the value in the RAX register into every bit position in the RDX register
buf.extend(&[0x48, 0x99]);
buf.extend(&[rex, 0xF7, 0b1111_1000 | (src as u8 % 8)]);
}
/// `DIV r/m64` -> Unsigned divide RDX:RAX by r/m64, with result stored in RAX ← Quotient, RDX ← Remainder.
#[inline(always)]
fn udiv_reg64_reg64(buf: &mut Vec<'_, u8>, src: X86_64GeneralReg) {
let mut rex = REX_W;
rex = add_reg_extension(src, rex);
if src.value() > 7 {
rex |= REX_PREFIX_B;
}
// The CQO instruction can be used to produce a double quadword dividend
// from a quadword before a quadword division.
//
// The CQO instruction (available in 64-bit mode only) copies the sign (bit 63)
// of the value in the RAX register into every bit position in the RDX register
buf.extend(&[0x48, 0x99]);
// adds a cqo (convert doubleword to quadword)
buf.extend(&[rex, 0xF7, 0b1111_0000 | (src as u8 % 8)]);
}
/// Jump near, relative, RIP = RIP + 32-bit displacement sign extended to 64-bits.
#[inline(always)]
fn jmp_imm32(buf: &mut Vec<'_, u8>, imm: i32) {
@ -1847,6 +2164,12 @@ fn setge_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(0x9d, buf, reg);
}
/// `SETO r/m64` -> Set byte if oveflow flag is set.
#[inline(always)]
fn seto_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(0x90, buf, reg);
}
/// `RET` -> Near return to calling procedure.
#[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) {
@ -1894,13 +2217,6 @@ fn push_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
}
}
/// `XOR r/m64,r64` -> Xor r64 to r/m64.
#[inline(always)]
#[allow(dead_code)]
fn xor_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
binop_reg64_reg64(0x31, buf, dst, src);
}
// When writing tests, it is a good idea to test both a number and unnumbered register.
// This is because R8-R15 often have special instruction prefixes.
#[cfg(test)]
@ -2046,11 +2362,31 @@ mod tests {
);
}
#[test]
fn test_and_reg64_reg64() {
disassembler_test!(
and_reg64_reg64,
|reg1, reg2| format!("and {reg1}, {reg2}"),
ALL_GENERAL_REGS,
ALL_GENERAL_REGS
);
}
#[test]
fn test_or_reg64_reg64() {
disassembler_test!(
or_reg64_reg64,
|reg1, reg2| format!("or {reg1}, {reg2}"),
ALL_GENERAL_REGS,
ALL_GENERAL_REGS
);
}
#[test]
fn test_xor_reg64_reg64() {
disassembler_test!(
xor_reg64_reg64,
|reg1, reg2| format!("xor {}, {}", reg1, reg2),
|reg1, reg2| format!("xor {reg1}, {reg2}"),
ALL_GENERAL_REGS,
ALL_GENERAL_REGS
);
@ -2086,6 +2422,73 @@ mod tests {
);
}
#[test]
fn test_mul_reg64_reg64() {
disassembler_test!(
mul_reg64_reg64,
|reg| format!("mul {}", reg),
ALL_GENERAL_REGS
);
}
#[test]
fn test_mulsd_freg64_freg64() {
disassembler_test!(
mulsd_freg64_freg64,
|reg1, reg2| format!("mulsd {}, {}", reg1, reg2),
ALL_FLOAT_REGS,
ALL_FLOAT_REGS
);
}
#[test]
fn test_mulss_freg32_freg32() {
disassembler_test!(
mulss_freg32_freg32,
|reg1, reg2| format!("mulss {}, {}", reg1, reg2),
ALL_FLOAT_REGS,
ALL_FLOAT_REGS
);
}
#[test]
fn test_idiv_reg64_reg64() {
disassembler_test!(
idiv_reg64_reg64,
|reg| format!("cqo\nidiv {}", reg),
ALL_GENERAL_REGS
);
}
#[test]
fn test_div_reg64_reg64() {
disassembler_test!(
udiv_reg64_reg64,
|reg| format!("cqo\ndiv {}", reg),
ALL_GENERAL_REGS
);
}
#[test]
fn test_divsd_freg64_freg64() {
disassembler_test!(
divsd_freg64_freg64,
|reg1, reg2| format!("divsd {}, {}", reg1, reg2),
ALL_FLOAT_REGS,
ALL_FLOAT_REGS
);
}
#[test]
fn test_divss_freg32_freg32() {
disassembler_test!(
divss_freg32_freg32,
|reg1, reg2| format!("divss {}, {}", reg1, reg2),
ALL_FLOAT_REGS,
ALL_FLOAT_REGS
);
}
#[test]
fn test_jmp_imm32() {
const INST_SIZE: i32 = 5;

View file

@ -14,7 +14,9 @@ use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
SelfRecursive, Stmt,
};
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, UnionLayout};
use roc_mono::layout::{
Builtin, Layout, LayoutId, LayoutIds, STLayoutInterner, TagIdIntType, UnionLayout,
};
mod generic64;
mod object_builder;
@ -23,6 +25,7 @@ mod run_roc;
pub struct Env<'a> {
pub arena: &'a Bump,
pub layout_interner: &'a STLayoutInterner<'a>,
pub module_id: ModuleId,
pub exposed_to_host: MutSet<Symbol>,
pub lazy_literals: bool,
@ -405,6 +408,9 @@ trait Backend<'a> {
);
self.build_num_add(sym, &args[0], &args[1], ret_layout)
}
LowLevel::NumAddChecked => {
self.build_num_add_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout)
}
LowLevel::NumAcos => self.build_fn_call(
sym,
bitcode::NUM_ACOS[FloatWidth::F64].to_string(),
@ -442,6 +448,22 @@ trait Backend<'a> {
);
self.build_num_mul(sym, &args[0], &args[1], ret_layout)
}
LowLevel::NumDivTruncUnchecked | LowLevel::NumDivFrac => {
debug_assert_eq!(
2,
args.len(),
"NumDiv: expected to have exactly two argument"
);
debug_assert_eq!(
arg_layouts[0], arg_layouts[1],
"NumDiv: expected all arguments of to have the same layout"
);
debug_assert_eq!(
arg_layouts[0], *ret_layout,
"NumDiv: expected to have the same argument and return layout"
);
self.build_num_div(sym, &args[0], &args[1], ret_layout)
}
LowLevel::NumNeg => {
debug_assert_eq!(
1,
@ -477,6 +499,27 @@ trait Backend<'a> {
);
self.build_num_sub(sym, &args[0], &args[1], ret_layout)
}
LowLevel::NumBitwiseAnd => {
if let Layout::Builtin(Builtin::Int(int_width)) = ret_layout {
self.build_int_bitwise_and(sym, &args[0], &args[1], *int_width)
} else {
internal_error!("bitwise and on a non-integer")
}
}
LowLevel::NumBitwiseOr => {
if let Layout::Builtin(Builtin::Int(int_width)) = ret_layout {
self.build_int_bitwise_or(sym, &args[0], &args[1], *int_width)
} else {
internal_error!("bitwise or on a non-integer")
}
}
LowLevel::NumBitwiseXor => {
if let Layout::Builtin(Builtin::Int(int_width)) = ret_layout {
self.build_int_bitwise_xor(sym, &args[0], &args[1], *int_width)
} else {
internal_error!("bitwise xor on a non-integer")
}
}
LowLevel::Eq => {
debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument");
debug_assert_eq!(
@ -678,6 +721,13 @@ trait Backend<'a> {
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
Symbol::NUM_ADD_CHECKED => {
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
_ => todo!("the function, {:?}", func_sym),
}
}
@ -699,15 +749,55 @@ trait Backend<'a> {
/// build_num_add stores the sum of src1 and src2 into dst.
fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
/// build_num_add_checked stores the sum of src1 and src2 into dst.
fn build_num_add_checked(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
num_layout: &Layout<'a>,
return_layout: &Layout<'a>,
);
/// build_num_mul stores `src1 * src2` into dst.
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
/// build_num_mul stores `src1 / src2` into dst.
fn build_num_div(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
/// build_num_neg stores the negated value of src into dst.
fn build_num_neg(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>);
/// build_num_sub stores the `src1 - src2` difference into dst.
fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
/// stores the `src1 & src2` into dst.
fn build_int_bitwise_and(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
int_width: IntWidth,
);
/// stores the `src1 | src2` into dst.
fn build_int_bitwise_or(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
int_width: IntWidth,
);
/// stores the `src1 ^ src2` into dst.
fn build_int_bitwise_xor(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
int_width: IntWidth,
);
/// build_eq stores the result of `src1 == src2` into dst.
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>);
@ -1004,7 +1094,8 @@ trait Backend<'a> {
}
}
Stmt::Expect { .. } => todo!("expect is not implemented in the wasm backend"),
Stmt::Expect { .. } => todo!("expect is not implemented in the dev backend"),
Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the dev backend"),
Stmt::RuntimeError(_) => {}
}