mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
Merge pull request #2626 from rtfeldman/gen-dev/quicksort2
Gen dev - Quicksort
This commit is contained in:
commit
92ace8ff67
15 changed files with 1181 additions and 247 deletions
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue