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