Add StrCat support and update hello-zig to support dev backend

This commit is contained in:
Brendan Hansknecht 2021-09-18 15:55:04 -07:00
parent 2f24067267
commit d1021d652d
6 changed files with 186 additions and 94 deletions

View file

@ -3,6 +3,7 @@ use roc_build::{
link::{link, rebuild_host, LinkType}, link::{link, rebuild_host, LinkType},
program, program,
}; };
use roc_builtins::bitcode;
use roc_can::builtins::builtin_defs_map; use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
@ -240,11 +241,19 @@ pub fn build_file<'a>(
})?; })?;
BuildOutcome::NoProblems BuildOutcome::NoProblems
} else { } else {
let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(),
app_o_file.to_str().unwrap(),
];
if matches!(opt_level, OptLevel::Development) {
inputs.push(bitcode::OBJ_PATH);
}
let (mut child, _) = // TODO use lld let (mut child, _) = // TODO use lld
link( link(
target, target,
binary_path.clone(), binary_path.clone(),
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()], &inputs,
link_type link_type
) )
.map_err(|_| { .map_err(|_| {

View file

@ -226,6 +226,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
#[inline(always)] #[inline(always)]
fn load_args<'a>( fn load_args<'a>(
_buf: &mut Vec<'a, u8>,
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>, _symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)], _args: &'a [(Layout<'a>, Symbol)],
_ret_layout: &Layout<'a>, _ret_layout: &Layout<'a>,

View file

@ -48,6 +48,7 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
// load_args updates the symbol map to know where every arg is stored. // load_args updates the symbol map to know where every arg is stored.
fn load_args<'a>( fn load_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
// 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.
@ -422,7 +423,12 @@ impl<
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
CC::load_args(&mut self.symbol_storage_map, args, ret_layout)?; CC::load_args(
&mut self.buf,
&mut self.symbol_storage_map,
args,
ret_layout,
)?;
// Update used and free regs. // Update used and free regs.
for (sym, storage) in &self.symbol_storage_map { for (sym, storage) in &self.symbol_storage_map {
match storage { match storage {
@ -1063,7 +1069,7 @@ impl<
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str) => {
if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) {
// This will happen on windows, return via pointer here. // This will happen on windows, return via pointer here.
Err("Returning strings via pointer not yet implemented".to_string()) return Err("Returning strings via pointer not yet implemented".to_string());
} else { } else {
ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset);
ASM::mov_reg64_base32( ASM::mov_reg64_base32(
@ -1071,7 +1077,6 @@ impl<
CC::GENERAL_RETURN_REGS[1], CC::GENERAL_RETURN_REGS[1],
*offset + 8, *offset + 8,
); );
Ok(())
} }
} }
Layout::Struct(field_layouts) => { Layout::Struct(field_layouts) => {

View file

@ -177,6 +177,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
#[inline(always)] #[inline(always)]
fn load_args<'a>( fn load_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
@ -231,6 +232,29 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
); );
} }
} }
Layout::Builtin(Builtin::Str) => {
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg.
let dst1 = Self::GENERAL_PARAM_REGS[general_i];
let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1];
base_offset += 16;
X86_64Assembler::mov_reg64_base32(buf, dst1, base_offset - 8);
X86_64Assembler::mov_reg64_base32(buf, dst2, base_offset);
symbol_map.insert(
*sym,
SymbolStorage::Base {
offset: base_offset,
size: 16,
owned: true,
},
);
general_i += 2;
} else {
return Err(
"loading strings args on the stack is not yet implemented".to_string()
);
}
}
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
@ -257,7 +281,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
// For most return layouts we will do nothing. // For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg. // In some cases, we need to put the return address as the first arg.
match ret_layout { match ret_layout {
Layout::Builtin(single_register_builtins!()) => {} Layout::Builtin(single_register_builtins!() | Builtin::Str) => {}
x => { x => {
return Err(format!( return Err(format!(
"receiving return type, {:?}, is not yet implemented", "receiving return type, {:?}, is not yet implemented",
@ -373,6 +397,32 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Str) => {
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg.
let dst1 = Self::GENERAL_PARAM_REGS[general_i];
let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::Base { offset, .. } => {
X86_64Assembler::mov_reg64_base32(buf, dst1, *offset);
X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8);
}
_ => {
return Err("Strings only support being loaded from base offsets"
.to_string());
}
}
general_i += 2;
} else {
return Err(
"calling functions with strings on the stack is not yet implemented"
.to_string(),
);
}
}
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
@ -516,6 +566,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
#[inline(always)] #[inline(always)]
fn load_args<'a>( fn load_args<'a>(
_buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
@ -535,9 +586,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_integers!()) => { Layout::Builtin(single_register_integers!()) => {
symbol_map symbol_map
.insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i])); .insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]));
i += 1;
} }
Layout::Builtin(single_register_floats!()) => { Layout::Builtin(single_register_floats!()) => {
symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i])); symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i]));
i += 1;
}
Layout::Builtin(Builtin::Str) => {
// I think this just needs to be passed on the stack, so not a huge deal.
return Err(
"Passing str args with Windows fast call not yet implemented."
.to_string(),
);
} }
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
@ -547,7 +607,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
)); ));
} }
} }
i += 1;
} else { } else {
base_offset += match layout { base_offset += match layout {
Layout::Builtin(single_register_builtins!()) => 8, Layout::Builtin(single_register_builtins!()) => 8,
@ -580,7 +639,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<u32, String> { ) -> Result<u32, String> {
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32; let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut reg_i = 0;
// For most return layouts we will do nothing. // For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg. // In some cases, we need to put the return address as the first arg.
match ret_layout { match ret_layout {
@ -597,7 +655,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_integers!()) => { Layout::Builtin(single_register_integers!()) => {
if i < Self::GENERAL_PARAM_REGS.len() { if i < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg. // Load the value to the param reg.
let dst = Self::GENERAL_PARAM_REGS[reg_i]; let dst = Self::GENERAL_PARAM_REGS[i];
match symbol_map match symbol_map
.get(&args[i]) .get(&args[i])
.ok_or("function argument does not reference any symbol")? .ok_or("function argument does not reference any symbol")?
@ -615,7 +673,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
) )
} }
} }
reg_i += 1;
} else { } else {
// Load the value to the stack. // Load the value to the stack.
match symbol_map match symbol_map
@ -651,7 +708,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_floats!()) => { Layout::Builtin(single_register_floats!()) => {
if i < Self::FLOAT_PARAM_REGS.len() { if i < Self::FLOAT_PARAM_REGS.len() {
// Load the value to the param reg. // Load the value to the param reg.
let dst = Self::FLOAT_PARAM_REGS[reg_i]; let dst = Self::FLOAT_PARAM_REGS[i];
match symbol_map match symbol_map
.get(&args[i]) .get(&args[i])
.ok_or("function argument does not reference any symbol")? .ok_or("function argument does not reference any symbol")?
@ -668,7 +725,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
return Err("Cannot load general symbol into FloatReg".to_string()) return Err("Cannot load general symbol into FloatReg".to_string())
} }
} }
reg_i += 1;
} else { } else {
// Load the value to the stack. // Load the value to the stack.
match symbol_map match symbol_map
@ -700,6 +756,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Str) => {
// I think this just needs to be passed on the stack, so not a huge deal.
return Err(
"Passing str args with Windows fast call not yet implemented.".to_string(),
);
}
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(

View file

@ -93,12 +93,8 @@ where
for (layout, sym) in proc.args { for (layout, sym) in proc.args {
self.set_layout_map(*sym, layout)?; self.set_layout_map(*sym, layout)?;
} }
// let start = std::time::Instant::now();
self.scan_ast(&proc.body); self.scan_ast(&proc.body);
self.create_free_map(); self.create_free_map();
// let duration = start.elapsed();
// println!("Time to calculate lifetimes: {:?}", duration);
// println!("{:?}", self.last_seen_map());
self.build_stmt(&proc.body, &proc.ret_layout)?; self.build_stmt(&proc.body, &proc.ret_layout)?;
self.finalize() self.finalize()
} }
@ -119,6 +115,11 @@ where
self.free_symbols(stmt)?; self.free_symbols(stmt)?;
Ok(()) Ok(())
} }
Stmt::Refcounting(_modify, following) => {
// TODO: actually deal with refcounting. For hello world, we just skipped it.
self.build_stmt(following, ret_layout)?;
Ok(())
}
Stmt::Switch { Stmt::Switch {
cond_symbol, cond_symbol,
cond_layout, cond_layout,
@ -298,6 +299,13 @@ where
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
Symbol::STR_CONCAT => self.build_run_low_level(
sym,
&LowLevel::StrConcat,
arguments,
arg_layouts,
ret_layout,
),
x if x x if x
.module_string(&self.env().interns) .module_string(&self.env().interns)
.starts_with(ModuleName::APP) => .starts_with(ModuleName::APP) =>
@ -470,6 +478,13 @@ where
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
LowLevel::StrConcat => self.build_fn_call(
sym,
bitcode::STR_CONCAT.to_string(),
args,
arg_layouts,
ret_layout,
),
x => Err(format!("low level, {:?}. is not yet implemented", x)), x => Err(format!("low level, {:?}. is not yet implemented", x)),
} }
} }

