mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
Merge pull request #2626 from rtfeldman/gen-dev/quicksort2
Gen dev - Quicksort
This commit is contained in:
commit
92ace8ff67
15 changed files with 1181 additions and 247 deletions
|
@ -66,10 +66,12 @@ impl RegTrait for AArch64FloatReg {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct AArch64Assembler {}
|
||||
|
||||
// AArch64Call may need to eventually be split by OS,
|
||||
// but I think with how we use it, they may all be the same.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct AArch64Call {}
|
||||
|
||||
const STACK_ALIGNMENT: u8 = 16;
|
||||
|
@ -281,7 +283,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
|
|||
AArch64Assembler,
|
||||
AArch64Call,
|
||||
>,
|
||||
_args: &'a [Symbol],
|
||||
_dst: &Symbol,
|
||||
_args: &[Symbol],
|
||||
_arg_layouts: &[Layout<'a>],
|
||||
_ret_layout: &Layout<'a>,
|
||||
) {
|
||||
|
@ -480,6 +483,70 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_reg64_mem64_offset32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: AArch64GeneralReg,
|
||||
src: AArch64GeneralReg,
|
||||
offset: i32,
|
||||
) {
|
||||
if offset < 0 {
|
||||
todo!("negative mem offsets for AArch64");
|
||||
} else if offset < (0xFFF << 8) {
|
||||
debug_assert!(offset % 8 == 0);
|
||||
ldr_reg64_imm12(buf, dst, src, (offset as u16) >> 3);
|
||||
} else {
|
||||
todo!("mem offsets over 32k for AArch64");
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn mov_mem64_offset32_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: AArch64GeneralReg,
|
||||
offset: i32,
|
||||
src: AArch64GeneralReg,
|
||||
) {
|
||||
if offset < 0 {
|
||||
todo!("negative mem offsets for AArch64");
|
||||
} else if offset < (0xFFF << 8) {
|
||||
debug_assert!(offset % 8 == 0);
|
||||
str_reg64_imm12(buf, src, dst, (offset as u16) >> 3);
|
||||
} else {
|
||||
todo!("mem offsets over 32k for AArch64");
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, 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);
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, 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 {
|
||||
todo!("zero extending 1 byte values");
|
||||
} else {
|
||||
internal_error!("Invalid size for zero extension: {}", size);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
|
||||
todo!("loading floating point reg from stack for AArch64");
|
||||
|
@ -606,6 +673,16 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
todo!("registers to float for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn lte_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
_dst: AArch64GeneralReg,
|
||||
_src1: AArch64GeneralReg,
|
||||
_src2: AArch64GeneralReg,
|
||||
) {
|
||||
todo!("registers less than or equal for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gte_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
use crate::{single_register_floats, single_register_integers, Backend, Env, Relocation};
|
||||
use crate::{
|
||||
single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env,
|
||||
Relocation,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::code_gen_help::CodeGenHelp;
|
||||
use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt};
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
use roc_mono::layout::{Builtin, Layout, TagIdIntType, UnionLayout};
|
||||
use roc_target::TargetInfo;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
|
@ -16,8 +19,10 @@ pub(crate) mod x86_64;
|
|||
|
||||
use storage::StorageManager;
|
||||
|
||||
// TODO: on all number functions double check and deal with over/underflow.
|
||||
|
||||
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<GeneralReg, FloatReg>>:
|
||||
Sized
|
||||
Sized + Copy
|
||||
{
|
||||
const BASE_PTR_REG: GeneralReg;
|
||||
const STACK_PTR_REG: GeneralReg;
|
||||
|
@ -72,7 +77,8 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
|
|||
fn store_args<'a>(
|
||||
buf: &mut Vec<'a, u8>,
|
||||
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>,
|
||||
args: &'a [Symbol],
|
||||
dst: &Symbol,
|
||||
args: &[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>,
|
||||
|
@ -103,7 +109,7 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
|
|||
/// Thus, some backends will need to use mulitiple instructions to preform a single one of this calls.
|
||||
/// Generally, I prefer explicit sources, as opposed to dst being one of the sources. Ex: `x = x + y` would be `add x, x, y` instead of `add x, y`.
|
||||
/// dst should always come before sources.
|
||||
pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized {
|
||||
pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
||||
fn abs_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
|
||||
fn abs_freg64_freg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
|
@ -167,6 +173,26 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized {
|
|||
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
|
||||
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
|
||||
|
||||
fn mov_reg64_mem64_offset32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src: GeneralReg,
|
||||
offset: i32,
|
||||
);
|
||||
fn mov_mem64_offset32_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
offset: i32,
|
||||
src: GeneralReg,
|
||||
);
|
||||
|
||||
/// Sign extends the data at `offset` with `size` as it copies it to `dst`
|
||||
/// size must be less than or equal to 8.
|
||||
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
|
||||
/// Zero extends the data at `offset` with `size` as it copies it to `dst`
|
||||
/// size must be less than or equal to 8.
|
||||
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, size: u8);
|
||||
|
||||
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
|
||||
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
|
||||
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
|
||||
|
@ -217,6 +243,13 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized {
|
|||
|
||||
fn to_float_freg64_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
|
||||
|
||||
fn lte_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src1: GeneralReg,
|
||||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn gte_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
|
@ -256,7 +289,7 @@ pub struct Backend64Bit<
|
|||
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
|
||||
|
||||
literal_map: MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)>,
|
||||
join_map: MutMap<JoinPointId, u64>,
|
||||
join_map: MutMap<JoinPointId, Vec<'a, (u64, u64)>>,
|
||||
|
||||
storage_manager: StorageManager<'a, GeneralReg, FloatReg, ASM, CC>,
|
||||
}
|
||||
|
@ -328,7 +361,6 @@ impl<
|
|||
self.join_map.clear();
|
||||
self.free_map.clear();
|
||||
self.buf.clear();
|
||||
self.helper_proc_symbols.clear();
|
||||
self.storage_manager.reset();
|
||||
}
|
||||
|
||||
|
@ -462,7 +494,7 @@ impl<
|
|||
&mut self,
|
||||
dst: &Symbol,
|
||||
fn_name: String,
|
||||
args: &'a [Symbol],
|
||||
args: &[Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) {
|
||||
|
@ -479,6 +511,7 @@ impl<
|
|||
CC::store_args(
|
||||
&mut self.buf,
|
||||
&mut self.storage_manager,
|
||||
dst,
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
|
@ -523,52 +556,56 @@ impl<
|
|||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, cond_symbol);
|
||||
|
||||
let mut base_storage = self.storage_manager.clone();
|
||||
let mut max_branch_stack_size = 0;
|
||||
let mut ret_jumps = bumpalo::vec![in self.env.arena];
|
||||
let mut tmp = bumpalo::vec![in self.env.arena];
|
||||
for (val, branch_info, stmt) in branches.iter() {
|
||||
for (val, _branch_info, stmt) in branches.iter() {
|
||||
// TODO: look into branch info and if it matters here.
|
||||
tmp.clear();
|
||||
if let BranchInfo::None = branch_info {
|
||||
// Create jump to next branch if not cond_sym not equal to value.
|
||||
// Since we don't know the offset yet, set it to 0 and overwrite later.
|
||||
let jne_location = self.buf.len();
|
||||
let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
|
||||
// Create jump to next branch if cond_sym not equal to value.
|
||||
// Since we don't know the offset yet, set it to 0 and overwrite later.
|
||||
let jne_location = self.buf.len();
|
||||
let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
|
||||
|
||||
// Build all statements in this branch.
|
||||
self.build_stmt(stmt, ret_layout);
|
||||
|
||||
// Build unconditional jump to the end of this switch.
|
||||
// Since we don't know the offset yet, set it to 0 and overwrite later.
|
||||
let jmp_location = self.buf.len();
|
||||
let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
|
||||
ret_jumps.push((jmp_location, jmp_offset));
|
||||
|
||||
// Overwrite the original jne with the correct offset.
|
||||
let end_offset = self.buf.len();
|
||||
let jne_offset = end_offset - start_offset;
|
||||
ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32);
|
||||
for (i, byte) in tmp.iter().enumerate() {
|
||||
self.buf[jne_location + i] = *byte;
|
||||
}
|
||||
} else {
|
||||
todo!("Switch: branch info, {:?}", branch_info);
|
||||
}
|
||||
}
|
||||
let (branch_info, stmt) = default_branch;
|
||||
if let BranchInfo::None = branch_info {
|
||||
// Build all statements in this branch. Using storage as from before any branch.
|
||||
self.storage_manager = base_storage.clone();
|
||||
self.build_stmt(stmt, ret_layout);
|
||||
|
||||
// Update all return jumps to jump past the default case.
|
||||
let ret_offset = self.buf.len();
|
||||
for (jmp_location, start_offset) in ret_jumps.into_iter() {
|
||||
self.update_jmp_imm32_offset(
|
||||
&mut tmp,
|
||||
jmp_location as u64,
|
||||
start_offset as u64,
|
||||
ret_offset as u64,
|
||||
);
|
||||
// Build unconditional jump to the end of this switch.
|
||||
// Since we don't know the offset yet, set it to 0 and overwrite later.
|
||||
let jmp_location = self.buf.len();
|
||||
let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
|
||||
ret_jumps.push((jmp_location, jmp_offset));
|
||||
|
||||
// Overwrite the original jne with the correct offset.
|
||||
let end_offset = self.buf.len();
|
||||
let jne_offset = end_offset - start_offset;
|
||||
ASM::jne_reg64_imm64_imm32(&mut tmp, cond_reg, *val, jne_offset as i32);
|
||||
for (i, byte) in tmp.iter().enumerate() {
|
||||
self.buf[jne_location + i] = *byte;
|
||||
}
|
||||
} else {
|
||||
todo!("Switch: branch info, {:?}", branch_info);
|
||||
|
||||
// Update important storage information to avoid overwrites.
|
||||
max_branch_stack_size =
|
||||
std::cmp::max(max_branch_stack_size, self.storage_manager.stack_size());
|
||||
base_storage.update_fn_call_stack_size(self.storage_manager.fn_call_stack_size());
|
||||
}
|
||||
self.storage_manager = base_storage;
|
||||
self.storage_manager
|
||||
.update_stack_size(max_branch_stack_size);
|
||||
let (_branch_info, stmt) = default_branch;
|
||||
self.build_stmt(stmt, ret_layout);
|
||||
|
||||
// Update all return jumps to jump past the default case.
|
||||
let ret_offset = self.buf.len();
|
||||
for (jmp_location, start_offset) in ret_jumps.into_iter() {
|
||||
self.update_jmp_imm32_offset(
|
||||
&mut tmp,
|
||||
jmp_location as u64,
|
||||
start_offset as u64,
|
||||
ret_offset as u64,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -580,36 +617,41 @@ impl<
|
|||
remainder: &'a Stmt<'a>,
|
||||
ret_layout: &Layout<'a>,
|
||||
) {
|
||||
// Free everything to the stack to make sure they don't get messed up when looping back to this point.
|
||||
// TODO: look into a nicer solution.
|
||||
self.storage_manager.free_all_to_stack(&mut self.buf);
|
||||
|
||||
// Ensure all the joinpoint parameters have storage locations.
|
||||
// On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint.
|
||||
self.storage_manager
|
||||
.setup_joinpoint(&mut self.buf, id, parameters);
|
||||
|
||||
// Create jump to remaining.
|
||||
let jmp_location = self.buf.len();
|
||||
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
|
||||
self.join_map.insert(*id, bumpalo::vec![in self.env.arena]);
|
||||
|
||||
// Build remainder of function first. It is what gets run and jumps to join.
|
||||
self.build_stmt(remainder, ret_layout);
|
||||
|
||||
let join_location = self.buf.len() as u64;
|
||||
|
||||
// Build all statements in body.
|
||||
self.join_map.insert(*id, self.buf.len() as u64);
|
||||
self.build_stmt(body, ret_layout);
|
||||
|
||||
// Overwrite the original jump with the correct offset.
|
||||
// Overwrite the all jumps to the joinpoint with the correct offset.
|
||||
let mut tmp = bumpalo::vec![in self.env.arena];
|
||||
self.update_jmp_imm32_offset(
|
||||
&mut tmp,
|
||||
jmp_location as u64,
|
||||
start_offset as u64,
|
||||
self.buf.len() as u64,
|
||||
);
|
||||
|
||||
// Build remainder of function.
|
||||
self.build_stmt(remainder, ret_layout)
|
||||
for (jmp_location, start_offset) in self
|
||||
.join_map
|
||||
.remove(id)
|
||||
.unwrap_or_else(|| internal_error!("join point not defined"))
|
||||
{
|
||||
tmp.clear();
|
||||
self.update_jmp_imm32_offset(&mut tmp, jmp_location, start_offset, join_location);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_jump(
|
||||
&mut self,
|
||||
id: &JoinPointId,
|
||||
args: &'a [Symbol],
|
||||
args: &[Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
_ret_layout: &Layout<'a>,
|
||||
) {
|
||||
|
@ -619,15 +661,8 @@ impl<
|
|||
let jmp_location = self.buf.len();
|
||||
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
|
||||
|
||||
if let Some(offset) = self.join_map.get(id) {
|
||||
let offset = *offset;
|
||||
let mut tmp = bumpalo::vec![in self.env.arena];
|
||||
self.update_jmp_imm32_offset(
|
||||
&mut tmp,
|
||||
jmp_location as u64,
|
||||
start_offset as u64,
|
||||
offset,
|
||||
);
|
||||
if let Some(vec) = self.join_map.get_mut(id) {
|
||||
vec.push((jmp_location as u64, start_offset as u64))
|
||||
} else {
|
||||
internal_error!("Jump: unknown point specified to jump to: {:?}", id);
|
||||
}
|
||||
|
@ -716,7 +751,7 @@ impl<
|
|||
|
||||
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) {
|
||||
match arg_layout {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
|
||||
Layout::Builtin(single_register_int_builtins!()) => {
|
||||
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
let src1_reg = self
|
||||
.storage_manager
|
||||
|
@ -823,6 +858,28 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
fn build_num_lte(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
arg_layout: &Layout<'a>,
|
||||
) {
|
||||
match arg_layout {
|
||||
Layout::Builtin(single_register_int_builtins!()) => {
|
||||
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::lte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
x => todo!("NumLte: layout, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_gte(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
|
@ -831,7 +888,7 @@ impl<
|
|||
arg_layout: &Layout<'a>,
|
||||
) {
|
||||
match arg_layout {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
|
||||
Layout::Builtin(single_register_int_builtins!()) => {
|
||||
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
let src1_reg = self
|
||||
.storage_manager
|
||||
|
@ -845,13 +902,173 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) {
|
||||
self.storage_manager.list_len(&mut self.buf, dst, list);
|
||||
}
|
||||
|
||||
fn build_list_get_unsafe(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
list: &Symbol,
|
||||
index: &Symbol,
|
||||
ret_layout: &Layout<'a>,
|
||||
) {
|
||||
let (base_offset, _) = self.storage_manager.stack_offset_and_size(list);
|
||||
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());
|
||||
// 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(
|
||||
&mut self.buf,
|
||||
|storage_manager, buf, list_ptr| {
|
||||
ASM::mov_reg64_base32(buf, list_ptr, base_offset as i32);
|
||||
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp| {
|
||||
ASM::mov_reg64_imm64(buf, tmp, ret_stack_size as i64);
|
||||
ASM::imul_reg64_reg64_reg64(buf, tmp, tmp, index_reg);
|
||||
ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr);
|
||||
match ret_layout {
|
||||
single_register_integers!() if ret_stack_size == 8 => {
|
||||
let dst_reg = storage_manager.claim_general_reg(buf, dst);
|
||||
ASM::mov_reg64_mem64_offset32(buf, dst_reg, tmp, 0);
|
||||
}
|
||||
x => internal_error!("Loading list element with layout: {:?}", x),
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn build_list_replace_unsafe(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
args: &'a [Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) {
|
||||
// We want to delegate to the zig builtin, but it takes some extra parameters.
|
||||
// Firstly, it takes the alignment of the list.
|
||||
// Secondly, it takes the stack size of an element.
|
||||
// Thirdly, it takes a pointer that it will write the output element to.
|
||||
let list = args[0];
|
||||
let list_layout = arg_layouts[0];
|
||||
let index = args[1];
|
||||
let index_layout = arg_layouts[1];
|
||||
let elem = args[2];
|
||||
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());
|
||||
self.load_literal(
|
||||
&Symbol::DEV_TMP,
|
||||
u32_layout,
|
||||
&Literal::Int(list_alignment as i128),
|
||||
);
|
||||
|
||||
// Have to pass the input element by pointer, so put it on the stack and load it's address.
|
||||
self.storage_manager
|
||||
.ensure_symbol_on_stack(&mut self.buf, &elem);
|
||||
let u64_layout = &Layout::Builtin(Builtin::Int(IntWidth::U64));
|
||||
let (new_elem_offset, _) = self.storage_manager.stack_offset_and_size(&elem);
|
||||
// Load address of output element into register.
|
||||
let reg = self
|
||||
.storage_manager
|
||||
.claim_general_reg(&mut self.buf, &Symbol::DEV_TMP2);
|
||||
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());
|
||||
self.load_literal(
|
||||
&Symbol::DEV_TMP3,
|
||||
u64_layout,
|
||||
&Literal::Int(elem_stack_size as i128),
|
||||
);
|
||||
|
||||
// Setup the return location.
|
||||
let base_offset = self.storage_manager.claim_stack_area(
|
||||
dst,
|
||||
ret_layout.stack_size(self.storage_manager.target_info()),
|
||||
);
|
||||
|
||||
let ret_fields = if let Layout::Struct { field_layouts, .. } = ret_layout {
|
||||
field_layouts
|
||||
} else {
|
||||
internal_error!(
|
||||
"Expected replace to return a struct instead found: {:?}",
|
||||
ret_layout
|
||||
)
|
||||
};
|
||||
|
||||
// Only return list and old element.
|
||||
debug_assert_eq!(ret_fields.len(), 2);
|
||||
|
||||
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,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
base_offset,
|
||||
base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32,
|
||||
)
|
||||
};
|
||||
|
||||
// Load address of output element into register.
|
||||
let reg = self
|
||||
.storage_manager
|
||||
.claim_general_reg(&mut self.buf, &Symbol::DEV_TMP4);
|
||||
ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, out_elem_offset);
|
||||
|
||||
let lowlevel_args = bumpalo::vec![
|
||||
in self.env.arena;
|
||||
list,
|
||||
Symbol::DEV_TMP,
|
||||
index,
|
||||
Symbol::DEV_TMP2,
|
||||
Symbol::DEV_TMP3,
|
||||
Symbol::DEV_TMP4,
|
||||
];
|
||||
let lowlevel_arg_layouts = bumpalo::vec![
|
||||
in self.env.arena;
|
||||
list_layout,
|
||||
*u32_layout,
|
||||
index_layout,
|
||||
*u64_layout,
|
||||
*u64_layout,
|
||||
*u64_layout,
|
||||
];
|
||||
|
||||
self.build_fn_call(
|
||||
&Symbol::DEV_TMP5,
|
||||
bitcode::LIST_REPLACE.to_string(),
|
||||
&lowlevel_args,
|
||||
&lowlevel_arg_layouts,
|
||||
&list_layout,
|
||||
);
|
||||
self.free_symbol(&Symbol::DEV_TMP);
|
||||
self.free_symbol(&Symbol::DEV_TMP2);
|
||||
self.free_symbol(&Symbol::DEV_TMP3);
|
||||
self.free_symbol(&Symbol::DEV_TMP4);
|
||||
|
||||
// Copy from list to the output record.
|
||||
self.storage_manager.copy_symbol_to_stack_offset(
|
||||
&mut self.buf,
|
||||
out_list_offset,
|
||||
&Symbol::DEV_TMP5,
|
||||
&list_layout,
|
||||
);
|
||||
|
||||
self.free_symbol(&Symbol::DEV_TMP5);
|
||||
}
|
||||
|
||||
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) {
|
||||
// We may not strictly need an instruction here.
|
||||
// What's important is to load the value, and for src and dest to have different Layouts.
|
||||
// This is used for pointer math in refcounting and for pointer equality
|
||||
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
|
||||
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg);
|
||||
self.storage_manager
|
||||
.ensure_symbol_on_stack(&mut self.buf, src);
|
||||
let (offset, _) = self.storage_manager.stack_offset_and_size(src);
|
||||
ASM::add_reg64_reg64_imm32(&mut self.buf, dst_reg, CC::BASE_PTR_REG, offset);
|
||||
}
|
||||
|
||||
fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) {
|
||||
|
@ -870,6 +1087,43 @@ impl<
|
|||
.load_field_at_index(sym, structure, index, field_layouts);
|
||||
}
|
||||
|
||||
fn load_union_at_index(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
structure: &Symbol,
|
||||
tag_id: TagIdIntType,
|
||||
index: u64,
|
||||
union_layout: &UnionLayout<'a>,
|
||||
) {
|
||||
match union_layout {
|
||||
UnionLayout::NonRecursive(tag_layouts) | UnionLayout::Recursive(tag_layouts) => {
|
||||
self.storage_manager.load_field_at_index(
|
||||
sym,
|
||||
structure,
|
||||
index,
|
||||
tag_layouts[tag_id as usize],
|
||||
);
|
||||
}
|
||||
x => todo!("loading from union type: {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) {
|
||||
self.storage_manager
|
||||
.load_union_tag_id(&mut self.buf, sym, structure, union_layout);
|
||||
}
|
||||
|
||||
fn tag(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
fields: &'a [Symbol],
|
||||
union_layout: &UnionLayout<'a>,
|
||||
tag_id: TagIdIntType,
|
||||
) {
|
||||
self.storage_manager
|
||||
.create_union(&mut self.buf, sym, union_layout, fields, tag_id)
|
||||
}
|
||||
|
||||
fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>) {
|
||||
match (lit, layout) {
|
||||
(
|
||||
|
@ -993,22 +1247,40 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! sign_extended_int_builtins {
|
||||
() => {
|
||||
Builtin::Int(IntWidth::I8 | IntWidth::I16 | IntWidth::I32 | IntWidth::I64 | IntWidth::I128)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! zero_extended_int_builtins {
|
||||
() => {
|
||||
Builtin::Int(IntWidth::U8 | IntWidth::U16 | IntWidth::U32 | IntWidth::U64 | IntWidth::U128)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! single_register_int_builtins {
|
||||
() => {
|
||||
Builtin::Int(
|
||||
IntWidth::I8
|
||||
| IntWidth::I16
|
||||
| IntWidth::I32
|
||||
| IntWidth::I64
|
||||
| IntWidth::U8
|
||||
| IntWidth::U16
|
||||
| IntWidth::U32
|
||||
| IntWidth::U64,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! single_register_integers {
|
||||
() => {
|
||||
Layout::Builtin(
|
||||
Builtin::Bool
|
||||
| Builtin::Int(
|
||||
IntWidth::I8
|
||||
| IntWidth::I16
|
||||
| IntWidth::I32
|
||||
| IntWidth::I64
|
||||
| IntWidth::U8
|
||||
| IntWidth::U16
|
||||
| IntWidth::U32
|
||||
| IntWidth::U64,
|
||||
),
|
||||
) | Layout::RecursivePointer
|
||||
Layout::Builtin(Builtin::Bool | single_register_int_builtins!()) | Layout::RecursivePointer
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
generic64::{Assembler, CallConv, RegTrait},
|
||||
single_register_floats, single_register_integers, single_register_layouts, Env,
|
||||
sign_extended_int_builtins, single_register_floats, single_register_int_builtins,
|
||||
single_register_integers, single_register_layouts, Env,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
|
@ -9,7 +10,7 @@ use roc_error_macros::internal_error;
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::{
|
||||
ir::{JoinPointId, Param},
|
||||
layout::{Builtin, Layout},
|
||||
layout::{Builtin, Layout, TagIdIntType, UnionLayout},
|
||||
};
|
||||
use roc_target::TargetInfo;
|
||||
use std::cmp::max;
|
||||
|
@ -48,6 +49,9 @@ enum StackStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
|||
base_offset: i32,
|
||||
// Size on the stack in bytes.
|
||||
size: u32,
|
||||
// Whether or not the data is need to be sign extended on load.
|
||||
// If not, it must be zero extended.
|
||||
sign_extend: bool,
|
||||
},
|
||||
/// Complex data (lists, unions, structs, str) stored on the stack.
|
||||
/// Note, this is also used for referencing a value within a struct/union.
|
||||
|
@ -72,6 +76,7 @@ enum Storage<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
|||
NoData,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StorageManager<
|
||||
'a,
|
||||
GeneralReg: RegTrait,
|
||||
|
@ -177,6 +182,10 @@ impl<
|
|||
self.fn_call_stack_size = 0;
|
||||
}
|
||||
|
||||
pub fn target_info(&self) -> TargetInfo {
|
||||
self.target_info
|
||||
}
|
||||
|
||||
pub fn stack_size(&self) -> u32 {
|
||||
self.stack_size
|
||||
}
|
||||
|
@ -323,20 +332,22 @@ impl<
|
|||
);
|
||||
reg
|
||||
}
|
||||
Stack(ReferencedPrimitive { base_offset, size })
|
||||
if base_offset % 8 == 0 && size == 8 =>
|
||||
{
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset,
|
||||
size,
|
||||
sign_extend,
|
||||
}) => {
|
||||
let reg = self.get_general_reg(buf);
|
||||
ASM::mov_reg64_base32(buf, reg, base_offset);
|
||||
if sign_extend {
|
||||
ASM::movsx_reg64_base32(buf, reg, base_offset, size as u8);
|
||||
} else {
|
||||
ASM::movzx_reg64_base32(buf, reg, base_offset, size as u8);
|
||||
}
|
||||
self.general_used_regs.push((reg, *sym));
|
||||
self.symbol_storage_map.insert(*sym, Reg(General(reg)));
|
||||
self.free_reference(sym);
|
||||
reg
|
||||
}
|
||||
Stack(ReferencedPrimitive { .. }) => {
|
||||
todo!("loading referenced primitives")
|
||||
}
|
||||
Stack(Complex { .. }) => {
|
||||
internal_error!("Cannot load large values into general registers: {}", sym)
|
||||
}
|
||||
|
@ -385,9 +396,9 @@ impl<
|
|||
);
|
||||
reg
|
||||
}
|
||||
Stack(ReferencedPrimitive { base_offset, size })
|
||||
if base_offset % 8 == 0 && size == 8 =>
|
||||
{
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}) if base_offset % 8 == 0 && size == 8 => {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
let reg = self.get_float_reg(buf);
|
||||
ASM::mov_freg64_base32(buf, reg, base_offset);
|
||||
|
@ -444,9 +455,9 @@ impl<
|
|||
debug_assert_eq!(base_offset % 8, 0);
|
||||
ASM::mov_reg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
Stack(ReferencedPrimitive { base_offset, size })
|
||||
if base_offset % 8 == 0 && *size == 8 =>
|
||||
{
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}) if base_offset % 8 == 0 && *size == 8 => {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
ASM::mov_reg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
|
@ -493,9 +504,9 @@ impl<
|
|||
debug_assert_eq!(base_offset % 8, 0);
|
||||
ASM::mov_freg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
Stack(ReferencedPrimitive { base_offset, size })
|
||||
if base_offset % 8 == 0 && *size == 8 =>
|
||||
{
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}) if base_offset % 8 == 0 && *size == 8 => {
|
||||
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
|
||||
ASM::mov_freg64_base32(buf, reg, *base_offset);
|
||||
}
|
||||
|
@ -522,11 +533,7 @@ impl<
|
|||
) {
|
||||
debug_assert!(index < field_layouts.len() as u64);
|
||||
// This must be removed and reinserted for ownership and mutability reasons.
|
||||
let owned_data = if let Some(owned_data) = self.allocation_map.remove(structure) {
|
||||
owned_data
|
||||
} else {
|
||||
internal_error!("Unknown symbol: {}", structure);
|
||||
};
|
||||
let owned_data = self.remove_allocation_for_sym(structure);
|
||||
self.allocation_map
|
||||
.insert(*structure, Rc::clone(&owned_data));
|
||||
match self.get_storage_for_sym(structure) {
|
||||
|
@ -538,15 +545,19 @@ impl<
|
|||
data_offset += field_size as i32;
|
||||
}
|
||||
debug_assert!(data_offset < base_offset + size as i32);
|
||||
self.allocation_map.insert(*sym, owned_data);
|
||||
let layout = field_layouts[index as usize];
|
||||
let size = layout.stack_size(self.target_info);
|
||||
self.allocation_map.insert(*sym, owned_data);
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
Stack(if is_primitive(&layout) {
|
||||
ReferencedPrimitive {
|
||||
base_offset: data_offset,
|
||||
size,
|
||||
sign_extend: matches!(
|
||||
layout,
|
||||
Layout::Builtin(sign_extended_int_builtins!())
|
||||
),
|
||||
}
|
||||
} else {
|
||||
Complex {
|
||||
|
@ -565,6 +576,57 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
pub fn load_union_tag_id(
|
||||
&mut self,
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
sym: &Symbol,
|
||||
structure: &Symbol,
|
||||
union_layout: &UnionLayout<'a>,
|
||||
) {
|
||||
// This must be removed and reinserted for ownership and mutability reasons.
|
||||
let owned_data = self.remove_allocation_for_sym(structure);
|
||||
self.allocation_map
|
||||
.insert(*structure, Rc::clone(&owned_data));
|
||||
match union_layout {
|
||||
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 id_offset = data_size - data_alignment;
|
||||
let id_builtin = union_layout.tag_id_builtin();
|
||||
|
||||
let size = id_builtin.stack_size(self.target_info);
|
||||
self.allocation_map.insert(*sym, owned_data);
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset: union_offset + id_offset as i32,
|
||||
size,
|
||||
sign_extend: matches!(id_builtin, sign_extended_int_builtins!()),
|
||||
}),
|
||||
);
|
||||
}
|
||||
x => todo!("getting tag id of union with layout ({:?})", x),
|
||||
}
|
||||
}
|
||||
|
||||
// Loads the dst to be the later 64 bits of a list (its length).
|
||||
pub fn list_len(&mut self, _buf: &mut Vec<'a, u8>, dst: &Symbol, list: &Symbol) {
|
||||
let owned_data = self.remove_allocation_for_sym(list);
|
||||
self.allocation_map.insert(*list, Rc::clone(&owned_data));
|
||||
self.allocation_map.insert(*dst, owned_data);
|
||||
let (list_offset, _) = self.stack_offset_and_size(list);
|
||||
self.symbol_storage_map.insert(
|
||||
*dst,
|
||||
Stack(ReferencedPrimitive {
|
||||
base_offset: list_offset + 8,
|
||||
size: 8,
|
||||
sign_extend: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a struct on the stack, moving the data in fields into the struct.
|
||||
pub fn create_struct(
|
||||
&mut self,
|
||||
|
@ -594,11 +656,66 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a union on the stack, moving the data in fields into the union and tagging it.
|
||||
pub fn create_union(
|
||||
&mut self,
|
||||
buf: &mut Vec<'a, u8>,
|
||||
sym: &Symbol,
|
||||
union_layout: &UnionLayout<'a>,
|
||||
fields: &'a [Symbol],
|
||||
tag_id: TagIdIntType,
|
||||
) {
|
||||
match union_layout {
|
||||
UnionLayout::NonRecursive(field_layouts) => {
|
||||
let (data_size, data_alignment) =
|
||||
union_layout.data_size_and_alignment(self.target_info);
|
||||
let id_offset = data_size - data_alignment;
|
||||
if data_alignment < 8 || data_alignment % 8 != 0 {
|
||||
todo!("small/unaligned tagging");
|
||||
}
|
||||
let base_offset = self.claim_stack_area(sym, data_size);
|
||||
let mut current_offset = base_offset;
|
||||
for (field, field_layout) in
|
||||
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);
|
||||
current_offset += field_size as i32;
|
||||
}
|
||||
self.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| {
|
||||
ASM::mov_reg64_imm64(buf, reg, tag_id as i64);
|
||||
debug_assert!((base_offset + id_offset as i32) % 8 == 0);
|
||||
ASM::mov_base32_reg64(buf, base_offset + id_offset as i32, reg);
|
||||
});
|
||||
}
|
||||
x => todo!("creating unions with layout: {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies a complex symbol on the stack to the arg pointer.
|
||||
pub fn copy_symbol_to_arg_pointer(
|
||||
&mut self,
|
||||
buf: &mut Vec<'a, u8>,
|
||||
sym: &Symbol,
|
||||
_layout: &Layout<'a>,
|
||||
) {
|
||||
let ret_reg = self.load_to_general_reg(buf, &Symbol::RET_POINTER);
|
||||
let (base_offset, size) = self.stack_offset_and_size(sym);
|
||||
debug_assert!(base_offset % 8 == 0);
|
||||
debug_assert!(size % 8 == 0);
|
||||
self.with_tmp_general_reg(buf, |_storage_manager, buf, tmp_reg| {
|
||||
for i in (0..size as i32).step_by(8) {
|
||||
ASM::mov_reg64_base32(buf, tmp_reg, base_offset + i);
|
||||
ASM::mov_mem64_offset32_reg64(buf, ret_reg, i, tmp_reg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Copies a symbol to the specified stack offset. This is used for things like filling structs.
|
||||
/// The offset is not guarenteed to be perfectly aligned, it follows Roc's alignment plan.
|
||||
/// This means that, for example 2 I32s might be back to back on the stack.
|
||||
/// Always interact with the stack using aligned 64bit movement.
|
||||
fn copy_symbol_to_stack_offset(
|
||||
pub fn copy_symbol_to_stack_offset(
|
||||
&mut self,
|
||||
buf: &mut Vec<'a, u8>,
|
||||
to_offset: i32,
|
||||
|
@ -616,32 +733,33 @@ impl<
|
|||
let reg = self.load_to_float_reg(buf, sym);
|
||||
ASM::mov_base32_freg64(buf, to_offset, reg);
|
||||
}
|
||||
// Layout::Struct(_) if layout.safe_to_memcpy() => {
|
||||
// // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, )
|
||||
// // if let Some(SymbolStorage::Base {
|
||||
// // offset: from_offset,
|
||||
// // size,
|
||||
// // ..
|
||||
// // }) = self.symbol_storage_map.get(sym)
|
||||
// // {
|
||||
// // debug_assert_eq!(
|
||||
// // *size,
|
||||
// // layout.stack_size(self.target_info),
|
||||
// // "expected struct to have same size as data being stored in it"
|
||||
// // );
|
||||
// // for i in 0..layout.stack_size(self.target_info) as i32 {
|
||||
// // ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i);
|
||||
// // ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg);
|
||||
// // }
|
||||
// todo!()
|
||||
// } else {
|
||||
// internal_error!("unknown struct: {:?}", sym);
|
||||
// }
|
||||
// }
|
||||
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
|
||||
let (from_offset, _) = self.stack_offset_and_size(sym);
|
||||
self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
|
||||
ASM::mov_reg64_base32(buf, reg, from_offset);
|
||||
ASM::mov_base32_reg64(buf, to_offset, reg);
|
||||
ASM::mov_reg64_base32(buf, reg, from_offset + 8);
|
||||
ASM::mov_base32_reg64(buf, to_offset + 8, reg);
|
||||
});
|
||||
}
|
||||
_ if layout.stack_size(self.target_info) == 0 => {}
|
||||
_ if layout.safe_to_memcpy() && layout.stack_size(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));
|
||||
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);
|
||||
ASM::mov_base32_reg64(buf, to_offset + i, reg);
|
||||
}
|
||||
});
|
||||
}
|
||||
x => todo!("copying data to the stack with layout, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
#[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(
|
||||
&mut self,
|
||||
|
@ -690,6 +808,58 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
pub fn ensure_symbol_on_stack(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) {
|
||||
match self.remove_storage_for_sym(sym) {
|
||||
Reg(reg_storage) => {
|
||||
let base_offset = self.claim_stack_size(8);
|
||||
match reg_storage {
|
||||
General(reg) => ASM::mov_base32_reg64(buf, base_offset, reg),
|
||||
Float(reg) => ASM::mov_base32_freg64(buf, base_offset, reg),
|
||||
}
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
Stack(Primitive {
|
||||
base_offset,
|
||||
reg: Some(reg_storage),
|
||||
}),
|
||||
);
|
||||
}
|
||||
x => {
|
||||
self.symbol_storage_map.insert(*sym, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees all symbols to the stack setuping up a clean slate.
|
||||
pub fn free_all_to_stack(&mut self, buf: &mut Vec<'a, u8>) {
|
||||
let mut free_list = bumpalo::vec![in self.env.arena];
|
||||
for (sym, storage) in self.symbol_storage_map.iter() {
|
||||
match storage {
|
||||
Reg(reg_storage)
|
||||
| Stack(Primitive {
|
||||
reg: Some(reg_storage),
|
||||
..
|
||||
}) => {
|
||||
free_list.push((*sym, *reg_storage));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for (sym, reg_storage) in free_list {
|
||||
match reg_storage {
|
||||
General(reg) => {
|
||||
self.general_free_regs.push(reg);
|
||||
self.general_used_regs.retain(|(r, _)| *r != reg);
|
||||
}
|
||||
Float(reg) => {
|
||||
self.float_free_regs.push(reg);
|
||||
self.float_used_regs.retain(|(r, _)| *r != reg);
|
||||
}
|
||||
}
|
||||
self.free_to_stack(buf, &sym, reg_storage);
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees `wanted_reg` which is currently owned by `sym` by making sure the value is loaded on the stack.
|
||||
/// Note, used and free regs are expected to be updated outside of this function.
|
||||
fn free_to_stack(
|
||||
|
@ -739,9 +909,12 @@ impl<
|
|||
pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) {
|
||||
match self.get_storage_for_sym(sym) {
|
||||
Stack(Primitive { base_offset, .. }) => (*base_offset, 8),
|
||||
Stack(ReferencedPrimitive { base_offset, size } | Complex { base_offset, size }) => {
|
||||
(*base_offset, *size)
|
||||
}
|
||||
Stack(
|
||||
ReferencedPrimitive {
|
||||
base_offset, size, ..
|
||||
}
|
||||
| Complex { base_offset, size },
|
||||
) => (*base_offset, *size),
|
||||
storage => {
|
||||
internal_error!(
|
||||
"Data not on the stack for sym ({}) with storage ({:?})",
|
||||
|
@ -775,12 +948,33 @@ impl<
|
|||
reg: None,
|
||||
}),
|
||||
);
|
||||
self.allocation_map.insert(*sym, Rc::new((base_offset, 8)));
|
||||
}
|
||||
|
||||
/// Specifies a complex is loaded at the specific base offset.
|
||||
pub fn complex_stack_arg(&mut self, sym: &Symbol, base_offset: i32, size: u32) {
|
||||
self.symbol_storage_map
|
||||
.insert(*sym, Stack(Complex { base_offset, size }));
|
||||
self.allocation_map
|
||||
.insert(*sym, Rc::new((base_offset, size)));
|
||||
}
|
||||
|
||||
/// Specifies a no data exists.
|
||||
pub fn no_data_arg(&mut self, sym: &Symbol) {
|
||||
self.symbol_storage_map.insert(*sym, NoData);
|
||||
}
|
||||
|
||||
/// Loads the arg pointer symbol to the specified general reg.
|
||||
pub fn ret_pointer_arg(&mut self, reg: GeneralReg) {
|
||||
self.symbol_storage_map
|
||||
.insert(Symbol::RET_POINTER, Reg(General(reg)));
|
||||
self.general_free_regs.retain(|x| *x != reg);
|
||||
self.general_used_regs.push((reg, Symbol::RET_POINTER));
|
||||
}
|
||||
|
||||
/// updates the stack size to the max of its current value and the tmp size needed.
|
||||
pub fn update_stack_size(&mut self, tmp_size: u32) {
|
||||
self.stack_size = max(self.stack_size, tmp_size);
|
||||
}
|
||||
|
||||
/// updates the function call stack size to the max of its current value and the size need for this call.
|
||||
|
@ -794,7 +988,7 @@ impl<
|
|||
/// Later jumps to the join point can overwrite the stored locations to pass parameters.
|
||||
pub fn setup_joinpoint(
|
||||
&mut self,
|
||||
buf: &mut Vec<'a, u8>,
|
||||
_buf: &mut Vec<'a, u8>,
|
||||
id: &JoinPointId,
|
||||
params: &'a [Param<'a>],
|
||||
) {
|
||||
|
@ -812,12 +1006,19 @@ impl<
|
|||
todo!("joinpoints with borrowed parameters");
|
||||
}
|
||||
// Claim a location for every join point parameter to be loaded at.
|
||||
// Put everything on the stack for simplicity.
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
self.claim_general_reg(buf, symbol);
|
||||
}
|
||||
single_register_floats!() => {
|
||||
self.claim_float_reg(buf, symbol);
|
||||
single_register_layouts!() => {
|
||||
let base_offset = self.claim_stack_size(8);
|
||||
self.symbol_storage_map.insert(
|
||||
*symbol,
|
||||
Stack(Primitive {
|
||||
base_offset,
|
||||
reg: None,
|
||||
}),
|
||||
);
|
||||
self.allocation_map
|
||||
.insert(*symbol, Rc::new((base_offset, 8)));
|
||||
}
|
||||
_ => {
|
||||
let stack_size = layout.stack_size(self.target_info);
|
||||
|
@ -839,7 +1040,7 @@ impl<
|
|||
&mut self,
|
||||
buf: &mut Vec<'a, u8>,
|
||||
id: &JoinPointId,
|
||||
args: &'a [Symbol],
|
||||
args: &[Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
) {
|
||||
// TODO: remove was use here and for current_storage to deal with borrow checker.
|
||||
|
@ -856,28 +1057,45 @@ impl<
|
|||
continue;
|
||||
}
|
||||
match wanted_storage {
|
||||
Reg(General(reg)) => {
|
||||
// Ensure the reg is free, if not free it.
|
||||
self.ensure_reg_free(buf, General(*reg));
|
||||
// Copy the value over to the reg.
|
||||
self.load_to_specified_general_reg(buf, sym, *reg)
|
||||
Reg(_) => {
|
||||
internal_error!("Register storage is not allowed for jumping to joinpoint")
|
||||
}
|
||||
Reg(Float(reg)) => {
|
||||
// Ensure the reg is free, if not free it.
|
||||
self.ensure_reg_free(buf, Float(*reg));
|
||||
// Copy the value over to the reg.
|
||||
self.load_to_specified_float_reg(buf, sym, *reg)
|
||||
}
|
||||
Stack(ReferencedPrimitive { base_offset, .. } | Complex { base_offset, .. }) => {
|
||||
Stack(Complex { base_offset, .. }) => {
|
||||
// TODO: This might be better not to call.
|
||||
// Maybe we want a more memcpy like method to directly get called here.
|
||||
// That would also be capable of asserting the size.
|
||||
// Maybe copy stack to stack or something.
|
||||
self.copy_symbol_to_stack_offset(buf, *base_offset, sym, layout);
|
||||
}
|
||||
Stack(Primitive {
|
||||
base_offset,
|
||||
reg: None,
|
||||
}) => match layout {
|
||||
single_register_integers!() => {
|
||||
let reg = self.load_to_general_reg(buf, sym);
|
||||
ASM::mov_base32_reg64(buf, *base_offset, reg);
|
||||
}
|
||||
single_register_floats!() => {
|
||||
let reg = self.load_to_float_reg(buf, sym);
|
||||
ASM::mov_base32_freg64(buf, *base_offset, reg);
|
||||
}
|
||||
_ => {
|
||||
internal_error!(
|
||||
"cannot load non-primitive layout ({:?}) to primitive stack location",
|
||||
layout
|
||||
);
|
||||
}
|
||||
},
|
||||
NoData => {}
|
||||
Stack(Primitive { .. }) => {
|
||||
internal_error!("Primitive stack storage is not allowed for jumping")
|
||||
Stack(Primitive { reg: Some(_), .. }) => {
|
||||
internal_error!(
|
||||
"primitives with register storage are not allowed for jumping to joinpoint"
|
||||
)
|
||||
}
|
||||
Stack(ReferencedPrimitive { .. }) => {
|
||||
internal_error!(
|
||||
"referenced primitive stack storage is not allowed for jumping to joinpoint"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -973,11 +1191,7 @@ impl<
|
|||
|
||||
/// Frees an reference and release an allocation if it is no longer used.
|
||||
fn free_reference(&mut self, sym: &Symbol) {
|
||||
let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) {
|
||||
owned_data
|
||||
} else {
|
||||
internal_error!("Unknown symbol: {:?}", sym);
|
||||
};
|
||||
let owned_data = self.remove_allocation_for_sym(sym);
|
||||
if Rc::strong_count(&owned_data) == 1 {
|
||||
self.free_stack_chunk(owned_data.0, owned_data.1);
|
||||
}
|
||||
|
@ -1060,7 +1274,26 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets a value from storage. They index symbol must be defined.
|
||||
#[allow(dead_code)]
|
||||
/// Gets the allocated area for a symbol. The index symbol must be defined.
|
||||
fn get_allocation_for_sym(&self, sym: &Symbol) -> &Rc<(i32, u32)> {
|
||||
if let Some(allocation) = self.allocation_map.get(sym) {
|
||||
allocation
|
||||
} else {
|
||||
internal_error!("Unknown symbol: {:?}", sym);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes and returns the allocated area for a symbol. They index symbol must be defined.
|
||||
fn remove_allocation_for_sym(&mut self, sym: &Symbol) -> Rc<(i32, u32)> {
|
||||
if let Some(allocation) = self.allocation_map.remove(sym) {
|
||||
allocation
|
||||
} else {
|
||||
internal_error!("Unknown symbol: {:?}", sym);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a value from storage. The index symbol must be defined.
|
||||
fn get_storage_for_sym(&self, sym: &Symbol) -> &Storage<GeneralReg, FloatReg> {
|
||||
if let Some(storage) = self.symbol_storage_map.get(sym) {
|
||||
storage
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
|
||||
use crate::{
|
||||
single_register_floats, single_register_integers, single_register_layouts, Relocation,
|
||||
single_register_floats, single_register_int_builtins, single_register_integers,
|
||||
single_register_layouts, Relocation,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
|
@ -63,8 +64,11 @@ impl RegTrait for X86_64FloatReg {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct X86_64Assembler {}
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct X86_64WindowsFastcall {}
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct X86_64SystemV {}
|
||||
|
||||
const STACK_ALIGNMENT: u8 = 16;
|
||||
|
@ -215,6 +219,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
general_i += 1;
|
||||
}
|
||||
for (layout, sym) in args.iter() {
|
||||
let stack_size = layout.stack_size(TARGET_INFO);
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
if general_i < Self::GENERAL_PARAM_REGS.len() {
|
||||
|
@ -247,7 +252,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
todo!("loading lists and strings args on the stack");
|
||||
}
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
_ if stack_size == 0 => {
|
||||
storage_manager.no_data_arg(sym);
|
||||
}
|
||||
_ if stack_size > 16 => {
|
||||
// TODO: Double check this.
|
||||
storage_manager.complex_stack_arg(sym, arg_offset, stack_size);
|
||||
arg_offset += stack_size as i32;
|
||||
}
|
||||
x => {
|
||||
todo!("Loading args with layout {:?}", x);
|
||||
}
|
||||
|
@ -265,19 +277,28 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
X86_64Assembler,
|
||||
X86_64SystemV,
|
||||
>,
|
||||
args: &'a [Symbol],
|
||||
dst: &Symbol,
|
||||
args: &[Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) {
|
||||
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
if Self::returns_via_arg_pointer(ret_layout) {
|
||||
// Save space on the stack for the arg we will return.
|
||||
storage_manager
|
||||
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
|
||||
todo!("claim first parama reg for the address");
|
||||
}
|
||||
let mut general_i = 0;
|
||||
let mut float_i = 0;
|
||||
if Self::returns_via_arg_pointer(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));
|
||||
// Set the first reg to the address base + offset.
|
||||
let ret_reg = Self::GENERAL_PARAM_REGS[general_i];
|
||||
general_i += 1;
|
||||
X86_64Assembler::add_reg64_reg64_imm32(
|
||||
buf,
|
||||
ret_reg,
|
||||
X86_64GeneralReg::RBP,
|
||||
base_offset,
|
||||
);
|
||||
}
|
||||
for (sym, layout) in args.iter().zip(arg_layouts.iter()) {
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
|
@ -326,7 +347,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
|
||||
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
|
||||
let (base_offset, _size) = storage_manager.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(base_offset % 8, 0);
|
||||
|
@ -346,6 +367,19 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
}
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x if x.stack_size(TARGET_INFO) > 16 => {
|
||||
// TODO: Double check this.
|
||||
// Just copy onto the stack.
|
||||
let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(base_offset % 8, 0);
|
||||
storage_manager.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
|
||||
for i in (0..size as i32).step_by(8) {
|
||||
X86_64Assembler::mov_reg64_base32(buf, reg, base_offset + i);
|
||||
X86_64Assembler::mov_stack32_reg64(buf, tmp_stack_offset + i, reg);
|
||||
}
|
||||
});
|
||||
tmp_stack_offset += size as i32;
|
||||
}
|
||||
x => {
|
||||
todo!("calling with arg type, {:?}", x);
|
||||
}
|
||||
|
@ -381,7 +415,42 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
);
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => todo!("returning complex type, {:?}", x),
|
||||
x if !Self::returns_via_arg_pointer(x) => {
|
||||
let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(base_offset % 8, 0);
|
||||
if size <= 8 {
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
base_offset,
|
||||
);
|
||||
} else if size <= 16 {
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
base_offset,
|
||||
);
|
||||
X86_64Assembler::mov_reg64_base32(
|
||||
buf,
|
||||
Self::GENERAL_RETURN_REGS[1],
|
||||
base_offset + 8,
|
||||
);
|
||||
} else {
|
||||
internal_error!(
|
||||
"types that don't return via arg pointer must be less than 16 bytes"
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// This is a large type returned via the arg pointer.
|
||||
storage_manager.copy_symbol_to_arg_pointer(buf, sym, layout);
|
||||
// Also set the return reg to the arg pointer.
|
||||
storage_manager.load_to_specified_general_reg(
|
||||
buf,
|
||||
&Symbol::RET_POINTER,
|
||||
Self::GENERAL_RETURN_REGS[0],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -407,7 +476,29 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]);
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x => todo!("receiving complex return type, {:?}", x),
|
||||
x if !Self::returns_via_arg_pointer(x) => {
|
||||
let size = layout.stack_size(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]);
|
||||
} else if size <= 16 {
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
|
||||
X86_64Assembler::mov_base32_reg64(
|
||||
buf,
|
||||
offset + 8,
|
||||
Self::GENERAL_RETURN_REGS[1],
|
||||
);
|
||||
} else {
|
||||
internal_error!(
|
||||
"types that don't return via arg pointer must be less than 16 bytes"
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// This should have been recieved via an arg pointer.
|
||||
// That means the value is already loaded onto the stack area we allocated before the call.
|
||||
// Nothing to do.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -612,15 +703,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
|
|||
X86_64Assembler,
|
||||
X86_64WindowsFastcall,
|
||||
>,
|
||||
args: &'a [Symbol],
|
||||
dst: &Symbol,
|
||||
args: &[Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
) {
|
||||
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
if Self::returns_via_arg_pointer(ret_layout) {
|
||||
// Save space on the stack for the arg we will return.
|
||||
storage_manager
|
||||
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
|
||||
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
|
||||
todo!("claim first parama reg for the address");
|
||||
}
|
||||
for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() {
|
||||
|
@ -669,7 +760,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
|
|||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
|
||||
// I think this just needs to be passed on the stack, so not a huge deal.
|
||||
todo!("Passing str args with Windows fast call");
|
||||
}
|
||||
|
@ -988,6 +1079,56 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
|||
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_reg64_mem64_offset32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64GeneralReg,
|
||||
src: X86_64GeneralReg,
|
||||
offset: i32,
|
||||
) {
|
||||
mov_reg64_base64_offset32(buf, dst, src, offset)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn mov_mem64_offset32_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64GeneralReg,
|
||||
offset: i32,
|
||||
src: X86_64GeneralReg,
|
||||
) {
|
||||
mov_base64_offset32_reg64(buf, dst, offset, src)
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
|
||||
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RSP, offset)
|
||||
|
@ -1091,6 +1232,17 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
|||
cvtsi2sd_freg64_reg64(buf, dst, src);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn lte_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64GeneralReg,
|
||||
src1: X86_64GeneralReg,
|
||||
src2: X86_64GeneralReg,
|
||||
) {
|
||||
cmp_reg64_reg64(buf, src1, src2);
|
||||
setle_reg64(buf, dst);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gte_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
|
@ -1378,6 +1530,27 @@ fn mov_reg64_base64_offset32(
|
|||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOVZX r64,r/m8` -> Move r/m8 with zero extention to r64, where m8 references a base + offset.
|
||||
#[inline(always)]
|
||||
fn movzx_reg64_base8_offset32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64GeneralReg,
|
||||
base: X86_64GeneralReg,
|
||||
offset: i32,
|
||||
) {
|
||||
let rex = add_rm_extension(base, REX_W);
|
||||
let rex = add_reg_extension(dst, rex);
|
||||
let dst_mod = (dst as u8 % 8) << 3;
|
||||
let base_mod = base as u8 % 8;
|
||||
buf.reserve(9);
|
||||
buf.extend(&[rex, 0x0F, 0xB6, 0x80 + dst_mod + base_mod]);
|
||||
// Using RSP or R12 requires a secondary index byte.
|
||||
if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
|
||||
buf.push(0x24);
|
||||
}
|
||||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register.
|
||||
#[inline(always)]
|
||||
fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
|
||||
|
@ -1429,7 +1602,7 @@ fn movsd_freg64_rip_offset32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset:
|
|||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOVSD r/m64,xmm1` -> Move xmm1 to r/m64. where m64 references the base pionter.
|
||||
/// `MOVSD r/m64,xmm1` -> Move xmm1 to r/m64. where m64 references the base pointer.
|
||||
#[inline(always)]
|
||||
fn movsd_base64_offset32_freg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
|
@ -1452,7 +1625,7 @@ fn movsd_base64_offset32_freg64(
|
|||
buf.extend(&offset.to_le_bytes());
|
||||
}
|
||||
|
||||
/// `MOVSD xmm1,r/m64` -> Move r/m64 to xmm1. where m64 references the base pionter.
|
||||
/// `MOVSD xmm1,r/m64` -> Move r/m64 to xmm1. where m64 references the base pointer.
|
||||
#[inline(always)]
|
||||
fn movsd_freg64_base64_offset32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
|
@ -1585,6 +1758,12 @@ fn setl_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
|||
set_reg64_help(0x9c, buf, reg);
|
||||
}
|
||||
|
||||
/// `SETLE r/m64` -> Set byte if less or equal (ZF=1 or SF≠ OF).
|
||||
#[inline(always)]
|
||||
fn setle_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
set_reg64_help(0x9e, buf, reg);
|
||||
}
|
||||
|
||||
/// `SETGE r/m64` -> Set byte if greater or equal (SF=OF).
|
||||
#[inline(always)]
|
||||
fn setge_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
|
@ -2081,6 +2260,35 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_movzx_reg64_base8_offset32() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src, offset), expected) in &[
|
||||
(
|
||||
(X86_64GeneralReg::RAX, X86_64GeneralReg::RBP, TEST_I32),
|
||||
vec![0x48, 0x0F, 0xB6, 0x85],
|
||||
),
|
||||
(
|
||||
(X86_64GeneralReg::R15, X86_64GeneralReg::RBP, TEST_I32),
|
||||
vec![0x4C, 0x0F, 0xB6, 0xBD],
|
||||
),
|
||||
(
|
||||
(X86_64GeneralReg::RAX, X86_64GeneralReg::RSP, TEST_I32),
|
||||
vec![0x48, 0x0F, 0xB6, 0x84, 0x24],
|
||||
),
|
||||
(
|
||||
(X86_64GeneralReg::R15, X86_64GeneralReg::RSP, TEST_I32),
|
||||
vec![0x4C, 0x0F, 0xB6, 0xBC, 0x24],
|
||||
),
|
||||
] {
|
||||
buf.clear();
|
||||
movzx_reg64_base8_offset32(&mut buf, *dst, *src, *offset);
|
||||
assert_eq!(expected, &buf[..expected.len()]);
|
||||
assert_eq!(TEST_I32.to_le_bytes(), &buf[expected.len()..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mov_reg64_stack32() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
|
|
|
@ -14,7 +14,7 @@ use roc_mono::ir::{
|
|||
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
|
||||
SelfRecursive, Stmt,
|
||||
};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, UnionLayout};
|
||||
|
||||
mod generic64;
|
||||
mod object_builder;
|
||||
|
@ -233,7 +233,7 @@ trait Backend<'a> {
|
|||
fn build_jump(
|
||||
&mut self,
|
||||
id: &JoinPointId,
|
||||
args: &'a [Symbol],
|
||||
args: &[Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
);
|
||||
|
@ -277,13 +277,7 @@ trait Backend<'a> {
|
|||
self.load_literal_symbols(arguments);
|
||||
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
|
||||
} else {
|
||||
self.build_inline_builtin(
|
||||
sym,
|
||||
*func_sym,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
)
|
||||
self.build_builtin(sym, *func_sym, arguments, arg_layouts, ret_layout)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,6 +315,29 @@ trait Backend<'a> {
|
|||
} => {
|
||||
self.load_struct_at_index(sym, structure, *index, field_layouts);
|
||||
}
|
||||
Expr::UnionAtIndex {
|
||||
structure,
|
||||
tag_id,
|
||||
union_layout,
|
||||
index,
|
||||
} => {
|
||||
self.load_union_at_index(sym, structure, *tag_id, *index, union_layout);
|
||||
}
|
||||
Expr::GetTagId {
|
||||
structure,
|
||||
union_layout,
|
||||
} => {
|
||||
self.get_tag_id(sym, structure, union_layout);
|
||||
}
|
||||
Expr::Tag {
|
||||
tag_layout,
|
||||
tag_id,
|
||||
arguments,
|
||||
..
|
||||
} => {
|
||||
self.load_literal_symbols(arguments);
|
||||
self.tag(sym, arguments, tag_layout, *tag_id);
|
||||
}
|
||||
x => todo!("the expression, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
@ -501,6 +518,23 @@ trait Backend<'a> {
|
|||
);
|
||||
self.build_num_to_float(sym, &args[0], &arg_layouts[0], ret_layout)
|
||||
}
|
||||
LowLevel::NumLte => {
|
||||
debug_assert_eq!(
|
||||
2,
|
||||
args.len(),
|
||||
"NumLte: expected to have exactly two argument"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
arg_layouts[0], arg_layouts[1],
|
||||
"NumLte: expected all arguments of to have the same layout"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
Layout::Builtin(Builtin::Bool),
|
||||
*ret_layout,
|
||||
"NumLte: expected to have return layout of type Bool"
|
||||
);
|
||||
self.build_num_lte(sym, &args[0], &args[1], &arg_layouts[0])
|
||||
}
|
||||
LowLevel::NumGte => {
|
||||
debug_assert_eq!(
|
||||
2,
|
||||
|
@ -525,6 +559,30 @@ trait Backend<'a> {
|
|||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::ListLen => {
|
||||
debug_assert_eq!(
|
||||
1,
|
||||
args.len(),
|
||||
"ListLen: expected to have exactly one argument"
|
||||
);
|
||||
self.build_list_len(sym, &args[0])
|
||||
}
|
||||
LowLevel::ListGetUnsafe => {
|
||||
debug_assert_eq!(
|
||||
2,
|
||||
args.len(),
|
||||
"ListGetUnsafe: expected to have exactly two arguments"
|
||||
);
|
||||
self.build_list_get_unsafe(sym, &args[0], &args[1], ret_layout)
|
||||
}
|
||||
LowLevel::ListReplaceUnsafe => {
|
||||
debug_assert_eq!(
|
||||
3,
|
||||
args.len(),
|
||||
"ListReplaceUnsafe: expected to have exactly three arguments"
|
||||
);
|
||||
self.build_list_replace_unsafe(sym, args, arg_layouts, ret_layout)
|
||||
}
|
||||
LowLevel::StrConcat => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::STR_CONCAT.to_string(),
|
||||
|
@ -558,8 +616,9 @@ trait Backend<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
// inlines simple builtin functions that do not map directly to a low level
|
||||
fn build_inline_builtin(
|
||||
/// Builds a builtin functions that do not map directly to a low level
|
||||
/// If the builtin is simple enough, it will be inlined.
|
||||
fn build_builtin(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
func_sym: Symbol,
|
||||
|
@ -585,6 +644,14 @@ trait Backend<'a> {
|
|||
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
|
||||
self.free_symbol(&Symbol::DEV_TMP)
|
||||
}
|
||||
Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE => {
|
||||
// TODO: This is probably simple enough to be worth inlining.
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
@ -595,7 +662,7 @@ trait Backend<'a> {
|
|||
&mut self,
|
||||
dst: &Symbol,
|
||||
fn_name: String,
|
||||
args: &'a [Symbol],
|
||||
args: &[Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
);
|
||||
|
@ -633,6 +700,15 @@ trait Backend<'a> {
|
|||
ret_layout: &Layout<'a>,
|
||||
);
|
||||
|
||||
/// build_num_lte stores the result of `src1 <= src2` into dst.
|
||||
fn build_num_lte(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
arg_layout: &Layout<'a>,
|
||||
);
|
||||
|
||||
/// build_num_gte stores the result of `src1 >= src2` into dst.
|
||||
fn build_num_gte(
|
||||
&mut self,
|
||||
|
@ -642,6 +718,27 @@ trait Backend<'a> {
|
|||
arg_layout: &Layout<'a>,
|
||||
);
|
||||
|
||||
/// build_list_len returns the length of a list.
|
||||
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);
|
||||
|
||||
/// build_list_get_unsafe loads the element from the list at the index.
|
||||
fn build_list_get_unsafe(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
list: &Symbol,
|
||||
index: &Symbol,
|
||||
ret_layout: &Layout<'a>,
|
||||
);
|
||||
|
||||
/// build_list_replace_unsafe returns the old element and new list with the list having the new element inserted.
|
||||
fn build_list_replace_unsafe(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
args: &'a [Symbol],
|
||||
arg_layouts: &[Layout<'a>],
|
||||
ret_layout: &Layout<'a>,
|
||||
);
|
||||
|
||||
/// build_refcount_getptr loads the pointer to the reference count of src into dst.
|
||||
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol);
|
||||
|
||||
|
@ -677,6 +774,28 @@ trait Backend<'a> {
|
|||
field_layouts: &'a [Layout<'a>],
|
||||
);
|
||||
|
||||
/// load_union_at_index loads into `sym` the value at `index` for `tag_id`.
|
||||
fn load_union_at_index(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
structure: &Symbol,
|
||||
tag_id: TagIdIntType,
|
||||
index: u64,
|
||||
union_layout: &UnionLayout<'a>,
|
||||
);
|
||||
|
||||
/// get_tag_id loads the tag id from a the union.
|
||||
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>);
|
||||
|
||||
/// tag sets the tag for a union.
|
||||
fn tag(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
args: &'a [Symbol],
|
||||
tag_layout: &UnionLayout<'a>,
|
||||
tag_id: TagIdIntType,
|
||||
);
|
||||
|
||||
/// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function.
|
||||
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>);
|
||||
|
||||
|
@ -831,15 +950,16 @@ trait Backend<'a> {
|
|||
parameters,
|
||||
body: continuation,
|
||||
remainder,
|
||||
id,
|
||||
id: JoinPointId(sym),
|
||||
..
|
||||
} => {
|
||||
join_map.insert(*id, parameters);
|
||||
self.set_last_seen(*sym, stmt);
|
||||
join_map.insert(JoinPointId(*sym), parameters);
|
||||
for param in *parameters {
|
||||
self.set_last_seen(param.symbol, stmt);
|
||||
}
|
||||
self.scan_ast(continuation);
|
||||
self.scan_ast(remainder);
|
||||
self.scan_ast(continuation);
|
||||
}
|
||||
Stmt::Jump(JoinPointId(sym), symbols) => {
|
||||
if let Some(parameters) = join_map.get(&JoinPointId(*sym)) {
|
||||
|
@ -848,7 +968,6 @@ trait Backend<'a> {
|
|||
self.set_last_seen(param.symbol, stmt);
|
||||
}
|
||||
}
|
||||
self.set_last_seen(*sym, stmt);
|
||||
for sym in *symbols {
|
||||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
|
|
|
@ -267,6 +267,27 @@ fn build_object<'a, B: Backend<'a>>(
|
|||
helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc));
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// The symbol isn't defined yet and will just be used by other rc procs.
|
||||
let section_id = output.add_section(
|
||||
output.segment_name(StandardSegment::Text).to_vec(),
|
||||
format!(".text.{:x}", sym.as_u64()).as_bytes().to_vec(),
|
||||
SectionKind::Text,
|
||||
);
|
||||
|
||||
let rc_symbol = Symbol {
|
||||
name: fn_name.as_bytes().to_vec(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
kind: SymbolKind::Text,
|
||||
scope: SymbolScope::Linkage,
|
||||
weak: false,
|
||||
section: SymbolSection::Section(section_id),
|
||||
flags: SymbolFlags::None,
|
||||
};
|
||||
let proc_id = output.add_symbol(rc_symbol);
|
||||
helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc));
|
||||
continue;
|
||||
}
|
||||
internal_error!("failed to create rc fn for symbol {:?}", sym);
|
||||
}
|
||||
|
|
|
@ -884,6 +884,10 @@ define_builtins! {
|
|||
|
||||
// used in dev backend
|
||||
26 DEV_TMP: "#dev_tmp"
|
||||
27 DEV_TMP2: "#dev_tmp2"
|
||||
28 DEV_TMP3: "#dev_tmp3"
|
||||
29 DEV_TMP4: "#dev_tmp4"
|
||||
30 DEV_TMP5: "#dev_tmp5"
|
||||
}
|
||||
1 NUM: "Num" => {
|
||||
0 NUM_NUM: "Num" imported // the Num.Num type alias
|
||||
|
|
|
@ -19,7 +19,7 @@ use indoc::indoc;
|
|||
use roc_std::{RocList, RocStr};
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn roc_list_construction() {
|
||||
let list = RocList::from_slice(&[1i64; 23]);
|
||||
assert_eq!(&list, &list);
|
||||
|
|
|
@ -275,7 +275,7 @@ fn empty_record() {
|
|||
);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn i64_record2_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -302,7 +302,7 @@ fn i64_record2_literal() {
|
|||
// );
|
||||
// }
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn f64_record2_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -404,7 +404,7 @@ fn bool_literal() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn return_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -655,7 +655,7 @@ fn optional_field_empty_record() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn return_record_2() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -669,7 +669,7 @@ fn return_record_2() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn return_record_3() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -683,7 +683,7 @@ fn return_record_3() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn return_record_4() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -697,7 +697,7 @@ fn return_record_4() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn return_record_5() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -711,7 +711,7 @@ fn return_record_5() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn return_record_6() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -725,7 +725,7 @@ fn return_record_6() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn return_record_7() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -795,7 +795,7 @@ fn return_record_float_float_float() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn return_nested_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#[cfg(feature = "gen-llvm")]
|
||||
use crate::helpers::llvm::assert_evals_to;
|
||||
|
||||
// #[cfg(feature = "gen-dev")]
|
||||
// use crate::helpers::dev::assert_evals_to;
|
||||
#[cfg(feature = "gen-dev")]
|
||||
use crate::helpers::dev::assert_evals_to;
|
||||
|
||||
#[cfg(feature = "gen-wasm")]
|
||||
use crate::helpers::wasm::assert_evals_to;
|
||||
|
@ -29,7 +29,7 @@ fn width_and_alignment_u8_u8() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn applied_tag_nothing_ir() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -49,7 +49,7 @@ fn applied_tag_nothing_ir() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn applied_tag_nothing() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -69,7 +69,7 @@ fn applied_tag_nothing() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn applied_tag_just() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -88,7 +88,7 @@ fn applied_tag_just() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn applied_tag_just_ir() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -314,7 +314,7 @@ fn gen_if_float() {
|
|||
);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn when_on_nothing() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -333,7 +333,7 @@ fn when_on_nothing() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn when_on_just() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -352,7 +352,7 @@ fn when_on_just() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn when_on_result() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -371,7 +371,7 @@ fn when_on_result() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn when_on_these() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -393,7 +393,7 @@ fn when_on_these() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn match_on_two_values() {
|
||||
// this will produce a Chain internally
|
||||
assert_evals_to!(
|
||||
|
@ -410,7 +410,7 @@ fn match_on_two_values() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn pair_with_underscore() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -427,7 +427,7 @@ fn pair_with_underscore() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn result_with_underscore() {
|
||||
// This test revealed an issue with hashing Test values
|
||||
assert_evals_to!(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue