first attempt at List.replace in the dev backend

This commit is contained in:
Brendan Hansknecht 2022-02-27 22:02:47 -08:00
parent a5ce124bd3
commit 069361a07e
6 changed files with 180 additions and 19 deletions

View file

@ -282,7 +282,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg, AArch64Assembler> for AArch64C
AArch64Call, AArch64Call,
>, >,
_dst: &Symbol, _dst: &Symbol,
_args: &'a [Symbol], _args: &[Symbol],
_arg_layouts: &[Layout<'a>], _arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>, _ret_layout: &Layout<'a>,
) { ) {

View file

@ -3,7 +3,7 @@ use crate::{
Relocation, 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};
@ -78,7 +78,7 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait, ASM: Assembler<Gene
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>,
dst: &Symbol, dst: &Symbol,
args: &'a [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>,
@ -495,7 +495,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>,
) { ) {
@ -636,7 +636,7 @@ impl<
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>,
) { ) {
@ -897,6 +897,7 @@ impl<
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) { fn build_list_len(&mut self, dst: &Symbol, list: &Symbol) {
self.storage_manager.list_len(&mut self.buf, dst, list); self.storage_manager.list_len(&mut self.buf, dst, list);
} }
fn build_list_get_unsafe( fn build_list_get_unsafe(
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
@ -931,6 +932,129 @@ impl<
); );
} }
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. // 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. // What's important is to load the value, and for src and dest to have different Layouts.

View file

@ -806,6 +806,28 @@ 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 `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(
@ -960,7 +982,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.

View file

@ -267,7 +267,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
X86_64SystemV, X86_64SystemV,
>, >,
dst: &Symbol, dst: &Symbol,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) { ) {
@ -693,7 +693,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
X86_64WindowsFastcall, X86_64WindowsFastcall,
>, >,
dst: &Symbol, dst: &Symbol,
args: &'a [Symbol], args: &[Symbol],
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) { ) {

View file

@ -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>,
); );
@ -571,17 +571,18 @@ trait Backend<'a> {
debug_assert_eq!( debug_assert_eq!(
2, 2,
args.len(), args.len(),
"ListLen: expected to have exactly two arguments" "ListGetUnsafe: expected to have exactly two arguments"
); );
self.build_list_get_unsafe(sym, &args[0], &args[1], ret_layout) self.build_list_get_unsafe(sym, &args[0], &args[1], ret_layout)
} }
LowLevel::ListSet => self.build_fn_call( LowLevel::ListReplaceUnsafe => {
sym, debug_assert_eq!(
bitcode::LIST_SET.to_string(), 3,
args, args.len(),
arg_layouts, "ListReplaceUnsafe: expected to have exactly three arguments"
ret_layout, );
), 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(),
@ -643,7 +644,7 @@ 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_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE => {
// TODO: This is probably simple enough to be worth inlining. // TODO: This is probably simple enough to be worth inlining.
let layout_id = LayoutIds::default().get(func_sym, ret_layout); let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id); let fn_name = self.symbol_to_string(func_sym, layout_id);
@ -661,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>,
); );
@ -728,6 +729,16 @@ trait Backend<'a> {
index: &Symbol, index: &Symbol,
ret_layout: &Layout<'a>, 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);

View file

@ -883,6 +883,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