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},
program,
};
use roc_builtins::bitcode;
use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap;
use roc_load::file::LoadingProblem;
@ -240,11 +241,19 @@ pub fn build_file<'a>(
})?;
BuildOutcome::NoProblems
} 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
link(
target,
binary_path.clone(),
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()],
&inputs,
link_type
)
.map_err(|_| {

View file

@ -226,6 +226,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
#[inline(always)]
fn load_args<'a>(
_buf: &mut Vec<'a, u8>,
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)],
_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.
fn load_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
// 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)],
ret_layout: &Layout<'a>,
) -> 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.
for (sym, storage) in &self.symbol_storage_map {
match storage {
@ -1063,7 +1069,7 @@ impl<
Layout::Builtin(Builtin::Str) => {
if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) {
// 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 {
ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset);
ASM::mov_reg64_base32(
@ -1071,7 +1077,6 @@ impl<
CC::GENERAL_RETURN_REGS[1],
*offset + 8,
);
Ok(())
}
}
Layout::Struct(field_layouts) => {

View file

@ -177,6 +177,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
#[inline(always)]
fn load_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
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(&[]) => {}
x => {
return Err(format!(
@ -257,7 +281,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
// For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg.
match ret_layout {
Layout::Builtin(single_register_builtins!()) => {}
Layout::Builtin(single_register_builtins!() | Builtin::Str) => {}
x => {
return Err(format!(
"receiving return type, {:?}, is not yet implemented",
@ -373,6 +397,32 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
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(&[]) => {}
x => {
return Err(format!(
@ -516,6 +566,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
#[inline(always)]
fn load_args<'a>(
_buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
@ -535,9 +586,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_integers!()) => {
symbol_map
.insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]));
i += 1;
}
Layout::Builtin(single_register_floats!()) => {
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(&[]) => {}
x => {
@ -547,7 +607,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
));
}
}
i += 1;
} else {
base_offset += match layout {
Layout::Builtin(single_register_builtins!()) => 8,
@ -580,7 +639,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
ret_layout: &Layout<'a>,
) -> Result<u32, String> {
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut reg_i = 0;
// For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg.
match ret_layout {
@ -597,7 +655,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_integers!()) => {
if i < Self::GENERAL_PARAM_REGS.len() {
// 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
.get(&args[i])
.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 {
// Load the value to the stack.
match symbol_map
@ -651,7 +708,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_floats!()) => {
if i < Self::FLOAT_PARAM_REGS.len() {
// 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
.get(&args[i])
.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())
}
}
reg_i += 1;
} else {
// Load the value to the stack.
match symbol_map
@ -700,6 +756,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
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(&[]) => {}
x => {
return Err(format!(

View file

@ -93,12 +93,8 @@ where
for (layout, sym) in proc.args {
self.set_layout_map(*sym, layout)?;
}
// let start = std::time::Instant::now();
self.scan_ast(&proc.body);
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.finalize()
}
@ -119,6 +115,11 @@ where
self.free_symbols(stmt)?;
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 {
cond_symbol,
cond_layout,
@ -298,6 +299,13 @@ where
arg_layouts,
ret_layout,
),
Symbol::STR_CONCAT => self.build_run_low_level(
sym,
&LowLevel::StrConcat,
arguments,
arg_layouts,
ret_layout,
),
x if x
.module_string(&self.env().interns)
.starts_with(ModuleName::APP) =>
@ -470,6 +478,13 @@ where
arg_layouts,
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)),
}
}

View file

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