From cfe1d3d8938f6d962bfad97268d6e93a0b6ec430 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 20 Jul 2022 15:12:20 -0700 Subject: [PATCH 1/5] add empty array expr to gen_dev --- crates/compiler/gen_dev/src/generic64/mod.rs | 4 ++++ crates/compiler/gen_dev/src/generic64/storage.rs | 11 +++++++++++ crates/compiler/gen_dev/src/lib.rs | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 2fdd24c9a8..ab2847dbee 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1075,6 +1075,10 @@ impl< ASM::add_reg64_reg64_imm32(&mut self.buf, dst_reg, CC::BASE_PTR_REG, offset); } + fn create_empty_array(&mut self, sym: &Symbol) { + self.storage_manager.create_empty_array(&mut self.buf, sym); + } + fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { self.storage_manager .create_struct(&mut self.buf, sym, layout, fields); diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 6e07c2d4a8..a43c4a6557 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -627,6 +627,17 @@ impl< ); } + pub fn create_empty_array(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) { + let base_offset = self.claim_stack_area(sym, 24); + // TODO: This can be optimized. We just need to add the ability to set a stack value to zero or load an immediate to the stack. + self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { + ASM::mov_reg64_imm64(buf, reg, 0); + ASM::mov_reg64_base32(buf, reg, base_offset); + ASM::mov_reg64_base32(buf, reg, base_offset + 8); + ASM::mov_reg64_base32(buf, reg, base_offset + 16); + }); + } + /// Creates a struct on the stack, moving the data in fields into the struct. pub fn create_struct( &mut self, diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index eef12f4cf8..65c0c1a941 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -310,6 +310,9 @@ trait Backend<'a> { x => todo!("the call type, {:?}", x), } } + Expr::EmptyArray => { + self.create_empty_array(sym); + } Expr::Struct(fields) => { self.load_literal_symbols(fields); self.create_struct(sym, layout, fields); @@ -772,6 +775,9 @@ trait Backend<'a> { /// load_literal sets a symbol to be equal to a literal. fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>); + /// create_empyt_array creates an empyt array with nullptr, zero length, and zero capacity. + fn create_empty_array(&mut self, sym: &Symbol); + /// create_struct creates a struct with the elements specified loaded into it as data. fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]); From 8b672a3fcdd2dad714fe2ed044cd7d36241cf9f3 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 20 Jul 2022 16:45:04 -0700 Subject: [PATCH 2/5] enable empty list test and fix list init --- crates/compiler/gen_dev/src/generic64/storage.rs | 6 +++--- crates/compiler/test_gen/src/gen_list.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index a43c4a6557..063023d261 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -632,9 +632,9 @@ impl< // TODO: This can be optimized. We just need to add the ability to set a stack value to zero or load an immediate to the stack. self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { ASM::mov_reg64_imm64(buf, reg, 0); - ASM::mov_reg64_base32(buf, reg, base_offset); - ASM::mov_reg64_base32(buf, reg, base_offset + 8); - ASM::mov_reg64_base32(buf, reg, base_offset + 16); + ASM::mov_base32_reg64(buf, base_offset, reg); + ASM::mov_base32_reg64(buf, base_offset + 8, reg); + ASM::mov_base32_reg64(buf, base_offset + 16, reg); }); } diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index 24472ad76d..39b5c33ea5 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.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; @@ -25,7 +25,7 @@ fn roc_list_construction() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn empty_list_literal() { assert_evals_to!("[]", RocList::::from_slice(&[]), RocList); } From 35a452a69b387a2914ead09676fdf6b4957ec5e1 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 22 Jul 2022 18:33:11 -0700 Subject: [PATCH 3/5] setup partially functional array loading --- crates/compiler/gen_dev/src/generic64/mod.rs | 130 +++++++++++++++++- .../compiler/gen_dev/src/generic64/storage.rs | 11 -- crates/compiler/gen_dev/src/lib.rs | 23 +++- crates/compiler/test_gen/src/gen_list.rs | 8 +- 4 files changed, 154 insertions(+), 18 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index ab2847dbee..8744b3ab0d 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -8,7 +8,9 @@ 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::ir::{ + BranchInfo, JoinPointId, ListLiteralElement, Literal, Param, ProcLayout, SelfRecursive, Stmt, +}; use roc_mono::layout::{Builtin, Layout, TagIdIntType, UnionLayout}; use roc_target::TargetInfo; use std::marker::PhantomData; @@ -21,6 +23,7 @@ pub(crate) mod x86_64; use storage::StorageManager; +const REFCOUNT_ONE: u64 = i64::MIN as u64; // TODO: on all number functions double check and deal with over/underflow. pub trait CallConv>: @@ -1076,7 +1079,130 @@ impl< } fn create_empty_array(&mut self, sym: &Symbol) { - self.storage_manager.create_empty_array(&mut self.buf, sym); + let base_offset = self.storage_manager.claim_stack_area(sym, 24); + self.storage_manager + .with_tmp_general_reg(&mut self.buf, |_storage_manager, buf, reg| { + ASM::mov_reg64_imm64(buf, reg, 0); + ASM::mov_base32_reg64(buf, base_offset, reg); + ASM::mov_base32_reg64(buf, base_offset + 8, reg); + ASM::mov_base32_reg64(buf, base_offset + 16, reg); + }); + } + + fn create_array( + &mut self, + sym: &Symbol, + elem_layout: &Layout<'a>, + elems: &'a [ListLiteralElement<'a>], + ) { + // Allocate + // This requires at least 8 for the refcount alignment. + let allocation_alignment = std::cmp::max( + 8, + elem_layout.allocation_alignment_bytes(self.storage_manager.target_info()) as u64, + ); + + let elem_size = elem_layout.stack_size(self.storage_manager.target_info()) as u64; + let allocation_size = elem_size * elems.len() as u64 + allocation_alignment /* add space for refcount */; + let u32_layout = Layout::Builtin(Builtin::Int(IntWidth::U32)); + self.load_literal( + &Symbol::DEV_TMP, + &u32_layout, + &Literal::Int((allocation_alignment as i128).to_ne_bytes()), + ); + let u64_layout = Layout::Builtin(Builtin::Int(IntWidth::U64)); + self.load_literal( + &Symbol::DEV_TMP2, + &u64_layout, + &Literal::Int((allocation_size as i128).to_ne_bytes()), + ); + + self.build_fn_call( + &Symbol::DEV_TMP3, + "roc_alloc".to_string(), + &[Symbol::DEV_TMP, Symbol::DEV_TMP2], + &[u64_layout, u32_layout], + &u64_layout, + ); + self.free_symbol(&Symbol::DEV_TMP); + self.free_symbol(&Symbol::DEV_TMP2); + + // Fill pointer with elems + let ptr_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, &Symbol::DEV_TMP3); + // Point to first element of array. + ASM::add_reg64_reg64_imm32(&mut self.buf, ptr_reg, ptr_reg, allocation_alignment as i32); + + // fill refcount at -8. + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |_storage_manager, buf, tmp_reg| { + ASM::mov_reg64_imm64(buf, tmp_reg, REFCOUNT_ONE as i64); + ASM::mov_mem64_offset32_reg64(buf, ptr_reg, -8, tmp_reg); + }, + ); + + // Copy everything into output array. + let mut elem_offset = 0; + for elem in elems { + // TODO: this could be a lot faster when loading large lists + // if we move matching on the element layout to outside this loop. + // It also greatly bloats the code here. + // Refactor this and switch to one external match. + // We also could make loadining indivitual literals much faster + let elem_sym = match elem { + ListLiteralElement::Symbol(sym) => sym, + ListLiteralElement::Literal(lit) => { + self.load_literal(&Symbol::DEV_TMP, elem_layout, lit); + &Symbol::DEV_TMP + } + }; + // TODO: Expand to all types. + match elem_layout { + Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { + let sym_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, elem_sym); + ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, elem_offset, sym_reg); + } + _ if elem_size == 0 => {} + _ if elem_size > 8 => { + let (from_offset, size) = self.storage_manager.stack_offset_and_size(elem_sym); + debug_assert!(from_offset % 8 == 0); + debug_assert!(size % 8 == 0); + debug_assert_eq!(size as u64, elem_size); + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |_storage_manager, buf, tmp_reg| { + for i in (0..size as i32).step_by(8) { + ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i); + ASM::mov_mem64_offset32_reg64(buf, ptr_reg, elem_offset, tmp_reg); + } + }, + ); + } + x => todo!("copying data to list with layout, {:?}", x), + } + elem_offset += elem_size as i32; + if elem_sym == &Symbol::DEV_TMP { + self.free_symbol(elem_sym); + } + } + + // Setup list on stack. + self.storage_manager.with_tmp_general_reg( + &mut self.buf, + |storage_manager, buf, tmp_reg| { + let base_offset = storage_manager.claim_stack_area(sym, 24); + ASM::mov_base32_reg64(buf, base_offset, ptr_reg); + + ASM::mov_reg64_imm64(buf, tmp_reg, elems.len() as i64); + ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg); + ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg); + }, + ); + self.free_symbol(&Symbol::DEV_TMP3); } fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]) { diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 063023d261..6e07c2d4a8 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -627,17 +627,6 @@ impl< ); } - pub fn create_empty_array(&mut self, buf: &mut Vec<'a, u8>, sym: &Symbol) { - let base_offset = self.claim_stack_area(sym, 24); - // TODO: This can be optimized. We just need to add the ability to set a stack value to zero or load an immediate to the stack. - self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| { - ASM::mov_reg64_imm64(buf, reg, 0); - ASM::mov_base32_reg64(buf, base_offset, reg); - ASM::mov_base32_reg64(buf, base_offset + 8, reg); - ASM::mov_base32_reg64(buf, base_offset + 16, reg); - }); - } - /// Creates a struct on the stack, moving the data in fields into the struct. pub fn create_struct( &mut self, diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 65c0c1a941..4227df975c 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -313,6 +313,19 @@ trait Backend<'a> { Expr::EmptyArray => { self.create_empty_array(sym); } + Expr::Array { elem_layout, elems } => { + let mut syms = bumpalo::vec![in self.env().arena]; + for sym in elems.iter().filter_map(|x| match x { + ListLiteralElement::Symbol(sym) => Some(sym), + _ => None, + }) { + syms.push(*sym); + } + // TODO: This could be a huge waste. + // We probably want to call this within create_array, one element at a time. + self.load_literal_symbols(syms.into_bump_slice()); + self.create_array(sym, elem_layout, elems); + } Expr::Struct(fields) => { self.load_literal_symbols(fields); self.create_struct(sym, layout, fields); @@ -775,9 +788,17 @@ trait Backend<'a> { /// load_literal sets a symbol to be equal to a literal. fn load_literal(&mut self, sym: &Symbol, layout: &Layout<'a>, lit: &Literal<'a>); - /// create_empyt_array creates an empyt array with nullptr, zero length, and zero capacity. + /// create_empty_array creates an empty array with nullptr, zero length, and zero capacity. fn create_empty_array(&mut self, sym: &Symbol); + /// create_array creates an array filling it with the specified objects. + fn create_array( + &mut self, + sym: &Symbol, + elem_layout: &Layout<'a>, + elems: &'a [ListLiteralElement<'a>], + ); + /// create_struct creates a struct with the elements specified loaded into it as data. fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]); diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index 39b5c33ea5..2e865d3a30 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -31,19 +31,19 @@ fn empty_list_literal() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn list_literal_empty_record() { assert_evals_to!("[{}]", RocList::from_slice(&[()]), RocList<()>); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn int_singleton_list_literal() { assert_evals_to!("[1, 2]", RocList::from_slice(&[1, 2]), RocList); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn int_list_literal() { assert_evals_to!("[12, 9]", RocList::from_slice(&[12, 9]), RocList); assert_evals_to!( @@ -133,7 +133,7 @@ fn bool_list_literal() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn variously_sized_list_literals() { assert_evals_to!("[]", RocList::::from_slice(&[]), RocList); assert_evals_to!("[1]", RocList::from_slice(&[1]), RocList); From 29b5e11515a82e70077f8fdcff34379eab7eae01 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 22 Jul 2022 19:13:06 -0700 Subject: [PATCH 4/5] add all roc_ functions like roc_alloc instead of just builtins --- crates/compiler/gen_dev/src/object_builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/compiler/gen_dev/src/object_builder.rs b/crates/compiler/gen_dev/src/object_builder.rs index 73e353d37f..d5bf4aaa87 100644 --- a/crates/compiler/gen_dev/src/object_builder.rs +++ b/crates/compiler/gen_dev/src/object_builder.rs @@ -420,8 +420,8 @@ fn build_proc<'a, B: Backend<'a>>( } } Relocation::LinkedFunction { offset, name } => { - // If the symbol is an undefined zig builtin, we need to add it here. - if output.symbol_id(name.as_bytes()) == None && name.starts_with("roc_builtins.") { + // If the symbol is an undefined roc function, we need to add it here. + if output.symbol_id(name.as_bytes()) == None && name.starts_with("roc_") { let builtin_symbol = Symbol { name: name.as_bytes().to_vec(), value: 0, From 4077edcaec1cee75fe297a4e166dca5d6da0b8cd Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 22 Jul 2022 20:55:38 -0700 Subject: [PATCH 5/5] fix roc_alloc arg order --- crates/compiler/gen_dev/src/generic64/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 8744b3ab0d..2cfc01fd5d 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1104,18 +1104,18 @@ impl< let elem_size = elem_layout.stack_size(self.storage_manager.target_info()) as u64; let allocation_size = elem_size * elems.len() as u64 + allocation_alignment /* add space for refcount */; - let u32_layout = Layout::Builtin(Builtin::Int(IntWidth::U32)); - self.load_literal( - &Symbol::DEV_TMP, - &u32_layout, - &Literal::Int((allocation_alignment as i128).to_ne_bytes()), - ); let u64_layout = Layout::Builtin(Builtin::Int(IntWidth::U64)); self.load_literal( - &Symbol::DEV_TMP2, + &Symbol::DEV_TMP, &u64_layout, &Literal::Int((allocation_size as i128).to_ne_bytes()), ); + let u32_layout = Layout::Builtin(Builtin::Int(IntWidth::U32)); + self.load_literal( + &Symbol::DEV_TMP2, + &u32_layout, + &Literal::Int((allocation_alignment as i128).to_ne_bytes()), + ); self.build_fn_call( &Symbol::DEV_TMP3,