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 // actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed(); 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 the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit(); 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); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; 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 // actually call roc to populate the callresult
const callresult = roc__mainForHost_1_exposed(); 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 the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit(); 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); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; 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 {} pub struct AArch64Assembler {}
// AArch64Call may need to eventually be split by OS, // AArch64Call may need to eventually be split by OS,
// but I think with how we use it, they may all be the same. // but I think with how we use it, they may all be the same.
#[derive(Copy, Clone)]
pub struct AArch64Call {} pub struct AArch64Call {}
const STACK_ALIGNMENT: u8 = 16; const STACK_ALIGNMENT: u8 = 16;
@ -281,7 +283,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
AArch64Assembler, AArch64Assembler,
AArch64Call, AArch64Call,
>, >,
_args: &'a [Symbol], _dst: &Symbol,
_args: &[Symbol],
_arg_layouts: &[Layout<'a>], _arg_layouts: &[Layout<'a>],
_ret_layout: &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)] #[inline(always)]
fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) { fn mov_freg64_stack32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) {
todo!("loading floating point reg from stack for AArch64"); 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"); 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)] #[inline(always)]
fn gte_reg64_reg64_reg64( fn gte_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>, _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 bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt}; 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 roc_target::TargetInfo;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -16,8 +19,10 @@ pub(crate) mod x86_64;
use storage::StorageManager; 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>>: pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<GeneralReg, FloatReg>>:
Sized Sized + Copy
{ {
const BASE_PTR_REG: GeneralReg; const BASE_PTR_REG: GeneralReg;
const STACK_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>( fn store_args<'a>(
buf: &mut Vec<'a, u8>, buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>, storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, Self>,
args: &'a [Symbol], dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>], 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 is needed because if it is a complex type, we pass a pointer as the first arg.
ret_layout: &Layout<'a>, 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. /// 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`. /// 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. /// 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_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn abs_freg64_freg64( fn abs_freg64_freg64(
buf: &mut Vec<'_, u8>, 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_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); 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_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, 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); 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 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( fn gte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
dst: GeneralReg, dst: GeneralReg,
@ -256,7 +289,7 @@ pub struct Backend64Bit<
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
literal_map: MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)>, 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>, storage_manager: StorageManager<'a, GeneralReg, FloatReg, ASM, CC>,
} }
@ -328,7 +361,6 @@ impl<
self.join_map.clear(); self.join_map.clear();
self.free_map.clear(); self.free_map.clear();
self.buf.clear(); self.buf.clear();
self.helper_proc_symbols.clear();
self.storage_manager.reset(); self.storage_manager.reset();
} }
@ -462,7 +494,7 @@ impl<
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
fn_name: String, fn_name: String,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) { ) {
@ -479,6 +511,7 @@ impl<
CC::store_args( CC::store_args(
&mut self.buf, &mut self.buf,
&mut self.storage_manager, &mut self.storage_manager,
dst,
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,
@ -523,52 +556,56 @@ impl<
.storage_manager .storage_manager
.load_to_general_reg(&mut self.buf, cond_symbol); .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 ret_jumps = bumpalo::vec![in self.env.arena];
let mut tmp = 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(); tmp.clear();
if let BranchInfo::None = branch_info { // Create jump to next branch if cond_sym not equal to value.
// 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.
// Since we don't know the offset yet, set it to 0 and overwrite later. let jne_location = self.buf.len();
let jne_location = self.buf.len(); let start_offset = ASM::jne_reg64_imm64_imm32(&mut self.buf, cond_reg, *val, 0);
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.build_stmt(stmt, ret_layout); self.storage_manager = base_storage.clone();
// 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 {
self.build_stmt(stmt, ret_layout); self.build_stmt(stmt, ret_layout);
// Update all return jumps to jump past the default case. // Build unconditional jump to the end of this switch.
let ret_offset = self.buf.len(); // Since we don't know the offset yet, set it to 0 and overwrite later.
for (jmp_location, start_offset) in ret_jumps.into_iter() { let jmp_location = self.buf.len();
self.update_jmp_imm32_offset( let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
&mut tmp, ret_jumps.push((jmp_location, jmp_offset));
jmp_location as u64,
start_offset as u64, // Overwrite the original jne with the correct offset.
ret_offset as u64, 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>, remainder: &'a Stmt<'a>,
ret_layout: &Layout<'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. // 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. // On jumps to the joinpoint, we will overwrite those locations as a way to "pass parameters" to the joinpoint.
self.storage_manager self.storage_manager
.setup_joinpoint(&mut self.buf, id, parameters); .setup_joinpoint(&mut self.buf, id, parameters);
// Create jump to remaining. self.join_map.insert(*id, bumpalo::vec![in self.env.arena]);
let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); // 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. // Build all statements in body.
self.join_map.insert(*id, self.buf.len() as u64);
self.build_stmt(body, ret_layout); 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]; let mut tmp = bumpalo::vec![in self.env.arena];
self.update_jmp_imm32_offset( for (jmp_location, start_offset) in self
&mut tmp, .join_map
jmp_location as u64, .remove(id)
start_offset as u64, .unwrap_or_else(|| internal_error!("join point not defined"))
self.buf.len() as u64, {
); tmp.clear();
self.update_jmp_imm32_offset(&mut tmp, jmp_location, start_offset, join_location);
// Build remainder of function. }
self.build_stmt(remainder, ret_layout)
} }
fn build_jump( fn build_jump(
&mut self, &mut self,
id: &JoinPointId, id: &JoinPointId,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>, _ret_layout: &Layout<'a>,
) { ) {
@ -619,15 +661,8 @@ impl<
let jmp_location = self.buf.len(); let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
if let Some(offset) = self.join_map.get(id) { if let Some(vec) = self.join_map.get_mut(id) {
let offset = *offset; vec.push((jmp_location as u64, start_offset as u64))
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,
);
} else { } else {
internal_error!("Jump: unknown point specified to jump to: {:?}", id); 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>) { fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>) {
match arg_layout { 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 dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self let src1_reg = self
.storage_manager .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( fn build_num_gte(
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
@ -831,7 +888,7 @@ impl<
arg_layout: &Layout<'a>, arg_layout: &Layout<'a>,
) { ) {
match arg_layout { 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 dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self let src1_reg = self
.storage_manager .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) { 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 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); self.storage_manager
ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg); .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]) { 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); .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>) { fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>) {
match (lit, layout) { 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_export]
macro_rules! single_register_integers { macro_rules! single_register_integers {
() => { () => {
Layout::Builtin( Layout::Builtin(Builtin::Bool | single_register_int_builtins!()) | Layout::RecursivePointer
Builtin::Bool
| Builtin::Int(
IntWidth::I8
| IntWidth::I16
| IntWidth::I32
| IntWidth::I64
| IntWidth::U8
| IntWidth::U16
| IntWidth::U32
| IntWidth::U64,
),
) | Layout::RecursivePointer
}; };
} }

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
generic64::{Assembler, CallConv, RegTrait}, 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 bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -9,7 +10,7 @@ use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::{ use roc_mono::{
ir::{JoinPointId, Param}, ir::{JoinPointId, Param},
layout::{Builtin, Layout}, layout::{Builtin, Layout, TagIdIntType, UnionLayout},
}; };
use roc_target::TargetInfo; use roc_target::TargetInfo;
use std::cmp::max; use std::cmp::max;
@ -48,6 +49,9 @@ enum StackStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
base_offset: i32, base_offset: i32,
// Size on the stack in bytes. // Size on the stack in bytes.
size: u32, 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. /// Complex data (lists, unions, structs, str) stored on the stack.
/// Note, this is also used for referencing a value within a struct/union. /// Note, this is also used for referencing a value within a struct/union.
@ -72,6 +76,7 @@ enum Storage<GeneralReg: RegTrait, FloatReg: RegTrait> {
NoData, NoData,
} }
#[derive(Clone)]
pub struct StorageManager< pub struct StorageManager<
'a, 'a,
GeneralReg: RegTrait, GeneralReg: RegTrait,
@ -177,6 +182,10 @@ impl<
self.fn_call_stack_size = 0; self.fn_call_stack_size = 0;
} }
pub fn target_info(&self) -> TargetInfo {
self.target_info
}
pub fn stack_size(&self) -> u32 { pub fn stack_size(&self) -> u32 {
self.stack_size self.stack_size
} }
@ -323,20 +332,22 @@ impl<
); );
reg reg
} }
Stack(ReferencedPrimitive { base_offset, size }) Stack(ReferencedPrimitive {
if base_offset % 8 == 0 && size == 8 => base_offset,
{ size,
// The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack. sign_extend,
}) => {
let reg = self.get_general_reg(buf); 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.general_used_regs.push((reg, *sym));
self.symbol_storage_map.insert(*sym, Reg(General(reg))); self.symbol_storage_map.insert(*sym, Reg(General(reg)));
self.free_reference(sym); self.free_reference(sym);
reg reg
} }
Stack(ReferencedPrimitive { .. }) => {
todo!("loading referenced primitives")
}
Stack(Complex { .. }) => { Stack(Complex { .. }) => {
internal_error!("Cannot load large values into general registers: {}", sym) internal_error!("Cannot load large values into general registers: {}", sym)
} }
@ -385,9 +396,9 @@ impl<
); );
reg reg
} }
Stack(ReferencedPrimitive { base_offset, size }) Stack(ReferencedPrimitive {
if base_offset % 8 == 0 && size == 8 => 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. // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
let reg = self.get_float_reg(buf); let reg = self.get_float_reg(buf);
ASM::mov_freg64_base32(buf, reg, base_offset); ASM::mov_freg64_base32(buf, reg, base_offset);
@ -444,9 +455,9 @@ impl<
debug_assert_eq!(base_offset % 8, 0); debug_assert_eq!(base_offset % 8, 0);
ASM::mov_reg64_base32(buf, reg, *base_offset); ASM::mov_reg64_base32(buf, reg, *base_offset);
} }
Stack(ReferencedPrimitive { base_offset, size }) Stack(ReferencedPrimitive {
if base_offset % 8 == 0 && *size == 8 => 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. // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
ASM::mov_reg64_base32(buf, reg, *base_offset); ASM::mov_reg64_base32(buf, reg, *base_offset);
} }
@ -493,9 +504,9 @@ impl<
debug_assert_eq!(base_offset % 8, 0); debug_assert_eq!(base_offset % 8, 0);
ASM::mov_freg64_base32(buf, reg, *base_offset); ASM::mov_freg64_base32(buf, reg, *base_offset);
} }
Stack(ReferencedPrimitive { base_offset, size }) Stack(ReferencedPrimitive {
if base_offset % 8 == 0 && *size == 8 => 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. // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
ASM::mov_freg64_base32(buf, reg, *base_offset); ASM::mov_freg64_base32(buf, reg, *base_offset);
} }
@ -522,11 +533,7 @@ impl<
) { ) {
debug_assert!(index < field_layouts.len() as u64); debug_assert!(index < field_layouts.len() as u64);
// This must be removed and reinserted for ownership and mutability reasons. // This must be removed and reinserted for ownership and mutability reasons.
let owned_data = if let Some(owned_data) = self.allocation_map.remove(structure) { let owned_data = self.remove_allocation_for_sym(structure);
owned_data
} else {
internal_error!("Unknown symbol: {}", structure);
};
self.allocation_map self.allocation_map
.insert(*structure, Rc::clone(&owned_data)); .insert(*structure, Rc::clone(&owned_data));
match self.get_storage_for_sym(structure) { match self.get_storage_for_sym(structure) {
@ -538,15 +545,19 @@ impl<
data_offset += field_size as i32; data_offset += field_size as i32;
} }
debug_assert!(data_offset < base_offset + 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 layout = field_layouts[index as usize];
let size = layout.stack_size(self.target_info); let size = layout.stack_size(self.target_info);
self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert( self.symbol_storage_map.insert(
*sym, *sym,
Stack(if is_primitive(&layout) { Stack(if is_primitive(&layout) {
ReferencedPrimitive { ReferencedPrimitive {
base_offset: data_offset, base_offset: data_offset,
size, size,
sign_extend: matches!(
layout,
Layout::Builtin(sign_extended_int_builtins!())
),
} }
} else { } else {
Complex { 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. /// Creates a struct on the stack, moving the data in fields into the struct.
pub fn create_struct( pub fn create_struct(
&mut self, &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. /// 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. /// 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. /// This means that, for example 2 I32s might be back to back on the stack.
/// Always interact with the stack using aligned 64bit movement. /// Always interact with the stack using aligned 64bit movement.
fn copy_symbol_to_stack_offset( pub fn copy_symbol_to_stack_offset(
&mut self, &mut self,
buf: &mut Vec<'a, u8>, buf: &mut Vec<'a, u8>,
to_offset: i32, to_offset: i32,
@ -616,32 +733,33 @@ impl<
let reg = self.load_to_float_reg(buf, sym); let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, to_offset, reg); ASM::mov_base32_freg64(buf, to_offset, reg);
} }
// Layout::Struct(_) if layout.safe_to_memcpy() => { Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
// // self.storage_manager.with_tmp_float_reg(&mut self.buf, |buf, storage, ) let (from_offset, _) = self.stack_offset_and_size(sym);
// // if let Some(SymbolStorage::Base { self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
// // offset: from_offset, ASM::mov_reg64_base32(buf, reg, from_offset);
// // size, ASM::mov_base32_reg64(buf, to_offset, reg);
// // .. ASM::mov_reg64_base32(buf, reg, from_offset + 8);
// // }) = self.symbol_storage_map.get(sym) ASM::mov_base32_reg64(buf, to_offset + 8, reg);
// // { });
// // debug_assert_eq!( }
// // *size, _ if layout.stack_size(self.target_info) == 0 => {}
// // layout.stack_size(self.target_info), _ if layout.safe_to_memcpy() && layout.stack_size(self.target_info) > 8 => {
// // "expected struct to have same size as data being stored in it" let (from_offset, size) = self.stack_offset_and_size(sym);
// // ); debug_assert!(from_offset % 8 == 0);
// // for i in 0..layout.stack_size(self.target_info) as i32 { debug_assert!(size % 8 == 0);
// // ASM::mov_reg64_base32(&mut self.buf, tmp_reg, from_offset + i); debug_assert_eq!(size, layout.stack_size(self.target_info));
// // ASM::mov_base32_reg64(&mut self.buf, to_offset + i, tmp_reg); self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
// // } for i in (0..size as i32).step_by(8) {
// todo!() ASM::mov_reg64_base32(buf, reg, from_offset + i);
// } else { ASM::mov_base32_reg64(buf, to_offset + i, reg);
// internal_error!("unknown struct: {:?}", sym); }
// } });
// } }
x => todo!("copying data to the stack with layout, {:?}", x), 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. /// Ensures that a register is free. If it is not free, data will be moved to make it free.
fn ensure_reg_free( fn ensure_reg_free(
&mut self, &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. /// 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. /// Note, used and free regs are expected to be updated outside of this function.
fn free_to_stack( fn free_to_stack(
@ -739,9 +909,12 @@ impl<
pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) { pub fn stack_offset_and_size(&self, sym: &Symbol) -> (i32, u32) {
match self.get_storage_for_sym(sym) { match self.get_storage_for_sym(sym) {
Stack(Primitive { base_offset, .. }) => (*base_offset, 8), Stack(Primitive { base_offset, .. }) => (*base_offset, 8),
Stack(ReferencedPrimitive { base_offset, size } | Complex { base_offset, size }) => { Stack(
(*base_offset, *size) ReferencedPrimitive {
} base_offset, size, ..
}
| Complex { base_offset, size },
) => (*base_offset, *size),
storage => { storage => {
internal_error!( internal_error!(
"Data not on the stack for sym ({}) with storage ({:?})", "Data not on the stack for sym ({}) with storage ({:?})",
@ -775,12 +948,33 @@ impl<
reg: None, 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. /// Loads the arg pointer symbol to the specified general reg.
pub fn ret_pointer_arg(&mut self, reg: GeneralReg) { pub fn ret_pointer_arg(&mut self, reg: GeneralReg) {
self.symbol_storage_map self.symbol_storage_map
.insert(Symbol::RET_POINTER, Reg(General(reg))); .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. /// 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. /// Later jumps to the join point can overwrite the stored locations to pass parameters.
pub fn setup_joinpoint( pub fn setup_joinpoint(
&mut self, &mut self,
buf: &mut Vec<'a, u8>, _buf: &mut Vec<'a, u8>,
id: &JoinPointId, id: &JoinPointId,
params: &'a [Param<'a>], params: &'a [Param<'a>],
) { ) {
@ -812,12 +1006,19 @@ impl<
todo!("joinpoints with borrowed parameters"); todo!("joinpoints with borrowed parameters");
} }
// Claim a location for every join point parameter to be loaded at. // Claim a location for every join point parameter to be loaded at.
// Put everything on the stack for simplicity.
match layout { match layout {
single_register_integers!() => { single_register_layouts!() => {
self.claim_general_reg(buf, symbol); let base_offset = self.claim_stack_size(8);
} self.symbol_storage_map.insert(
single_register_floats!() => { *symbol,
self.claim_float_reg(buf, 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); let stack_size = layout.stack_size(self.target_info);
@ -839,7 +1040,7 @@ impl<
&mut self, &mut self,
buf: &mut Vec<'a, u8>, buf: &mut Vec<'a, u8>,
id: &JoinPointId, id: &JoinPointId,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
) { ) {
// TODO: remove was use here and for current_storage to deal with borrow checker. // TODO: remove was use here and for current_storage to deal with borrow checker.
@ -856,28 +1057,45 @@ impl<
continue; continue;
} }
match wanted_storage { match wanted_storage {
Reg(General(reg)) => { Reg(_) => {
// Ensure the reg is free, if not free it. internal_error!("Register storage is not allowed for jumping to joinpoint")
self.ensure_reg_free(buf, General(*reg));
// Copy the value over to the reg.
self.load_to_specified_general_reg(buf, sym, *reg)
} }
Reg(Float(reg)) => { Stack(Complex { base_offset, .. }) => {
// 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, .. }) => {
// TODO: This might be better not to call. // TODO: This might be better not to call.
// Maybe we want a more memcpy like method to directly get called here. // Maybe we want a more memcpy like method to directly get called here.
// That would also be capable of asserting the size. // That would also be capable of asserting the size.
// Maybe copy stack to stack or something. // Maybe copy stack to stack or something.
self.copy_symbol_to_stack_offset(buf, *base_offset, sym, layout); 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 => {} NoData => {}
Stack(Primitive { .. }) => { Stack(Primitive { reg: Some(_), .. }) => {
internal_error!("Primitive stack storage is not allowed for jumping") 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. /// Frees an reference and release an allocation if it is no longer used.
fn free_reference(&mut self, sym: &Symbol) { fn free_reference(&mut self, sym: &Symbol) {
let owned_data = if let Some(owned_data) = self.allocation_map.remove(sym) { let owned_data = self.remove_allocation_for_sym(sym);
owned_data
} else {
internal_error!("Unknown symbol: {:?}", sym);
};
if Rc::strong_count(&owned_data) == 1 { if Rc::strong_count(&owned_data) == 1 {
self.free_stack_chunk(owned_data.0, 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> { fn get_storage_for_sym(&self, sym: &Symbol) -> &Storage<GeneralReg, FloatReg> {
if let Some(storage) = self.symbol_storage_map.get(sym) { if let Some(storage) = self.symbol_storage_map.get(sym) {
storage storage

View file

@ -1,6 +1,7 @@
use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait}; use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
use crate::{ 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 bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -63,8 +64,11 @@ impl RegTrait for X86_64FloatReg {
} }
} }
#[derive(Copy, Clone)]
pub struct X86_64Assembler {} pub struct X86_64Assembler {}
#[derive(Copy, Clone)]
pub struct X86_64WindowsFastcall {} pub struct X86_64WindowsFastcall {}
#[derive(Copy, Clone)]
pub struct X86_64SystemV {} pub struct X86_64SystemV {}
const STACK_ALIGNMENT: u8 = 16; const STACK_ALIGNMENT: u8 = 16;
@ -215,6 +219,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
general_i += 1; general_i += 1;
} }
for (layout, sym) in args.iter() { for (layout, sym) in args.iter() {
let stack_size = layout.stack_size(TARGET_INFO);
match layout { match layout {
single_register_integers!() => { single_register_integers!() => {
if general_i < Self::GENERAL_PARAM_REGS.len() { 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"); 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 => { x => {
todo!("Loading args with layout {:?}", 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_64Assembler,
X86_64SystemV, X86_64SystemV,
>, >,
args: &'a [Symbol], dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) { ) {
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; 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 general_i = 0;
let mut float_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()) { for (sym, layout) in args.iter().zip(arg_layouts.iter()) {
match layout { match layout {
single_register_integers!() => { single_register_integers!() => {
@ -326,7 +347,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
tmp_stack_offset += 8; tmp_stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str | Builtin::List(_)) => {
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
let (base_offset, _size) = storage_manager.stack_offset_and_size(sym); let (base_offset, _size) = storage_manager.stack_offset_and_size(sym);
debug_assert_eq!(base_offset % 8, 0); 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) == 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 => { x => {
todo!("calling with arg type, {:?}", 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 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]); X86_64Assembler::mov_base32_reg64(buf, offset + 8, Self::GENERAL_RETURN_REGS[1]);
} }
x if x.stack_size(TARGET_INFO) == 0 => {} 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_64Assembler,
X86_64WindowsFastcall, X86_64WindowsFastcall,
>, >,
args: &'a [Symbol], dst: &Symbol,
args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) { ) {
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
if Self::returns_via_arg_pointer(ret_layout) { if Self::returns_via_arg_pointer(ret_layout) {
// Save space on the stack for the arg we will return. // Save space on the stack for the arg we will return.
storage_manager storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
.claim_stack_area(&Symbol::RET_POINTER, ret_layout.stack_size(TARGET_INFO));
todo!("claim first parama reg for the address"); todo!("claim first parama reg for the address");
} }
for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() { 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; 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. // I think this just needs to be passed on the stack, so not a huge deal.
todo!("Passing str args with Windows fast call"); 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) 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)] #[inline(always)]
fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) { fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RSP, offset) 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); 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)] #[inline(always)]
fn gte_reg64_reg64_reg64( fn gte_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -1378,6 +1530,27 @@ fn mov_reg64_base64_offset32(
buf.extend(&offset.to_le_bytes()); 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. /// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register.
#[inline(always)] #[inline(always)]
fn movsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { 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()); 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)] #[inline(always)]
fn movsd_base64_offset32_freg64( fn movsd_base64_offset32_freg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -1452,7 +1625,7 @@ fn movsd_base64_offset32_freg64(
buf.extend(&offset.to_le_bytes()); 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)] #[inline(always)]
fn movsd_freg64_base64_offset32( fn movsd_freg64_base64_offset32(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -1585,6 +1758,12 @@ fn setl_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(0x9c, buf, reg); 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). /// `SETGE r/m64` -> Set byte if greater or equal (SF=OF).
#[inline(always)] #[inline(always)]
fn setge_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) { 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] #[test]
fn test_mov_reg64_stack32() { fn test_mov_reg64_stack32() {
let arena = bumpalo::Bump::new(); let arena = bumpalo::Bump::new();

View file

@ -14,7 +14,7 @@ use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout, BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
SelfRecursive, Stmt, SelfRecursive, Stmt,
}; };
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds}; use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, UnionLayout};
mod generic64; mod generic64;
mod object_builder; mod object_builder;
@ -233,7 +233,7 @@ trait Backend<'a> {
fn build_jump( fn build_jump(
&mut self, &mut self,
id: &JoinPointId, id: &JoinPointId,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
); );
@ -277,13 +277,7 @@ trait Backend<'a> {
self.load_literal_symbols(arguments); self.load_literal_symbols(arguments);
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout) self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
} else { } else {
self.build_inline_builtin( self.build_builtin(sym, *func_sym, arguments, arg_layouts, ret_layout)
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); 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), 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) 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 => { LowLevel::NumGte => {
debug_assert_eq!( debug_assert_eq!(
2, 2,
@ -525,6 +559,30 @@ trait Backend<'a> {
arg_layouts, arg_layouts,
ret_layout, 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( LowLevel::StrConcat => self.build_fn_call(
sym, sym,
bitcode::STR_CONCAT.to_string(), 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 /// Builds a builtin functions that do not map directly to a low level
fn build_inline_builtin( /// If the builtin is simple enough, it will be inlined.
fn build_builtin(
&mut self, &mut self,
sym: &Symbol, sym: &Symbol,
func_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.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
self.free_symbol(&Symbol::DEV_TMP) 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), _ => todo!("the function, {:?}", func_sym),
} }
} }
@ -595,7 +662,7 @@ trait Backend<'a> {
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
fn_name: String, fn_name: String,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
); );
@ -633,6 +700,15 @@ trait Backend<'a> {
ret_layout: &Layout<'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. /// build_num_gte stores the result of `src1 >= src2` into dst.
fn build_num_gte( fn build_num_gte(
&mut self, &mut self,
@ -642,6 +718,27 @@ trait Backend<'a> {
arg_layout: &Layout<'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. /// build_refcount_getptr loads the pointer to the reference count of src into dst.
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol); fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol);
@ -677,6 +774,28 @@ trait Backend<'a> {
field_layouts: &'a [Layout<'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. /// 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>); fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>);
@ -831,15 +950,16 @@ trait Backend<'a> {
parameters, parameters,
body: continuation, body: continuation,
remainder, 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 { for param in *parameters {
self.set_last_seen(param.symbol, stmt); self.set_last_seen(param.symbol, stmt);
} }
self.scan_ast(continuation);
self.scan_ast(remainder); self.scan_ast(remainder);
self.scan_ast(continuation);
} }
Stmt::Jump(JoinPointId(sym), symbols) => { Stmt::Jump(JoinPointId(sym), symbols) => {
if let Some(parameters) = join_map.get(&JoinPointId(*sym)) { 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(param.symbol, stmt);
} }
} }
self.set_last_seen(*sym, stmt);
for sym in *symbols { for sym in *symbols {
self.set_last_seen(*sym, stmt); 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)); helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc));
continue; 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); internal_error!("failed to create rc fn for symbol {:?}", sym);
} }

View file

@ -884,6 +884,10 @@ define_builtins! {
// used in dev backend // used in dev backend
26 DEV_TMP: "#dev_tmp" 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" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias

View file

@ -19,7 +19,7 @@ use indoc::indoc;
use roc_std::{RocList, RocStr}; use roc_std::{RocList, RocStr};
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn roc_list_construction() { fn roc_list_construction() {
let list = RocList::from_slice(&[1i64; 23]); let list = RocList::from_slice(&[1i64; 23]);
assert_eq!(&list, &list); assert_eq!(&list, &list);

View file

@ -275,7 +275,7 @@ fn empty_record() {
); );
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn i64_record2_literal() { fn i64_record2_literal() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -302,7 +302,7 @@ fn i64_record2_literal() {
// ); // );
// } // }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn f64_record2_literal() { fn f64_record2_literal() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -404,7 +404,7 @@ fn bool_literal() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record() { fn return_record() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -655,7 +655,7 @@ fn optional_field_empty_record() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_2() { fn return_record_2() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -669,7 +669,7 @@ fn return_record_2() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_3() { fn return_record_3() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -683,7 +683,7 @@ fn return_record_3() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_4() { fn return_record_4() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -697,7 +697,7 @@ fn return_record_4() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_5() { fn return_record_5() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -711,7 +711,7 @@ fn return_record_5() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_6() { fn return_record_6() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -725,7 +725,7 @@ fn return_record_6() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_record_7() { fn return_record_7() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -795,7 +795,7 @@ fn return_record_float_float_float() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn return_nested_record() { fn return_nested_record() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(

View file

@ -1,8 +1,8 @@
#[cfg(feature = "gen-llvm")] #[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to; use crate::helpers::llvm::assert_evals_to;
// #[cfg(feature = "gen-dev")] #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to; use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-wasm")] #[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to; use crate::helpers::wasm::assert_evals_to;
@ -29,7 +29,7 @@ fn width_and_alignment_u8_u8() {
} }
#[test] #[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() { fn applied_tag_nothing_ir() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -49,7 +49,7 @@ fn applied_tag_nothing_ir() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn applied_tag_nothing() { fn applied_tag_nothing() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -69,7 +69,7 @@ fn applied_tag_nothing() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn applied_tag_just() { fn applied_tag_just() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -88,7 +88,7 @@ fn applied_tag_just() {
} }
#[test] #[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() { fn applied_tag_just_ir() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -314,7 +314,7 @@ fn gen_if_float() {
); );
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_nothing() { fn when_on_nothing() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -333,7 +333,7 @@ fn when_on_nothing() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_just() { fn when_on_just() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -352,7 +352,7 @@ fn when_on_just() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_result() { fn when_on_result() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -371,7 +371,7 @@ fn when_on_result() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn when_on_these() { fn when_on_these() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -393,7 +393,7 @@ fn when_on_these() {
} }
#[test] #[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() { fn match_on_two_values() {
// this will produce a Chain internally // this will produce a Chain internally
assert_evals_to!( assert_evals_to!(
@ -410,7 +410,7 @@ fn match_on_two_values() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn pair_with_underscore() { fn pair_with_underscore() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -427,7 +427,7 @@ fn pair_with_underscore() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn result_with_underscore() { fn result_with_underscore() {
// This test revealed an issue with hashing Test values // This test revealed an issue with hashing Test values
assert_evals_to!( assert_evals_to!(

View file

@ -79,12 +79,12 @@ pub export fn main() u8 {
const result = roc__mainForHost_1_exposed(10); const result = roc__mainForHost_1_exposed(10);
stdout.print("{d}\n", .{result}) catch unreachable;
// end time // end time
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; 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); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; 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 // actually call roc to populate the callresult
var callresult = roc__mainForHost_1_exposed(); 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 the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.deinit(); 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); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; 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); const length = std.math.min(20, callresult.length);
var result = callresult.elements[0..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| { for (result) |x, i| {
if (i == 0) { if (i == 0) {
stdout.print("[{}, ", .{x}) catch unreachable; 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); const delta = to_seconds(ts2) - to_seconds(ts1);
stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable;