Merge pull request #2626 from rtfeldman/gen-dev/quicksort2

Gen dev - Quicksort
This commit is contained in:
Brian Carroll 2022-03-07 17:46:46 +00:00 committed by GitHub
commit 92ace8ff67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1181 additions and 247 deletions

View file

@ -61,15 +61,15 @@ pub export fn main() i32 {
// actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -60,15 +60,15 @@ pub export fn main() i32 {
// actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -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>,

View file

@ -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,17 +556,20 @@ 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.
// 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.
// 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);
// Build unconditional jump to the end of this switch.
@ -549,12 +585,16 @@ impl<
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());
}
}
let (branch_info, stmt) = default_branch;
if let BranchInfo::None = branch_info {
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.
@ -567,9 +607,6 @@ impl<
ret_offset as u64,
);
}
} else {
todo!("Switch: branch info, {:?}", branch_info);
}
}
fn build_join(
@ -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) {
(
@ -994,11 +1248,23 @@ impl<
}
#[macro_export]
macro_rules! single_register_integers {
macro_rules! sign_extended_int_builtins {
() => {
Layout::Builtin(
Builtin::Bool
| Builtin::Int(
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
@ -1007,8 +1273,14 @@ macro_rules! single_register_integers {
| IntWidth::U16
| IntWidth::U32
| IntWidth::U64,
),
) | Layout::RecursivePointer
)
};
}
#[macro_export]
macro_rules! single_register_integers {
() => {
Layout::Builtin(Builtin::Bool | single_register_int_builtins!()) | Layout::RecursivePointer
};
}

View file

@ -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

View file

@ -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();

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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

View file

@ -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);

View file

@ -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!(

View file

@ -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!(

View file

@ -79,12 +79,12 @@ pub export fn main() u8 {
const result = roc__mainForHost_1_exposed(10);
stdout.print("{d}\n", .{result}) catch unreachable;
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
stdout.print("{d}\n", .{result}) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -92,15 +92,15 @@ pub fn main() u8 {
// actually call roc to populate the callresult
var callresult = roc__mainForHost_1_exposed();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit();
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;

View file

@ -112,6 +112,10 @@ pub export fn main() u8 {
const length = std.math.min(20, callresult.length);
var result = callresult.elements[0..length];
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
for (result) |x, i| {
if (i == 0) {
stdout.print("[{}, ", .{x}) catch unreachable;
@ -122,10 +126,6 @@ pub export fn main() u8 {
}
}
// end time
var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;