View file

@ -1,12 +1,12 @@
// use roc_std::{RocList, RocStr}; // #[macro_use]
#[macro_use] // extern crate indoc;
extern crate indoc;
#[macro_use] #[macro_use]
mod helpers; mod helpers;
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] #[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod dev_str { mod dev_str {
// use roc_std::{RocList, RocStr};
// #[test] // #[test]
// fn str_split_bigger_delimiter_small_str() { // fn str_split_bigger_delimiter_small_str() {
// assert_evals_to!( // assert_evals_to!(
@ -261,7 +261,7 @@ mod dev_str {
// // Verifies that we zero out unused bytes in the string. // // Verifies that we zero out unused bytes in the string.
// // This is important so that string equality tests don't randomly // // This is important so that string equality tests don't randomly
// // fail due to unused memory being there! // // fail due to unused memory being there!
// assert_llvm_evals_to!( // assert_evals_to!(
// "\"J\"", // "\"J\"",
// [ // [
// 0x4a, // 0x4a,
@ -285,57 +285,57 @@ mod dev_str {
// ); // );
// } // }
// #[test] #[test]
// fn small_str_concat_empty_first_arg() { fn small_str_concat_empty_first_arg() {
// assert_llvm_evals_to!( assert_evals_to!(
// r#"Str.concat "" "JJJJJJJJJJJJJJJ""#, r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
// [ [
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0b1000_1111 0b1000_1111
// ], ],
// [u8; 16] [u8; 16]
// ); );
// } }
// #[test] #[test]
// fn small_str_concat_empty_second_arg() { fn small_str_concat_empty_second_arg() {
// assert_llvm_evals_to!( assert_evals_to!(
// r#"Str.concat "JJJJJJJJJJJJJJJ" """#, r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
// [ [
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0b1000_1111 0b1000_1111
// ], ],
// [u8; 16] [u8; 16]
// ); );
// } }
// #[test] // #[test]
// fn small_str_concat_small_to_big() { // fn small_str_concat_small_to_big() {
@ -346,31 +346,31 @@ mod dev_str {
// ); // );
// } // }
// #[test] #[test]
// fn small_str_concat_small_to_small_staying_small() { fn small_str_concat_small_to_small_staying_small() {
// assert_llvm_evals_to!( assert_evals_to!(
// r#"Str.concat "J" "JJJJJJJJJJJJJJ""#, r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
// [ [
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0x4a, 0x4a,
// 0b1000_1111 0b1000_1111
// ], ],
// [u8; 16] [u8; 16]
// ); );
// } }
// #[test] // #[test]
// fn small_str_concat_small_to_small_overflow_to_big() { // fn small_str_concat_small_to_small_overflow_to_big() {