From 672aa14f7ceb566a0922e490ebe57530be3d1cc7 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 13 Sep 2021 23:45:02 +0200 Subject: [PATCH 001/136] Cosmetic: reorganise WasmLayout match statment --- compiler/gen_wasm/src/backend.rs | 62 ++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index acd2ce69b4..ab091ad350 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -41,36 +41,44 @@ pub enum WasmLayout { impl WasmLayout { fn new(layout: &Layout) -> Self { + use roc_mono::layout::Builtin::*; + use UnionLayout::*; use ValueType::*; + let size = layout.stack_size(PTR_SIZE); + match layout { - Layout::Builtin(Builtin::Int128) => Self::StackMemory(size), - Layout::Builtin(Builtin::Int64) => Self::LocalOnly(I64, size), - Layout::Builtin(Builtin::Int32) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Int16) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Int8) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Int1) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Usize) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Decimal) => Self::StackMemory(size), - Layout::Builtin(Builtin::Float128) => Self::StackMemory(size), - Layout::Builtin(Builtin::Float64) => Self::LocalOnly(F64, size), - Layout::Builtin(Builtin::Float32) => Self::LocalOnly(F32, size), - Layout::Builtin(Builtin::Float16) => Self::LocalOnly(F32, size), - Layout::Builtin(Builtin::Str) => Self::StackMemory(size), - Layout::Builtin(Builtin::Dict(_, _)) => Self::StackMemory(size), - Layout::Builtin(Builtin::Set(_)) => Self::StackMemory(size), - Layout::Builtin(Builtin::List(_)) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptyStr) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptyList) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptyDict) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptySet) => Self::StackMemory(size), - Layout::Struct(_) => Self::StackMemory(size), - Layout::Union(UnionLayout::NonRecursive(_)) => Self::StackMemory(size), - Layout::Union(UnionLayout::Recursive(_)) => Self::HeapMemory, - Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => Self::HeapMemory, - Layout::Union(UnionLayout::NullableWrapped { .. }) => Self::HeapMemory, - Layout::Union(UnionLayout::NullableUnwrapped { .. }) => Self::HeapMemory, - Layout::RecursivePointer => Self::HeapMemory, + Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), + + Layout::Builtin(Int64) => Self::LocalOnly(I64, size), + + Layout::Builtin(Float32 | Float16) => Self::LocalOnly(F32, size), + + Layout::Builtin(Float64) => Self::LocalOnly(F64, size), + + Layout::Builtin( + Int128 + | Decimal + | Float128 + | Str + | Dict(_, _) + | Set(_) + | List(_) + | EmptyStr + | EmptyList + | EmptyDict + | EmptySet, + ) + | Layout::Struct(_) + | Layout::Union(NonRecursive(_)) => Self::StackMemory(size), + + Layout::Union( + Recursive(_) + | NonNullableUnwrapped(_) + | NullableWrapped { .. } + | NullableUnwrapped { .. }, + ) + | Layout::RecursivePointer => Self::HeapMemory, } } From 258513a57cf298c0cf451d1f1c565938d087f41c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 14 Sep 2021 08:18:33 +0200 Subject: [PATCH 002/136] reset join/jump data --- compiler/gen_wasm/src/backend.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index ab091ad350..862a324feb 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -214,7 +214,8 @@ impl<'a> WasmBackend<'a> { // Functions: internal state & IR mappings self.stack_memory = 0; self.symbol_storage_map.clear(); - // joinpoint_label_map.clear(); + self.joinpoint_label_map.clear(); + assert_eq!(self.block_depth, 0); } pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { From 866d9f47a08cda4bed81f0130e20d45706f32694 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 14 Sep 2021 08:31:32 +0200 Subject: [PATCH 003/136] Move WasmLayout to its own module --- compiler/gen_wasm/src/backend.rs | 136 +------------------------------ compiler/gen_wasm/src/layout.rs | 136 +++++++++++++++++++++++++++++++ compiler/gen_wasm/src/lib.rs | 1 + 3 files changed, 139 insertions(+), 134 deletions(-) create mode 100644 compiler/gen_wasm/src/layout.rs diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 862a324feb..e43a4228fe 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -8,9 +8,9 @@ use roc_collections::all::MutMap; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; -use roc_mono::layout::{Builtin, Layout, UnionLayout}; +use roc_mono::layout::{Builtin, Layout}; -use crate::*; +use crate::layout::WasmLayout; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -25,138 +25,6 @@ struct LabelId(u32); #[derive(Debug)] struct SymbolStorage(LocalId, WasmLayout); -// See README for background information on Wasm locals, memory and function calls -#[derive(Debug)] -pub enum WasmLayout { - // Most number types can fit in a Wasm local without any stack memory. - // Roc i8 is represented as an i32 local. Store the type and the original size. - LocalOnly(ValueType, u32), - - // A `local` pointing to stack memory - StackMemory(u32), - - // A `local` pointing to heap memory - HeapMemory, -} - -impl WasmLayout { - fn new(layout: &Layout) -> Self { - use roc_mono::layout::Builtin::*; - use UnionLayout::*; - use ValueType::*; - - let size = layout.stack_size(PTR_SIZE); - - match layout { - Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), - - Layout::Builtin(Int64) => Self::LocalOnly(I64, size), - - Layout::Builtin(Float32 | Float16) => Self::LocalOnly(F32, size), - - Layout::Builtin(Float64) => Self::LocalOnly(F64, size), - - Layout::Builtin( - Int128 - | Decimal - | Float128 - | Str - | Dict(_, _) - | Set(_) - | List(_) - | EmptyStr - | EmptyList - | EmptyDict - | EmptySet, - ) - | Layout::Struct(_) - | Layout::Union(NonRecursive(_)) => Self::StackMemory(size), - - Layout::Union( - Recursive(_) - | NonNullableUnwrapped(_) - | NullableWrapped { .. } - | NullableUnwrapped { .. }, - ) - | Layout::RecursivePointer => Self::HeapMemory, - } - } - - fn value_type(&self) -> ValueType { - match self { - Self::LocalOnly(type_, _) => *type_, - _ => PTR_TYPE, - } - } - - fn stack_memory(&self) -> u32 { - match self { - Self::StackMemory(size) => *size, - _ => 0, - } - } - - #[allow(dead_code)] - fn load(&self, offset: u32) -> Result { - use crate::backend::WasmLayout::*; - use ValueType::*; - - match self { - LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)), - - // LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? - HeapMemory => { - if PTR_TYPE == I64 { - Ok(I64Load(ALIGN_8, offset)) - } else { - Ok(I32Load(ALIGN_4, offset)) - } - } - - _ => Err(format!( - "Failed to generate load instruction for WasmLayout {:?}", - self - )), - } - } - - #[allow(dead_code)] - fn store(&self, offset: u32) -> Result { - use crate::backend::WasmLayout::*; - use ValueType::*; - - match self { - LocalOnly(I32, 4) => Ok(I32Store(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Store16(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Store8(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Store(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Store(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Store(ALIGN_4, offset)), - - // LocalOnly(F32, 2) => Ok(), // convert F32 to F16 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? - HeapMemory => { - if PTR_TYPE == I64 { - Ok(I64Store(ALIGN_8, offset)) - } else { - Ok(I32Store(ALIGN_4, offset)) - } - } - - _ => Err(format!( - "Failed to generate store instruction for WasmLayout {:?}", - self - )), - } - } -} - pub struct WasmBackend<'a> { // Module: Wasm AST pub builder: ModuleBuilder, diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs new file mode 100644 index 0000000000..edf94e6c78 --- /dev/null +++ b/compiler/gen_wasm/src/layout.rs @@ -0,0 +1,136 @@ +use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; +use roc_mono::layout::{Layout, UnionLayout}; + +use crate::{ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE}; + +// See README for background information on Wasm locals, memory and function calls +#[derive(Debug)] +pub enum WasmLayout { + // Most number types can fit in a Wasm local without any stack memory. + // Roc i8 is represented as an i32 local. Store the type and the original size. + LocalOnly(ValueType, u32), + + // A `local` pointing to stack memory + StackMemory(u32), + + // A `local` pointing to heap memory + HeapMemory, +} + +impl WasmLayout { + pub fn new(layout: &Layout) -> Self { + use roc_mono::layout::Builtin::*; + use UnionLayout::*; + use ValueType::*; + + let size = layout.stack_size(PTR_SIZE); + + match layout { + Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), + + Layout::Builtin(Int64) => Self::LocalOnly(I64, size), + + Layout::Builtin(Float32 | Float16) => Self::LocalOnly(F32, size), + + Layout::Builtin(Float64) => Self::LocalOnly(F64, size), + + Layout::Builtin( + Int128 + | Decimal + | Float128 + | Str + | Dict(_, _) + | Set(_) + | List(_) + | EmptyStr + | EmptyList + | EmptyDict + | EmptySet, + ) + | Layout::Struct(_) + | Layout::Union(NonRecursive(_)) => Self::StackMemory(size), + + Layout::Union( + Recursive(_) + | NonNullableUnwrapped(_) + | NullableWrapped { .. } + | NullableUnwrapped { .. }, + ) + | Layout::RecursivePointer => Self::HeapMemory, + } + } + + pub fn value_type(&self) -> ValueType { + match self { + Self::LocalOnly(type_, _) => *type_, + _ => PTR_TYPE, + } + } + + pub fn stack_memory(&self) -> u32 { + match self { + Self::StackMemory(size) => *size, + _ => 0, + } + } + + #[allow(dead_code)] + fn load(&self, offset: u32) -> Result { + use crate::layout::WasmLayout::*; + use ValueType::*; + + match self { + LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)), + LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)), + LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)), + LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)), + LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)), + LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)), + + // LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?) + // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? + HeapMemory => { + if PTR_TYPE == I64 { + Ok(I64Load(ALIGN_8, offset)) + } else { + Ok(I32Load(ALIGN_4, offset)) + } + } + + _ => Err(format!( + "Failed to generate load instruction for WasmLayout {:?}", + self + )), + } + } + + #[allow(dead_code)] + fn store(&self, offset: u32) -> Result { + use crate::layout::WasmLayout::*; + use ValueType::*; + + match self { + LocalOnly(I32, 4) => Ok(I32Store(ALIGN_4, offset)), + LocalOnly(I32, 2) => Ok(I32Store16(ALIGN_2, offset)), + LocalOnly(I32, 1) => Ok(I32Store8(ALIGN_1, offset)), + LocalOnly(I64, 8) => Ok(I64Store(ALIGN_8, offset)), + LocalOnly(F64, 8) => Ok(F64Store(ALIGN_8, offset)), + LocalOnly(F32, 4) => Ok(F32Store(ALIGN_4, offset)), + + // LocalOnly(F32, 2) => Ok(), // convert F32 to F16 (lowlevel function? Wasm-only?) + // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? + HeapMemory => { + if PTR_TYPE == I64 { + Ok(I64Store(ALIGN_8, offset)) + } else { + Ok(I32Store(ALIGN_4, offset)) + } + } + + _ => Err(format!( + "Failed to generate store instruction for WasmLayout {:?}", + self + )), + } + } +} diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 37eb3c1d5b..748044ea20 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,5 +1,6 @@ mod backend; pub mod from_wasm32_memory; +mod layout; use bumpalo::Bump; use parity_wasm::builder; From 0ef9498a6945fc936b30ad9ed71108ea7bf1fd74 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 14 Sep 2021 14:46:03 -0700 Subject: [PATCH 004/136] Rebuild hosts in a separate thread and only optimize when specified --- Cargo.lock | 3 ++ cli/Cargo.toml | 1 + cli/src/build.rs | 72 +++++++++++++++++++------- cli/src/lib.rs | 21 ++++++++ compiler/build/src/link.rs | 103 ++++++++++++++++++++----------------- linker/Cargo.toml | 2 + linker/src/lib.rs | 22 ++++++++ 7 files changed, 160 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b6373852f..6819b9a48d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3483,6 +3483,7 @@ dependencies = [ "roc_editor", "roc_fmt", "roc_gen_llvm", + "roc_linker", "roc_load", "roc_module", "roc_mono", @@ -3725,8 +3726,10 @@ dependencies = [ "iced-x86", "memmap2 0.3.1", "object 0.26.2", + "roc_build", "roc_collections", "serde", + "target-lexicon", ] [[package]] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 0225a11916..97ecb81a46 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -57,6 +57,7 @@ roc_build = { path = "../compiler/build", default-features = false } roc_fmt = { path = "../compiler/fmt" } roc_reporting = { path = "../compiler/reporting" } roc_editor = { path = "../editor", optional = true } +roc_linker = { path = "../linker" } # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it clap = { git = "https://github.com/rtfeldman/clap", branch = "master" } const_format = "0.2" diff --git a/cli/src/build.rs b/cli/src/build.rs index face340a56..3426bdad55 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -53,6 +53,7 @@ pub fn build_file<'a>( emit_debug_info: bool, emit_timings: bool, link_type: LinkType, + surgically_link: bool, ) -> Result> { let compilation_start = SystemTime::now(); let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; @@ -97,7 +98,31 @@ pub fn build_file<'a>( let host_extension = if emit_wasm { "zig" } else { "o" }; let app_extension = if emit_wasm { "bc" } else { "o" }; + let cwd = roc_file_path.parent().unwrap(); let path_to_platform = loaded.platform_path.clone(); + let mut host_input_path = PathBuf::from(cwd); + host_input_path.push(&*path_to_platform); + host_input_path.push("host"); + host_input_path.set_extension(host_extension); + + // TODO this should probably be moved before load_and_monomorphize. + // To do this we will need to preprocess files just for their exported symbols. + // Also, we should no longer need to do this once we have platforms on + // a package repository, as we can then get precompiled hosts from there. + let rebuild_thread = spawn_rebuild_thread( + opt_level, + surgically_link, + host_input_path.clone(), + target.clone(), + loaded + .exposed_to_host + .keys() + .map(|x| x.as_str(&loaded.interns).to_string()) + .collect(), + ); + + // TODO try to move as much of this linking as possible to the precompiled + // host, to minimize the amount of host-application linking required. let app_o_file = Builder::new() .prefix("roc_app") .suffix(&format!(".{}", app_extension)) @@ -154,7 +179,6 @@ pub fn build_file<'a>( program::report_problems(&mut loaded); let loaded = loaded; - let cwd = roc_file_path.parent().unwrap(); let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows let code_gen_timing = program::gen_from_mono_module( arena, @@ -198,28 +222,15 @@ pub fn build_file<'a>( ); } - // Step 2: link the precompiled host and compiled app - let mut host_input_path = PathBuf::from(cwd); - - host_input_path.push(&*path_to_platform); - host_input_path.push("host"); - host_input_path.set_extension(host_extension); - - // TODO we should no longer need to do this once we have platforms on - // a package repository, as we can then get precompiled hosts from there. - let rebuild_host_start = SystemTime::now(); - rebuild_host(target, host_input_path.as_path()); - let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); - + let rebuild_duration = rebuild_thread.join().unwrap(); if emit_timings { println!( - "Finished rebuilding the host in {} ms\n", - rebuild_host_end.as_millis() + "Finished rebuilding and preprocessing the host in {} ms\n", + rebuild_duration ); } - // TODO try to move as much of this linking as possible to the precompiled - // host, to minimize the amount of host-application linking required. + // Step 2: link the precompiled host and compiled app let link_start = SystemTime::now(); let (mut child, binary_path) = // TODO use lld link( @@ -260,3 +271,28 @@ pub fn build_file<'a>( total_time, }) } + +fn spawn_rebuild_thread( + opt_level: OptLevel, + surgically_link: bool, + host_input_path: PathBuf, + target: Triple, + exported_symbols: Vec, +) -> std::thread::JoinHandle { + let thread_local_target = target.clone(); + std::thread::spawn(move || { + let rebuild_host_start = SystemTime::now(); + if surgically_link { + roc_linker::build_and_preprocess_host( + &thread_local_target, + host_input_path.as_path(), + exported_symbols, + ) + .unwrap(); + } else { + rebuild_host(opt_level, &thread_local_target, host_input_path.as_path()); + } + let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); + rebuild_host_end.as_millis() + }) +} diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 2027e2f300..d73c38054f 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -32,6 +32,7 @@ pub const FLAG_OPTIMIZE: &str = "optimize"; pub const FLAG_LIB: &str = "lib"; pub const FLAG_BACKEND: &str = "backend"; pub const FLAG_TIME: &str = "time"; +pub const FLAG_LINK: &str = "roc-linker"; pub const ROC_FILE: &str = "ROC_FILE"; pub const BACKEND: &str = "BACKEND"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; @@ -81,6 +82,12 @@ pub fn build_app<'a>() -> App<'a> { .help("Prints detailed compilation time information.") .required(false), ) + .arg( + Arg::with_name(FLAG_LINK) + .long(FLAG_LINK) + .help("Uses the roc linker instead of the system linker.") + .required(false), + ) ) .subcommand(App::new(CMD_RUN) .about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`") @@ -143,6 +150,12 @@ pub fn build_app<'a>() -> App<'a> { .help("Prints detailed compilation time information.") .required(false), ) + .arg( + Arg::with_name(FLAG_LINK) + .long(FLAG_LINK) + .help("Uses the roc linker instead of the system linker.") + .required(false), + ) .arg( Arg::with_name(FLAG_BACKEND) .long(FLAG_BACKEND) @@ -223,6 +236,13 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { } else { LinkType::Executable }; + let surgically_link = matches.is_present(FLAG_LINK); + if surgically_link && !roc_linker::supported(&link_type, &target) { + panic!( + "Link type, {:?}, with target, {}, not supported by roc linker", + link_type, target + ); + } let path = Path::new(filename); @@ -255,6 +275,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { emit_debug_info, emit_timings, link_type, + surgically_link, ); match res_binary_path { diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index d3f7aba399..5e1de46012 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -83,8 +83,10 @@ pub fn build_zig_host_native( zig_host_src: &str, zig_str_path: &str, target: &str, + opt_level: OptLevel, ) -> Output { - Command::new("zig") + let mut command = Command::new("zig"); + command .env_clear() .env("PATH", env_path) .env("HOME", env_home) @@ -102,14 +104,14 @@ pub fn build_zig_host_native( "--library", "c", "-fPIC", - "-O", - "ReleaseSafe", // cross-compile? "-target", target, - ]) - .output() - .unwrap() + ]); + if matches!(opt_level, OptLevel::Optimize) { + command.args(&["-O", "ReleaseSafe"]); + } + command.output().unwrap() } #[cfg(target_os = "macos")] @@ -120,6 +122,7 @@ pub fn build_zig_host_native( zig_host_src: &str, zig_str_path: &str, _target: &str, + opt_level: OptLevel, ) -> Output { use serde_json::Value; @@ -161,7 +164,8 @@ pub fn build_zig_host_native( zig_compiler_rt_path.push("special"); zig_compiler_rt_path.push("compiler_rt.zig"); - Command::new("zig") + let mut command = Command::new("zig"); + command .env_clear() .env("PATH", &env_path) .env("HOME", &env_home) @@ -182,11 +186,11 @@ pub fn build_zig_host_native( "--library", "c", "-fPIC", - "-O", - "ReleaseSafe", - ]) - .output() - .unwrap() + ]); + if matches!(opt_level, OptLevel::Optimize) { + command.args(&["-O", "ReleaseSafe"]); + } + command.output().unwrap() } pub fn build_zig_host_wasm32( @@ -195,6 +199,7 @@ pub fn build_zig_host_wasm32( emit_bin: &str, zig_host_src: &str, zig_str_path: &str, + opt_level: OptLevel, ) -> Output { // NOTE currently just to get compiler warnings if the host code is invalid. // the produced artifact is not used @@ -204,7 +209,8 @@ pub fn build_zig_host_wasm32( // we'd like to compile with `-target wasm32-wasi` but that is blocked on // // https://github.com/ziglang/zig/issues/9414 - Command::new("zig") + let mut command = Command::new("zig"); + command .env_clear() .env("PATH", env_path) .env("HOME", env_home) @@ -226,14 +232,14 @@ pub fn build_zig_host_wasm32( // "wasm32-wasi", // "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll", "-fPIC", - "-O", - "ReleaseSafe", - ]) - .output() - .unwrap() + ]); + if matches!(opt_level, OptLevel::Optimize) { + command.args(&["-O", "ReleaseSafe"]); + } + command.output().unwrap() } -pub fn rebuild_host(target: &Triple, host_input_path: &Path) { +pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path) { let c_host_src = host_input_path.with_file_name("host.c"); let c_host_dest = host_input_path.with_file_name("c_host.o"); let zig_host_src = host_input_path.with_file_name("host.zig"); @@ -266,6 +272,7 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) { &emit_bin, zig_host_src.to_str().unwrap(), zig_str_path.to_str().unwrap(), + opt_level, ) } Architecture::X86_64 => { @@ -277,6 +284,7 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) { zig_host_src.to_str().unwrap(), zig_str_path.to_str().unwrap(), "native", + opt_level, ) } Architecture::X86_32(_) => { @@ -288,6 +296,7 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) { zig_host_src.to_str().unwrap(), zig_str_path.to_str().unwrap(), "i386-linux-musl", + opt_level, ) } _ => panic!("Unsupported architecture {:?}", target.architecture), @@ -296,19 +305,18 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) { validate_output("host.zig", "zig", output) } else { // Compile host.c - let output = Command::new("clang") - .env_clear() - .env("PATH", &env_path) - .args(&[ - "-O2", - "-fPIC", - "-c", - c_host_src.to_str().unwrap(), - "-o", - c_host_dest.to_str().unwrap(), - ]) - .output() - .unwrap(); + let mut command = Command::new("clang"); + command.env_clear().env("PATH", &env_path).args(&[ + "-fPIC", + "-c", + c_host_src.to_str().unwrap(), + "-o", + c_host_dest.to_str().unwrap(), + ]); + if matches!(opt_level, OptLevel::Optimize) { + command.arg("-O2"); + } + let output = command.output().unwrap(); validate_output("host.c", "clang", output); } @@ -318,13 +326,14 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) { let cargo_dir = host_input_path.parent().unwrap(); let libhost_dir = cargo_dir.join("target").join("release"); - let output = Command::new("cargo") - .args(&["build", "--release"]) - .current_dir(cargo_dir) - .output() - .unwrap(); + let mut command = Command::new("cargo"); + command.arg("build").current_dir(cargo_dir); + if matches!(opt_level, OptLevel::Optimize) { + command.arg("--release"); + } + let output = command.output().unwrap(); - validate_output("src/lib.rs", "cargo build --release", output); + validate_output("src/lib.rs", "cargo build", output); let output = Command::new("ld") .env_clear() @@ -344,14 +353,16 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) { validate_output("c_host.o", "ld", output); } else if rust_host_src.exists() { // Compile and link host.rs, if it exists - let output = Command::new("rustc") - .args(&[ - rust_host_src.to_str().unwrap(), - "-o", - rust_host_dest.to_str().unwrap(), - ]) - .output() - .unwrap(); + let mut command = Command::new("rustc"); + command.args(&[ + rust_host_src.to_str().unwrap(), + "-o", + rust_host_dest.to_str().unwrap(), + ]); + if matches!(opt_level, OptLevel::Optimize) { + command.arg("-O"); + } + let output = command.output().unwrap(); validate_output("host.rs", "rustc", output); diff --git a/linker/Cargo.toml b/linker/Cargo.toml index 43461fc276..520ed8f660 100644 --- a/linker/Cargo.toml +++ b/linker/Cargo.toml @@ -18,6 +18,7 @@ test = false bench = false [dependencies] +roc_build = { path = "../compiler/build", default-features = false } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.6", features = ["collections"] } # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it @@ -27,3 +28,4 @@ memmap2 = "0.3" object = { version = "0.26", features = ["read"] } serde = { version = "1.0", features = ["derive"] } bincode = "1.3" +target-lexicon = "0.12.2" diff --git a/linker/src/lib.rs b/linker/src/lib.rs index e5e6b6f1a8..c747d184f8 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -8,6 +8,7 @@ use object::{ Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, Section, SectionIndex, Symbol, SymbolIndex, SymbolSection, }; +use roc_build::link::LinkType; use roc_collections::all::MutMap; use std::cmp::Ordering; use std::convert::TryFrom; @@ -19,6 +20,7 @@ use std::mem; use std::os::raw::c_char; use std::path::Path; use std::time::{Duration, SystemTime}; +use target_lexicon::Triple; mod metadata; @@ -122,6 +124,26 @@ pub fn build_app<'a>() -> App<'a> { ) } +pub fn supported(link_type: &LinkType, target: &Triple) -> bool { + link_type == &LinkType::Executable + && target.architecture == target_lexicon::Architecture::X86_64 + && target.operating_system == target_lexicon::OperatingSystem::Linux + && target.binary_format == target_lexicon::BinaryFormat::Elf +} + +pub fn build_and_preprocess_host( + target: &Triple, + host_input_path: &Path, + exposed_to_host: Vec, +) -> io::Result<()> { + let lib = generate_dynamic_lib(exposed_to_host)?; + Ok(()) +} + +fn generate_dynamic_lib(exposed_to_host: Vec) -> io::Result<()> { + Ok(()) +} + // TODO: Most of this file is a mess of giant functions just to check if things work. // Clean it all up and refactor nicely. pub fn preprocess(matches: &ArgMatches) -> io::Result { From 7297e969bd95063233c14438935a82270d7b0db4 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 14 Sep 2021 17:30:26 -0700 Subject: [PATCH 005/136] Fix cargo debug build --- compiler/build/src/link.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 5e1de46012..08dd7f0f6e 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -324,7 +324,14 @@ pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path if cargo_host_src.exists() { // Compile and link Cargo.toml, if it exists let cargo_dir = host_input_path.parent().unwrap(); - let libhost_dir = cargo_dir.join("target").join("release"); + let libhost_dir = + cargo_dir + .join("target") + .join(if matches!(opt_level, OptLevel::Optimize) { + "release" + } else { + "debug" + }); let mut command = Command::new("cargo"); command.arg("build").current_dir(cargo_dir); From e96291e9a7b149174eb1fef1ff0297eb60714a77 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 14 Sep 2021 21:59:15 -0700 Subject: [PATCH 006/136] Enable rebuilding hosts into a dynamic executable --- Cargo.lock | 2 + cli/src/build.rs | 8 +- compiler/build/src/link.rs | 293 +++++++++++++++++++++++++------------ linker/Cargo.toml | 2 + linker/src/lib.rs | 67 +++++++-- 5 files changed, 260 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6819b9a48d..c17cb03261 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3728,8 +3728,10 @@ dependencies = [ "object 0.26.2", "roc_build", "roc_collections", + "roc_mono", "serde", "target-lexicon", + "tempfile", ] [[package]] diff --git a/cli/src/build.rs b/cli/src/build.rs index 3426bdad55..fec21f420b 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -284,13 +284,19 @@ fn spawn_rebuild_thread( let rebuild_host_start = SystemTime::now(); if surgically_link { roc_linker::build_and_preprocess_host( + opt_level, &thread_local_target, host_input_path.as_path(), exported_symbols, ) .unwrap(); } else { - rebuild_host(opt_level, &thread_local_target, host_input_path.as_path()); + rebuild_host( + opt_level, + &thread_local_target, + host_input_path.as_path(), + None, + ); } let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); rebuild_host_end.as_millis() diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 08dd7f0f6e..d6de4b12d9 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -84,30 +84,35 @@ pub fn build_zig_host_native( zig_str_path: &str, target: &str, opt_level: OptLevel, + shared_lib_path: Option<&Path>, ) -> Output { let mut command = Command::new("zig"); command .env_clear() .env("PATH", env_path) - .env("HOME", env_home) - .args(&[ - "build-obj", - zig_host_src, - emit_bin, - "--pkg-begin", - "str", - zig_str_path, - "--pkg-end", - // include the zig runtime - "-fcompiler-rt", - // include libc - "--library", - "c", - "-fPIC", - // cross-compile? - "-target", - target, - ]); + .env("HOME", env_home); + if let Some(shared_lib_path) = shared_lib_path { + command.args(&["build-exe", shared_lib_path.to_str().unwrap()]); + } else { + command.arg("build-obj"); + } + command.args(&[ + zig_host_src, + emit_bin, + "--pkg-begin", + "str", + zig_str_path, + "--pkg-end", + // include the zig runtime + "-fcompiler-rt", + // include libc + "--library", + "c", + "-fPIC", + // cross-compile? + "-target", + target, + ]); if matches!(opt_level, OptLevel::Optimize) { command.args(&["-O", "ReleaseSafe"]); } @@ -123,6 +128,7 @@ pub fn build_zig_host_native( zig_str_path: &str, _target: &str, opt_level: OptLevel, + shared_lib_path: Option<&Path>, ) -> Output { use serde_json::Value; @@ -168,25 +174,29 @@ pub fn build_zig_host_native( command .env_clear() .env("PATH", &env_path) - .env("HOME", &env_home) - .args(&[ - "build-obj", - zig_host_src, - emit_bin, - "--pkg-begin", - "str", - zig_str_path, - "--pkg-end", - // include the zig runtime - "--pkg-begin", - "compiler_rt", - zig_compiler_rt_path.to_str().unwrap(), - "--pkg-end", - // include libc - "--library", - "c", - "-fPIC", - ]); + .env("HOME", &env_home); + if let Some(shared_lib_path) = shared_lib_path { + command.args(&["build-exe", shared_lib_path.to_str().unwrap()]); + } else { + command.arg("build-obj"); + } + command.args(&[ + zig_host_src, + emit_bin, + "--pkg-begin", + "str", + zig_str_path, + "--pkg-end", + // include the zig runtime + "--pkg-begin", + "compiler_rt", + zig_compiler_rt_path.to_str().unwrap(), + "--pkg-end", + // include libc + "--library", + "c", + "-fPIC", + ]); if matches!(opt_level, OptLevel::Optimize) { command.args(&["-O", "ReleaseSafe"]); } @@ -200,7 +210,11 @@ pub fn build_zig_host_wasm32( zig_host_src: &str, zig_str_path: &str, opt_level: OptLevel, + shared_lib_path: Option<&Path>, ) -> Output { + if shared_lib_path.is_some() { + unimplemented!("Linking a shared library to wasm not yet implemented"); + } // NOTE currently just to get compiler warnings if the host code is invalid. // the produced artifact is not used // @@ -239,14 +253,56 @@ pub fn build_zig_host_wasm32( command.output().unwrap() } -pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path) { +pub fn build_c_host_native( + env_path: &str, + env_home: &str, + dest: &str, + sources: &[&str], + opt_level: OptLevel, + shared_lib_path: Option<&Path>, +) -> Output { + let mut command = Command::new("clang"); + command + .env_clear() + .env("PATH", &env_path) + .env("HOME", &env_home) + .args(sources) + .args(&["-fPIC", "-o", dest]); + if let Some(shared_lib_path) = shared_lib_path { + command.args(&[ + shared_lib_path.to_str().unwrap(), + "-lm", + "-lpthread", + "-ldl", + "-lrt", + "-lutil", + ]); + } else { + command.arg("-c"); + } + if matches!(opt_level, OptLevel::Optimize) { + command.arg("-O2"); + } + command.output().unwrap() +} + +pub fn rebuild_host( + opt_level: OptLevel, + target: &Triple, + host_input_path: &Path, + shared_lib_path: Option<&Path>, +) { let c_host_src = host_input_path.with_file_name("host.c"); let c_host_dest = host_input_path.with_file_name("c_host.o"); let zig_host_src = host_input_path.with_file_name("host.zig"); let rust_host_src = host_input_path.with_file_name("host.rs"); let rust_host_dest = host_input_path.with_file_name("rust_host.o"); let cargo_host_src = host_input_path.with_file_name("Cargo.toml"); - let host_dest_native = host_input_path.with_file_name("host.o"); + let host_dest_native = host_input_path.with_file_name(if shared_lib_path.is_some() { + "dynhost" + } else { + "host.o" + }); let host_dest_wasm = host_input_path.with_file_name("host.bc"); let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string()); @@ -273,6 +329,7 @@ pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path zig_host_src.to_str().unwrap(), zig_str_path.to_str().unwrap(), opt_level, + shared_lib_path, ) } Architecture::X86_64 => { @@ -285,6 +342,7 @@ pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path zig_str_path.to_str().unwrap(), "native", opt_level, + shared_lib_path, ) } Architecture::X86_32(_) => { @@ -297,31 +355,14 @@ pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path zig_str_path.to_str().unwrap(), "i386-linux-musl", opt_level, + shared_lib_path, ) } _ => panic!("Unsupported architecture {:?}", target.architecture), }; validate_output("host.zig", "zig", output) - } else { - // Compile host.c - let mut command = Command::new("clang"); - command.env_clear().env("PATH", &env_path).args(&[ - "-fPIC", - "-c", - c_host_src.to_str().unwrap(), - "-o", - c_host_dest.to_str().unwrap(), - ]); - if matches!(opt_level, OptLevel::Optimize) { - command.arg("-O2"); - } - let output = command.output().unwrap(); - - validate_output("host.c", "clang", output); - } - - if cargo_host_src.exists() { + } else if cargo_host_src.exists() { // Compile and link Cargo.toml, if it exists let cargo_dir = host_input_path.parent().unwrap(); let libhost_dir = @@ -332,6 +373,7 @@ pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path } else { "debug" }); + let libhost = libhost_dir.join("libhost.a"); let mut command = Command::new("cargo"); command.arg("build").current_dir(cargo_dir); @@ -342,22 +384,54 @@ pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path validate_output("src/lib.rs", "cargo build", output); - let output = Command::new("ld") - .env_clear() - .env("PATH", &env_path) - .args(&[ - "-r", - "-L", - libhost_dir.to_str().unwrap(), - c_host_dest.to_str().unwrap(), - "-lhost", - "-o", + // Cargo hosts depend on a c wrapper for the api. Compile host.c as well. + if shared_lib_path.is_some() { + // If compiling to executable, let c deal with linking as well. + let output = build_c_host_native( + &env_path, + &env_home, host_dest_native.to_str().unwrap(), - ]) - .output() - .unwrap(); + &[c_host_src.to_str().unwrap(), libhost.to_str().unwrap()], + opt_level, + shared_lib_path, + ); + validate_output("host.c", "clang", output); + } else { + let output = build_c_host_native( + &env_path, + &env_home, + c_host_dest.to_str().unwrap(), + &[c_host_src.to_str().unwrap()], + opt_level, + shared_lib_path, + ); + validate_output("host.c", "clang", output); - validate_output("c_host.o", "ld", output); + let output = Command::new("ld") + .env_clear() + .env("PATH", &env_path) + .args(&[ + "-r", + "-L", + libhost_dir.to_str().unwrap(), + c_host_dest.to_str().unwrap(), + "-lhost", + "-o", + host_dest_native.to_str().unwrap(), + ]) + .output() + .unwrap(); + validate_output("c_host.o", "ld", output); + + // Clean up c_host.o + let output = Command::new("rm") + .env_clear() + .args(&["-f", c_host_dest.to_str().unwrap()]) + .output() + .unwrap(); + + validate_output("rust_host.o", "rm", output); + } } else if rust_host_src.exists() { // Compile and link host.rs, if it exists let mut command = Command::new("rustc"); @@ -373,22 +447,49 @@ pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path validate_output("host.rs", "rustc", output); - let output = Command::new("ld") - .env_clear() - .env("PATH", &env_path) - .args(&[ - "-r", - c_host_dest.to_str().unwrap(), - rust_host_dest.to_str().unwrap(), - "-o", + // Rust hosts depend on a c wrapper for the api. Compile host.c as well. + if shared_lib_path.is_some() { + // If compiling to executable, let c deal with linking as well. + let output = build_c_host_native( + &env_path, + &env_home, host_dest_native.to_str().unwrap(), - ]) - .output() - .unwrap(); + &[ + c_host_src.to_str().unwrap(), + rust_host_dest.to_str().unwrap(), + ], + opt_level, + shared_lib_path, + ); + validate_output("host.c", "clang", output); + } else { + let output = build_c_host_native( + &env_path, + &env_home, + c_host_dest.to_str().unwrap(), + &[c_host_src.to_str().unwrap()], + opt_level, + shared_lib_path, + ); - validate_output("rust_host.o", "ld", output); + validate_output("host.c", "clang", output); + let output = Command::new("ld") + .env_clear() + .env("PATH", &env_path) + .args(&[ + "-r", + c_host_dest.to_str().unwrap(), + rust_host_dest.to_str().unwrap(), + "-o", + host_dest_native.to_str().unwrap(), + ]) + .output() + .unwrap(); - // Clean up rust_host.o + validate_output("rust_host.o", "ld", output); + } + + // Clean up rust_host.o and c_host.o let output = Command::new("rm") .env_clear() .args(&[ @@ -400,15 +501,17 @@ pub fn rebuild_host(opt_level: OptLevel, target: &Triple, host_input_path: &Path .unwrap(); validate_output("rust_host.o", "rm", output); - } else if c_host_dest.exists() { - // Clean up c_host.o - let output = Command::new("mv") - .env_clear() - .args(&[c_host_dest, host_dest_native]) - .output() - .unwrap(); - - validate_output("c_host.o", "mv", output); + } else if c_host_src.exists() { + // Compile host.c, if it exists + let output = build_c_host_native( + &env_path, + &env_home, + host_dest_native.to_str().unwrap(), + &[c_host_src.to_str().unwrap()], + opt_level, + shared_lib_path, + ); + validate_output("host.c", "clang", output); } } diff --git a/linker/Cargo.toml b/linker/Cargo.toml index 520ed8f660..b5babe9ebf 100644 --- a/linker/Cargo.toml +++ b/linker/Cargo.toml @@ -18,6 +18,7 @@ test = false bench = false [dependencies] +roc_mono = { path = "../compiler/mono" } roc_build = { path = "../compiler/build", default-features = false } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.6", features = ["collections"] } @@ -29,3 +30,4 @@ object = { version = "0.26", features = ["read"] } serde = { version = "1.0", features = ["derive"] } bincode = "1.3" target-lexicon = "0.12.2" +tempfile = "3.1.0" diff --git a/linker/src/lib.rs b/linker/src/lib.rs index c747d184f8..26eff91133 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -8,8 +8,9 @@ use object::{ Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, Section, SectionIndex, Symbol, SymbolIndex, SymbolSection, }; -use roc_build::link::LinkType; +use roc_build::link::{rebuild_host, LinkType}; use roc_collections::all::MutMap; +use roc_mono::ir::OptLevel; use std::cmp::Ordering; use std::convert::TryFrom; use std::ffi::CStr; @@ -21,6 +22,7 @@ use std::os::raw::c_char; use std::path::Path; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; +use tempfile::{Builder, NamedTempFile}; mod metadata; @@ -132,27 +134,47 @@ pub fn supported(link_type: &LinkType, target: &Triple) -> bool { } pub fn build_and_preprocess_host( + opt_level: OptLevel, target: &Triple, host_input_path: &Path, exposed_to_host: Vec, ) -> io::Result<()> { let lib = generate_dynamic_lib(exposed_to_host)?; + rebuild_host(opt_level, target, host_input_path, Some(&lib.path())); Ok(()) } -fn generate_dynamic_lib(exposed_to_host: Vec) -> io::Result<()> { - Ok(()) +fn generate_dynamic_lib(exposed_to_host: Vec) -> io::Result { + for sym in exposed_to_host { + println!("{}", sym); + } + let dummy_lib_file = Builder::new().prefix("roc_lib").suffix(".so").tempfile()?; + Ok(dummy_lib_file) } +pub fn preprocess(matches: &ArgMatches) -> io::Result { + preprocess_impl( + &matches.value_of(EXEC).unwrap(), + &matches.value_of(METADATA).unwrap(), + &matches.value_of(OUT).unwrap(), + &matches.value_of(SHARED_LIB).unwrap(), + matches.is_present(FLAG_VERBOSE), + matches.is_present(FLAG_TIME), + ) +} // TODO: Most of this file is a mess of giant functions just to check if things work. // Clean it all up and refactor nicely. -pub fn preprocess(matches: &ArgMatches) -> io::Result { - let verbose = matches.is_present(FLAG_VERBOSE); - let time = matches.is_present(FLAG_TIME); - +fn preprocess_impl( + exec_filename: &str, + metadata_filename: &str, + out_filename: &str, + shared_lib_filename: &str, + verbose: bool, + time: bool, +) -> io::Result { let total_start = SystemTime::now(); let exec_parsing_start = total_start; - let exec_file = fs::File::open(&matches.value_of(EXEC).unwrap())?; + let exec_file = fs::File::open(exec_filename)?; let exec_mmap = unsafe { Mmap::map(&exec_file)? }; let exec_data = &*exec_mmap; let exec_obj = match object::File::parse(exec_data) { @@ -489,7 +511,7 @@ pub fn preprocess(matches: &ArgMatches) -> io::Result { } }; - let shared_lib_name = Path::new(matches.value_of(SHARED_LIB).unwrap()) + let shared_lib_name = Path::new(shared_lib_filename) .file_name() .unwrap() .to_str() @@ -623,7 +645,7 @@ pub fn preprocess(matches: &ArgMatches) -> io::Result { .write(true) .create(true) .truncate(true) - .open(&matches.value_of(OUT).unwrap())?; + .open(out_filename)?; out_file.set_len(md.exec_len)?; let mut out_mmap = unsafe { MmapMut::map_mut(&out_file)? }; @@ -884,7 +906,7 @@ pub fn preprocess(matches: &ArgMatches) -> io::Result { } let saving_metadata_start = SystemTime::now(); - let output = fs::File::create(&matches.value_of(METADATA).unwrap())?; + let output = fs::File::create(metadata_filename)?; let output = BufWriter::new(output); if let Err(err) = serialize_into(output, &md) { println!("Failed to serialize metadata: {}", err); @@ -929,12 +951,25 @@ pub fn preprocess(matches: &ArgMatches) -> io::Result { } pub fn surgery(matches: &ArgMatches) -> io::Result { - let verbose = matches.is_present(FLAG_VERBOSE); - let time = matches.is_present(FLAG_TIME); + surgery_impl( + &matches.value_of(APP).unwrap(), + &matches.value_of(METADATA).unwrap(), + &matches.value_of(OUT).unwrap(), + matches.is_present(FLAG_VERBOSE), + matches.is_present(FLAG_TIME), + ) +} +fn surgery_impl( + app_filename: &str, + metadata_filename: &str, + out_filename: &str, + verbose: bool, + time: bool, +) -> io::Result { let total_start = SystemTime::now(); let loading_metadata_start = total_start; - let input = fs::File::open(&matches.value_of(METADATA).unwrap())?; + let input = fs::File::open(metadata_filename)?; let input = BufReader::new(input); let md: metadata::Metadata = match deserialize_from(input) { Ok(data) => data, @@ -946,7 +981,7 @@ pub fn surgery(matches: &ArgMatches) -> io::Result { let loading_metadata_duration = loading_metadata_start.elapsed().unwrap(); let app_parsing_start = SystemTime::now(); - let app_file = fs::File::open(&matches.value_of(APP).unwrap())?; + let app_file = fs::File::open(app_filename)?; let app_mmap = unsafe { Mmap::map(&app_file)? }; let app_data = &*app_mmap; let app_obj = match object::File::parse(app_data) { @@ -962,7 +997,7 @@ pub fn surgery(matches: &ArgMatches) -> io::Result { let exec_file = fs::OpenOptions::new() .read(true) .write(true) - .open(&matches.value_of(OUT).unwrap())?; + .open(out_filename)?; let max_out_len = md.exec_len + app_data.len() as u64 + md.load_align_constraint; exec_file.set_len(max_out_len)?; From e2411ea83fce9f918b3b9bcb9b5b7b3bf0810607 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 14 Sep 2021 23:06:22 -0700 Subject: [PATCH 007/136] Add surgical linking to frontend with simple dummy lib creation --- Cargo.lock | 2 + cli/src/build.rs | 50 +++++++++++---------- examples/.gitignore | 3 ++ linker/Cargo.toml | 2 +- linker/src/lib.rs | 105 ++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 129 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c17cb03261..8d5d3bb0b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2517,7 +2517,9 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" dependencies = [ + "crc32fast", "flate2", + "indexmap", "memchr", ] diff --git a/cli/src/build.rs b/cli/src/build.rs index fec21f420b..f3db76b80a 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -232,21 +232,35 @@ pub fn build_file<'a>( // Step 2: link the precompiled host and compiled app let link_start = SystemTime::now(); - let (mut child, binary_path) = // TODO use lld - link( - target, - binary_path, - &[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()], - link_type - ) - .map_err(|_| { - todo!("gracefully handle `rustc` failing to spawn."); + let outcome = if surgically_link { + roc_linker::link_preprocessed_host(target, &host_input_path, &app_o_file, &binary_path) + .map_err(|_| { + todo!("gracefully handle failing to surgically link"); + })?; + BuildOutcome::NoProblems + } else { + 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()], + link_type + ) + .map_err(|_| { + todo!("gracefully handle `ld` failing to spawn."); + })?; + + let exit_status = child.wait().map_err(|_| { + todo!("gracefully handle error after `ld` spawned"); })?; - let cmd_result = child.wait().map_err(|_| { - todo!("gracefully handle error after `rustc` spawned"); - }); - + // TODO change this to report whether there were errors or warnings! + if exit_status.success() { + BuildOutcome::NoProblems + } else { + BuildOutcome::Errors + } + }; let linking_time = link_start.elapsed().unwrap(); if emit_timings { @@ -255,16 +269,6 @@ pub fn build_file<'a>( let total_time = compilation_start.elapsed().unwrap(); - // If the cmd errored out, return the Err. - let exit_status = cmd_result?; - - // TODO change this to report whether there were errors or warnings! - let outcome = if exit_status.success() { - BuildOutcome::NoProblems - } else { - BuildOutcome::Errors - }; - Ok(BuiltFile { binary_path, outcome, diff --git a/examples/.gitignore b/examples/.gitignore index 874bbd86e5..000f652572 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -4,3 +4,6 @@ app libhost.a roc_app.ll roc_app.bc +dynhost +preprocessedhost +metadata diff --git a/linker/Cargo.toml b/linker/Cargo.toml index b5babe9ebf..843bf6caac 100644 --- a/linker/Cargo.toml +++ b/linker/Cargo.toml @@ -26,7 +26,7 @@ bumpalo = { version = "3.6", features = ["collections"] } clap = { git = "https://github.com/rtfeldman/clap", branch = "master" } iced-x86 = "1.14" memmap2 = "0.3" -object = { version = "0.26", features = ["read"] } +object = { version = "0.26", features = ["read", "write"] } serde = { version = "1.0", features = ["derive"] } bincode = "1.3" target-lexicon = "0.12.2" diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 26eff91133..fd26990c18 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -2,11 +2,12 @@ use bincode::{deserialize_from, serialize_into}; use clap::{App, AppSettings, Arg, ArgMatches}; use iced_x86::{Decoder, DecoderOptions, Instruction, OpCodeOperandKind, OpKind}; use memmap2::{Mmap, MmapMut}; +use object::write; use object::{elf, endian}; use object::{ - Architecture, BinaryFormat, CompressedFileRange, CompressionFormat, LittleEndian, NativeEndian, - Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, Section, SectionIndex, - Symbol, SymbolIndex, SymbolSection, + Architecture, BinaryFormat, CompressedFileRange, CompressionFormat, Endianness, LittleEndian, + NativeEndian, Object, ObjectSection, ObjectSymbol, RelocationKind, RelocationTarget, Section, + SectionIndex, Symbol, SymbolFlags, SymbolIndex, SymbolKind, SymbolScope, SymbolSection, }; use roc_build::link::{rebuild_host, LinkType}; use roc_collections::all::MutMap; @@ -20,6 +21,7 @@ use std::io::{BufReader, BufWriter}; use std::mem; use std::os::raw::c_char; use std::path::Path; +use std::process::Command; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; use tempfile::{Builder, NamedTempFile}; @@ -139,16 +141,100 @@ pub fn build_and_preprocess_host( host_input_path: &Path, exposed_to_host: Vec, ) -> io::Result<()> { - let lib = generate_dynamic_lib(exposed_to_host)?; - rebuild_host(opt_level, target, host_input_path, Some(&lib.path())); + let dummy_lib = generate_dynamic_lib(target, exposed_to_host)?; + let dummy_lib = dummy_lib.path(); + rebuild_host(opt_level, target, host_input_path, Some(&dummy_lib)); + let dynhost = host_input_path.with_file_name("dynhost"); + let metadata = host_input_path.with_file_name("metadata"); + let prehost = host_input_path.with_file_name("preprocessedhost"); + if preprocess_impl( + dynhost.to_str().unwrap(), + metadata.to_str().unwrap(), + prehost.to_str().unwrap(), + dummy_lib.to_str().unwrap(), + false, + false, + )? != 0 + { + panic!("Failed to preprocess host"); + } Ok(()) } -fn generate_dynamic_lib(exposed_to_host: Vec) -> io::Result { - for sym in exposed_to_host { - println!("{}", sym); +pub fn link_preprocessed_host( + _target: &Triple, + host_input_path: &Path, + roc_app_obj: &Path, + binary_path: &Path, +) -> io::Result<()> { + let metadata = host_input_path.with_file_name("metadata"); + let prehost = host_input_path.with_file_name("preprocessedhost"); + if surgery_impl( + roc_app_obj.to_str().unwrap(), + metadata.to_str().unwrap(), + prehost.to_str().unwrap(), + false, + false, + )? != 0 + { + panic!("Failed to surgically link host"); } + std::fs::rename(prehost, binary_path) +} + +fn generate_dynamic_lib( + _target: &Triple, + exposed_to_host: Vec, +) -> io::Result { + let dummy_obj_file = Builder::new().prefix("roc_lib").suffix(".o").tempfile()?; + let dummy_obj_file = dummy_obj_file.path(); let dummy_lib_file = Builder::new().prefix("roc_lib").suffix(".so").tempfile()?; + + // TODO deal with other architectures here. + let mut out_object = + write::Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little); + + let text_section = out_object.section_id(write::StandardSection::Text); + for sym in exposed_to_host { + out_object.add_symbol(write::Symbol { + name: format!("roc__{}_1_exposed", sym).as_bytes().to_vec(), + value: 0, + size: 0, + kind: SymbolKind::Text, + scope: SymbolScope::Dynamic, + weak: false, + section: write::SymbolSection::Section(text_section), + flags: SymbolFlags::None, + }); + } + std::fs::write( + &dummy_obj_file, + out_object.write().expect("failed to build output object"), + ) + .expect("failed to write object to file"); + + let output = Command::new("ld") + .args(&[ + "-shared", + dummy_obj_file.to_str().unwrap(), + "-o", + dummy_lib_file.path().to_str().unwrap(), + ]) + .output() + .unwrap(); + + if !output.status.success() { + match std::str::from_utf8(&output.stderr) { + Ok(stderr) => panic!( + "Failed to link dummy shared library - stderr of the `ld` command was:\n{}", + stderr + ), + Err(utf8_err) => panic!( + "Failed to link dummy shared library - stderr of the `ld` command was invalid utf8 ({:?})", + utf8_err + ), + } + } Ok(dummy_lib_file) } @@ -454,6 +540,7 @@ fn preprocess_impl( || inst.is_jmp_far_indirect() || inst.is_jmp_near_indirect()) && !indirect_warning_given + && verbose { indirect_warning_given = true; println!(); @@ -538,7 +625,7 @@ fn preprocess_impl( ) as usize; let c_buf: *const c_char = dynstr_data[dynstr_off..].as_ptr() as *const i8; let c_str = unsafe { CStr::from_ptr(c_buf) }.to_str().unwrap(); - if c_str == shared_lib_name { + if Path::new(c_str).file_name().unwrap().to_str().unwrap() == shared_lib_name { shared_lib_index = Some(dyn_lib_index); if verbose { println!( From e8e7f9cad8408e3b24a8674ac14d926cb0aba91f Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 15 Sep 2021 08:58:23 -0700 Subject: [PATCH 008/136] Add executable file permissions --- linker/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/linker/src/lib.rs b/linker/src/lib.rs index fd26990c18..7d57238ec9 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -20,6 +20,7 @@ use std::io; use std::io::{BufReader, BufWriter}; use std::mem; use std::os::raw::c_char; +use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::process::Command; use std::time::{Duration, SystemTime}; @@ -1525,6 +1526,12 @@ fn surgery_impl( let flushing_data_duration = flushing_data_start.elapsed().unwrap(); exec_file.set_len(offset as u64 + 1)?; + + // Make sure the final executable has permision to execute. + let mut perms = fs::metadata(out_filename)?.permissions(); + perms.set_mode(perms.mode() | 0o111); + fs::set_permissions(out_filename, perms)?; + let total_duration = total_start.elapsed().unwrap(); if verbose || time { From 3d18d34135896addfd9b8a0b483682cb0deae2aa Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 15 Sep 2021 18:45:20 +0200 Subject: [PATCH 009/136] Insert extra argument when returning on stack --- compiler/gen_wasm/src/backend.rs | 68 ++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index e43a4228fe..2520019d86 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -11,6 +11,7 @@ use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; +use crate::PTR_TYPE; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -25,6 +26,12 @@ struct LabelId(u32); #[derive(Debug)] struct SymbolStorage(LocalId, WasmLayout); +enum LocalKind { + Parameter, + Variable, +} + +// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43) pub struct WasmBackend<'a> { // Module: Wasm AST pub builder: ModuleBuilder, @@ -36,11 +43,11 @@ pub struct WasmBackend<'a> { // Functions: Wasm AST instructions: std::vec::Vec, - ret_type: ValueType, arg_types: std::vec::Vec, locals: std::vec::Vec, // Functions: internal state & IR mappings + next_local_index: u32, stack_memory: u32, symbol_storage_map: MutMap, /// how many blocks deep are we (used for jumps) @@ -61,11 +68,11 @@ impl<'a> WasmBackend<'a> { // Functions: Wasm AST instructions: std::vec::Vec::with_capacity(256), - ret_type: ValueType::I32, arg_types: std::vec::Vec::with_capacity(8), locals: std::vec::Vec::with_capacity(32), // Functions: internal state & IR mappings + next_local_index: 0, stack_memory: 0, symbol_storage_map: MutMap::default(), block_depth: 0, @@ -80,6 +87,7 @@ impl<'a> WasmBackend<'a> { self.locals.clear(); // Functions: internal state & IR mappings + self.next_local_index = 0; self.stack_memory = 0; self.symbol_storage_map.clear(); self.joinpoint_label_map.clear(); @@ -89,38 +97,30 @@ impl<'a> WasmBackend<'a> { pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { let ret_layout = WasmLayout::new(&proc.ret_layout); - if let WasmLayout::StackMemory { .. } = ret_layout { - return Err(format!( - "Not yet implemented: Returning values to callee stack memory {:?} {:?}", - proc.name, sym - )); - } - - self.ret_type = ret_layout.value_type(); - self.arg_types.reserve(proc.args.len()); + let sig_builder = if let WasmLayout::StackMemory { .. } = ret_layout { + self.arg_types.push(PTR_TYPE); + self.next_local_index += 1; + builder::signature() + } else { + builder::signature().with_result(ret_layout.value_type()) + }; for (layout, symbol) in proc.args { - let wasm_layout = WasmLayout::new(layout); - self.arg_types.push(wasm_layout.value_type()); - self.insert_local(wasm_layout, *symbol); + self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); } + let signature = sig_builder.with_params(self.arg_types.clone()).build_sig(); + self.build_stmt(&proc.body, &proc.ret_layout)?; - let signature = builder::signature() - .with_params(self.arg_types.clone()) // requires std::Vec, not Bumpalo - .with_result(self.ret_type) - .build_sig(); - // functions must end with an End instruction/opcode - let mut instructions = self.instructions.clone(); - instructions.push(Instruction::End); + self.instructions.push(Instruction::End); let function_def = builder::function() .with_signature(signature) .body() .with_locals(self.locals.clone()) - .with_instructions(Instructions::new(instructions)) + .with_instructions(Instructions::new(self.instructions.clone())) .build() // body .build(); // function @@ -132,15 +132,24 @@ impl<'a> WasmBackend<'a> { Ok(function_index) } - fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol) -> LocalId { + fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol, kind: LocalKind) -> LocalId { self.stack_memory += layout.stack_memory(); - let index = self.symbol_storage_map.len(); - if index >= self.arg_types.len() { - self.locals.push(Local::new(1, layout.value_type())); + + match kind { + LocalKind::Parameter => { + self.arg_types.push(layout.value_type()); + } + LocalKind::Variable => { + self.locals.push(Local::new(1, layout.value_type())); + } } - let local_id = LocalId(index as u32); + + let local_id = LocalId(self.next_local_index); + self.next_local_index += 1; + let storage = SymbolStorage(local_id, layout); self.symbol_storage_map.insert(symbol, storage); + local_id } @@ -193,7 +202,7 @@ impl<'a> WasmBackend<'a> { Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - let local_id = self.insert_local(wasm_layout, *sym); + let local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable); self.build_expr(sym, expr, layout)?; self.instructions.push(SetLocal(local_id.0)); @@ -275,7 +284,8 @@ impl<'a> WasmBackend<'a> { let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); - let local_id = self.insert_local(wasm_layout, parameter.symbol); + let local_id = + self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable); jp_parameter_local_ids.push(local_id); } From da28b669bbea5f9a272c63284f8c71739d4ac1b9 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 15 Sep 2021 11:45:44 -0700 Subject: [PATCH 010/136] Get zig host working --- compiler/build/src/link.rs | 15 ++- examples/benchmarks/platform/host.zig | 10 ++ examples/effect/thing/platform-dir/host.zig | 10 ++ examples/hello-rust/platform/host.c | 9 +- examples/hello-world/platform/host.c | 120 ++++++++++---------- examples/hello-zig/platform/host.zig | 10 ++ examples/quicksort/platform/host.zig | 10 ++ linker/src/lib.rs | 57 ++++++---- linker/tests/fib/.gitignore | 6 +- 9 files changed, 157 insertions(+), 90 deletions(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index d6de4b12d9..42f8d40bf9 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -92,9 +92,9 @@ pub fn build_zig_host_native( .env("PATH", env_path) .env("HOME", env_home); if let Some(shared_lib_path) = shared_lib_path { - command.args(&["build-exe", shared_lib_path.to_str().unwrap()]); + command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); } else { - command.arg("build-obj"); + command.args(&["build-obj", "-fPIC"]); } command.args(&[ zig_host_src, @@ -108,7 +108,6 @@ pub fn build_zig_host_native( // include libc "--library", "c", - "-fPIC", // cross-compile? "-target", target, @@ -176,9 +175,9 @@ pub fn build_zig_host_native( .env("PATH", &env_path) .env("HOME", &env_home); if let Some(shared_lib_path) = shared_lib_path { - command.args(&["build-exe", shared_lib_path.to_str().unwrap()]); + command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); } else { - command.arg("build-obj"); + command.args(&["build-obj", "-fPIC"]); } command.args(&[ zig_host_src, @@ -195,7 +194,6 @@ pub fn build_zig_host_native( // include libc "--library", "c", - "-fPIC", ]); if matches!(opt_level, OptLevel::Optimize) { command.args(&["-O", "ReleaseSafe"]); @@ -267,10 +265,11 @@ pub fn build_c_host_native( .env("PATH", &env_path) .env("HOME", &env_home) .args(sources) - .args(&["-fPIC", "-o", dest]); + .args(&["-o", dest]); if let Some(shared_lib_path) = shared_lib_path { command.args(&[ shared_lib_path.to_str().unwrap(), + "-fPIE", "-lm", "-lpthread", "-ldl", @@ -278,7 +277,7 @@ pub fn build_c_host_native( "-lutil", ]); } else { - command.arg("-c"); + command.args(&["-fPIC", "-c"]); } if matches!(opt_level, OptLevel::Optimize) { command.arg("-O2"); diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index 503a66ed2d..36c84a8321 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -33,6 +33,8 @@ const Align = extern struct { a: usize, b: usize }; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void; extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void; +extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; +extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; const DEBUG: bool = false; @@ -74,6 +76,14 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { std.process.exit(0); } +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{ + return memcpy(dst, src, size); +} + +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{ + return memset(dst, value, size); +} + const Unit = extern struct {}; pub fn main() u8 { diff --git a/examples/effect/thing/platform-dir/host.zig b/examples/effect/thing/platform-dir/host.zig index 063d28973f..e9d02cb3a2 100644 --- a/examples/effect/thing/platform-dir/host.zig +++ b/examples/effect/thing/platform-dir/host.zig @@ -32,6 +32,8 @@ extern fn roc__mainForHost_1_Fx_result_size() i64; extern fn malloc(size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; +extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; +extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { return malloc(size); @@ -52,6 +54,14 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { std.process.exit(0); } +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{ + return memcpy(dst, src, size); +} + +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{ + return memset(dst, value, size); +} + const Unit = extern struct {}; pub export fn main() u8 { diff --git a/examples/hello-rust/platform/host.c b/examples/hello-rust/platform/host.c index 0378c69589..9b91965724 100644 --- a/examples/hello-rust/platform/host.c +++ b/examples/hello-rust/platform/host.c @@ -1,7 +1,12 @@ #include +#include extern int rust_main(); -int main() { - return rust_main(); +int main() { return rust_main(); } + +void *roc_memcpy(void *dest, const void *src, size_t n) { + return memcpy(dest, src, n); } + +void *roc_memset(void *str, int c, size_t n) { return memset(str, c, n); } \ No newline at end of file diff --git a/examples/hello-world/platform/host.c b/examples/hello-world/platform/host.c index f968d0c763..4b45c20482 100644 --- a/examples/hello-world/platform/host.c +++ b/examples/hello-world/platform/host.c @@ -1,89 +1,91 @@ -#include -#include -#include #include -#include #include +#include +#include +#include +#include -void* roc_alloc(size_t size, unsigned int alignment) { - return malloc(size); +void* roc_alloc(size_t size, unsigned int alignment) { return malloc(size); } + +void* roc_realloc(void* ptr, size_t old_size, size_t new_size, + unsigned int alignment) { + return realloc(ptr, new_size); } -void* roc_realloc(void* ptr, size_t old_size, size_t new_size, unsigned int alignment) { - return realloc(ptr, new_size); -} - -void roc_dealloc(void* ptr, unsigned int alignment) { - free(ptr); -} +void roc_dealloc(void* ptr, unsigned int alignment) { free(ptr); } void roc_panic(void* ptr, unsigned int alignment) { - char* msg = (char *)ptr; - fprintf(stderr, "Application crashed with message\n\n %s\n\nShutting down\n", msg); - exit(0); + char* msg = (char*)ptr; + fprintf(stderr, + "Application crashed with message\n\n %s\n\nShutting down\n", msg); + exit(0); } +void* roc_memcpy(void* dest, const void* src, size_t n) { + return memcpy(dest, src, n); +} + +void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); } + struct RocStr { - char* bytes; - size_t len; + char* bytes; + size_t len; }; -bool is_small_str(struct RocStr str) { - return ((ssize_t)str.len) < 0; -} +bool is_small_str(struct RocStr str) { return ((ssize_t)str.len) < 0; } // Determine the length of the string, taking into // account the small string optimization size_t roc_str_len(struct RocStr str) { - char* bytes = (char*)&str; - char last_byte = bytes[sizeof(str) - 1]; - char last_byte_xored = last_byte ^ 0b10000000; - size_t small_len = (size_t)(last_byte_xored); - size_t big_len = str.len; + char* bytes = (char*)&str; + char last_byte = bytes[sizeof(str) - 1]; + char last_byte_xored = last_byte ^ 0b10000000; + size_t small_len = (size_t)(last_byte_xored); + size_t big_len = str.len; - // Avoid branch misprediction costs by always - // determining both small_len and big_len, - // so this compiles to a cmov instruction. - if (is_small_str(str)) { - return small_len; - } else { - return big_len; - } + // Avoid branch misprediction costs by always + // determining both small_len and big_len, + // so this compiles to a cmov instruction. + if (is_small_str(str)) { + return small_len; + } else { + return big_len; + } } struct RocCallResult { - size_t flag; - struct RocStr content; + size_t flag; + struct RocStr content; }; -extern void roc__mainForHost_1_exposed(struct RocCallResult *re); +extern void roc__mainForHost_1_exposed(struct RocCallResult* re); int main() { - // Make space for the Roc call result - struct RocCallResult call_result; + // Make space for the Roc call result + struct RocCallResult call_result; - // Call Roc to populate call_result - roc__mainForHost_1_exposed(&call_result); + // Call Roc to populate call_result + roc__mainForHost_1_exposed(&call_result); - // Determine str_len and the str_bytes pointer, - // taking into account the small string optimization. - struct RocStr str = call_result.content; - size_t str_len = roc_str_len(str); - char* str_bytes; + // Determine str_len and the str_bytes pointer, + // taking into account the small string optimization. + struct RocStr str = call_result.content; + size_t str_len = roc_str_len(str); + char* str_bytes; - if (is_small_str(str)) { - str_bytes = (char*)&str; - } else { - str_bytes = str.bytes; - } + if (is_small_str(str)) { + str_bytes = (char*)&str; + } else { + str_bytes = str.bytes; + } - // Write to stdout - if (write(1, str_bytes, str_len) >= 0) { - // Writing succeeded! - return 0; - } else { - printf("Error writing to stdout: %s\n", strerror(errno)); + // Write to stdout + if (write(1, str_bytes, str_len) >= 0) { + // Writing succeeded! + return 0; + } else { + printf("Error writing to stdout: %s\n", strerror(errno)); - return 1; - } + return 1; + } } diff --git a/examples/hello-zig/platform/host.zig b/examples/hello-zig/platform/host.zig index 8384c996d3..23e87920fb 100644 --- a/examples/hello-zig/platform/host.zig +++ b/examples/hello-zig/platform/host.zig @@ -23,6 +23,8 @@ const Align = extern struct { a: usize, b: usize }; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void; extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void; +extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; +extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { _ = alignment; @@ -51,6 +53,14 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { std.process.exit(0); } +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{ + return memcpy(dst, src, size); +} + +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{ + return memset(dst, value, size); +} + const mem = std.mem; const Allocator = mem.Allocator; diff --git a/examples/quicksort/platform/host.zig b/examples/quicksort/platform/host.zig index 38fb29f699..328562f28b 100644 --- a/examples/quicksort/platform/host.zig +++ b/examples/quicksort/platform/host.zig @@ -26,6 +26,8 @@ const Align = extern struct { a: usize, b: usize }; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void; extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void; +extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; +extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; const DEBUG: bool = false; @@ -67,6 +69,14 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { std.process.exit(0); } +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{ + return memcpy(dst, src, size); +} + +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{ + return memset(dst, value, size); +} + // warning! the array is currently stack-allocated so don't make this too big const NUM_NUMS = 100; diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 7d57238ec9..f17e1f4888 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -170,17 +170,18 @@ pub fn link_preprocessed_host( ) -> io::Result<()> { let metadata = host_input_path.with_file_name("metadata"); let prehost = host_input_path.with_file_name("preprocessedhost"); + std::fs::copy(prehost, binary_path)?; if surgery_impl( roc_app_obj.to_str().unwrap(), metadata.to_str().unwrap(), - prehost.to_str().unwrap(), + binary_path.to_str().unwrap(), false, false, )? != 0 { panic!("Failed to surgically link host"); } - std::fs::rename(prehost, binary_path) + Ok(()) } fn generate_dynamic_lib( @@ -197,16 +198,24 @@ fn generate_dynamic_lib( let text_section = out_object.section_id(write::StandardSection::Text); for sym in exposed_to_host { - out_object.add_symbol(write::Symbol { - name: format!("roc__{}_1_exposed", sym).as_bytes().to_vec(), - value: 0, - size: 0, - kind: SymbolKind::Text, - scope: SymbolScope::Dynamic, - weak: false, - section: write::SymbolSection::Section(text_section), - flags: SymbolFlags::None, - }); + for name in &[ + format!("roc__{}_1_exposed", sym), + format!("roc__{}_1_Fx_caller", sym), + format!("roc__{}_1_Fx_size", sym), + format!("roc__{}_1_Fx_result_size", sym), + format!("roc__{}_size", sym), + ] { + out_object.add_symbol(write::Symbol { + name: name.as_bytes().to_vec(), + value: 0, + size: 0, + kind: SymbolKind::Text, + scope: SymbolScope::Dynamic, + weak: false, + section: write::SymbolSection::Section(text_section), + flags: SymbolFlags::None, + }); + } } std::fs::write( &dummy_obj_file, @@ -994,16 +1003,22 @@ fn preprocess_impl( } let saving_metadata_start = SystemTime::now(); - let output = fs::File::create(metadata_filename)?; - let output = BufWriter::new(output); - if let Err(err) = serialize_into(output, &md) { - println!("Failed to serialize metadata: {}", err); - return Ok(-1); - }; + // This block ensure that the metadata is fully written and timed before continuing. + { + let output = fs::File::create(metadata_filename)?; + let output = BufWriter::new(output); + if let Err(err) = serialize_into(output, &md) { + println!("Failed to serialize metadata: {}", err); + return Ok(-1); + }; + } let saving_metadata_duration = saving_metadata_start.elapsed().unwrap(); let flushing_data_start = SystemTime::now(); out_mmap.flush()?; + // Also drop files to to ensure data is fully written here. + drop(out_mmap); + drop(out_file); let flushing_data_duration = flushing_data_start.elapsed().unwrap(); let total_duration = total_start.elapsed().unwrap(); @@ -1523,9 +1538,11 @@ fn surgery_impl( let flushing_data_start = SystemTime::now(); exec_mmap.flush()?; - let flushing_data_duration = flushing_data_start.elapsed().unwrap(); - + // Also drop files to to ensure data is fully written here. + drop(exec_mmap); exec_file.set_len(offset as u64 + 1)?; + drop(exec_file); + let flushing_data_duration = flushing_data_start.elapsed().unwrap(); // Make sure the final executable has permision to execute. let mut perms = fs::metadata(out_filename)?.permissions(); diff --git a/linker/tests/fib/.gitignore b/linker/tests/fib/.gitignore index a229b0fd25..3db2165e59 100644 --- a/linker/tests/fib/.gitignore +++ b/linker/tests/fib/.gitignore @@ -3,4 +3,8 @@ fib zig-cache zig-out -*.o \ No newline at end of file +*.o + +dynhost +preprocessedhost +metadata From b1e02315d003840f2a5091ea694af014b9495caf Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 15 Sep 2021 12:28:19 -0700 Subject: [PATCH 011/136] Strip debug info from zig --- compiler/build/src/link.rs | 3 +++ linker/src/lib.rs | 1 + 2 files changed, 4 insertions(+) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 42f8d40bf9..1d5a834c55 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -108,6 +108,7 @@ pub fn build_zig_host_native( // include libc "--library", "c", + "--strip", // cross-compile? "-target", target, @@ -194,6 +195,7 @@ pub fn build_zig_host_native( // include libc "--library", "c", + "--strip", ]); if matches!(opt_level, OptLevel::Optimize) { command.args(&["-O", "ReleaseSafe"]); @@ -244,6 +246,7 @@ pub fn build_zig_host_wasm32( // "wasm32-wasi", // "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll", "-fPIC", + "--strip", ]); if matches!(opt_level, OptLevel::Optimize) { command.args(&["-O", "ReleaseSafe"]); diff --git a/linker/src/lib.rs b/linker/src/lib.rs index f17e1f4888..3b1e71be90 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -198,6 +198,7 @@ fn generate_dynamic_lib( let text_section = out_object.section_id(write::StandardSection::Text); for sym in exposed_to_host { + // TODO properly generate this list. for name in &[ format!("roc__{}_1_exposed", sym), format!("roc__{}_1_Fx_caller", sym), From e4b3402369965cc6e9ceac828c8884be37342bd7 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 15 Sep 2021 15:16:39 -0700 Subject: [PATCH 012/136] Create dummy lib as libapp.so --- examples/.gitignore | 1 + linker/src/lib.rs | 16 +++++++++------- linker/tests/fib/.gitignore | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/.gitignore b/examples/.gitignore index 000f652572..c0e522cc76 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -7,3 +7,4 @@ roc_app.bc dynhost preprocessedhost metadata +libapp.so \ No newline at end of file diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 3b1e71be90..4d815c10b7 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -25,7 +25,7 @@ use std::path::Path; use std::process::Command; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; -use tempfile::{Builder, NamedTempFile}; +use tempfile::Builder; mod metadata; @@ -142,8 +142,8 @@ pub fn build_and_preprocess_host( host_input_path: &Path, exposed_to_host: Vec, ) -> io::Result<()> { - let dummy_lib = generate_dynamic_lib(target, exposed_to_host)?; - let dummy_lib = dummy_lib.path(); + let dummy_lib = host_input_path.with_file_name("libapp.so"); + generate_dynamic_lib(target, exposed_to_host, &dummy_lib)?; rebuild_host(opt_level, target, host_input_path, Some(&dummy_lib)); let dynhost = host_input_path.with_file_name("dynhost"); let metadata = host_input_path.with_file_name("metadata"); @@ -187,10 +187,10 @@ pub fn link_preprocessed_host( fn generate_dynamic_lib( _target: &Triple, exposed_to_host: Vec, -) -> io::Result { + dummy_lib_path: &Path, +) -> io::Result<()> { let dummy_obj_file = Builder::new().prefix("roc_lib").suffix(".o").tempfile()?; let dummy_obj_file = dummy_obj_file.path(); - let dummy_lib_file = Builder::new().prefix("roc_lib").suffix(".so").tempfile()?; // TODO deal with other architectures here. let mut out_object = @@ -227,9 +227,11 @@ fn generate_dynamic_lib( let output = Command::new("ld") .args(&[ "-shared", + "-soname", + dummy_lib_path.file_name().unwrap().to_str().unwrap(), dummy_obj_file.to_str().unwrap(), "-o", - dummy_lib_file.path().to_str().unwrap(), + dummy_lib_path.to_str().unwrap(), ]) .output() .unwrap(); @@ -246,7 +248,7 @@ fn generate_dynamic_lib( ), } } - Ok(dummy_lib_file) + Ok(()) } pub fn preprocess(matches: &ArgMatches) -> io::Result { diff --git a/linker/tests/fib/.gitignore b/linker/tests/fib/.gitignore index 3db2165e59..a3c0d77f6d 100644 --- a/linker/tests/fib/.gitignore +++ b/linker/tests/fib/.gitignore @@ -8,3 +8,4 @@ zig-out dynhost preprocessedhost metadata +libapp.so \ No newline at end of file From b56606c5acec7e5f178ea46f1291a508f9b76918 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Wed, 15 Sep 2021 15:38:40 -0700 Subject: [PATCH 013/136] Add comment explaining rust host failure --- linker/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 4d815c10b7..3613489e4f 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -369,6 +369,9 @@ fn preprocess_impl( println!("PLT File Offset: {:+x}", plt_offset); } + // TODO: it looks like we may need to support global data host relocations. + // Rust host look to be using them by default instead of the plt. + // I think this is due to first linking into a static lib and then linking to the c wrapper. let plt_relocs = (match exec_obj.dynamic_relocations() { Some(relocs) => relocs, None => { From d8d147375df86735e99cc1938f33881527dd0fe8 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 16 Sep 2021 20:19:28 -0700 Subject: [PATCH 014/136] update fib gitignore --- examples/fib/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/fib/.gitignore b/examples/fib/.gitignore index 76d4bb83f8..41d1223f94 100644 --- a/examples/fib/.gitignore +++ b/examples/fib/.gitignore @@ -1 +1,2 @@ add +fib From 66a7a3aa074075122e9842f051305bde5a94fe8d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 16 Sep 2021 22:34:55 -0700 Subject: [PATCH 015/136] Make clippy happy again --- cli/src/build.rs | 6 +++--- compiler/build/src/link.rs | 2 ++ linker/src/lib.rs | 14 +++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index eed7578b3e..defb8e0781 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -101,7 +101,7 @@ pub fn build_file<'a>( opt_level, surgically_link, host_input_path.clone(), - target.clone(), + target, loaded .exposed_to_host .keys() @@ -227,7 +227,7 @@ pub fn build_file<'a>( // Step 2: link the precompiled host and compiled app let link_start = SystemTime::now(); let outcome = if surgically_link { - roc_linker::link_preprocessed_host(target, &host_input_path, &app_o_file, &binary_path) + roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path) .map_err(|_| { todo!("gracefully handle failing to surgically link"); })?; @@ -274,7 +274,7 @@ fn spawn_rebuild_thread( opt_level: OptLevel, surgically_link: bool, host_input_path: PathBuf, - target: Triple, + target: &Triple, exported_symbols: Vec, ) -> std::thread::JoinHandle { let thread_local_target = target.clone(); diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 1d5a834c55..6d61145cc1 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -76,6 +76,7 @@ fn find_wasi_libc_path() -> PathBuf { } #[cfg(not(target_os = "macos"))] +#[allow(clippy::too_many_arguments)] pub fn build_zig_host_native( env_path: &str, env_home: &str, @@ -120,6 +121,7 @@ pub fn build_zig_host_native( } #[cfg(target_os = "macos")] +#[allow(clippy::too_many_arguments)] pub fn build_zig_host_native( env_path: &str, env_home: &str, diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 3613489e4f..2324671cc9 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -253,10 +253,10 @@ fn generate_dynamic_lib( pub fn preprocess(matches: &ArgMatches) -> io::Result { preprocess_impl( - &matches.value_of(EXEC).unwrap(), - &matches.value_of(METADATA).unwrap(), - &matches.value_of(OUT).unwrap(), - &matches.value_of(SHARED_LIB).unwrap(), + matches.value_of(EXEC).unwrap(), + matches.value_of(METADATA).unwrap(), + matches.value_of(OUT).unwrap(), + matches.value_of(SHARED_LIB).unwrap(), matches.is_present(FLAG_VERBOSE), matches.is_present(FLAG_TIME), ) @@ -1061,9 +1061,9 @@ fn preprocess_impl( pub fn surgery(matches: &ArgMatches) -> io::Result { surgery_impl( - &matches.value_of(APP).unwrap(), - &matches.value_of(METADATA).unwrap(), - &matches.value_of(OUT).unwrap(), + matches.value_of(APP).unwrap(), + matches.value_of(METADATA).unwrap(), + matches.value_of(OUT).unwrap(), matches.is_present(FLAG_VERBOSE), matches.is_present(FLAG_TIME), ) From 036503c750a4c7da732d5055c31120722d40fde3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 17 Sep 2021 19:42:29 +0100 Subject: [PATCH 016/136] copy returned structs to caller stack --- compiler/gen_wasm/src/backend.rs | 39 ++++++++++++-------- compiler/gen_wasm/src/layout.rs | 61 +++++++++++++++++++++----------- compiler/gen_wasm/src/lib.rs | 49 ++++++++++++++++++++++++- 3 files changed, 114 insertions(+), 35 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 2520019d86..d07adc266e 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -10,16 +10,13 @@ use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use crate::layout::WasmLayout; -use crate::PTR_TYPE; +use crate::layout::{WasmLayout}; +use crate::{PTR_TYPE, copy_memory, LocalId}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) const UNUSED_DATA_SECTION_BYTES: u32 = 1024; -#[derive(Clone, Copy, Debug)] -struct LocalId(u32); - #[derive(Clone, Copy, Debug)] struct LabelId(u32); @@ -212,16 +209,30 @@ impl<'a> WasmBackend<'a> { } Stmt::Ret(sym) => { - if let Some(SymbolStorage(local_id, _)) = self.symbol_storage_map.get(sym) { - self.instructions.push(GetLocal(local_id.0)); - self.instructions.push(Return); - Ok(()) - } else { - Err(format!( - "Not yet implemented: returning values with layout {:?}", - ret_layout - )) + use crate::layout::WasmLayout::*; + + let SymbolStorage(local_id, wasm_layout) = + self.symbol_storage_map.get(sym).unwrap(); + + match wasm_layout { + LocalOnly(_, _) | HeapMemory => { + self.instructions.push(GetLocal(local_id.0)); + self.instructions.push(Return); + } + + StackMemory { + size, + alignment_bytes, + } => { + let from = local_id.clone(); + let to = LocalId(0); + let copy_size: u32 = *size; + let copy_alignment_bytes: u32 = *alignment_bytes; + copy_memory(&mut self.instructions, from, to, copy_size, copy_alignment_bytes)?; + } } + + Ok(()) } Stmt::Switch { diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 46acf9fad8..d45a502055 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,7 +1,7 @@ -use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; +use parity_wasm::elements::ValueType; use roc_mono::layout::{Layout, UnionLayout}; -use crate::{ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE}; +use crate::{PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls #[derive(Debug)] @@ -11,7 +11,7 @@ pub enum WasmLayout { LocalOnly(ValueType, u32), // A `local` pointing to stack memory - StackMemory(u32), + StackMemory { size: u32, alignment_bytes: u32 }, // A `local` pointing to heap memory HeapMemory, @@ -24,6 +24,7 @@ impl WasmLayout { use ValueType::*; let size = layout.stack_size(PTR_SIZE); + let alignment_bytes = layout.alignment_bytes(PTR_SIZE); match layout { Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), @@ -49,7 +50,10 @@ impl WasmLayout { ) | Layout::Struct(_) | Layout::LambdaSet(_) - | Layout::Union(NonRecursive(_)) => Self::StackMemory(size), + | Layout::Union(NonRecursive(_)) => Self::StackMemory { + size, + alignment_bytes, + }, Layout::Union( Recursive(_) @@ -70,11 +74,12 @@ impl WasmLayout { pub fn stack_memory(&self) -> u32 { match self { - Self::StackMemory(size) => *size, + Self::StackMemory { size, .. } => *size, _ => 0, } } + /* #[allow(dead_code)] fn load(&self, offset: u32) -> Result { use crate::layout::WasmLayout::*; @@ -104,34 +109,50 @@ impl WasmLayout { )), } } + */ + /* + TODO: this is probably in the wrong place, need specific locals for the StackMemory case. + Come back to it later. #[allow(dead_code)] - fn store(&self, offset: u32) -> Result { + pub fn store(&self, offset: u32, instructions: &mut Vec) -> Result<(), String> { use crate::layout::WasmLayout::*; use ValueType::*; + let mut result = Ok(()); match self { - LocalOnly(I32, 4) => Ok(I32Store(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Store16(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Store8(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Store(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Store(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Store(ALIGN_4, offset)), + LocalOnly(I32, 4) => instructions.push(I32Store(ALIGN_4, offset)), + LocalOnly(I32, 2) => instructions.push(I32Store16(ALIGN_2, offset)), + LocalOnly(I32, 1) => instructions.push(I32Store8(ALIGN_1, offset)), + LocalOnly(I64, 8) => instructions.push(I64Store(ALIGN_8, offset)), + LocalOnly(F64, 8) => instructions.push(F64Store(ALIGN_8, offset)), + LocalOnly(F32, 4) => instructions.push(F32Store(ALIGN_4, offset)), + + StackMemory { + size, + alignment_bytes, + } => { + let from_ptr = LocalId(0); + let to_ptr = LocalId(0); + copy_memory(instructions, from_ptr, to_ptr: size, alignment_bytes) + } - // LocalOnly(F32, 2) => Ok(), // convert F32 to F16 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? HeapMemory => { if PTR_TYPE == I64 { - Ok(I64Store(ALIGN_8, offset)) + instructions.push(I64Store(ALIGN_8, offset)); } else { - Ok(I32Store(ALIGN_4, offset)) + instructions.push(I32Store(ALIGN_4, offset)); } } - _ => Err(format!( - "Failed to generate store instruction for WasmLayout {:?}", - self - )), + _ => { + result = Err(format!( + "Failed to generate store instruction for WasmLayout {:?}", + self + )); + } } + result } + */ } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 748044ea20..e3bfcd6fcf 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -4,7 +4,7 @@ mod layout; use bumpalo::Bump; use parity_wasm::builder; -use parity_wasm::elements::{Instruction, Internal, ValueType}; +use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType}; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::{Interns, Symbol}; @@ -24,6 +24,9 @@ pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; +#[derive(Clone, Copy, Debug)] +struct LocalId(u32); + pub struct Env<'a> { pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot pub interns: Interns, @@ -105,3 +108,47 @@ pub fn build_module_help<'a>( Ok((backend.builder, main_function_index)) } + +fn encode_alignment(bytes: u32) -> Result { + match bytes { + 1 => Ok(ALIGN_1), + 2 => Ok(ALIGN_2), + 4 => Ok(ALIGN_4), + 8 => Ok(ALIGN_8), + _ => Err(format!("{:?}-byte alignment is not supported", bytes)), + } +} + +fn copy_memory( + instructions: &mut Vec, + from_ptr: LocalId, + to_ptr: LocalId, + size_with_alignment: u32, + alignment_bytes: u32, +) -> Result<(), String> { + let alignment_flag = encode_alignment(alignment_bytes)?; + let size = size_with_alignment - alignment_bytes; + let mut offset = 0; + while size - offset >= 8 { + instructions.push(GetLocal(to_ptr.0)); + instructions.push(GetLocal(from_ptr.0)); + instructions.push(I64Load(alignment_flag, offset)); + instructions.push(I64Store(alignment_flag, offset)); + offset += 8; + } + if size - offset >= 4 { + instructions.push(GetLocal(to_ptr.0)); + instructions.push(GetLocal(from_ptr.0)); + instructions.push(I32Load(alignment_flag, offset)); + instructions.push(I32Store(alignment_flag, offset)); + offset += 4; + } + while size - offset > 0 { + instructions.push(GetLocal(to_ptr.0)); + instructions.push(GetLocal(from_ptr.0)); + instructions.push(I32Load8U(alignment_flag, offset)); + instructions.push(I32Store8(alignment_flag, offset)); + offset += 1; + } + Ok(()) +} From 4f55b7a56e4ab6805515afa9323bdc7c8fb74b2c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 17 Sep 2021 21:17:05 +0100 Subject: [PATCH 017/136] Allocate and free stack frames --- compiler/gen_wasm/src/backend.rs | 25 +++++++++++++------ compiler/gen_wasm/src/lib.rs | 22 +++++++++++++++- .../tests/helpers/wasm32_test_result.rs | 17 ++++--------- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index d07adc266e..06a7ac50aa 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -10,8 +10,8 @@ use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use crate::layout::{WasmLayout}; -use crate::{PTR_TYPE, copy_memory, LocalId}; +use crate::layout::WasmLayout; +use crate::{allocate_stack_frame, copy_memory, free_stack_frame, LocalId, PTR_TYPE}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -110,14 +110,17 @@ impl<'a> WasmBackend<'a> { self.build_stmt(&proc.body, &proc.ret_layout)?; - // functions must end with an End instruction/opcode - self.instructions.push(Instruction::End); + let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); + allocate_stack_frame(&mut final_instructions, self.stack_memory as i32); + final_instructions.extend(self.instructions.clone()); + free_stack_frame(&mut final_instructions, self.stack_memory as i32); + final_instructions.push(Instruction::End); let function_def = builder::function() .with_signature(signature) .body() .with_locals(self.locals.clone()) - .with_instructions(Instructions::new(self.instructions.clone())) + .with_instructions(Instructions::new(final_instructions)) .build() // body .build(); // function @@ -130,13 +133,13 @@ impl<'a> WasmBackend<'a> { } fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol, kind: LocalKind) -> LocalId { - self.stack_memory += layout.stack_memory(); - match kind { LocalKind::Parameter => { + // Don't increment stack_memory! Structs are allocated in caller's stack memory and passed as pointers. self.arg_types.push(layout.value_type()); } LocalKind::Variable => { + self.stack_memory += layout.stack_memory(); self.locals.push(Local::new(1, layout.value_type())); } } @@ -228,7 +231,13 @@ impl<'a> WasmBackend<'a> { let to = LocalId(0); let copy_size: u32 = *size; let copy_alignment_bytes: u32 = *alignment_bytes; - copy_memory(&mut self.instructions, from, to, copy_size, copy_alignment_bytes)?; + copy_memory( + &mut self.instructions, + from, + to, + copy_size, + copy_alignment_bytes, + )?; } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index e3bfcd6fcf..95dbc267d0 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -25,7 +25,7 @@ pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; #[derive(Clone, Copy, Debug)] -struct LocalId(u32); +pub struct LocalId(u32); pub struct Env<'a> { pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot @@ -152,3 +152,23 @@ fn copy_memory( } Ok(()) } + +pub fn allocate_stack_frame(instructions: &mut Vec, size: i32) { + if size == 0 { + return; + } + instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); + instructions.push(I32Const(size)); + instructions.push(I32Sub); + instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); +} + +pub fn free_stack_frame(instructions: &mut Vec, size: i32) { + if size == 0 { + return; + } + instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); + instructions.push(I32Const(size)); + instructions.push(I32Add); + instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); +} diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index 184edf43f7..a2d0463ba0 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -35,20 +35,12 @@ pub trait Wasm32TestResult { fn build_wrapper_body(main_function_index: u32) -> Vec; } -fn build_wrapper_body_prelude(stack_memory_size: usize) -> Vec { - vec![ - GetGlobal(STACK_POINTER_GLOBAL_ID), - I32Const(stack_memory_size as i32), - I32Sub, - SetGlobal(STACK_POINTER_GLOBAL_ID), - ] -} - macro_rules! build_wrapper_body_primitive { ($store_instruction: expr, $align: expr) => { fn build_wrapper_body(main_function_index: u32) -> Vec { - const MAX_ALIGNED_SIZE: usize = 16; - let mut instructions = build_wrapper_body_prelude(MAX_ALIGNED_SIZE); + const MAX_ALIGNED_SIZE: i32 = 16; + let mut instructions = Vec::with_capacity(9); + allocate_stack_frame(&mut instructions, MAX_ALIGNED_SIZE); instructions.extend([ GetGlobal(STACK_POINTER_GLOBAL_ID), // @@ -76,7 +68,8 @@ macro_rules! wasm_test_result_primitive { } fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec { - let mut instructions = build_wrapper_body_prelude(size); + let mut instructions = Vec::with_capacity(8); + allocate_stack_frame(&mut instructions, size as i32); instructions.extend([ // // Call the main function with the allocated address to write the result. From 52a56bfa271b02893dd088b9b998984464a2fc47 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 17 Sep 2021 21:49:21 +0100 Subject: [PATCH 018/136] Optimise away a memory copy for returned structs in simple cases --- compiler/gen_wasm/src/backend.rs | 10 ++++++++-- compiler/gen_wasm/src/layout.rs | 8 ++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 06a7ac50aa..3652709546 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -192,9 +192,15 @@ impl<'a> WasmBackend<'a> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { match stmt { - // This pattern is a simple optimisation to get rid of one local and two instructions per proc. - // If we are just returning the expression result, then don't SetLocal and immediately GetLocal + // Simple optimisation: if we are just returning the expression, we don't need a local Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => { + let wasm_layout = WasmLayout::new(layout); + if let WasmLayout::StackMemory { .. } = wasm_layout { + // Map this symbol to the first argument (pointer into caller's stack) + // Saves us from having to copy it later + let storage = SymbolStorage(LocalId(0), wasm_layout); + self.symbol_storage_map.insert(*let_sym, storage); + } self.build_expr(let_sym, expr, layout)?; self.instructions.push(Return); Ok(()) diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index d45a502055..43580a9aec 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -6,14 +6,14 @@ use crate::{PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls #[derive(Debug)] pub enum WasmLayout { - // Most number types can fit in a Wasm local without any stack memory. - // Roc i8 is represented as an i32 local. Store the type and the original size. + // Primitive number value. Just a Wasm local, without any stack memory. + // For example, Roc i8 is represented as Wasm i32. Store the type and the original size. LocalOnly(ValueType, u32), - // A `local` pointing to stack memory + // Local pointer to stack memory StackMemory { size: u32, alignment_bytes: u32 }, - // A `local` pointing to heap memory + // Local pointer to heap memory HeapMemory, } From c3b5ac6c8210738816041f8e7cc034ed119f6d1b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 18 Sep 2021 13:39:38 +0100 Subject: [PATCH 019/136] Allocate stack memory to local variables --- compiler/gen_wasm/src/backend.rs | 123 +++++++++++++----- compiler/gen_wasm/src/layout.rs | 23 ++-- compiler/gen_wasm/src/lib.rs | 34 ++++- .../tests/helpers/wasm32_test_result.rs | 45 +++++-- 4 files changed, 167 insertions(+), 58 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 3652709546..00e2dcbe68 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,5 +1,5 @@ use parity_wasm::builder; -use parity_wasm::builder::{CodeLocation, ModuleBuilder}; +use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder}; use parity_wasm::elements::{ BlockType, Instruction, Instruction::*, Instructions, Local, ValueType, }; @@ -44,8 +44,8 @@ pub struct WasmBackend<'a> { locals: std::vec::Vec, // Functions: internal state & IR mappings - next_local_index: u32, - stack_memory: u32, + stack_memory: i32, + stack_frame_pointer: Option, symbol_storage_map: MutMap, /// how many blocks deep are we (used for jumps) block_depth: u32, @@ -69,8 +69,8 @@ impl<'a> WasmBackend<'a> { locals: std::vec::Vec::with_capacity(32), // Functions: internal state & IR mappings - next_local_index: 0, stack_memory: 0, + stack_frame_pointer: None, symbol_storage_map: MutMap::default(), block_depth: 0, joinpoint_label_map: MutMap::default(), @@ -84,8 +84,8 @@ impl<'a> WasmBackend<'a> { self.locals.clear(); // Functions: internal state & IR mappings - self.next_local_index = 0; self.stack_memory = 0; + self.stack_frame_pointer = None; self.symbol_storage_map.clear(); self.joinpoint_label_map.clear(); assert_eq!(self.block_depth, 0); @@ -94,28 +94,57 @@ impl<'a> WasmBackend<'a> { pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { let ret_layout = WasmLayout::new(&proc.ret_layout); - let sig_builder = if let WasmLayout::StackMemory { .. } = ret_layout { + let ret_type = if let WasmLayout::StackMemory { .. } = ret_layout { self.arg_types.push(PTR_TYPE); - self.next_local_index += 1; - builder::signature() + None } else { - builder::signature().with_result(ret_layout.value_type()) + Some(ret_layout.value_type()) }; for (layout, symbol) in proc.args { self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); } - let signature = sig_builder.with_params(self.arg_types.clone()).build_sig(); - self.build_stmt(&proc.body, &proc.ret_layout)?; + let function_def = self.finalize(ret_type); + let location = self.builder.push_function(function_def); + let function_index = location.body; + self.proc_symbol_map.insert(sym, location); + self.reset(); + + Ok(function_index) + } + + fn finalize(&mut self, return_type: Option) -> FunctionDefinition { let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); - allocate_stack_frame(&mut final_instructions, self.stack_memory as i32); - final_instructions.extend(self.instructions.clone()); - free_stack_frame(&mut final_instructions, self.stack_memory as i32); + + allocate_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer, + ); + + final_instructions.extend(self.instructions.drain(0..)); + + free_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer, + ); + final_instructions.push(Instruction::End); + let signature_builder = if let Some(t) = return_type { + builder::signature().with_result(t) + } else { + builder::signature() + }; + + let signature = signature_builder + .with_params(self.arg_types.clone()) + .build_sig(); + let function_def = builder::function() .with_signature(signature) .body() @@ -124,35 +153,69 @@ impl<'a> WasmBackend<'a> { .build() // body .build(); // function - let location = self.builder.push_function(function_def); - let function_index = location.body; - self.proc_symbol_map.insert(sym, location); - self.reset(); - - Ok(function_index) + function_def } - fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol, kind: LocalKind) -> LocalId { + fn insert_local( + &mut self, + wasm_layout: WasmLayout, + symbol: Symbol, + kind: LocalKind, + ) -> LocalId { + let local_index = (self.arg_types.len() + self.locals.len()) as u32; + let local_id = LocalId(local_index); + match kind { LocalKind::Parameter => { - // Don't increment stack_memory! Structs are allocated in caller's stack memory and passed as pointers. - self.arg_types.push(layout.value_type()); + // Already stack-allocated by the caller if needed. + self.arg_types.push(wasm_layout.value_type()); } LocalKind::Variable => { - self.stack_memory += layout.stack_memory(); - self.locals.push(Local::new(1, layout.value_type())); + self.locals.push(Local::new(1, wasm_layout.value_type())); + + if let WasmLayout::StackMemory { + size, + alignment_bytes, + } = wasm_layout + { + let align = alignment_bytes as i32; + let mut offset = self.stack_memory; + offset += align - 1; + offset &= -align; + self.stack_memory = offset + (size - alignment_bytes) as i32; + + let frame_pointer = self.get_or_create_frame_pointer(); + + // initialise the local with the appropriate address + self.instructions.extend([ + GetLocal(frame_pointer.0), + I32Const(offset), + I32Add, + SetLocal(local_index), + ]); + } } } - let local_id = LocalId(self.next_local_index); - self.next_local_index += 1; - - let storage = SymbolStorage(local_id, layout); + let storage = SymbolStorage(local_id, wasm_layout); self.symbol_storage_map.insert(symbol, storage); local_id } + fn get_or_create_frame_pointer(&mut self) -> LocalId { + match self.stack_frame_pointer { + Some(local_id) => local_id, + None => { + let local_index = (self.arg_types.len() + self.locals.len()) as u32; + let local_id = LocalId(local_index); + self.stack_frame_pointer = Some(local_id); + self.locals.push(Local::new(1, ValueType::I32)); + local_id + } + } + } + fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> { self.symbol_storage_map.get(sym).ok_or_else(|| { format!( @@ -199,7 +262,7 @@ impl<'a> WasmBackend<'a> { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later let storage = SymbolStorage(LocalId(0), wasm_layout); - self.symbol_storage_map.insert(*let_sym, storage); + self.symbol_storage_map.insert(*let_sym, storage); } self.build_expr(let_sym, expr, layout)?; self.instructions.push(Return); diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 43580a9aec..576316a049 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,7 +1,7 @@ -use parity_wasm::elements::ValueType; +use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; use roc_mono::layout::{Layout, UnionLayout}; -use crate::{PTR_SIZE, PTR_TYPE}; +use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls #[derive(Debug)] @@ -72,6 +72,7 @@ impl WasmLayout { } } + #[allow(dead_code)] pub fn stack_memory(&self) -> u32 { match self { Self::StackMemory { size, .. } => *size, @@ -79,7 +80,6 @@ impl WasmLayout { } } - /* #[allow(dead_code)] fn load(&self, offset: u32) -> Result { use crate::layout::WasmLayout::*; @@ -93,8 +93,10 @@ impl WasmLayout { LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)), LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)), + // TODO: Come back to this when we need to access fields of structs // LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?) // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? + HeapMemory => { if PTR_TYPE == I64 { Ok(I64Load(ALIGN_8, offset)) @@ -109,10 +111,6 @@ impl WasmLayout { )), } } - */ - /* - TODO: this is probably in the wrong place, need specific locals for the StackMemory case. - Come back to it later. #[allow(dead_code)] pub fn store(&self, offset: u32, instructions: &mut Vec) -> Result<(), String> { @@ -132,9 +130,13 @@ impl WasmLayout { size, alignment_bytes, } => { - let from_ptr = LocalId(0); - let to_ptr = LocalId(0); - copy_memory(instructions, from_ptr, to_ptr: size, alignment_bytes) + // TODO + // Need extra arguments for this case that we don't need for primitives. + // Maybe it should be somewhere we have more relevant context? + // Come back to it when we need to insert things into structs. + let from_ptr = LocalId(0); // TODO + let to_ptr = LocalId(0); // TODO + copy_memory(instructions, from_ptr, to_ptr, *size, *alignment_bytes)?; } HeapMemory => { @@ -154,5 +156,4 @@ impl WasmLayout { } result } - */ } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 95dbc267d0..8a612572bf 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -25,7 +25,7 @@ pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; #[derive(Clone, Copy, Debug)] -pub struct LocalId(u32); +pub struct LocalId(pub u32); pub struct Env<'a> { pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot @@ -153,22 +153,42 @@ fn copy_memory( Ok(()) } -pub fn allocate_stack_frame(instructions: &mut Vec, size: i32) { +pub fn allocate_stack_frame( + instructions: &mut Vec, + size: i32, + local_frame_pointer: Option, +) { if size == 0 { return; } instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); instructions.push(I32Const(size)); instructions.push(I32Sub); + if let Some(LocalId(local_index)) = local_frame_pointer { + instructions.push(TeeLocal(local_index)); + } instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); } -pub fn free_stack_frame(instructions: &mut Vec, size: i32) { +pub fn free_stack_frame( + instructions: &mut Vec, + size: i32, + local_frame_pointer: Option, +) { if size == 0 { return; } - instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); - instructions.push(I32Const(size)); - instructions.push(I32Add); - instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); + let get_stack_frame = + if let Some(LocalId(local_index)) = local_frame_pointer { + GetLocal(local_index) + } else { + GetGlobal(STACK_POINTER_GLOBAL_ID) + }; + + instructions.extend([ + get_stack_frame, + I32Const(size), + I32Add, + SetGlobal(STACK_POINTER_GLOBAL_ID), + ]); } diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index a2d0463ba0..dd069ec0bb 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -1,11 +1,15 @@ use parity_wasm::builder; use parity_wasm::builder::ModuleBuilder; -use parity_wasm::elements::{Instruction, Instruction::*, Instructions, Internal, ValueType}; +use parity_wasm::elements::{ + Instruction, Instruction::*, Instructions, Internal, Local, ValueType, +}; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; use roc_gen_wasm::*; use roc_std::{RocDec, RocList, RocOrder, RocStr}; +const STACK_POINTER_LOCAL_ID: u32 = 0; + pub trait Wasm32TestResult { fn insert_test_wrapper( module_builder: &mut ModuleBuilder, @@ -16,9 +20,11 @@ pub trait Wasm32TestResult { let signature = builder::signature().with_result(ValueType::I32).build_sig(); + let stack_frame_pointer = Local::new(1, ValueType::I32); let function_def = builder::function() .with_signature(signature) .body() + .with_locals(vec![stack_frame_pointer]) .with_instructions(Instructions::new(instructions)) .build() // body .build(); // function @@ -39,10 +45,15 @@ macro_rules! build_wrapper_body_primitive { ($store_instruction: expr, $align: expr) => { fn build_wrapper_body(main_function_index: u32) -> Vec { const MAX_ALIGNED_SIZE: i32 = 16; - let mut instructions = Vec::with_capacity(9); - allocate_stack_frame(&mut instructions, MAX_ALIGNED_SIZE); + let mut instructions = Vec::with_capacity(16); + allocate_stack_frame( + &mut instructions, + MAX_ALIGNED_SIZE, + Some(LocalId(STACK_POINTER_LOCAL_ID)), + ); instructions.extend([ - GetGlobal(STACK_POINTER_GLOBAL_ID), + // load result address to prepare for the store instruction later + GetLocal(STACK_POINTER_LOCAL_ID), // // Call the main function with no arguments. Get primitive back. Call(main_function_index), @@ -51,9 +62,14 @@ macro_rules! build_wrapper_body_primitive { $store_instruction($align, 0), // // Return the result pointer - GetGlobal(STACK_POINTER_GLOBAL_ID), - End, + GetLocal(STACK_POINTER_LOCAL_ID), ]); + free_stack_frame( + &mut instructions, + MAX_ALIGNED_SIZE, + Some(LocalId(STACK_POINTER_LOCAL_ID)), + ); + instructions.push(End); instructions } }; @@ -68,19 +84,28 @@ macro_rules! wasm_test_result_primitive { } fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec { - let mut instructions = Vec::with_capacity(8); - allocate_stack_frame(&mut instructions, size as i32); + let mut instructions = Vec::with_capacity(16); + allocate_stack_frame( + &mut instructions, + size as i32, + Some(LocalId(STACK_POINTER_LOCAL_ID)), + ); instructions.extend([ // // Call the main function with the allocated address to write the result. // No value is returned to the VM stack. This is the same as in compiled C. - GetGlobal(STACK_POINTER_GLOBAL_ID), + GetLocal(STACK_POINTER_LOCAL_ID), Call(main_function_index), // // Return the result address GetGlobal(STACK_POINTER_GLOBAL_ID), - End, ]); + free_stack_frame( + &mut instructions, + size as i32, + Some(LocalId(STACK_POINTER_LOCAL_ID)), + ); + instructions.push(End); instructions } From 65446ce1e865564ec8e8de09f92fb5e98ef62c08 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 18 Sep 2021 14:46:09 +0100 Subject: [PATCH 020/136] Remove unnecessary Option --- compiler/gen_wasm/src/backend.rs | 25 ++++++------ compiler/gen_wasm/src/lib.rs | 38 +++++++------------ .../tests/helpers/wasm32_test_result.rs | 16 ++++---- 3 files changed, 36 insertions(+), 43 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 00e2dcbe68..924c86b992 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -119,20 +119,23 @@ impl<'a> WasmBackend<'a> { fn finalize(&mut self, return_type: Option) -> FunctionDefinition { let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); - allocate_stack_frame( - &mut final_instructions, - self.stack_memory, - self.stack_frame_pointer, - ); + if self.stack_memory > 0 { + allocate_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer.unwrap(), + ); + } final_instructions.extend(self.instructions.drain(0..)); - free_stack_frame( - &mut final_instructions, - self.stack_memory, - self.stack_frame_pointer, - ); - + if self.stack_memory > 0 { + free_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer.unwrap(), + ); + } final_instructions.push(Instruction::End); let signature_builder = if let Some(t) = return_type { diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 8a612572bf..d439fca3cc 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -23,6 +23,7 @@ pub const ALIGN_4: u32 = 2; pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; +pub const STACK_ALIGNMENT_BYTES: i32 = 16; #[derive(Clone, Copy, Debug)] pub struct LocalId(pub u32); @@ -156,38 +157,27 @@ fn copy_memory( pub fn allocate_stack_frame( instructions: &mut Vec, size: i32, - local_frame_pointer: Option, + local_frame_pointer: LocalId, ) { - if size == 0 { - return; - } - instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); - instructions.push(I32Const(size)); - instructions.push(I32Sub); - if let Some(LocalId(local_index)) = local_frame_pointer { - instructions.push(TeeLocal(local_index)); - } - instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); + let aligned_size = (size + STACK_ALIGNMENT_BYTES - 1) & (-STACK_ALIGNMENT_BYTES); + instructions.extend([ + GetGlobal(STACK_POINTER_GLOBAL_ID), + I32Const(aligned_size), + I32Sub, + TeeLocal(local_frame_pointer.0), + SetGlobal(STACK_POINTER_GLOBAL_ID), + ]); } pub fn free_stack_frame( instructions: &mut Vec, size: i32, - local_frame_pointer: Option, + local_frame_pointer: LocalId, ) { - if size == 0 { - return; - } - let get_stack_frame = - if let Some(LocalId(local_index)) = local_frame_pointer { - GetLocal(local_index) - } else { - GetGlobal(STACK_POINTER_GLOBAL_ID) - }; - + let aligned_size = (size + STACK_ALIGNMENT_BYTES - 1) & (-STACK_ALIGNMENT_BYTES); instructions.extend([ - get_stack_frame, - I32Const(size), + GetLocal(local_frame_pointer.0), + I32Const(aligned_size), I32Add, SetGlobal(STACK_POINTER_GLOBAL_ID), ]); diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index dd069ec0bb..a84bf48e89 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -44,12 +44,12 @@ pub trait Wasm32TestResult { macro_rules! build_wrapper_body_primitive { ($store_instruction: expr, $align: expr) => { fn build_wrapper_body(main_function_index: u32) -> Vec { - const MAX_ALIGNED_SIZE: i32 = 16; + let size: i32 = 8; let mut instructions = Vec::with_capacity(16); allocate_stack_frame( &mut instructions, - MAX_ALIGNED_SIZE, - Some(LocalId(STACK_POINTER_LOCAL_ID)), + size, + LocalId(STACK_POINTER_LOCAL_ID), ); instructions.extend([ // load result address to prepare for the store instruction later @@ -66,8 +66,8 @@ macro_rules! build_wrapper_body_primitive { ]); free_stack_frame( &mut instructions, - MAX_ALIGNED_SIZE, - Some(LocalId(STACK_POINTER_LOCAL_ID)), + size, + LocalId(STACK_POINTER_LOCAL_ID), ); instructions.push(End); instructions @@ -88,7 +88,7 @@ fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec allocate_stack_frame( &mut instructions, size as i32, - Some(LocalId(STACK_POINTER_LOCAL_ID)), + LocalId(STACK_POINTER_LOCAL_ID), ); instructions.extend([ // @@ -98,12 +98,12 @@ fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec Call(main_function_index), // // Return the result address - GetGlobal(STACK_POINTER_GLOBAL_ID), + GetLocal(STACK_POINTER_LOCAL_ID), ]); free_stack_frame( &mut instructions, size as i32, - Some(LocalId(STACK_POINTER_LOCAL_ID)), + LocalId(STACK_POINTER_LOCAL_ID), ); instructions.push(End); instructions From e0af849518926167053c614f7c6dfb05747c94f4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 18 Sep 2021 22:56:33 +0100 Subject: [PATCH 021/136] Create hello-web example --- .gitignore | 1 + examples/hello-web/.gitignore | 2 + examples/hello-web/Hello.roc | 12 +++ examples/hello-web/README.md | 44 +++++++++++ .../hello-web/platform/Package-Config.roc | 10 +++ examples/hello-web/platform/host.js | 31 ++++++++ examples/hello-web/platform/host.zig | 78 +++++++++++++++++++ 7 files changed, 178 insertions(+) create mode 100644 examples/hello-web/.gitignore create mode 100644 examples/hello-web/Hello.roc create mode 100644 examples/hello-web/README.md create mode 100644 examples/hello-web/platform/Package-Config.roc create mode 100644 examples/hello-web/platform/host.js create mode 100644 examples/hello-web/platform/host.zig diff --git a/.gitignore b/.gitignore index 1ce4f54edd..1c7ea24a83 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ generated-docs zig-cache .direnv *.rs.bk +*.o # llvm human-readable output *.ll diff --git a/examples/hello-web/.gitignore b/examples/hello-web/.gitignore new file mode 100644 index 0000000000..a957f57cda --- /dev/null +++ b/examples/hello-web/.gitignore @@ -0,0 +1,2 @@ +hello-world +*.wat diff --git a/examples/hello-web/Hello.roc b/examples/hello-web/Hello.roc new file mode 100644 index 0000000000..b27d53cf8c --- /dev/null +++ b/examples/hello-web/Hello.roc @@ -0,0 +1,12 @@ +app "hello-world" + packages { base: "platform" } + imports [] + provides [ main ] to base + +greeting = + hi = "Hello" + name = "World" + + "\(hi), \(name)!" + +main = greeting diff --git a/examples/hello-web/README.md b/examples/hello-web/README.md new file mode 100644 index 0000000000..4f0ec8dfd1 --- /dev/null +++ b/examples/hello-web/README.md @@ -0,0 +1,44 @@ +# Hello, World! + +To run, `cd` into this directory and run: + +```bash +$ cargo run Hello.roc +``` + +To run in release mode instead, do: + +```bash +$ cargo run --release Hello.roc +``` + +## Design Notes + +This demonstrates the basic design of hosts: Roc code gets compiled into a pure +function (in this case, a thunk that always returns `"Hello, World!"`) and +then the host calls that function. Fundamentally, that's the whole idea! The host +might not even have a `main` - it could be a library, a plugin, anything. +Everything else is built on this basic "hosts calling linked pure functions" design. + +For example, things get more interesting when the compiled Roc function returns +a `Task` - that is, a tagged union data structure containing function pointers +to callback closures. This lets the Roc pure function describe arbitrary +chainable effects, which the host can interpret to perform I/O as requested by +the Roc program. (The tagged union `Task` would have a variant for each supported +I/O operation.) + +In this trivial example, it's very easy to line up the API between the host and +the Roc program. In a more involved host, this would be much trickier - especially +if the API were changing frequently during development. + +The idea there is to have a first-class concept of "glue code" which host authors +can write (it would be plain Roc code, but with some extra keywords that aren't +available in normal modules - kinda like `port module` in Elm), and which +describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary. +Roc application authors only care about the Roc-host/Roc-app portion, and the +host author only cares about the Roc-host/C boundary when implementing the host. + +Using this glue code, the Roc compiler can generate C header files describing the +boundary. This not only gets us host compatibility with C compilers, but also +Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) +generates correct Rust FFI bindings from C headers. diff --git a/examples/hello-web/platform/Package-Config.roc b/examples/hello-web/platform/Package-Config.roc new file mode 100644 index 0000000000..377d5c0994 --- /dev/null +++ b/examples/hello-web/platform/Package-Config.roc @@ -0,0 +1,10 @@ +platform examples/hello-world + requires {}{ main : Str } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + effects fx.Effect {} + +mainForHost : Str +mainForHost = main diff --git a/examples/hello-web/platform/host.js b/examples/hello-web/platform/host.js new file mode 100644 index 0000000000..b737379379 --- /dev/null +++ b/examples/hello-web/platform/host.js @@ -0,0 +1,31 @@ +const test_decoder = true; + +function run() { + const memory = new Uint8Array(1024); + const decoder = new TextDecoder(); + + function js_display_roc_string(str_bytes, str_len) { + const utf8_bytes = memory.subarray(str_bytes, str_bytes + str_len); + const js_string = decoder.decode(utf8_bytes); + console.log(js_string); + } + + if (test_decoder) { + const testAddress = 123; + const testString = "Hello, world"; + + const utf8Encoder = new TextEncoder(); + const targetBytes = memory.subarray( + testAddress, + testAddress + testString.length + ); + const { read, written } = utf8Encoder.encodeInto(testString, targetBytes); + if (written !== read) { + throw new Error("Not enough space"); + } + + js_display_roc_string(testAddress, testString.length); + } +} + +run(); diff --git a/examples/hello-web/platform/host.zig b/examples/hello-web/platform/host.zig new file mode 100644 index 0000000000..961619340e --- /dev/null +++ b/examples/hello-web/platform/host.zig @@ -0,0 +1,78 @@ +const std = @import("std"); +const str = @import("str"); +const RocStr = str.RocStr; +const testing = std.testing; +const expectEqual = testing.expectEqual; +const expect = testing.expect; + +comptime { + // This is a workaround for https://github.com/ziglang/zig/issues/8218 + // which is only necessary on macOS. + // + // Once that issue is fixed, we can undo the changes in + // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing + // -fcompiler-rt in link.rs instead of doing this. Note that this + // workaround is present in many host.zig files, so make sure to undo + // it everywhere! + if (std.builtin.os.tag == .macos) { + _ = @import("compiler_rt"); + } +} + +const Align = extern struct { a: usize, b: usize }; +extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; +extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void; +extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void; + +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { + _ = alignment; + + return malloc(size); +} + +export fn roc_realloc(c_ptr: *c_void, old_size: usize, new_size: usize, alignment: u32) callconv(.C) ?*c_void { + _ = old_size; + _ = alignment; + + return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size); +} + +export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { + _ = alignment; + + free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr))); +} + +export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { + _ = tag_id; + const stderr = std.io.getStdErr().writer(); + const msg = @ptrCast([*:0]const u8, c_ptr); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + std.process.exit(0); +} + +const mem = std.mem; +const Allocator = mem.Allocator; + +extern fn roc__mainForHost_1_exposed(*RocCallResult) void; + +const RocCallResult = extern struct { flag: u64, content: RocStr }; + +const Unit = extern struct {}; + +extern fn js_display_roc_string(str_bytes: ?[*]u8, str_len: usize) void; + +pub fn main() u8 { + // make space for the result + var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() }; + + // actually call roc to populate the callresult + roc__mainForHost_1_exposed(&callresult); + + // display the result using JavaScript + js_display_roc_string(callresult.content.str_bytes, callresult.content.str_len); + + callresult.content.deinit(); + + return 0; +} From c20a2e57400b692d877c55d13dfd10f5268ac81a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 18 Sep 2021 23:52:18 +0100 Subject: [PATCH 022/136] Hello web is working!! --- examples/hello-web/hello-world.wasm | 1 + examples/hello-web/index.html | 12 +++++++ examples/hello-web/platform/host.js | 50 ++++++++++++++++++----------- 3 files changed, 44 insertions(+), 19 deletions(-) create mode 120000 examples/hello-web/hello-world.wasm create mode 100644 examples/hello-web/index.html diff --git a/examples/hello-web/hello-world.wasm b/examples/hello-web/hello-world.wasm new file mode 120000 index 0000000000..bdd51cc27b --- /dev/null +++ b/examples/hello-web/hello-world.wasm @@ -0,0 +1 @@ +hello-world \ No newline at end of file diff --git a/examples/hello-web/index.html b/examples/hello-web/index.html new file mode 100644 index 0000000000..cf2c92688f --- /dev/null +++ b/examples/hello-web/index.html @@ -0,0 +1,12 @@ + + +
+ + + + diff --git a/examples/hello-web/platform/host.js b/examples/hello-web/platform/host.js index b737379379..d6af6ffde0 100644 --- a/examples/hello-web/platform/host.js +++ b/examples/hello-web/platform/host.js @@ -1,31 +1,43 @@ const test_decoder = true; -function run() { - const memory = new Uint8Array(1024); +async function roc_web_platform_run(wasm_filename, dom_node) { const decoder = new TextDecoder(); + let memory_bytes; + let exit_code; function js_display_roc_string(str_bytes, str_len) { - const utf8_bytes = memory.subarray(str_bytes, str_bytes + str_len); + const utf8_bytes = memory_bytes.subarray(str_bytes, str_bytes + str_len); const js_string = decoder.decode(utf8_bytes); - console.log(js_string); + dom_node.textContent = js_string; } - if (test_decoder) { - const testAddress = 123; - const testString = "Hello, world"; + const importObj = { + wasi_snapshot_preview1: { + proc_exit: (code) => { + if (code !== 0) { + console.error(`Exited with code ${code}`); + } + exit_code = code; + }, + }, + env: { + js_display_roc_string, + }, + }; - const utf8Encoder = new TextEncoder(); - const targetBytes = memory.subarray( - testAddress, - testAddress + testString.length - ); - const { read, written } = utf8Encoder.encodeInto(testString, targetBytes); - if (written !== read) { - throw new Error("Not enough space"); + const wasm = await WebAssembly.instantiateStreaming( + fetch(wasm_filename), + importObj + ); + + memory_bytes = new Uint8Array(wasm.instance.exports.memory.buffer); + + try { + wasm.instance.exports._start(); + } catch (e) { + const is_ok = e.message === "unreachable" && exit_code === 0; + if (!is_ok) { + console.error(e); } - - js_display_roc_string(testAddress, testString.length); } } - -run(); From 4a6e6207059927210a933bffcbfd3a41ba8abfd6 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 18 Sep 2021 16:11:51 -0700 Subject: [PATCH 023/136] Add --precompiled-host flag to enable skipping rebuild --- cli/src/build.rs | 37 +++++++++++++++++++++---------------- cli/src/lib.rs | 15 +++++++++++++++ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index defb8e0781..fd5173ca3d 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -54,6 +54,7 @@ pub fn build_file<'a>( emit_timings: bool, link_type: LinkType, surgically_link: bool, + precompiled: bool, ) -> Result> { let compilation_start = SystemTime::now(); let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; @@ -100,6 +101,7 @@ pub fn build_file<'a>( let rebuild_thread = spawn_rebuild_thread( opt_level, surgically_link, + precompiled, host_input_path.clone(), target, loaded @@ -217,7 +219,7 @@ pub fn build_file<'a>( } let rebuild_duration = rebuild_thread.join().unwrap(); - if emit_timings { + if emit_timings && !precompiled { println!( "Finished rebuilding and preprocessing the host in {} ms\n", rebuild_duration @@ -273,6 +275,7 @@ pub fn build_file<'a>( fn spawn_rebuild_thread( opt_level: OptLevel, surgically_link: bool, + precompiled: bool, host_input_path: PathBuf, target: &Triple, exported_symbols: Vec, @@ -280,21 +283,23 @@ fn spawn_rebuild_thread( let thread_local_target = target.clone(); std::thread::spawn(move || { let rebuild_host_start = SystemTime::now(); - if surgically_link { - roc_linker::build_and_preprocess_host( - opt_level, - &thread_local_target, - host_input_path.as_path(), - exported_symbols, - ) - .unwrap(); - } else { - rebuild_host( - opt_level, - &thread_local_target, - host_input_path.as_path(), - None, - ); + if !precompiled { + if surgically_link { + roc_linker::build_and_preprocess_host( + opt_level, + &thread_local_target, + host_input_path.as_path(), + exported_symbols, + ) + .unwrap(); + } else { + rebuild_host( + opt_level, + &thread_local_target, + host_input_path.as_path(), + None, + ); + } } let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); rebuild_host_end.as_millis() diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 963ba2e4bc..1bebaaece4 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -34,6 +34,7 @@ pub const FLAG_LIB: &str = "lib"; pub const FLAG_BACKEND: &str = "backend"; pub const FLAG_TIME: &str = "time"; pub const FLAG_LINK: &str = "roc-linker"; +pub const FLAG_PRECOMPILED: &str = "precompiled-host"; pub const ROC_FILE: &str = "ROC_FILE"; pub const BACKEND: &str = "BACKEND"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; @@ -95,6 +96,12 @@ pub fn build_app<'a>() -> App<'a> { .help("Uses the roc linker instead of the system linker.") .required(false), ) + .arg( + Arg::with_name(FLAG_PRECOMPILED) + .long(FLAG_PRECOMPILED) + .help("Assumes the host has been precompiled and skips recompiling the host.") + .required(false), + ) ) .subcommand(App::new(CMD_RUN) .about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`") @@ -175,6 +182,12 @@ pub fn build_app<'a>() -> App<'a> { .help("Uses the roc linker instead of the system linker.") .required(false), ) + .arg( + Arg::with_name(FLAG_PRECOMPILED) + .long(FLAG_PRECOMPILED) + .help("Assumes the host has been precompiled and skips recompiling the host.") + .required(false), + ) .arg( Arg::with_name(FLAG_BACKEND) .long(FLAG_BACKEND) @@ -260,6 +273,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { LinkType::Executable }; let surgically_link = matches.is_present(FLAG_LINK); + let precompiled = matches.is_present(FLAG_PRECOMPILED); if surgically_link && !roc_linker::supported(&link_type, &target) { panic!( "Link type, {:?}, with target, {}, not supported by roc linker", @@ -299,6 +313,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result { emit_timings, link_type, surgically_link, + precompiled, ); match res_binary_path { From 874ef7a5908fabb1b59dd4216e42cd2d9b9033b0 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 18 Sep 2021 17:54:02 -0700 Subject: [PATCH 024/136] Move copying precompiled host to rebuild thread --- cli/src/build.rs | 13 ++++++++++--- linker/src/lib.rs | 2 -- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index fd5173ca3d..9f6f46e51c 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -88,8 +88,10 @@ pub fn build_file<'a>( let app_extension = if emit_wasm { "bc" } else { "o" }; let cwd = roc_file_path.parent().unwrap(); - let path_to_platform = loaded.platform_path.clone(); + let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows + let mut host_input_path = PathBuf::from(cwd); + let path_to_platform = loaded.platform_path.clone(); host_input_path.push(&*path_to_platform); host_input_path.push("host"); host_input_path.set_extension(host_extension); @@ -103,6 +105,7 @@ pub fn build_file<'a>( surgically_link, precompiled, host_input_path.clone(), + binary_path.clone(), target, loaded .exposed_to_host @@ -169,8 +172,6 @@ pub fn build_file<'a>( program::report_problems(&mut loaded); let loaded = loaded; - let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows - let code_gen_timing = match opt_level { OptLevel::Normal | OptLevel::Optimize => program::gen_from_mono_module_llvm( arena, @@ -277,6 +278,7 @@ fn spawn_rebuild_thread( surgically_link: bool, precompiled: bool, host_input_path: PathBuf, + binary_path: PathBuf, target: &Triple, exported_symbols: Vec, ) -> std::thread::JoinHandle { @@ -301,6 +303,11 @@ fn spawn_rebuild_thread( ); } } + if surgically_link { + // Copy preprocessed host to executable location. + let prehost = host_input_path.with_file_name("preprocessedhost"); + std::fs::copy(prehost, binary_path.as_path()).unwrap(); + } let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); rebuild_host_end.as_millis() }) diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 2324671cc9..1a043fbce1 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -169,8 +169,6 @@ pub fn link_preprocessed_host( binary_path: &Path, ) -> io::Result<()> { let metadata = host_input_path.with_file_name("metadata"); - let prehost = host_input_path.with_file_name("preprocessedhost"); - std::fs::copy(prehost, binary_path)?; if surgery_impl( roc_app_obj.to_str().unwrap(), metadata.to_str().unwrap(), From cda29d07061016f2129c4748c604e8ca0b015a08 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Sep 2021 23:40:36 -0400 Subject: [PATCH 025/136] memcpy and memset are byte-aligned --- examples/benchmarks/platform/host.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index b560c0cf6d..d2a7de8aeb 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -34,8 +34,8 @@ const Align = 2 * @alignOf(usize); extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void; extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void; extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; -extern fn memcpy(dst: [*]align(Align) u8, src: [*]align(Align) u8, size: usize) callconv(.C) void; -extern fn memset(dst: [*]align(Align) u8, value: i32, size: usize) callconv(.C) void; +extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void; +extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void; const DEBUG: bool = false; @@ -77,12 +77,12 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { std.process.exit(0); } -export fn roc_memcpy(dst: *c_void, src: *c_void, size: usize) callconv(.C) void{ - return memcpy(@alignCast(Align, @ptrCast([*]u8, dst)), @alignCast(Align, @ptrCast([*]u8, src)), size); +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{ + return memcpy(dst, src, size); } -export fn roc_memset(dst: *c_void, value: i32, size: usize) callconv(.C) void{ - return memset(@alignCast(Align, @ptrCast([*]u8, dst)), value, size); +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{ + return memset(dst, value, size); } const Unit = extern struct {}; From 11f8652ff6a0187f46ad6242f51793ff93be1607 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 19 Sep 2021 11:45:02 +0100 Subject: [PATCH 026/136] Refactor hello-web example for possible future automated testing For now there's a Node.js test that works, but we're not actually running it in CI, and Node is not a dependency of the project. --- examples/hello-web/index.html | 8 ++++---- examples/hello-web/platform/host.js | 27 +++++++++++++++++++-------- examples/hello-web/test-node.js | 25 +++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 examples/hello-web/test-node.js diff --git a/examples/hello-web/index.html b/examples/hello-web/index.html index cf2c92688f..99f633645d 100644 --- a/examples/hello-web/index.html +++ b/examples/hello-web/index.html @@ -3,10 +3,10 @@
diff --git a/examples/hello-web/platform/host.js b/examples/hello-web/platform/host.js index d6af6ffde0..9e3548aa26 100644 --- a/examples/hello-web/platform/host.js +++ b/examples/hello-web/platform/host.js @@ -1,6 +1,4 @@ -const test_decoder = true; - -async function roc_web_platform_run(wasm_filename, dom_node) { +async function roc_web_platform_run(wasm_filename, callback) { const decoder = new TextDecoder(); let memory_bytes; let exit_code; @@ -8,7 +6,7 @@ async function roc_web_platform_run(wasm_filename, dom_node) { function js_display_roc_string(str_bytes, str_len) { const utf8_bytes = memory_bytes.subarray(str_bytes, str_bytes + str_len); const js_string = decoder.decode(utf8_bytes); - dom_node.textContent = js_string; + callback(js_string); } const importObj = { @@ -25,10 +23,17 @@ async function roc_web_platform_run(wasm_filename, dom_node) { }, }; - const wasm = await WebAssembly.instantiateStreaming( - fetch(wasm_filename), - importObj - ); + let wasm; + + const response = await fetch(wasm_filename); + + if (WebAssembly.instantiateStreaming) { + // streaming API has better performance if available + wasm = await WebAssembly.instantiateStreaming(response, importObj); + } else { + const module_bytes = await response.arrayBuffer(); + wasm = await WebAssembly.instantiate(module_bytes, importObj); + } memory_bytes = new Uint8Array(wasm.instance.exports.memory.buffer); @@ -41,3 +46,9 @@ async function roc_web_platform_run(wasm_filename, dom_node) { } } } + +if (typeof module !== 'undefined') { + module.exports = { + roc_web_platform_run, + }; +} diff --git a/examples/hello-web/test-node.js b/examples/hello-web/test-node.js new file mode 100644 index 0000000000..ce1eed5d6e --- /dev/null +++ b/examples/hello-web/test-node.js @@ -0,0 +1,25 @@ +/** + * Node.js test file for hello-web example + * We are not running this in CI currently, and Node.js is not a Roc dependency. + * But if you happen to have it, you can run this. + */ + +// Node doesn't have the fetch API +const fs = require("fs/promises"); +global.fetch = (filename) => + fs.readFile(filename).then((buffer) => ({ + arrayBuffer() { + return buffer; + }, + })); + +const { roc_web_platform_run } = require("./platform/host"); + +roc_web_platform_run("./hello-world.wasm", (string_from_roc) => { + const expected = "Hello, World!"; + if (string_from_roc !== expected) { + console.error(`Expected "${expected}", but got "${string_from_roc}"`); + process.exit(1); + } + console.log("OK"); +}); From 874bf803213b35b124f95e60cb76fac5731c0ebe Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 19 Sep 2021 11:59:24 +0100 Subject: [PATCH 027/136] README corrections --- examples/hello-web/README.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/examples/hello-web/README.md b/examples/hello-web/README.md index 4f0ec8dfd1..f0beccdb6b 100644 --- a/examples/hello-web/README.md +++ b/examples/hello-web/README.md @@ -1,30 +1,35 @@ # Hello, World! -To run, `cd` into this directory and run: +To run, go to the project home directory and run: ```bash -$ cargo run Hello.roc +$ cargo run -- build --backend=wasm32 examples/hello-web/Hello.roc ``` -To run in release mode instead, do: +Then `cd` into the example directory and run any web server that can handle WebAssembly. +For example with `http-server`: ```bash -$ cargo run --release Hello.roc +cd examples/hello-web +npm install -g http-server +http-server ``` +Now open your browser at http://localhost:8080 + ## Design Notes -This demonstrates the basic design of hosts: Roc code gets compiled into a pure +This demonstrates the basic design of hosts: Roc code gets compiled into a pure function (in this case, a thunk that always returns `"Hello, World!"`) and then the host calls that function. Fundamentally, that's the whole idea! The host might not even have a `main` - it could be a library, a plugin, anything. Everything else is built on this basic "hosts calling linked pure functions" design. For example, things get more interesting when the compiled Roc function returns -a `Task` - that is, a tagged union data structure containing function pointers -to callback closures. This lets the Roc pure function describe arbitrary -chainable effects, which the host can interpret to perform I/O as requested by -the Roc program. (The tagged union `Task` would have a variant for each supported +a `Task` - that is, a tagged union data structure containing function pointers +to callback closures. This lets the Roc pure function describe arbitrary +chainable effects, which the host can interpret to perform I/O as requested by +the Roc program. (The tagged union `Task` would have a variant for each supported I/O operation.) In this trivial example, it's very easy to line up the API between the host and @@ -39,6 +44,6 @@ Roc application authors only care about the Roc-host/Roc-app portion, and the host author only cares about the Roc-host/C boundary when implementing the host. Using this glue code, the Roc compiler can generate C header files describing the -boundary. This not only gets us host compatibility with C compilers, but also -Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) +boundary. This not only gets us host compatibility with C compilers, but also +Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) generates correct Rust FFI bindings from C headers. From c5eeaab2c24c7382c883598544f769b168befcae Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 19 Sep 2021 18:34:42 +0200 Subject: [PATCH 028/136] remove callresult from cli examples --- .../fixtures/multi-dep-str/platform/host.zig | 10 ++--- .../multi-dep-thunk/platform/host.zig | 10 ++--- compiler/gen_llvm/src/llvm/build.rs | 23 ++++++---- examples/benchmarks/platform/host.zig | 16 +------ examples/cli/platform/src/lib.rs | 18 ++------ examples/effect/thing/platform-dir/host.zig | 42 ++++++------------- examples/hello-rust/platform/src/lib.rs | 25 ++++------- examples/hello-world/platform/host.c | 10 ++--- examples/hello-zig/platform/host.zig | 8 ++-- examples/quicksort/platform/host.zig | 10 ++--- 10 files changed, 60 insertions(+), 112 deletions(-) diff --git a/cli/tests/fixtures/multi-dep-str/platform/host.zig b/cli/tests/fixtures/multi-dep-str/platform/host.zig index bf16bd9b65..8ecaced47f 100644 --- a/cli/tests/fixtures/multi-dep-str/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-str/platform/host.zig @@ -22,7 +22,7 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(*RocCallResult) void; +extern fn roc__mainForHost_1_exposed(*RocStr) void; extern fn malloc(size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; @@ -47,8 +47,6 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { std.process.exit(0); } -const RocCallResult = extern struct { flag: usize, content: RocStr }; - const Unit = extern struct {}; pub export fn main() i32 { @@ -56,7 +54,7 @@ pub export fn main() i32 { const stderr = std.io.getStdErr().writer(); // make space for the result - var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() }; + var callresult = RocStr.empty(); // start time var ts1: std.os.timespec = undefined; @@ -66,9 +64,9 @@ pub export fn main() i32 { roc__mainForHost_1_exposed(&callresult); // stdout the result - stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable; + stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; - callresult.content.deinit(); + callresult.deinit(); // end time var ts2: std.os.timespec = undefined; diff --git a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig index bf16bd9b65..8ecaced47f 100644 --- a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig @@ -22,7 +22,7 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(*RocCallResult) void; +extern fn roc__mainForHost_1_exposed(*RocStr) void; extern fn malloc(size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; @@ -47,8 +47,6 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { std.process.exit(0); } -const RocCallResult = extern struct { flag: usize, content: RocStr }; - const Unit = extern struct {}; pub export fn main() i32 { @@ -56,7 +54,7 @@ pub export fn main() i32 { const stderr = std.io.getStdErr().writer(); // make space for the result - var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() }; + var callresult = RocStr.empty(); // start time var ts1: std.os.timespec = undefined; @@ -66,9 +64,9 @@ pub export fn main() i32 { roc__mainForHost_1_exposed(&callresult); // stdout the result - stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable; + stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; - callresult.content.deinit(); + callresult.deinit(); // end time var ts2: std.os.timespec = undefined; diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 8eac005088..22ceff9704 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3098,13 +3098,19 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( ) -> FunctionValue<'ctx> { let context = env.context; - let wrapper_return_type = context.struct_type( - &[ - context.i64_type().into(), - roc_function.get_type().get_return_type().unwrap(), - ], - false, - ); + let wrapper_return_type = if env.is_gen_test { + context + .struct_type( + &[ + context.i64_type().into(), + roc_function.get_type().get_return_type().unwrap(), + ], + false, + ) + .into() + } else { + roc_function.get_type().get_return_type().unwrap() + }; let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); for layout in arguments { @@ -3189,7 +3195,8 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); - make_good_roc_result(env, call_unwrapped_result) + // make_good_roc_result(env, call_unwrapped_result) + call_unwrapped_result } }; diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index 0021e8d8df..097c342692 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -92,21 +92,9 @@ pub export fn main() callconv(.C) u8 { roc__mainForHost_1_exposed(output); - const flag = @ptrCast(*u64, @alignCast(@alignOf(u64), output)).*; + const closure_data_pointer = @ptrCast([*]u8, output); - if (flag == 0) { - // all is well - const closure_data_pointer = @ptrCast([*]u8, output[@sizeOf(u64)..size]); - - call_the_closure(closure_data_pointer); - } else { - const ptr = @ptrCast(*u32, output + @sizeOf(u64)); - const msg = @intToPtr([*:0]const u8, ptr.*); - const stderr = std.io.getStdErr().writer(); - stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; - - return 0; - } + call_the_closure(closure_data_pointer); var ts2: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs index 2a98179f8e..682e10de45 100644 --- a/examples/cli/platform/src/lib.rs +++ b/examples/cli/platform/src/lib.rs @@ -70,23 +70,11 @@ pub fn rust_main() -> isize { roc_main(buffer); - let output = buffer as *mut RocCallResult<()>; + let result = call_the_closure(buffer); - match (&*output).into() { - Ok(()) => { - let closure_data_ptr = buffer.offset(8); - let result = call_the_closure(closure_data_ptr as *const u8); + std::alloc::dealloc(buffer, layout); - std::alloc::dealloc(buffer, layout); - - result - } - Err(msg) => { - std::alloc::dealloc(buffer, layout); - - panic!("Roc failed with message: {}", msg); - } - } + result }; // Exit code diff --git a/examples/effect/thing/platform-dir/host.zig b/examples/effect/thing/platform-dir/host.zig index 063d28973f..d62dae1936 100644 --- a/examples/effect/thing/platform-dir/host.zig +++ b/examples/effect/thing/platform-dir/host.zig @@ -55,15 +55,18 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { const Unit = extern struct {}; pub export fn main() u8 { + const allocator = std.heap.page_allocator; + const stdout = std.io.getStdOut().writer(); const stderr = std.io.getStdErr().writer(); - const size = @intCast(usize, roc__mainForHost_size()); - const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable; + // NOTE the return size can be zero, which will segfault. Always allocate at least 8 bytes + const size = std.math.max(8, @intCast(usize, roc__mainForHost_size())); + const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; var output = @ptrCast([*]u8, raw_output); defer { - std.heap.c_allocator.free(raw_output); + allocator.free(raw_output); } var ts1: std.os.timespec = undefined; @@ -71,21 +74,7 @@ pub export fn main() u8 { roc__mainForHost_1_exposed(output); - const elements = @ptrCast([*]u64, @alignCast(8, output)); - - var flag = elements[0]; - - if (flag == 0) { - // all is well - const closure_data_pointer = @ptrCast([*]u8, output[8..size]); - - call_the_closure(closure_data_pointer); - } else { - const msg = @intToPtr([*:0]const u8, elements[1]); - stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; - - return 0; - } + call_the_closure(output); var ts2: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; @@ -102,27 +91,20 @@ fn to_seconds(tms: std.os.timespec) f64 { } fn call_the_closure(closure_data_pointer: [*]u8) void { + const allocator = std.heap.page_allocator; + const size = roc__mainForHost_1_Fx_result_size(); - const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable; + const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; var output = @ptrCast([*]u8, raw_output); defer { - std.heap.c_allocator.free(raw_output); + allocator.free(raw_output); } const flags: u8 = 0; - roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output); - const elements = @ptrCast([*]u64, @alignCast(8, output)); - - var flag = elements[0]; - - if (flag == 0) { - return; - } else { - unreachable; - } + return; } pub export fn roc_fx_getLine() str.RocStr { diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs index 8726cab399..f29f25d6df 100644 --- a/examples/hello-rust/platform/src/lib.rs +++ b/examples/hello-rust/platform/src/lib.rs @@ -3,12 +3,12 @@ use core::ffi::c_void; use core::mem::MaybeUninit; use libc::c_char; -use roc_std::{RocCallResult, RocStr}; +use roc_std::RocStr; use std::ffi::CStr; extern "C" { #[link_name = "roc__mainForHost_1_exposed"] - fn roc_main(output: *mut RocCallResult) -> (); + fn roc_main(output: *mut RocStr) -> (); } #[no_mangle] @@ -46,25 +46,18 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { #[no_mangle] pub fn rust_main() -> isize { - let mut call_result: MaybeUninit> = MaybeUninit::uninit(); + let mut raw_output: MaybeUninit = MaybeUninit::uninit(); unsafe { - roc_main(call_result.as_mut_ptr()); + roc_main(raw_output.as_mut_ptr()); - let output = call_result.assume_init(); + let roc_str = raw_output.assume_init(); - match output.into() { - Ok(roc_str) => { - let len = roc_str.len(); - let str_bytes = roc_str.get_bytes() as *const libc::c_void; + let len = roc_str.len(); + let str_bytes = roc_str.get_bytes() as *const libc::c_void; - if libc::write(1, str_bytes, len) < 0 { - panic!("Writing to stdout failed!"); - } - } - Err(msg) => { - panic!("Roc failed with message: {}", msg); - } + if libc::write(1, str_bytes, len) < 0 { + panic!("Writing to stdout failed!"); } } diff --git a/examples/hello-world/platform/host.c b/examples/hello-world/platform/host.c index f968d0c763..8eecfb14c3 100644 --- a/examples/hello-world/platform/host.c +++ b/examples/hello-world/platform/host.c @@ -51,23 +51,19 @@ size_t roc_str_len(struct RocStr str) { } } -struct RocCallResult { - size_t flag; - struct RocStr content; -}; -extern void roc__mainForHost_1_exposed(struct RocCallResult *re); +extern void roc__mainForHost_1_exposed(struct RocStr *re); int main() { // Make space for the Roc call result - struct RocCallResult call_result; + struct RocStr call_result; // Call Roc to populate call_result roc__mainForHost_1_exposed(&call_result); // Determine str_len and the str_bytes pointer, // taking into account the small string optimization. - struct RocStr str = call_result.content; + struct RocStr str = call_result; size_t str_len = roc_str_len(str); char* str_bytes; diff --git a/examples/hello-zig/platform/host.zig b/examples/hello-zig/platform/host.zig index 8384c996d3..aa62ac6f03 100644 --- a/examples/hello-zig/platform/host.zig +++ b/examples/hello-zig/platform/host.zig @@ -54,7 +54,7 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(*RocCallResult) void; +extern fn roc__mainForHost_1_exposed(*RocStr) void; const RocCallResult = extern struct { flag: u64, content: RocStr }; @@ -65,7 +65,7 @@ pub fn main() u8 { const stderr = std.io.getStdErr().writer(); // make space for the result - var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() }; + var callresult = RocStr.empty(); // start time var ts1: std.os.timespec = undefined; @@ -75,9 +75,9 @@ pub fn main() u8 { roc__mainForHost_1_exposed(&callresult); // stdout the result - stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable; + stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; - callresult.content.deinit(); + callresult.deinit(); // end time var ts2: std.os.timespec = undefined; diff --git a/examples/quicksort/platform/host.zig b/examples/quicksort/platform/host.zig index 38fb29f699..a232ec46fc 100644 --- a/examples/quicksort/platform/host.zig +++ b/examples/quicksort/platform/host.zig @@ -20,7 +20,7 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(RocList, *RocCallResult) void; +extern fn roc__mainForHost_1_exposed(RocList, *RocList) void; const Align = extern struct { a: usize, b: usize }; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; @@ -72,8 +72,6 @@ const NUM_NUMS = 100; const RocList = extern struct { elements: [*]i64, length: usize }; -const RocCallResult = extern struct { flag: u64, content: RocList }; - const Unit = extern struct {}; pub export fn main() u8 { @@ -94,7 +92,7 @@ pub export fn main() u8 { const roc_list = RocList{ .elements = numbers, .length = NUM_NUMS }; // make space for the result - var callresult = RocCallResult{ .flag = 0, .content = undefined }; + var callresult: RocList = undefined; // start time var ts1: std.os.timespec = undefined; @@ -104,8 +102,8 @@ pub export fn main() u8 { roc__mainForHost_1_exposed(roc_list, &callresult); // stdout the result - const length = std.math.min(20, callresult.content.length); - var result = callresult.content.elements[0..length]; + const length = std.math.min(20, callresult.length); + var result = callresult.elements[0..length]; for (result) |x, i| { if (i == 0) { From f3bf9bdbd03f60bfc119e71f911f9176bdcbbfd5 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 19 Sep 2021 18:50:52 +0200 Subject: [PATCH 029/136] add comment to fib host.zig --- examples/fib/platform/host.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/fib/platform/host.zig b/examples/fib/platform/host.zig index 11878782c8..111a84c27f 100644 --- a/examples/fib/platform/host.zig +++ b/examples/fib/platform/host.zig @@ -20,6 +20,8 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; +// NOTE the LLVM backend expects this signature +// extern fn roc__mainForHost_1_exposed(i64, *i64) void; extern fn roc__mainForHost_1_exposed(i64) i64; const Align = extern struct { a: usize, b: usize }; From cb82bcb173d817430b8db1634798bb9d39aa7845 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 19 Sep 2021 18:53:46 +0200 Subject: [PATCH 030/136] remove callresult from linker test --- linker/tests/fib/platform/host.zig | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/linker/tests/fib/platform/host.zig b/linker/tests/fib/platform/host.zig index 7bcd06290d..c439c889c7 100644 --- a/linker/tests/fib/platform/host.zig +++ b/linker/tests/fib/platform/host.zig @@ -43,9 +43,7 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(i64, *RocCallResult) void; - -const RocCallResult = extern struct { flag: usize, content: u64 }; +extern fn roc__mainForHost_1_exposed(i64, *i64) void; const Unit = extern struct {}; @@ -55,7 +53,7 @@ pub export fn main() u8 { const iterations: usize = 50; // number of times to repeatedly find that Fibonacci number // make space for the result - var callresult = RocCallResult{ .flag = 0, .content = 0 }; + var callresult = 0; var remaining_iterations = iterations; while (remaining_iterations > 0) { @@ -66,7 +64,10 @@ pub export fn main() u8 { } // stdout the final result - stdout.print("After calling the Roc app {d} times, the Fibonacci number at index {d} is {d}\n", .{iterations, fib_number_to_find, callresult.content}) catch unreachable; + stdout.print( + "After calling the Roc app {d} times, the Fibonacci number at index {d} is {d}\n", + .{ iterations, fib_number_to_find, callresult }, + ) catch unreachable; return 0; -} \ No newline at end of file +} From f13e65ff8ed7ae2e3aebc59b31951738ef3fc767 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 19 Sep 2021 20:54:32 +0200 Subject: [PATCH 031/136] more removal of roccallresult --- examples/cli/platform/src/lib.rs | 12 +++--------- examples/hello-zig/platform/host.zig | 2 -- linker/tests/fib/platform/app.zig | 4 +--- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs index 682e10de45..2b24da5ff7 100644 --- a/examples/cli/platform/src/lib.rs +++ b/examples/cli/platform/src/lib.rs @@ -4,7 +4,7 @@ use core::alloc::Layout; use core::ffi::c_void; use core::mem::MaybeUninit; use libc; -use roc_std::{RocCallResult, RocStr}; +use roc_std::RocStr; use std::ffi::CStr; use std::os::raw::c_char; @@ -93,15 +93,9 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { buffer as *mut u8, ); - let output = &*(buffer as *mut RocCallResult<()>); + std::alloc::dealloc(buffer, layout); - match output.into() { - Ok(_) => { - std::alloc::dealloc(buffer, layout); - 0 - } - Err(e) => panic!("failed with {}", e), - } + 0 } #[no_mangle] diff --git a/examples/hello-zig/platform/host.zig b/examples/hello-zig/platform/host.zig index aa62ac6f03..54883b2fcb 100644 --- a/examples/hello-zig/platform/host.zig +++ b/examples/hello-zig/platform/host.zig @@ -56,8 +56,6 @@ const Allocator = mem.Allocator; extern fn roc__mainForHost_1_exposed(*RocStr) void; -const RocCallResult = extern struct { flag: u64, content: RocStr }; - const Unit = extern struct {}; pub fn main() u8 { diff --git a/linker/tests/fib/platform/app.zig b/linker/tests/fib/platform/app.zig index 7cc1759319..105908633f 100644 --- a/linker/tests/fib/platform/app.zig +++ b/linker/tests/fib/platform/app.zig @@ -1,3 +1 @@ -const RocCallResult = extern struct { flag: usize, content: u64 }; - -export fn roc__mainForHost_1_exposed(_i: i64, _result: *RocCallResult) void{} +export fn roc__mainForHost_1_exposed(_i: i64, _result: *u64) void {} From e319d1e758ccb7f8741e8e8d81198e04b11cd3c7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 19 Sep 2021 22:05:48 +0200 Subject: [PATCH 032/136] make roc main return values, instead of write them into pointer --- .../fixtures/multi-dep-str/platform/host.zig | 7 +- .../multi-dep-thunk/platform/host.zig | 7 +- compiler/gen_llvm/src/llvm/build.rs | 209 ++++++++++++++++-- examples/benchmarks/platform/host.zig | 4 +- examples/hello-rust/platform/src/lib.rs | 8 +- examples/hello-world/platform/host.c | 7 +- examples/hello-zig/platform/host.zig | 7 +- examples/quicksort/platform/host.zig | 7 +- roc_std/src/lib.rs | 63 ------ 9 files changed, 199 insertions(+), 120 deletions(-) diff --git a/cli/tests/fixtures/multi-dep-str/platform/host.zig b/cli/tests/fixtures/multi-dep-str/platform/host.zig index 8ecaced47f..4c8ee671cf 100644 --- a/cli/tests/fixtures/multi-dep-str/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-str/platform/host.zig @@ -22,7 +22,7 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(*RocStr) void; +extern fn roc__mainForHost_1_exposed() RocStr; extern fn malloc(size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; @@ -53,15 +53,12 @@ pub export fn main() i32 { const stdout = std.io.getStdOut().writer(); const stderr = std.io.getStdErr().writer(); - // make space for the result - var callresult = RocStr.empty(); - // start time var ts1: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; // actually call roc to populate the callresult - roc__mainForHost_1_exposed(&callresult); + const callresult = roc__mainForHost_1_exposed(); // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) 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 8ecaced47f..4c8ee671cf 100644 --- a/cli/tests/fixtures/multi-dep-thunk/platform/host.zig +++ b/cli/tests/fixtures/multi-dep-thunk/platform/host.zig @@ -22,7 +22,7 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(*RocStr) void; +extern fn roc__mainForHost_1_exposed() RocStr; extern fn malloc(size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; @@ -53,15 +53,12 @@ pub export fn main() i32 { const stdout = std.io.getStdOut().writer(); const stderr = std.io.getStdErr().writer(); - // make space for the result - var callresult = RocStr.empty(); - // start time var ts1: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; // actually call roc to populate the callresult - roc__mainForHost_1_exposed(&callresult); + const callresult = roc__mainForHost_1_exposed(); // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 22ceff9704..dbfb8bacc2 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -704,8 +704,14 @@ fn promote_to_main_function<'a, 'ctx, 'env>( let main_fn_name = "$Test.main"; // Add main to the module. - let main_fn = - expose_function_to_host_help_c_abi(env, main_fn_name, roc_main_fn, &[], main_fn_name); + let main_fn = expose_function_to_host_help_c_abi( + env, + main_fn_name, + roc_main_fn, + &[], + top_level.result, + main_fn_name, + ); (main_fn_name, main_fn) } @@ -3075,6 +3081,7 @@ fn expose_function_to_host<'a, 'ctx, 'env>( symbol: Symbol, roc_function: FunctionValue<'ctx>, arguments: &[Layout<'a>], + return_layout: Layout<'a>, ) { // Assumption: there is only one specialization of a host-exposed function let ident_string = symbol.as_str(&env.interns); @@ -3085,32 +3092,19 @@ fn expose_function_to_host<'a, 'ctx, 'env>( ident_string, roc_function, arguments, + return_layout, &c_function_name, ); } -fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( +fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - ident_string: &str, roc_function: FunctionValue<'ctx>, arguments: &[Layout<'a>], c_function_name: &str, ) -> FunctionValue<'ctx> { - let context = env.context; - - let wrapper_return_type = if env.is_gen_test { - context - .struct_type( - &[ - context.i64_type().into(), - roc_function.get_type().get_return_type().unwrap(), - ], - false, - ) - .into() - } else { - roc_function.get_type().get_return_type().unwrap() - }; + // NOTE we ingore env.is_gen_test here + let wrapper_return_type = roc_function.get_type().get_return_type().unwrap(); let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); for layout in arguments { @@ -3121,6 +3115,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( // let mut argument_types = roc_function.get_type().get_param_types(); let mut argument_types = cc_argument_types; let return_type = wrapper_return_type; + let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.push(output_type.into()); @@ -3148,9 +3143,11 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( debug_info_init!(env, c_function); // drop the final argument, which is the pointer we write the result into - let args = c_function.get_params(); - let output_arg_index = args.len() - 1; - let args = &args[..args.len() - 1]; + let args_vector = c_function.get_params(); + let mut args = args_vector.as_slice(); + let args_length = args.len(); + + args = &args[..args.len() - 1]; let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); @@ -3200,15 +3197,173 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( } }; + let output_arg_index = args_length - 1; + let output_arg = c_function .get_nth_param(output_arg_index as u32) .unwrap() .into_pointer_value(); builder.build_store(output_arg, call_result); - builder.build_return(None); + c_function +} + +fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + ident_string: &str, + roc_function: FunctionValue<'ctx>, + arguments: &[Layout<'a>], + return_layout: Layout<'a>, + c_function_name: &str, +) -> FunctionValue<'ctx> { + let context = env.context; + + // a generic version that writes the result into a passed *u8 pointer + if !env.is_gen_test { + expose_function_to_host_help_c_abi_generic( + env, + roc_function, + arguments, + &format!("{}_generic", c_function_name), + ); + } + + let wrapper_return_type = if env.is_gen_test { + context + .struct_type( + &[ + context.i64_type().into(), + roc_function.get_type().get_return_type().unwrap(), + ], + false, + ) + .into() + } else { + roc_function.get_type().get_return_type().unwrap() + }; + + let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); + for layout in arguments { + cc_argument_types.push(to_cc_type(env, layout)); + } + + // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` if the C abi demands it + let mut argument_types = cc_argument_types; + let return_type = wrapper_return_type; + + let cc_return = to_cc_return(env, &return_layout); + + let c_function_type = match cc_return { + CCReturn::Void => env.context.void_type().fn_type(&argument_types, false), + CCReturn::Return if !env.is_gen_test => return_type.fn_type(&argument_types, false), + _ => { + let output_type = return_type.ptr_type(AddressSpace::Generic); + argument_types.push(output_type.into()); + env.context.void_type().fn_type(&argument_types, false) + } + }; + + let c_function = add_func( + env.module, + c_function_name, + c_function_type, + Linkage::External, + C_CALL_CONV, + ); + + let subprogram = env.new_subprogram(c_function_name); + c_function.set_subprogram(subprogram); + + // STEP 2: build the exposed function's body + let builder = env.builder; + let context = env.context; + + let entry = context.append_basic_block(c_function, "entry"); + + builder.position_at_end(entry); + + debug_info_init!(env, c_function); + + // drop the final argument, which is the pointer we write the result into + let args_vector = c_function.get_params(); + let mut args = args_vector.as_slice(); + let args_length = args.len(); + + if let CCReturn::ByPointer = cc_return { + args = &args[..args.len() - 1]; + } + + let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); + + let it = args.iter().zip(roc_function.get_type().get_param_types()); + for (arg, fastcc_type) in it { + let arg_type = arg.get_type(); + if arg_type == fastcc_type { + // the C and Fast calling conventions agree + arguments_for_call.push(*arg); + } else { + let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type"); + arguments_for_call.push(cast); + } + } + + let arguments_for_call = &arguments_for_call.into_bump_slice(); + + debug_assert_eq!(args.len(), roc_function.get_params().len()); + + let call_result = { + if env.is_gen_test { + let roc_wrapper_function = make_exception_catcher(env, roc_function); + debug_assert_eq!( + arguments_for_call.len(), + roc_wrapper_function.get_params().len() + ); + + builder.position_at_end(entry); + + let call_wrapped = builder.build_call( + roc_wrapper_function, + arguments_for_call, + "call_wrapped_function", + ); + call_wrapped.set_call_convention(FAST_CALL_CONV); + + call_wrapped.try_as_basic_value().left().unwrap() + } else { + let call_unwrapped = + builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function"); + call_unwrapped.set_call_convention(FAST_CALL_CONV); + + let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); + + // make_good_roc_result(env, call_unwrapped_result) + call_unwrapped_result + } + }; + + match cc_return { + CCReturn::Void => { + // TODO return empty struct here? + builder.build_return(None); + } + CCReturn::Return if !env.is_gen_test => { + builder.build_return(Some(&call_result)); + } + _ => { + let output_arg_index = args_length - 1; + + let output_arg = c_function + .get_nth_param(output_arg_index as u32) + .unwrap() + .into_pointer_value(); + + builder.build_store(output_arg, call_result); + builder.build_return(None); + } + } + // STEP 3: build a {} -> u64 function that gives the size of the return type let size_function_type = env.context.i64_type().fn_type(&[], false); let size_function_name: String = format!("roc__{}_size", ident_string); @@ -3722,7 +3877,13 @@ fn build_proc_header<'a, 'ctx, 'env>( if env.exposed_to_host.contains(&symbol) { let arguments = Vec::from_iter_in(proc.args.iter().map(|(layout, _)| *layout), env.arena); - expose_function_to_host(env, symbol, fn_val, arguments.into_bump_slice()); + expose_function_to_host( + env, + symbol, + fn_val, + arguments.into_bump_slice(), + proc.ret_layout, + ); } fn_val diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index 097c342692..91967299b4 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -23,7 +23,7 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed([*]u8) void; +extern fn roc__mainForHost_1_exposed_generic([*]u8) void; extern fn roc__mainForHost_size() i64; extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void; extern fn roc__mainForHost_1_Fx_size() i64; @@ -90,7 +90,7 @@ pub export fn main() callconv(.C) u8 { var ts1: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; - roc__mainForHost_1_exposed(output); + roc__mainForHost_1_exposed_generic(output); const closure_data_pointer = @ptrCast([*]u8, output); diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs index f29f25d6df..6a78b4db0c 100644 --- a/examples/hello-rust/platform/src/lib.rs +++ b/examples/hello-rust/platform/src/lib.rs @@ -8,7 +8,7 @@ use std::ffi::CStr; extern "C" { #[link_name = "roc__mainForHost_1_exposed"] - fn roc_main(output: *mut RocStr) -> (); + fn roc_main() -> RocStr; } #[no_mangle] @@ -46,12 +46,8 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { #[no_mangle] pub fn rust_main() -> isize { - let mut raw_output: MaybeUninit = MaybeUninit::uninit(); - unsafe { - roc_main(raw_output.as_mut_ptr()); - - let roc_str = raw_output.assume_init(); + let roc_str = roc_main(); let len = roc_str.len(); let str_bytes = roc_str.get_bytes() as *const libc::c_void; diff --git a/examples/hello-world/platform/host.c b/examples/hello-world/platform/host.c index 8eecfb14c3..1a73962b18 100644 --- a/examples/hello-world/platform/host.c +++ b/examples/hello-world/platform/host.c @@ -52,14 +52,11 @@ size_t roc_str_len(struct RocStr str) { } -extern void roc__mainForHost_1_exposed(struct RocStr *re); +extern struct RocStr roc__mainForHost_1_exposed(); int main() { - // Make space for the Roc call result - struct RocStr call_result; - // Call Roc to populate call_result - roc__mainForHost_1_exposed(&call_result); + struct RocStr call_result = roc__mainForHost_1_exposed(); // Determine str_len and the str_bytes pointer, // taking into account the small string optimization. diff --git a/examples/hello-zig/platform/host.zig b/examples/hello-zig/platform/host.zig index 54883b2fcb..d1f3ddb287 100644 --- a/examples/hello-zig/platform/host.zig +++ b/examples/hello-zig/platform/host.zig @@ -54,7 +54,7 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(*RocStr) void; +extern fn roc__mainForHost_1_exposed() RocStr; const Unit = extern struct {}; @@ -62,15 +62,12 @@ pub fn main() u8 { const stdout = std.io.getStdOut().writer(); const stderr = std.io.getStdErr().writer(); - // make space for the result - var callresult = RocStr.empty(); - // start time var ts1: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; // actually call roc to populate the callresult - roc__mainForHost_1_exposed(&callresult); + var callresult = roc__mainForHost_1_exposed(); // stdout the result stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable; diff --git a/examples/quicksort/platform/host.zig b/examples/quicksort/platform/host.zig index a232ec46fc..94c423429e 100644 --- a/examples/quicksort/platform/host.zig +++ b/examples/quicksort/platform/host.zig @@ -20,7 +20,7 @@ comptime { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(RocList, *RocList) void; +extern fn roc__mainForHost_1_exposed(RocList) RocList; const Align = extern struct { a: usize, b: usize }; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; @@ -91,15 +91,12 @@ pub export fn main() u8 { const roc_list = RocList{ .elements = numbers, .length = NUM_NUMS }; - // make space for the result - var callresult: RocList = undefined; - // start time var ts1: std.os.timespec = undefined; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; // actually call roc to populate the callresult - roc__mainForHost_1_exposed(roc_list, &callresult); + var callresult = roc__mainForHost_1_exposed(roc_list); // stdout the result const length = std.math.min(20, callresult.length); diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index d5a15b4627..b6f07dcb8a 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -758,69 +758,6 @@ pub enum RocResult { Ok(Ok), } -#[allow(non_camel_case_types)] -type c_char = u8; - -#[repr(u64)] -pub enum RocCallResult { - Success(T), - Failure(*mut c_char), -} - -impl From> for Result { - fn from(call_result: RocCallResult) -> Self { - use RocCallResult::*; - - match call_result { - Success(value) => Ok(value), - Failure(failure) => Err({ - let msg = unsafe { - let mut null_byte_index = 0; - loop { - if *failure.offset(null_byte_index) == 0 { - break; - } - null_byte_index += 1; - } - - let bytes = core::slice::from_raw_parts(failure, null_byte_index as usize); - - core::str::from_utf8_unchecked(bytes) - }; - - msg - }), - } - } -} - -impl<'a, T: Sized + Copy> From<&'a RocCallResult> for Result { - fn from(call_result: &'a RocCallResult) -> Self { - use RocCallResult::*; - - match call_result { - Success(value) => Ok(*value), - Failure(failure) => Err({ - let msg = unsafe { - let mut null_byte_index = 0; - loop { - if *failure.offset(null_byte_index) == 0 { - break; - } - null_byte_index += 1; - } - - let bytes = core::slice::from_raw_parts(*failure, null_byte_index as usize); - - core::str::from_utf8_unchecked(bytes) - }; - - msg - }), - } - } -} - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct RocDec(pub i128); From f6cce89e0853e62f63a572b99a1a586000b89390 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 19 Sep 2021 22:06:41 +0200 Subject: [PATCH 033/136] enable fib example --- cli/tests/cli_run.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index bfde509e93..2e26163837 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -157,16 +157,6 @@ mod cli_run { let example = $example; let file_name = example_file(dir_name, example.filename); - match example.filename { - "Fib.roc" => { - // it is broken because the dev and normal backend don't generate the - // same name for main. The dev version is expected here. - eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename); - return; - } - _ => {} - } - // Check with and without optimizations check_output_with_stdin( &file_name, From b0f590f09e8373b27c002534838bd721ad9c8b34 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 20 Sep 2021 09:11:31 +0200 Subject: [PATCH 034/136] Improve cannot find str.zig error --- compiler/build/src/link.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index d3f7aba399..1103ca6e2f 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -56,7 +56,7 @@ fn find_zig_str_path() -> PathBuf { return zig_str_path; } - panic!("cannot find `str.zig`") + panic!("cannot find `str.zig`. Launch me from either the root of the roc repo or one level down(roc/examples, roc/cli...)") } fn find_wasi_libc_path() -> PathBuf { From f8e53d6268a5cca13845b13909ac30541d260b08 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 20 Sep 2021 19:33:56 +0200 Subject: [PATCH 035/136] temp disable benchmarks --- .github/workflows/benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 512d4113fc..72cb2cbd22 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -42,4 +42,4 @@ jobs: run: cd ci/bench-runner && cargo build --release && cd ../.. - name: run benchmarks with regression check - run: ./ci/bench-runner/target/release/bench-runner --check-executables-changed + run: echo "TODO re-enable benchmarks once race condition is fixed"#./ci/bench-runner/target/release/bench-runner --check-executables-changed From fe0746951daec557923ae51dc02c1042f2af8714 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Sep 2021 22:59:42 +0200 Subject: [PATCH 036/136] add dummy test for the hello-web example --- cli/tests/cli_run.rs | 14 ++++++++++---- examples/hello-web/.gitignore | 2 +- examples/hello-web/Hello.roc | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index bfde509e93..0e915fe2ac 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -157,10 +157,9 @@ mod cli_run { let example = $example; let file_name = example_file(dir_name, example.filename); - match example.filename { - "Fib.roc" => { - // it is broken because the dev and normal backend don't generate the - // same name for main. The dev version is expected here. + match example.executable_filename { + "hello-web" => { + // this is a web webassembly example, but we don't test with JS at the moment eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename); return; } @@ -234,6 +233,13 @@ mod cli_run { expected_ending:"Hello, World!\n", use_valgrind: true, }, + hello_world:"hello-web" => Example { + filename: "Hello.roc", + executable_filename: "hello-web", + stdin: &[], + expected_ending:"Hello, World!\n", + use_valgrind: true, + }, fib:"fib" => Example { filename: "Fib.roc", executable_filename: "fib", diff --git a/examples/hello-web/.gitignore b/examples/hello-web/.gitignore index a957f57cda..227b499154 100644 --- a/examples/hello-web/.gitignore +++ b/examples/hello-web/.gitignore @@ -1,2 +1,2 @@ -hello-world +hello-web *.wat diff --git a/examples/hello-web/Hello.roc b/examples/hello-web/Hello.roc index b27d53cf8c..49f05ceacf 100644 --- a/examples/hello-web/Hello.roc +++ b/examples/hello-web/Hello.roc @@ -1,4 +1,4 @@ -app "hello-world" +app "hello-web" packages { base: "platform" } imports [] provides [ main ] to base From 8bab0f4637cb2d3730eda4f62315541a2fe95e82 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Sep 2021 23:07:07 +0200 Subject: [PATCH 037/136] emit *.wasm files if the backend is wasm32 --- cli/src/build.rs | 6 +++++- examples/hello-web/hello-world.wasm | 1 - examples/hello-web/index.html | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) delete mode 120000 examples/hello-web/hello-world.wasm diff --git a/cli/src/build.rs b/cli/src/build.rs index 5a6fc756dc..270c16afd7 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -143,7 +143,11 @@ pub fn build_file<'a>( let loaded = loaded; let cwd = roc_file_path.parent().unwrap(); - let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows + let mut binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows + + if emit_wasm { + binary_path.set_extension("wasm"); + } let code_gen_timing = match opt_level { OptLevel::Normal | OptLevel::Optimize => program::gen_from_mono_module_llvm( diff --git a/examples/hello-web/hello-world.wasm b/examples/hello-web/hello-world.wasm deleted file mode 120000 index bdd51cc27b..0000000000 --- a/examples/hello-web/hello-world.wasm +++ /dev/null @@ -1 +0,0 @@ -hello-world \ No newline at end of file diff --git a/examples/hello-web/index.html b/examples/hello-web/index.html index 99f633645d..87ca0302cb 100644 --- a/examples/hello-web/index.html +++ b/examples/hello-web/index.html @@ -4,7 +4,7 @@ From a25d6c82b5aec2bd2049f24f6822bca8622d7a43 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Sep 2021 23:11:24 +0200 Subject: [PATCH 038/136] provide roc_panic from javascript --- examples/hello-web/platform/host.js | 7 ++----- examples/hello-web/platform/host.zig | 8 +------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/examples/hello-web/platform/host.js b/examples/hello-web/platform/host.js index 9e3548aa26..0d953eabff 100644 --- a/examples/hello-web/platform/host.js +++ b/examples/hello-web/platform/host.js @@ -11,12 +11,9 @@ async function roc_web_platform_run(wasm_filename, callback) { const importObj = { wasi_snapshot_preview1: { - proc_exit: (code) => { - if (code !== 0) { - console.error(`Exited with code ${code}`); + roc_panic: (_pointer, _tag_id) => { + throw 'Roc panicked!'; } - exit_code = code; - }, }, env: { js_display_roc_string, diff --git a/examples/hello-web/platform/host.zig b/examples/hello-web/platform/host.zig index 961619340e..5d588d6912 100644 --- a/examples/hello-web/platform/host.zig +++ b/examples/hello-web/platform/host.zig @@ -43,13 +43,7 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr))); } -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { - _ = tag_id; - const stderr = std.io.getStdErr().writer(); - const msg = @ptrCast([*:0]const u8, c_ptr); - stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; - std.process.exit(0); -} +// NOTE roc_panic is provided in the JS file, so it can throw an exception const mem = std.mem; const Allocator = mem.Allocator; From a4903ccf81eb77d4f54d4840cf6a2d124f91cff8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Sep 2021 23:27:20 +0200 Subject: [PATCH 039/136] fix repl --- compiler/gen_llvm/src/llvm/build.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index dbfb8bacc2..7c53a68531 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3256,7 +3256,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let cc_return = to_cc_return(env, &return_layout); let c_function_type = match cc_return { - CCReturn::Void => env.context.void_type().fn_type(&argument_types, false), + CCReturn::Void if !env.is_gen_test => { + env.context.void_type().fn_type(&argument_types, false) + } CCReturn::Return if !env.is_gen_test => return_type.fn_type(&argument_types, false), _ => { let output_type = return_type.ptr_type(AddressSpace::Generic); @@ -3291,8 +3293,17 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let mut args = args_vector.as_slice(); let args_length = args.len(); - if let CCReturn::ByPointer = cc_return { - args = &args[..args.len() - 1]; + match cc_return { + CCReturn::Return if !env.is_gen_test => { + debug_assert_eq!(args.len(), roc_function.get_params().len()); + } + CCReturn::Void if !env.is_gen_test => { + debug_assert_eq!(args.len(), roc_function.get_params().len()); + } + _ => { + args = &args[..args.len() - 1]; + debug_assert_eq!(args.len(), roc_function.get_params().len()); + } } let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); @@ -3311,8 +3322,6 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); - debug_assert_eq!(args.len(), roc_function.get_params().len()); - let call_result = { if env.is_gen_test { let roc_wrapper_function = make_exception_catcher(env, roc_function); @@ -3344,7 +3353,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( }; match cc_return { - CCReturn::Void => { + CCReturn::Void if !env.is_gen_test => { // TODO return empty struct here? builder.build_return(None); } From 16d098da5e9cf29d02674f6f8449f2490e96b0b0 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 20 Sep 2021 23:13:30 -0700 Subject: [PATCH 040/136] Add join points and tail call optimization to the dev backend. --- compiler/gen_dev/src/generic64/mod.rs | 322 +++++++++++++++++++------ compiler/gen_dev/src/lib.rs | 69 +++++- compiler/gen_dev/src/object_builder.rs | 7 +- compiler/gen_dev/tests/dev_num.rs | 74 +++--- examples/fib/.gitignore | 1 + examples/fib/Fib.roc | 26 +- 6 files changed, 375 insertions(+), 124 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 171a92ca9a..518a41a728 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -2,10 +2,9 @@ use crate::{Backend, Env, Relocation}; use bumpalo::collections::Vec; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::Symbol; -use roc_mono::ir::{BranchInfo, Literal, Stmt}; +use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, SelfRecursive, Stmt}; use roc_mono::layout::{Builtin, Layout}; use std::marker::PhantomData; -use target_lexicon::Triple; pub mod aarch64; pub mod x86_64; @@ -211,12 +210,16 @@ pub struct Backend64Bit< env: &'a Env<'a>, buf: Vec<'a, u8>, relocs: Vec<'a, Relocation>, + proc_name: Option, + is_self_recursive: Option, last_seen_map: MutMap>, layout_map: MutMap>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, + symbol_storage_map: MutMap>, literal_map: MutMap>, + join_map: MutMap, // This should probably be smarter than a vec. // There are certain registers we should always use first. With pushing and popping, this could get mixed. @@ -247,11 +250,13 @@ impl< CC: CallConv, > Backend<'a> for Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> { - fn new(env: &'a Env, _target: &Triple) -> Result { + fn new(env: &'a Env) -> Result { Ok(Backend64Bit { phantom_asm: PhantomData, phantom_cc: PhantomData, env, + proc_name: None, + is_self_recursive: None, buf: bumpalo::vec![in env.arena], relocs: bumpalo::vec![in env.arena], last_seen_map: MutMap::default(), @@ -259,6 +264,7 @@ impl< free_map: MutMap::default(), symbol_storage_map: MutMap::default(), literal_map: MutMap::default(), + join_map: MutMap::default(), general_free_regs: bumpalo::vec![in env.arena], general_used_regs: bumpalo::vec![in env.arena], general_used_callee_saved_regs: MutSet::default(), @@ -275,12 +281,15 @@ impl< self.env } - fn reset(&mut self) { + fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) { + self.proc_name = Some(name); + self.is_self_recursive = Some(is_self_recursive); self.stack_size = 0; self.free_stack_chunks.clear(); self.fn_call_stack_size = 0; self.last_seen_map.clear(); self.layout_map.clear(); + self.join_map.clear(); self.free_map.clear(); self.symbol_storage_map.clear(); self.buf.clear(); @@ -330,6 +339,19 @@ impl< )?; let setup_offset = out.len(); + // Deal with jumps to the return address. + let ret_offset = self.buf.len(); + let old_relocs = std::mem::replace(&mut self.relocs, bumpalo::vec![in self.env.arena]); + let mut tmp = bumpalo::vec![in self.env.arena]; + for reloc in old_relocs + .iter() + .filter(|reloc| matches!(reloc, Relocation::JmpToReturn { .. })) + { + if let Relocation::JmpToReturn { inst_loc, offset } = reloc { + self.update_jmp_imm32_offset(&mut tmp, *inst_loc, *offset, ret_offset as u64); + } + } + // Add function body. out.extend(&self.buf); @@ -342,23 +364,28 @@ impl< )?; ASM::ret(&mut out); - // Update relocs to include stack setup offset. + // Update other relocs to include stack setup offset. let mut out_relocs = bumpalo::vec![in self.env.arena]; - let old_relocs = std::mem::replace(&mut self.relocs, bumpalo::vec![in self.env.arena]); - out_relocs.extend(old_relocs.into_iter().map(|reloc| match reloc { - Relocation::LocalData { offset, data } => Relocation::LocalData { - offset: offset + setup_offset as u64, - data, - }, - Relocation::LinkedData { offset, name } => Relocation::LinkedData { - offset: offset + setup_offset as u64, - name, - }, - Relocation::LinkedFunction { offset, name } => Relocation::LinkedFunction { - offset: offset + setup_offset as u64, - name, - }, - })); + out_relocs.extend( + old_relocs + .into_iter() + .filter(|reloc| !matches!(reloc, Relocation::JmpToReturn { .. })) + .map(|reloc| match reloc { + Relocation::LocalData { offset, data } => Relocation::LocalData { + offset: offset + setup_offset as u64, + data, + }, + Relocation::LinkedData { offset, name } => Relocation::LinkedData { + offset: offset + setup_offset as u64, + name, + }, + Relocation::LinkedFunction { offset, name } => Relocation::LinkedFunction { + offset: offset + setup_offset as u64, + name, + }, + Relocation::JmpToReturn { .. } => unreachable!(), + }), + ); Ok((out.into_bump_slice(), out_relocs.into_bump_slice())) } @@ -401,29 +428,13 @@ impl< arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, ) -> Result<(), String> { + if let Some(SelfRecursive::SelfRecursive(id)) = self.is_self_recursive { + if &fn_name == self.proc_name.as_ref().unwrap() && self.join_map.contains_key(&id) { + return self.build_jump(&id, args, arg_layouts, ret_layout); + } + } // Save used caller saved regs. - let old_general_used_regs = std::mem::replace( - &mut self.general_used_regs, - bumpalo::vec![in self.env.arena], - ); - for (reg, saved_sym) in old_general_used_regs.into_iter() { - if CC::general_caller_saved(®) { - self.general_free_regs.push(reg); - self.free_to_stack(&saved_sym)?; - } else { - self.general_used_regs.push((reg, saved_sym)); - } - } - let old_float_used_regs = - std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in self.env.arena]); - for (reg, saved_sym) in old_float_used_regs.into_iter() { - if CC::float_caller_saved(®) { - self.float_free_regs.push(reg); - self.free_to_stack(&saved_sym)?; - } else { - self.float_used_regs.push((reg, saved_sym)); - } - } + self.push_used_caller_saved_regs_to_stack()?; // Put values in param regs or on top of the stack. let tmp_stack_size = CC::store_args( @@ -486,7 +497,7 @@ impl< // 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, 0); + let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); ret_jumps.push((jmp_location, jmp_offset)); // Overwite the original jne with the correct offset. @@ -510,12 +521,12 @@ impl< // 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() { - tmp.clear(); - let jmp_offset = ret_offset - start_offset; - ASM::jmp_imm32(&mut tmp, jmp_offset as i32); - for (i, byte) in tmp.iter().enumerate() { - self.buf[jmp_location + i] = *byte; - } + self.update_jmp_imm32_offset( + &mut tmp, + jmp_location as u64, + start_offset as u64, + ret_offset as u64, + ); } Ok(()) } else { @@ -526,6 +537,135 @@ impl< } } + fn build_join( + &mut self, + id: &JoinPointId, + parameters: &'a [Param<'a>], + body: &'a Stmt<'a>, + remainder: &'a Stmt<'a>, + ret_layout: &Layout<'a>, + ) -> Result<(), String> { + for param in parameters { + if param.borrow { + return Err("Join: borrowed parameters not yet supported".to_string()); + } + } + + // Create jump to remaining. + let jmp_location = self.buf.len(); + let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); + + // This section can essentially be seen as a sub function within the main function. + // Thus we build using a new backend with some minor extra syncronization. + let mut sub_backend = Self::new(self.env)?; + sub_backend.reset( + self.proc_name.as_ref().unwrap().clone(), + self.is_self_recursive.as_ref().unwrap().clone(), + ); + // Sync static maps of important information. + sub_backend.last_seen_map = self.last_seen_map.clone(); + sub_backend.layout_map = self.layout_map.clone(); + sub_backend.free_map = self.free_map.clone(); + + // Setup join point. + sub_backend.join_map.insert(*id, 0); + self.join_map.insert(*id, self.buf.len() as u64); + + // Sync stack size so the "sub function" doesn't mess up our stack. + sub_backend.stack_size = self.stack_size; + sub_backend.fn_call_stack_size = self.fn_call_stack_size; + + // Load params as if they were args. + let mut args = bumpalo::vec![in self.env.arena]; + for param in parameters { + args.push((param.layout, param.symbol)); + } + sub_backend.load_args(args.into_bump_slice(), ret_layout)?; + + // Build all statements in body. + sub_backend.build_stmt(body, ret_layout)?; + + // Merge the "sub function" into the main function. + let sub_func_offset = self.buf.len() as u64; + self.buf.extend_from_slice(&sub_backend.buf); + // Update stack based on how much was used by the sub function. + self.stack_size = sub_backend.stack_size; + self.fn_call_stack_size = sub_backend.fn_call_stack_size; + // Relocations must be shifted to be merged correctly. + self.relocs + .extend(sub_backend.relocs.into_iter().map(|reloc| match reloc { + Relocation::LocalData { offset, data } => Relocation::LocalData { + offset: offset + sub_func_offset, + data, + }, + Relocation::LinkedData { offset, name } => Relocation::LinkedData { + offset: offset + sub_func_offset, + name, + }, + Relocation::LinkedFunction { offset, name } => Relocation::LinkedFunction { + offset: offset + sub_func_offset, + name, + }, + Relocation::JmpToReturn { inst_loc, offset } => Relocation::JmpToReturn { + inst_loc: inst_loc + sub_func_offset, + offset: offset + sub_func_offset, + }, + })); + + // Overwite the original jump 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) + } + + fn build_jump( + &mut self, + id: &JoinPointId, + args: &'a [Symbol], + arg_layouts: &[Layout<'a>], + ret_layout: &Layout<'a>, + ) -> Result<(), String> { + // Treat this like a function call, but with a jump install of a call instruction at the end. + + self.push_used_caller_saved_regs_to_stack()?; + + let tmp_stack_size = CC::store_args( + &mut self.buf, + &self.symbol_storage_map, + args, + arg_layouts, + ret_layout, + )?; + self.fn_call_stack_size = std::cmp::max(self.fn_call_stack_size, tmp_stack_size); + + 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, + ); + Ok(()) + } else { + Err(format!( + "Jump: unknown point specified to jump to: {:?}", + id + )) + } + } + fn build_num_abs( &mut self, dst: &Symbol, @@ -828,29 +968,26 @@ impl< fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> { let val = self.symbol_storage_map.get(sym); match val { - Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => Ok(()), + Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => {} Some(SymbolStorage::GeneralReg(reg)) => { // If it fits in a general purpose register, just copy it over to. // Technically this can be optimized to produce shorter instructions if less than 64bits. ASM::mov_reg64_reg64(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *reg); - Ok(()) } - Some(SymbolStorage::FloatReg(reg)) if *reg == CC::FLOAT_RETURN_REGS[0] => Ok(()), + Some(SymbolStorage::FloatReg(reg)) if *reg == CC::FLOAT_RETURN_REGS[0] => {} Some(SymbolStorage::FloatReg(reg)) => { ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg); - Ok(()) } Some(SymbolStorage::Base { offset, size, .. }) => match layout { Layout::Builtin(Builtin::Int64) => { ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); - Ok(()) } Layout::Builtin(Builtin::Float64) => { ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); - Ok(()) } Layout::Struct(field_layouts) => { let (offset, size) = (*offset, *size); + // Nothing to do for empty struct if size > 0 { let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { @@ -858,23 +995,31 @@ impl< } else { None }; - CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg) - } else { - // Nothing to do for empty struct - Ok(()) + CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg)?; } } - x => Err(format!( - "returning symbol with layout, {:?}, is not yet implemented", - x - )), + x => { + return Err(format!( + "returning symbol with layout, {:?}, is not yet implemented", + x + )); + } }, - Some(x) => Err(format!( - "returning symbol storage, {:?}, is not yet implemented", - x - )), - None => Err(format!("Unknown return symbol: {}", sym)), + Some(x) => { + return Err(format!( + "returning symbol storage, {:?}, is not yet implemented", + x + )); + } + None => { + return Err(format!("Unknown return symbol: {}", sym)); + } } + let inst_loc = self.buf.len() as u64; + let offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678) as u64; + self.relocs + .push(Relocation::JmpToReturn { inst_loc, offset }); + Ok(()) } } @@ -1212,4 +1357,45 @@ impl< )), } } + + fn push_used_caller_saved_regs_to_stack(&mut self) -> Result<(), String> { + let old_general_used_regs = std::mem::replace( + &mut self.general_used_regs, + bumpalo::vec![in self.env.arena], + ); + for (reg, saved_sym) in old_general_used_regs.into_iter() { + if CC::general_caller_saved(®) { + self.general_free_regs.push(reg); + self.free_to_stack(&saved_sym)?; + } else { + self.general_used_regs.push((reg, saved_sym)); + } + } + let old_float_used_regs = + std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in self.env.arena]); + for (reg, saved_sym) in old_float_used_regs.into_iter() { + if CC::float_caller_saved(®) { + self.float_free_regs.push(reg); + self.free_to_stack(&saved_sym)?; + } else { + self.float_used_regs.push((reg, saved_sym)); + } + } + Ok(()) + } + + fn update_jmp_imm32_offset( + &mut self, + tmp: &mut Vec<'a, u8>, + jmp_location: u64, + base_offset: u64, + target_offset: u64, + ) { + tmp.clear(); + let jmp_offset = target_offset as i32 - base_offset as i32; + ASM::jmp_imm32(tmp, jmp_offset); + for (i, byte) in tmp.iter().enumerate() { + self.buf[jmp_location as usize + i] = *byte; + } + } } diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 70e9bf0f61..d190091cf7 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -9,10 +9,10 @@ use roc_module::ident::{ModuleName, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, Symbol}; use roc_mono::ir::{ - BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Proc, Stmt, + BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, + SelfRecursive, Stmt, }; use roc_mono::layout::{Builtin, Layout, LayoutIds}; -use target_lexicon::Triple; mod generic64; mod object_builder; @@ -46,6 +46,10 @@ pub enum Relocation { offset: u64, name: String, }, + JmpToReturn { + inst_loc: u64, + offset: u64, + }, } trait Backend<'a> @@ -53,12 +57,13 @@ where Self: Sized, { /// new creates a new backend that will output to the specific Object. - fn new(env: &'a Env, target: &Triple) -> Result; + fn new(env: &'a Env) -> Result; fn env(&self) -> &'a Env<'a>; /// reset resets any registers or other values that may be occupied at the end of a procedure. - fn reset(&mut self); + /// It also passes basic procedure information to the builder for setup of the next function. + fn reset(&mut self, name: String, is_self_recursive: SelfRecursive); /// finalize does any setup and cleanup that should happen around the procedure. /// finalize does setup because things like stack size and jump locations are not know until the function is written. @@ -79,7 +84,10 @@ where /// build_proc creates a procedure and outputs it to the wrapped object writer. fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> { - self.reset(); + let proc_name = LayoutIds::default() + .get(proc.name, &proc.ret_layout) + .to_symbol_string(proc.name, &self.env().interns); + self.reset(proc_name, proc.is_self_recursive); self.load_args(proc.args, &proc.ret_layout)?; for (layout, sym) in proc.args { self.set_layout_map(*sym, layout)?; @@ -128,6 +136,36 @@ where self.free_symbols(stmt)?; Ok(()) } + Stmt::Join { + id, + parameters, + body, + remainder, + } => { + for param in parameters.iter() { + self.set_layout_map(param.symbol, ¶m.layout)?; + } + self.build_join(id, parameters, body, remainder, ret_layout)?; + self.free_symbols(stmt)?; + Ok(()) + } + Stmt::Jump(id, args) => { + let mut arg_layouts: bumpalo::collections::Vec> = + bumpalo::vec![in self.env().arena]; + arg_layouts.reserve(args.len()); + let layout_map = self.layout_map(); + for arg in *args { + if let Some(layout) = layout_map.get(arg) { + // This is safe because every value in the map is always set with a valid layout and cannot be null. + arg_layouts.push(unsafe { *(*layout) }); + } else { + return Err(format!("the argument, {:?}, has no know layout", arg)); + } + } + self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout)?; + self.free_symbols(stmt)?; + Ok(()) + } x => Err(format!("the statement, {:?}, is not yet implemented", x)), } } @@ -141,6 +179,25 @@ where ret_layout: &Layout<'a>, ) -> Result<(), String>; + // build_join generates a instructions for a join statement. + fn build_join( + &mut self, + id: &JoinPointId, + parameters: &'a [Param<'a>], + body: &'a Stmt<'a>, + remainder: &'a Stmt<'a>, + ret_layout: &Layout<'a>, + ) -> Result<(), String>; + + // build_jump generates a instructions for a jump statement. + fn build_jump( + &mut self, + id: &JoinPointId, + args: &'a [Symbol], + arg_layouts: &[Layout<'a>], + ret_layout: &Layout<'a>, + ) -> Result<(), String>; + /// build_expr builds the expressions for the specified symbol. /// The builder must keep track of the symbol because it may be referred to later. fn build_expr( @@ -507,7 +564,7 @@ where /// load_literal sets a symbol to be equal to a literal. fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>; - /// return_symbol moves a symbol to the correct return location for the backend. + /// 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>) -> Result<(), String>; /// free_symbols will free all symbols for the given statement. diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index 88ab92c245..9e5ae6f76e 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -34,7 +34,7 @@ pub fn build_module<'a>( x86_64::X86_64FloatReg, x86_64::X86_64Assembler, x86_64::X86_64SystemV, - > = Backend::new(env, target)?; + > = Backend::new(env)?; build_object( env, procedures, @@ -52,7 +52,7 @@ pub fn build_module<'a>( x86_64::X86_64FloatReg, x86_64::X86_64Assembler, x86_64::X86_64SystemV, - > = Backend::new(env, target)?; + > = Backend::new(env)?; build_object( env, procedures, @@ -74,7 +74,7 @@ pub fn build_module<'a>( aarch64::AArch64FloatReg, aarch64::AArch64Assembler, aarch64::AArch64Call, - > = Backend::new(env, target)?; + > = Backend::new(env)?; build_object( env, procedures, @@ -304,6 +304,7 @@ fn build_object<'a, B: Backend<'a>>( return Err(format!("failed to find fn symbol for {:?}", name)); } } + Relocation::JmpToReturn { .. } => unreachable!(), }; relocations.push((section_id, elfreloc)); } diff --git a/compiler/gen_dev/tests/dev_num.rs b/compiler/gen_dev/tests/dev_num.rs index d1c0ae0d75..e03b3787b8 100644 --- a/compiler/gen_dev/tests/dev_num.rs +++ b/compiler/gen_dev/tests/dev_num.rs @@ -281,6 +281,24 @@ mod dev_num { ); } + #[test] + fn gen_fast_fib_fn() { + assert_evals_to!( + indoc!( + r#" + fib = \n, a, b -> + if n == 0 then + a + else + fib (n - 1) b (a + b) + fib 10 0 1 + "# + ), + 55, + i64 + ); + } + #[test] fn f64_abs() { assert_evals_to!("Num.abs -4.7", 4.7, f64); @@ -580,18 +598,18 @@ mod dev_num { // assert_evals_to!("0.0 >= 0.0", true, bool); // } - // #[test] - // fn gen_order_of_arithmetic_ops() { - // assert_evals_to!( - // indoc!( - // r#" - // 1 + 3 * 7 - 2 - // "# - // ), - // 20, - // i64 - // ); - // } + #[test] + fn gen_order_of_arithmetic_ops() { + assert_evals_to!( + indoc!( + r#" + 1 + 3 * 7 - 2 + "# + ), + 20, + i64 + ); + } // #[test] // fn gen_order_of_arithmetic_ops_complex_float() { @@ -642,23 +660,23 @@ mod dev_num { // ); // } - // #[test] - // fn tail_call_elimination() { - // assert_evals_to!( - // indoc!( - // r#" - // sum = \n, accum -> - // when n is - // 0 -> accum - // _ -> sum (n - 1) (n + accum) + #[test] + fn tail_call_elimination() { + assert_evals_to!( + indoc!( + r#" + sum = \n, accum -> + when n is + 0 -> accum + _ -> sum (n - 1) (n + accum) - // sum 1_000_000 0 - // "# - // ), - // 500000500000, - // i64 - // ); - // } + sum 1_000_000 0 + "# + ), + 500000500000, + i64 + ); + } // #[test] // fn int_negate() { diff --git a/examples/fib/.gitignore b/examples/fib/.gitignore index 76d4bb83f8..8cd68df839 100644 --- a/examples/fib/.gitignore +++ b/examples/fib/.gitignore @@ -1 +1,2 @@ add +fib \ No newline at end of file diff --git a/examples/fib/Fib.roc b/examples/fib/Fib.roc index e36734edb8..e5098a023b 100644 --- a/examples/fib/Fib.roc +++ b/examples/fib/Fib.roc @@ -3,25 +3,13 @@ app "fib" imports [] provides [ main ] to base -main = \n -> fib n +main = \n -> fib n 0 1 -fib = \n -> - if n == 0 then - 0 - else if n == 1 then - 1 - else - (fib (n - 1)) + (fib (n - 2)) - # the clever implementation requires join points -# fib = \n, a, b -> -# if n == 0 then -# a -# -# else -# fib (n - 1) b (a + b) -# -# fib n 0 1 - - +fib = \n, a, b -> + if n == 0 then + a + + else + fib (n - 1) b (a + b) From 91057ed8b509f64996d851a4b372d697c8d0dad2 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 20 Sep 2021 23:28:57 -0700 Subject: [PATCH 041/136] Expand support numeric types --- compiler/gen_dev/src/generic64/x86_64.rs | 84 ++++++++++++++++++++---- compiler/gen_dev/tests/dev_num.rs | 64 +++++++++--------- 2 files changed, 102 insertions(+), 46 deletions(-) diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index d052c44e43..2fa225ef53 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -191,7 +191,14 @@ impl CallConv for X86_64SystemV { } for (layout, sym) in args.iter() { match layout { - Layout::Builtin(Builtin::Int64) => { + Layout::Builtin( + Builtin::Int1 + | Builtin::Int8 + | Builtin::Int16 + | Builtin::Int32 + | Builtin::Int64 + | Builtin::Usize, + ) => { if general_i < Self::GENERAL_PARAM_REGS.len() { symbol_map.insert( *sym, @@ -210,7 +217,7 @@ impl CallConv for X86_64SystemV { ); } } - Layout::Builtin(Builtin::Float64) => { + Layout::Builtin(Builtin::Float32 | Builtin::Float64) => { if float_i < Self::FLOAT_PARAM_REGS.len() { symbol_map.insert( *sym, @@ -229,6 +236,7 @@ impl CallConv for X86_64SystemV { ); } } + Layout::Struct(&[]) => {} x => { return Err(format!( "Loading args with layout {:?} not yet implementd", @@ -254,8 +262,16 @@ impl CallConv 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(Builtin::Int64) => {} - Layout::Builtin(Builtin::Float64) => {} + Layout::Builtin( + Builtin::Int1 + | Builtin::Int8 + | Builtin::Int16 + | Builtin::Int32 + | Builtin::Int64 + | Builtin::Usize + | Builtin::Float32 + | Builtin::Float64, + ) => {} x => { return Err(format!( "receiving return type, {:?}, is not yet implemented", @@ -265,7 +281,14 @@ impl CallConv for X86_64SystemV { } for (i, layout) in arg_layouts.iter().enumerate() { match layout { - Layout::Builtin(Builtin::Int64) => { + Layout::Builtin( + Builtin::Int1 + | Builtin::Int8 + | Builtin::Int16 + | Builtin::Int32 + | Builtin::Int64 + | Builtin::Usize, + ) => { if general_i < Self::GENERAL_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::GENERAL_PARAM_REGS[general_i]; @@ -319,7 +342,7 @@ impl CallConv for X86_64SystemV { stack_offset += 8; } } - Layout::Builtin(Builtin::Float64) => { + Layout::Builtin(Builtin::Float32 | Builtin::Float64) => { if float_i < Self::FLOAT_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::FLOAT_PARAM_REGS[float_i]; @@ -371,6 +394,7 @@ impl CallConv for X86_64SystemV { stack_offset += 8; } } + Layout::Struct(&[]) => {} x => { return Err(format!( "calling with arg type, {:?}, is not yet implemented", @@ -529,13 +553,21 @@ impl CallConv for X86_64WindowsFastcall { for (layout, sym) in args.iter() { if i < Self::GENERAL_PARAM_REGS.len() { match layout { - Layout::Builtin(Builtin::Int64) => { + Layout::Builtin( + Builtin::Int1 + | Builtin::Int8 + | Builtin::Int16 + | Builtin::Int32 + | Builtin::Int64 + | Builtin::Usize, + ) => { symbol_map .insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i])); } - Layout::Builtin(Builtin::Float64) => { + Layout::Builtin(Builtin::Float32 | Builtin::Float64) => { symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i])); } + Layout::Struct(&[]) => {} x => { return Err(format!( "Loading args with layout {:?} not yet implementd", @@ -546,8 +578,16 @@ impl CallConv for X86_64WindowsFastcall { i += 1; } else { base_offset += match layout { - Layout::Builtin(Builtin::Int64) => 8, - Layout::Builtin(Builtin::Float64) => 8, + Layout::Builtin( + Builtin::Int1 + | Builtin::Int8 + | Builtin::Int16 + | Builtin::Int32 + | Builtin::Int64 + | Builtin::Usize + | Builtin::Float32 + | Builtin::Float64, + ) => 8, x => { return Err(format!( "Loading args with layout {:?} not yet implemented", @@ -581,8 +621,16 @@ impl CallConv for X86_64WindowsFastcall { // 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(Builtin::Int64) => {} - Layout::Builtin(Builtin::Float64) => {} + Layout::Builtin( + Builtin::Int1 + | Builtin::Int8 + | Builtin::Int16 + | Builtin::Int32 + | Builtin::Int64 + | Builtin::Usize + | Builtin::Float32 + | Builtin::Float64, + ) => {} x => { return Err(format!( "receiving return type, {:?}, is not yet implemented", @@ -592,7 +640,14 @@ impl CallConv for X86_64WindowsFastcall { } for (i, layout) in arg_layouts.iter().enumerate() { match layout { - Layout::Builtin(Builtin::Int64) => { + Layout::Builtin( + Builtin::Int1 + | Builtin::Int8 + | Builtin::Int16 + | Builtin::Int32 + | Builtin::Int64 + | Builtin::Usize, + ) => { if i < Self::GENERAL_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::GENERAL_PARAM_REGS[reg_i]; @@ -646,7 +701,7 @@ impl CallConv for X86_64WindowsFastcall { stack_offset += 8; } } - Layout::Builtin(Builtin::Float64) => { + Layout::Builtin(Builtin::Float32 | Builtin::Float64) => { if i < Self::FLOAT_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::FLOAT_PARAM_REGS[reg_i]; @@ -698,6 +753,7 @@ impl CallConv for X86_64WindowsFastcall { stack_offset += 8; } } + Layout::Struct(&[]) => {} x => { return Err(format!( "calling with arg type, {:?}, is not yet implemented", diff --git a/compiler/gen_dev/tests/dev_num.rs b/compiler/gen_dev/tests/dev_num.rs index e03b3787b8..c223acbcdf 100644 --- a/compiler/gen_dev/tests/dev_num.rs +++ b/compiler/gen_dev/tests/dev_num.rs @@ -624,41 +624,41 @@ mod dev_num { // ); // } - // #[test] - // fn if_guard_bind_variable_false() { - // assert_evals_to!( - // indoc!( - // r#" - // wrapper = \{} -> - // when 10 is - // x if x == 5 -> 0 - // _ -> 42 + #[test] + fn if_guard_bind_variable_false() { + assert_evals_to!( + indoc!( + r#" + wrapper = \{} -> + when 10 is + x if x == 5 -> 0 + _ -> 42 - // wrapper {} - // "# - // ), - // 42, - // i64 - // ); - // } + wrapper {} + "# + ), + 42, + i64 + ); + } - // #[test] - // fn if_guard_bind_variable_true() { - // assert_evals_to!( - // indoc!( - // r#" - // wrapper = \{} -> - // when 10 is - // x if x == 10 -> 42 - // _ -> 0 + #[test] + fn if_guard_bind_variable_true() { + assert_evals_to!( + indoc!( + r#" + wrapper = \{} -> + when 10 is + x if x == 10 -> 42 + _ -> 0 - // wrapper {} - // "# - // ), - // 42, - // i64 - // ); - // } + wrapper {} + "# + ), + 42, + i64 + ); + } #[test] fn tail_call_elimination() { From 54e2792b12b0b11767181f447e75adae2820d4fd Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Mon, 20 Sep 2021 23:41:20 -0700 Subject: [PATCH 042/136] Fix typo --- compiler/gen_dev/src/generic64/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 518a41a728..82ab3cfe1f 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -556,7 +556,7 @@ impl< let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); // This section can essentially be seen as a sub function within the main function. - // Thus we build using a new backend with some minor extra syncronization. + // Thus we build using a new backend with some minor extra synchronization. let mut sub_backend = Self::new(self.env)?; sub_backend.reset( self.proc_name.as_ref().unwrap().clone(), From 1fb0c8043f5770a8a0c6d9d065cfab73a7bc5a2f Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 21 Sep 2021 00:14:13 -0700 Subject: [PATCH 043/136] Optimize away unnecessary jump right before return --- compiler/gen_dev/src/generic64/mod.rs | 51 +++++++++++++++++++++++---- compiler/gen_dev/src/lib.rs | 1 + 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 82ab3cfe1f..279a5f28f8 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -340,20 +340,48 @@ impl< let setup_offset = out.len(); // Deal with jumps to the return address. - let ret_offset = self.buf.len(); let old_relocs = std::mem::replace(&mut self.relocs, bumpalo::vec![in self.env.arena]); + + // Check if their is an unnessary jump to return right at the end of the function. + let mut end_jmp_size = 0; + for reloc in old_relocs + .iter() + .filter(|reloc| matches!(reloc, Relocation::JmpToReturn { .. })) + { + if let Relocation::JmpToReturn { + inst_loc, + inst_size, + .. + } = reloc + { + if *inst_loc as usize + *inst_size as usize == self.buf.len() { + end_jmp_size = *inst_size as usize; + break; + } + } + } + + // Update jumps to returns. + let ret_offset = self.buf.len() - end_jmp_size; let mut tmp = bumpalo::vec![in self.env.arena]; for reloc in old_relocs .iter() .filter(|reloc| matches!(reloc, Relocation::JmpToReturn { .. })) { - if let Relocation::JmpToReturn { inst_loc, offset } = reloc { - self.update_jmp_imm32_offset(&mut tmp, *inst_loc, *offset, ret_offset as u64); + if let Relocation::JmpToReturn { + inst_loc, + inst_size, + offset, + } = reloc + { + if *inst_loc as usize + *inst_size as usize != self.buf.len() { + self.update_jmp_imm32_offset(&mut tmp, *inst_loc, *offset, ret_offset as u64); + } } } // Add function body. - out.extend(&self.buf); + out.extend(&self.buf[..self.buf.len() - end_jmp_size]); // Cleanup stack. CC::cleanup_stack( @@ -606,8 +634,13 @@ impl< offset: offset + sub_func_offset, name, }, - Relocation::JmpToReturn { inst_loc, offset } => Relocation::JmpToReturn { + Relocation::JmpToReturn { + inst_loc, + inst_size, + offset, + } => Relocation::JmpToReturn { inst_loc: inst_loc + sub_func_offset, + inst_size, offset: offset + sub_func_offset, }, })); @@ -1017,8 +1050,11 @@ impl< } let inst_loc = self.buf.len() as u64; let offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678) as u64; - self.relocs - .push(Relocation::JmpToReturn { inst_loc, offset }); + self.relocs.push(Relocation::JmpToReturn { + inst_loc, + inst_size: self.buf.len() as u64 - inst_loc, + offset, + }); Ok(()) } } @@ -1384,6 +1420,7 @@ impl< Ok(()) } + // Updates a jump instruction to a new offset and returns the number of bytes written. fn update_jmp_imm32_offset( &mut self, tmp: &mut Vec<'a, u8>, diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index d190091cf7..2c618b600d 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -48,6 +48,7 @@ pub enum Relocation { }, JmpToReturn { inst_loc: u64, + inst_size: u64, offset: u64, }, } From af34541b673c0dcfd3d378b0ef3bf5879efcc851 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Tue, 21 Sep 2021 11:13:56 +0200 Subject: [PATCH 044/136] upgrade to wgpu-10, fix save on s without Ctrl --- Cargo.lock | 379 ++++++---------------- editor/Cargo.toml | 4 +- editor/src/editor/ed_error.rs | 8 - editor/src/editor/main.rs | 34 +- editor/src/editor/mvc/ed_update.rs | 48 +-- editor/src/graphics/lowlevel/buffer.rs | 6 +- editor/src/graphics/lowlevel/ortho.rs | 8 +- editor/src/graphics/lowlevel/pipelines.rs | 9 +- editor/src/graphics/lowlevel/vertex.rs | 2 +- 9 files changed, 156 insertions(+), 342 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a03a50a6f..580353bc1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,10 +131,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] -name = "ash" -version = "0.32.1" +name = "arrayvec" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06063a002a77d2734631db74e8f4ce7148b77fe522e6bca46f2ae7774fd48112" +checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" + +[[package]] +name = "ash" +version = "0.33.3+1.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219" dependencies = [ "libloading 0.7.0", ] @@ -332,9 +338,6 @@ name = "cc" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -1074,15 +1077,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" -dependencies = [ - "serde", -] - [[package]] name = "dtoa" version = "0.4.8" @@ -1154,16 +1148,6 @@ dependencies = [ "serde", ] -[[package]] -name = "external-memory" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4dfe8d292b014422776a8c516862d2bff8a81b223a4461dfdc45f3862dc9d39" -dependencies = [ - "bitflags", - "drm-fourcc", -] - [[package]] name = "fake-simd" version = "0.1.2" @@ -1182,6 +1166,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "fixedbitset" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" + [[package]] name = "flate2" version = "1.0.21" @@ -1397,168 +1387,6 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] -[[package]] -name = "gfx-auxil" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1694991b11d642680e82075a75c7c2bd75556b805efa7660b705689f05b1ab1c" -dependencies = [ - "fxhash", - "gfx-hal", - "spirv_cross", -] - -[[package]] -name = "gfx-backend-dx11" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9e453baf3aaef2b0c354ce0b3d63d76402e406a59b64b7182d123cfa6635ae" -dependencies = [ - "arrayvec", - "bitflags", - "gfx-auxil", - "gfx-hal", - "gfx-renderdoc", - "libloading 0.7.0", - "log", - "parking_lot", - "range-alloc", - "raw-window-handle", - "smallvec", - "spirv_cross", - "thunderdome", - "winapi 0.3.9", - "wio", -] - -[[package]] -name = "gfx-backend-dx12" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21506399f64a3c4d389182a89a30073856ae33eb712315456b4fd8f39ee7682a" -dependencies = [ - "arrayvec", - "bit-set", - "bitflags", - "d3d12", - "gfx-auxil", - "gfx-hal", - "gfx-renderdoc", - "log", - "parking_lot", - "range-alloc", - "raw-window-handle", - "smallvec", - "spirv_cross", - "thunderdome", - "winapi 0.3.9", -] - -[[package]] -name = "gfx-backend-empty" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c8f813c47791918aa00dc9c9ddf961d23fa8c2a5d869e6cb8ea84f944820f4" -dependencies = [ - "gfx-hal", - "log", - "raw-window-handle", -] - -[[package]] -name = "gfx-backend-gl" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bae057fc3a0ab23ecf97ae51d4017d27d5ddf0aab16ee6dcb58981af88c3152" -dependencies = [ - "arrayvec", - "bitflags", - "fxhash", - "gfx-hal", - "glow", - "js-sys", - "khronos-egl", - "libloading 0.7.0", - "log", - "naga", - "parking_lot", - "raw-window-handle", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gfx-backend-metal" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de85808e2a98994c6af925253f8a9593bc57180ef1ea137deab6d35cc949517" -dependencies = [ - "arrayvec", - "bitflags", - "block", - "cocoa-foundation", - "copyless", - "core-graphics-types", - "foreign-types", - "fxhash", - "gfx-hal", - "log", - "metal", - "naga", - "objc", - "parking_lot", - "profiling", - "range-alloc", - "raw-window-handle", - "storage-map", -] - -[[package]] -name = "gfx-backend-vulkan" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9861ec855acbbc65c0e4f966d761224886e811dc2c6d413a4776e9293d0e5c0" -dependencies = [ - "arrayvec", - "ash", - "byteorder", - "core-graphics-types", - "gfx-hal", - "gfx-renderdoc", - "inplace_it", - "log", - "naga", - "objc", - "parking_lot", - "raw-window-handle", - "smallvec", - "winapi 0.3.9", -] - -[[package]] -name = "gfx-hal" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbb575ea793dd0507b3082f4f2cde62dc9f3cebd98f5cd49ba2a4da97a976fd" -dependencies = [ - "bitflags", - "external-memory", - "naga", - "raw-window-handle", - "thiserror", -] - -[[package]] -name = "gfx-renderdoc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8027995e247e2426d3a00d13f5191dd56c314bff02dc4b54cbf727f1ba9c40a" -dependencies = [ - "libloading 0.7.0", - "log", - "renderdoc-sys", -] - [[package]] name = "ghost" version = "0.1.2" @@ -1589,9 +1417,9 @@ checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" [[package]] name = "glow" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b80b98efaa8a34fce11d60dd2ce2760d5d83c373cbcc73bb87c2a3a84a54108" +checksum = "4f04649123493bc2483cbef4daddb45d40bbdae5adb221a63a23efdb0cc99520" dependencies = [ "js-sys", "slotmap", @@ -1640,9 +1468,9 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.4.7" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc1b6ca374e81862526786d9cb42357ce03706ed1b8761730caafd02ab91f3a" +checksum = "ab8524eac5fc9d05625c891adf78fcf64dc0ee9f8d0882874b9f220f42b442bf" dependencies = [ "bitflags", "gpu-alloc-types", @@ -1659,9 +1487,9 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a70f1e87a3840ed6a3e99e02c2b861e4dbdf26f0d07e38f42ea5aff46cfce2" +checksum = "d7a237f0419ab10d17006d55c62ac4f689a6bf52c75d3f38b8361d249e8d4b0b" dependencies = [ "bitflags", "gpu-descriptor-types", @@ -1954,15 +1782,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.54" @@ -2289,9 +2108,9 @@ dependencies = [ [[package]] name = "naga" -version = "0.5.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef670817eef03d356d5a509ea275e7dd3a78ea9e24261ea3cb2dfed1abb08f64" +checksum = "8c5859e55c51da10b98e7a73068e0a0c5da7bbcae4fc38f86043d0c6d1b917cf" dependencies = [ "bit-set", "bitflags", @@ -2299,9 +2118,8 @@ dependencies = [ "fxhash", "log", "num-traits", - "petgraph", - "rose_tree", - "spirv_headers", + "petgraph 0.6.0", + "spirv", "thiserror", ] @@ -2648,7 +2466,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "petgraph", + "petgraph 0.5.1", "redox_syscall", "smallvec", "thread-id", @@ -2710,7 +2528,17 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ - "fixedbitset", + "fixedbitset 0.2.0", + "indexmap", +] + +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset 0.4.0", "indexmap", ] @@ -3946,15 +3774,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "rose_tree" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284de9dae38774e2813aaabd7e947b4a6fe9b8c58c2309f754a487cdd50de1c2" -dependencies = [ - "petgraph", -] - [[package]] name = "rustc-demangle" version = "0.1.21" @@ -4231,9 +4050,12 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "slotmap" -version = "0.4.3" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf34684c5767b87de9119790e92e9a1d60056be2ceeaf16a8e6ef13082aeab1" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] [[package]] name = "smallvec" @@ -4310,21 +4132,10 @@ dependencies = [ ] [[package]] -name = "spirv_cross" -version = "0.23.1" +name = "spirv" +version = "0.2.0+1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60647fadbf83c4a72f0d7ea67a7ca3a81835cf442b8deae5c134c3e0055b2e14" -dependencies = [ - "cc", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "spirv_headers" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f5b132530b1ac069df335577e3581765995cba5a13995cdbbdbc8fb057c532c" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" dependencies = [ "bitflags", "num-traits", @@ -4342,15 +4153,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "storage-map" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418bb14643aa55a7841d5303f72cf512cfb323b8cc221d51580500a1ca75206c" -dependencies = [ - "lock_api", -] - [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -4569,12 +4371,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "thunderdome" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b4947742c93ece24a0032141d9caa3d853752e694a57e35029dd2bd08673e0" - [[package]] name = "time" version = "0.1.43" @@ -4794,7 +4590,7 @@ dependencies = [ name = "ven_pretty" version = "0.9.1-alpha.0" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "criterion", "difference", "tempfile", @@ -4820,7 +4616,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "utf8parse", "vte_generate_state_changes", ] @@ -5237,9 +5033,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" dependencies = [ "js-sys", "wasm-bindgen", @@ -5247,14 +5043,13 @@ dependencies = [ [[package]] name = "wgpu" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd247f8b26fd3d42ef2f320d378025cd6e84d782ef749fab45cc3b981fbe3275" +checksum = "3d92a4fe73b1e7d7ef99938dacd49258cbf1ad87cdb5bf6efa20c27447442b45" dependencies = [ - "arrayvec", + "arrayvec 0.7.1", "js-sys", "log", - "naga", "parking_lot", "raw-window-handle", "smallvec", @@ -5262,29 +5057,21 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "wgpu-core", + "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-core" -version = "0.9.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958a8a5e418492723ab4e7933bf6dbdf06f5dc87274ba2ae0e4f9c891aac579c" +checksum = "5f1b4d918c970526cbc83b72ccb72dbefd38aec45f07b2310de4ffcd7f4bd8c5" dependencies = [ - "arrayvec", + "arrayvec 0.7.1", "bitflags", "cfg_aliases", "copyless", "fxhash", - "gfx-backend-dx11", - "gfx-backend-dx12", - "gfx-backend-empty", - "gfx-backend-gl", - "gfx-backend-metal", - "gfx-backend-vulkan", - "gfx-hal", - "gpu-alloc", - "gpu-descriptor", "log", "naga", "parking_lot", @@ -5292,23 +5079,58 @@ dependencies = [ "raw-window-handle", "smallvec", "thiserror", + "wgpu-hal", "wgpu-types", ] [[package]] -name = "wgpu-types" -version = "0.9.0" +name = "wgpu-hal" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5c9678cd533558e28b416d66947b099742df1939307478db54f867137f1b60" +checksum = "27cd894b17bff1958ee93da1cc991fd64bf99667746d4bd2a7403855f4d37fe2" +dependencies = [ + "arrayvec 0.7.1", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "khronos-egl", + "libloading 0.7.0", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wgpu-types", + "winapi 0.3.9", +] + +[[package]] +name = "wgpu-types" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25feb2fbf24ab3219a9f10890ceb8e1ef02b13314ed89d64a9ae99dcad883e18" dependencies = [ "bitflags", ] [[package]] name = "wgpu_glyph" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fee8c96eda18195a7ad9989737183e0a357f14b15e98838c76abbcf56a5f970" +checksum = "cbf11aebbcf20806535bee127367bcb393c83d77c60c4f7917184d839716cf41" dependencies = [ "bytemuck", "glyph_brush", @@ -5401,15 +5223,6 @@ dependencies = [ "x11-dl", ] -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index f9207b2e38..603498ce87 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -29,13 +29,13 @@ arraystring = "0.3.0" libc = "0.2" page_size = "0.4" winit = "0.24" -wgpu = "0.9" +wgpu = "0.10" glyph_brush = "0.7" log = "0.4" zerocopy = "0.3" env_logger = "0.8" futures = "0.3" -wgpu_glyph = "0.13" +wgpu_glyph = "0.14" cgmath = "0.18.0" snafu = { version = "0.6", features = ["backtraces"] } colored = "2" diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index b838fdef42..f0c280eeab 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,5 +1,4 @@ use crate::lang::parse::ASTNodeId; -use crate::ui::ui_error::UIResult; use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; use colored::*; use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; @@ -292,10 +291,3 @@ impl From for EdError { dummy_res.context(UIErrorBacktrace { msg }).unwrap_err() } } - -pub fn from_ui_res(ui_res: UIResult) -> EdResult { - match ui_res { - Ok(t) => Ok(t), - Err(ui_err) => Err(EdError::from(ui_err)), - } -} diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 9ab70d32ba..ba31393054 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -70,7 +70,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box { size = new_size; - swap_chain = gpu_device.create_swap_chain( - &surface, - &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::RENDER_ATTACHMENT, + surface.configure( + &gpu_device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: render_format, width: size.width, height: size.height, @@ -274,11 +274,15 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box EdModel<'a> { virtual_keycode: VirtualKeyCode, ) -> EdResult<()> { match virtual_keycode { - Left => from_ui_res(self.move_caret_left(modifiers)), + Left => self.move_caret_left(modifiers)?, Up => { if modifiers.cmd_or_ctrl() && modifiers.shift { - self.select_expr() + self.select_expr()? } else { - from_ui_res(self.move_caret_up(modifiers)) + self.move_caret_up(modifiers)? } } - Right => from_ui_res(self.move_caret_right(modifiers)), - Down => from_ui_res(self.move_caret_down(modifiers)), + Right => self.move_caret_right(modifiers)?, + Down => self.move_caret_down(modifiers)?, + + A => { + if modifiers.cmd_or_ctrl() { + self.select_all()? + } + } + S => { + if modifiers.cmd_or_ctrl() { + self.save_file()? + } + } + R => { + if modifiers.cmd_or_ctrl() { + self.run_file()? + } + } + + Home => self.move_caret_home(modifiers)?, + End => self.move_caret_end(modifiers)?, - A => if_modifiers(modifiers, self.select_all()), - S => if_modifiers(modifiers, self.save_file()), - R => if_modifiers(modifiers, self.run_file()), - Home => from_ui_res(self.move_caret_home(modifiers)), - End => from_ui_res(self.move_caret_end(modifiers)), F11 => { self.show_debug_view = !self.show_debug_view; self.dirty = true; - Ok(()) } - _ => Ok(()), + _ => (), } + + Ok(()) } // Replaces selected expression with blank. @@ -787,14 +801,6 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> }) } -fn if_modifiers(modifiers: &Modifiers, shortcut_result: UIResult<()>) -> EdResult<()> { - if modifiers.cmd_or_ctrl() { - from_ui_res(shortcut_result) - } else { - Ok(()) - } -} - // current(=caret is here) MarkupNode corresponds to a Def2 in the AST pub fn handle_new_char_def( received_char: &char, diff --git a/editor/src/graphics/lowlevel/buffer.rs b/editor/src/graphics/lowlevel/buffer.rs index f9cf9fc9a4..723e5957ef 100644 --- a/editor/src/graphics/lowlevel/buffer.rs +++ b/editor/src/graphics/lowlevel/buffer.rs @@ -105,7 +105,7 @@ pub fn create_rect_buffers( let vertex_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { label: None, size: Vertex::SIZE * 4 * nr_of_rects, - usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); @@ -114,7 +114,7 @@ pub fn create_rect_buffers( let index_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { label: None, size: u32_size * 6 * nr_of_rects, - usage: wgpu::BufferUsage::INDEX | wgpu::BufferUsage::COPY_DST, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); @@ -148,7 +148,7 @@ impl StagingBuffer { StagingBuffer { buffer: device.create_buffer_init(&BufferInitDescriptor { contents: bytemuck::cast_slice(data), - usage: wgpu::BufferUsage::COPY_SRC, + usage: wgpu::BufferUsages::COPY_SRC, label: Some("Staging Buffer"), }), size: size_of_slice(data) as wgpu::BufferAddress, diff --git a/editor/src/graphics/lowlevel/ortho.rs b/editor/src/graphics/lowlevel/ortho.rs index 0b5871bb93..2f4577871a 100644 --- a/editor/src/graphics/lowlevel/ortho.rs +++ b/editor/src/graphics/lowlevel/ortho.rs @@ -2,7 +2,7 @@ use cgmath::{Matrix4, Ortho}; use wgpu::util::DeviceExt; use wgpu::{ BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer, - ShaderStage, + ShaderStages, }; // orthographic projection is used to transform pixel coords to the coordinate system used by wgpu @@ -45,7 +45,7 @@ pub fn update_ortho_buffer( let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Ortho uniform buffer"), contents: bytemuck::cast_slice(&[new_uniforms]), - usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_SRC, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC, }); // get a command encoder for the current frame @@ -83,14 +83,14 @@ pub fn init_ortho( let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Ortho uniform buffer"), contents: bytemuck::cast_slice(&[uniforms]), - usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); // bind groups consist of extra resources that are provided to the shaders let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, - visibility: ShaderStage::VERTEX, + visibility: ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, diff --git a/editor/src/graphics/lowlevel/pipelines.rs b/editor/src/graphics/lowlevel/pipelines.rs index 810766ccfd..5bc768a33f 100644 --- a/editor/src/graphics/lowlevel/pipelines.rs +++ b/editor/src/graphics/lowlevel/pipelines.rs @@ -9,9 +9,9 @@ pub struct RectResources { pub fn make_rect_pipeline( gpu_device: &wgpu::Device, - swap_chain_descr: &wgpu::SwapChainDescriptor, + surface_config: &wgpu::SurfaceConfiguration, ) -> RectResources { - let ortho = init_ortho(swap_chain_descr.width, swap_chain_descr.height, gpu_device); + let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device); let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { bind_group_layouts: &[&ortho.bind_group_layout], @@ -21,11 +21,10 @@ pub fn make_rect_pipeline( let pipeline = create_render_pipeline( gpu_device, &pipeline_layout, - swap_chain_descr.format, + surface_config.format, &wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/shader.wgsl"))), - flags: wgpu::ShaderFlags::all(), }, ); @@ -61,7 +60,7 @@ pub fn create_render_pipeline( }, alpha: wgpu::BlendComponent::REPLACE, }), - write_mask: wgpu::ColorWrite::ALL, + write_mask: wgpu::ColorWrites::ALL, }], }), primitive: wgpu::PrimitiveState::default(), diff --git a/editor/src/graphics/lowlevel/vertex.rs b/editor/src/graphics/lowlevel/vertex.rs index 64cf9fac1f..f17a840bb2 100644 --- a/editor/src/graphics/lowlevel/vertex.rs +++ b/editor/src/graphics/lowlevel/vertex.rs @@ -18,7 +18,7 @@ impl Vertex { pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { array_stride: Self::SIZE, - step_mode: wgpu::InputStepMode::Vertex, + step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ // position wgpu::VertexAttribute { From 3e22aa60f28fe53bb12f484d4f753450b2521fa9 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Tue, 21 Sep 2021 13:23:16 +0200 Subject: [PATCH 045/136] Added nix warning to BUILDING_FROM_SOURCE --- BUILDING_FROM_SOURCE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index e516224409..07b503cd34 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -74,6 +74,8 @@ There are also alternative installation options at http://releases.llvm.org/down ## Using Nix +:exclamation: **Our Nix setup is currently broken, you'll have to install manually for now** :exclamation: + ### Install Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command. From b2ade4c79f04eb088df12a9c571d67a6473348e0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 21 Sep 2021 23:08:35 +0200 Subject: [PATCH 046/136] give web test a different name --- cli/tests/cli_run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 0e915fe2ac..aa7020700f 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -233,7 +233,7 @@ mod cli_run { expected_ending:"Hello, World!\n", use_valgrind: true, }, - hello_world:"hello-web" => Example { + hello_web:"hello-web" => Example { filename: "Hello.roc", executable_filename: "hello-web", stdin: &[], From 006fe3beff69f99342e9b73a48b98f25e949c5df Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 21 Sep 2021 15:09:10 -0700 Subject: [PATCH 047/136] Remove borrow constraint, it is used for refcounting before the backend --- compiler/gen_dev/src/generic64/mod.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 279a5f28f8..a7b8d7c30f 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -573,12 +573,6 @@ impl< remainder: &'a Stmt<'a>, ret_layout: &Layout<'a>, ) -> Result<(), String> { - for param in parameters { - if param.borrow { - return Err("Join: borrowed parameters not yet supported".to_string()); - } - } - // Create jump to remaining. let jmp_location = self.buf.len(); let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678); From 1a6ca4be59f0aaa20f075134262e3c9f2e75b1c0 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 21 Sep 2021 15:27:21 -0700 Subject: [PATCH 048/136] Convert layout map to store Layouts in order to avoid unsafe mangling --- compiler/gen_dev/src/generic64/mod.rs | 6 +++--- compiler/gen_dev/src/lib.rs | 14 +++++--------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index a7b8d7c30f..ac0417f975 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -214,7 +214,7 @@ pub struct Backend64Bit< is_self_recursive: Option, last_seen_map: MutMap>, - layout_map: MutMap>, + layout_map: MutMap>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, symbol_storage_map: MutMap>, @@ -313,7 +313,7 @@ impl< &mut self.last_seen_map } - fn layout_map(&mut self) -> &mut MutMap> { + fn layout_map(&mut self) -> &mut MutMap> { &mut self.layout_map } @@ -659,7 +659,7 @@ impl< arg_layouts: &[Layout<'a>], ret_layout: &Layout<'a>, ) -> Result<(), String> { - // Treat this like a function call, but with a jump install of a call instruction at the end. + // Treat this like a function call, but with a jump instead of a call instruction at the end. self.push_used_caller_saved_regs_to_stack()?; diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 2c618b600d..010fa066d3 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -157,8 +157,7 @@ where let layout_map = self.layout_map(); for arg in *args { if let Some(layout) = layout_map.get(arg) { - // This is safe because every value in the map is always set with a valid layout and cannot be null. - arg_layouts.push(unsafe { *(*layout) }); + arg_layouts.push(*layout); } else { return Err(format!("the argument, {:?}, has no know layout", arg)); } @@ -321,8 +320,7 @@ where let layout_map = self.layout_map(); for arg in *arguments { if let Some(layout) = layout_map.get(arg) { - // This is safe because every value in the map is always set with a valid layout and cannot be null. - arg_layouts.push(unsafe { *(*layout) }); + arg_layouts.push(*layout); } else { return Err(format!("the argument, {:?}, has no know layout", arg)); } @@ -600,12 +598,10 @@ where /// set_layout_map sets the layout for a specific symbol. fn set_layout_map(&mut self, sym: Symbol, layout: &Layout<'a>) -> Result<(), String> { - if let Some(x) = self.layout_map().insert(sym, layout) { + if let Some(old_layout) = self.layout_map().insert(sym, *layout) { // Layout map already contains the symbol. We should never need to overwrite. // If the layout is not the same, that is a bug. - // There is always an old layout value and this dereference is safe. - let old_layout = unsafe { *x }; - if old_layout != *layout { + if &old_layout != layout { Err(format!( "Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}", sym, layout, old_layout @@ -619,7 +615,7 @@ where } /// layout_map gets the map from symbol to layout. - fn layout_map(&mut self) -> &mut MutMap>; + fn layout_map(&mut self) -> &mut MutMap>; fn create_free_map(&mut self) { let mut free_map = MutMap::default(); From d3c344e4da854a08c55490f5f782892eb4ec06ac Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 21 Sep 2021 15:38:46 -0700 Subject: [PATCH 049/136] Add macros for common builtin types --- compiler/gen_dev/src/generic64/mod.rs | 28 ++++++++ compiler/gen_dev/src/generic64/x86_64.rs | 81 ++++-------------------- 2 files changed, 42 insertions(+), 67 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index ac0417f975..9f98f512be 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1430,3 +1430,31 @@ impl< } } } + +#[macro_export] +macro_rules! single_register_integers { + () => { + Builtin::Int1 + | Builtin::Int8 + | Builtin::Int16 + | Builtin::Int32 + | Builtin::Int64 + | Builtin::Usize + }; +} + +#[macro_export] +macro_rules! single_register_floats { + () => { + // Float16 is explicitly ignored because it is not supported by must hardware and may require special exceptions. + // Builtin::Float16 | + Builtin::Float32 | Builtin::Float64 + }; +} + +#[macro_export] +macro_rules! single_register_builtins { + () => { + single_register_integers!() | single_register_floats!() + }; +} diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index 2fa225ef53..a39a828f8a 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -1,5 +1,7 @@ use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE}; -use crate::Relocation; +use crate::{ + single_register_builtins, single_register_floats, single_register_integers, Relocation, +}; use bumpalo::collections::Vec; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; @@ -191,14 +193,7 @@ impl CallConv for X86_64SystemV { } for (layout, sym) in args.iter() { match layout { - Layout::Builtin( - Builtin::Int1 - | Builtin::Int8 - | Builtin::Int16 - | Builtin::Int32 - | Builtin::Int64 - | Builtin::Usize, - ) => { + Layout::Builtin(single_register_integers!()) => { if general_i < Self::GENERAL_PARAM_REGS.len() { symbol_map.insert( *sym, @@ -217,7 +212,7 @@ impl CallConv for X86_64SystemV { ); } } - Layout::Builtin(Builtin::Float32 | Builtin::Float64) => { + Layout::Builtin(single_register_floats!()) => { if float_i < Self::FLOAT_PARAM_REGS.len() { symbol_map.insert( *sym, @@ -262,16 +257,7 @@ impl CallConv 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( - Builtin::Int1 - | Builtin::Int8 - | Builtin::Int16 - | Builtin::Int32 - | Builtin::Int64 - | Builtin::Usize - | Builtin::Float32 - | Builtin::Float64, - ) => {} + Layout::Builtin(single_register_builtins!()) => {} x => { return Err(format!( "receiving return type, {:?}, is not yet implemented", @@ -281,14 +267,7 @@ impl CallConv for X86_64SystemV { } for (i, layout) in arg_layouts.iter().enumerate() { match layout { - Layout::Builtin( - Builtin::Int1 - | Builtin::Int8 - | Builtin::Int16 - | Builtin::Int32 - | Builtin::Int64 - | Builtin::Usize, - ) => { + Layout::Builtin(single_register_integers!()) => { if general_i < Self::GENERAL_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::GENERAL_PARAM_REGS[general_i]; @@ -342,7 +321,7 @@ impl CallConv for X86_64SystemV { stack_offset += 8; } } - Layout::Builtin(Builtin::Float32 | Builtin::Float64) => { + Layout::Builtin(single_register_floats!()) => { if float_i < Self::FLOAT_PARAM_REGS.len() { // Load the value to the param reg. let dst = Self::FLOAT_PARAM_REGS[float_i]; @@ -553,18 +532,11 @@ impl CallConv for X86_64WindowsFastcall { for (layout, sym) in args.iter() { if i < Self::GENERAL_PARAM_REGS.len() { match layout { - Layout::Builtin( - Builtin::Int1 - | Builtin::Int8 - | Builtin::Int16 - | Builtin::Int32 - | Builtin::Int64 - | Builtin::Usize, - ) => { + Layout::Builtin(single_register_integers!()) => { symbol_map .insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i])); } - Layout::Builtin(Builtin::Float32 | Builtin::Float64) => { + Layout::Builtin(single_register_floats!()) => { symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i])); } Layout::Struct(&[]) => {} @@ -578,16 +550,7 @@ impl CallConv for X86_64WindowsFastcall { i += 1; } else { base_offset += match layout { - Layout::Builtin( - Builtin::Int1 - | Builtin::Int8 - | Builtin::Int16 - | Builtin::Int32 - | Builtin::Int64 - | Builtin::Usize - | Builtin::Float32 - | Builtin::Float64, - ) => 8, + Layout::Builtin(single_register_builtins!()) => 8, x => { return Err(format!( "Loading args with layout {:?} not yet implemented", @@ -621,16 +584,7 @@ impl CallConv for X86_64WindowsFastcall { // 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( - Builtin::Int1 - | Builtin::Int8 - | Builtin::Int16 - | Builtin::Int32 - | Builtin::Int64 - | Builtin::Usize - | Builtin::Float32 - | Builtin::Float64, - ) => {} + Layout::Builtin(single_register_builtins!()) => {} x => { return Err(format!( "receiving return type, {:?}, is not yet implemented", @@ -640,14 +594,7 @@ impl CallConv for X86_64WindowsFastcall { } for (i, layout) in arg_layouts.iter().enumerate() { match layout { - Layout::Builtin( - Builtin::Int1 - | Builtin::Int8 - | Builtin::Int16 - | Builtin::Int32 - | Builtin::Int64 - | Builtin::Usize, - ) => { + 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]; @@ -701,7 +648,7 @@ impl CallConv for X86_64WindowsFastcall { stack_offset += 8; } } - Layout::Builtin(Builtin::Float32 | Builtin::Float64) => { + 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]; From 0c6f8f308f22834209a38d11a6ed0cf6b1915e27 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 21 Sep 2021 16:51:47 -0700 Subject: [PATCH 050/136] Remove f16. It is not really supported by modern CPU hardware. --- compiler/gen_llvm/src/llvm/build.rs | 9 ++++----- compiler/gen_llvm/src/llvm/build_hash.rs | 1 - compiler/gen_llvm/src/llvm/compare.rs | 2 -- compiler/gen_llvm/src/llvm/convert.rs | 1 - compiler/gen_wasm/src/backend.rs | 1 - compiler/mono/src/alias_analysis.rs | 2 +- compiler/mono/src/layout.rs | 12 +++--------- compiler/test_mono/generated/quicksort_help.txt | 4 ++-- 8 files changed, 10 insertions(+), 22 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 22ceff9704..dbd98f5e06 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -4863,7 +4863,7 @@ fn run_low_level<'a, 'ctx, 'env>( Usize | Int128 | Int64 | Int32 | Int16 | Int8 => { build_int_unary_op(env, arg.into_int_value(), arg_builtin, op) } - Float128 | Float64 | Float32 | Float16 => { + Float128 | Float64 | Float32 => { build_float_unary_op(env, arg.into_float_value(), op) } _ => { @@ -4959,7 +4959,7 @@ fn run_low_level<'a, 'ctx, 'env>( "lt_or_gt", ) } - Float128 | Float64 | Float32 | Float16 => { + Float128 | Float64 | Float32 => { let are_equal = env.builder.build_float_compare( FloatPredicate::OEQ, lhs_arg.into_float_value(), @@ -5405,8 +5405,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>( | Builtin::Decimal | Builtin::Float128 | Builtin::Float64 - | Builtin::Float32 - | Builtin::Float16 => basic_type_from_builtin(env, builtin), + | Builtin::Float32 => basic_type_from_builtin(env, builtin), Builtin::Str | Builtin::EmptyStr | Builtin::List(_) | Builtin::EmptyList => { env.str_list_c_abi().into() } @@ -5769,7 +5768,7 @@ pub fn build_num_binop<'a, 'ctx, 'env>( rhs_layout, op, ), - Float128 | Float64 | Float32 | Float16 => build_float_binop( + Float128 | Float64 | Float32 => build_float_binop( env, parent, lhs_arg.into_float_value(), diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index 651948fcf6..d3dc006198 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -132,7 +132,6 @@ fn hash_builtin<'a, 'ctx, 'env>( | Builtin::Float64 | Builtin::Float32 | Builtin::Float128 - | Builtin::Float16 | Builtin::Decimal | Builtin::Usize => { let hash_bytes = store_and_use_as_u8_ptr(env, val, layout); diff --git a/compiler/gen_llvm/src/llvm/compare.rs b/compiler/gen_llvm/src/llvm/compare.rs index 7caed1e054..1ca6178c0b 100644 --- a/compiler/gen_llvm/src/llvm/compare.rs +++ b/compiler/gen_llvm/src/llvm/compare.rs @@ -103,7 +103,6 @@ fn build_eq_builtin<'a, 'ctx, 'env>( Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"), Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"), Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"), - Builtin::Float16 => float_cmp(FloatPredicate::OEQ, "eq_f16"), Builtin::Str => str_equal(env, lhs_val, rhs_val), Builtin::List(elem) => build_list_eq( @@ -247,7 +246,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>( Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"), Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"), Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"), - Builtin::Float16 => float_cmp(FloatPredicate::ONE, "neq_f16"), Builtin::Str => { let is_equal = str_equal(env, lhs_val, rhs_val).into_int_value(); diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index aa50de295f..d32216c10a 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -97,7 +97,6 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>( Float128 => context.f128_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(), Float32 => context.f32_type().as_basic_type_enum(), - Float16 => context.f16_type().as_basic_type_enum(), Dict(_, _) | EmptyDict => zig_dict_type(env).into(), Set(_) | EmptySet => zig_dict_type(env).into(), List(_) | EmptyList => zig_list_type(env).into(), diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 9475b82ec7..0b03f4aea8 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -55,7 +55,6 @@ impl WasmLayout { Layout::Builtin(Builtin::Float128) => Self::StackMemory(size), Layout::Builtin(Builtin::Float64) => Self::LocalOnly(F64, size), Layout::Builtin(Builtin::Float32) => Self::LocalOnly(F32, size), - Layout::Builtin(Builtin::Float16) => Self::LocalOnly(F32, size), Layout::Builtin(Builtin::Str) => Self::StackMemory(size), Layout::Builtin(Builtin::Dict(_, _)) => Self::StackMemory(size), Layout::Builtin(Builtin::Set(_)) => Self::StackMemory(size), diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 49c944f90e..f8e5dddb71 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -1241,7 +1241,7 @@ fn builtin_spec( match builtin { Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]), - Decimal | Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]), + Decimal | Float128 | Float64 | Float32 => builder.add_tuple_type(&[]), Str | EmptyStr => str_type(builder), Dict(key_layout, value_layout) => { let value_type = layout_spec_help(builder, value_layout, when_recursive)?; diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 5d1a679a12..959d679cb3 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -681,7 +681,6 @@ pub enum Builtin<'a> { Float128, Float64, Float32, - Float16, Str, Dict(&'a Layout<'a>, &'a Layout<'a>), Set(&'a Layout<'a>), @@ -1119,7 +1118,6 @@ impl<'a> Builtin<'a> { const F128_SIZE: u32 = 16; const F64_SIZE: u32 = std::mem::size_of::() as u32; const F32_SIZE: u32 = std::mem::size_of::() as u32; - const F16_SIZE: u32 = 2; /// Number of machine words in an empty one of these pub const STR_WORDS: u32 = 2; @@ -1149,7 +1147,6 @@ impl<'a> Builtin<'a> { Float128 => Builtin::F128_SIZE, Float64 => Builtin::F64_SIZE, Float32 => Builtin::F32_SIZE, - Float16 => Builtin::F16_SIZE, Str | EmptyStr => Builtin::STR_WORDS * pointer_size, Dict(_, _) | EmptyDict => Builtin::DICT_WORDS * pointer_size, Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size, @@ -1176,7 +1173,6 @@ impl<'a> Builtin<'a> { Float128 => align_of::() as u32, Float64 => align_of::() as u32, Float32 => align_of::() as u32, - Float16 => align_of::() as u32, Dict(_, _) | EmptyDict => pointer_size, Set(_) | EmptySet => pointer_size, // we often treat these as i128 (64-bit systems) @@ -1194,7 +1190,7 @@ impl<'a> Builtin<'a> { match self { Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 - | Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true, + | Float32 | EmptyStr | EmptyDict | EmptyList | EmptySet => true, Str | Dict(_, _) | Set(_) | List(_) => false, } } @@ -1205,7 +1201,7 @@ impl<'a> Builtin<'a> { match self { Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 - | Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false, + | Float32 | EmptyStr | EmptyDict | EmptyList | EmptySet => false, List(_) => true, Str | Dict(_, _) | Set(_) => true, @@ -1232,7 +1228,6 @@ impl<'a> Builtin<'a> { Float128 => alloc.text("Float128"), Float64 => alloc.text("Float64"), Float32 => alloc.text("Float32"), - Float16 => alloc.text("Float16"), EmptyStr => alloc.text("EmptyStr"), EmptyList => alloc.text("EmptyList"), @@ -1266,8 +1261,7 @@ impl<'a> Builtin<'a> { | Builtin::Decimal | Builtin::Float128 | Builtin::Float64 - | Builtin::Float32 - | Builtin::Float16 => unreachable!("not heap-allocated"), + | Builtin::Float32 => unreachable!("not heap-allocated"), Builtin::Str => pointer_size, Builtin::Dict(k, v) => k .alignment_bytes(pointer_size) diff --git a/compiler/test_mono/generated/quicksort_help.txt b/compiler/test_mono/generated/quicksort_help.txt index 8435559668..79af94f700 100644 --- a/compiler/test_mono/generated/quicksort_help.txt +++ b/compiler/test_mono/generated/quicksort_help.txt @@ -10,7 +10,7 @@ procedure Num.27 (#Attr.2, #Attr.3): let Test.26 = lowlevel NumLt #Attr.2 #Attr.3; ret Test.26; -procedure Test.1 (Test.29, Test.30, Test.31): +procedure Test.1 (Test.27, Test.28, Test.29): joinpoint Test.12 Test.2 Test.3 Test.4: let Test.14 = CallByName Num.27 Test.3 Test.4; if Test.14 then @@ -29,7 +29,7 @@ procedure Test.1 (Test.29, Test.30, Test.31): else ret Test.2; in - jump Test.12 Test.29 Test.30 Test.31; + jump Test.12 Test.27 Test.28 Test.29; procedure Test.0 (): let Test.9 = Array []; From 9c1b3ff86afb058770a0e7eff2d2566c31e84f20 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Tue, 21 Sep 2021 17:17:20 -0700 Subject: [PATCH 051/136] Update TODO list for linker with next steps --- linker/README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/linker/README.md b/linker/README.md index 8b46b825d0..24ee2a3f61 100644 --- a/linker/README.md +++ b/linker/README.md @@ -29,13 +29,17 @@ This linker is run in 2 phases: preprocessing and surigical linking. 1. Surgically update all call locations in the platform 1. Surgically update call information in the application (also dealing with other relocations for builtins) -## TODO for merging with compiler flow +## TODO (In a lightly prioritized order) -1. Add new compiler flag to hide this all behind. -1. Get compiler to generate dummy shared libraries with Roc exported symbols defined. -1. Modify host linking to generate dynamic executable that links against the dummy lib. -1. Call the preprocessor on the dynamic executable host. -1. Call the surgical linker on the emitted roc object file and the preprocessed host. -1. Enjoy! -1. Extract preprocessing generation to run earlier, maybe in parallel with the main compiler until we have full precompiled hosts. -1. Maybe add a roc command to generate the dummy lib to be used by platform authors. +- Run CLI tests and/or benchmarks with the Roc Linker. +- Test with an executable completely generated by Cargo (It will hopefully work out of the box like zig). +- Investigate why C is using absolute jumps to the main function from `_start`. This means our shifts break the executable. +- Add Macho support + - Honestly should be almost exactly the same code. + This means we likely need to do a lot of refactoring to minimize the duplicate code. + The fun of almost but not quite the same. +- Add PE support + - As a prereq, we need roc building on Windows (I'm not sure it does currently). + - Definitely a solid bit different than elf, but hopefully after refactoring for Macho, won't be that crazy to add. +- Look at enabling completely in memory linking that could be used with `roc run` and/or `roc repl` +- Add a feature to the compiler to make this linker optional. From f82c4350fb1e1d6b1531585d80837ae23696de02 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 22 Sep 2021 15:10:02 +0200 Subject: [PATCH 052/136] handle shadowing type names --- compiler/reporting/src/error/type.rs | 30 +++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/compiler/reporting/src/error/type.rs b/compiler/reporting/src/error/type.rs index 3a7238b789..c4cd15d406 100644 --- a/compiler/reporting/src/error/type.rs +++ b/compiler/reporting/src/error/type.rs @@ -1,7 +1,8 @@ use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{Index, MutSet, SendMap}; -use roc_module::ident::{IdentStr, Lowercase, TagName}; +use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; use roc_solve::solve; use roc_types::pretty_print::Parens; use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt}; @@ -10,6 +11,7 @@ use std::path::PathBuf; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use ven_pretty::DocAllocator; +const DUPLICATE_NAME: &str = "DUPLICATE NAME"; const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#; pub fn type_problem<'b>( @@ -103,12 +105,38 @@ pub fn type_problem<'b>( SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711 + Shadowed(original_region, shadow) => { + let doc = report_shadowing(alloc, original_region, shadow); + let title = DUPLICATE_NAME.to_string(); + + report(title, doc, filename) + } + other => panic!("unhandled bad type: {:?}", other), } } } } +fn report_shadowing<'b>( + alloc: &'b RocDocAllocator<'b>, + original_region: Region, + shadow: Located, +) -> RocDocBuilder<'b> { + let line = r#"Since these types have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#; + + alloc.stack(vec![ + alloc + .text("The ") + .append(alloc.ident(shadow.value)) + .append(alloc.reflow(" name is first defined here:")), + alloc.region(original_region), + alloc.reflow("But then it's defined a second time here:"), + alloc.region(shadow.region), + alloc.reflow(line), + ]) +} + pub fn cyclic_alias<'b>( alloc: &'b RocDocAllocator<'b>, symbol: Symbol, From b257a24edfe94bfc3dc83f732a5bc5e1401099d8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 22 Sep 2021 15:34:00 +0200 Subject: [PATCH 053/136] don't canonicalize Apply arguments twice --- compiler/can/src/annotation.rs | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 5bade7aeee..bbb1707ef2 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -82,6 +82,7 @@ pub fn canonicalize_annotation( let mut introduced_variables = IntroducedVariables::default(); let mut references = MutSet::default(); let mut aliases = SendMap::default(); + let typ = can_annotation_help( env, annotation, @@ -249,28 +250,7 @@ fn can_annotation_help( actual: Box::new(actual), } } - None => { - let mut args = Vec::new(); - - references.insert(symbol); - - for arg in *type_arguments { - let arg_ann = can_annotation_help( - env, - &arg.value, - region, - scope, - var_store, - introduced_variables, - local_aliases, - references, - ); - - args.push(arg_ann); - } - - Type::Apply(symbol, args) - } + None => Type::Apply(symbol, args), } } BoundVariable(v) => { From 3c53435e7e2e8bbe4451c04facfda3d3f2e254a4 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 22 Sep 2021 16:46:25 +0200 Subject: [PATCH 054/136] properly handle arguments to a closure caller --- compiler/mono/src/ir.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 9df64d81ef..bc620bdf74 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1933,7 +1933,29 @@ fn specialize_external<'a>( match layout { RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout) => { let assigned = env.unique_symbol(); - let unit = env.unique_symbol(); + + let mut argument_symbols = + Vec::with_capacity_in(argument_layouts.len(), env.arena); + let mut proc_arguments = + Vec::with_capacity_in(argument_layouts.len() + 1, env.arena); + let mut top_level_arguments = + Vec::with_capacity_in(argument_layouts.len() + 1, env.arena); + + for layout in argument_layouts { + let symbol = env.unique_symbol(); + + proc_arguments.push((*layout, symbol)); + + argument_symbols.push(symbol); + top_level_arguments.push(*layout); + } + + // the proc needs to take an extra closure argument + let lambda_set_layout = Layout::LambdaSet(lambda_set); + proc_arguments.push((lambda_set_layout, Symbol::ARG_CLOSURE)); + + // this should also be reflected in the TopLevel signature + top_level_arguments.push(lambda_set_layout); let hole = env.arena.alloc(Stmt::Ret(assigned)); @@ -1941,19 +1963,16 @@ fn specialize_external<'a>( env, lambda_set, Symbol::ARG_CLOSURE, - env.arena.alloc([unit]), + argument_symbols.into_bump_slice(), argument_layouts, *return_layout, assigned, hole, ); - let body = let_empty_struct(unit, env.arena.alloc(body)); - let lambda_set_layout = Layout::LambdaSet(lambda_set); - let proc = Proc { name, - args: env.arena.alloc([(lambda_set_layout, Symbol::ARG_CLOSURE)]), + args: proc_arguments.into_bump_slice(), body, closure_data_layout: None, ret_layout: *return_layout, @@ -1964,7 +1983,7 @@ fn specialize_external<'a>( let top_level = ProcLayout::new( env.arena, - env.arena.alloc([lambda_set_layout]), + top_level_arguments.into_bump_slice(), *return_layout, ); From cfdda10df4d53a1bd3066b659de0ac1e2978fbac Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 22 Sep 2021 21:23:53 +0200 Subject: [PATCH 055/136] fix argument passing --- compiler/gen_llvm/src/llvm/build.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 82a36b20c2..4864b04fcb 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3959,23 +3959,17 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( builder.position_at_end(entry); - let mut parameters = function_value.get_params(); - let output = parameters.pop().unwrap().into_pointer_value(); + let mut evaluator_arguments = function_value.get_params(); - let closure_data = if let Some(closure_data_ptr) = parameters.pop() { - let closure_data = - builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data"); + // the final parameter is the output pointer, pop it + let output = evaluator_arguments.pop().unwrap().into_pointer_value(); - env.arena.alloc([closure_data]) as &[_] - } else { - &[] - }; - - let mut parameters = parameters; - - for param in parameters.iter_mut() { - debug_assert!(param.is_pointer_value()); - *param = builder.build_load(param.into_pointer_value(), "load_param"); + // NOTE this may be incorrect in the long run + // here we load any argument that is a pointer + for param in evaluator_arguments.iter_mut() { + if param.is_pointer_value() { + *param = builder.build_load(param.into_pointer_value(), "load_param"); + } } let call_result = if env.is_gen_test { @@ -3984,13 +3978,13 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( function_value, evaluator, evaluator.get_call_conventions(), - closure_data, + &evaluator_arguments, result_type, ) } else { let call = env .builder - .build_call(evaluator, closure_data, "call_function"); + .build_call(evaluator, &evaluator_arguments, "call_function"); call.set_call_convention(evaluator.get_call_conventions()); From b7827159eed40d6ae71cc94697b4855ab4f7a278 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 22 Sep 2021 21:30:18 +0200 Subject: [PATCH 056/136] skip hello-web example --- cli/tests/cli_run.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index b34445b2ec..aa7020700f 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -157,6 +157,15 @@ mod cli_run { let example = $example; let file_name = example_file(dir_name, example.filename); + match example.executable_filename { + "hello-web" => { + // this is a web webassembly example, but we don't test with JS at the moment + eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename); + return; + } + _ => {} + } + // Check with and without optimizations check_output_with_stdin( &file_name, From f3fae8ea6107e9bc30cafade644c81f8533bf9e0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 23 Sep 2021 23:17:31 +0200 Subject: [PATCH 057/136] alias analysis static list --- compiler/mono/src/alias_analysis.rs | 41 +++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index f8e5dddb71..bff15284a7 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -16,6 +16,7 @@ use crate::layout::{Builtin, Layout, ListLayout, UnionLayout}; pub const MOD_APP: ModName = ModName(b"UserApp"); pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STATIC.to_ne_bytes()); +pub const STATIC_LIST_NAME: ConstName = ConstName(b"THIS IS A STATIC LIST"); const ENTRY_POINT_NAME: &[u8] = b"mainForHost"; @@ -128,6 +129,22 @@ where }; m.add_const(STATIC_STR_NAME, static_str_def)?; + // a const that models all static lists + let static_list_def = { + let mut cbuilder = ConstDefBuilder::new(); + let block = cbuilder.add_block(); + let cell = cbuilder.add_new_heap_cell(block)?; + + let unit_type = cbuilder.add_tuple_type(&[])?; + let bag = cbuilder.add_empty_bag(block, unit_type)?; + let value_id = cbuilder.add_make_tuple(block, &[cell, bag])?; + let root = BlockExpr(block, value_id); + let list_type_id = static_list_type(&mut cbuilder)?; + + cbuilder.build(list_type_id, root)? + }; + m.add_const(STATIC_LIST_NAME, static_list_def)?; + // the entry point wrapper let roc_main_bytes = func_name_bytes_help( entry_point.symbol, @@ -1117,9 +1134,11 @@ fn expr_spec<'a>( let list = new_list(builder, block, type_id)?; let mut bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let mut all_constants = true; for element in elems.iter() { let value_id = if let ListLiteralElement::Symbol(symbol) = element { + all_constants = false; env.symbols[symbol] } else { builder.add_make_tuple(block, &[]).unwrap() @@ -1128,9 +1147,13 @@ fn expr_spec<'a>( bag = builder.add_bag_insert(block, bag, value_id)?; } - let cell = builder.add_new_heap_cell(block)?; + if all_constants { + new_static_list(builder, block) + } else { + let cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[cell, bag]) + builder.add_make_tuple(block, &[cell, bag]) + } } EmptyArray => { @@ -1296,6 +1319,14 @@ fn str_type(builder: &mut TC) -> Result { builder.add_tuple_type(&[cell_id]) } +fn static_list_type(builder: &mut TC) -> Result { + let unit_type = builder.add_tuple_type(&[])?; + let cell = builder.add_heap_cell_type(); + let bag = builder.add_bag_type(unit_type)?; + + builder.add_tuple_type(&[cell, bag]) +} + // const OK_TAG_ID: u8 = 1u8; // const ERR_TAG_ID: u8 = 0u8; @@ -1329,6 +1360,12 @@ fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result Result { + let module = MOD_APP; + + builder.add_const_ref(block, module, STATIC_LIST_NAME) +} + fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result { // we model all our numbers as unit values builder.add_make_tuple(block, &[]) From a593713800352177ded2c56182cd81ec98d3f35c Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Thu, 23 Sep 2021 21:29:53 -0700 Subject: [PATCH 058/136] Fix surgical linking for C hosts with extra arg --- compiler/build/src/link.rs | 1 + linker/README.md | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 528cb208a5..40a8f84cbf 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -275,6 +275,7 @@ pub fn build_c_host_native( command.args(&[ shared_lib_path.to_str().unwrap(), "-fPIE", + "-pie", "-lm", "-lpthread", "-ldl", diff --git a/linker/README.md b/linker/README.md index 24ee2a3f61..0d1e2d77ee 100644 --- a/linker/README.md +++ b/linker/README.md @@ -33,7 +33,6 @@ This linker is run in 2 phases: preprocessing and surigical linking. - Run CLI tests and/or benchmarks with the Roc Linker. - Test with an executable completely generated by Cargo (It will hopefully work out of the box like zig). -- Investigate why C is using absolute jumps to the main function from `_start`. This means our shifts break the executable. - Add Macho support - Honestly should be almost exactly the same code. This means we likely need to do a lot of refactoring to minimize the duplicate code. From 45774df2db28cbe74d09a0734df273dc73552393 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 10:00:33 +0200 Subject: [PATCH 059/136] fix hello-web platform --- examples/hello-web/platform/host.js | 6 ++++++ examples/hello-web/platform/host.zig | 12 ++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/hello-web/platform/host.js b/examples/hello-web/platform/host.js index 0d953eabff..a90ff4187b 100644 --- a/examples/hello-web/platform/host.js +++ b/examples/hello-web/platform/host.js @@ -11,6 +11,12 @@ async function roc_web_platform_run(wasm_filename, callback) { const importObj = { wasi_snapshot_preview1: { + proc_exit: (code) => { + if (code !== 0) { + console.error(`Exited with code ${code}`); + } + exit_code = code; + }, roc_panic: (_pointer, _tag_id) => { throw 'Roc panicked!'; } diff --git a/examples/hello-web/platform/host.zig b/examples/hello-web/platform/host.zig index 5d588d6912..2623097836 100644 --- a/examples/hello-web/platform/host.zig +++ b/examples/hello-web/platform/host.zig @@ -48,25 +48,21 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(*RocCallResult) void; - -const RocCallResult = extern struct { flag: u64, content: RocStr }; +extern fn roc__mainForHost_1_exposed(*RocStr) void; const Unit = extern struct {}; extern fn js_display_roc_string(str_bytes: ?[*]u8, str_len: usize) void; pub fn main() u8 { - // make space for the result - var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() }; - // actually call roc to populate the callresult + var callresult = RocStr.empty(); roc__mainForHost_1_exposed(&callresult); // display the result using JavaScript - js_display_roc_string(callresult.content.str_bytes, callresult.content.str_len); + js_display_roc_string(callresult.str_bytes, callresult.str_len); - callresult.content.deinit(); + callresult.deinit(); return 0; } From 5d30e26e973e5b0fba3cbdbfb695a498bc31250c Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 24 Sep 2021 13:56:53 +0200 Subject: [PATCH 060/136] removed benchmark debug printing --- cli/cli_utils/src/bench_utils.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cli/cli_utils/src/bench_utils.rs b/cli/cli_utils/src/bench_utils.rs index 4c929e0ea7..146f500380 100644 --- a/cli/cli_utils/src/bench_utils.rs +++ b/cli/cli_utils/src/bench_utils.rs @@ -12,11 +12,9 @@ fn exec_bench_w_input( ) { let flags: &[&str] = &["--optimize"]; - println!("building {:?}", executable_filename); let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); - println!("done building."); - /*if !compile_out.stderr.is_empty() { + if !compile_out.stderr.is_empty() { panic!("{}", compile_out.stderr); } @@ -26,13 +24,9 @@ fn exec_bench_w_input( compile_out ); - println!("checking output for {:?}", executable_filename); check_cmd_output(file, stdin_str, executable_filename, expected_ending); - println!("benching {:?}", executable_filename); bench_cmd(file, stdin_str, executable_filename, bench_group_opt); - - println!("DONE");*/ } fn check_cmd_output( From 34a25408c3f4c56d0622b2805f5145a1e4500dd9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 14:27:38 +0200 Subject: [PATCH 061/136] fix issue --- cli/tests/repl_eval.rs | 5 + compiler/gen_llvm/src/llvm/build.rs | 204 ++++++++++++++++++++++------ 2 files changed, 168 insertions(+), 41 deletions(-) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 87d53837a8..bfb04ae0e0 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -502,6 +502,11 @@ mod repl_eval { expect_success("\\x -> x", " : a -> a"); } + #[test] + fn sum_lambda() { + expect_success("\\x, y -> x + y", " : Num a, Num a -> Num a"); + } + #[test] fn stdlib_function() { expect_success("Num.abs", " : Num a -> Num a"); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 4864b04fcb..99c83a2126 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -708,7 +708,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>( env, main_fn_name, roc_main_fn, - &[], + top_level.arguments, top_level.result, main_fn_name, ); @@ -3210,6 +3210,141 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( c_function } +fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + ident_string: &str, + roc_function: FunctionValue<'ctx>, + arguments: &[Layout<'a>], + c_function_name: &str, +) -> FunctionValue<'ctx> { + let context = env.context; + + // a tagged union to indicate to the test loader that a panic occured. + // especially when running 32-bit binaries on a 64-bit machine, there + // does not seem to be a smarter solution + let wrapper_return_type = context.struct_type( + &[ + context.i64_type().into(), + roc_function.get_type().get_return_type().unwrap(), + ], + false, + ); + + let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); + for layout in arguments { + cc_argument_types.push(to_cc_type(env, layout)); + } + + // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` if the C abi demands it + let mut argument_types = cc_argument_types; + let return_type = wrapper_return_type; + + let c_function_type = { + let output_type = return_type.ptr_type(AddressSpace::Generic); + argument_types.push(output_type.into()); + env.context.void_type().fn_type(&argument_types, false) + }; + + let c_function = add_func( + env.module, + c_function_name, + c_function_type, + Linkage::External, + C_CALL_CONV, + ); + + let subprogram = env.new_subprogram(c_function_name); + c_function.set_subprogram(subprogram); + + // STEP 2: build the exposed function's body + let builder = env.builder; + let context = env.context; + + let entry = context.append_basic_block(c_function, "entry"); + + builder.position_at_end(entry); + + debug_info_init!(env, c_function); + + // drop the final argument, which is the pointer we write the result into + let args_vector = c_function.get_params(); + let mut args = args_vector.as_slice(); + let args_length = args.len(); + + args = &args[..args.len() - 1]; + + let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); + + let it = args.iter().zip(roc_function.get_type().get_param_types()); + for (arg, fastcc_type) in it { + let arg_type = arg.get_type(); + if arg_type == fastcc_type { + // the C and Fast calling conventions agree + arguments_for_call.push(*arg); + } else { + let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type"); + arguments_for_call.push(cast); + } + } + + let arguments_for_call = &arguments_for_call.into_bump_slice(); + + let call_result = { + let roc_wrapper_function = make_exception_catcher(env, roc_function); + debug_assert_eq!( + arguments_for_call.len(), + roc_wrapper_function.get_params().len() + ); + + builder.position_at_end(entry); + + let call_wrapped = builder.build_call( + roc_wrapper_function, + arguments_for_call, + "call_wrapped_function", + ); + call_wrapped.set_call_convention(FAST_CALL_CONV); + + call_wrapped.try_as_basic_value().left().unwrap() + }; + + let output_arg_index = args_length - 1; + + let output_arg = c_function + .get_nth_param(output_arg_index as u32) + .unwrap() + .into_pointer_value(); + + builder.build_store(output_arg, call_result); + builder.build_return(None); + + // STEP 3: build a {} -> u64 function that gives the size of the return type + let size_function_type = env.context.i64_type().fn_type(&[], false); + let size_function_name: String = format!("roc__{}_size", ident_string); + + let size_function = add_func( + env.module, + size_function_name.as_str(), + size_function_type, + Linkage::External, + C_CALL_CONV, + ); + + let subprogram = env.new_subprogram(&size_function_name); + size_function.set_subprogram(subprogram); + + let entry = context.append_basic_block(size_function, "entry"); + + builder.position_at_end(entry); + + debug_info_init!(env, size_function); + + let size: BasicValueEnum = return_type.size_of().unwrap().into(); + builder.build_return(Some(&size)); + + c_function +} + fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, ident_string: &str, @@ -3220,16 +3355,24 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( ) -> FunctionValue<'ctx> { let context = env.context; - // a generic version that writes the result into a passed *u8 pointer - if !env.is_gen_test { - expose_function_to_host_help_c_abi_generic( + if env.is_gen_test { + return expose_function_to_host_help_c_abi_gen_test( env, + ident_string, roc_function, arguments, - &format!("{}_generic", c_function_name), + c_function_name, ); } + // a generic version that writes the result into a passed *u8 pointer + expose_function_to_host_help_c_abi_generic( + env, + roc_function, + arguments, + &format!("{}_generic", c_function_name), + ); + let wrapper_return_type = if env.is_gen_test { context .struct_type( @@ -3256,11 +3399,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let cc_return = to_cc_return(env, &return_layout); let c_function_type = match cc_return { - CCReturn::Void if !env.is_gen_test => { - env.context.void_type().fn_type(&argument_types, false) - } - CCReturn::Return if !env.is_gen_test => return_type.fn_type(&argument_types, false), - _ => { + CCReturn::Void => env.context.void_type().fn_type(&argument_types, false), + CCReturn::Return => return_type.fn_type(&argument_types, false), + CCReturn::ByPointer => { let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.push(output_type.into()); env.context.void_type().fn_type(&argument_types, false) @@ -3294,13 +3435,13 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let args_length = args.len(); match cc_return { - CCReturn::Return if !env.is_gen_test => { + CCReturn::Return => { debug_assert_eq!(args.len(), roc_function.get_params().len()); } - CCReturn::Void if !env.is_gen_test => { + CCReturn::Void => { debug_assert_eq!(args.len(), roc_function.get_params().len()); } - _ => { + CCReturn::ByPointer => { args = &args[..args.len() - 1]; debug_assert_eq!(args.len(), roc_function.get_params().len()); } @@ -3323,44 +3464,25 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); let call_result = { - if env.is_gen_test { - let roc_wrapper_function = make_exception_catcher(env, roc_function); - debug_assert_eq!( - arguments_for_call.len(), - roc_wrapper_function.get_params().len() - ); + let call_unwrapped = + builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function"); + call_unwrapped.set_call_convention(FAST_CALL_CONV); - builder.position_at_end(entry); + let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); - let call_wrapped = builder.build_call( - roc_wrapper_function, - arguments_for_call, - "call_wrapped_function", - ); - call_wrapped.set_call_convention(FAST_CALL_CONV); - - call_wrapped.try_as_basic_value().left().unwrap() - } else { - let call_unwrapped = - builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function"); - call_unwrapped.set_call_convention(FAST_CALL_CONV); - - let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); - - // make_good_roc_result(env, call_unwrapped_result) - call_unwrapped_result - } + // make_good_roc_result(env, call_unwrapped_result) + call_unwrapped_result }; match cc_return { - CCReturn::Void if !env.is_gen_test => { + CCReturn::Void => { // TODO return empty struct here? builder.build_return(None); } - CCReturn::Return if !env.is_gen_test => { + CCReturn::Return => { builder.build_return(Some(&call_result)); } - _ => { + CCReturn::ByPointer => { let output_arg_index = args_length - 1; let output_arg = c_function From f43c10373f0b42c11261299dc56fde96a0879f32 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 15:41:00 +0200 Subject: [PATCH 062/136] represent empty closure as unit (not void) --- compiler/mono/src/layout.rs | 2 +- compiler/test_gen/src/gen_list.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 959d679cb3..507bcfaf7e 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -599,7 +599,7 @@ impl<'a> LambdaSet<'a> { // this can happen when there is a type error somewhere Ok(LambdaSet { set: &[], - representation: arena.alloc(Layout::Union(UnionLayout::NonRecursive(&[]))), + representation: arena.alloc(Layout::Struct(&[])), }) } _ => panic!("called LambdaSet.from_var on invalid input"), diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 582b9acceb..758603f2c1 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -2031,3 +2031,31 @@ fn map_with_index_multi_record() { RocList<((), ())> ); } + +#[test] +fn empty_list_of_function_type() { + // see https://github.com/rtfeldman/roc/issues/1732 + assert_evals_to!( + indoc!( + r#" + myList : List (Str -> Str) + myList = [] + + myClosure : Str -> Str + myClosure = \_ -> "bar" + + choose = + if False then + myList + else + [ myClosure ] + + when List.get choose 0 is + Ok f -> f "foo" + Err _ -> "bad!" + "# + ), + RocStr::from_slice(b"bar"), + RocStr + ); +} From b7f26baf9566be5e4e060f8873e197ee10476fa0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 15:44:09 +0200 Subject: [PATCH 063/136] add mono test --- .../generated/empty_list_of_function_type.txt | 43 +++++++++++++++++++ compiler/test_mono/src/lib.rs | 27 ++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 compiler/test_mono/generated/empty_list_of_function_type.txt diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt new file mode 100644 index 0000000000..89aa3d309b --- /dev/null +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -0,0 +1,43 @@ +procedure List.3 (#Attr.2, #Attr.3): + let Test.20 = lowlevel ListLen #Attr.2; + let Test.17 = lowlevel NumLt #Attr.3 Test.20; + if Test.17 then + let Test.19 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.18 = Ok Test.19; + ret Test.18; + else + let Test.16 = Struct {}; + let Test.15 = Err Test.16; + ret Test.15; + +procedure Test.2 (Test.6): + let Test.24 = "bar"; + ret Test.24; + +procedure Test.0 (): + let Test.1 = Array []; + joinpoint Test.22 Test.3: + let Test.14 = 0i64; + let Test.7 = CallByName List.3 Test.3 Test.14; + dec Test.3; + let Test.11 = 1i64; + let Test.12 = GetTagId Test.7; + let Test.13 = lowlevel Eq Test.11 Test.12; + if Test.13 then + let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.7; + let Test.9 = "foo"; + let Test.8 = CallByName Test.2 Test.9; + dec Test.9; + ret Test.8; + else + let Test.10 = "bad!"; + ret Test.10; + in + let Test.25 = false; + if Test.25 then + jump Test.22 Test.1; + else + dec Test.1; + let Test.23 = Struct {}; + let Test.21 = Array [Test.23]; + jump Test.22 Test.21; diff --git a/compiler/test_mono/src/lib.rs b/compiler/test_mono/src/lib.rs index 2d05f9cd16..cbcdbed066 100644 --- a/compiler/test_mono/src/lib.rs +++ b/compiler/test_mono/src/lib.rs @@ -1084,6 +1084,33 @@ fn specialize_lowlevel() { ) } +#[mono_test] +fn empty_list_of_function_type() { + // see https://github.com/rtfeldman/roc/issues/1732 + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + myList : List (Str -> Str) + myList = [] + + myClosure : Str -> Str + myClosure = \_ -> "bar" + + choose = + if False then + myList + else + [ myClosure ] + + when List.get choose 0 is + Ok f -> f "foo" + Err _ -> "bad!" + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { From c11d89c4bf1dad25d3e3c664937425b69517fdc0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 15:47:53 +0200 Subject: [PATCH 064/136] fix typo --- compiler/gen_llvm/src/llvm/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 99c83a2126..40c80e4157 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3219,7 +3219,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( ) -> FunctionValue<'ctx> { let context = env.context; - // a tagged union to indicate to the test loader that a panic occured. + // a tagged union to indicate to the test loader that a panic occurred. // especially when running 32-bit binaries on a 64-bit machine, there // does not seem to be a smarter solution let wrapper_return_type = context.struct_type( From 2f24067267668afdc9da0a51421ede89c66e4539 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 18 Sep 2021 14:41:44 -0700 Subject: [PATCH 065/136] Add small string support to the dev backend --- compiler/gen_dev/src/generic64/mod.rs | 64 +- compiler/gen_dev/tests/dev_num.rs | 6 - compiler/gen_dev/tests/dev_str.rs | 954 ++++++++++++++++++++++++++ 3 files changed, 1017 insertions(+), 7 deletions(-) create mode 100644 compiler/gen_dev/tests/dev_str.rs diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 9f98f512be..ca26a61f5f 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -9,7 +9,7 @@ use std::marker::PhantomData; pub mod aarch64; pub mod x86_64; -const PTR_SIZE: u32 = 64; +const PTR_SIZE: u32 = 8; pub trait CallConv { const GENERAL_PARAM_REGS: &'static [GeneralReg]; @@ -489,6 +489,25 @@ impl< ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); Ok(()) } + Layout::Builtin(Builtin::Str) => { + if CC::returns_via_arg_pointer(ret_layout)? { + // This will happen on windows, return via pointer here. + Err("FnCall: Returning strings via pointer not yet implemented".to_string()) + } else { + let offset = self.claim_stack_size(16)?; + self.symbol_storage_map.insert( + *dst, + SymbolStorage::Base { + offset, + size: 16, + owned: true, + }, + ); + ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]); + ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]); + Ok(()) + } + } x => Err(format!( "FnCall: receiving return type, {:?}, is not yet implemented", x @@ -893,6 +912,35 @@ impl< ASM::mov_freg64_imm64(&mut self.buf, &mut self.relocs, reg, val); Ok(()) } + Literal::Str(x) if x.len() < 16 => { + // Load small string. + let reg = self.get_tmp_general_reg()?; + + let offset = self.claim_stack_size(16)?; + self.symbol_storage_map.insert( + *sym, + SymbolStorage::Base { + offset, + size: 16, + owned: true, + }, + ); + let mut bytes = [0; 16]; + bytes[..x.len()].copy_from_slice(x.as_bytes()); + bytes[15] = (x.len() as u8) | 0b1000_0000; + + let mut num_bytes = [0; 8]; + num_bytes.copy_from_slice(&bytes[..8]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(&mut self.buf, reg, num); + ASM::mov_base32_reg64(&mut self.buf, offset, reg); + + num_bytes.copy_from_slice(&bytes[8..]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(&mut self.buf, reg, num); + ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg); + Ok(()) + } x => Err(format!("loading literal, {:?}, is not yet implemented", x)), } } @@ -1012,6 +1060,20 @@ impl< Layout::Builtin(Builtin::Float64) => { ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); } + 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()) + } 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[1], + *offset + 8, + ); + Ok(()) + } + } Layout::Struct(field_layouts) => { let (offset, size) = (*offset, *size); // Nothing to do for empty struct diff --git a/compiler/gen_dev/tests/dev_num.rs b/compiler/gen_dev/tests/dev_num.rs index c223acbcdf..c82d2ca23f 100644 --- a/compiler/gen_dev/tests/dev_num.rs +++ b/compiler/gen_dev/tests/dev_num.rs @@ -1,12 +1,6 @@ -#[macro_use] -extern crate pretty_assertions; - #[macro_use] extern crate indoc; -extern crate bumpalo; -extern crate libc; - #[macro_use] mod helpers; diff --git a/compiler/gen_dev/tests/dev_str.rs b/compiler/gen_dev/tests/dev_str.rs new file mode 100644 index 0000000000..c03240a089 --- /dev/null +++ b/compiler/gen_dev/tests/dev_str.rs @@ -0,0 +1,954 @@ +// use roc_std::{RocList, RocStr}; +#[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 { + // #[test] + // fn str_split_bigger_delimiter_small_str() { + // assert_evals_to!( + // indoc!( + // r#" + // List.len (Str.split "hello" "JJJJ there") + // "# + // ), + // 1, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // when List.first (Str.split "JJJ" "JJJJ there") is + // Ok str -> + // Str.countGraphemes str + + // _ -> + // -1 + + // "# + // ), + // 3, + // i64 + // ); + // } + + // #[test] + // fn str_split_str_concat_repeated() { + // assert_evals_to!( + // indoc!( + // r#" + // when List.first (Str.split "JJJJJ" "JJJJ there") is + // Ok str -> + // str + // |> Str.concat str + // |> Str.concat str + // |> Str.concat str + // |> Str.concat str + + // _ -> + // "Not Str!" + + // "# + // ), + // RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"), + // RocStr + // ); + // } + + // #[test] + // fn str_split_small_str_bigger_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // when + // List.first + // (Str.split "JJJ" "0123456789abcdefghi") + // is + // Ok str -> str + // _ -> "" + // "# + // ), + // RocStr::from_slice(b"JJJ"), + // RocStr + // ); + // } + + // #[test] + // fn str_split_big_str_small_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "01234567789abcdefghi?01234567789abcdefghi" "?" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"01234567789abcdefghi"), + // RocStr::from_slice(b"01234567789abcdefghi") + // ]), + // RocList + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"01234567789abcdefghi "), + // RocStr::from_slice(b" 01234567789abcdefghi") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_split_small_str_small_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "J!J!J" "!" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"J"), + // RocStr::from_slice(b"J"), + // RocStr::from_slice(b"J") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_split_bigger_delimiter_big_strs() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "string to split is shorter" + // "than the delimiter which happens to be very very long" + // "# + // ), + // RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]), + // RocList + // ); + // } + + // #[test] + // fn str_split_empty_strs() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "" "" + // "# + // ), + // RocList::from_slice(&[RocStr::from_slice(b"")]), + // RocList + // ); + // } + + // #[test] + // fn str_split_minimal_example() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "a," "," + // "# + // ), + // RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]), + // RocList + // ) + // } + + // #[test] + // fn str_split_small_str_big_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" + // "---- ---- ---- ---- ----" + // |> List.len + // "# + // ), + // 3, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" + // "---- ---- ---- ---- ----" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"1"), + // RocStr::from_slice(b"2"), + // RocStr::from_slice(b"") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_split_small_str_20_char_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |" + // "|-- -- -- -- -- -- |" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"3"), + // RocStr::from_slice(b"4"), + // RocStr::from_slice(b"") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_concat_big_to_big() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.concat + // "First string that is fairly long. Longer strings make for different errors. " + // "Second string that is also fairly long. Two long strings test things that might not appear with short strings." + // "# + // ), + // RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."), + // RocStr + // ); + // } + + #[test] + fn small_str_literal() { + assert_evals_to!( + "\"JJJJJJJJJJJJJJJ\"", + [ + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0b1000_1111 + ], + [u8; 16] + ); + } + + // #[test] + // fn small_str_zeroed_literal() { + // // 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!( + // "\"J\"", + // [ + // 0x4a, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0b1000_0001 + // ], + // [u8; 16] + // ); + // } + + // #[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_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_small_to_big() { + // assert_evals_to!( + // r#"Str.concat "abc" " this is longer than 15 chars""#, + // RocStr::from_slice(b"abc this is longer than 15 chars"), + // RocStr + // ); + // } + + // #[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_overflow_to_big() { + // assert_evals_to!( + // r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#, + // RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"), + // RocStr + // ); + // } + + // #[test] + // fn str_concat_empty() { + // assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr); + // } + + // #[test] + // fn small_str_is_empty() { + // assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool); + // } + + // #[test] + // fn big_str_is_empty() { + // assert_evals_to!( + // r#"Str.isEmpty "this is more than 15 chars long""#, + // false, + // bool + // ); + // } + + // #[test] + // fn empty_str_is_empty() { + // assert_evals_to!(r#"Str.isEmpty """#, true, bool); + // } + + // #[test] + // fn str_starts_with() { + // assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool); + // assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool); + // assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool); + // assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool); + // assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool); + // } + + // #[test] + // fn str_starts_with_code_point() { + // assert_evals_to!( + // &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32), + // true, + // bool + // ); + // assert_evals_to!( + // &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32), + // false, + // bool + // ); + // } + + // #[test] + // fn str_ends_with() { + // assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool); + // assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool); + // assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool); + // } + + // #[test] + // fn str_count_graphemes_small_str() { + // assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize); + // } + + // #[test] + // fn str_count_graphemes_three_js() { + // assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize); + // } + + // #[test] + // fn str_count_graphemes_big_str() { + // assert_evals_to!( + // r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#, + // 45, + // usize + // ); + // } + + // #[test] + // fn str_starts_with_same_big_str() { + // assert_evals_to!( + // r#"Str.startsWith "123456789123456789" "123456789123456789""#, + // true, + // bool + // ); + // } + + // #[test] + // fn str_starts_with_different_big_str() { + // assert_evals_to!( + // r#"Str.startsWith "12345678912345678910" "123456789123456789""#, + // true, + // bool + // ); + // } + + // #[test] + // fn str_starts_with_same_small_str() { + // assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool); + // } + + // #[test] + // fn str_starts_with_different_small_str() { + // assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool); + // } + // #[test] + // fn str_starts_with_false_small_str() { + // assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); + // } + + // #[test] + // fn str_from_int() { + // assert_evals_to!( + // r#"Str.fromInt 1234"#, + // roc_std::RocStr::from_slice("1234".as_bytes()), + // roc_std::RocStr + // ); + // assert_evals_to!( + // r#"Str.fromInt 0"#, + // roc_std::RocStr::from_slice("0".as_bytes()), + // roc_std::RocStr + // ); + // assert_evals_to!( + // r#"Str.fromInt -1"#, + // roc_std::RocStr::from_slice("-1".as_bytes()), + // roc_std::RocStr + // ); + + // let max = format!("{}", i64::MAX); + // assert_evals_to!( + // r#"Str.fromInt Num.maxInt"#, + // RocStr::from_slice(max.as_bytes()), + // RocStr + // ); + + // let min = format!("{}", i64::MIN); + // assert_evals_to!( + // r#"Str.fromInt Num.minInt"#, + // RocStr::from_slice(min.as_bytes()), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_single_ascii() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_many_ascii() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 99, 0x7E ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("abc~".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_single_unicode() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xE2, 0x88, 0x86 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("∆".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_many_unicode() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("∆œ¬".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_single_grapheme() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("💖".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_many_grapheme() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_all() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("💖b∆".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_invalid_start_byte() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 0x80, 99 ] is + // Err (BadUtf8 InvalidStartByte byteIndex) -> + // if byteIndex == 2 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_unexpected_end_of_sequence() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 99, 0xC2 ] is + // Err (BadUtf8 UnexpectedEndOfSequence byteIndex) -> + // if byteIndex == 3 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_expected_continuation() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 99, 0xC2, 0x00 ] is + // Err (BadUtf8 ExpectedContinuation byteIndex) -> + // if byteIndex == 3 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_overlong_encoding() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 0xF0, 0x80, 0x80, 0x80 ] is + // Err (BadUtf8 OverlongEncoding byteIndex) -> + // if byteIndex == 1 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_codepoint_too_large() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 0xF4, 0x90, 0x80, 0x80 ] is + // Err (BadUtf8 CodepointTooLarge byteIndex) -> + // if byteIndex == 1 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_surrogate_half() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 0xED, 0xA0, 0x80 ] is + // Err (BadUtf8 EncodesSurrogateHalf byteIndex) -> + // if byteIndex == 2 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_equality() { + // assert_evals_to!(r#""a" == "a""#, true, bool); + // assert_evals_to!( + // r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#, + // true, + // bool + // ); + // assert_evals_to!(r#""a" != "b""#, true, bool); + // assert_evals_to!(r#""a" == "b""#, false, bool); + // } + + // #[test] + // fn str_clone() { + // use roc_std::RocStr; + // let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes()); + // let short = RocStr::from_slice("x".as_bytes()); + // let empty = RocStr::from_slice("".as_bytes()); + + // debug_assert_eq!(long.clone(), long); + // debug_assert_eq!(short.clone(), short); + // debug_assert_eq!(empty.clone(), empty); + // } + + // #[test] + // fn nested_recursive_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // Expr : [ Add Expr Expr, Val I64, Var I64 ] + + // expr : Expr + // expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1)) + + // printExpr : Expr -> Str + // printExpr = \e -> + // when e is + // Add a b -> + // "Add (" + // |> Str.concat (printExpr a) + // |> Str.concat ") (" + // |> Str.concat (printExpr b) + // |> Str.concat ")" + // Val v -> "Val " |> Str.concat (Str.fromInt v) + // Var v -> "Var " |> Str.concat (Str.fromInt v) + + // printExpr expr + // "# + // ), + // RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"), + // RocStr + // ); + // } + + // #[test] + // fn str_join_comma_small() { + // assert_evals_to!( + // r#"Str.joinWith ["1", "2"] ", " "#, + // RocStr::from("1, 2"), + // RocStr + // ); + // } + + // #[test] + // fn str_join_comma_big() { + // assert_evals_to!( + // r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#, + // RocStr::from("10000000, 2000000, 30000000"), + // RocStr + // ); + // } + + // #[test] + // fn str_join_comma_single() { + // assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr); + // } + + // #[test] + // fn str_from_float() { + // assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr); + // } + + // #[test] + // fn str_to_utf8() { + // assert_evals_to!( + // r#"Str.toUtf8 "hello""#, + // RocList::from_slice(&[104, 101, 108, 108, 111]), + // RocList + // ); + // assert_evals_to!( + // r#"Str.toUtf8 "this is a long string""#, + // RocList::from_slice(&[ + // 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116, + // 114, 105, 110, 103 + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_from_utf8_range() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { count: 5, start: 0 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("hello"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_slice() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { count: 4, start: 1 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("ello"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_slice_not_end() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { count: 3, start: 1 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("ell"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_order_does_not_matter() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 1, count: 3 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("ell"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_out_of_bounds_start_value() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 7, count: 3 } is + // Ok _ -> "" + // Err (BadUtf8 _ _) -> "" + // Err OutOfBounds -> "out of bounds" + // "# + // ), + // RocStr::from("out of bounds"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_count_too_high() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 0, count: 6 } is + // Ok _ -> "" + // Err (BadUtf8 _ _) -> "" + // Err OutOfBounds -> "out of bounds" + // "# + // ), + // RocStr::from("out of bounds"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_count_too_high_for_start() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 4, count: 3 } is + // Ok _ -> "" + // Err (BadUtf8 _ _) -> "" + // Err OutOfBounds -> "out of bounds" + // "# + // ), + // RocStr::from("out of bounds"), + // RocStr + // ); + // } +} From d1021d652dd0ba27b0b638bdffcff43949d7ea42 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 18 Sep 2021 15:55:04 -0700 Subject: [PATCH 066/136] Add StrCat support and update hello-zig to support dev backend --- cli/src/build.rs | 11 +- compiler/gen_dev/src/generic64/aarch64.rs | 1 + compiler/gen_dev/src/generic64/mod.rs | 11 +- compiler/gen_dev/src/generic64/x86_64.rs | 76 ++++++++++- compiler/gen_dev/src/lib.rs | 23 +++- compiler/gen_dev/tests/dev_str.rs | 158 +++++++++++----------- 6 files changed, 186 insertions(+), 94 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index d0702ce3dd..85fc609717 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -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(|_| { diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 6149bff14c..32273fbf91 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -226,6 +226,7 @@ impl CallConv for AArch64Call { #[inline(always)] fn load_args<'a>( + _buf: &mut Vec<'a, u8>, _symbol_map: &mut MutMap>, _args: &'a [(Layout<'a>, Symbol)], _ret_layout: &Layout<'a>, diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index ca26a61f5f..eb5a79150e 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -48,6 +48,7 @@ pub trait CallConv { // 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>, 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) => { diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index a39a828f8a..a30a1a4e9c 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -177,6 +177,7 @@ impl CallConv for X86_64SystemV { #[inline(always)] fn load_args<'a>( + buf: &mut Vec<'a, u8>, symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, @@ -231,6 +232,29 @@ impl CallConv 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 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 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 for X86_64WindowsFastcall { #[inline(always)] fn load_args<'a>( + _buf: &mut Vec<'a, u8>, symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, @@ -535,9 +586,18 @@ impl CallConv 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 for X86_64WindowsFastcall { )); } } - i += 1; } else { base_offset += match layout { Layout::Builtin(single_register_builtins!()) => 8, @@ -580,7 +639,6 @@ impl CallConv for X86_64WindowsFastcall { ret_layout: &Layout<'a>, ) -> Result { 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 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 for X86_64WindowsFastcall { ) } } - reg_i += 1; } else { // Load the value to the stack. match symbol_map @@ -651,7 +708,7 @@ impl CallConv 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 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 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!( diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 010fa066d3..5ef03a3a42 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -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)), } } diff --git a/compiler/gen_dev/tests/dev_str.rs b/compiler/gen_dev/tests/dev_str.rs index c03240a089..19da650a89 100644 --- a/compiler/gen_dev/tests/dev_str.rs +++ b/compiler/gen_dev/tests/dev_str.rs @@ -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() { From e546ec0c37137be31ab4e3c0c5f4ca3a0127fff8 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 18 Sep 2021 16:29:26 -0700 Subject: [PATCH 067/136] Add builtins to precompiled host --- compiler/build/src/link.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 40a8f84cbf..2d3517ee60 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -1,6 +1,7 @@ use crate::target::arch_str; #[cfg(feature = "llvm")] use libloading::{Error, Library}; +use roc_builtins::bitcode; #[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; use std::collections::HashMap; @@ -93,7 +94,12 @@ pub fn build_zig_host_native( .env("PATH", env_path) .env("HOME", env_home); if let Some(shared_lib_path) = shared_lib_path { - command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); + command.args(&[ + "build-exe", + "-fPIE", + shared_lib_path.to_str().unwrap(), + bitcode::OBJ_PATH, + ]); } else { command.args(&["build-obj", "-fPIC"]); } @@ -109,7 +115,6 @@ pub fn build_zig_host_native( // include libc "--library", "c", - "--strip", // cross-compile? "-target", target, @@ -178,7 +183,12 @@ pub fn build_zig_host_native( .env("PATH", &env_path) .env("HOME", &env_home); if let Some(shared_lib_path) = shared_lib_path { - command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); + command.args(&[ + "build-exe", + "-fPIE", + shared_lib_path.to_str().unwrap(), + bitcode::OBJ_PATH, + ]); } else { command.args(&["build-obj", "-fPIC"]); } @@ -197,7 +207,6 @@ pub fn build_zig_host_native( // include libc "--library", "c", - "--strip", ]); if matches!(opt_level, OptLevel::Optimize) { command.args(&["-O", "ReleaseSafe"]); @@ -274,6 +283,7 @@ pub fn build_c_host_native( if let Some(shared_lib_path) = shared_lib_path { command.args(&[ shared_lib_path.to_str().unwrap(), + bitcode::OBJ_PATH, "-fPIE", "-pie", "-lm", From 95f29c4d5b369d132391caa0760e5b8003436d79 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 08:35:34 -0700 Subject: [PATCH 068/136] Remove stale comment about F16 --- compiler/gen_dev/src/generic64/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index eb5a79150e..008633c202 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1513,8 +1513,6 @@ macro_rules! single_register_integers { #[macro_export] macro_rules! single_register_floats { () => { - // Float16 is explicitly ignored because it is not supported by must hardware and may require special exceptions. - // Builtin::Float16 | Builtin::Float32 | Builtin::Float64 }; } From c334e4d3e5738ce41896f90851374cde3c94a66d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 08:54:41 -0700 Subject: [PATCH 069/136] Export zig builtin version of __muloti4 as weak so it doesn't conflict if it already exists --- compiler/builtins/bitcode/src/main.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 69bf1ca5ac..8fe350f16d 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -164,7 +164,12 @@ test "" { // https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig // // Thank you Zig Contributors! -export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 { + +// Export it as weak incase it is alreadly linked in by something else. +comptime { + @export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak }); +} +fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 { // @setRuntimeSafety(std.builtin.is_test); const min = @bitCast(i128, @as(u128, 1 << (128 - 1))); From 23e8f6c68788f30ba7da3854e9745ec26992e767 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 21:53:41 +0200 Subject: [PATCH 070/136] only introduce rigid once! --- compiler/constrain/src/expr.rs | 13 ++++- compiler/test_gen/src/gen_primitives.rs | 74 +++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 77b8a3c82c..5584779f03 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -9,7 +9,7 @@ use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; use roc_can::expr::{Field, WhenBranch}; use roc_can::pattern::Pattern; -use roc_collections::all::{ImMap, Index, SendMap}; +use roc_collections::all::{ImMap, Index, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Located, Region}; @@ -1438,13 +1438,15 @@ fn instantiate_rigids( annotation: &Type, introduced_vars: &IntroducedVariables, new_rigids: &mut Vec, - ftv: &mut ImMap, + ftv: &mut ImMap, // rigids defined before the current annotation loc_pattern: &Located, headers: &mut SendMap>, ) -> Type { let mut annotation = annotation.clone(); let mut rigid_substitution: ImMap = ImMap::default(); + let outside_rigids: MutSet = ftv.values().copied().collect(); + for (name, var) in introduced_vars.var_by_name.iter() { if let Some(existing_rigid) = ftv.get(name) { rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); @@ -1464,7 +1466,12 @@ fn instantiate_rigids( &Located::at(loc_pattern.region, annotation.clone()), ) { for (symbol, loc_type) in new_headers { - new_rigids.extend(loc_type.value.variables()); + for var in loc_type.value.variables() { + // a rigid is only new if this annotation is the first occurence of this rigid + if !outside_rigids.contains(&var) { + new_rigids.push(var); + } + } headers.insert(symbol, loc_type); } } diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index d183b95d0b..1de82fa5f7 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -2906,3 +2906,77 @@ fn do_pass_bool_byte_closure_layout() { RocStr ); } + +#[test] +fn nested_rigid_list() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + foo : List a -> List a + foo = \list -> + p2 : List a + p2 = list + + p2 + + main = + when foo [] is + _ -> "hello world" + "# + ), + RocStr::from_slice(b"hello world"), + RocStr + ); +} + +#[test] +fn nested_rigid_alias() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + Identity a : [ @Identity a ] + + foo : Identity a -> Identity a + foo = \list -> + p2 : Identity a + p2 = list + + p2 + + main = + when foo (@Identity "foo") is + _ -> "hello world" + "# + ), + RocStr::from_slice(b"hello world"), + RocStr + ); +} + +#[test] +fn nested_rigid_tag_union() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + foo : [ @Identity a ] -> [ @Identity a ] + foo = \list -> + p2 : [ @Identity a ] + p2 = list + + p2 + + main = + when foo (@Identity "foo") is + _ -> "hello world" + "# + ), + RocStr::from_slice(b"hello world"), + RocStr + ); +} From 72194b87dfa27d83d49d1dfcb07830a45ab75f04 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 21:58:25 +0200 Subject: [PATCH 071/136] fix typo --- compiler/constrain/src/expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 5584779f03..4e920147d0 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1467,7 +1467,7 @@ fn instantiate_rigids( ) { for (symbol, loc_type) in new_headers { for var in loc_type.value.variables() { - // a rigid is only new if this annotation is the first occurence of this rigid + // a rigid is only new if this annotation is the first occurrence of this rigid if !outside_rigids.contains(&var) { new_rigids.push(var); } From eae8a2ea372576912d9bc866d5b96bb4763f00f3 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 13:43:00 -0700 Subject: [PATCH 072/136] Add surgical linker to cli_run tests that don't use rust --- cli/tests/cli_run.rs | 28 ++++++++++++++++++++++++++-- examples/hello-rust/.gitignore | 2 +- examples/hello-rust/Hello.roc | 2 +- linker/README.md | 1 - linker/src/lib.rs | 1 + 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index aa7020700f..6b0f34780b 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -18,6 +18,13 @@ mod cli_run { #[cfg(not(debug_assertions))] use roc_collections::all::MutMap; + #[cfg(target_os = "linux")] + const TEST_SURGICAL_LINKER: bool = true; + + // Surgical linker currently only supports linux. + #[cfg(not(target_os = "linux"))] + const TEST_SURGICAL_LINKER: bool = false; + #[cfg(not(target_os = "macos"))] const ALLOW_VALGRIND: bool = true; @@ -136,7 +143,6 @@ mod cli_run { ); } } - /// This macro does two things. /// /// First, it generates and runs a separate test for each of the given @@ -184,6 +190,24 @@ mod cli_run { example.expected_ending, example.use_valgrind, ); + + // Also check with the surgical linker. + + if TEST_SURGICAL_LINKER { + if matches!(example.executable_filename, "echo" | "hello-rust") { + eprintln!("WARNING: skipping testing example {} with surgical linking because rust is currently not supported!", example.filename); + return; + } + + check_output_with_stdin( + &file_name, + example.stdin, + example.executable_filename, + &["--roc-linker"], + example.expected_ending, + example.use_valgrind, + ); + } } )* @@ -228,7 +252,7 @@ mod cli_run { }, hello_rust:"hello-rust" => Example { filename: "Hello.roc", - executable_filename: "hello-world", + executable_filename: "hello-rust", stdin: &[], expected_ending:"Hello, World!\n", use_valgrind: true, diff --git a/examples/hello-rust/.gitignore b/examples/hello-rust/.gitignore index 6b820fd903..8485821d7c 100644 --- a/examples/hello-rust/.gitignore +++ b/examples/hello-rust/.gitignore @@ -1 +1 @@ -hello-world +hello-rust diff --git a/examples/hello-rust/Hello.roc b/examples/hello-rust/Hello.roc index d78f48ff19..cd7092308d 100644 --- a/examples/hello-rust/Hello.roc +++ b/examples/hello-rust/Hello.roc @@ -1,4 +1,4 @@ -app "hello-world" +app "hello-rust" packages { base: "platform" } imports [] provides [ main ] to base diff --git a/linker/README.md b/linker/README.md index 0d1e2d77ee..d65d68e14b 100644 --- a/linker/README.md +++ b/linker/README.md @@ -31,7 +31,6 @@ This linker is run in 2 phases: preprocessing and surigical linking. ## TODO (In a lightly prioritized order) -- Run CLI tests and/or benchmarks with the Roc Linker. - Test with an executable completely generated by Cargo (It will hopefully work out of the box like zig). - Add Macho support - Honestly should be almost exactly the same code. diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 1a043fbce1..3fa53e1bbd 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -199,6 +199,7 @@ fn generate_dynamic_lib( // TODO properly generate this list. for name in &[ format!("roc__{}_1_exposed", sym), + format!("roc__{}_1_exposed_generic", sym), format!("roc__{}_1_Fx_caller", sym), format!("roc__{}_1_Fx_size", sym), format!("roc__{}_1_Fx_result_size", sym), From 411ed58eecaaa4d17ef4a5474b3c926c8b3fa48d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 21:37:07 -0700 Subject: [PATCH 073/136] Fix rust host with surgical linking --- cli/tests/cli_run.rs | 5 - compiler/build/src/link.rs | 29 +- examples/cli/platform/Cargo.toml | 10 +- examples/cli/platform/build.rs | 4 + examples/cli/platform/host.c | 6 +- examples/cli/platform/src/lib.rs | 26 +- examples/cli/platform/src/main.rs | 64 +++ examples/hello-rust/platform/Cargo.toml | 9 +- examples/hello-rust/platform/build.rs | 4 + examples/hello-rust/platform/host.c | 11 +- examples/hello-rust/platform/src/lib.rs | 20 +- examples/hello-rust/platform/src/main.rs | 51 ++ linker/README.md | 2 +- linker/src/lib.rs | 87 ++- linker/src/metadata.rs | 8 +- ult = result ^ roc_memcpy_ptr | 651 +++++++++++++++++++++++ 16 files changed, 920 insertions(+), 67 deletions(-) create mode 100644 examples/cli/platform/build.rs create mode 100644 examples/cli/platform/src/main.rs create mode 100644 examples/hello-rust/platform/build.rs create mode 100644 examples/hello-rust/platform/src/main.rs create mode 100644 ult = result ^ roc_memcpy_ptr diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 6b0f34780b..718f9df4bf 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -194,11 +194,6 @@ mod cli_run { // Also check with the surgical linker. if TEST_SURGICAL_LINKER { - if matches!(example.executable_filename, "echo" | "hello-rust") { - eprintln!("WARNING: skipping testing example {} with surgical linking because rust is currently not supported!", example.filename); - return; - } - check_output_with_stdin( &file_name, example.stdin, diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 40a8f84cbf..4764493911 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -370,7 +370,7 @@ pub fn rebuild_host( } else if cargo_host_src.exists() { // Compile and link Cargo.toml, if it exists let cargo_dir = host_input_path.parent().unwrap(); - let libhost_dir = + let cargo_out_dir = cargo_dir .join("target") .join(if matches!(opt_level, OptLevel::Optimize) { @@ -378,30 +378,29 @@ pub fn rebuild_host( } else { "debug" }); - let libhost = libhost_dir.join("libhost.a"); let mut command = Command::new("cargo"); command.arg("build").current_dir(cargo_dir); if matches!(opt_level, OptLevel::Optimize) { command.arg("--release"); } + let source_file = if shared_lib_path.is_some() { + command.args(&["--bin", "host"]); + "src/main.rs" + } else { + command.arg("--lib"); + "src/lib.rs" + }; let output = command.output().unwrap(); - validate_output("src/lib.rs", "cargo build", output); + validate_output(source_file, "cargo build", output); - // Cargo hosts depend on a c wrapper for the api. Compile host.c as well. if shared_lib_path.is_some() { - // If compiling to executable, let c deal with linking as well. - let output = build_c_host_native( - &env_path, - &env_home, - host_dest_native.to_str().unwrap(), - &[c_host_src.to_str().unwrap(), libhost.to_str().unwrap()], - opt_level, - shared_lib_path, - ); - validate_output("host.c", "clang", output); + // For surgical linking, just copy the dynamically linked rust app. + std::fs::copy(cargo_out_dir.join("host"), host_dest_native).unwrap(); } else { + // Cargo hosts depend on a c wrapper for the api. Compile host.c as well. + let output = build_c_host_native( &env_path, &env_home, @@ -418,7 +417,7 @@ pub fn rebuild_host( .args(&[ "-r", "-L", - libhost_dir.to_str().unwrap(), + cargo_out_dir.to_str().unwrap(), c_host_dest.to_str().unwrap(), "-lhost", "-o", diff --git a/examples/cli/platform/Cargo.toml b/examples/cli/platform/Cargo.toml index ad2bc7c449..eba1dfa680 100644 --- a/examples/cli/platform/Cargo.toml +++ b/examples/cli/platform/Cargo.toml @@ -5,8 +5,16 @@ authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" +links = "app" + [lib] -crate-type = ["staticlib"] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" [dependencies] roc_std = { path = "../../../roc_std" } diff --git a/examples/cli/platform/build.rs b/examples/cli/platform/build.rs new file mode 100644 index 0000000000..73159e387c --- /dev/null +++ b/examples/cli/platform/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-lib=dylib=app"); + println!("cargo:rustc-link-search=."); +} diff --git a/examples/cli/platform/host.c b/examples/cli/platform/host.c index 0378c69589..645d900c8e 100644 --- a/examples/cli/platform/host.c +++ b/examples/cli/platform/host.c @@ -1,7 +1,3 @@ -#include - extern int rust_main(); -int main() { - return rust_main(); -} +int main() { return rust_main(); } diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs index 2b24da5ff7..d313d6f424 100644 --- a/examples/cli/platform/src/lib.rs +++ b/examples/cli/platform/src/lib.rs @@ -27,12 +27,12 @@ extern "C" { } #[no_mangle] -pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { libc::malloc(size) } #[no_mangle] -pub unsafe fn roc_realloc( +pub unsafe extern "C" fn roc_realloc( c_ptr: *mut c_void, new_size: usize, _old_size: usize, @@ -42,12 +42,12 @@ pub unsafe fn roc_realloc( } #[no_mangle] -pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { libc::free(c_ptr) } #[no_mangle] -pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { match tag_id { 0 => { let slice = CStr::from_ptr(c_ptr as *const c_char); @@ -60,7 +60,17 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { } #[no_mangle] -pub fn rust_main() -> isize { +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} + +#[no_mangle] +pub extern "C" fn rust_main() -> isize { let size = unsafe { roc_main_size() } as usize; let layout = Layout::array::(size).unwrap(); @@ -81,7 +91,7 @@ pub fn rust_main() -> isize { 0 } -unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { +unsafe extern "C" fn call_the_closure(closure_data_ptr: *const u8) -> i64 { let size = size_Fx_result() as usize; let layout = Layout::array::(size).unwrap(); let buffer = std::alloc::alloc(layout) as *mut u8; @@ -99,7 +109,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { } #[no_mangle] -pub fn roc_fx_getLine() -> RocStr { +pub extern "C" fn roc_fx_getLine() -> RocStr { use std::io::{self, BufRead}; let stdin = io::stdin(); @@ -109,7 +119,7 @@ pub fn roc_fx_getLine() -> RocStr { } #[no_mangle] -pub fn roc_fx_putLine(line: RocStr) -> () { +pub extern "C" fn roc_fx_putLine(line: RocStr) -> () { let bytes = line.as_slice(); let string = unsafe { std::str::from_utf8_unchecked(bytes) }; println!("{}", string); diff --git a/examples/cli/platform/src/main.rs b/examples/cli/platform/src/main.rs new file mode 100644 index 0000000000..0d2ec6edb1 --- /dev/null +++ b/examples/cli/platform/src/main.rs @@ -0,0 +1,64 @@ +#![allow(non_snake_case)] + +use core::ffi::c_void; +use roc_std::RocStr; + +fn main() { + let mut result = host::rust_main(); + // This is stupid code that does nothing to avoid rust optimizing functions that roc needs away. + if result == 0x1234_5678_9ABC_DEF0 { + let roc_alloc_ptr: isize = unsafe { + std::mem::transmute( + host::roc_alloc as *const unsafe extern "C" fn(usize, u32) -> *mut c_void, + ) + }; + let roc_realloc_ptr: isize = unsafe { + std::mem::transmute( + host::roc_realloc + as *const unsafe extern "C" fn(*mut c_void, usize, usize, u32) -> *mut c_void, + ) + }; + let roc_dealloc_ptr: isize = unsafe { + std::mem::transmute(host::roc_dealloc as *const unsafe extern "C" fn(*mut c_void, u32)) + }; + let roc_panic_ptr: isize = unsafe { + std::mem::transmute(host::roc_panic as *const unsafe extern "C" fn(*mut c_void, u32)) + }; + let roc_memcpy_ptr: isize = unsafe { + std::mem::transmute( + host::roc_memcpy + as *const unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void, + ) + }; + let roc_memset_ptr: isize = unsafe { + std::mem::transmute( + host::roc_memset + as *const unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void, + ) + }; + let roc_fx_putLine_ptr: isize = unsafe { + std::mem::transmute(host::roc_fx_putLine as *const extern "C" fn(line: RocStr) -> ()) + }; + let roc_fx_getLine_ptr: isize = unsafe { + std::mem::transmute(host::roc_fx_getLine as *const extern "C" fn() -> RocStr) + }; + // I really want to use the equivalent of std::hint::black_box, but it is expirimental. + result = result ^ roc_alloc_ptr; + result = result ^ roc_realloc_ptr; + result = result ^ roc_dealloc_ptr; + result = result ^ roc_panic_ptr; + result = result ^ roc_memcpy_ptr; + result = result ^ roc_memset_ptr; + result = result ^ roc_fx_putLine_ptr; + result = result ^ roc_fx_getLine_ptr; + result = result ^ roc_alloc_ptr; + result = result ^ roc_realloc_ptr; + result = result ^ roc_dealloc_ptr; + result = result ^ roc_panic_ptr; + result = result ^ roc_memcpy_ptr; + result = result ^ roc_memset_ptr; + result = result ^ roc_fx_putLine_ptr; + result = result ^ roc_fx_getLine_ptr; + } + std::process::exit(result as i32); +} diff --git a/examples/hello-rust/platform/Cargo.toml b/examples/hello-rust/platform/Cargo.toml index ad2bc7c449..72f534c88e 100644 --- a/examples/hello-rust/platform/Cargo.toml +++ b/examples/hello-rust/platform/Cargo.toml @@ -4,9 +4,16 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" +links = "app" [lib] -crate-type = ["staticlib"] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" [dependencies] roc_std = { path = "../../../roc_std" } diff --git a/examples/hello-rust/platform/build.rs b/examples/hello-rust/platform/build.rs new file mode 100644 index 0000000000..73159e387c --- /dev/null +++ b/examples/hello-rust/platform/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-lib=dylib=app"); + println!("cargo:rustc-link-search=."); +} diff --git a/examples/hello-rust/platform/host.c b/examples/hello-rust/platform/host.c index 9b91965724..b9214bcf33 100644 --- a/examples/hello-rust/platform/host.c +++ b/examples/hello-rust/platform/host.c @@ -1,12 +1,3 @@ -#include -#include - extern int rust_main(); -int main() { return rust_main(); } - -void *roc_memcpy(void *dest, const void *src, size_t n) { - return memcpy(dest, src, n); -} - -void *roc_memset(void *str, int c, size_t n) { return memset(str, c, n); } \ No newline at end of file +int main() { return rust_main(); } \ No newline at end of file diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs index 6a78b4db0c..d81192b939 100644 --- a/examples/hello-rust/platform/src/lib.rs +++ b/examples/hello-rust/platform/src/lib.rs @@ -12,12 +12,12 @@ extern "C" { } #[no_mangle] -pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { return libc::malloc(size); } #[no_mangle] -pub unsafe fn roc_realloc( +pub unsafe extern "C" fn roc_realloc( c_ptr: *mut c_void, new_size: usize, _old_size: usize, @@ -27,12 +27,12 @@ pub unsafe fn roc_realloc( } #[no_mangle] -pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { return libc::free(c_ptr); } #[no_mangle] -pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { match tag_id { 0 => { let slice = CStr::from_ptr(c_ptr as *const c_char); @@ -45,7 +45,17 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { } #[no_mangle] -pub fn rust_main() -> isize { +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} + +#[no_mangle] +pub extern "C" fn rust_main() -> isize { unsafe { let roc_str = roc_main(); diff --git a/examples/hello-rust/platform/src/main.rs b/examples/hello-rust/platform/src/main.rs new file mode 100644 index 0000000000..49cefd692f --- /dev/null +++ b/examples/hello-rust/platform/src/main.rs @@ -0,0 +1,51 @@ +use core::ffi::c_void; + +fn main() { + let mut result = host::rust_main(); + // This is stupid code that does nothing to avoid rust optimizing functions that roc needs away. + if result == 0x1234_5678_9ABC_DEF0 { + let roc_alloc_ptr: isize = unsafe { + std::mem::transmute( + host::roc_alloc as *const unsafe extern "C" fn(usize, u32) -> *mut c_void, + ) + }; + let roc_realloc_ptr: isize = unsafe { + std::mem::transmute( + host::roc_realloc + as *const unsafe extern "C" fn(*mut c_void, usize, usize, u32) -> *mut c_void, + ) + }; + let roc_dealloc_ptr: isize = unsafe { + std::mem::transmute(host::roc_dealloc as *const unsafe extern "C" fn(*mut c_void, u32)) + }; + let roc_panic_ptr: isize = unsafe { + std::mem::transmute(host::roc_panic as *const unsafe extern "C" fn(*mut c_void, u32)) + }; + let roc_memcpy_ptr: isize = unsafe { + std::mem::transmute( + host::roc_memcpy + as *const unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void, + ) + }; + let roc_memset_ptr: isize = unsafe { + std::mem::transmute( + host::roc_memset + as *const unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void, + ) + }; + // I really want to use the equivalent of std::hint::black_box, but it is expirimental. + result = result ^ roc_alloc_ptr; + result = result ^ roc_realloc_ptr; + result = result ^ roc_dealloc_ptr; + result = result ^ roc_panic_ptr; + result = result ^ roc_memcpy_ptr; + result = result ^ roc_memset_ptr; + result = result ^ roc_alloc_ptr; + result = result ^ roc_realloc_ptr; + result = result ^ roc_dealloc_ptr; + result = result ^ roc_panic_ptr; + result = result ^ roc_memcpy_ptr; + result = result ^ roc_memset_ptr; + } + std::process::exit(result as i32); +} diff --git a/linker/README.md b/linker/README.md index d65d68e14b..150eef59d3 100644 --- a/linker/README.md +++ b/linker/README.md @@ -31,7 +31,7 @@ This linker is run in 2 phases: preprocessing and surigical linking. ## TODO (In a lightly prioritized order) -- Test with an executable completely generated by Cargo (It will hopefully work out of the box like zig). +- Try to make rust hosts nicer. Currently making it expose functions is a huge pain. - Add Macho support - Honestly should be almost exactly the same code. This means we likely need to do a lot of refactoring to minimize the duplicate code. diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 3fa53e1bbd..15ec09084a 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -28,6 +28,7 @@ use target_lexicon::Triple; use tempfile::Builder; mod metadata; +use metadata::VirtualOffset; pub const CMD_PREPROCESS: &str = "preprocess"; pub const CMD_SURGERY: &str = "surgery"; @@ -196,7 +197,6 @@ fn generate_dynamic_lib( let text_section = out_object.section_id(write::StandardSection::Text); for sym in exposed_to_host { - // TODO properly generate this list. for name in &[ format!("roc__{}_1_exposed", sym), format!("roc__{}_1_exposed_generic", sym), @@ -317,7 +317,9 @@ fn preprocess_impl( for sym in exec_obj.symbols().filter(|sym| { sym.is_definition() && sym.name().is_ok() && sym.name().unwrap().starts_with("roc_") }) { - let name = sym.name().unwrap().to_string(); + // remove potentially trailing "@version". + let name = sym.name().unwrap().split("@").next().unwrap().to_string(); + // special exceptions for memcpy and memset. if &name == "roc_memcpy" { md.roc_symbol_vaddresses @@ -368,9 +370,6 @@ fn preprocess_impl( println!("PLT File Offset: {:+x}", plt_offset); } - // TODO: it looks like we may need to support global data host relocations. - // Rust host look to be using them by default instead of the plt. - // I think this is due to first linking into a static lib and then linking to the c wrapper. let plt_relocs = (match exec_obj.dynamic_relocations() { Some(relocs) => relocs, None => { @@ -380,7 +379,7 @@ fn preprocess_impl( } }) .map(|(_, reloc)| reloc) - .filter(|reloc| reloc.kind() == RelocationKind::Elf(7)); + .filter(|reloc| matches!(reloc.kind(), RelocationKind::Elf(7))); let app_syms: Vec = exec_obj .dynamic_symbols() @@ -388,6 +387,28 @@ fn preprocess_impl( sym.is_undefined() && sym.name().is_ok() && sym.name().unwrap().starts_with("roc_") }) .collect(); + + let got_app_syms: Vec<(String, usize)> = (match exec_obj.dynamic_relocations() { + Some(relocs) => relocs, + None => { + println!("Executable never calls any application functions."); + println!("No work to do. Probably an invalid input."); + return Ok(-1); + } + }) + .map(|(_, reloc)| reloc) + .filter(|reloc| matches!(reloc.kind(), RelocationKind::Elf(6))) + .map(|reloc| { + for symbol in app_syms.iter() { + if reloc.target() == RelocationTarget::Symbol(symbol.index()) { + return Some((symbol.name().unwrap().to_string(), symbol.index().0)); + } + } + None + }) + .filter_map(|x| x) + .collect(); + for sym in app_syms.iter() { let name = sym.name().unwrap().to_string(); md.app_functions.push(name.clone()); @@ -408,6 +429,7 @@ fn preprocess_impl( if reloc.target() == RelocationTarget::Symbol(symbol.index()) { let func_address = (i as u64 + 1) * PLT_ADDRESS_OFFSET + plt_address; let func_offset = (i as u64 + 1) * PLT_ADDRESS_OFFSET + plt_offset; + println!("{}", symbol.name().unwrap().to_string()); app_func_addresses.insert(func_address, symbol.name().unwrap()); md.plt_addresses.insert( symbol.name().unwrap().to_string(), @@ -537,7 +559,7 @@ fn preprocess_impl( .unwrap() .push(metadata::SurgeryEntry { file_offset: offset, - virtual_offset: inst.next_ip(), + virtual_offset: VirtualOffset::Relative(inst.next_ip()), size: op_size, }); } @@ -879,7 +901,7 @@ fn preprocess_impl( sec_offset as usize + md.added_byte_count as usize, sec_size as usize / mem::size_of::>(), ); - for rel in relocations.iter_mut() { + for (i, rel) in relocations.iter_mut().enumerate() { let r_offset = rel.r_offset.get(NativeEndian); if virtual_shift_start <= r_offset { rel.r_offset = endian::U64::new(LittleEndian, r_offset + md.added_byte_count); @@ -891,6 +913,28 @@ fn preprocess_impl( .set(LittleEndian, r_addend + md.added_byte_count as i64); } } + // If the relocation goes to a roc function, we need to surgically link it and change it to relative. + let r_type = rel.r_type(NativeEndian, false); + if r_type == elf::R_X86_64_GLOB_DAT { + let r_sym = rel.r_sym(NativeEndian, false); + for (name, index) in got_app_syms.iter() { + if *index as u32 == r_sym { + rel.set_r_info(LittleEndian, false, 0, elf::R_X86_64_RELATIVE); + let addend_addr = sec_offset as usize + + i * mem::size_of::>() + // This 16 skips the first 2 fields and gets to the addend field. + + 16; + md.surgeries + .get_mut(name) + .unwrap() + .push(metadata::SurgeryEntry { + file_offset: addend_addr as u64, + virtual_offset: VirtualOffset::Absolute, + size: 8, + }); + } + } + } } } @@ -1462,7 +1506,7 @@ fn surgery_impl( let dynsym_offset = md.dynamic_symbol_table_section_offset + md.added_byte_count; for func_name in md.app_functions { - let virt_offset = match app_func_vaddr_map.get(&func_name) { + let func_virt_offset = match app_func_vaddr_map.get(&func_name) { Some(offset) => *offset as u64, None => { println!("Function, {}, was not defined by the app", &func_name); @@ -1472,7 +1516,7 @@ fn surgery_impl( if verbose { println!( "Updating calls to {} to the address: {:+x}", - &func_name, virt_offset + &func_name, func_virt_offset ); } @@ -1480,11 +1524,13 @@ fn surgery_impl( if verbose { println!("\tPerforming surgery: {:+x?}", s); } + let surgery_virt_offset = match s.virtual_offset { + VirtualOffset::Relative(vs) => (vs + md.added_byte_count) as i64, + VirtualOffset::Absolute => 0, + }; match s.size { 4 => { - let target = (virt_offset as i64 - - (s.virtual_offset + md.added_byte_count) as i64) - as i32; + let target = (func_virt_offset as i64 - surgery_virt_offset) as i32; if verbose { println!("\tTarget Jump: {:+x}", target); } @@ -1493,6 +1539,16 @@ fn surgery_impl( ..(s.file_offset + md.added_byte_count) as usize + 4] .copy_from_slice(&data); } + 8 => { + let target = func_virt_offset as i64 - surgery_virt_offset; + if verbose { + println!("\tTarget Jump: {:+x}", target); + } + let data = target.to_le_bytes(); + exec_mmap[(s.file_offset + md.added_byte_count) as usize + ..(s.file_offset + md.added_byte_count) as usize + 8] + .copy_from_slice(&data); + } x => { println!("Surgery size not yet supported: {}", x); return Ok(-1); @@ -1506,7 +1562,8 @@ fn surgery_impl( let plt_off = (*plt_off + md.added_byte_count) as usize; let plt_vaddr = *plt_vaddr + md.added_byte_count; let jmp_inst_len = 5; - let target = (virt_offset as i64 - (plt_vaddr as i64 + jmp_inst_len as i64)) as i32; + let target = + (func_virt_offset as i64 - (plt_vaddr as i64 + jmp_inst_len as i64)) as i32; if verbose { println!("\tPLT: {:+x}, {:+x}", plt_off, plt_vaddr); println!("\tTarget Jump: {:+x}", target); @@ -1525,7 +1582,7 @@ fn surgery_impl( dynsym_offset as usize + *i as usize * mem::size_of::>(), ); sym.st_shndx = endian::U16::new(LittleEndian, new_text_section_index as u16); - sym.st_value = endian::U64::new(LittleEndian, virt_offset as u64); + sym.st_value = endian::U64::new(LittleEndian, func_virt_offset as u64); sym.st_size = endian::U64::new( LittleEndian, match app_func_size_map.get(&func_name) { diff --git a/linker/src/metadata.rs b/linker/src/metadata.rs index 16f06232cd..f24bf9a626 100644 --- a/linker/src/metadata.rs +++ b/linker/src/metadata.rs @@ -1,10 +1,16 @@ use roc_collections::all::MutMap; use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub enum VirtualOffset { + Absolute, + Relative(u64), +} + #[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct SurgeryEntry { pub file_offset: u64, - pub virtual_offset: u64, + pub virtual_offset: VirtualOffset, pub size: u8, } diff --git a/ult = result ^ roc_memcpy_ptr b/ult = result ^ roc_memcpy_ptr new file mode 100644 index 0000000000..88b26daf7c --- /dev/null +++ b/ult = result ^ roc_memcpy_ptr @@ -0,0 +1,651 @@ + +examples/hello-rust/platform/dynhost: file format elf64-x86-64 + +DYNAMIC RELOCATION RECORDS +OFFSET TYPE VALUE +000000000003f520 R_X86_64_RELATIVE *ABS*+0x00000000000112f0 +000000000003f528 R_X86_64_RELATIVE *ABS*+0x0000000000023b70 +000000000003f530 R_X86_64_RELATIVE *ABS*+0x0000000000011330 +000000000003f538 R_X86_64_RELATIVE *ABS*+0x00000000000113f0 +000000000003f550 R_X86_64_RELATIVE *ABS*+0x00000000000113d0 +000000000003f558 R_X86_64_RELATIVE *ABS*+0x00000000000113d0 +000000000003f560 R_X86_64_RELATIVE *ABS*+0x00000000000113e0 +000000000003f568 R_X86_64_RELATIVE *ABS*+0x0000000000006a10 +000000000003f580 R_X86_64_RELATIVE *ABS*+0x0000000000011560 +000000000003f598 R_X86_64_RELATIVE *ABS*+0x00000000000115f0 +000000000003f5a0 R_X86_64_RELATIVE *ABS*+0x0000000000011650 +000000000003f5b8 R_X86_64_RELATIVE *ABS*+0x0000000000011590 +000000000003f5c0 R_X86_64_RELATIVE *ABS*+0x0000000000011570 +000000000003f5c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f5e0 R_X86_64_RELATIVE *ABS*+0x0000000000015090 +000000000003f5e8 R_X86_64_RELATIVE *ABS*+0x0000000000014f80 +000000000003f5f0 R_X86_64_RELATIVE *ABS*+0x0000000000014f90 +000000000003f5f8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f610 R_X86_64_RELATIVE *ABS*+0x0000000000015080 +000000000003f618 R_X86_64_RELATIVE *ABS*+0x0000000000014ea0 +000000000003f620 R_X86_64_RELATIVE *ABS*+0x0000000000014fe0 +000000000003f628 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f640 R_X86_64_RELATIVE *ABS*+0x0000000000015100 +000000000003f648 R_X86_64_RELATIVE *ABS*+0x0000000000014d80 +000000000003f650 R_X86_64_RELATIVE *ABS*+0x0000000000015030 +000000000003f658 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f670 R_X86_64_RELATIVE *ABS*+0x0000000000011870 +000000000003f678 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +000000000003f690 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 +000000000003f6a8 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 +000000000003f6c0 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 +000000000003f6d8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f6f0 R_X86_64_RELATIVE *ABS*+0x00000000000116d0 +000000000003f6f8 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 +000000000003f710 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 +000000000003f728 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 +000000000003f740 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f758 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f770 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f788 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f7a0 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f7b8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f7d0 R_X86_64_RELATIVE *ABS*+0x000000000001d480 +000000000003f7d8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f7f0 R_X86_64_RELATIVE *ABS*+0x00000000000391b0 +000000000003f7f8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f810 R_X86_64_RELATIVE *ABS*+0x0000000000039190 +000000000003f818 R_X86_64_RELATIVE *ABS*+0x0000000000012420 +000000000003f830 R_X86_64_RELATIVE *ABS*+0x00000000000239d0 +000000000003f838 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f850 R_X86_64_RELATIVE *ABS*+0x000000000003e1c0 +000000000003f858 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f870 R_X86_64_RELATIVE *ABS*+0x0000000000011700 +000000000003f878 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f890 R_X86_64_RELATIVE *ABS*+0x0000000000011810 +000000000003f898 R_X86_64_RELATIVE *ABS*+0x0000000000007e14 +000000000003f8b0 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f8c8 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f8e0 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f8f8 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f910 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f928 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa +000000000003f940 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa +000000000003f958 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa +000000000003f970 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa +000000000003f988 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 +000000000003f9a0 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003f9b8 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003f9d0 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003f9e8 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003fa00 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003fa18 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003fa30 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003fa48 R_X86_64_RELATIVE *ABS*+0x0000000000007fe0 +000000000003fa60 R_X86_64_RELATIVE *ABS*+0x000000000000805d +000000000003fa78 R_X86_64_RELATIVE *ABS*+0x000000000000805d +000000000003fa90 R_X86_64_RELATIVE *ABS*+0x000000000000805d +000000000003faa8 R_X86_64_RELATIVE *ABS*+0x000000000000805d +000000000003fac0 R_X86_64_RELATIVE *ABS*+0x0000000000008184 +000000000003fad0 R_X86_64_RELATIVE *ABS*+0x00000000000081a8 +000000000003fae0 R_X86_64_RELATIVE *ABS*+0x000000000000816e +000000000003faf8 R_X86_64_RELATIVE *ABS*+0x00000000000081ab +000000000003fb08 R_X86_64_RELATIVE *ABS*+0x00000000000081cc +000000000003fb20 R_X86_64_RELATIVE *ABS*+0x00000000000081f4 +000000000003fb30 R_X86_64_RELATIVE *ABS*+0x00000000000081cc +000000000003fb48 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +000000000003fb58 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +000000000003fb68 R_X86_64_RELATIVE *ABS*+0x0000000000008336 +000000000003fb78 R_X86_64_RELATIVE *ABS*+0x0000000000008341 +000000000003fb88 R_X86_64_RELATIVE *ABS*+0x0000000000008342 +000000000003fb98 R_X86_64_RELATIVE *ABS*+0x000000000000835e +000000000003fbb0 R_X86_64_RELATIVE *ABS*+0x000000000000837b +000000000003fbc8 R_X86_64_RELATIVE *ABS*+0x000000000000837b +000000000003fbe0 R_X86_64_RELATIVE *ABS*+0x0000000000008394 +000000000003fbf0 R_X86_64_RELATIVE *ABS*+0x0000000000013010 +000000000003fc08 R_X86_64_RELATIVE *ABS*+0x000000000001ef40 +000000000003fc10 R_X86_64_RELATIVE *ABS*+0x0000000000011910 +000000000003fc18 R_X86_64_RELATIVE *ABS*+0x0000000000011b40 +000000000003fc20 R_X86_64_RELATIVE *ABS*+0x0000000000013010 +000000000003fc38 R_X86_64_RELATIVE *ABS*+0x000000000001eda0 +000000000003fc40 R_X86_64_RELATIVE *ABS*+0x0000000000011a20 +000000000003fc48 R_X86_64_RELATIVE *ABS*+0x0000000000011b00 +000000000003fc50 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fc68 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fc80 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fc98 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fcb0 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fcc8 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fce0 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fcf8 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fd10 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fd28 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003fd40 R_X86_64_RELATIVE *ABS*+0x000000000001fe50 +000000000003fd48 R_X86_64_RELATIVE *ABS*+0x0000000000011c00 +000000000003fd50 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fd68 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fd80 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fd98 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fdb0 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fdc8 R_X86_64_RELATIVE *ABS*+0x000000000000843b +000000000003fdd8 R_X86_64_RELATIVE *ABS*+0x0000000000013090 +000000000003fdf0 R_X86_64_RELATIVE *ABS*+0x00000000000207d0 +000000000003fdf8 R_X86_64_RELATIVE *ABS*+0x0000000000011b80 +000000000003fe00 R_X86_64_RELATIVE *ABS*+0x000000000000844c +000000000003fe10 R_X86_64_RELATIVE *ABS*+0x00000000000083a4 +000000000003fe20 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +000000000003fe30 R_X86_64_RELATIVE *ABS*+0x00000000000084e3 +000000000003fe40 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fe58 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fe70 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fe88 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fea0 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003feb8 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fed0 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fee8 R_X86_64_RELATIVE *ABS*+0x000000000000856f +000000000003fef8 R_X86_64_RELATIVE *ABS*+0x0000000000008584 +000000000003ff08 R_X86_64_RELATIVE *ABS*+0x0000000000008585 +000000000003ff18 R_X86_64_RELATIVE *ABS*+0x00000000000085a2 +000000000003ff28 R_X86_64_RELATIVE *ABS*+0x00000000000085b7 +000000000003ff38 R_X86_64_RELATIVE *ABS*+0x00000000000085c5 +000000000003ff48 R_X86_64_RELATIVE *ABS*+0x00000000000085e1 +000000000003ff58 R_X86_64_RELATIVE *ABS*+0x0000000000008605 +000000000003ff70 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003ff88 R_X86_64_RELATIVE *ABS*+0x00000000000249d0 +000000000003ff90 R_X86_64_RELATIVE *ABS*+0x0000000000024a30 +000000000003ff98 R_X86_64_RELATIVE *ABS*+0x0000000000024a90 +000000000003ffa0 R_X86_64_RELATIVE *ABS*+0x0000000000024aa0 +000000000003ffa8 R_X86_64_RELATIVE *ABS*+0x000000000001e550 +000000000003ffb0 R_X86_64_RELATIVE *ABS*+0x000000000001e640 +000000000003ffb8 R_X86_64_RELATIVE *ABS*+0x000000000001ec50 +000000000003ffc8 R_X86_64_RELATIVE *ABS*+0x0000000000012190 +000000000003ffe0 R_X86_64_RELATIVE *ABS*+0x000000000001e2c0 +000000000003ffe8 R_X86_64_RELATIVE *ABS*+0x000000000001e330 +000000000003fff0 R_X86_64_RELATIVE *ABS*+0x000000000001e4c0 +000000000003fff8 R_X86_64_RELATIVE *ABS*+0x000000000001e540 +0000000000040000 R_X86_64_RELATIVE *ABS*+0x000000000001e4d0 +0000000000040008 R_X86_64_RELATIVE *ABS*+0x000000000001e890 +0000000000040010 R_X86_64_RELATIVE *ABS*+0x000000000001eb00 +0000000000040020 R_X86_64_RELATIVE *ABS*+0x0000000000008150 +0000000000040030 R_X86_64_RELATIVE *ABS*+0x0000000000008636 +0000000000040040 R_X86_64_RELATIVE *ABS*+0x0000000000008645 +0000000000040050 R_X86_64_RELATIVE *ABS*+0x0000000000008584 +0000000000040060 R_X86_64_RELATIVE *ABS*+0x0000000000008648 +0000000000040070 R_X86_64_RELATIVE *ABS*+0x0000000000008605 +0000000000040088 R_X86_64_RELATIVE *ABS*+0x0000000000008605 +00000000000400a0 R_X86_64_RELATIVE *ABS*+0x0000000000012d90 +00000000000400b8 R_X86_64_RELATIVE *ABS*+0x0000000000022690 +00000000000400c0 R_X86_64_RELATIVE *ABS*+0x0000000000022790 +00000000000400c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +00000000000400e0 R_X86_64_RELATIVE *ABS*+0x0000000000022830 +00000000000400e8 R_X86_64_RELATIVE *ABS*+0x0000000000022880 +00000000000400f0 R_X86_64_RELATIVE *ABS*+0x0000000000012190 +0000000000040108 R_X86_64_RELATIVE *ABS*+0x00000000000116e0 +0000000000040110 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +0000000000040128 R_X86_64_RELATIVE *ABS*+0x00000000000116f0 +0000000000040130 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +0000000000040148 R_X86_64_RELATIVE *ABS*+0x00000000000228f0 +0000000000040150 R_X86_64_RELATIVE *ABS*+0x0000000000022950 +0000000000040158 R_X86_64_RELATIVE *ABS*+0x0000000000008696 +0000000000040168 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +0000000000040178 R_X86_64_RELATIVE *ABS*+0x00000000000086c8 +0000000000040188 R_X86_64_RELATIVE *ABS*+0x00000000000086f9 +0000000000040198 R_X86_64_RELATIVE *ABS*+0x0000000000007c20 +00000000000401a8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +00000000000401c0 R_X86_64_RELATIVE *ABS*+0x0000000000020a40 +00000000000401c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c20 +00000000000401d0 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +00000000000401e8 R_X86_64_RELATIVE *ABS*+0x0000000000020810 +00000000000401f0 R_X86_64_RELATIVE *ABS*+0x0000000000011bf0 +00000000000401f8 R_X86_64_RELATIVE *ABS*+0x0000000000008724 +0000000000040210 R_X86_64_RELATIVE *ABS*+0x0000000000008758 +0000000000040220 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +0000000000040230 R_X86_64_RELATIVE *ABS*+0x0000000000008379 +0000000000040240 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +0000000000040250 R_X86_64_RELATIVE *ABS*+0x00000000000087d0 +0000000000040260 R_X86_64_RELATIVE *ABS*+0x00000000000084da +0000000000040270 R_X86_64_RELATIVE *ABS*+0x0000000000006b40 +0000000000040280 R_X86_64_RELATIVE *ABS*+0x00000000000083a3 +0000000000040290 R_X86_64_RELATIVE *ABS*+0x0000000000008584 +00000000000402a0 R_X86_64_RELATIVE *ABS*+0x00000000000087fd +00000000000402b8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +00000000000402d0 R_X86_64_RELATIVE *ABS*+0x0000000000011780 +00000000000402d8 R_X86_64_RELATIVE *ABS*+0x000000000000884a +00000000000402f0 R_X86_64_RELATIVE *ABS*+0x000000000000887a +0000000000040308 R_X86_64_RELATIVE *ABS*+0x000000000000887a +0000000000040320 R_X86_64_RELATIVE *ABS*+0x00000000000088a6 +0000000000040330 R_X86_64_RELATIVE *ABS*+0x0000000000008902 +0000000000040348 R_X86_64_RELATIVE *ABS*+0x0000000000008902 +0000000000040360 R_X86_64_RELATIVE *ABS*+0x000000000000894d +0000000000040370 R_X86_64_RELATIVE *ABS*+0x0000000000008956 +0000000000040380 R_X86_64_RELATIVE *ABS*+0x0000000000008971 +0000000000040390 R_X86_64_RELATIVE *ABS*+0x000000000000897f +00000000000403a0 R_X86_64_RELATIVE *ABS*+0x00000000000089a8 +00000000000403b8 R_X86_64_RELATIVE *ABS*+0x00000000000089d2 +00000000000403c8 R_X86_64_RELATIVE *ABS*+0x00000000000089a8 +00000000000403e0 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 +00000000000403f8 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 +0000000000040410 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 +0000000000040428 R_X86_64_RELATIVE *ABS*+0x0000000000008a23 +0000000000040440 R_X86_64_RELATIVE *ABS*+0x0000000000008a96 +0000000000040458 R_X86_64_RELATIVE *ABS*+0x0000000000008abb +0000000000040470 R_X86_64_RELATIVE *ABS*+0x0000000000008af7 +0000000000040488 R_X86_64_RELATIVE *ABS*+0x0000000000008af7 +00000000000404a0 R_X86_64_RELATIVE *ABS*+0x0000000000008b2d +00000000000404b8 R_X86_64_RELATIVE *ABS*+0x000000000002d900 +00000000000404d0 R_X86_64_RELATIVE *ABS*+0x000000000002dbc0 +00000000000404d8 R_X86_64_RELATIVE *ABS*+0x000000000002dbc0 +00000000000404e0 R_X86_64_RELATIVE *ABS*+0x000000000002d880 +00000000000404e8 R_X86_64_RELATIVE *ABS*+0x000000000002d900 +0000000000040500 R_X86_64_RELATIVE *ABS*+0x000000000002dbd0 +0000000000040508 R_X86_64_RELATIVE *ABS*+0x000000000002dbd0 +0000000000040510 R_X86_64_RELATIVE *ABS*+0x000000000002d870 +0000000000040518 R_X86_64_RELATIVE *ABS*+0x0000000000008c57 +0000000000040530 R_X86_64_RELATIVE *ABS*+0x0000000000008c57 +0000000000040548 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040560 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040578 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040590 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000405a8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000405c0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000405d8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000405f0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040608 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040620 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040638 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040650 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040668 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040680 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040698 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000406b0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000406c8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000406e0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000406f8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040710 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040728 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040740 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040758 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040770 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040788 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000407a0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000407b8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000407d0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000407e8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040800 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040818 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040830 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040848 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040860 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040878 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040890 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000408a8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000408c0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000408d8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000408f0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040908 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040920 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040938 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040950 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040968 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040980 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040998 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000409b0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000409c8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000409e0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000409f8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040a10 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040a28 R_X86_64_RELATIVE *ABS*+0x0000000000008d92 +0000000000040a40 R_X86_64_RELATIVE *ABS*+0x0000000000008d92 +0000000000040a58 R_X86_64_RELATIVE *ABS*+0x00000000000315f0 +0000000000040a70 R_X86_64_RELATIVE *ABS*+0x0000000000031590 +0000000000040a78 R_X86_64_RELATIVE *ABS*+0x000000000000904b +0000000000040a90 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040aa8 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040ac0 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040ad8 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040af0 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040b08 R_X86_64_RELATIVE *ABS*+0x0000000000009260 +0000000000040b20 R_X86_64_RELATIVE *ABS*+0x0000000000009260 +0000000000040b38 R_X86_64_RELATIVE *ABS*+0x0000000000009260 +0000000000040b50 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040b68 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040b80 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040b98 R_X86_64_RELATIVE *ABS*+0x00000000000331f0 +0000000000040bb0 R_X86_64_RELATIVE *ABS*+0x000000000003d290 +0000000000040bb8 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040bd0 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040be8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c00 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c18 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c30 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c48 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c60 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c78 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c90 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040ca8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040cc0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040cd8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040cf0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d08 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d20 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d38 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d50 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d68 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d80 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d98 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040db0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040dc8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040de0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040df8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e10 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e28 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e40 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e58 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e70 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e88 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040ea0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040eb8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040ed0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040ee8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040f00 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040f18 R_X86_64_RELATIVE *ABS*+0x000000000000979a +0000000000040f28 R_X86_64_RELATIVE *ABS*+0x00000000000097d8 +0000000000040f40 R_X86_64_RELATIVE *ABS*+0x00000000000097d8 +0000000000040f58 R_X86_64_RELATIVE *ABS*+0x0000000000009828 +0000000000040f70 R_X86_64_RELATIVE *ABS*+0x0000000000009939 +0000000000040f80 R_X86_64_RELATIVE *ABS*+0x0000000000009975 +0000000000040f98 R_X86_64_RELATIVE *ABS*+0x0000000000009990 +0000000000040fa8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 +0000000000040fb8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000040fd0 R_X86_64_RELATIVE *ABS*+0x0000000000039180 +0000000000040fd8 R_X86_64_RELATIVE *ABS*+0x00000000000099e2 +0000000000040fe8 R_X86_64_RELATIVE *ABS*+0x00000000000099e3 +0000000000040ff8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 +0000000000041008 R_X86_64_RELATIVE *ABS*+0x00000000000099e6 +0000000000041018 R_X86_64_RELATIVE *ABS*+0x00000000000099e6 +0000000000041028 R_X86_64_RELATIVE *ABS*+0x0000000000007c80 +0000000000041038 R_X86_64_RELATIVE *ABS*+0x00000000000099e7 +0000000000041048 R_X86_64_RELATIVE *ABS*+0x0000000000009a04 +0000000000041058 R_X86_64_RELATIVE *ABS*+0x0000000000009a1d +0000000000041068 R_X86_64_RELATIVE *ABS*+0x0000000000009a2f +0000000000041078 R_X86_64_RELATIVE *ABS*+0x0000000000009a3b +0000000000041088 R_X86_64_RELATIVE *ABS*+0x0000000000009a04 +0000000000041098 R_X86_64_RELATIVE *ABS*+0x0000000000009a1d +00000000000410a8 R_X86_64_RELATIVE *ABS*+0x0000000000009a2f +00000000000410b8 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e +00000000000410c8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 +00000000000410d8 R_X86_64_RELATIVE *ABS*+0x0000000000009a3f +00000000000410e8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041100 R_X86_64_RELATIVE *ABS*+0x0000000000039880 +0000000000041108 R_X86_64_RELATIVE *ABS*+0x000000000003a060 +0000000000041110 R_X86_64_RELATIVE *ABS*+0x000000000003a140 +0000000000041118 R_X86_64_RELATIVE *ABS*+0x0000000000007ce0 +0000000000041130 R_X86_64_RELATIVE *ABS*+0x0000000000007ce0 +0000000000041148 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041160 R_X86_64_RELATIVE *ABS*+0x000000000003deb0 +0000000000041168 R_X86_64_RELATIVE *ABS*+0x0000000000009a56 +0000000000041180 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041198 R_X86_64_RELATIVE *ABS*+0x000000000003a180 +00000000000411a0 R_X86_64_RELATIVE *ABS*+0x000000000003a190 +00000000000411a8 R_X86_64_RELATIVE *ABS*+0x000000000003a270 +00000000000411b0 R_X86_64_RELATIVE *ABS*+0x0000000000007c60 +00000000000411c8 R_X86_64_RELATIVE *ABS*+0x0000000000009b3b +00000000000411d8 R_X86_64_RELATIVE *ABS*+0x0000000000009b4d +00000000000411e8 R_X86_64_RELATIVE *ABS*+0x0000000000006c30 +00000000000411f8 R_X86_64_RELATIVE *ABS*+0x0000000000009b4d +0000000000041208 R_X86_64_RELATIVE *ABS*+0x0000000000009b6f +0000000000041218 R_X86_64_RELATIVE *ABS*+0x0000000000009b85 +0000000000041228 R_X86_64_RELATIVE *ABS*+0x0000000000009b92 +0000000000041238 R_X86_64_RELATIVE *ABS*+0x0000000000009ba7 +0000000000041248 R_X86_64_RELATIVE *ABS*+0x0000000000009a52 +0000000000041258 R_X86_64_RELATIVE *ABS*+0x0000000000009bfc +0000000000041270 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +0000000000041288 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +00000000000412a0 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +00000000000412b8 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +00000000000412d0 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +00000000000412e8 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +0000000000041300 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041318 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041330 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041348 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041360 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041378 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041390 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +00000000000413a8 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +00000000000413c0 R_X86_64_RELATIVE *ABS*+0x0000000000009d83 +00000000000413d0 R_X86_64_RELATIVE *ABS*+0x0000000000009d8e +00000000000413e0 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e +00000000000413f0 R_X86_64_RELATIVE *ABS*+0x0000000000009da4 +0000000000041400 R_X86_64_RELATIVE *ABS*+0x0000000000007f18 +0000000000041410 R_X86_64_RELATIVE *ABS*+0x0000000000006b50 +0000000000041420 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e +0000000000041430 R_X86_64_RELATIVE *ABS*+0x0000000000009d83 +0000000000041440 R_X86_64_RELATIVE *ABS*+0x0000000000009db2 +0000000000041450 R_X86_64_RELATIVE *ABS*+0x0000000000008120 +0000000000041460 R_X86_64_RELATIVE *ABS*+0x0000000000009dd8 +0000000000041470 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e +0000000000041480 R_X86_64_RELATIVE *ABS*+0x0000000000009dde +0000000000041498 R_X86_64_RELATIVE *ABS*+0x0000000000009dde +00000000000414b0 R_X86_64_RELATIVE *ABS*+0x000000000000a34a +00000000000414c8 R_X86_64_RELATIVE *ABS*+0x000000000000a34a +00000000000414e0 R_X86_64_RELATIVE *ABS*+0x000000000000a34a +00000000000414f8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041510 R_X86_64_RELATIVE *ABS*+0x000000000003e130 +0000000000041518 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041530 R_X86_64_RELATIVE *ABS*+0x000000000003de00 +0000000000041538 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041550 R_X86_64_RELATIVE *ABS*+0x000000000003e030 +0000000000041780 R_X86_64_RELATIVE *ABS*+0x00000000000114b0 +0000000000041788 R_X86_64_RELATIVE *ABS*+0x0000000000011470 +0000000000041790 R_X86_64_RELATIVE *ABS*+0x0000000000011460 +0000000000041798 R_X86_64_RELATIVE *ABS*+0x0000000000011490 +00000000000417a0 R_X86_64_RELATIVE *ABS*+0x0000000000011480 +00000000000417a8 R_X86_64_RELATIVE *ABS*+0x00000000000114a0 +00000000000417b0 R_X86_64_RELATIVE *ABS*+0x000000000001fe20 +00000000000417b8 R_X86_64_RELATIVE *ABS*+0x0000000000022dc0 +00000000000417c0 R_X86_64_RELATIVE *ABS*+0x0000000000011690 +00000000000417f8 R_X86_64_RELATIVE *ABS*+0x0000000000011660 +0000000000041800 R_X86_64_RELATIVE *ABS*+0x0000000000011680 +0000000000041810 R_X86_64_RELATIVE *ABS*+0x0000000000039480 +0000000000041818 R_X86_64_RELATIVE *ABS*+0x000000000001fe40 +0000000000041820 R_X86_64_RELATIVE *ABS*+0x0000000000011400 +0000000000041828 R_X86_64_RELATIVE *ABS*+0x0000000000038ad0 +0000000000041830 R_X86_64_RELATIVE *ABS*+0x0000000000022970 +0000000000041838 R_X86_64_RELATIVE *ABS*+0x000000000003b1c0 +0000000000041840 R_X86_64_RELATIVE *ABS*+0x000000000003d660 +0000000000041848 R_X86_64_RELATIVE *ABS*+0x000000000003b1d0 +0000000000041850 R_X86_64_RELATIVE *ABS*+0x000000000003d700 +0000000000041858 R_X86_64_RELATIVE *ABS*+0x000000000003dcd0 +0000000000041860 R_X86_64_RELATIVE *ABS*+0x0000000000020d80 +0000000000041868 R_X86_64_RELATIVE *ABS*+0x000000000003b240 +0000000000041870 R_X86_64_RELATIVE *ABS*+0x000000000003a020 +0000000000041878 R_X86_64_RELATIVE *ABS*+0x000000000003a030 +0000000000041880 R_X86_64_RELATIVE *ABS*+0x000000000003d520 +0000000000041888 R_X86_64_RELATIVE *ABS*+0x000000000003d5c0 +0000000000041890 R_X86_64_RELATIVE *ABS*+0x000000000003da60 +0000000000041898 R_X86_64_RELATIVE *ABS*+0x000000000003d3e0 +00000000000418a0 R_X86_64_RELATIVE *ABS*+0x000000000003d480 +00000000000418a8 R_X86_64_RELATIVE *ABS*+0x000000000003d9d0 +00000000000418b0 R_X86_64_RELATIVE *ABS*+0x0000000000039490 +00000000000418b8 R_X86_64_RELATIVE *ABS*+0x000000000003b290 +00000000000418c0 R_X86_64_RELATIVE *ABS*+0x000000000003a310 +00000000000418c8 R_X86_64_RELATIVE *ABS*+0x0000000000011410 +00000000000418e0 R_X86_64_RELATIVE *ABS*+0x0000000000042f78 +00000000000418e8 R_X86_64_RELATIVE *ABS*+0x0000000000022450 +00000000000418f0 R_X86_64_RELATIVE *ABS*+0x00000000000204c0 +0000000000041910 R_X86_64_RELATIVE *ABS*+0x000000000003b8d0 +0000000000041918 R_X86_64_RELATIVE *ABS*+0x000000000003bcd0 +0000000000041920 R_X86_64_RELATIVE *ABS*+0x0000000000039570 +0000000000041928 R_X86_64_RELATIVE *ABS*+0x000000000003cb90 +0000000000041930 R_X86_64_RELATIVE *ABS*+0x000000000003b850 +0000000000041938 R_X86_64_RELATIVE *ABS*+0x000000000003b950 +0000000000041940 R_X86_64_RELATIVE *ABS*+0x0000000000039520 +0000000000041948 R_X86_64_RELATIVE *ABS*+0x0000000000039630 +0000000000041950 R_X86_64_RELATIVE *ABS*+0x000000000003b1b0 +0000000000041960 R_X86_64_RELATIVE *ABS*+0x0000000000011420 +0000000000041968 R_X86_64_RELATIVE *ABS*+0x0000000000038c90 +0000000000041970 R_X86_64_RELATIVE *ABS*+0x00000000000331c0 +0000000000041978 R_X86_64_RELATIVE *ABS*+0x0000000000033190 +0000000000041980 R_X86_64_RELATIVE *ABS*+0x0000000000038cb0 +0000000000041988 R_X86_64_RELATIVE *ABS*+0x0000000000031480 +0000000000041990 R_X86_64_RELATIVE *ABS*+0x0000000000033150 +0000000000041998 R_X86_64_RELATIVE *ABS*+0x000000000003b1e0 +00000000000419a0 R_X86_64_RELATIVE *ABS*+0x0000000000039ce0 +00000000000419a8 R_X86_64_RELATIVE *ABS*+0x0000000000024ab0 +00000000000419b0 R_X86_64_RELATIVE *ABS*+0x0000000000038f30 +00000000000419c0 R_X86_64_RELATIVE *ABS*+0x000000000001da50 +00000000000419d0 R_X86_64_RELATIVE *ABS*+0x00000000000397f0 +00000000000419f0 R_X86_64_RELATIVE *ABS*+0x0000000000024bf0 +00000000000419f8 R_X86_64_RELATIVE *ABS*+0x000000000003b170 +0000000000041a08 R_X86_64_RELATIVE *ABS*+0x000000000001f5a0 +0000000000041a10 R_X86_64_RELATIVE *ABS*+0x000000000001efb0 +0000000000041a38 R_X86_64_RELATIVE *ABS*+0x0000000000039280 +0000000000041a40 R_X86_64_RELATIVE *ABS*+0x000000000001d870 +0000000000041a50 R_X86_64_RELATIVE *ABS*+0x0000000000038a00 +0000000000041a58 R_X86_64_RELATIVE *ABS*+0x000000000003ba50 +0000000000041a68 R_X86_64_RELATIVE *ABS*+0x000000000003c600 +0000000000041a70 R_X86_64_RELATIVE *ABS*+0x000000000003ca80 +0000000000041a78 R_X86_64_RELATIVE *ABS*+0x000000000001fb70 +0000000000041a80 R_X86_64_RELATIVE *ABS*+0x000000000003b650 +0000000000041a88 R_X86_64_RELATIVE *ABS*+0x000000000003b150 +0000000000041a90 R_X86_64_RELATIVE *ABS*+0x000000000003c610 +0000000000041a98 R_X86_64_RELATIVE *ABS*+0x000000000003c620 +0000000000041aa0 R_X86_64_RELATIVE *ABS*+0x00000000000391d0 +0000000000041aa8 R_X86_64_RELATIVE *ABS*+0x000000000003b270 +0000000000041ab0 R_X86_64_RELATIVE *ABS*+0x000000000003e2c0 +0000000000041ab8 R_X86_64_RELATIVE *ABS*+0x000000000003d000 +0000000000041ac0 R_X86_64_RELATIVE *ABS*+0x0000000000020010 +0000000000041ac8 R_X86_64_RELATIVE *ABS*+0x000000000001d6c0 +0000000000041ad8 R_X86_64_RELATIVE *ABS*+0x0000000000021630 +0000000000041af8 R_X86_64_RELATIVE *ABS*+0x000000000003a2d0 +0000000000041b10 R_X86_64_RELATIVE *ABS*+0x0000000000039310 +0000000000041b18 R_X86_64_RELATIVE *ABS*+0x00000000000392f0 +0000000000041b20 R_X86_64_RELATIVE *ABS*+0x000000000001e000 +0000000000041b28 R_X86_64_RELATIVE *ABS*+0x0000000000022550 +0000000000041b30 R_X86_64_RELATIVE *ABS*+0x00000000000205c0 +0000000000041b38 R_X86_64_RELATIVE *ABS*+0x000000000002dad0 +0000000000041b40 R_X86_64_RELATIVE *ABS*+0x0000000000022590 +0000000000041b48 R_X86_64_RELATIVE *ABS*+0x0000000000039300 +0000000000041b58 R_X86_64_RELATIVE *ABS*+0x0000000000039320 +0000000000041b60 R_X86_64_RELATIVE *ABS*+0x0000000000022cd0 +0000000000041b68 R_X86_64_RELATIVE *ABS*+0x000000000002db20 +0000000000041b70 R_X86_64_RELATIVE *ABS*+0x000000000003dba0 +0000000000041bc0 R_X86_64_RELATIVE *ABS*+0x00000000000212f0 +0000000000041bc8 R_X86_64_RELATIVE *ABS*+0x00000000000224b0 +0000000000041bd0 R_X86_64_RELATIVE *ABS*+0x00000000000389a0 +0000000000041bd8 R_X86_64_RELATIVE *ABS*+0x000000000003a2c0 +0000000000041be0 R_X86_64_RELATIVE *ABS*+0x0000000000024f90 +0000000000041be8 R_X86_64_RELATIVE *ABS*+0x000000000003b200 +0000000000041bf0 R_X86_64_RELATIVE *ABS*+0x0000000000039d30 +0000000000041bf8 R_X86_64_RELATIVE *ABS*+0x0000000000039e80 +0000000000041c50 R_X86_64_RELATIVE *ABS*+0x0000000000038a10 +0000000000041c58 R_X86_64_RELATIVE *ABS*+0x0000000000011430 +0000000000041c68 R_X86_64_RELATIVE *ABS*+0x000000000001db70 +0000000000041c70 R_X86_64_RELATIVE *ABS*+0x000000000003e4a0 +0000000000041c78 R_X86_64_RELATIVE *ABS*+0x0000000000031310 +0000000000041c80 R_X86_64_RELATIVE *ABS*+0x0000000000031300 +0000000000041c88 R_X86_64_RELATIVE *ABS*+0x0000000000031990 +0000000000041c90 R_X86_64_RELATIVE *ABS*+0x0000000000031aa0 +0000000000041c98 R_X86_64_RELATIVE *ABS*+0x0000000000032fc0 +0000000000041ca0 R_X86_64_RELATIVE *ABS*+0x0000000000032fe0 +0000000000041ca8 R_X86_64_RELATIVE *ABS*+0x0000000000032f30 +0000000000041cb0 R_X86_64_RELATIVE *ABS*+0x0000000000031ae0 +0000000000041cb8 R_X86_64_RELATIVE *ABS*+0x000000000002ded0 +0000000000041cc0 R_X86_64_RELATIVE *ABS*+0x000000000002ea40 +0000000000041cc8 R_X86_64_RELATIVE *ABS*+0x0000000000023d10 +0000000000041ce8 R_X86_64_RELATIVE *ABS*+0x0000000000021ab0 +0000000000041cf8 R_X86_64_RELATIVE *ABS*+0x0000000000021a00 +0000000000041d28 R_X86_64_RELATIVE *ABS*+0x0000000000030e90 +0000000000041d30 R_X86_64_RELATIVE *ABS*+0x000000000003b9d0 +0000000000041d38 R_X86_64_RELATIVE *ABS*+0x000000000003d660 +0000000000041d40 R_X86_64_RELATIVE *ABS*+0x000000000003d700 +0000000000041d48 R_X86_64_RELATIVE *ABS*+0x000000000003dcd0 +0000000000041d50 R_X86_64_RELATIVE *ABS*+0x0000000000038af0 +0000000000041d58 R_X86_64_RELATIVE *ABS*+0x000000000003b730 +0000000000041d60 R_X86_64_RELATIVE *ABS*+0x000000000003d350 +0000000000041d68 R_X86_64_RELATIVE *ABS*+0x000000000003d340 +0000000000041d70 R_X86_64_RELATIVE *ABS*+0x000000000003e2a0 +0000000000041d78 R_X86_64_RELATIVE *ABS*+0x000000000003bcb0 +0000000000041d80 R_X86_64_RELATIVE *ABS*+0x0000000000034040 +0000000000041d88 R_X86_64_RELATIVE *ABS*+0x000000000003b2b0 +0000000000041d90 R_X86_64_RELATIVE *ABS*+0x0000000000037c40 +0000000000041d98 R_X86_64_RELATIVE *ABS*+0x0000000000033440 +0000000000041da0 R_X86_64_RELATIVE *ABS*+0x0000000000011440 +0000000000041da8 R_X86_64_RELATIVE *ABS*+0x0000000000021860 +0000000000041db0 R_X86_64_RELATIVE *ABS*+0x00000000000395f0 +0000000000041db8 R_X86_64_RELATIVE *ABS*+0x0000000000039880 +0000000000041dc0 R_X86_64_RELATIVE *ABS*+0x000000000003aaa0 +0000000000041dc8 R_X86_64_RELATIVE *ABS*+0x0000000000039b30 +0000000000041dd0 R_X86_64_RELATIVE *ABS*+0x000000000003a530 +0000000000042e08 R_X86_64_RELATIVE *ABS*+0x0000000000042e08 +0000000000042e10 R_X86_64_RELATIVE *ABS*+0x000000000002dbe0 +0000000000042e28 R_X86_64_RELATIVE *ABS*+0x00000000000214f0 +0000000000042e38 R_X86_64_RELATIVE *ABS*+0x0000000000042e08 +0000000000042e40 R_X86_64_RELATIVE *ABS*+0x0000000000008ab5 +0000000000041758 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5 +0000000000041760 R_X86_64_GLOB_DAT __gmon_start__@Base +0000000000041768 R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTable@Base +0000000000041770 R_X86_64_GLOB_DAT _ITM_registerTMCloneTable@Base +0000000000041778 R_X86_64_GLOB_DAT __cxa_finalize@GLIBC_2.2.5 +00000000000417d8 R_X86_64_GLOB_DAT free@GLIBC_2.2.5 +00000000000417c8 R_X86_64_GLOB_DAT malloc@GLIBC_2.2.5 +00000000000417e0 R_X86_64_GLOB_DAT memcpy@GLIBC_2.14 +00000000000417e8 R_X86_64_GLOB_DAT memset@GLIBC_2.2.5 +00000000000417d0 R_X86_64_GLOB_DAT realloc@GLIBC_2.2.5 +00000000000417f0 R_X86_64_GLOB_DAT roc__mainForHost_1_exposed@Base +0000000000041808 R_X86_64_GLOB_DAT write@GLIBC_2.2.5 +0000000000041c00 R_X86_64_GLOB_DAT bcmp@GLIBC_2.2.5 +0000000000041a48 R_X86_64_GLOB_DAT _Unwind_Backtrace@GCC_3.3 +0000000000041a60 R_X86_64_GLOB_DAT _Unwind_GetIP@GCC_3.0 +0000000000041c48 R_X86_64_GLOB_DAT __cxa_thread_atexit_impl@GLIBC_2.18 +00000000000419e8 R_X86_64_GLOB_DAT __errno_location@GLIBC_2.2.5 +0000000000041c20 R_X86_64_GLOB_DAT __xpg_strerror_r@GLIBC_2.3.4 +0000000000041b80 R_X86_64_GLOB_DAT abort@GLIBC_2.2.5 +0000000000041b08 R_X86_64_GLOB_DAT calloc@GLIBC_2.2.5 +00000000000418d8 R_X86_64_GLOB_DAT close@GLIBC_2.2.5 +0000000000041c60 R_X86_64_GLOB_DAT dl_iterate_phdr@GLIBC_2.2.5 +0000000000041c08 R_X86_64_GLOB_DAT dlsym@GLIBC_2.2.5 +0000000000041c30 R_X86_64_GLOB_DAT exit@GLIBC_2.2.5 +00000000000419d8 R_X86_64_GLOB_DAT getcwd@GLIBC_2.2.5 +0000000000041c28 R_X86_64_GLOB_DAT getenv@GLIBC_2.2.5 +00000000000419b8 R_X86_64_GLOB_DAT memchr@GLIBC_2.2.5 +0000000000041908 R_X86_64_GLOB_DAT memmove@GLIBC_2.2.5 +0000000000041c38 R_X86_64_GLOB_DAT mmap@GLIBC_2.2.5 +0000000000041c40 R_X86_64_GLOB_DAT mprotect@GLIBC_2.2.5 +00000000000418f8 R_X86_64_GLOB_DAT munmap@GLIBC_2.2.5 +0000000000041b88 R_X86_64_GLOB_DAT open@GLIBC_2.2.5 +0000000000041c10 R_X86_64_GLOB_DAT open64@GLIBC_2.2.5 +0000000000041b78 R_X86_64_GLOB_DAT poll@GLIBC_2.2.5 +0000000000041b00 R_X86_64_GLOB_DAT posix_memalign@GLIBC_2.2.5 +0000000000041bb8 R_X86_64_GLOB_DAT pthread_attr_destroy@GLIBC_2.2.5 +0000000000041bb0 R_X86_64_GLOB_DAT pthread_attr_getstack@GLIBC_2.2.5 +0000000000041ba8 R_X86_64_GLOB_DAT pthread_getattr_np@GLIBC_2.2.5 +0000000000041ae0 R_X86_64_GLOB_DAT pthread_getspecific@GLIBC_2.2.5 +0000000000041ae8 R_X86_64_GLOB_DAT pthread_key_create@GLIBC_2.2.5 +0000000000041af0 R_X86_64_GLOB_DAT pthread_key_delete@GLIBC_2.2.5 +0000000000041958 R_X86_64_GLOB_DAT pthread_mutex_destroy@GLIBC_2.2.5 +00000000000419c8 R_X86_64_GLOB_DAT pthread_mutex_lock@GLIBC_2.2.5 +0000000000041a18 R_X86_64_GLOB_DAT pthread_mutex_trylock@GLIBC_2.2.5 +00000000000418d0 R_X86_64_GLOB_DAT pthread_mutex_unlock@GLIBC_2.2.5 +0000000000041b50 R_X86_64_GLOB_DAT pthread_rwlock_rdlock@GLIBC_2.2.5 +0000000000041900 R_X86_64_GLOB_DAT pthread_rwlock_unlock@GLIBC_2.2.5 +0000000000041ba0 R_X86_64_GLOB_DAT pthread_self@GLIBC_2.2.5 +0000000000041ad0 R_X86_64_GLOB_DAT pthread_setspecific@GLIBC_2.2.5 +0000000000041c18 R_X86_64_GLOB_DAT readlink@GLIBC_2.2.5 +0000000000041b98 R_X86_64_GLOB_DAT sigaction@GLIBC_2.2.5 +0000000000041a20 R_X86_64_GLOB_DAT sigaltstack@GLIBC_2.2.5 +0000000000041b90 R_X86_64_GLOB_DAT signal@GLIBC_2.2.5 +00000000000419e0 R_X86_64_GLOB_DAT strlen@GLIBC_2.2.5 +0000000000041a30 R_X86_64_GLOB_DAT syscall@GLIBC_2.2.5 +0000000000041a28 R_X86_64_GLOB_DAT sysconf@GLIBC_2.2.5 +0000000000041a00 R_X86_64_GLOB_DAT writev@GLIBC_2.2.5 +0000000000041ce0 R_X86_64_GLOB_DAT _Unwind_DeleteException@GCC_3.0 +0000000000041cd0 R_X86_64_GLOB_DAT _Unwind_GetDataRelBase@GCC_3.0 +0000000000041d08 R_X86_64_GLOB_DAT _Unwind_GetIPInfo@GCC_4.2.0 +0000000000041d00 R_X86_64_GLOB_DAT _Unwind_GetLanguageSpecificData@GCC_3.0 +0000000000041d10 R_X86_64_GLOB_DAT _Unwind_GetRegionStart@GCC_3.0 +0000000000041cd8 R_X86_64_GLOB_DAT _Unwind_GetTextRelBase@GCC_3.0 +0000000000041cf0 R_X86_64_GLOB_DAT _Unwind_RaiseException@GCC_3.0 +0000000000041d18 R_X86_64_GLOB_DAT _Unwind_SetGR@GCC_3.0 +0000000000041d20 R_X86_64_GLOB_DAT _Unwind_SetIP@GCC_3.0 +0000000000041df0 R_X86_64_JUMP_SLOT __cxa_finalize@GLIBC_2.2.5 +0000000000041df8 R_X86_64_JUMP_SLOT _Unwind_Resume@GCC_3.0 +0000000000041e00 R_X86_64_JUMP_SLOT __fxstat64@GLIBC_2.2.5 + + From d4c32df97d2611291be4cc7b48cb2f788fc8e85c Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 22:00:58 -0700 Subject: [PATCH 074/136] Sate clippy and fix accidental garbage file --- .gitignore | 1 + linker/src/lib.rs | 5 +- ult = result ^ roc_memcpy_ptr | 651 ---------------------------------- 3 files changed, 3 insertions(+), 654 deletions(-) delete mode 100644 ult = result ^ roc_memcpy_ptr diff --git a/.gitignore b/.gitignore index 1c7ea24a83..33679f024a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ zig-cache .direnv *.rs.bk *.o +*.tmp # llvm human-readable output *.ll diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 15ec09084a..be4aa891c9 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -318,7 +318,7 @@ fn preprocess_impl( sym.is_definition() && sym.name().is_ok() && sym.name().unwrap().starts_with("roc_") }) { // remove potentially trailing "@version". - let name = sym.name().unwrap().split("@").next().unwrap().to_string(); + let name = sym.name().unwrap().split('@').next().unwrap().to_string(); // special exceptions for memcpy and memset. if &name == "roc_memcpy" { @@ -406,7 +406,7 @@ fn preprocess_impl( } None }) - .filter_map(|x| x) + .flatten() .collect(); for sym in app_syms.iter() { @@ -429,7 +429,6 @@ fn preprocess_impl( if reloc.target() == RelocationTarget::Symbol(symbol.index()) { let func_address = (i as u64 + 1) * PLT_ADDRESS_OFFSET + plt_address; let func_offset = (i as u64 + 1) * PLT_ADDRESS_OFFSET + plt_offset; - println!("{}", symbol.name().unwrap().to_string()); app_func_addresses.insert(func_address, symbol.name().unwrap()); md.plt_addresses.insert( symbol.name().unwrap().to_string(), diff --git a/ult = result ^ roc_memcpy_ptr b/ult = result ^ roc_memcpy_ptr deleted file mode 100644 index 88b26daf7c..0000000000 --- a/ult = result ^ roc_memcpy_ptr +++ /dev/null @@ -1,651 +0,0 @@ - -examples/hello-rust/platform/dynhost: file format elf64-x86-64 - -DYNAMIC RELOCATION RECORDS -OFFSET TYPE VALUE -000000000003f520 R_X86_64_RELATIVE *ABS*+0x00000000000112f0 -000000000003f528 R_X86_64_RELATIVE *ABS*+0x0000000000023b70 -000000000003f530 R_X86_64_RELATIVE *ABS*+0x0000000000011330 -000000000003f538 R_X86_64_RELATIVE *ABS*+0x00000000000113f0 -000000000003f550 R_X86_64_RELATIVE *ABS*+0x00000000000113d0 -000000000003f558 R_X86_64_RELATIVE *ABS*+0x00000000000113d0 -000000000003f560 R_X86_64_RELATIVE *ABS*+0x00000000000113e0 -000000000003f568 R_X86_64_RELATIVE *ABS*+0x0000000000006a10 -000000000003f580 R_X86_64_RELATIVE *ABS*+0x0000000000011560 -000000000003f598 R_X86_64_RELATIVE *ABS*+0x00000000000115f0 -000000000003f5a0 R_X86_64_RELATIVE *ABS*+0x0000000000011650 -000000000003f5b8 R_X86_64_RELATIVE *ABS*+0x0000000000011590 -000000000003f5c0 R_X86_64_RELATIVE *ABS*+0x0000000000011570 -000000000003f5c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f5e0 R_X86_64_RELATIVE *ABS*+0x0000000000015090 -000000000003f5e8 R_X86_64_RELATIVE *ABS*+0x0000000000014f80 -000000000003f5f0 R_X86_64_RELATIVE *ABS*+0x0000000000014f90 -000000000003f5f8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f610 R_X86_64_RELATIVE *ABS*+0x0000000000015080 -000000000003f618 R_X86_64_RELATIVE *ABS*+0x0000000000014ea0 -000000000003f620 R_X86_64_RELATIVE *ABS*+0x0000000000014fe0 -000000000003f628 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f640 R_X86_64_RELATIVE *ABS*+0x0000000000015100 -000000000003f648 R_X86_64_RELATIVE *ABS*+0x0000000000014d80 -000000000003f650 R_X86_64_RELATIVE *ABS*+0x0000000000015030 -000000000003f658 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f670 R_X86_64_RELATIVE *ABS*+0x0000000000011870 -000000000003f678 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -000000000003f690 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 -000000000003f6a8 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 -000000000003f6c0 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 -000000000003f6d8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f6f0 R_X86_64_RELATIVE *ABS*+0x00000000000116d0 -000000000003f6f8 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 -000000000003f710 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 -000000000003f728 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 -000000000003f740 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f758 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f770 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f788 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f7a0 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f7b8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f7d0 R_X86_64_RELATIVE *ABS*+0x000000000001d480 -000000000003f7d8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f7f0 R_X86_64_RELATIVE *ABS*+0x00000000000391b0 -000000000003f7f8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f810 R_X86_64_RELATIVE *ABS*+0x0000000000039190 -000000000003f818 R_X86_64_RELATIVE *ABS*+0x0000000000012420 -000000000003f830 R_X86_64_RELATIVE *ABS*+0x00000000000239d0 -000000000003f838 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f850 R_X86_64_RELATIVE *ABS*+0x000000000003e1c0 -000000000003f858 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f870 R_X86_64_RELATIVE *ABS*+0x0000000000011700 -000000000003f878 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f890 R_X86_64_RELATIVE *ABS*+0x0000000000011810 -000000000003f898 R_X86_64_RELATIVE *ABS*+0x0000000000007e14 -000000000003f8b0 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f8c8 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f8e0 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f8f8 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f910 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f928 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa -000000000003f940 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa -000000000003f958 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa -000000000003f970 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa -000000000003f988 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 -000000000003f9a0 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003f9b8 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003f9d0 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003f9e8 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003fa00 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003fa18 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003fa30 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003fa48 R_X86_64_RELATIVE *ABS*+0x0000000000007fe0 -000000000003fa60 R_X86_64_RELATIVE *ABS*+0x000000000000805d -000000000003fa78 R_X86_64_RELATIVE *ABS*+0x000000000000805d -000000000003fa90 R_X86_64_RELATIVE *ABS*+0x000000000000805d -000000000003faa8 R_X86_64_RELATIVE *ABS*+0x000000000000805d -000000000003fac0 R_X86_64_RELATIVE *ABS*+0x0000000000008184 -000000000003fad0 R_X86_64_RELATIVE *ABS*+0x00000000000081a8 -000000000003fae0 R_X86_64_RELATIVE *ABS*+0x000000000000816e -000000000003faf8 R_X86_64_RELATIVE *ABS*+0x00000000000081ab -000000000003fb08 R_X86_64_RELATIVE *ABS*+0x00000000000081cc -000000000003fb20 R_X86_64_RELATIVE *ABS*+0x00000000000081f4 -000000000003fb30 R_X86_64_RELATIVE *ABS*+0x00000000000081cc -000000000003fb48 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -000000000003fb58 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -000000000003fb68 R_X86_64_RELATIVE *ABS*+0x0000000000008336 -000000000003fb78 R_X86_64_RELATIVE *ABS*+0x0000000000008341 -000000000003fb88 R_X86_64_RELATIVE *ABS*+0x0000000000008342 -000000000003fb98 R_X86_64_RELATIVE *ABS*+0x000000000000835e -000000000003fbb0 R_X86_64_RELATIVE *ABS*+0x000000000000837b -000000000003fbc8 R_X86_64_RELATIVE *ABS*+0x000000000000837b -000000000003fbe0 R_X86_64_RELATIVE *ABS*+0x0000000000008394 -000000000003fbf0 R_X86_64_RELATIVE *ABS*+0x0000000000013010 -000000000003fc08 R_X86_64_RELATIVE *ABS*+0x000000000001ef40 -000000000003fc10 R_X86_64_RELATIVE *ABS*+0x0000000000011910 -000000000003fc18 R_X86_64_RELATIVE *ABS*+0x0000000000011b40 -000000000003fc20 R_X86_64_RELATIVE *ABS*+0x0000000000013010 -000000000003fc38 R_X86_64_RELATIVE *ABS*+0x000000000001eda0 -000000000003fc40 R_X86_64_RELATIVE *ABS*+0x0000000000011a20 -000000000003fc48 R_X86_64_RELATIVE *ABS*+0x0000000000011b00 -000000000003fc50 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fc68 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fc80 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fc98 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fcb0 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fcc8 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fce0 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fcf8 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fd10 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fd28 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003fd40 R_X86_64_RELATIVE *ABS*+0x000000000001fe50 -000000000003fd48 R_X86_64_RELATIVE *ABS*+0x0000000000011c00 -000000000003fd50 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fd68 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fd80 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fd98 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fdb0 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fdc8 R_X86_64_RELATIVE *ABS*+0x000000000000843b -000000000003fdd8 R_X86_64_RELATIVE *ABS*+0x0000000000013090 -000000000003fdf0 R_X86_64_RELATIVE *ABS*+0x00000000000207d0 -000000000003fdf8 R_X86_64_RELATIVE *ABS*+0x0000000000011b80 -000000000003fe00 R_X86_64_RELATIVE *ABS*+0x000000000000844c -000000000003fe10 R_X86_64_RELATIVE *ABS*+0x00000000000083a4 -000000000003fe20 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -000000000003fe30 R_X86_64_RELATIVE *ABS*+0x00000000000084e3 -000000000003fe40 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fe58 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fe70 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fe88 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fea0 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003feb8 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fed0 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fee8 R_X86_64_RELATIVE *ABS*+0x000000000000856f -000000000003fef8 R_X86_64_RELATIVE *ABS*+0x0000000000008584 -000000000003ff08 R_X86_64_RELATIVE *ABS*+0x0000000000008585 -000000000003ff18 R_X86_64_RELATIVE *ABS*+0x00000000000085a2 -000000000003ff28 R_X86_64_RELATIVE *ABS*+0x00000000000085b7 -000000000003ff38 R_X86_64_RELATIVE *ABS*+0x00000000000085c5 -000000000003ff48 R_X86_64_RELATIVE *ABS*+0x00000000000085e1 -000000000003ff58 R_X86_64_RELATIVE *ABS*+0x0000000000008605 -000000000003ff70 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003ff88 R_X86_64_RELATIVE *ABS*+0x00000000000249d0 -000000000003ff90 R_X86_64_RELATIVE *ABS*+0x0000000000024a30 -000000000003ff98 R_X86_64_RELATIVE *ABS*+0x0000000000024a90 -000000000003ffa0 R_X86_64_RELATIVE *ABS*+0x0000000000024aa0 -000000000003ffa8 R_X86_64_RELATIVE *ABS*+0x000000000001e550 -000000000003ffb0 R_X86_64_RELATIVE *ABS*+0x000000000001e640 -000000000003ffb8 R_X86_64_RELATIVE *ABS*+0x000000000001ec50 -000000000003ffc8 R_X86_64_RELATIVE *ABS*+0x0000000000012190 -000000000003ffe0 R_X86_64_RELATIVE *ABS*+0x000000000001e2c0 -000000000003ffe8 R_X86_64_RELATIVE *ABS*+0x000000000001e330 -000000000003fff0 R_X86_64_RELATIVE *ABS*+0x000000000001e4c0 -000000000003fff8 R_X86_64_RELATIVE *ABS*+0x000000000001e540 -0000000000040000 R_X86_64_RELATIVE *ABS*+0x000000000001e4d0 -0000000000040008 R_X86_64_RELATIVE *ABS*+0x000000000001e890 -0000000000040010 R_X86_64_RELATIVE *ABS*+0x000000000001eb00 -0000000000040020 R_X86_64_RELATIVE *ABS*+0x0000000000008150 -0000000000040030 R_X86_64_RELATIVE *ABS*+0x0000000000008636 -0000000000040040 R_X86_64_RELATIVE *ABS*+0x0000000000008645 -0000000000040050 R_X86_64_RELATIVE *ABS*+0x0000000000008584 -0000000000040060 R_X86_64_RELATIVE *ABS*+0x0000000000008648 -0000000000040070 R_X86_64_RELATIVE *ABS*+0x0000000000008605 -0000000000040088 R_X86_64_RELATIVE *ABS*+0x0000000000008605 -00000000000400a0 R_X86_64_RELATIVE *ABS*+0x0000000000012d90 -00000000000400b8 R_X86_64_RELATIVE *ABS*+0x0000000000022690 -00000000000400c0 R_X86_64_RELATIVE *ABS*+0x0000000000022790 -00000000000400c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -00000000000400e0 R_X86_64_RELATIVE *ABS*+0x0000000000022830 -00000000000400e8 R_X86_64_RELATIVE *ABS*+0x0000000000022880 -00000000000400f0 R_X86_64_RELATIVE *ABS*+0x0000000000012190 -0000000000040108 R_X86_64_RELATIVE *ABS*+0x00000000000116e0 -0000000000040110 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -0000000000040128 R_X86_64_RELATIVE *ABS*+0x00000000000116f0 -0000000000040130 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -0000000000040148 R_X86_64_RELATIVE *ABS*+0x00000000000228f0 -0000000000040150 R_X86_64_RELATIVE *ABS*+0x0000000000022950 -0000000000040158 R_X86_64_RELATIVE *ABS*+0x0000000000008696 -0000000000040168 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -0000000000040178 R_X86_64_RELATIVE *ABS*+0x00000000000086c8 -0000000000040188 R_X86_64_RELATIVE *ABS*+0x00000000000086f9 -0000000000040198 R_X86_64_RELATIVE *ABS*+0x0000000000007c20 -00000000000401a8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -00000000000401c0 R_X86_64_RELATIVE *ABS*+0x0000000000020a40 -00000000000401c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c20 -00000000000401d0 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -00000000000401e8 R_X86_64_RELATIVE *ABS*+0x0000000000020810 -00000000000401f0 R_X86_64_RELATIVE *ABS*+0x0000000000011bf0 -00000000000401f8 R_X86_64_RELATIVE *ABS*+0x0000000000008724 -0000000000040210 R_X86_64_RELATIVE *ABS*+0x0000000000008758 -0000000000040220 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -0000000000040230 R_X86_64_RELATIVE *ABS*+0x0000000000008379 -0000000000040240 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -0000000000040250 R_X86_64_RELATIVE *ABS*+0x00000000000087d0 -0000000000040260 R_X86_64_RELATIVE *ABS*+0x00000000000084da -0000000000040270 R_X86_64_RELATIVE *ABS*+0x0000000000006b40 -0000000000040280 R_X86_64_RELATIVE *ABS*+0x00000000000083a3 -0000000000040290 R_X86_64_RELATIVE *ABS*+0x0000000000008584 -00000000000402a0 R_X86_64_RELATIVE *ABS*+0x00000000000087fd -00000000000402b8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -00000000000402d0 R_X86_64_RELATIVE *ABS*+0x0000000000011780 -00000000000402d8 R_X86_64_RELATIVE *ABS*+0x000000000000884a -00000000000402f0 R_X86_64_RELATIVE *ABS*+0x000000000000887a -0000000000040308 R_X86_64_RELATIVE *ABS*+0x000000000000887a -0000000000040320 R_X86_64_RELATIVE *ABS*+0x00000000000088a6 -0000000000040330 R_X86_64_RELATIVE *ABS*+0x0000000000008902 -0000000000040348 R_X86_64_RELATIVE *ABS*+0x0000000000008902 -0000000000040360 R_X86_64_RELATIVE *ABS*+0x000000000000894d -0000000000040370 R_X86_64_RELATIVE *ABS*+0x0000000000008956 -0000000000040380 R_X86_64_RELATIVE *ABS*+0x0000000000008971 -0000000000040390 R_X86_64_RELATIVE *ABS*+0x000000000000897f -00000000000403a0 R_X86_64_RELATIVE *ABS*+0x00000000000089a8 -00000000000403b8 R_X86_64_RELATIVE *ABS*+0x00000000000089d2 -00000000000403c8 R_X86_64_RELATIVE *ABS*+0x00000000000089a8 -00000000000403e0 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 -00000000000403f8 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 -0000000000040410 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 -0000000000040428 R_X86_64_RELATIVE *ABS*+0x0000000000008a23 -0000000000040440 R_X86_64_RELATIVE *ABS*+0x0000000000008a96 -0000000000040458 R_X86_64_RELATIVE *ABS*+0x0000000000008abb -0000000000040470 R_X86_64_RELATIVE *ABS*+0x0000000000008af7 -0000000000040488 R_X86_64_RELATIVE *ABS*+0x0000000000008af7 -00000000000404a0 R_X86_64_RELATIVE *ABS*+0x0000000000008b2d -00000000000404b8 R_X86_64_RELATIVE *ABS*+0x000000000002d900 -00000000000404d0 R_X86_64_RELATIVE *ABS*+0x000000000002dbc0 -00000000000404d8 R_X86_64_RELATIVE *ABS*+0x000000000002dbc0 -00000000000404e0 R_X86_64_RELATIVE *ABS*+0x000000000002d880 -00000000000404e8 R_X86_64_RELATIVE *ABS*+0x000000000002d900 -0000000000040500 R_X86_64_RELATIVE *ABS*+0x000000000002dbd0 -0000000000040508 R_X86_64_RELATIVE *ABS*+0x000000000002dbd0 -0000000000040510 R_X86_64_RELATIVE *ABS*+0x000000000002d870 -0000000000040518 R_X86_64_RELATIVE *ABS*+0x0000000000008c57 -0000000000040530 R_X86_64_RELATIVE *ABS*+0x0000000000008c57 -0000000000040548 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040560 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040578 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040590 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000405a8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000405c0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000405d8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000405f0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040608 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040620 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040638 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040650 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040668 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040680 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040698 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000406b0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000406c8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000406e0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000406f8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040710 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040728 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040740 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040758 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040770 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040788 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000407a0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000407b8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000407d0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000407e8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040800 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040818 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040830 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040848 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040860 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040878 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040890 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000408a8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000408c0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000408d8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000408f0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040908 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040920 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040938 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040950 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040968 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040980 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040998 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000409b0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000409c8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000409e0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000409f8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040a10 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040a28 R_X86_64_RELATIVE *ABS*+0x0000000000008d92 -0000000000040a40 R_X86_64_RELATIVE *ABS*+0x0000000000008d92 -0000000000040a58 R_X86_64_RELATIVE *ABS*+0x00000000000315f0 -0000000000040a70 R_X86_64_RELATIVE *ABS*+0x0000000000031590 -0000000000040a78 R_X86_64_RELATIVE *ABS*+0x000000000000904b -0000000000040a90 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040aa8 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040ac0 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040ad8 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040af0 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040b08 R_X86_64_RELATIVE *ABS*+0x0000000000009260 -0000000000040b20 R_X86_64_RELATIVE *ABS*+0x0000000000009260 -0000000000040b38 R_X86_64_RELATIVE *ABS*+0x0000000000009260 -0000000000040b50 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040b68 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040b80 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040b98 R_X86_64_RELATIVE *ABS*+0x00000000000331f0 -0000000000040bb0 R_X86_64_RELATIVE *ABS*+0x000000000003d290 -0000000000040bb8 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040bd0 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040be8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c00 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c18 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c30 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c48 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c60 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c78 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c90 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040ca8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040cc0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040cd8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040cf0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d08 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d20 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d38 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d50 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d68 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d80 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d98 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040db0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040dc8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040de0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040df8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e10 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e28 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e40 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e58 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e70 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e88 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040ea0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040eb8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040ed0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040ee8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040f00 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040f18 R_X86_64_RELATIVE *ABS*+0x000000000000979a -0000000000040f28 R_X86_64_RELATIVE *ABS*+0x00000000000097d8 -0000000000040f40 R_X86_64_RELATIVE *ABS*+0x00000000000097d8 -0000000000040f58 R_X86_64_RELATIVE *ABS*+0x0000000000009828 -0000000000040f70 R_X86_64_RELATIVE *ABS*+0x0000000000009939 -0000000000040f80 R_X86_64_RELATIVE *ABS*+0x0000000000009975 -0000000000040f98 R_X86_64_RELATIVE *ABS*+0x0000000000009990 -0000000000040fa8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 -0000000000040fb8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000040fd0 R_X86_64_RELATIVE *ABS*+0x0000000000039180 -0000000000040fd8 R_X86_64_RELATIVE *ABS*+0x00000000000099e2 -0000000000040fe8 R_X86_64_RELATIVE *ABS*+0x00000000000099e3 -0000000000040ff8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 -0000000000041008 R_X86_64_RELATIVE *ABS*+0x00000000000099e6 -0000000000041018 R_X86_64_RELATIVE *ABS*+0x00000000000099e6 -0000000000041028 R_X86_64_RELATIVE *ABS*+0x0000000000007c80 -0000000000041038 R_X86_64_RELATIVE *ABS*+0x00000000000099e7 -0000000000041048 R_X86_64_RELATIVE *ABS*+0x0000000000009a04 -0000000000041058 R_X86_64_RELATIVE *ABS*+0x0000000000009a1d -0000000000041068 R_X86_64_RELATIVE *ABS*+0x0000000000009a2f -0000000000041078 R_X86_64_RELATIVE *ABS*+0x0000000000009a3b -0000000000041088 R_X86_64_RELATIVE *ABS*+0x0000000000009a04 -0000000000041098 R_X86_64_RELATIVE *ABS*+0x0000000000009a1d -00000000000410a8 R_X86_64_RELATIVE *ABS*+0x0000000000009a2f -00000000000410b8 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e -00000000000410c8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 -00000000000410d8 R_X86_64_RELATIVE *ABS*+0x0000000000009a3f -00000000000410e8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041100 R_X86_64_RELATIVE *ABS*+0x0000000000039880 -0000000000041108 R_X86_64_RELATIVE *ABS*+0x000000000003a060 -0000000000041110 R_X86_64_RELATIVE *ABS*+0x000000000003a140 -0000000000041118 R_X86_64_RELATIVE *ABS*+0x0000000000007ce0 -0000000000041130 R_X86_64_RELATIVE *ABS*+0x0000000000007ce0 -0000000000041148 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041160 R_X86_64_RELATIVE *ABS*+0x000000000003deb0 -0000000000041168 R_X86_64_RELATIVE *ABS*+0x0000000000009a56 -0000000000041180 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041198 R_X86_64_RELATIVE *ABS*+0x000000000003a180 -00000000000411a0 R_X86_64_RELATIVE *ABS*+0x000000000003a190 -00000000000411a8 R_X86_64_RELATIVE *ABS*+0x000000000003a270 -00000000000411b0 R_X86_64_RELATIVE *ABS*+0x0000000000007c60 -00000000000411c8 R_X86_64_RELATIVE *ABS*+0x0000000000009b3b -00000000000411d8 R_X86_64_RELATIVE *ABS*+0x0000000000009b4d -00000000000411e8 R_X86_64_RELATIVE *ABS*+0x0000000000006c30 -00000000000411f8 R_X86_64_RELATIVE *ABS*+0x0000000000009b4d -0000000000041208 R_X86_64_RELATIVE *ABS*+0x0000000000009b6f -0000000000041218 R_X86_64_RELATIVE *ABS*+0x0000000000009b85 -0000000000041228 R_X86_64_RELATIVE *ABS*+0x0000000000009b92 -0000000000041238 R_X86_64_RELATIVE *ABS*+0x0000000000009ba7 -0000000000041248 R_X86_64_RELATIVE *ABS*+0x0000000000009a52 -0000000000041258 R_X86_64_RELATIVE *ABS*+0x0000000000009bfc -0000000000041270 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -0000000000041288 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -00000000000412a0 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -00000000000412b8 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -00000000000412d0 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -00000000000412e8 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -0000000000041300 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041318 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041330 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041348 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041360 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041378 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041390 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -00000000000413a8 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -00000000000413c0 R_X86_64_RELATIVE *ABS*+0x0000000000009d83 -00000000000413d0 R_X86_64_RELATIVE *ABS*+0x0000000000009d8e -00000000000413e0 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e -00000000000413f0 R_X86_64_RELATIVE *ABS*+0x0000000000009da4 -0000000000041400 R_X86_64_RELATIVE *ABS*+0x0000000000007f18 -0000000000041410 R_X86_64_RELATIVE *ABS*+0x0000000000006b50 -0000000000041420 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e -0000000000041430 R_X86_64_RELATIVE *ABS*+0x0000000000009d83 -0000000000041440 R_X86_64_RELATIVE *ABS*+0x0000000000009db2 -0000000000041450 R_X86_64_RELATIVE *ABS*+0x0000000000008120 -0000000000041460 R_X86_64_RELATIVE *ABS*+0x0000000000009dd8 -0000000000041470 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e -0000000000041480 R_X86_64_RELATIVE *ABS*+0x0000000000009dde -0000000000041498 R_X86_64_RELATIVE *ABS*+0x0000000000009dde -00000000000414b0 R_X86_64_RELATIVE *ABS*+0x000000000000a34a -00000000000414c8 R_X86_64_RELATIVE *ABS*+0x000000000000a34a -00000000000414e0 R_X86_64_RELATIVE *ABS*+0x000000000000a34a -00000000000414f8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041510 R_X86_64_RELATIVE *ABS*+0x000000000003e130 -0000000000041518 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041530 R_X86_64_RELATIVE *ABS*+0x000000000003de00 -0000000000041538 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041550 R_X86_64_RELATIVE *ABS*+0x000000000003e030 -0000000000041780 R_X86_64_RELATIVE *ABS*+0x00000000000114b0 -0000000000041788 R_X86_64_RELATIVE *ABS*+0x0000000000011470 -0000000000041790 R_X86_64_RELATIVE *ABS*+0x0000000000011460 -0000000000041798 R_X86_64_RELATIVE *ABS*+0x0000000000011490 -00000000000417a0 R_X86_64_RELATIVE *ABS*+0x0000000000011480 -00000000000417a8 R_X86_64_RELATIVE *ABS*+0x00000000000114a0 -00000000000417b0 R_X86_64_RELATIVE *ABS*+0x000000000001fe20 -00000000000417b8 R_X86_64_RELATIVE *ABS*+0x0000000000022dc0 -00000000000417c0 R_X86_64_RELATIVE *ABS*+0x0000000000011690 -00000000000417f8 R_X86_64_RELATIVE *ABS*+0x0000000000011660 -0000000000041800 R_X86_64_RELATIVE *ABS*+0x0000000000011680 -0000000000041810 R_X86_64_RELATIVE *ABS*+0x0000000000039480 -0000000000041818 R_X86_64_RELATIVE *ABS*+0x000000000001fe40 -0000000000041820 R_X86_64_RELATIVE *ABS*+0x0000000000011400 -0000000000041828 R_X86_64_RELATIVE *ABS*+0x0000000000038ad0 -0000000000041830 R_X86_64_RELATIVE *ABS*+0x0000000000022970 -0000000000041838 R_X86_64_RELATIVE *ABS*+0x000000000003b1c0 -0000000000041840 R_X86_64_RELATIVE *ABS*+0x000000000003d660 -0000000000041848 R_X86_64_RELATIVE *ABS*+0x000000000003b1d0 -0000000000041850 R_X86_64_RELATIVE *ABS*+0x000000000003d700 -0000000000041858 R_X86_64_RELATIVE *ABS*+0x000000000003dcd0 -0000000000041860 R_X86_64_RELATIVE *ABS*+0x0000000000020d80 -0000000000041868 R_X86_64_RELATIVE *ABS*+0x000000000003b240 -0000000000041870 R_X86_64_RELATIVE *ABS*+0x000000000003a020 -0000000000041878 R_X86_64_RELATIVE *ABS*+0x000000000003a030 -0000000000041880 R_X86_64_RELATIVE *ABS*+0x000000000003d520 -0000000000041888 R_X86_64_RELATIVE *ABS*+0x000000000003d5c0 -0000000000041890 R_X86_64_RELATIVE *ABS*+0x000000000003da60 -0000000000041898 R_X86_64_RELATIVE *ABS*+0x000000000003d3e0 -00000000000418a0 R_X86_64_RELATIVE *ABS*+0x000000000003d480 -00000000000418a8 R_X86_64_RELATIVE *ABS*+0x000000000003d9d0 -00000000000418b0 R_X86_64_RELATIVE *ABS*+0x0000000000039490 -00000000000418b8 R_X86_64_RELATIVE *ABS*+0x000000000003b290 -00000000000418c0 R_X86_64_RELATIVE *ABS*+0x000000000003a310 -00000000000418c8 R_X86_64_RELATIVE *ABS*+0x0000000000011410 -00000000000418e0 R_X86_64_RELATIVE *ABS*+0x0000000000042f78 -00000000000418e8 R_X86_64_RELATIVE *ABS*+0x0000000000022450 -00000000000418f0 R_X86_64_RELATIVE *ABS*+0x00000000000204c0 -0000000000041910 R_X86_64_RELATIVE *ABS*+0x000000000003b8d0 -0000000000041918 R_X86_64_RELATIVE *ABS*+0x000000000003bcd0 -0000000000041920 R_X86_64_RELATIVE *ABS*+0x0000000000039570 -0000000000041928 R_X86_64_RELATIVE *ABS*+0x000000000003cb90 -0000000000041930 R_X86_64_RELATIVE *ABS*+0x000000000003b850 -0000000000041938 R_X86_64_RELATIVE *ABS*+0x000000000003b950 -0000000000041940 R_X86_64_RELATIVE *ABS*+0x0000000000039520 -0000000000041948 R_X86_64_RELATIVE *ABS*+0x0000000000039630 -0000000000041950 R_X86_64_RELATIVE *ABS*+0x000000000003b1b0 -0000000000041960 R_X86_64_RELATIVE *ABS*+0x0000000000011420 -0000000000041968 R_X86_64_RELATIVE *ABS*+0x0000000000038c90 -0000000000041970 R_X86_64_RELATIVE *ABS*+0x00000000000331c0 -0000000000041978 R_X86_64_RELATIVE *ABS*+0x0000000000033190 -0000000000041980 R_X86_64_RELATIVE *ABS*+0x0000000000038cb0 -0000000000041988 R_X86_64_RELATIVE *ABS*+0x0000000000031480 -0000000000041990 R_X86_64_RELATIVE *ABS*+0x0000000000033150 -0000000000041998 R_X86_64_RELATIVE *ABS*+0x000000000003b1e0 -00000000000419a0 R_X86_64_RELATIVE *ABS*+0x0000000000039ce0 -00000000000419a8 R_X86_64_RELATIVE *ABS*+0x0000000000024ab0 -00000000000419b0 R_X86_64_RELATIVE *ABS*+0x0000000000038f30 -00000000000419c0 R_X86_64_RELATIVE *ABS*+0x000000000001da50 -00000000000419d0 R_X86_64_RELATIVE *ABS*+0x00000000000397f0 -00000000000419f0 R_X86_64_RELATIVE *ABS*+0x0000000000024bf0 -00000000000419f8 R_X86_64_RELATIVE *ABS*+0x000000000003b170 -0000000000041a08 R_X86_64_RELATIVE *ABS*+0x000000000001f5a0 -0000000000041a10 R_X86_64_RELATIVE *ABS*+0x000000000001efb0 -0000000000041a38 R_X86_64_RELATIVE *ABS*+0x0000000000039280 -0000000000041a40 R_X86_64_RELATIVE *ABS*+0x000000000001d870 -0000000000041a50 R_X86_64_RELATIVE *ABS*+0x0000000000038a00 -0000000000041a58 R_X86_64_RELATIVE *ABS*+0x000000000003ba50 -0000000000041a68 R_X86_64_RELATIVE *ABS*+0x000000000003c600 -0000000000041a70 R_X86_64_RELATIVE *ABS*+0x000000000003ca80 -0000000000041a78 R_X86_64_RELATIVE *ABS*+0x000000000001fb70 -0000000000041a80 R_X86_64_RELATIVE *ABS*+0x000000000003b650 -0000000000041a88 R_X86_64_RELATIVE *ABS*+0x000000000003b150 -0000000000041a90 R_X86_64_RELATIVE *ABS*+0x000000000003c610 -0000000000041a98 R_X86_64_RELATIVE *ABS*+0x000000000003c620 -0000000000041aa0 R_X86_64_RELATIVE *ABS*+0x00000000000391d0 -0000000000041aa8 R_X86_64_RELATIVE *ABS*+0x000000000003b270 -0000000000041ab0 R_X86_64_RELATIVE *ABS*+0x000000000003e2c0 -0000000000041ab8 R_X86_64_RELATIVE *ABS*+0x000000000003d000 -0000000000041ac0 R_X86_64_RELATIVE *ABS*+0x0000000000020010 -0000000000041ac8 R_X86_64_RELATIVE *ABS*+0x000000000001d6c0 -0000000000041ad8 R_X86_64_RELATIVE *ABS*+0x0000000000021630 -0000000000041af8 R_X86_64_RELATIVE *ABS*+0x000000000003a2d0 -0000000000041b10 R_X86_64_RELATIVE *ABS*+0x0000000000039310 -0000000000041b18 R_X86_64_RELATIVE *ABS*+0x00000000000392f0 -0000000000041b20 R_X86_64_RELATIVE *ABS*+0x000000000001e000 -0000000000041b28 R_X86_64_RELATIVE *ABS*+0x0000000000022550 -0000000000041b30 R_X86_64_RELATIVE *ABS*+0x00000000000205c0 -0000000000041b38 R_X86_64_RELATIVE *ABS*+0x000000000002dad0 -0000000000041b40 R_X86_64_RELATIVE *ABS*+0x0000000000022590 -0000000000041b48 R_X86_64_RELATIVE *ABS*+0x0000000000039300 -0000000000041b58 R_X86_64_RELATIVE *ABS*+0x0000000000039320 -0000000000041b60 R_X86_64_RELATIVE *ABS*+0x0000000000022cd0 -0000000000041b68 R_X86_64_RELATIVE *ABS*+0x000000000002db20 -0000000000041b70 R_X86_64_RELATIVE *ABS*+0x000000000003dba0 -0000000000041bc0 R_X86_64_RELATIVE *ABS*+0x00000000000212f0 -0000000000041bc8 R_X86_64_RELATIVE *ABS*+0x00000000000224b0 -0000000000041bd0 R_X86_64_RELATIVE *ABS*+0x00000000000389a0 -0000000000041bd8 R_X86_64_RELATIVE *ABS*+0x000000000003a2c0 -0000000000041be0 R_X86_64_RELATIVE *ABS*+0x0000000000024f90 -0000000000041be8 R_X86_64_RELATIVE *ABS*+0x000000000003b200 -0000000000041bf0 R_X86_64_RELATIVE *ABS*+0x0000000000039d30 -0000000000041bf8 R_X86_64_RELATIVE *ABS*+0x0000000000039e80 -0000000000041c50 R_X86_64_RELATIVE *ABS*+0x0000000000038a10 -0000000000041c58 R_X86_64_RELATIVE *ABS*+0x0000000000011430 -0000000000041c68 R_X86_64_RELATIVE *ABS*+0x000000000001db70 -0000000000041c70 R_X86_64_RELATIVE *ABS*+0x000000000003e4a0 -0000000000041c78 R_X86_64_RELATIVE *ABS*+0x0000000000031310 -0000000000041c80 R_X86_64_RELATIVE *ABS*+0x0000000000031300 -0000000000041c88 R_X86_64_RELATIVE *ABS*+0x0000000000031990 -0000000000041c90 R_X86_64_RELATIVE *ABS*+0x0000000000031aa0 -0000000000041c98 R_X86_64_RELATIVE *ABS*+0x0000000000032fc0 -0000000000041ca0 R_X86_64_RELATIVE *ABS*+0x0000000000032fe0 -0000000000041ca8 R_X86_64_RELATIVE *ABS*+0x0000000000032f30 -0000000000041cb0 R_X86_64_RELATIVE *ABS*+0x0000000000031ae0 -0000000000041cb8 R_X86_64_RELATIVE *ABS*+0x000000000002ded0 -0000000000041cc0 R_X86_64_RELATIVE *ABS*+0x000000000002ea40 -0000000000041cc8 R_X86_64_RELATIVE *ABS*+0x0000000000023d10 -0000000000041ce8 R_X86_64_RELATIVE *ABS*+0x0000000000021ab0 -0000000000041cf8 R_X86_64_RELATIVE *ABS*+0x0000000000021a00 -0000000000041d28 R_X86_64_RELATIVE *ABS*+0x0000000000030e90 -0000000000041d30 R_X86_64_RELATIVE *ABS*+0x000000000003b9d0 -0000000000041d38 R_X86_64_RELATIVE *ABS*+0x000000000003d660 -0000000000041d40 R_X86_64_RELATIVE *ABS*+0x000000000003d700 -0000000000041d48 R_X86_64_RELATIVE *ABS*+0x000000000003dcd0 -0000000000041d50 R_X86_64_RELATIVE *ABS*+0x0000000000038af0 -0000000000041d58 R_X86_64_RELATIVE *ABS*+0x000000000003b730 -0000000000041d60 R_X86_64_RELATIVE *ABS*+0x000000000003d350 -0000000000041d68 R_X86_64_RELATIVE *ABS*+0x000000000003d340 -0000000000041d70 R_X86_64_RELATIVE *ABS*+0x000000000003e2a0 -0000000000041d78 R_X86_64_RELATIVE *ABS*+0x000000000003bcb0 -0000000000041d80 R_X86_64_RELATIVE *ABS*+0x0000000000034040 -0000000000041d88 R_X86_64_RELATIVE *ABS*+0x000000000003b2b0 -0000000000041d90 R_X86_64_RELATIVE *ABS*+0x0000000000037c40 -0000000000041d98 R_X86_64_RELATIVE *ABS*+0x0000000000033440 -0000000000041da0 R_X86_64_RELATIVE *ABS*+0x0000000000011440 -0000000000041da8 R_X86_64_RELATIVE *ABS*+0x0000000000021860 -0000000000041db0 R_X86_64_RELATIVE *ABS*+0x00000000000395f0 -0000000000041db8 R_X86_64_RELATIVE *ABS*+0x0000000000039880 -0000000000041dc0 R_X86_64_RELATIVE *ABS*+0x000000000003aaa0 -0000000000041dc8 R_X86_64_RELATIVE *ABS*+0x0000000000039b30 -0000000000041dd0 R_X86_64_RELATIVE *ABS*+0x000000000003a530 -0000000000042e08 R_X86_64_RELATIVE *ABS*+0x0000000000042e08 -0000000000042e10 R_X86_64_RELATIVE *ABS*+0x000000000002dbe0 -0000000000042e28 R_X86_64_RELATIVE *ABS*+0x00000000000214f0 -0000000000042e38 R_X86_64_RELATIVE *ABS*+0x0000000000042e08 -0000000000042e40 R_X86_64_RELATIVE *ABS*+0x0000000000008ab5 -0000000000041758 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5 -0000000000041760 R_X86_64_GLOB_DAT __gmon_start__@Base -0000000000041768 R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTable@Base -0000000000041770 R_X86_64_GLOB_DAT _ITM_registerTMCloneTable@Base -0000000000041778 R_X86_64_GLOB_DAT __cxa_finalize@GLIBC_2.2.5 -00000000000417d8 R_X86_64_GLOB_DAT free@GLIBC_2.2.5 -00000000000417c8 R_X86_64_GLOB_DAT malloc@GLIBC_2.2.5 -00000000000417e0 R_X86_64_GLOB_DAT memcpy@GLIBC_2.14 -00000000000417e8 R_X86_64_GLOB_DAT memset@GLIBC_2.2.5 -00000000000417d0 R_X86_64_GLOB_DAT realloc@GLIBC_2.2.5 -00000000000417f0 R_X86_64_GLOB_DAT roc__mainForHost_1_exposed@Base -0000000000041808 R_X86_64_GLOB_DAT write@GLIBC_2.2.5 -0000000000041c00 R_X86_64_GLOB_DAT bcmp@GLIBC_2.2.5 -0000000000041a48 R_X86_64_GLOB_DAT _Unwind_Backtrace@GCC_3.3 -0000000000041a60 R_X86_64_GLOB_DAT _Unwind_GetIP@GCC_3.0 -0000000000041c48 R_X86_64_GLOB_DAT __cxa_thread_atexit_impl@GLIBC_2.18 -00000000000419e8 R_X86_64_GLOB_DAT __errno_location@GLIBC_2.2.5 -0000000000041c20 R_X86_64_GLOB_DAT __xpg_strerror_r@GLIBC_2.3.4 -0000000000041b80 R_X86_64_GLOB_DAT abort@GLIBC_2.2.5 -0000000000041b08 R_X86_64_GLOB_DAT calloc@GLIBC_2.2.5 -00000000000418d8 R_X86_64_GLOB_DAT close@GLIBC_2.2.5 -0000000000041c60 R_X86_64_GLOB_DAT dl_iterate_phdr@GLIBC_2.2.5 -0000000000041c08 R_X86_64_GLOB_DAT dlsym@GLIBC_2.2.5 -0000000000041c30 R_X86_64_GLOB_DAT exit@GLIBC_2.2.5 -00000000000419d8 R_X86_64_GLOB_DAT getcwd@GLIBC_2.2.5 -0000000000041c28 R_X86_64_GLOB_DAT getenv@GLIBC_2.2.5 -00000000000419b8 R_X86_64_GLOB_DAT memchr@GLIBC_2.2.5 -0000000000041908 R_X86_64_GLOB_DAT memmove@GLIBC_2.2.5 -0000000000041c38 R_X86_64_GLOB_DAT mmap@GLIBC_2.2.5 -0000000000041c40 R_X86_64_GLOB_DAT mprotect@GLIBC_2.2.5 -00000000000418f8 R_X86_64_GLOB_DAT munmap@GLIBC_2.2.5 -0000000000041b88 R_X86_64_GLOB_DAT open@GLIBC_2.2.5 -0000000000041c10 R_X86_64_GLOB_DAT open64@GLIBC_2.2.5 -0000000000041b78 R_X86_64_GLOB_DAT poll@GLIBC_2.2.5 -0000000000041b00 R_X86_64_GLOB_DAT posix_memalign@GLIBC_2.2.5 -0000000000041bb8 R_X86_64_GLOB_DAT pthread_attr_destroy@GLIBC_2.2.5 -0000000000041bb0 R_X86_64_GLOB_DAT pthread_attr_getstack@GLIBC_2.2.5 -0000000000041ba8 R_X86_64_GLOB_DAT pthread_getattr_np@GLIBC_2.2.5 -0000000000041ae0 R_X86_64_GLOB_DAT pthread_getspecific@GLIBC_2.2.5 -0000000000041ae8 R_X86_64_GLOB_DAT pthread_key_create@GLIBC_2.2.5 -0000000000041af0 R_X86_64_GLOB_DAT pthread_key_delete@GLIBC_2.2.5 -0000000000041958 R_X86_64_GLOB_DAT pthread_mutex_destroy@GLIBC_2.2.5 -00000000000419c8 R_X86_64_GLOB_DAT pthread_mutex_lock@GLIBC_2.2.5 -0000000000041a18 R_X86_64_GLOB_DAT pthread_mutex_trylock@GLIBC_2.2.5 -00000000000418d0 R_X86_64_GLOB_DAT pthread_mutex_unlock@GLIBC_2.2.5 -0000000000041b50 R_X86_64_GLOB_DAT pthread_rwlock_rdlock@GLIBC_2.2.5 -0000000000041900 R_X86_64_GLOB_DAT pthread_rwlock_unlock@GLIBC_2.2.5 -0000000000041ba0 R_X86_64_GLOB_DAT pthread_self@GLIBC_2.2.5 -0000000000041ad0 R_X86_64_GLOB_DAT pthread_setspecific@GLIBC_2.2.5 -0000000000041c18 R_X86_64_GLOB_DAT readlink@GLIBC_2.2.5 -0000000000041b98 R_X86_64_GLOB_DAT sigaction@GLIBC_2.2.5 -0000000000041a20 R_X86_64_GLOB_DAT sigaltstack@GLIBC_2.2.5 -0000000000041b90 R_X86_64_GLOB_DAT signal@GLIBC_2.2.5 -00000000000419e0 R_X86_64_GLOB_DAT strlen@GLIBC_2.2.5 -0000000000041a30 R_X86_64_GLOB_DAT syscall@GLIBC_2.2.5 -0000000000041a28 R_X86_64_GLOB_DAT sysconf@GLIBC_2.2.5 -0000000000041a00 R_X86_64_GLOB_DAT writev@GLIBC_2.2.5 -0000000000041ce0 R_X86_64_GLOB_DAT _Unwind_DeleteException@GCC_3.0 -0000000000041cd0 R_X86_64_GLOB_DAT _Unwind_GetDataRelBase@GCC_3.0 -0000000000041d08 R_X86_64_GLOB_DAT _Unwind_GetIPInfo@GCC_4.2.0 -0000000000041d00 R_X86_64_GLOB_DAT _Unwind_GetLanguageSpecificData@GCC_3.0 -0000000000041d10 R_X86_64_GLOB_DAT _Unwind_GetRegionStart@GCC_3.0 -0000000000041cd8 R_X86_64_GLOB_DAT _Unwind_GetTextRelBase@GCC_3.0 -0000000000041cf0 R_X86_64_GLOB_DAT _Unwind_RaiseException@GCC_3.0 -0000000000041d18 R_X86_64_GLOB_DAT _Unwind_SetGR@GCC_3.0 -0000000000041d20 R_X86_64_GLOB_DAT _Unwind_SetIP@GCC_3.0 -0000000000041df0 R_X86_64_JUMP_SLOT __cxa_finalize@GLIBC_2.2.5 -0000000000041df8 R_X86_64_JUMP_SLOT _Unwind_Resume@GCC_3.0 -0000000000041e00 R_X86_64_JUMP_SLOT __fxstat64@GLIBC_2.2.5 - - From 754130d25fb07c889ebe57714cd573c4136ac7f3 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 22:03:59 -0700 Subject: [PATCH 075/136] Remove old unused linker tests --- linker/tests/fib/.gitignore | 11 --- linker/tests/fib/Main.roc | 15 ---- linker/tests/fib/README.md | 48 ------------- linker/tests/fib/platform/Package-Config.roc | 10 --- linker/tests/fib/platform/app.zig | 1 - linker/tests/fib/platform/build.zig | 33 --------- linker/tests/fib/platform/host.zig | 73 -------------------- 7 files changed, 191 deletions(-) delete mode 100644 linker/tests/fib/.gitignore delete mode 100644 linker/tests/fib/Main.roc delete mode 100644 linker/tests/fib/README.md delete mode 100644 linker/tests/fib/platform/Package-Config.roc delete mode 100644 linker/tests/fib/platform/app.zig delete mode 100644 linker/tests/fib/platform/build.zig delete mode 100644 linker/tests/fib/platform/host.zig diff --git a/linker/tests/fib/.gitignore b/linker/tests/fib/.gitignore deleted file mode 100644 index a3c0d77f6d..0000000000 --- a/linker/tests/fib/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -fib - -zig-cache -zig-out - -*.o - -dynhost -preprocessedhost -metadata -libapp.so \ No newline at end of file diff --git a/linker/tests/fib/Main.roc b/linker/tests/fib/Main.roc deleted file mode 100644 index 646fdbea75..0000000000 --- a/linker/tests/fib/Main.roc +++ /dev/null @@ -1,15 +0,0 @@ -app "fib" - packages { base: "platform" } - imports [] - provides [ main ] to base - -main : U64 -> U64 -main = \index -> - fibHelp index 0 1 - -fibHelp : U64, U64, U64 -> U64 -fibHelp = \index, parent, grandparent -> - if index == 0 then - parent - else - fibHelp (index - 1) grandparent (parent + grandparent) \ No newline at end of file diff --git a/linker/tests/fib/README.md b/linker/tests/fib/README.md deleted file mode 100644 index 0f1af10077..0000000000 --- a/linker/tests/fib/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Hello, World! - -To run, `cd` into this directory and run: - -```bash -$ cargo run Hello.roc -``` - -To run in release mode instead, do: - -```bash -$ cargo run --release Hello.roc -``` - -## Troubleshooting - -If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`. - -## Design Notes - -This demonstrates the basic design of hosts: Roc code gets compiled into a pure -function (in this case, a thunk that always returns `"Hello, World!"`) and -then the host calls that function. Fundamentally, that's the whole idea! The host -might not even have a `main` - it could be a library, a plugin, anything. -Everything else is built on this basic "hosts calling linked pure functions" design. - -For example, things get more interesting when the compiled Roc function returns -a `Task` - that is, a tagged union data structure containing function pointers -to callback closures. This lets the Roc pure function describe arbitrary -chainable effects, which the host can interpret to perform I/O as requested by -the Roc program. (The tagged union `Task` would have a variant for each supported -I/O operation.) - -In this trivial example, it's very easy to line up the API between the host and -the Roc program. In a more involved host, this would be much trickier - especially -if the API were changing frequently during development. - -The idea there is to have a first-class concept of "glue code" which host authors -can write (it would be plain Roc code, but with some extra keywords that aren't -available in normal modules - kinda like `port module` in Elm), and which -describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary. -Roc application authors only care about the Roc-host/Roc-app portion, and the -host author only cares about the Roc-host/C boundary when implementing the host. - -Using this glue code, the Roc compiler can generate C header files describing the -boundary. This not only gets us host compatibility with C compilers, but also -Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) -generates correct Rust FFI bindings from C headers. diff --git a/linker/tests/fib/platform/Package-Config.roc b/linker/tests/fib/platform/Package-Config.roc deleted file mode 100644 index d93cd7c258..0000000000 --- a/linker/tests/fib/platform/Package-Config.roc +++ /dev/null @@ -1,10 +0,0 @@ -platform tests/fib - requires {}{ main : U64 -> U64 } - exposes [] - packages {} - imports [] - provides [ mainForHost ] - effects fx.Effect {} - -mainForHost : U64 -> U64 -mainForHost = \arg -> main arg # workaround for https://github.com/rtfeldman/roc/issues/1622 \ No newline at end of file diff --git a/linker/tests/fib/platform/app.zig b/linker/tests/fib/platform/app.zig deleted file mode 100644 index 105908633f..0000000000 --- a/linker/tests/fib/platform/app.zig +++ /dev/null @@ -1 +0,0 @@ -export fn roc__mainForHost_1_exposed(_i: i64, _result: *u64) void {} diff --git a/linker/tests/fib/platform/build.zig b/linker/tests/fib/platform/build.zig deleted file mode 100644 index deb36d6c78..0000000000 --- a/linker/tests/fib/platform/build.zig +++ /dev/null @@ -1,33 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - // Standard target options allows the person running `zig build` to choose - // what target to build for. Here we do not override the defaults, which - // means any target is allowed, and the default is native. Other options - // for restricting supported target set are available. - const target = b.standardTargetOptions(.{}); - - // Standard release options allow the person running `zig build` to select - // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. - const mode = b.standardReleaseOptions(); - - const app = b.addSharedLibrary("app", "app.zig", .unversioned); - app.setTarget(target); - app.setBuildMode(mode); - app.install(); - - const exe = b.addExecutable("dynhost", "host.zig"); - exe.pie = true; - exe.strip = true; - exe.setTarget(target); - exe.setBuildMode(mode); - exe.linkLibrary(app); - exe.linkLibC(); - exe.install(); - - const run_cmd = exe.run(); - run_cmd.step.dependOn(b.getInstallStep()); - - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} diff --git a/linker/tests/fib/platform/host.zig b/linker/tests/fib/platform/host.zig deleted file mode 100644 index c439c889c7..0000000000 --- a/linker/tests/fib/platform/host.zig +++ /dev/null @@ -1,73 +0,0 @@ -const std = @import("std"); -const testing = std.testing; -const expectEqual = testing.expectEqual; -const expect = testing.expect; - -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (std.builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - -extern fn malloc(size: usize) callconv(.C) ?*c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; -extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; - -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { - return malloc(size); -} - -export fn roc_realloc(c_ptr: *c_void, old_size: usize, new_size: usize, alignment: u32) callconv(.C) ?*c_void { - return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size); -} - -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { - free(@alignCast(16, @ptrCast([*]u8, c_ptr))); -} - -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { - const stderr = std.io.getStdErr().writer(); - const msg = @ptrCast([*:0]const u8, c_ptr); - stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; - std.process.exit(0); -} - -const mem = std.mem; -const Allocator = mem.Allocator; - -extern fn roc__mainForHost_1_exposed(i64, *i64) void; - -const Unit = extern struct {}; - -pub export fn main() u8 { - const stdout = std.io.getStdOut().writer(); - const fib_number_to_find: u64 = 10; // find the nth Fibonacci number - const iterations: usize = 50; // number of times to repeatedly find that Fibonacci number - - // make space for the result - var callresult = 0; - var remaining_iterations = iterations; - - while (remaining_iterations > 0) { - // actually call roc to populate the callresult - roc__mainForHost_1_exposed(fib_number_to_find, &callresult); - - remaining_iterations -= 1; - } - - // stdout the final result - stdout.print( - "After calling the Roc app {d} times, the Fibonacci number at index {d} is {d}\n", - .{ iterations, fib_number_to_find, callresult }, - ) catch unreachable; - - return 0; -} From a6bc37eccaff7924a6b761d51d0b3e7f7ce4db00 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 25 Sep 2021 15:16:17 +0200 Subject: [PATCH 076/136] handle small strings --- examples/hello-web/platform/host.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hello-web/platform/host.zig b/examples/hello-web/platform/host.zig index 2623097836..bfd519e9de 100644 --- a/examples/hello-web/platform/host.zig +++ b/examples/hello-web/platform/host.zig @@ -60,7 +60,7 @@ pub fn main() u8 { roc__mainForHost_1_exposed(&callresult); // display the result using JavaScript - js_display_roc_string(callresult.str_bytes, callresult.str_len); + js_display_roc_string(callresult.asU8ptr(), callresult.len()); callresult.deinit(); From e6c137889a552f6f2c0ff408cbe7e63331b9e921 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 25 Sep 2021 12:19:56 -0700 Subject: [PATCH 077/136] Just disable gc-sections with surgical linker for rust hosts for now --- compiler/build/src/link.rs | 1 + examples/cli/platform/src/lib.rs | 2 +- examples/cli/platform/src/main.rs | 63 +----------------------- examples/hello-rust/platform/src/lib.rs | 3 +- examples/hello-rust/platform/src/main.rs | 50 +------------------ linker/README.md | 4 +- 6 files changed, 8 insertions(+), 115 deletions(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 4764493911..32c8a4c9f6 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -385,6 +385,7 @@ pub fn rebuild_host( command.arg("--release"); } let source_file = if shared_lib_path.is_some() { + command.env("RUSTFLAGS", "-C link-dead-code"); command.args(&["--bin", "host"]); "src/main.rs" } else { diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs index d313d6f424..d316e264d8 100644 --- a/examples/cli/platform/src/lib.rs +++ b/examples/cli/platform/src/lib.rs @@ -70,7 +70,7 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut } #[no_mangle] -pub extern "C" fn rust_main() -> isize { +pub extern "C" fn rust_main() -> i32 { let size = unsafe { roc_main_size() } as usize; let layout = Layout::array::(size).unwrap(); diff --git a/examples/cli/platform/src/main.rs b/examples/cli/platform/src/main.rs index 0d2ec6edb1..51175f934b 100644 --- a/examples/cli/platform/src/main.rs +++ b/examples/cli/platform/src/main.rs @@ -1,64 +1,3 @@ -#![allow(non_snake_case)] - -use core::ffi::c_void; -use roc_std::RocStr; - fn main() { - let mut result = host::rust_main(); - // This is stupid code that does nothing to avoid rust optimizing functions that roc needs away. - if result == 0x1234_5678_9ABC_DEF0 { - let roc_alloc_ptr: isize = unsafe { - std::mem::transmute( - host::roc_alloc as *const unsafe extern "C" fn(usize, u32) -> *mut c_void, - ) - }; - let roc_realloc_ptr: isize = unsafe { - std::mem::transmute( - host::roc_realloc - as *const unsafe extern "C" fn(*mut c_void, usize, usize, u32) -> *mut c_void, - ) - }; - let roc_dealloc_ptr: isize = unsafe { - std::mem::transmute(host::roc_dealloc as *const unsafe extern "C" fn(*mut c_void, u32)) - }; - let roc_panic_ptr: isize = unsafe { - std::mem::transmute(host::roc_panic as *const unsafe extern "C" fn(*mut c_void, u32)) - }; - let roc_memcpy_ptr: isize = unsafe { - std::mem::transmute( - host::roc_memcpy - as *const unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void, - ) - }; - let roc_memset_ptr: isize = unsafe { - std::mem::transmute( - host::roc_memset - as *const unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void, - ) - }; - let roc_fx_putLine_ptr: isize = unsafe { - std::mem::transmute(host::roc_fx_putLine as *const extern "C" fn(line: RocStr) -> ()) - }; - let roc_fx_getLine_ptr: isize = unsafe { - std::mem::transmute(host::roc_fx_getLine as *const extern "C" fn() -> RocStr) - }; - // I really want to use the equivalent of std::hint::black_box, but it is expirimental. - result = result ^ roc_alloc_ptr; - result = result ^ roc_realloc_ptr; - result = result ^ roc_dealloc_ptr; - result = result ^ roc_panic_ptr; - result = result ^ roc_memcpy_ptr; - result = result ^ roc_memset_ptr; - result = result ^ roc_fx_putLine_ptr; - result = result ^ roc_fx_getLine_ptr; - result = result ^ roc_alloc_ptr; - result = result ^ roc_realloc_ptr; - result = result ^ roc_dealloc_ptr; - result = result ^ roc_panic_ptr; - result = result ^ roc_memcpy_ptr; - result = result ^ roc_memset_ptr; - result = result ^ roc_fx_putLine_ptr; - result = result ^ roc_fx_getLine_ptr; - } - std::process::exit(result as i32); + std::process::exit(host::rust_main()); } diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs index d81192b939..341556bb4b 100644 --- a/examples/hello-rust/platform/src/lib.rs +++ b/examples/hello-rust/platform/src/lib.rs @@ -1,7 +1,6 @@ #![allow(non_snake_case)] use core::ffi::c_void; -use core::mem::MaybeUninit; use libc::c_char; use roc_std::RocStr; use std::ffi::CStr; @@ -55,7 +54,7 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut } #[no_mangle] -pub extern "C" fn rust_main() -> isize { +pub extern "C" fn rust_main() -> i32 { unsafe { let roc_str = roc_main(); diff --git a/examples/hello-rust/platform/src/main.rs b/examples/hello-rust/platform/src/main.rs index 49cefd692f..51175f934b 100644 --- a/examples/hello-rust/platform/src/main.rs +++ b/examples/hello-rust/platform/src/main.rs @@ -1,51 +1,3 @@ -use core::ffi::c_void; - fn main() { - let mut result = host::rust_main(); - // This is stupid code that does nothing to avoid rust optimizing functions that roc needs away. - if result == 0x1234_5678_9ABC_DEF0 { - let roc_alloc_ptr: isize = unsafe { - std::mem::transmute( - host::roc_alloc as *const unsafe extern "C" fn(usize, u32) -> *mut c_void, - ) - }; - let roc_realloc_ptr: isize = unsafe { - std::mem::transmute( - host::roc_realloc - as *const unsafe extern "C" fn(*mut c_void, usize, usize, u32) -> *mut c_void, - ) - }; - let roc_dealloc_ptr: isize = unsafe { - std::mem::transmute(host::roc_dealloc as *const unsafe extern "C" fn(*mut c_void, u32)) - }; - let roc_panic_ptr: isize = unsafe { - std::mem::transmute(host::roc_panic as *const unsafe extern "C" fn(*mut c_void, u32)) - }; - let roc_memcpy_ptr: isize = unsafe { - std::mem::transmute( - host::roc_memcpy - as *const unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void, - ) - }; - let roc_memset_ptr: isize = unsafe { - std::mem::transmute( - host::roc_memset - as *const unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void, - ) - }; - // I really want to use the equivalent of std::hint::black_box, but it is expirimental. - result = result ^ roc_alloc_ptr; - result = result ^ roc_realloc_ptr; - result = result ^ roc_dealloc_ptr; - result = result ^ roc_panic_ptr; - result = result ^ roc_memcpy_ptr; - result = result ^ roc_memset_ptr; - result = result ^ roc_alloc_ptr; - result = result ^ roc_realloc_ptr; - result = result ^ roc_dealloc_ptr; - result = result ^ roc_panic_ptr; - result = result ^ roc_memcpy_ptr; - result = result ^ roc_memset_ptr; - } - std::process::exit(result as i32); + std::process::exit(host::rust_main()); } diff --git a/linker/README.md b/linker/README.md index 150eef59d3..e5342c64ea 100644 --- a/linker/README.md +++ b/linker/README.md @@ -31,7 +31,6 @@ This linker is run in 2 phases: preprocessing and surigical linking. ## TODO (In a lightly prioritized order) -- Try to make rust hosts nicer. Currently making it expose functions is a huge pain. - Add Macho support - Honestly should be almost exactly the same code. This means we likely need to do a lot of refactoring to minimize the duplicate code. @@ -40,4 +39,7 @@ This linker is run in 2 phases: preprocessing and surigical linking. - As a prereq, we need roc building on Windows (I'm not sure it does currently). - Definitely a solid bit different than elf, but hopefully after refactoring for Macho, won't be that crazy to add. - Look at enabling completely in memory linking that could be used with `roc run` and/or `roc repl` +- Look more into roc hosts and keeping certain functions. Currently I just disabled linker garbage collection. + This works but adds 1.2MB (40%) to even a tiny app. It may be a size issue for large rust hosts. + Roc, for reference, adds 13MB (20%) when linked without garbage collection. - Add a feature to the compiler to make this linker optional. From 267f88626f71abe39713546f4cc8dd7bd730f41a Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Sep 2021 13:05:45 +0200 Subject: [PATCH 078/136] add failing test --- compiler/test_gen/src/gen_primitives.rs | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index d183b95d0b..10920b6f68 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -2906,3 +2906,30 @@ fn do_pass_bool_byte_closure_layout() { RocStr ); } + +#[test] +fn call_that_needs_closure_parameter() { + // here both p2 is lifted to the top-level, which means that `list` must be + // passed to it from `manyAux`. + assert_evals_to!( + indoc!( + r#" + Step state a : [ Loop state, Done a ] + + manyAux : List a -> [ Pair (Step (List a) (List a))] + manyAux = \list -> + p2 = \_ -> Pair (Done list) + + p2 "foo" + + manyAuxTest = (manyAux [ ]) == Pair (Loop [97]) + + runTest = \t -> if t then "PASS" else "FAIL" + + runTest manyAuxTest + "# + ), + RocStr::from_slice(b"FAIL"), + RocStr + ); +} From ed658ca2aaac3d80fd233b27b868a65b1f2149ed Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 25 Sep 2021 23:34:02 +0200 Subject: [PATCH 079/136] ugly but working fix for passing the closure env to a function --- compiler/mono/src/ir.rs | 118 ++++++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 22 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index bc620bdf74..ab0881eb36 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -6437,8 +6437,6 @@ fn call_by_name<'a>( assign_to_symbols(env, procs, layout_cache, iter, result) } } else { - let argument_layouts = lambda_set.extend_argument_list(env.arena, arg_layouts); - call_by_name_help( env, procs, @@ -6446,7 +6444,7 @@ fn call_by_name<'a>( proc_name, loc_args, lambda_set, - argument_layouts, + arg_layouts, ret_layout, layout_cache, assigned, @@ -6489,15 +6487,11 @@ fn call_by_name_help<'a>( ret_layout: &'a Layout<'a>, layout_cache: &mut LayoutCache<'a>, assigned: Symbol, - hole: &'a Stmt<'a>, + mut hole: &'a Stmt<'a>, ) -> Stmt<'a> { let original_fn_var = fn_var; let arena = env.arena; - // debug_assert!(!procs.module_thunks.contains(&proc_name), "{:?}", proc_name); - - let top_level_layout = ProcLayout::new(env.arena, argument_layouts, *ret_layout); - // the arguments given to the function, stored in symbols let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena); field_symbols.extend( @@ -6506,7 +6500,13 @@ fn call_by_name_help<'a>( .map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), ); - let field_symbols = field_symbols.into_bump_slice(); + // If required, add an extra argument to the layout that is the captured environment + // afterwards, we MUST make sure the number of arguments in the layout matches the + // number of arguments actually passed. + let top_level_layout = { + let argument_layouts = lambda_set.extend_argument_list(env.arena, argument_layouts); + ProcLayout::new(env.arena, argument_layouts, *ret_layout) + }; // the variables of the given arguments let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena); @@ -6535,6 +6535,8 @@ fn call_by_name_help<'a>( proc_name, ); + let field_symbols = field_symbols.into_bump_slice(); + let call = self::Call { call_type: CallType::ByName { name: proc_name, @@ -6573,6 +6575,9 @@ fn call_by_name_help<'a>( "see call_by_name for background (scroll down a bit), function is {:?}", proc_name, ); + + let field_symbols = field_symbols.into_bump_slice(); + let call = self::Call { call_type: CallType::ByName { name: proc_name, @@ -6625,6 +6630,8 @@ fn call_by_name_help<'a>( proc_name, ); + let field_symbols = field_symbols.into_bump_slice(); + let call = self::Call { call_type: CallType::ByName { name: proc_name, @@ -6643,6 +6650,19 @@ fn call_by_name_help<'a>( None => { let opt_partial_proc = procs.partial_procs.get(&proc_name); + /* + debug_assert_eq!( + argument_layouts.len(), + field_symbols.len(), + "Function {:?} is called with {} arguments, but the layout expects {}", + proc_name, + field_symbols.len(), + argument_layouts.len(), + ); + */ + + let field_symbols = field_symbols.into_bump_slice(); + match opt_partial_proc { Some(partial_proc) => { // TODO should pending_procs hold a Rc to avoid this .clone()? @@ -6655,20 +6675,74 @@ fn call_by_name_help<'a>( .specialized .insert((proc_name, top_level_layout), InProgress); + let captured = partial_proc.captured_symbols.clone(); + match specialize(env, procs, proc_name, layout_cache, pending, partial_proc) { - Ok((proc, layout)) => call_specialized_proc( - env, - procs, - proc_name, - proc, - layout, - field_symbols, - loc_args, - layout_cache, - assigned, - hole, - ), + Ok((proc, layout)) => { + // ugh + + if lambda_set.is_represented().is_some() { + let closure_data_symbol = env.unique_symbol(); + + let function_layout = ProcLayout::from_raw(env.arena, layout); + + procs.specialized.remove(&(proc_name, function_layout)); + + procs + .specialized + .insert((proc_name, function_layout), Done(proc)); + + let symbols = match captured { + CapturedSymbols::Captured(captured_symbols) => { + Vec::from_iter_in( + captured_symbols.iter().map(|x| x.0), + env.arena, + ) + .into_bump_slice() + } + CapturedSymbols::None => unreachable!(), + }; + + dbg!(field_symbols, argument_layouts); + let new_hole = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + field_symbols, + argument_layouts, + *ret_layout, + assigned, + hole, + ); + + construct_closure_data( + env, + lambda_set, + proc_name, + symbols, + closure_data_symbol, + env.arena.alloc(new_hole), + ) + + // field_symbols.push(closure_data_symbol); + // debug_assert_eq!(argument_layouts.len(), field_symbols.len()); + // hole + } else { + call_specialized_proc( + env, + procs, + proc_name, + proc, + layout, + field_symbols, + loc_args, + layout_cache, + assigned, + hole, + ) + } + } Err(SpecializeFailure { attempted_layout, problem: _, @@ -6881,7 +6955,7 @@ fn call_specialized_proc<'a>( debug_assert_eq!( function_layout.arguments.len(), field_symbols.len(), - "function {:?} with layout {:?} expects {:?} arguments, but is applied to {:?}", + "function {:?} with layout {:#?} expects {:?} arguments, but is applied to {:?}", proc_name, function_layout, function_layout.arguments.len(), From e9f2fb619341a72134e6e5db9aab0663c7a7bf5d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Sep 2021 12:45:52 +0200 Subject: [PATCH 080/136] actually define the arguments --- compiler/mono/src/ir.rs | 177 +++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 85 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index ab0881eb36..1a5283f08a 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -94,6 +94,12 @@ impl<'a> CapturedSymbols<'a> { } } +impl<'a> Default for CapturedSymbols<'a> { + fn default() -> Self { + CapturedSymbols::None + } +} + #[derive(Clone, Debug, PartialEq)] pub struct PendingSpecialization<'a> { solved_type: SolvedType, @@ -6208,6 +6214,8 @@ fn reuse_function_symbol<'a>( layout_cache, ); + // even though this function may not itself capture, + // unification may still cause it to have an extra argument construct_closure_data( env, lambda_set, @@ -6675,73 +6683,23 @@ fn call_by_name_help<'a>( .specialized .insert((proc_name, top_level_layout), InProgress); - let captured = partial_proc.captured_symbols.clone(); - match specialize(env, procs, proc_name, layout_cache, pending, partial_proc) { Ok((proc, layout)) => { - // ugh - - if lambda_set.is_represented().is_some() { - let closure_data_symbol = env.unique_symbol(); - - let function_layout = ProcLayout::from_raw(env.arena, layout); - - procs.specialized.remove(&(proc_name, function_layout)); - - procs - .specialized - .insert((proc_name, function_layout), Done(proc)); - - let symbols = match captured { - CapturedSymbols::Captured(captured_symbols) => { - Vec::from_iter_in( - captured_symbols.iter().map(|x| x.0), - env.arena, - ) - .into_bump_slice() - } - CapturedSymbols::None => unreachable!(), - }; - - dbg!(field_symbols, argument_layouts); - let new_hole = match_on_lambda_set( - env, - lambda_set, - closure_data_symbol, - field_symbols, - argument_layouts, - *ret_layout, - assigned, - hole, - ); - - construct_closure_data( - env, - lambda_set, - proc_name, - symbols, - closure_data_symbol, - env.arena.alloc(new_hole), - ) - - // field_symbols.push(closure_data_symbol); - // debug_assert_eq!(argument_layouts.len(), field_symbols.len()); - // hole - } else { - call_specialized_proc( - env, - procs, - proc_name, - proc, - layout, - field_symbols, - loc_args, - layout_cache, - assigned, - hole, - ) - } + // now we just call our freshly-specialized function + call_specialized_proc( + env, + procs, + proc_name, + proc, + lambda_set, + layout, + field_symbols, + loc_args, + layout_cache, + assigned, + hole, + ) } Err(SpecializeFailure { attempted_layout, @@ -6758,6 +6716,7 @@ fn call_by_name_help<'a>( procs, proc_name, proc, + lambda_set, attempted_layout, field_symbols, loc_args, @@ -6907,6 +6866,7 @@ fn call_specialized_proc<'a>( procs: &mut Procs<'a>, proc_name: Symbol, proc: Proc<'a>, + lambda_set: LambdaSet<'a>, layout: RawFunctionLayout<'a>, field_symbols: &'a [Symbol], loc_args: std::vec::Vec<(Variable, Located)>, @@ -6945,6 +6905,8 @@ fn call_specialized_proc<'a>( arguments: field_symbols, }; + // the closure argument is already added here (to get the right specialization) + // but now we need to remove it because the `match_on_lambda_set` will add it again build_call(env, call, assigned, Layout::LambdaSet(lambda_set), hole) } RawFunctionLayout::ZeroArgumentThunk(_) => { @@ -6952,30 +6914,75 @@ fn call_specialized_proc<'a>( } } } else { - debug_assert_eq!( - function_layout.arguments.len(), - field_symbols.len(), - "function {:?} with layout {:#?} expects {:?} arguments, but is applied to {:?}", - proc_name, - function_layout, - function_layout.arguments.len(), - field_symbols.len(), - ); - let call = self::Call { - call_type: CallType::ByName { - name: proc_name, - ret_layout: function_layout.result, - arg_layouts: function_layout.arguments, - specialization_id: env.next_call_specialization_id(), - }, - arguments: field_symbols, - }; - let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); - let result = build_call(env, call, assigned, function_layout.result, hole); + match procs + .partial_procs + .get(&proc_name) + .map(|pp| &pp.captured_symbols) + { + Some(&CapturedSymbols::Captured(captured_symbols)) => { + let symbols = Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena) + .into_bump_slice(); - assign_to_symbols(env, procs, layout_cache, iter, result) + let closure_data_symbol = env.unique_symbol(); + + // the closure argument is already added here (to get the right specialization) + // but now we need to remove it because the `match_on_lambda_set` will add it again + let mut argument_layouts = + Vec::from_iter_in(function_layout.arguments.iter().copied(), env.arena); + argument_layouts.pop().unwrap(); + + debug_assert_eq!(argument_layouts.len(), field_symbols.len(),); + + let new_hole = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + field_symbols, + argument_layouts.into_bump_slice(), + function_layout.result, + assigned, + hole, + ); + + let result = construct_closure_data( + env, + lambda_set, + proc_name, + symbols, + closure_data_symbol, + env.arena.alloc(new_hole), + ); + + assign_to_symbols(env, procs, layout_cache, iter, result) + } + _ => { + debug_assert_eq!( + function_layout.arguments.len(), + field_symbols.len(), + "function {:?} with layout {:#?} expects {:?} arguments, but is applied to {:?}", + proc_name, + function_layout, + function_layout.arguments.len(), + field_symbols.len(), + ); + + let call = self::Call { + call_type: CallType::ByName { + name: proc_name, + ret_layout: function_layout.result, + arg_layouts: function_layout.arguments, + specialization_id: env.next_call_specialization_id(), + }, + arguments: field_symbols, + }; + + let result = build_call(env, call, assigned, function_layout.result, hole); + + assign_to_symbols(env, procs, layout_cache, iter, result) + } + } } } From bd6e966833c0ee0e421c73a979313bdc0c62b150 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Sep 2021 13:28:03 +0200 Subject: [PATCH 081/136] clippy --- compiler/mono/src/ir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 1a5283f08a..c611ef99a2 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -6495,7 +6495,7 @@ fn call_by_name_help<'a>( ret_layout: &'a Layout<'a>, layout_cache: &mut LayoutCache<'a>, assigned: Symbol, - mut hole: &'a Stmt<'a>, + hole: &'a Stmt<'a>, ) -> Stmt<'a> { let original_fn_var = fn_var; let arena = env.arena; From 92b1d73bd6a29f29f7c2262a25342afafda6c9b4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 18 Sep 2021 15:17:20 +0100 Subject: [PATCH 082/136] Refactor build_proc into smaller helpers --- compiler/gen_wasm/src/backend.rs | 48 +++++++++++++++----------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 924c86b992..0bcb0f3e40 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,5 +1,5 @@ use parity_wasm::builder; -use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder}; +use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder}; use parity_wasm::elements::{ BlockType, Instruction, Instruction::*, Instructions, Local, ValueType, }; @@ -92,22 +92,11 @@ impl<'a> WasmBackend<'a> { } pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { - let ret_layout = WasmLayout::new(&proc.ret_layout); - - let ret_type = if let WasmLayout::StackMemory { .. } = ret_layout { - self.arg_types.push(PTR_TYPE); - None - } else { - Some(ret_layout.value_type()) - }; - - for (layout, symbol) in proc.args { - self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); - } + let signature_builder = self.build_signature(&proc); self.build_stmt(&proc.body, &proc.ret_layout)?; - let function_def = self.finalize(ret_type); + let function_def = self.finalize_proc(signature_builder); let location = self.builder.push_function(function_def); let function_index = location.body; self.proc_symbol_map.insert(sym, location); @@ -116,7 +105,24 @@ impl<'a> WasmBackend<'a> { Ok(function_index) } - fn finalize(&mut self, return_type: Option) -> FunctionDefinition { + fn build_signature(&mut self, proc: &Proc<'a>) -> SignatureBuilder { + let ret_layout = WasmLayout::new(&proc.ret_layout); + + let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { + self.arg_types.push(PTR_TYPE); + builder::signature() + } else { + builder::signature().with_result(ret_layout.value_type()) + }; + + for (layout, symbol) in proc.args { + self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); + } + + signature_builder.with_params(self.arg_types.clone()) + } + + fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); if self.stack_memory > 0 { @@ -138,18 +144,8 @@ impl<'a> WasmBackend<'a> { } final_instructions.push(Instruction::End); - let signature_builder = if let Some(t) = return_type { - builder::signature().with_result(t) - } else { - builder::signature() - }; - - let signature = signature_builder - .with_params(self.arg_types.clone()) - .build_sig(); - let function_def = builder::function() - .with_signature(signature) + .with_signature(signature_builder.build_sig()) .body() .with_locals(self.locals.clone()) .with_instructions(Instructions::new(final_instructions)) From ab4f28fd69d5c5be5b5ae911ff53129d3df4a2ac Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 19 Sep 2021 19:50:51 +0100 Subject: [PATCH 083/136] Comment out a test that isn't working --- compiler/gen_wasm/tests/wasm_records.rs | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index 9c776ecfaa..7d4bc9af3f 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -851,20 +851,20 @@ mod wasm_records { // ); // } - #[test] - fn update_single_element_record() { - assert_evals_to!( - indoc!( - r#" - rec = { foo: 42} + // #[test] + // fn update_single_element_record() { + // assert_evals_to!( + // indoc!( + // r#" + // rec = { foo: 42} - { rec & foo: rec.foo + 1 } - "# - ), - 43, - i64 - ); - } + // { rec & foo: rec.foo + 1 } + // "# + // ), + // 43, + // i64 + // ); + // } // #[test] // fn booleans_in_record() { From 286ca680e0d97500b81974cdc1cb11ee85a6fc32 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 19 Sep 2021 19:53:35 +0100 Subject: [PATCH 084/136] Tidy up load_literal --- compiler/gen_wasm/src/backend.rs | 62 +++++++++++++++----------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 0bcb0f3e40..35c68f4cc9 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -459,41 +459,35 @@ impl<'a> WasmBackend<'a> { } fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> { - match lit { - Literal::Bool(x) => { - self.instructions.push(I32Const(*x as i32)); - Ok(()) + let instruction = match lit { + Literal::Bool(x) => I32Const(*x as i32), + Literal::Byte(x) => I32Const(*x as i32), + Literal::Int(x) => match layout { + Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), + Layout::Builtin( + Builtin::Int32 + | Builtin::Int16 + | Builtin::Int8 + | Builtin::Int1 + | Builtin::Usize, + ) => I32Const(*x as i32), + x => { + return Err(format!("loading literal, {:?}, is not yet implemented", x)); + } + }, + Literal::Float(x) => match layout { + Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), + Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), + x => { + return Err(format!("loading literal, {:?}, is not yet implemented", x)); + } + }, + x => { + return Err(format!("loading literal, {:?}, is not yet implemented", x)); } - Literal::Byte(x) => { - self.instructions.push(I32Const(*x as i32)); - Ok(()) - } - Literal::Int(x) => { - let instruction = match layout { - Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), - Layout::Builtin( - Builtin::Int32 - | Builtin::Int16 - | Builtin::Int8 - | Builtin::Int1 - | Builtin::Usize, - ) => I32Const(*x as i32), - x => panic!("loading literal, {:?}, is not yet implemented", x), - }; - self.instructions.push(instruction); - Ok(()) - } - Literal::Float(x) => { - let instruction = match layout { - Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), - Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), - x => panic!("loading literal, {:?}, is not yet implemented", x), - }; - self.instructions.push(instruction); - Ok(()) - } - x => Err(format!("loading literal, {:?}, is not yet implemented", x)), - } + }; + self.instructions.push(instruction); + Ok(()) } fn build_call_low_level( From ae6cfe19927e66de031898b2968364f50b79e1e4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 19 Sep 2021 20:54:24 +0100 Subject: [PATCH 085/136] Moree implementations for Wasm32TestResult --- .../tests/helpers/wasm32_test_result.rs | 115 ++++++++++++++++-- 1 file changed, 105 insertions(+), 10 deletions(-) diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index a84bf48e89..7e73d5727e 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -46,11 +46,7 @@ macro_rules! build_wrapper_body_primitive { fn build_wrapper_body(main_function_index: u32) -> Vec { let size: i32 = 8; let mut instructions = Vec::with_capacity(16); - allocate_stack_frame( - &mut instructions, - size, - LocalId(STACK_POINTER_LOCAL_ID), - ); + allocate_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); instructions.extend([ // load result address to prepare for the store instruction later GetLocal(STACK_POINTER_LOCAL_ID), @@ -64,11 +60,7 @@ macro_rules! build_wrapper_body_primitive { // Return the result pointer GetLocal(STACK_POINTER_LOCAL_ID), ]); - free_stack_frame( - &mut instructions, - size, - LocalId(STACK_POINTER_LOCAL_ID), - ); + free_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); instructions.push(End); instructions } @@ -181,3 +173,106 @@ where ) } } + +impl Wasm32TestResult for (T, U, V, W) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y, Z) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, + Z: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH + + Z::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y, Z, A) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, + Z: Wasm32TestResult + FromWasm32Memory, + A: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH + + Z::ACTUAL_WIDTH + + A::ACTUAL_WIDTH, + ) + } +} From ecece45a837e3c74d748dd2ab6e3029aaf373dc1 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 26 Sep 2021 17:16:08 +0100 Subject: [PATCH 086/136] Add a helper method local_id_from_symbol --- compiler/gen_wasm/src/backend.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 35c68f4cc9..9577fd9703 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -224,6 +224,11 @@ impl<'a> WasmBackend<'a> { }) } + fn local_id_from_symbol(&self, sym: &Symbol) -> Result { + let SymbolStorage(local_id, _) = self.get_symbol_storage(sym)?; + Ok(*local_id) + } + fn load_from_symbol(&mut self, sym: &Symbol) -> Result<(), String> { let SymbolStorage(LocalId(local_id), _) = self.get_symbol_storage(sym)?; let id: u32 = *local_id; @@ -329,15 +334,12 @@ impl<'a> WasmBackend<'a> { } // the LocalId of the symbol that we match on - let matched_on = match self.symbol_storage_map.get(cond_symbol) { - Some(SymbolStorage(local_id, _)) => local_id.0, - None => unreachable!("symbol not defined: {:?}", cond_symbol), - }; + let matched_on = self.local_id_from_symbol(cond_symbol)?; // then, we jump whenever the value under scrutiny is equal to the value of a branch for (i, (value, _, _)) in branches.iter().enumerate() { // put the cond_symbol on the top of the stack - self.instructions.push(GetLocal(matched_on)); + self.instructions.push(GetLocal(matched_on.0)); self.instructions.push(I32Const(*value as i32)); @@ -404,12 +406,8 @@ impl<'a> WasmBackend<'a> { // put the arguments on the stack for (symbol, local_id) in arguments.iter().zip(locals.iter()) { - let argument = match self.symbol_storage_map.get(symbol) { - Some(SymbolStorage(local_id, _)) => local_id.0, - None => unreachable!("symbol not defined: {:?}", symbol), - }; - - self.instructions.push(GetLocal(argument)); + let argument = self.local_id_from_symbol(symbol)?; + self.instructions.push(GetLocal(argument.0)); self.instructions.push(SetLocal(local_id.0)); } From 39fda3e67585b73f79f28cd2737f821038f2b630 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 26 Sep 2021 19:16:54 +0100 Subject: [PATCH 087/136] Add more information to SymbolStorage SymbolStorage is function-specific, whereas WasmLayout is function-independent. SymbolStorage used to be a single-variant enum with a LocalId and a WasmLayout, but now we need more variants, so we can handle stack memory allocation.. - For values in stack memory, the storage needs to include an offset within the stack frame, but we don't need that for primitive values. - For function parameters, the allocated stack memory is in the caller's frame rather than the current frame, so we don't want size or offset. We could have kept a WasmLayout inside of SymbolStorage but that would have made possible a lot of invalid combinations. It seemed better to copy the fields we need. --- compiler/gen_wasm/src/backend.rs | 174 ++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 51 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 9577fd9703..392dd176ab 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -21,7 +21,52 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024; struct LabelId(u32); #[derive(Debug)] -struct SymbolStorage(LocalId, WasmLayout); +enum SymbolStorage { + ParamPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + ParamPointer { + local_id: LocalId, + }, + VarPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + VarStackMemory { + local_id: LocalId, + size: u32, + offset: u32, + }, + VarHeapMemory { + local_id: LocalId, + }, +} + +impl SymbolStorage { + fn local_id(&self) -> LocalId { + match self { + Self::ParamPrimitive { local_id, .. } => *local_id, + Self::ParamPointer { local_id, .. } => *local_id, + Self::VarPrimitive { local_id, .. } => *local_id, + Self::VarStackMemory { local_id, .. } => *local_id, + Self::VarHeapMemory { local_id, .. } => *local_id, + } + } + + #[allow(dead_code)] + fn value_type(&self) -> ValueType { + match self { + Self::ParamPrimitive { value_type, .. } => *value_type, + Self::VarPrimitive { value_type, .. } => *value_type, + Self::ParamPointer { .. } => ValueType::I32, + Self::VarStackMemory { .. } => ValueType::I32, + Self::VarHeapMemory { .. } => ValueType::I32, + } + } +} enum LocalKind { Parameter, @@ -164,39 +209,61 @@ impl<'a> WasmBackend<'a> { let local_index = (self.arg_types.len() + self.locals.len()) as u32; let local_id = LocalId(local_index); - match kind { + let storage = match kind { LocalKind::Parameter => { // Already stack-allocated by the caller if needed. self.arg_types.push(wasm_layout.value_type()); + match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive { + local_id, + value_type, + size, + }, + _ => SymbolStorage::ParamPointer { local_id }, + } } LocalKind::Variable => { self.locals.push(Local::new(1, wasm_layout.value_type())); - if let WasmLayout::StackMemory { - size, - alignment_bytes, - } = wasm_layout - { - let align = alignment_bytes as i32; - let mut offset = self.stack_memory; - offset += align - 1; - offset &= -align; - self.stack_memory = offset + (size - alignment_bytes) as i32; + match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive { + local_id, + value_type, + size, + }, - let frame_pointer = self.get_or_create_frame_pointer(); + WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { local_id }, - // initialise the local with the appropriate address - self.instructions.extend([ - GetLocal(frame_pointer.0), - I32Const(offset), - I32Add, - SetLocal(local_index), - ]); + WasmLayout::StackMemory { + size, + alignment_bytes, + } => { + let align = alignment_bytes as i32; + let mut offset = self.stack_memory; + offset += align - 1; + offset &= -align; + self.stack_memory = offset + (size - alignment_bytes) as i32; + + let frame_pointer = self.get_or_create_frame_pointer(); + + // initialise the local with the appropriate address + self.instructions.extend([ + GetLocal(frame_pointer.0), + I32Const(offset), + I32Add, + SetLocal(local_index), + ]); + + SymbolStorage::VarStackMemory { + local_id, + size, + offset: offset as u32, + } + } } } - } + }; - let storage = SymbolStorage(local_id, wasm_layout); self.symbol_storage_map.insert(symbol, storage); local_id @@ -225,14 +292,14 @@ impl<'a> WasmBackend<'a> { } fn local_id_from_symbol(&self, sym: &Symbol) -> Result { - let SymbolStorage(local_id, _) = self.get_symbol_storage(sym)?; - Ok(*local_id) + let storage = self.get_symbol_storage(sym)?; + Ok(storage.local_id()) } - fn load_from_symbol(&mut self, sym: &Symbol) -> Result<(), String> { - let SymbolStorage(LocalId(local_id), _) = self.get_symbol_storage(sym)?; - let id: u32 = *local_id; - self.instructions.push(GetLocal(id)); + fn load_symbol(&mut self, sym: &Symbol) -> Result<(), String> { + let storage = self.get_symbol_storage(sym)?; + let index: u32 = storage.local_id().0; + self.instructions.push(GetLocal(index)); Ok(()) } @@ -265,7 +332,9 @@ impl<'a> WasmBackend<'a> { if let WasmLayout::StackMemory { .. } = wasm_layout { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later - let storage = SymbolStorage(LocalId(0), wasm_layout); + let storage = SymbolStorage::ParamPointer { + local_id: LocalId(0), + }; self.symbol_storage_map.insert(*let_sym, storage); } self.build_expr(let_sym, expr, layout)?; @@ -287,30 +356,33 @@ impl<'a> WasmBackend<'a> { Stmt::Ret(sym) => { use crate::layout::WasmLayout::*; - let SymbolStorage(local_id, wasm_layout) = - self.symbol_storage_map.get(sym).unwrap(); + let storage = self.symbol_storage_map.get(sym).unwrap(); - match wasm_layout { - LocalOnly(_, _) | HeapMemory => { + match storage { + SymbolStorage::ParamPrimitive { local_id, .. } + | SymbolStorage::VarPrimitive { local_id, .. } + | SymbolStorage::ParamPointer { local_id, .. } + | SymbolStorage::VarHeapMemory { local_id, .. } => { self.instructions.push(GetLocal(local_id.0)); self.instructions.push(Return); } - StackMemory { - size, - alignment_bytes, - } => { - let from = local_id.clone(); - let to = LocalId(0); - let copy_size: u32 = *size; - let copy_alignment_bytes: u32 = *alignment_bytes; - copy_memory( - &mut self.instructions, - from, - to, - copy_size, - copy_alignment_bytes, - )?; + SymbolStorage::VarStackMemory { local_id, size, .. } => { + let ret_wasm_layout = WasmLayout::new(ret_layout); + if let StackMemory { alignment_bytes, .. } = ret_wasm_layout { + let from = local_id.clone(); + let to = LocalId(0); + let copy_size: u32 = *size; + copy_memory( + &mut self.instructions, + from, + to, + copy_size, + alignment_bytes, + )?; + } else { + panic!("Return layout doesn't match"); + } } } @@ -436,7 +508,7 @@ impl<'a> WasmBackend<'a> { }) => match call_type { CallType::ByName { name: func_sym, .. } => { for arg in *arguments { - self.load_from_symbol(arg)?; + self.load_symbol(arg)?; } let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( "Cannot find function {:?} called from {:?}", @@ -495,7 +567,7 @@ impl<'a> WasmBackend<'a> { return_layout: &Layout<'a>, ) -> Result<(), String> { for arg in args { - self.load_from_symbol(arg)?; + self.load_symbol(arg)?; } let wasm_layout = WasmLayout::new(return_layout); self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; @@ -513,7 +585,7 @@ impl<'a> WasmBackend<'a> { // For those, we'll need to pre-process each argument before the main op, // so simple arrays of instructions won't work. But there are common patterns. let instructions: &[Instruction] = match lowlevel { - // Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_from_symbol? + // Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_symbol? LowLevel::NumAdd => match return_value_type { ValueType::I32 => &[I32Add], ValueType::I64 => &[I64Add], From e0bd644c79c5cc31ffa0241427bd5785700d4dbe Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 27 Sep 2021 15:16:07 +0200 Subject: [PATCH 088/136] single threaded file loading with env var --- .github/workflows/benchmarks.yml | 1 + compiler/load/src/file.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 72cb2cbd22..375c8a87cd 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -7,6 +7,7 @@ name: Benchmarks env: RUST_BACKTRACE: 1 + ROC_NUM_WORKERS: 1 jobs: prep-dependency-container: diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 5aaefa463b..0a6fafe939 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -37,13 +37,13 @@ use roc_types::subs::{Subs, VarStore, Variable}; use roc_types::types::{Alias, Type}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; -use std::fs; use std::io; use std::iter; use std::path::{Path, PathBuf}; use std::str::from_utf8_unchecked; use std::sync::Arc; use std::time::{Duration, SystemTime}; +use std::{env, fs}; /// Default name for the binary generated for an app, if an invalid one was specified. const DEFAULT_APP_OUTPUT_PATH: &str = "app"; @@ -1351,7 +1351,12 @@ where // doing .max(1) on the entire expression guards against // num_cpus returning 0, while also avoiding wrapping // unsigned subtraction overflow. - let num_workers = num_cpus::get().max(2) - 1; + let default_num_workers = num_cpus::get().max(2) - 1; + + let num_workers = match env::var("ROC_NUM_WORKERS") { + Ok(env_str) => env_str.parse::().unwrap_or(default_num_workers), + Err(_) => default_num_workers, + }; let worker_arenas = arena.alloc(bumpalo::collections::Vec::with_capacity_in( num_workers, From 4026291945c70ce6ae73fa2ece2956dda6c802a6 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 27 Sep 2021 15:17:13 +0200 Subject: [PATCH 089/136] re-enjable benchmarks --- .github/workflows/benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 375c8a87cd..a7e7ac0ffe 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -43,4 +43,4 @@ jobs: run: cd ci/bench-runner && cargo build --release && cd ../.. - name: run benchmarks with regression check - run: echo "TODO re-enable benchmarks once race condition is fixed"#./ci/bench-runner/target/release/bench-runner --check-executables-changed + run: ./ci/bench-runner/target/release/bench-runner --check-executables-changed From 02bb9028efbcde53dafa3d05919218e82c1ffd33 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 28 Sep 2021 08:06:59 +0100 Subject: [PATCH 090/136] Returning records on the stack from Wasm dev backend! --- compiler/gen_wasm/src/backend.rs | 199 ++++++----- compiler/gen_wasm/src/layout.rs | 83 +---- compiler/gen_wasm/src/lib.rs | 45 +-- compiler/gen_wasm/src/storage.rs | 156 +++++++++ compiler/gen_wasm/tests/wasm_records.rs | 431 +++++++++++------------- 5 files changed, 504 insertions(+), 410 deletions(-) create mode 100644 compiler/gen_wasm/src/storage.rs diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 392dd176ab..278ca68966 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -11,6 +11,7 @@ use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; +use crate::storage::SymbolStorage; use crate::{allocate_stack_frame, copy_memory, free_stack_frame, LocalId, PTR_TYPE}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. @@ -20,54 +21,6 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024; #[derive(Clone, Copy, Debug)] struct LabelId(u32); -#[derive(Debug)] -enum SymbolStorage { - ParamPrimitive { - local_id: LocalId, - value_type: ValueType, - size: u32, - }, - ParamPointer { - local_id: LocalId, - }, - VarPrimitive { - local_id: LocalId, - value_type: ValueType, - size: u32, - }, - VarStackMemory { - local_id: LocalId, - size: u32, - offset: u32, - }, - VarHeapMemory { - local_id: LocalId, - }, -} - -impl SymbolStorage { - fn local_id(&self) -> LocalId { - match self { - Self::ParamPrimitive { local_id, .. } => *local_id, - Self::ParamPointer { local_id, .. } => *local_id, - Self::VarPrimitive { local_id, .. } => *local_id, - Self::VarStackMemory { local_id, .. } => *local_id, - Self::VarHeapMemory { local_id, .. } => *local_id, - } - } - - #[allow(dead_code)] - fn value_type(&self) -> ValueType { - match self { - Self::ParamPrimitive { value_type, .. } => *value_type, - Self::VarPrimitive { value_type, .. } => *value_type, - Self::ParamPointer { .. } => ValueType::I32, - Self::VarStackMemory { .. } => ValueType::I32, - Self::VarHeapMemory { .. } => ValueType::I32, - } - } -} - enum LocalKind { Parameter, Variable, @@ -205,7 +158,7 @@ impl<'a> WasmBackend<'a> { wasm_layout: WasmLayout, symbol: Symbol, kind: LocalKind, - ) -> LocalId { + ) -> SymbolStorage { let local_index = (self.arg_types.len() + self.locals.len()) as u32; let local_id = LocalId(local_index); @@ -219,7 +172,10 @@ impl<'a> WasmBackend<'a> { value_type, size, }, - _ => SymbolStorage::ParamPointer { local_id }, + _ => SymbolStorage::ParamPointer { + local_id, + wasm_layout, + }, } } LocalKind::Variable => { @@ -242,11 +198,13 @@ impl<'a> WasmBackend<'a> { let mut offset = self.stack_memory; offset += align - 1; offset &= -align; - self.stack_memory = offset + (size - alignment_bytes) as i32; + self.stack_memory = offset + size as i32; + // TODO: if we're creating the frame pointer just reuse the same local_id! let frame_pointer = self.get_or_create_frame_pointer(); // initialise the local with the appropriate address + // TODO: skip this the first time, no point adding zero offset! self.instructions.extend([ GetLocal(frame_pointer.0), I32Const(offset), @@ -258,15 +216,16 @@ impl<'a> WasmBackend<'a> { local_id, size, offset: offset as u32, + alignment_bytes, } } } } }; - self.symbol_storage_map.insert(symbol, storage); + self.symbol_storage_map.insert(symbol, storage.clone()); - local_id + storage } fn get_or_create_frame_pointer(&mut self) -> LocalId { @@ -334,17 +293,20 @@ impl<'a> WasmBackend<'a> { // Saves us from having to copy it later let storage = SymbolStorage::ParamPointer { local_id: LocalId(0), + wasm_layout, }; self.symbol_storage_map.insert(*let_sym, storage); } self.build_expr(let_sym, expr, layout)?; - self.instructions.push(Return); + self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack Ok(()) } Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - let local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable); + let local_id = self + .insert_local(wasm_layout, *sym, LocalKind::Variable) + .local_id(); self.build_expr(sym, expr, layout)?; self.instructions.push(SetLocal(local_id.0)); @@ -354,35 +316,37 @@ impl<'a> WasmBackend<'a> { } Stmt::Ret(sym) => { - use crate::layout::WasmLayout::*; + use crate::storage::SymbolStorage::*; let storage = self.symbol_storage_map.get(sym).unwrap(); match storage { - SymbolStorage::ParamPrimitive { local_id, .. } - | SymbolStorage::VarPrimitive { local_id, .. } - | SymbolStorage::ParamPointer { local_id, .. } - | SymbolStorage::VarHeapMemory { local_id, .. } => { - self.instructions.push(GetLocal(local_id.0)); - self.instructions.push(Return); + VarStackMemory { + local_id, + size, + alignment_bytes, + .. + } + | ParamPointer { + local_id, + wasm_layout: + WasmLayout::StackMemory { + size, + alignment_bytes, + .. + }, + } => { + let from = local_id.clone(); + let to = LocalId(0); + copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0)?; } - SymbolStorage::VarStackMemory { local_id, size, .. } => { - let ret_wasm_layout = WasmLayout::new(ret_layout); - if let StackMemory { alignment_bytes, .. } = ret_wasm_layout { - let from = local_id.clone(); - let to = LocalId(0); - let copy_size: u32 = *size; - copy_memory( - &mut self.instructions, - from, - to, - copy_size, - alignment_bytes, - )?; - } else { - panic!("Return layout doesn't match"); - } + ParamPrimitive { local_id, .. } + | VarPrimitive { local_id, .. } + | ParamPointer { local_id, .. } + | VarHeapMemory { local_id, .. } => { + self.instructions.push(GetLocal(local_id.0)); + self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack } } @@ -446,8 +410,9 @@ impl<'a> WasmBackend<'a> { let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); - let local_id = - self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable); + let local_id = self + .insert_local(wasm_layout, parameter.symbol, LocalKind::Variable) + .local_id(); jp_parameter_local_ids.push(local_id); } @@ -524,6 +489,8 @@ impl<'a> WasmBackend<'a> { x => Err(format!("the call type, {:?}, is not yet implemented", x)), }, + Expr::Struct(fields) => self.create_struct(sym, layout, fields), + x => Err(format!("Expression is not yet implemented {:?}", x)), } } @@ -560,6 +527,78 @@ impl<'a> WasmBackend<'a> { Ok(()) } + fn create_struct( + &mut self, + sym: &Symbol, + layout: &Layout<'a>, + fields: &'a [Symbol], + ) -> Result<(), String> { + let storage = self.get_symbol_storage(sym)?.to_owned(); + + if let Layout::Struct(field_layouts) = layout { + match storage { + SymbolStorage::VarStackMemory { local_id, size, .. } + | SymbolStorage::ParamPointer { + local_id, + wasm_layout: WasmLayout::StackMemory { size, .. }, + } => { + if size > 0 { + let mut relative_offset = 0; + for (field, _) in fields.iter().zip(field_layouts.iter()) { + relative_offset += self.copy_symbol_to_pointer_at_offset( + local_id, + relative_offset, + field, + )?; + } + } else { + return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); + } + } + _ => { + return Err(format!("Cannot create struct {:?} with storage {:?}", sym, storage)); + } + } + } else { + // Struct expression but not Struct layout => single element. Copy it. + let field_storage = self.get_symbol_storage(&fields[0])?.to_owned(); + self.copy_storage(&storage, &field_storage)?; + } + Ok(()) + } + + fn copy_symbol_to_pointer_at_offset( + &mut self, + to_ptr: LocalId, + to_offset: u32, + from_symbol: &Symbol, + ) -> Result { + let from_storage = self.get_symbol_storage(from_symbol)?.to_owned(); + from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset) + } + + fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) -> Result<(), String> { + let has_stack_memory = to.has_stack_memory(); + debug_assert!(from.has_stack_memory() == has_stack_memory); + + if !has_stack_memory { + debug_assert!(from.value_type() == to.value_type()); + self.instructions.push(GetLocal(from.local_id().0)); + self.instructions.push(SetLocal(to.local_id().0)); + Ok(()) + } else { + let (size, alignment_bytes) = from.stack_size_and_alignment(); + copy_memory( + &mut self.instructions, + from.local_id(), + to.local_id(), + size, + alignment_bytes, + 0, + ) + } + } + fn build_call_low_level( &mut self, lowlevel: &LowLevel, diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 576316a049..8a067c412f 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,10 +1,10 @@ -use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; +use parity_wasm::elements::ValueType; use roc_mono::layout::{Layout, UnionLayout}; -use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE}; +use crate::{PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum WasmLayout { // Primitive number value. Just a Wasm local, without any stack memory. // For example, Roc i8 is represented as Wasm i32. Store the type and the original size. @@ -79,81 +79,4 @@ impl WasmLayout { _ => 0, } } - - #[allow(dead_code)] - fn load(&self, offset: u32) -> Result { - use crate::layout::WasmLayout::*; - use ValueType::*; - - match self { - LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)), - - // TODO: Come back to this when we need to access fields of structs - // LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? - - HeapMemory => { - if PTR_TYPE == I64 { - Ok(I64Load(ALIGN_8, offset)) - } else { - Ok(I32Load(ALIGN_4, offset)) - } - } - - _ => Err(format!( - "Failed to generate load instruction for WasmLayout {:?}", - self - )), - } - } - - #[allow(dead_code)] - pub fn store(&self, offset: u32, instructions: &mut Vec) -> Result<(), String> { - use crate::layout::WasmLayout::*; - use ValueType::*; - - let mut result = Ok(()); - match self { - LocalOnly(I32, 4) => instructions.push(I32Store(ALIGN_4, offset)), - LocalOnly(I32, 2) => instructions.push(I32Store16(ALIGN_2, offset)), - LocalOnly(I32, 1) => instructions.push(I32Store8(ALIGN_1, offset)), - LocalOnly(I64, 8) => instructions.push(I64Store(ALIGN_8, offset)), - LocalOnly(F64, 8) => instructions.push(F64Store(ALIGN_8, offset)), - LocalOnly(F32, 4) => instructions.push(F32Store(ALIGN_4, offset)), - - StackMemory { - size, - alignment_bytes, - } => { - // TODO - // Need extra arguments for this case that we don't need for primitives. - // Maybe it should be somewhere we have more relevant context? - // Come back to it when we need to insert things into structs. - let from_ptr = LocalId(0); // TODO - let to_ptr = LocalId(0); // TODO - copy_memory(instructions, from_ptr, to_ptr, *size, *alignment_bytes)?; - } - - HeapMemory => { - if PTR_TYPE == I64 { - instructions.push(I64Store(ALIGN_8, offset)); - } else { - instructions.push(I32Store(ALIGN_4, offset)); - } - } - - _ => { - result = Err(format!( - "Failed to generate store instruction for WasmLayout {:?}", - self - )); - } - } - result - } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index d439fca3cc..4a22343f45 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,6 +1,7 @@ mod backend; pub mod from_wasm32_memory; mod layout; +mod storage; use bumpalo::Bump; use parity_wasm::builder; @@ -110,13 +111,13 @@ pub fn build_module_help<'a>( Ok((backend.builder, main_function_index)) } -fn encode_alignment(bytes: u32) -> Result { +fn encode_alignment(bytes: u32) -> u32 { match bytes { - 1 => Ok(ALIGN_1), - 2 => Ok(ALIGN_2), - 4 => Ok(ALIGN_4), - 8 => Ok(ALIGN_8), - _ => Err(format!("{:?}-byte alignment is not supported", bytes)), + 1 => ALIGN_1, + 2 => ALIGN_2, + 4 => ALIGN_4, + 8 => ALIGN_8, + _ => panic!("{:?}-byte alignment is not supported", bytes), } } @@ -124,32 +125,32 @@ fn copy_memory( instructions: &mut Vec, from_ptr: LocalId, to_ptr: LocalId, - size_with_alignment: u32, + size: u32, alignment_bytes: u32, + offset: u32, ) -> Result<(), String> { - let alignment_flag = encode_alignment(alignment_bytes)?; - let size = size_with_alignment - alignment_bytes; - let mut offset = 0; - while size - offset >= 8 { + let alignment_flag = encode_alignment(alignment_bytes); + let mut current_offset = offset; + while size - current_offset >= 8 { instructions.push(GetLocal(to_ptr.0)); instructions.push(GetLocal(from_ptr.0)); - instructions.push(I64Load(alignment_flag, offset)); - instructions.push(I64Store(alignment_flag, offset)); - offset += 8; + instructions.push(I64Load(alignment_flag, current_offset)); + instructions.push(I64Store(alignment_flag, current_offset)); + current_offset += 8; } - if size - offset >= 4 { + if size - current_offset >= 4 { instructions.push(GetLocal(to_ptr.0)); instructions.push(GetLocal(from_ptr.0)); - instructions.push(I32Load(alignment_flag, offset)); - instructions.push(I32Store(alignment_flag, offset)); - offset += 4; + instructions.push(I32Load(alignment_flag, current_offset)); + instructions.push(I32Store(alignment_flag, current_offset)); + current_offset += 4; } - while size - offset > 0 { + while size - current_offset > 0 { instructions.push(GetLocal(to_ptr.0)); instructions.push(GetLocal(from_ptr.0)); - instructions.push(I32Load8U(alignment_flag, offset)); - instructions.push(I32Store8(alignment_flag, offset)); - offset += 1; + instructions.push(I32Load8U(alignment_flag, current_offset)); + instructions.push(I32Store8(alignment_flag, current_offset)); + current_offset += 1; } Ok(()) } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs new file mode 100644 index 0000000000..24b2e7a87c --- /dev/null +++ b/compiler/gen_wasm/src/storage.rs @@ -0,0 +1,156 @@ +use crate::{copy_memory, layout::WasmLayout, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; + +#[derive(Debug, Clone)] +pub enum SymbolStorage { + ParamPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + ParamPointer { + local_id: LocalId, + wasm_layout: WasmLayout, + }, + VarPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + VarStackMemory { + local_id: LocalId, + size: u32, + offset: u32, + alignment_bytes: u32, + }, + VarHeapMemory { + local_id: LocalId, + }, +} + +impl SymbolStorage { + pub fn local_id(&self) -> LocalId { + match self { + Self::ParamPrimitive { local_id, .. } => *local_id, + Self::ParamPointer { local_id, .. } => *local_id, + Self::VarPrimitive { local_id, .. } => *local_id, + Self::VarStackMemory { local_id, .. } => *local_id, + Self::VarHeapMemory { local_id, .. } => *local_id, + } + } + + pub fn value_type(&self) -> ValueType { + match self { + Self::ParamPrimitive { value_type, .. } => *value_type, + Self::VarPrimitive { value_type, .. } => *value_type, + Self::ParamPointer { .. } => ValueType::I32, + Self::VarStackMemory { .. } => ValueType::I32, + Self::VarHeapMemory { .. } => ValueType::I32, + } + } + + pub fn has_stack_memory(&self) -> bool { + match self { + Self::ParamPointer { + wasm_layout: WasmLayout::StackMemory { .. }, + .. + } => true, + Self::ParamPointer { .. } => false, + Self::VarStackMemory { .. } => true, + Self::ParamPrimitive { .. } => false, + Self::VarPrimitive { .. } => false, + Self::VarHeapMemory { .. } => false, + } + } + + pub fn stack_size_and_alignment(&self) -> (u32, u32) { + match self { + Self::VarStackMemory { + size, + alignment_bytes, + .. + } + | Self::ParamPointer { + wasm_layout: + WasmLayout::StackMemory { + size, + alignment_bytes, + .. + }, + .. + } => (*size, *alignment_bytes), + + _ => (0, 0), + } + } + + pub fn copy_to_memory( + &self, + instructions: &mut Vec, + to_pointer: LocalId, + to_offset: u32, + ) -> Result { + match self { + Self::ParamPrimitive { + local_id, + value_type, + size, + .. + } + | Self::VarPrimitive { + local_id, + value_type, + size, + .. + } => { + let store_instruction = match (value_type, size) { + (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), + (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), + (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), + (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), + (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), + (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), + _ => { + return Err(format!("Cannot store {:?} with alignment of {:?}", value_type, size)); + } + }; + instructions.push(GetLocal(to_pointer.0)); + instructions.push(GetLocal(local_id.0)); + instructions.push(store_instruction); + Ok(*size) + } + + Self::ParamPointer { + local_id, + wasm_layout: + WasmLayout::StackMemory { + size, + alignment_bytes, + }, + } + | Self::VarStackMemory { + local_id, + size, + alignment_bytes, + .. + } => { + copy_memory( + instructions, + *local_id, + to_pointer, + *size, + *alignment_bytes, + to_offset, + )?; + Ok(*size) + } + + Self::ParamPointer { local_id, .. } | Self::VarHeapMemory { local_id, .. } => { + instructions.push(GetLocal(to_pointer.0)); + instructions.push(GetLocal(local_id.0)); + instructions.push(I32Store(ALIGN_4, to_offset)); + Ok(4) + } + } + } +} diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index 7d4bc9af3f..d9776f97f2 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -308,125 +308,100 @@ mod wasm_records { // ); // } // - // #[test] - // fn i64_record1_literal() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3 } - // "# - // ), - // 3, - // i64 - // ); - // } - - // #[test] - // fn i64_record2_literal() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3, y: 5 } - // "# - // ), - // (3, 5), - // (i64, i64) - // ); - // } - - // // #[test] - // // fn i64_record3_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { x: 3, y: 5, z: 17 } - // // "# - // // ), - // // (3, 5, 17), - // // (i64, i64, i64) - // // ); - // // } - - // #[test] - // fn f64_record2_literal() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3.1, y: 5.1 } - // "# - // ), - // (3.1, 5.1), - // (f64, f64) - // ); - // } - - // // #[test] - // // fn f64_record3_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { x: 3.1, y: 5.1, z: 17.1 } - // // "# - // // ), - // // (3.1, 5.1, 17.1), - // // (f64, f64, f64) - // // ); - // // } - - // // #[test] - // // fn bool_record4_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // record : { a : Bool, b : Bool, c : Bool, d : Bool } - // // record = { a: True, b: True, c : True, d : Bool } - - // // record - // // "# - // // ), - // // (true, false, false, true), - // // (bool, bool, bool, bool) - // // ); - // // } + #[test] + fn i64_record1_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3 } + "# + ), + 3, + i64 + ); + } #[test] - fn i64_record1_literal() { + fn i64_record2_literal() { assert_evals_to!( indoc!( r#" - { a: 3 } + { x: 3, y: 5 } "# ), - 3, - i64 + (3, 5), + (i64, i64) ); } - // // #[test] - // // fn i64_record9_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } - // // "# - // // ), - // // (3, 5, 17, 1, 9, 12, 13, 14, 15), - // // (i64, i64, i64, i64, i64, i64, i64, i64, i64) - // // ); - // // } + #[test] + fn i64_record3_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5, z: 17 } + "# + ), + (3, 5, 17), + (i64, i64, i64) + ); + } + + #[test] + fn f64_record2_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3.1, y: 5.1 } + "# + ), + (3.1, 5.1), + (f64, f64) + ); + } + + #[test] + fn f64_record3_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3.1, y: 5.1, z: 17.1 } + "# + ), + (3.1, 5.1, 17.1), + (f64, f64, f64) + ); + } + + #[test] + fn bool_record4_literal() { + assert_evals_to!( + indoc!( + r#" + record : { a : Bool, b : Bool, c : Bool, d : Bool } + record = { a: True, b: False, c : False, d : True } + + record + "# + ), + [true, false, false, true], + [bool; 4] + ); + } + + #[test] + fn i64_record9_literal() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } + "# + ), + [3, 5, 17, 1, 9, 12, 13, 14, 15], + [i64; 9] + ); + } - // // #[test] - // // fn f64_record3_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { x: 3.1, y: 5.1, z: 17.1 } - // // "# - // // ), - // // (3.1, 5.1, 17.1), - // // (f64, f64, f64) - // // ); - // // } #[test] fn bool_literal() { @@ -667,135 +642,135 @@ mod wasm_records { // ); // } - // #[test] - // fn return_record_2() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3, y: 5 } - // "# - // ), - // [3, 5], - // [i64; 2] - // ); - // } + #[test] + fn return_record_2() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5 } + "# + ), + [3, 5], + [i64; 2] + ); + } - // #[test] - // fn return_record_3() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3, y: 5, z: 4 } - // "# - // ), - // (3, 5, 4), - // (i64, i64, i64) - // ); - // } + #[test] + fn return_record_3() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5, z: 4 } + "# + ), + (3, 5, 4), + (i64, i64, i64) + ); + } - // #[test] - // fn return_record_4() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2 } - // "# - // ), - // [3, 5, 4, 2], - // [i64; 4] - // ); - // } + #[test] + fn return_record_4() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2 } + "# + ), + [3, 5, 4, 2], + [i64; 4] + ); + } - // #[test] - // fn return_record_5() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2, e: 1 } - // "# - // ), - // [3, 5, 4, 2, 1], - // [i64; 5] - // ); - // } + #[test] + fn return_record_5() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1 } + "# + ), + [3, 5, 4, 2, 1], + [i64; 5] + ); + } - // #[test] - // fn return_record_6() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } - // "# - // ), - // [3, 5, 4, 2, 1, 7], - // [i64; 6] - // ); - // } + #[test] + fn return_record_6() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } + "# + ), + [3, 5, 4, 2, 1, 7], + [i64; 6] + ); + } - // #[test] - // fn return_record_7() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } - // "# - // ), - // [3, 5, 4, 2, 1, 7, 8], - // [i64; 7] - // ); - // } + #[test] + fn return_record_7() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } + "# + ), + [3, 5, 4, 2, 1, 7, 8], + [i64; 7] + ); + } - // #[test] - // fn return_record_float_int() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3.14, b: 0x1 } - // "# - // ), - // (3.14, 0x1), - // (f64, i64) - // ); - // } + #[test] + fn return_record_float_int() { + assert_evals_to!( + indoc!( + r#" + { a: 3.14, b: 0x1 } + "# + ), + (3.14, 0x1), + (f64, i64) + ); + } - // #[test] - // fn return_record_int_float() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 0x1, b: 3.14 } - // "# - // ), - // (0x1, 3.14), - // (i64, f64) - // ); - // } + #[test] + fn return_record_int_float() { + assert_evals_to!( + indoc!( + r#" + { a: 0x1, b: 3.14 } + "# + ), + (0x1, 3.14), + (i64, f64) + ); + } - // #[test] - // fn return_record_float_float() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 6.28, b: 3.14 } - // "# - // ), - // (6.28, 3.14), - // (f64, f64) - // ); - // } + #[test] + fn return_record_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14 } + "# + ), + (6.28, 3.14), + (f64, f64) + ); + } - // #[test] - // fn return_record_float_float_float() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 6.28, b: 3.14, c: 0.1 } - // "# - // ), - // (6.28, 3.14, 0.1), - // (f64, f64, f64) - // ); - // } + #[test] + fn return_record_float_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14, c: 0.1 } + "# + ), + (6.28, 3.14, 0.1), + (f64, f64, f64) + ); + } // #[test] // fn return_nested_record() { From 45c9dc86176d2b616889e8040c7c026034bfc8b7 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 28 Sep 2021 19:06:47 +0100 Subject: [PATCH 091/136] update gen_wasm README --- compiler/gen_wasm/README.md | 66 ++++++++++++++++++++------------ compiler/gen_wasm/src/backend.rs | 2 +- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/compiler/gen_wasm/README.md b/compiler/gen_wasm/README.md index 50018f8758..a3f22297b6 100644 --- a/compiler/gen_wasm/README.md +++ b/compiler/gen_wasm/README.md @@ -3,48 +3,57 @@ ## Plan - Initial bringup - - Get a wasm backend working for some of the number tests. - - Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time. -- Improve the fundamentals + - [x] Get a wasm backend working for some of the number tests. + - [x] Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time. +- Get the fundamentals working + - [x] Come up with a way to do control flow - [x] Flesh out the details of value representations between local variables and stack memory - - [ ] Set up a way to write tests with any return value rather than just i64 and f64 - - [ ] Figure out relocations for linking object files - - [ ] Think about the Wasm module builder library we're using, are we happy with it? + - [x] Set up a way to write tests with any return value rather than just i64 and f64 + - [x] Implement stack memory + - [x] Push and pop stack frames + - [x] Deal with returning structs + - [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc. + - [ ] Ensure early Return statements don't skip stack cleanup + - [ ] Vendor-in parity_wasm library so that we can use `bumpalo::Vec` + - [ ] Implement relocations + - Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec` rather than a `Vec`. It may be worth serialising each instruction as it is inserted. + +- Refactor for code sharing with CPU backends + + - [ ] Implement a `scan_ast` pre-pass like `Backend` does, but for reusing Wasm locals rather than CPU registers + - [ ] Extract a trait from `WasmBackend` that looks as similar as possible to `Backend`, to prepare for code sharing + - [ ] Refactor to actually share code between `WasmBackend` and `Backend` if it seems feasible + - Integration - Move wasm files to `gen_dev/src/wasm` - Share tests between wasm and x64, with some way of saying which tests work on which backends, and dispatching to different eval helpers based on that. - Get `build_module` in object_builder.rs to dispatch to the wasm generator (adding some Wasm options to the `Triple` struct) - Get `build_module` to write to a file, or maybe return `Vec`, instead of returning an Object structure -- Code sharing - - Try to ensure that both Wasm and x64 use the same `Backend` trait so that we can share code. - - We need to work towards this after we've progressed a bit more with Wasm and gained more understanding and experience of the differences. - - We will have to think about how to deal with the `Backend` code that doesn't apply to Wasm. Perhaps we will end up with more traits like `RegisterBackend` / `StackBackend` or `NativeBackend` / `WasmBackend`, and perhaps even some traits to do with backends that support jumps and those that don't. ## Structured control flow -🚨 **This is an area that could be tricky** 🚨 - One of the security features of WebAssembly is that it does not allow unrestricted "jumps" to anywhere you like. It does not have an instruction for that. All of the [control instructions][control-inst] can only implement "structured" control flow, and have names like `if`, `loop`, `block` that you'd normally associate with high-level languages. There are branch (`br`) instructions that can jump to labelled blocks within the same function, but the blocks have to be nested in sensible ways. [control-inst]: https://webassembly.github.io/spec/core/syntax/instructions.html#control-instructions -Implications: +This way of representing control flow is similar to parts of the Roc AST like `When`, `If` and `LetRec`. But Mono IR converts this to jumps and join points, which are more of a Control Flow Graph than a tree. We need to map back from graph to a tree again in the Wasm backend. -Roc, like most modern languages, is already enforcing structured control flow in the source program. Constructs from the Roc AST like `When`, `If` and `LetRec` can all be converted straightforwardly to Wasm constructs. +Our solution is to wrap all joinpoint/jump graphs in an outer `loop`, with nested `block`s inside it. -However the Mono IR converts this to jumps and join points, which are more of a Control Flow Graph than a tree. That doesn't map so directly to the Wasm structures. This is such a common issue for compiler back-ends that the WebAssembly compiler toolkit `binaryen` has an [API for control-flow graphs][cfg-api]. We're not using `binaryen` right now. It's a C++ library, though it does have a (very thin and somewhat hard-to-use) [Rust wrapper][binaryen-rs]. We should probably investigate this area sooner rather than later. If relooping turns out to be necessary or difficult, we might need to switch from parity_wasm to binaryen. +### Possible future optimisations -> By the way, it's not obvious how to pronounce "binaryen" but apparently it rhymes with "Targaryen", the family name from the "Game of Thrones" TV series +There are other algorithms available that may result in more optimised control flow. We are not focusing on that for our development backend, but here are some notes for future reference. + +The WebAssembly compiler toolkit `binaryen` has an [API for control-flow graphs][cfg-api]. We're not using `binaryen` right now. It's a C++ library, though it does have a (very thin and somewhat hard-to-use) [Rust wrapper][binaryen-rs]. Binaryen's control-flow graph API implements the "Relooper" algorithm developed by the Emscripten project and described in [this paper](https://github.com/emscripten-core/emscripten/blob/main/docs/paper.pdf). + +> By the way, apparently "binaryen" rhymes with "Targaryen", the family name from the "Game of Thrones" TV series + +There is also an improvement on Relooper called ["Stackifier"](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2). It can reorder the joinpoints and jumps to make code more efficient. (It is also has things Roc wouldn't need but C++ does, like support for "irreducible" graphs that include `goto`). [cfg-api]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api [binaryen-rs]: https://crates.io/crates/binaryen -Binaryen's control-flow graph API implements the "Relooper" algorithm developed by the Emscripten project and described in [this paper](https://github.com/emscripten-core/emscripten/blob/main/docs/paper.pdf). - -There is an alternative algorithm that is supposed to be an improvement on Relooper, called ["Stackifier"](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2). - - ## Stack machine vs register machine Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can oly operate on whatever data is at the top of the stack. @@ -113,6 +122,7 @@ $ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm ``` The optimised functions have no local variables, and the code shrinks to about 60% of its original size. + ``` (func (;0;) (param i64 i64) (result i64) local.get 0 @@ -143,7 +153,7 @@ When we are talking about how we store values in _memory_, I'll use the term _st Of course our program can use another area of memory as a heap as well. WebAssembly doesn't mind how you divide up your memory. It just gives you some memory and some instructions for loading and storing. -## Function calls +## Calling conventions & stack memory In WebAssembly you call a function by pushing arguments to the stack and then issuing a `call` instruction, which specifies a function index. The VM knows how many values to pop off the stack by examining the _type_ of the function. In our example earlier, `Num.add` had the type `[i64 i64] → [i64]` so it expects to find two i64's on the stack and pushes one i64 back as the result. Remember, the runtime engine will validate the module before running it, and if your generated code is trying to call a function at a point in the program where the wrong value types are on the stack, it will fail validation. @@ -151,11 +161,17 @@ Function arguments are restricted to the four value types, `i32`, `i64`, `f32` a That's all great for primitive values but what happens when we want to pass more complex data structures between functions? -Well, remember, "stack memory" is not a special kind of memory in WebAssembly, it's just an area of our memory where we _decide_ that we want to implement a stack data structure. So we can implement it however we want. A good choice would be to make our stack frame look the same as it would when we're targeting a CPU, except without the return address (since there's no need for one). We can also decide to pass numbers through the machine stack rather than in stack memory, since that takes fewer instructions. +Well, remember, "stack memory" is not a special kind of memory in WebAssembly, and is separate from the VM stack. It's just an area of our memory where we implement a stack data structure. But there are some conventions that it makes sense to follow so that we can easily link to Wasm code generated from Zig or other languages. -The only other thing we need is a stack pointer. On CPU targets, there's often have a specific "stack pointer" register. WebAssembly has no equivalent to that, but we can use a `global` variable. +### Observations from compiled C code -The system I've outlined above is based on my experience of compiling C to WebAssembly via the Emscripten toolchain (which is built on top of clang). It's also in line with what the WebAssembly project describes [here](https://github.com/WebAssembly/design/blob/main/Rationale.md#locals). +- `global 0` is used as the stack pointer, and its value is normally copied to a `local` as well (presumably because locals tend to be assigned to CPU registers) +- Stack memory grows downwards +- If a C function returns a struct, the compiled WebAssembly function has no return value, but instead has an extra _argument_. The argument is an `i32` pointer to space allocated in the caller's stack, that the called function can write to. +- There is no maximum number of arguments for a WebAssembly function, and arguments are not passed via _stack memory_. This makes sense because the _VM stack_ has no size limit. It's like having a CPU with an unlimited number of registers. +- Stack memory is only used for allocating local variables, not for passing arguments. And it's only used for values that cannot be stored in one of WebAssembly's primitive values (`i32`, `i64`, `f32`, `f64`). + +These observations are based on experiments compiling C to WebAssembly via the Emscripten toolchain (which is built on top of clang). It's also in line with what the WebAssembly project describes [here](https://github.com/WebAssembly/design/blob/main/Rationale.md#locals). ## Modules vs Instances diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 278ca68966..53a3f429f4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -204,7 +204,7 @@ impl<'a> WasmBackend<'a> { let frame_pointer = self.get_or_create_frame_pointer(); // initialise the local with the appropriate address - // TODO: skip this the first time, no point adding zero offset! + // TODO: skip this the first time, no point generating code to add zero offset! self.instructions.extend([ GetLocal(frame_pointer.0), I32Const(offset), From 7ea59ad9d44f87ad82febc82c73a6a9cf403731b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 28 Sep 2021 23:16:34 +0100 Subject: [PATCH 092/136] PR tidy-ups --- compiler/gen_wasm/src/backend.rs | 24 ++++++++++++---------- compiler/gen_wasm/src/layout.rs | 2 +- compiler/gen_wasm/src/lib.rs | 12 +++++++++-- compiler/gen_wasm/src/storage.rs | 5 ++++- compiler/gen_wasm/tests/wasm_records.rs | 27 ++++++++++++------------- 5 files changed, 41 insertions(+), 29 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 53a3f429f4..920adae7b3 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -12,7 +12,9 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; -use crate::{allocate_stack_frame, copy_memory, free_stack_frame, LocalId, PTR_TYPE}; +use crate::{ + allocate_stack_frame, copy_memory, free_stack_frame, round_up_to_alignment, LocalId, PTR_TYPE, +}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -142,15 +144,13 @@ impl<'a> WasmBackend<'a> { } final_instructions.push(Instruction::End); - let function_def = builder::function() + builder::function() .with_signature(signature_builder.build_sig()) .body() .with_locals(self.locals.clone()) .with_instructions(Instructions::new(final_instructions)) .build() // body - .build(); // function - - function_def + .build() // function } fn insert_local( @@ -194,10 +194,9 @@ impl<'a> WasmBackend<'a> { size, alignment_bytes, } => { - let align = alignment_bytes as i32; - let mut offset = self.stack_memory; - offset += align - 1; - offset &= -align; + let offset = + round_up_to_alignment(self.stack_memory, alignment_bytes as i32); + self.stack_memory = offset + size as i32; // TODO: if we're creating the frame pointer just reuse the same local_id! @@ -336,7 +335,7 @@ impl<'a> WasmBackend<'a> { .. }, } => { - let from = local_id.clone(); + let from = *local_id; let to = LocalId(0); copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0)?; } @@ -556,7 +555,10 @@ impl<'a> WasmBackend<'a> { } } _ => { - return Err(format!("Cannot create struct {:?} with storage {:?}", sym, storage)); + return Err(format!( + "Cannot create struct {:?} with storage {:?}", + sym, storage + )); } } } else { diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 8a067c412f..df59b80eb1 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -31,7 +31,7 @@ impl WasmLayout { Layout::Builtin(Int64) => Self::LocalOnly(I64, size), - Layout::Builtin(Float32 | Float16) => Self::LocalOnly(F32, size), + Layout::Builtin(Float32) => Self::LocalOnly(F32, size), Layout::Builtin(Float64) => Self::LocalOnly(F64, size), diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 4a22343f45..d5d680e283 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -155,12 +155,20 @@ fn copy_memory( Ok(()) } +/// Round up to alignment_bytes (assumed to be a power of 2) +pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { + let mut aligned = unaligned; + aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary + aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0 + aligned +} + pub fn allocate_stack_frame( instructions: &mut Vec, size: i32, local_frame_pointer: LocalId, ) { - let aligned_size = (size + STACK_ALIGNMENT_BYTES - 1) & (-STACK_ALIGNMENT_BYTES); + let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); instructions.extend([ GetGlobal(STACK_POINTER_GLOBAL_ID), I32Const(aligned_size), @@ -175,7 +183,7 @@ pub fn free_stack_frame( size: i32, local_frame_pointer: LocalId, ) { - let aligned_size = (size + STACK_ALIGNMENT_BYTES - 1) & (-STACK_ALIGNMENT_BYTES); + let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); instructions.extend([ GetLocal(local_frame_pointer.0), I32Const(aligned_size), diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 24b2e7a87c..500f178e80 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -111,7 +111,10 @@ impl SymbolStorage { (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), _ => { - return Err(format!("Cannot store {:?} with alignment of {:?}", value_type, size)); + return Err(format!( + "Cannot store {:?} with alignment of {:?}", + value_type, size + )); } }; instructions.push(GetLocal(to_pointer.0)); diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index d9776f97f2..5ae26092ef 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -307,19 +307,19 @@ mod wasm_records { // () // ); // } - // - #[test] - fn i64_record1_literal() { - assert_evals_to!( - indoc!( - r#" - { x: 3 } - "# - ), - 3, - i64 - ); - } + + #[test] + fn i64_record1_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3 } + "# + ), + 3, + i64 + ); + } #[test] fn i64_record2_literal() { @@ -402,7 +402,6 @@ mod wasm_records { ); } - #[test] fn bool_literal() { assert_evals_to!( From b32a42f05a4bf52f8c5721342be0d06e2d9b2b8f Mon Sep 17 00:00:00 2001 From: Kofi Gumbs Date: Wed, 29 Sep 2021 17:32:42 -0400 Subject: [PATCH 093/136] Add Str.repeat builtin --- compiler/builtins/bitcode/src/main.zig | 1 + compiler/builtins/bitcode/src/str.zig | 17 ++++++++++++++++ compiler/builtins/src/bitcode.rs | 1 + compiler/builtins/src/std.rs | 7 +++++++ compiler/can/src/builtins.rs | 21 +++++++++++++++++++ compiler/gen_llvm/src/llvm/build.rs | 8 +++++++- compiler/gen_llvm/src/llvm/build_str.rs | 12 +++++++++++ compiler/module/src/low_level.rs | 27 +++++++++++++------------ compiler/module/src/symbol.rs | 1 + compiler/mono/src/borrow.rs | 1 + compiler/test_gen/src/gen_str.rs | 27 +++++++++++++++++++++++++ 11 files changed, 109 insertions(+), 14 deletions(-) diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 8fe350f16d..0841a8e8f5 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -101,6 +101,7 @@ comptime { exportStrFn(str.strToUtf8C, "to_utf8"); exportStrFn(str.fromUtf8C, "from_utf8"); exportStrFn(str.fromUtf8RangeC, "from_utf8_range"); + exportStrFn(str.repeat, "repeat"); } // Utils diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 2172e693d7..112c74cd5b 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -866,6 +866,23 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool { return true; } + +// Str.repeat +pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr { + const bytes_len = string.len(); + const bytes_ptr = string.asU8ptr(); + + var ret_string = RocStr.allocate(.Clone, count * bytes_len); + var ret_string_ptr = ret_string.asU8ptr(); + + var i: usize = 0; + while (i < count) : (i += 1) { + @memcpy(ret_string_ptr + (i * bytes_len), bytes_ptr, bytes_len); + } + + return ret_string; +} + // Str.startsWithCodePt pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool { const bytes_ptr = string.asU8ptr(); diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index b45d822c69..fd1ac7b17c 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -28,6 +28,7 @@ pub const STR_EQUAL: &str = "roc_builtins.str.equal"; pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8"; pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8"; pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range"; +pub const STR_REPEAT: &str = "roc_builtins.str.repeat"; pub const DICT_HASH: &str = "roc_builtins.dict.hash"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 98762b568b..7615879227 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -618,6 +618,13 @@ pub fn types() -> MutMap { Box::new(str_type()) ); + // repeat : Str, Nat -> Str + add_top_level_function_type!( + Symbol::STR_REPEAT, + vec![str_type(), nat_type()], + Box::new(str_type()) + ); + // fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* { let bad_utf8 = SolvedType::TagUnion( diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index fe45f1f673..d4fff2ef74 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -66,6 +66,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option STR_FROM_UTF8_RANGE => str_from_utf8_range, STR_TO_UTF8 => str_to_utf8, STR_FROM_FLOAT=> str_from_float, + STR_REPEAT => str_repeat, LIST_LEN => list_len, LIST_GET => list_get, LIST_SET => list_set, @@ -1233,6 +1234,26 @@ fn str_split(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Str.repeat : Str, Nat -> Str +fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def { + let str_var = var_store.fresh(); + let nat_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::StrRepeat, + args: vec![(str_var, Var(Symbol::ARG_1)), (nat_var, Var(Symbol::ARG_2))], + ret_var: str_var, + }; + + defn( + symbol, + vec![(str_var, Symbol::ARG_1), (nat_var, Symbol::ARG_2)], + var_store, + body, + str_var, + ) +} + /// Str.concat : Str, Str -> Str fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def { let str_var = var_store.fresh(); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 4864b04fcb..9864f277a8 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -15,7 +15,7 @@ use crate::llvm::build_list::{ }; use crate::llvm::build_str::{ empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, - str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_split, + str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split, str_starts_with, str_starts_with_code_point, str_to_utf8, }; use crate::llvm::compare::{generic_eq, generic_neq}; @@ -4853,6 +4853,12 @@ fn run_low_level<'a, 'ctx, 'env>( str_to_utf8(env, string.into_struct_value()) } + StrRepeat => { + // Str.repeat : Str, Nat -> Str + debug_assert_eq!(args.len(), 2); + + str_repeat(env, scope, args[0], args[1]) + } StrSplit => { // Str.split : Str, Str -> List Str debug_assert_eq!(args.len(), 2); diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 80bfa0fa3a..15fa225458 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -12,6 +12,18 @@ use super::build::load_symbol; pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8); +/// Str.repeat : Str, Nat -> Str +pub fn str_repeat<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + scope: &Scope<'a, 'ctx>, + str_symbol: Symbol, + count_symbol: Symbol, +) -> BasicValueEnum<'ctx> { + let str_c_abi = str_symbol_to_c_abi(env, scope, str_symbol); + let count = load_symbol(scope, &count_symbol); + call_bitcode_fn(env, &[str_c_abi.into(), count.into()], bitcode::STR_REPEAT) +} + /// Str.split : Str, Str -> List Str pub fn str_split<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 583e6017c1..7dec1f4ccf 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -15,6 +15,7 @@ pub enum LowLevel { StrFromUtf8, StrFromUtf8Range, StrToUtf8, + StrRepeat, StrFromFloat, ListLen, ListGetUnsafe, @@ -114,19 +115,19 @@ impl LowLevel { match self { StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 - | StrFromUtf8Range | StrToUtf8 | StrFromFloat | ListLen | ListGetUnsafe | ListSet - | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains - | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap | DictSize | DictEmpty - | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues - | DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap - | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap - | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked - | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos - | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling - | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd - | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBytesToU16 - | NumBytesToU32 | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not - | Hash | ExpectTrue => false, + | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe + | ListSet | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat + | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap + | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe + | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference + | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap + | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt + | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf + | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound + | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan + | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy + | NumShiftRightBy | NumBytesToU16 | NumBytesToU32 | NumShiftRightZfBy | NumIntCast + | Eq | NotEq | And | Or | Not | Hash | ExpectTrue => false, ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index aaa9ce321d..6e825a9953 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -927,6 +927,7 @@ define_builtins! { 16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime 18 STR_FROM_UTF8_RANGE: "fromUtf8Range" + 19 STR_REPEAT: "repeat" } 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 89d6ae2446..45f5eb9c0d 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -1013,6 +1013,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { StrFromUtf8 => arena.alloc_slice_copy(&[owned]), StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrToUtf8 => arena.alloc_slice_copy(&[owned]), + StrRepeat => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]), Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]), DictSize => arena.alloc_slice_copy(&[borrowed]), diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 2b5bdb45ad..8aca3288fd 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -949,3 +949,30 @@ fn str_from_utf8_range_count_too_high_for_start() { RocStr ); } + +#[test] +fn str_repeat() { + assert_evals_to!( + indoc!(r#"Str.repeat "Roc" 3"#), + RocStr::from("RocRocRoc"), + RocStr + ); +} + +#[test] +fn str_repeat_empty_string() { + assert_evals_to!( + indoc!(r#"Str.repeat "" 3"#), + RocStr::from(""), + RocStr + ); +} + +#[test] +fn str_repeat_zero_times() { + assert_evals_to!( + indoc!(r#"Str.repeat "Roc" 0"#), + RocStr::from(""), + RocStr + ); +} From 0cdafa16231ae7cee92a693a54170b80c6401df3 Mon Sep 17 00:00:00 2001 From: Kofi Gumbs Date: Wed, 29 Sep 2021 21:54:06 -0400 Subject: [PATCH 094/136] Add Str.repeat test for big strings --- compiler/test_gen/src/gen_str.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 8aca3288fd..292946403e 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -951,7 +951,7 @@ fn str_from_utf8_range_count_too_high_for_start() { } #[test] -fn str_repeat() { +fn str_repeat_small() { assert_evals_to!( indoc!(r#"Str.repeat "Roc" 3"#), RocStr::from("RocRocRoc"), @@ -960,19 +960,20 @@ fn str_repeat() { } #[test] -fn str_repeat_empty_string() { +fn str_repeat_big() { assert_evals_to!( - indoc!(r#"Str.repeat "" 3"#), - RocStr::from(""), + indoc!(r#"Str.repeat "more than 16 characters" 2"#), + RocStr::from("more than 16 charactersmore than 16 characters"), RocStr ); } #[test] -fn str_repeat_zero_times() { - assert_evals_to!( - indoc!(r#"Str.repeat "Roc" 0"#), - RocStr::from(""), - RocStr - ); +fn str_repeat_empty_string() { + assert_evals_to!(indoc!(r#"Str.repeat "" 3"#), RocStr::from(""), RocStr); +} + +#[test] +fn str_repeat_zero_times() { + assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr); } From b2343cb0adf14e4edc18b8f5a78423dd643b98dc Mon Sep 17 00:00:00 2001 From: Kofi Gumbs Date: Wed, 29 Sep 2021 21:54:14 -0400 Subject: [PATCH 095/136] Format zig --- compiler/builtins/bitcode/src/str.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 112c74cd5b..ac2b3f6399 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -866,7 +866,6 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool { return true; } - // Str.repeat pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr { const bytes_len = string.len(); From 2f574ea75eea53536649fc638c154328ac0ebffe Mon Sep 17 00:00:00 2001 From: Kofi Gumbs Date: Wed, 29 Sep 2021 22:41:30 -0400 Subject: [PATCH 096/136] Remove extraneous .into() --- compiler/gen_llvm/src/llvm/build_str.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 15fa225458..02098150b7 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -21,7 +21,7 @@ pub fn str_repeat<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { let str_c_abi = str_symbol_to_c_abi(env, scope, str_symbol); let count = load_symbol(scope, &count_symbol); - call_bitcode_fn(env, &[str_c_abi.into(), count.into()], bitcode::STR_REPEAT) + call_bitcode_fn(env, &[str_c_abi.into(), count], bitcode::STR_REPEAT) } /// Str.split : Str, Str -> List Str From f1b14c14e37165c8a1a5ac7dbbe38d05e758b09a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Sep 2021 20:35:52 +0100 Subject: [PATCH 097/136] Refactor SymbolStorage to emphasise stack/non-stack params We don't have much (any) code that cares about the difference between heap pointers and other primitives, but we have a _lot_ of code that cares if it's stack memory. So let's encode it that way. --- compiler/gen_wasm/src/backend.rs | 45 ++++++++++++++++++------------- compiler/gen_wasm/src/storage.rs | 46 +++++++++++++------------------- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 920adae7b3..1a5f975995 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,7 +13,8 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; use crate::{ - allocate_stack_frame, copy_memory, free_stack_frame, round_up_to_alignment, LocalId, PTR_TYPE, + allocate_stack_frame, copy_memory, free_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, + PTR_TYPE, }; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. @@ -172,9 +173,20 @@ impl<'a> WasmBackend<'a> { value_type, size, }, - _ => SymbolStorage::ParamPointer { + + WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive { local_id, - wasm_layout, + value_type: PTR_TYPE, + size: PTR_SIZE, + }, + + WasmLayout::StackMemory { + size, + alignment_bytes, + } => SymbolStorage::ParamStackMemory { + local_id, + size, + alignment_bytes, }, } } @@ -287,12 +299,17 @@ impl<'a> WasmBackend<'a> { // Simple optimisation: if we are just returning the expression, we don't need a local Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => { let wasm_layout = WasmLayout::new(layout); - if let WasmLayout::StackMemory { .. } = wasm_layout { + if let WasmLayout::StackMemory { + size, + alignment_bytes, + } = wasm_layout + { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later - let storage = SymbolStorage::ParamPointer { + let storage = SymbolStorage::ParamStackMemory { local_id: LocalId(0), - wasm_layout, + size, + alignment_bytes, }; self.symbol_storage_map.insert(*let_sym, storage); } @@ -326,14 +343,10 @@ impl<'a> WasmBackend<'a> { alignment_bytes, .. } - | ParamPointer { + | ParamStackMemory { local_id, - wasm_layout: - WasmLayout::StackMemory { - size, - alignment_bytes, - .. - }, + size, + alignment_bytes, } => { let from = *local_id; let to = LocalId(0); @@ -342,7 +355,6 @@ impl<'a> WasmBackend<'a> { ParamPrimitive { local_id, .. } | VarPrimitive { local_id, .. } - | ParamPointer { local_id, .. } | VarHeapMemory { local_id, .. } => { self.instructions.push(GetLocal(local_id.0)); self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack @@ -537,10 +549,7 @@ impl<'a> WasmBackend<'a> { if let Layout::Struct(field_layouts) = layout { match storage { SymbolStorage::VarStackMemory { local_id, size, .. } - | SymbolStorage::ParamPointer { - local_id, - wasm_layout: WasmLayout::StackMemory { size, .. }, - } => { + | SymbolStorage::ParamStackMemory { local_id, size, .. } => { if size > 0 { let mut relative_offset = 0; for (field, _) in fields.iter().zip(field_layouts.iter()) { diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 500f178e80..095998a0af 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,18 +1,14 @@ -use crate::{copy_memory, layout::WasmLayout, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; #[derive(Debug, Clone)] pub enum SymbolStorage { - ParamPrimitive { + VarPrimitive { local_id: LocalId, value_type: ValueType, size: u32, }, - ParamPointer { - local_id: LocalId, - wasm_layout: WasmLayout, - }, - VarPrimitive { + ParamPrimitive { local_id: LocalId, value_type: ValueType, size: u32, @@ -23,6 +19,11 @@ pub enum SymbolStorage { offset: u32, alignment_bytes: u32, }, + ParamStackMemory { + local_id: LocalId, + size: u32, + alignment_bytes: u32, + }, VarHeapMemory { local_id: LocalId, }, @@ -32,7 +33,7 @@ impl SymbolStorage { pub fn local_id(&self) -> LocalId { match self { Self::ParamPrimitive { local_id, .. } => *local_id, - Self::ParamPointer { local_id, .. } => *local_id, + Self::ParamStackMemory { local_id, .. } => *local_id, Self::VarPrimitive { local_id, .. } => *local_id, Self::VarStackMemory { local_id, .. } => *local_id, Self::VarHeapMemory { local_id, .. } => *local_id, @@ -43,7 +44,7 @@ impl SymbolStorage { match self { Self::ParamPrimitive { value_type, .. } => *value_type, Self::VarPrimitive { value_type, .. } => *value_type, - Self::ParamPointer { .. } => ValueType::I32, + Self::ParamStackMemory { .. } => ValueType::I32, Self::VarStackMemory { .. } => ValueType::I32, Self::VarHeapMemory { .. } => ValueType::I32, } @@ -51,11 +52,7 @@ impl SymbolStorage { pub fn has_stack_memory(&self) -> bool { match self { - Self::ParamPointer { - wasm_layout: WasmLayout::StackMemory { .. }, - .. - } => true, - Self::ParamPointer { .. } => false, + Self::ParamStackMemory { .. } => true, Self::VarStackMemory { .. } => true, Self::ParamPrimitive { .. } => false, Self::VarPrimitive { .. } => false, @@ -70,13 +67,9 @@ impl SymbolStorage { alignment_bytes, .. } - | Self::ParamPointer { - wasm_layout: - WasmLayout::StackMemory { - size, - alignment_bytes, - .. - }, + | Self::ParamStackMemory { + size, + alignment_bytes, .. } => (*size, *alignment_bytes), @@ -123,13 +116,10 @@ impl SymbolStorage { Ok(*size) } - Self::ParamPointer { + Self::ParamStackMemory { local_id, - wasm_layout: - WasmLayout::StackMemory { - size, - alignment_bytes, - }, + size, + alignment_bytes, } | Self::VarStackMemory { local_id, @@ -148,7 +138,7 @@ impl SymbolStorage { Ok(*size) } - Self::ParamPointer { local_id, .. } | Self::VarHeapMemory { local_id, .. } => { + Self::VarHeapMemory { local_id, .. } => { instructions.push(GetLocal(to_pointer.0)); instructions.push(GetLocal(local_id.0)); instructions.push(I32Store(ALIGN_4, to_offset)); From 79ac2f04b82f5b2b01bc7eb49f737648f7797681 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Sep 2021 21:19:57 +0100 Subject: [PATCH 098/136] Improve stack allocation code --- compiler/gen_wasm/src/backend.rs | 46 ++++++++++++++------------------ 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 1a5f975995..b6fd083d57 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -160,8 +160,7 @@ impl<'a> WasmBackend<'a> { symbol: Symbol, kind: LocalKind, ) -> SymbolStorage { - let local_index = (self.arg_types.len() + self.locals.len()) as u32; - let local_id = LocalId(local_index); + let local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); let storage = match kind { LocalKind::Parameter => { @@ -208,20 +207,28 @@ impl<'a> WasmBackend<'a> { } => { let offset = round_up_to_alignment(self.stack_memory, alignment_bytes as i32); - self.stack_memory = offset + size as i32; - // TODO: if we're creating the frame pointer just reuse the same local_id! - let frame_pointer = self.get_or_create_frame_pointer(); + match self.stack_frame_pointer { + None => { + // This is the first stack-memory variable in the function + // That means we can reuse it as the stack frame pointer, + // and it will get initialised at the start of the function + self.stack_frame_pointer = Some(local_id); + } - // initialise the local with the appropriate address - // TODO: skip this the first time, no point generating code to add zero offset! - self.instructions.extend([ - GetLocal(frame_pointer.0), - I32Const(offset), - I32Add, - SetLocal(local_index), - ]); + Some(frame_ptr_id) => { + // This local points to the base of a struct, at an offset from the stack frame pointer + // Having one local per variable means params and locals work the same way in code gen. + // (alternatively we could use one frame pointer + offset for all struct variables) + self.instructions.extend([ + GetLocal(frame_ptr_id.0), + I32Const(offset), + I32Add, + SetLocal(local_id.0), + ]); + } + }; SymbolStorage::VarStackMemory { local_id, @@ -239,19 +246,6 @@ impl<'a> WasmBackend<'a> { storage } - fn get_or_create_frame_pointer(&mut self) -> LocalId { - match self.stack_frame_pointer { - Some(local_id) => local_id, - None => { - let local_index = (self.arg_types.len() + self.locals.len()) as u32; - let local_id = LocalId(local_index); - self.stack_frame_pointer = Some(local_id); - self.locals.push(Local::new(1, ValueType::I32)); - local_id - } - } - } - fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> { self.symbol_storage_map.get(sym).ok_or_else(|| { format!( From 7ac7e16f60a6caddcf870ec89b07c10729f2561b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Sep 2021 15:49:54 +0100 Subject: [PATCH 099/136] Ensure stack frame is always popped when procedure returns from inside a branch --- compiler/gen_wasm/src/backend.rs | 41 +++++++++++-------- compiler/gen_wasm/src/lib.rs | 6 +-- .../tests/helpers/wasm32_test_result.rs | 8 ++-- compiler/gen_wasm/tests/wasm_records.rs | 18 ++++++++ 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index b6fd083d57..bba7d5271f 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,7 +13,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; use crate::{ - allocate_stack_frame, copy_memory, free_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, + push_stack_frame, copy_memory, pop_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, PTR_TYPE, }; @@ -93,7 +93,7 @@ impl<'a> WasmBackend<'a> { } pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { - let signature_builder = self.build_signature(&proc); + let signature_builder = self.start_proc(&proc); self.build_stmt(&proc.body, &proc.ret_layout)?; @@ -106,14 +106,17 @@ impl<'a> WasmBackend<'a> { Ok(function_index) } - fn build_signature(&mut self, proc: &Proc<'a>) -> SignatureBuilder { + fn start_proc(&mut self, proc: &Proc<'a>) -> SignatureBuilder { let ret_layout = WasmLayout::new(&proc.ret_layout); let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { self.arg_types.push(PTR_TYPE); + self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any) builder::signature() } else { - builder::signature().with_result(ret_layout.value_type()) + let ret_type = ret_layout.value_type(); + self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any) + builder::signature().with_result(ret_type) }; for (layout, symbol) in proc.args { @@ -124,10 +127,12 @@ impl<'a> WasmBackend<'a> { } fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { + self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any) + let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); if self.stack_memory > 0 { - allocate_stack_frame( + push_stack_frame( &mut final_instructions, self.stack_memory, self.stack_frame_pointer.unwrap(), @@ -137,13 +142,13 @@ impl<'a> WasmBackend<'a> { final_instructions.extend(self.instructions.drain(0..)); if self.stack_memory > 0 { - free_stack_frame( + pop_stack_frame( &mut final_instructions, self.stack_memory, self.stack_frame_pointer.unwrap(), ); } - final_instructions.push(Instruction::End); + final_instructions.push(End); builder::function() .with_signature(signature_builder.build_sig()) @@ -275,12 +280,9 @@ impl<'a> WasmBackend<'a> { self.instructions.push(Loop(BlockType::Value(value_type))); } - fn start_block(&mut self) { + fn start_block(&mut self, block_type: BlockType) { self.block_depth += 1; - - // Our blocks always end with a `return` or `br`, - // so they never leave extra values on the stack - self.instructions.push(Block(BlockType::NoResult)); + self.instructions.push(Block(block_type)); } fn end_block(&mut self) { @@ -308,7 +310,7 @@ impl<'a> WasmBackend<'a> { self.symbol_storage_map.insert(*let_sym, storage); } self.build_expr(let_sym, expr, layout)?; - self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack + self.instructions.push(Br(self.block_depth)); // jump to end of function (stack frame pop) Ok(()) } @@ -319,7 +321,12 @@ impl<'a> WasmBackend<'a> { .local_id(); self.build_expr(sym, expr, layout)?; - self.instructions.push(SetLocal(local_id.0)); + + // If this local is shared with the stack frame pointer, it's already assigned + match self.stack_frame_pointer { + Some(sfp) if sfp == local_id => {} + _ => self.instructions.push(SetLocal(local_id.0)) + } self.build_stmt(following, ret_layout)?; Ok(()) @@ -351,7 +358,7 @@ impl<'a> WasmBackend<'a> { | VarPrimitive { local_id, .. } | VarHeapMemory { local_id, .. } => { self.instructions.push(GetLocal(local_id.0)); - self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack + self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) } } @@ -371,7 +378,7 @@ impl<'a> WasmBackend<'a> { // create (number_of_branches - 1) new blocks. for _ in 0..branches.len() { - self.start_block() + self.start_block(BlockType::NoResult) } // the LocalId of the symbol that we match on @@ -422,7 +429,7 @@ impl<'a> WasmBackend<'a> { jp_parameter_local_ids.push(local_id); } - self.start_block(); + self.start_block(BlockType::NoResult); self.joinpoint_label_map .insert(*id, (self.block_depth, jp_parameter_local_ids)); diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index d5d680e283..eac77a4795 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -26,7 +26,7 @@ pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; pub const STACK_ALIGNMENT_BYTES: i32 = 16; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct LocalId(pub u32); pub struct Env<'a> { @@ -163,7 +163,7 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { aligned } -pub fn allocate_stack_frame( +pub fn push_stack_frame( instructions: &mut Vec, size: i32, local_frame_pointer: LocalId, @@ -178,7 +178,7 @@ pub fn allocate_stack_frame( ]); } -pub fn free_stack_frame( +pub fn pop_stack_frame( instructions: &mut Vec, size: i32, local_frame_pointer: LocalId, diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index 7e73d5727e..5cf709a597 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -46,7 +46,7 @@ macro_rules! build_wrapper_body_primitive { fn build_wrapper_body(main_function_index: u32) -> Vec { let size: i32 = 8; let mut instructions = Vec::with_capacity(16); - allocate_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); + push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); instructions.extend([ // load result address to prepare for the store instruction later GetLocal(STACK_POINTER_LOCAL_ID), @@ -60,7 +60,7 @@ macro_rules! build_wrapper_body_primitive { // Return the result pointer GetLocal(STACK_POINTER_LOCAL_ID), ]); - free_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); + pop_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); instructions.push(End); instructions } @@ -77,7 +77,7 @@ macro_rules! wasm_test_result_primitive { fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec { let mut instructions = Vec::with_capacity(16); - allocate_stack_frame( + push_stack_frame( &mut instructions, size as i32, LocalId(STACK_POINTER_LOCAL_ID), @@ -92,7 +92,7 @@ fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec // Return the result address GetLocal(STACK_POINTER_LOCAL_ID), ]); - free_stack_frame( + pop_stack_frame( &mut instructions, size as i32, LocalId(STACK_POINTER_LOCAL_ID), diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index 5ae26092ef..884e92c7db 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -873,6 +873,24 @@ mod wasm_records { // ); // } + #[test] + fn stack_memory_return_from_branch() { + // stack memory pointer should end up in the right place after returning from a branch + assert_evals_to!( + indoc!( + r#" + stackMemoryJunk = { x: 999, y: 111 } + if True then + { x: 123, y: 321 } + else + stackMemoryJunk + "# + ), + (123, 321), + (i64, i64) + ); + } + // #[test] // fn blue_and_present() { // assert_evals_to!( From 92085a0fcb5b88171086076aa9a4e92b5fa447cc Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Sep 2021 19:47:16 +0100 Subject: [PATCH 100/136] README notes on reducing gets and sets --- compiler/gen_wasm/README.md | 43 +++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/compiler/gen_wasm/README.md b/compiler/gen_wasm/README.md index a3f22297b6..9681e350c3 100644 --- a/compiler/gen_wasm/README.md +++ b/compiler/gen_wasm/README.md @@ -99,29 +99,29 @@ The Mono IR contains two functions, `Num.add` and `main`, so we generate two cor (func (;1;) (result i64) ; declare function index 1 (main) with no parameters and an i64 result (local i64 i64 i64 i64) ; declare 4 local variables, all with type i64, one for each symbol in the Mono IR - i64.const 1 ; load constant of type i64 and value 1 stack=[1] - local.set 0 ; store top of stack to local0 stack=[] local0=1 - i64.const 2 ; load constant of type i64 and value 2 stack=[2] local0=1 - local.set 1 ; store top of stack to local1 stack=[] local0=1 local1=2 - local.get 0 ; load local0 to top of stack stack=[1] local0=1 local1=2 - local.get 1 ; load local1 to top of stack stack=[1,2] local0=1 local1=2 - call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[3] local0=1 local1=2 - local.set 2 ; store top of stack to local2 stack=[] local0=1 local1=2 local2=3 - i64.const 4 ; load constant of type i64 and value 4 stack=[4] local0=1 local1=2 local2=3 - local.set 3 ; store top of stack to local3 stack=[] local0=1 local1=2 local2=3 local3=4 - local.get 2 ; load local2 to top of stack stack=[3] local0=1 local1=2 local2=3 local3=4 - local.get 3 ; load local3 to top of stack stack=[3,4] local0=1 local1=2 local2=3 local3=4 - call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[7] local0=1 local1=2 local2=3 local3=4 + i64.const 1 ; stack=[1] + local.set 0 ; stack=[] local0=1 + i64.const 2 ; stack=[2] local0=1 + local.set 1 ; stack=[] local0=1 local1=2 + local.get 0 ; stack=[1] local0=1 local1=2 + local.get 1 ; stack=[1,2] local0=1 local1=2 + call 0 ; stack=[3] local0=1 local1=2 + local.set 2 ; stack=[] local0=1 local1=2 local2=3 + i64.const 4 ; stack=[4] local0=1 local1=2 local2=3 + local.set 3 ; stack=[] local0=1 local1=2 local2=3 local3=4 + local.get 2 ; stack=[3] local0=1 local1=2 local2=3 local3=4 + local.get 3 ; stack=[3,4] local0=1 local1=2 local2=3 local3=4 + call 0 ; stack=[7] local0=1 local1=2 local2=3 local3=4 return) ; return the value at the top of the stack ``` -If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away. The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first). +If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away (which is all of them in this example!). The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first). ``` $ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm ``` -The optimised functions have no local variables, and the code shrinks to about 60% of its original size. +The optimised functions have no local variables at all for this example. (Of course, this is an oversimplified toy example! It might not be so extreme in a real program.) ``` (func (;0;) (param i64 i64) (result i64) @@ -132,9 +132,20 @@ The optimised functions have no local variables, and the code shrinks to about 6 i64.const 1 i64.const 2 call 0 - i64.const 4) + i64.const 4 + call 0) ``` +### Reducing sets and gets + +It would be nice to find some cheap optimisation to reduce the number of `local.set` and `local.get` instructions. + +We don't need a `local` if the value we want is already at the top of the VM stack. In fact, for our example above, it just so happens that if we simply skip generating the `local.set` instructions, everything _does_ appear on the VM stack in the right order, which means we can skip the `local.get` too. It ends up being very close to the fully optimised version! I assume this is because the Mono IR within the function is in dependency order, but I'm not sure... + +Of course the trick is to do this reliably for more complex dependency graphs. I am investigating whether we can do it by optimistically assuming it's OK not to create a local, and then keeping track of which symbols are at which positions in the VM stack after every instruction. Then when we need to use a symbol we can first check if it's on the VM stack and only create a local if it's not. In cases where we _do_ need to create a local, we need to go back and insert a `local.set` instruction at an earlier point in the program. We can make this fast by waiting to do all of the insertions in one batch when we're finalising the procedure. + +For a while we thought it would be very helpful to reuse the same local for multiple symbols at different points in the program. And we already have similar code in the CPU backends for register allocation. But on further examination, it doesn't actually buy us much! In our example above, we would still have the same number of `local.set` and `local.get` instructions - they'd just be operating on two locals instead of four! That doesn't shrink much code. Only the declaration at the top of the function would shrink from `(local i64 i64 i64 i64)` to `(local i64 i64)`... and in fact that's only smaller in the text format, it's the same size in the binary format! So the `scan_ast` pass doesn't seem worthwhile for Wasm. + ## Memory WebAssembly programs have a "linear memory" for storing data, which is a block of memory assigned to it by the host. You can assign a min and max size to the memory, and the WebAssembly program can request 64kB pages from the host, just like a "normal" program would request pages from the OS. Addresses start at zero and go up to whatever the current size is. Zero is a perfectly normal address like any other, and dereferencing it is not a segfault. But addresses beyond the current memory size are out of bounds and dereferencing them will cause a panic. From 304e9c904f1c6e1f4d6469ab5d34d738a810cba8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Sep 2021 17:23:02 +0100 Subject: [PATCH 101/136] Formatting --- compiler/gen_wasm/src/backend.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index bba7d5271f..24de4a3576 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,7 +13,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; use crate::{ - push_stack_frame, copy_memory, pop_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, + copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, PTR_TYPE, }; @@ -325,7 +325,7 @@ impl<'a> WasmBackend<'a> { // If this local is shared with the stack frame pointer, it's already assigned match self.stack_frame_pointer { Some(sfp) if sfp == local_id => {} - _ => self.instructions.push(SetLocal(local_id.0)) + _ => self.instructions.push(SetLocal(local_id.0)), } self.build_stmt(following, ret_layout)?; From 08a3f0fb9ae29745953f69a80fa00bd42963b662 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 Oct 2021 13:06:32 -0500 Subject: [PATCH 102/136] Add 'a taste of roc' link to www --- www/public/index.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/www/public/index.html b/www/public/index.html index 7144f8368a..cccb3ab181 100644 --- a/www/public/index.html +++ b/www/public/index.html @@ -16,9 +16,10 @@

Roc's initial release is still under development, and this website is a placeholder until that release is ready.

In the meantime, if you'd like to learn more about Roc, here are some videos:

From f1ec5c30d02746208da38d53f0cb2c8aa9a077b8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 1 Oct 2021 20:33:00 +0200 Subject: [PATCH 103/136] make things compile --- cli/src/build.rs | 2 ++ cli/src/main.rs | 7 ++++--- compiler/build/src/link.rs | 2 +- compiler/build/src/program.rs | 2 -- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index 85fc609717..e62ddb7043 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -3,6 +3,7 @@ use roc_build::{ link::{link, rebuild_host, LinkType}, program, }; +#[cfg(feature = "llvm")] use roc_builtins::bitcode; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; @@ -11,6 +12,7 @@ use roc_mono::ir::OptLevel; use std::path::PathBuf; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; +#[cfg(feature = "llvm")] use tempfile::Builder; fn report_timing(buf: &mut String, label: &str, duration: Duration) { diff --git a/cli/src/main.rs b/cli/src/main.rs index 739b341907..15a1de4b48 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -11,12 +11,13 @@ use std::path::{Path, PathBuf}; #[global_allocator] static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; -#[cfg(feature = "llvm")] -use roc_cli::build; use std::ffi::{OsStr, OsString}; +#[cfg(feature = "llvm")] +use roc_cli::build; + #[cfg(not(feature = "llvm"))] -fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result { +fn build(_matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result { panic!("Building without LLVM is not currently supported."); } diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 2d3517ee60..d7a894838c 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -2,7 +2,7 @@ use crate::target::arch_str; #[cfg(feature = "llvm")] use libloading::{Error, Library}; use roc_builtins::bitcode; -#[cfg(feature = "llvm")] +// #[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; use std::collections::HashMap; use std::env; diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 226bfaf8be..7d9803e4be 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -2,11 +2,9 @@ use roc_gen_llvm::llvm::build::module_from_builtins; #[cfg(feature = "llvm")] pub use roc_gen_llvm::llvm::build::FunctionIterator; -#[cfg(feature = "llvm")] use roc_load::file::MonomorphizedModule; #[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; -#[cfg(feature = "llvm")] use std::path::{Path, PathBuf}; use std::time::Duration; From 4aa2452e018b0834f7904f589d7ef66861123985 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 2 Oct 2021 13:04:10 +0100 Subject: [PATCH 104/136] gen_wasm: Change some compiler bugs error handling from Result to panic Result makes sense where I have something meaningful to say to the user like "X is not implemented yet". And also for public functions that may interface with other parts of the project like Backend. But for private functions internal to gen_wasm, it's just unhelpful to get a stack trace to where the Result is unwrapped! I want a stack trace to the root cause. I always end up temporarily rewriting Err("oops") to panic!("oops") and then waiting for it to recompile. This feels like a more balanced approach, using each technique where it makes sense. --- compiler/gen_wasm/src/backend.rs | 44 +++++++++++++++----------------- compiler/gen_wasm/src/lib.rs | 3 +-- compiler/gen_wasm/src/storage.rs | 15 +++++------ 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 24de4a3576..303fe38349 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -251,25 +251,24 @@ impl<'a> WasmBackend<'a> { storage } - fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> { - self.symbol_storage_map.get(sym).ok_or_else(|| { - format!( + fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage { + self.symbol_storage_map.get(sym).unwrap_or_else(|| { + panic!( "Symbol {:?} not found in function scope:\n{:?}", sym, self.symbol_storage_map ) }) } - fn local_id_from_symbol(&self, sym: &Symbol) -> Result { - let storage = self.get_symbol_storage(sym)?; - Ok(storage.local_id()) + fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId { + let storage = self.get_symbol_storage(sym); + storage.local_id() } - fn load_symbol(&mut self, sym: &Symbol) -> Result<(), String> { - let storage = self.get_symbol_storage(sym)?; + fn load_symbol(&mut self, sym: &Symbol) { + let storage = self.get_symbol_storage(sym); let index: u32 = storage.local_id().0; self.instructions.push(GetLocal(index)); - Ok(()) } /// start a loop that leaves a value on the stack @@ -351,7 +350,7 @@ impl<'a> WasmBackend<'a> { } => { let from = *local_id; let to = LocalId(0); - copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0)?; + copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0); } ParamPrimitive { local_id, .. } @@ -382,7 +381,7 @@ impl<'a> WasmBackend<'a> { } // the LocalId of the symbol that we match on - let matched_on = self.local_id_from_symbol(cond_symbol)?; + let matched_on = self.local_id_from_symbol(cond_symbol); // then, we jump whenever the value under scrutiny is equal to the value of a branch for (i, (value, _, _)) in branches.iter().enumerate() { @@ -455,7 +454,7 @@ impl<'a> WasmBackend<'a> { // put the arguments on the stack for (symbol, local_id) in arguments.iter().zip(locals.iter()) { - let argument = self.local_id_from_symbol(symbol)?; + let argument = self.local_id_from_symbol(symbol); self.instructions.push(GetLocal(argument.0)); self.instructions.push(SetLocal(local_id.0)); } @@ -485,7 +484,7 @@ impl<'a> WasmBackend<'a> { }) => match call_type { CallType::ByName { name: func_sym, .. } => { for arg in *arguments { - self.load_symbol(arg)?; + self.load_symbol(arg); } let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( "Cannot find function {:?} called from {:?}", @@ -545,7 +544,7 @@ impl<'a> WasmBackend<'a> { layout: &Layout<'a>, fields: &'a [Symbol], ) -> Result<(), String> { - let storage = self.get_symbol_storage(sym)?.to_owned(); + let storage = self.get_symbol_storage(sym).to_owned(); if let Layout::Struct(field_layouts) = layout { match storage { @@ -558,7 +557,7 @@ impl<'a> WasmBackend<'a> { local_id, relative_offset, field, - )?; + ); } } else { return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); @@ -573,8 +572,8 @@ impl<'a> WasmBackend<'a> { } } else { // Struct expression but not Struct layout => single element. Copy it. - let field_storage = self.get_symbol_storage(&fields[0])?.to_owned(); - self.copy_storage(&storage, &field_storage)?; + let field_storage = self.get_symbol_storage(&fields[0]).to_owned(); + self.copy_storage(&storage, &field_storage); } Ok(()) } @@ -584,12 +583,12 @@ impl<'a> WasmBackend<'a> { to_ptr: LocalId, to_offset: u32, from_symbol: &Symbol, - ) -> Result { - let from_storage = self.get_symbol_storage(from_symbol)?.to_owned(); + ) -> u32 { + let from_storage = self.get_symbol_storage(from_symbol).to_owned(); from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset) } - fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) -> Result<(), String> { + fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) { let has_stack_memory = to.has_stack_memory(); debug_assert!(from.has_stack_memory() == has_stack_memory); @@ -597,7 +596,6 @@ impl<'a> WasmBackend<'a> { debug_assert!(from.value_type() == to.value_type()); self.instructions.push(GetLocal(from.local_id().0)); self.instructions.push(SetLocal(to.local_id().0)); - Ok(()) } else { let (size, alignment_bytes) = from.stack_size_and_alignment(); copy_memory( @@ -607,7 +605,7 @@ impl<'a> WasmBackend<'a> { size, alignment_bytes, 0, - ) + ); } } @@ -618,7 +616,7 @@ impl<'a> WasmBackend<'a> { return_layout: &Layout<'a>, ) -> Result<(), String> { for arg in args { - self.load_symbol(arg)?; + self.load_symbol(arg); } let wasm_layout = WasmLayout::new(return_layout); self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index eac77a4795..32ff3e83ad 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -128,7 +128,7 @@ fn copy_memory( size: u32, alignment_bytes: u32, offset: u32, -) -> Result<(), String> { +) { let alignment_flag = encode_alignment(alignment_bytes); let mut current_offset = offset; while size - current_offset >= 8 { @@ -152,7 +152,6 @@ fn copy_memory( instructions.push(I32Store8(alignment_flag, current_offset)); current_offset += 1; } - Ok(()) } /// Round up to alignment_bytes (assumed to be a power of 2) diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 095998a0af..51c14203b1 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -82,7 +82,7 @@ impl SymbolStorage { instructions: &mut Vec, to_pointer: LocalId, to_offset: u32, - ) -> Result { + ) -> u32 { match self { Self::ParamPrimitive { local_id, @@ -104,16 +104,13 @@ impl SymbolStorage { (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), _ => { - return Err(format!( - "Cannot store {:?} with alignment of {:?}", - value_type, size - )); + panic!("Cannot store {:?} with alignment of {:?}", value_type, size); } }; instructions.push(GetLocal(to_pointer.0)); instructions.push(GetLocal(local_id.0)); instructions.push(store_instruction); - Ok(*size) + *size } Self::ParamStackMemory { @@ -134,15 +131,15 @@ impl SymbolStorage { *size, *alignment_bytes, to_offset, - )?; - Ok(*size) + ); + *size } Self::VarHeapMemory { local_id, .. } => { instructions.push(GetLocal(to_pointer.0)); instructions.push(GetLocal(local_id.0)); instructions.push(I32Store(ALIGN_4, to_offset)); - Ok(4) + 4 } } } From ffa6ff0a622621e2f84236eae0f7067ebdf2ff2a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 2 Oct 2021 15:12:25 +0100 Subject: [PATCH 105/136] gen_wasm: Get rid of individual locals for values in stack memory All values in stack memory can share the stack frame pointer, with different offsets. This avoids having to generate initialisation code for individual pointers we used to have. It should also make things more efficient when the runtime compiles Wasm to machine code. It can just assign the stack frame pointer to a single CPU register and leave it there. The original idea was to make params and local variables work the same. (A struct param will be passed as a pointer to caller stack memory.) But now I don't think that's worth it. Some match expressions get more awkward this way but we might be able to refactor that to be nicer. --- compiler/gen_wasm/src/backend.rs | 126 +++++++++++++++++-------------- compiler/gen_wasm/src/lib.rs | 56 +++++++------- compiler/gen_wasm/src/storage.rs | 49 ++++++++---- 3 files changed, 134 insertions(+), 97 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 303fe38349..1f028660d4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,7 +13,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; use crate::{ - copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, + pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, MemoryCopy, PTR_SIZE, PTR_TYPE, }; @@ -165,7 +165,7 @@ impl<'a> WasmBackend<'a> { symbol: Symbol, kind: LocalKind, ) -> SymbolStorage { - let local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); + let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); let storage = match kind { LocalKind::Parameter => { @@ -173,13 +173,13 @@ impl<'a> WasmBackend<'a> { self.arg_types.push(wasm_layout.value_type()); match wasm_layout { WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive { - local_id, + local_id: next_local_id, value_type, size, }, WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive { - local_id, + local_id: next_local_id, value_type: PTR_TYPE, size: PTR_SIZE, }, @@ -188,7 +188,7 @@ impl<'a> WasmBackend<'a> { size, alignment_bytes, } => SymbolStorage::ParamStackMemory { - local_id, + local_id: next_local_id, size, alignment_bytes, }, @@ -199,12 +199,14 @@ impl<'a> WasmBackend<'a> { match wasm_layout { WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive { - local_id, + local_id: next_local_id, value_type, size, }, - WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { local_id }, + WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { + local_id: next_local_id, + }, WasmLayout::StackMemory { size, @@ -216,27 +218,12 @@ impl<'a> WasmBackend<'a> { match self.stack_frame_pointer { None => { - // This is the first stack-memory variable in the function - // That means we can reuse it as the stack frame pointer, - // and it will get initialised at the start of the function - self.stack_frame_pointer = Some(local_id); - } - - Some(frame_ptr_id) => { - // This local points to the base of a struct, at an offset from the stack frame pointer - // Having one local per variable means params and locals work the same way in code gen. - // (alternatively we could use one frame pointer + offset for all struct variables) - self.instructions.extend([ - GetLocal(frame_ptr_id.0), - I32Const(offset), - I32Add, - SetLocal(local_id.0), - ]); + self.stack_frame_pointer = Some(next_local_id); } + Some(_) => {} }; SymbolStorage::VarStackMemory { - local_id, size, offset: offset as u32, alignment_bytes, @@ -262,12 +249,12 @@ impl<'a> WasmBackend<'a> { fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId { let storage = self.get_symbol_storage(sym); - storage.local_id() + storage.local_id(self.stack_frame_pointer) } fn load_symbol(&mut self, sym: &Symbol) { let storage = self.get_symbol_storage(sym); - let index: u32 = storage.local_id().0; + let index: u32 = storage.local_id(self.stack_frame_pointer).0; self.instructions.push(GetLocal(index)); } @@ -317,7 +304,7 @@ impl<'a> WasmBackend<'a> { let wasm_layout = WasmLayout::new(layout); let local_id = self .insert_local(wasm_layout, *sym, LocalKind::Variable) - .local_id(); + .local_id(self.stack_frame_pointer); self.build_expr(sym, expr, layout)?; @@ -338,19 +325,35 @@ impl<'a> WasmBackend<'a> { match storage { VarStackMemory { - local_id, size, alignment_bytes, - .. + offset, + } => { + let copy = MemoryCopy { + from_ptr: self.stack_frame_pointer.unwrap(), + from_offset: *offset, + to_ptr: LocalId(0), + to_offset: 0, + size: *size, + alignment_bytes: *alignment_bytes, + }; + copy.generate(&mut self.instructions); } - | ParamStackMemory { + + ParamStackMemory { local_id, size, alignment_bytes, } => { - let from = *local_id; - let to = LocalId(0); - copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0); + let copy = MemoryCopy { + from_ptr: *local_id, + from_offset: 0, + to_ptr: LocalId(0), + to_offset: 0, + size: *size, + alignment_bytes: *alignment_bytes, + }; + copy.generate(&mut self.instructions); } ParamPrimitive { local_id, .. } @@ -423,7 +426,7 @@ impl<'a> WasmBackend<'a> { let wasm_layout = WasmLayout::new(¶meter.layout); let local_id = self .insert_local(wasm_layout, parameter.symbol, LocalKind::Variable) - .local_id(); + .local_id(self.stack_frame_pointer); jp_parameter_local_ids.push(local_id); } @@ -547,28 +550,27 @@ impl<'a> WasmBackend<'a> { let storage = self.get_symbol_storage(sym).to_owned(); if let Layout::Struct(field_layouts) = layout { - match storage { - SymbolStorage::VarStackMemory { local_id, size, .. } - | SymbolStorage::ParamStackMemory { local_id, size, .. } => { - if size > 0 { - let mut relative_offset = 0; - for (field, _) in fields.iter().zip(field_layouts.iter()) { - relative_offset += self.copy_symbol_to_pointer_at_offset( - local_id, - relative_offset, - field, - ); - } - } else { - return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); - } + let (local_id, size) = match storage { + SymbolStorage::VarStackMemory { size, .. } => { + (self.stack_frame_pointer.unwrap(), size) } + SymbolStorage::ParamStackMemory { local_id, size, .. } => (local_id, size), _ => { return Err(format!( "Cannot create struct {:?} with storage {:?}", sym, storage )); } + }; + + if size > 0 { + let mut relative_offset = 0; + for (field, _) in fields.iter().zip(field_layouts.iter()) { + relative_offset += + self.copy_symbol_to_pointer_at_offset(local_id, relative_offset, field); + } + } else { + return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); } } else { // Struct expression but not Struct layout => single element. Copy it. @@ -585,7 +587,12 @@ impl<'a> WasmBackend<'a> { from_symbol: &Symbol, ) -> u32 { let from_storage = self.get_symbol_storage(from_symbol).to_owned(); - from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset) + from_storage.copy_to_memory( + &mut self.instructions, + to_ptr, + to_offset, + self.stack_frame_pointer, + ) } fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) { @@ -594,18 +601,21 @@ impl<'a> WasmBackend<'a> { if !has_stack_memory { debug_assert!(from.value_type() == to.value_type()); - self.instructions.push(GetLocal(from.local_id().0)); - self.instructions.push(SetLocal(to.local_id().0)); + self.instructions + .push(GetLocal(from.local_id(self.stack_frame_pointer).0)); + self.instructions + .push(SetLocal(to.local_id(self.stack_frame_pointer).0)); } else { let (size, alignment_bytes) = from.stack_size_and_alignment(); - copy_memory( - &mut self.instructions, - from.local_id(), - to.local_id(), + let copy = MemoryCopy { + from_ptr: from.local_id(self.stack_frame_pointer), + to_ptr: to.local_id(self.stack_frame_pointer), + from_offset: from.address_offset().unwrap(), + to_offset: to.address_offset().unwrap(), size, alignment_bytes, - 0, - ); + }; + copy.generate(&mut self.instructions); } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 32ff3e83ad..394b59b840 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -121,36 +121,40 @@ fn encode_alignment(bytes: u32) -> u32 { } } -fn copy_memory( - instructions: &mut Vec, +pub struct MemoryCopy { from_ptr: LocalId, + from_offset: u32, to_ptr: LocalId, + to_offset: u32, size: u32, alignment_bytes: u32, - offset: u32, -) { - let alignment_flag = encode_alignment(alignment_bytes); - let mut current_offset = offset; - while size - current_offset >= 8 { - instructions.push(GetLocal(to_ptr.0)); - instructions.push(GetLocal(from_ptr.0)); - instructions.push(I64Load(alignment_flag, current_offset)); - instructions.push(I64Store(alignment_flag, current_offset)); - current_offset += 8; - } - if size - current_offset >= 4 { - instructions.push(GetLocal(to_ptr.0)); - instructions.push(GetLocal(from_ptr.0)); - instructions.push(I32Load(alignment_flag, current_offset)); - instructions.push(I32Store(alignment_flag, current_offset)); - current_offset += 4; - } - while size - current_offset > 0 { - instructions.push(GetLocal(to_ptr.0)); - instructions.push(GetLocal(from_ptr.0)); - instructions.push(I32Load8U(alignment_flag, current_offset)); - instructions.push(I32Store8(alignment_flag, current_offset)); - current_offset += 1; +} + +impl MemoryCopy { + pub fn generate(&self, instructions: &mut Vec) { + let alignment_flag = encode_alignment(self.alignment_bytes); + let mut i = 0; + while self.size - i >= 8 { + instructions.push(GetLocal(self.to_ptr.0)); + instructions.push(GetLocal(self.from_ptr.0)); + instructions.push(I64Load(alignment_flag, i + self.from_offset)); + instructions.push(I64Store(alignment_flag, i + self.to_offset)); + i += 8; + } + if self.size - i >= 4 { + instructions.push(GetLocal(self.to_ptr.0)); + instructions.push(GetLocal(self.from_ptr.0)); + instructions.push(I32Load(alignment_flag, i + self.from_offset)); + instructions.push(I32Store(alignment_flag, i + self.to_offset)); + i += 4; + } + while self.size - i > 0 { + instructions.push(GetLocal(self.to_ptr.0)); + instructions.push(GetLocal(self.from_ptr.0)); + instructions.push(I32Load8U(alignment_flag, i + self.from_offset)); + instructions.push(I32Store8(alignment_flag, i + self.to_offset)); + i += 1; + } } } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 51c14203b1..59d728db72 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,4 +1,4 @@ -use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use crate::{LocalId, MemoryCopy, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; #[derive(Debug, Clone)] @@ -14,7 +14,6 @@ pub enum SymbolStorage { size: u32, }, VarStackMemory { - local_id: LocalId, size: u32, offset: u32, alignment_bytes: u32, @@ -30,12 +29,12 @@ pub enum SymbolStorage { } impl SymbolStorage { - pub fn local_id(&self) -> LocalId { + pub fn local_id(&self, stack_frame_pointer: Option) -> LocalId { match self { Self::ParamPrimitive { local_id, .. } => *local_id, Self::ParamStackMemory { local_id, .. } => *local_id, Self::VarPrimitive { local_id, .. } => *local_id, - Self::VarStackMemory { local_id, .. } => *local_id, + Self::VarStackMemory { .. } => stack_frame_pointer.unwrap(), Self::VarHeapMemory { local_id, .. } => *local_id, } } @@ -60,6 +59,16 @@ impl SymbolStorage { } } + pub fn address_offset(&self) -> Option { + match self { + Self::ParamStackMemory { .. } => Some(0), + Self::VarStackMemory { offset, .. } => Some(*offset), + Self::ParamPrimitive { .. } => None, + Self::VarPrimitive { .. } => None, + Self::VarHeapMemory { .. } => None, + } + } + pub fn stack_size_and_alignment(&self) -> (u32, u32) { match self { Self::VarStackMemory { @@ -82,6 +91,7 @@ impl SymbolStorage { instructions: &mut Vec, to_pointer: LocalId, to_offset: u32, + stack_frame_pointer: Option, ) -> u32 { match self { Self::ParamPrimitive { @@ -117,21 +127,34 @@ impl SymbolStorage { local_id, size, alignment_bytes, + } => { + let copy = MemoryCopy { + from_ptr: *local_id, + from_offset: 0, + to_ptr: to_pointer, + to_offset, + size: *size, + alignment_bytes: *alignment_bytes, + }; + copy.generate(instructions); + *size } - | Self::VarStackMemory { - local_id, + + Self::VarStackMemory { size, alignment_bytes, + offset, .. } => { - copy_memory( - instructions, - *local_id, - to_pointer, - *size, - *alignment_bytes, + let copy = MemoryCopy { + from_ptr: stack_frame_pointer.unwrap(), + from_offset: *offset, + to_ptr: to_pointer, to_offset, - ); + size: *size, + alignment_bytes: *alignment_bytes, + }; + copy.generate(instructions); *size } From 6aed70978d51d8127b95fe2f85bff03443c94d9c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 2 Oct 2021 16:15:06 +0100 Subject: [PATCH 106/136] Refactor SymbolStorage into two enums instead of one --- compiler/gen_wasm/src/backend.rs | 119 ++++++++++++------------------- compiler/gen_wasm/src/storage.rs | 108 ++++++++-------------------- 2 files changed, 78 insertions(+), 149 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 1f028660d4..b9c6cd6900 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -11,7 +11,7 @@ use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; -use crate::storage::SymbolStorage; +use crate::storage::{StackMemoryLocation, SymbolStorage}; use crate::{ pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, MemoryCopy, PTR_SIZE, PTR_TYPE, @@ -167,68 +167,55 @@ impl<'a> WasmBackend<'a> { ) -> SymbolStorage { let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); - let storage = match kind { + match kind { LocalKind::Parameter => { - // Already stack-allocated by the caller if needed. self.arg_types.push(wasm_layout.value_type()); - match wasm_layout { - WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive { - local_id: next_local_id, - value_type, - size, - }, - - WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive { - local_id: next_local_id, - value_type: PTR_TYPE, - size: PTR_SIZE, - }, - - WasmLayout::StackMemory { - size, - alignment_bytes, - } => SymbolStorage::ParamStackMemory { - local_id: next_local_id, - size, - alignment_bytes, - }, - } } LocalKind::Variable => { self.locals.push(Local::new(1, wasm_layout.value_type())); + } + } - match wasm_layout { - WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive { - local_id: next_local_id, - value_type, - size, - }, + let storage = match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => SymbolStorage::Local { + local_id: next_local_id, + value_type, + size, + }, - WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { - local_id: next_local_id, - }, + WasmLayout::HeapMemory => SymbolStorage::Local { + local_id: next_local_id, + value_type: PTR_TYPE, + size: PTR_SIZE, + }, - WasmLayout::StackMemory { - size, - alignment_bytes, - } => { + WasmLayout::StackMemory { + size, + alignment_bytes, + } => { + let location = match kind { + LocalKind::Parameter => StackMemoryLocation::ExternalPointer(next_local_id), + + LocalKind::Variable => { let offset = round_up_to_alignment(self.stack_memory, alignment_bytes as i32); + self.stack_memory = offset + size as i32; match self.stack_frame_pointer { + Some(_) => {} None => { self.stack_frame_pointer = Some(next_local_id); } - Some(_) => {} }; - SymbolStorage::VarStackMemory { - size, - offset: offset as u32, - alignment_bytes, - } + StackMemoryLocation::InternalOffset(offset as u32) } + }; + SymbolStorage::StackMemory { + location, + size, + alignment_bytes, } } }; @@ -288,8 +275,8 @@ impl<'a> WasmBackend<'a> { { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later - let storage = SymbolStorage::ParamStackMemory { - local_id: LocalId(0), + let storage = SymbolStorage::StackMemory { + location: StackMemoryLocation::ExternalPointer(LocalId(0)), size, alignment_bytes, }; @@ -324,14 +311,21 @@ impl<'a> WasmBackend<'a> { let storage = self.symbol_storage_map.get(sym).unwrap(); match storage { - VarStackMemory { + StackMemory { + location, size, alignment_bytes, - offset, } => { + let (from_ptr, from_offset) = match location { + StackMemoryLocation::ExternalPointer(local_id) => (*local_id, 0), + StackMemoryLocation::InternalOffset(offset) => { + (self.stack_frame_pointer.unwrap(), *offset) + } + }; + let copy = MemoryCopy { - from_ptr: self.stack_frame_pointer.unwrap(), - from_offset: *offset, + from_ptr, + from_offset, to_ptr: LocalId(0), to_offset: 0, size: *size, @@ -340,25 +334,7 @@ impl<'a> WasmBackend<'a> { copy.generate(&mut self.instructions); } - ParamStackMemory { - local_id, - size, - alignment_bytes, - } => { - let copy = MemoryCopy { - from_ptr: *local_id, - from_offset: 0, - to_ptr: LocalId(0), - to_offset: 0, - size: *size, - alignment_bytes: *alignment_bytes, - }; - copy.generate(&mut self.instructions); - } - - ParamPrimitive { local_id, .. } - | VarPrimitive { local_id, .. } - | VarHeapMemory { local_id, .. } => { + Local { local_id, .. } => { self.instructions.push(GetLocal(local_id.0)); self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) } @@ -551,10 +527,9 @@ impl<'a> WasmBackend<'a> { if let Layout::Struct(field_layouts) = layout { let (local_id, size) = match storage { - SymbolStorage::VarStackMemory { size, .. } => { - (self.stack_frame_pointer.unwrap(), size) + SymbolStorage::StackMemory { size, .. } => { + (storage.local_id(self.stack_frame_pointer), size) } - SymbolStorage::ParamStackMemory { local_id, size, .. } => (local_id, size), _ => { return Err(format!( "Cannot create struct {:?} with storage {:?}", diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 59d728db72..f1eb565097 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,82 +1,66 @@ use crate::{LocalId, MemoryCopy, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; +#[derive(Debug, Clone)] +pub enum StackMemoryLocation { + ExternalPointer(LocalId), + InternalOffset(u32), +} + #[derive(Debug, Clone)] pub enum SymbolStorage { - VarPrimitive { + Local { local_id: LocalId, value_type: ValueType, size: u32, }, - ParamPrimitive { - local_id: LocalId, - value_type: ValueType, - size: u32, - }, - VarStackMemory { - size: u32, - offset: u32, - alignment_bytes: u32, - }, - ParamStackMemory { - local_id: LocalId, + StackMemory { + location: StackMemoryLocation, size: u32, alignment_bytes: u32, }, - VarHeapMemory { - local_id: LocalId, - }, } impl SymbolStorage { pub fn local_id(&self, stack_frame_pointer: Option) -> LocalId { + use StackMemoryLocation::*; match self { - Self::ParamPrimitive { local_id, .. } => *local_id, - Self::ParamStackMemory { local_id, .. } => *local_id, - Self::VarPrimitive { local_id, .. } => *local_id, - Self::VarStackMemory { .. } => stack_frame_pointer.unwrap(), - Self::VarHeapMemory { local_id, .. } => *local_id, + Self::Local { local_id, .. } => *local_id, + Self::StackMemory { location, .. } => match *location { + ExternalPointer(local_id) => local_id, + InternalOffset(_) => stack_frame_pointer.unwrap(), + }, } } pub fn value_type(&self) -> ValueType { match self { - Self::ParamPrimitive { value_type, .. } => *value_type, - Self::VarPrimitive { value_type, .. } => *value_type, - Self::ParamStackMemory { .. } => ValueType::I32, - Self::VarStackMemory { .. } => ValueType::I32, - Self::VarHeapMemory { .. } => ValueType::I32, + Self::Local { value_type, .. } => *value_type, + Self::StackMemory { .. } => ValueType::I32, } } pub fn has_stack_memory(&self) -> bool { match self { - Self::ParamStackMemory { .. } => true, - Self::VarStackMemory { .. } => true, - Self::ParamPrimitive { .. } => false, - Self::VarPrimitive { .. } => false, - Self::VarHeapMemory { .. } => false, + Self::Local { .. } => false, + Self::StackMemory { .. } => true, } } pub fn address_offset(&self) -> Option { + use StackMemoryLocation::*; match self { - Self::ParamStackMemory { .. } => Some(0), - Self::VarStackMemory { offset, .. } => Some(*offset), - Self::ParamPrimitive { .. } => None, - Self::VarPrimitive { .. } => None, - Self::VarHeapMemory { .. } => None, + Self::Local { .. } => None, + Self::StackMemory { location, .. } => match *location { + ExternalPointer(_) => Some(0), + InternalOffset(offset) => Some(offset), + }, } } pub fn stack_size_and_alignment(&self) -> (u32, u32) { match self { - Self::VarStackMemory { - size, - alignment_bytes, - .. - } - | Self::ParamStackMemory { + Self::StackMemory { size, alignment_bytes, .. @@ -94,13 +78,7 @@ impl SymbolStorage { stack_frame_pointer: Option, ) -> u32 { match self { - Self::ParamPrimitive { - local_id, - value_type, - size, - .. - } - | Self::VarPrimitive { + Self::Local { local_id, value_type, size, @@ -123,13 +101,14 @@ impl SymbolStorage { *size } - Self::ParamStackMemory { - local_id, + Self::StackMemory { size, alignment_bytes, + .. } => { + let local_id = self.local_id(stack_frame_pointer); let copy = MemoryCopy { - from_ptr: *local_id, + from_ptr: local_id, from_offset: 0, to_ptr: to_pointer, to_offset, @@ -139,31 +118,6 @@ impl SymbolStorage { copy.generate(instructions); *size } - - Self::VarStackMemory { - size, - alignment_bytes, - offset, - .. - } => { - let copy = MemoryCopy { - from_ptr: stack_frame_pointer.unwrap(), - from_offset: *offset, - to_ptr: to_pointer, - to_offset, - size: *size, - alignment_bytes: *alignment_bytes, - }; - copy.generate(instructions); - *size - } - - Self::VarHeapMemory { local_id, .. } => { - instructions.push(GetLocal(to_pointer.0)); - instructions.push(GetLocal(local_id.0)); - instructions.push(I32Store(ALIGN_4, to_offset)); - 4 - } } } } From 2fe431f1b86f8ce1041a825e54d86f161e8133da Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 2 Oct 2021 22:45:31 +0200 Subject: [PATCH 107/136] update morphic (can solve quicksort now) --- Cargo.lock | 1 + vendor/morphic_lib/Cargo.toml | 5 +- vendor/morphic_lib/src/analyze.rs | 1770 ++++++++++++++++++++ vendor/morphic_lib/src/api.rs | 81 +- vendor/morphic_lib/src/ir.rs | 114 +- vendor/morphic_lib/src/lib.rs | 1 + vendor/morphic_lib/src/preprocess.rs | 41 +- vendor/morphic_lib/src/type_cache.rs | 2 + vendor/morphic_lib/src/util/blocks.rs | 23 + vendor/morphic_lib/src/util/flat_slices.rs | 8 +- vendor/morphic_lib/src/util/get2_mut.rs | 16 + vendor/morphic_lib/src/util/id_bi_map.rs | 4 + vendor/morphic_lib/src/util/id_vec.rs | 12 + vendor/morphic_lib/src/util/mod.rs | 2 + vendor/morphic_lib/src/util/norm_pair.rs | 33 + vendor/morphic_lib/tests/recursive.rs | 5 +- vendor/morphic_lib/tests/structures.rs | 73 + 17 files changed, 2117 insertions(+), 74 deletions(-) create mode 100644 vendor/morphic_lib/src/analyze.rs create mode 100644 vendor/morphic_lib/src/util/get2_mut.rs create mode 100644 vendor/morphic_lib/src/util/norm_pair.rs create mode 100644 vendor/morphic_lib/tests/structures.rs diff --git a/Cargo.lock b/Cargo.lock index e8289983d3..ec0031bb77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2279,6 +2279,7 @@ dependencies = [ "sha2", "smallvec", "thiserror", + "typed-arena", ] [[package]] diff --git a/vendor/morphic_lib/Cargo.toml b/vendor/morphic_lib/Cargo.toml index 5406cf3647..594ad4c408 100644 --- a/vendor/morphic_lib/Cargo.toml +++ b/vendor/morphic_lib/Cargo.toml @@ -5,6 +5,7 @@ authors = ["William Brandon", "Wilson Berkow", "Frank Dai", "Benjamin Driscoll"] edition = "2018" [dependencies] -thiserror = "1.0.24" +thiserror = "1.0" sha2 = "0.9.4" -smallvec = "1.6.1" +smallvec = "1.6" +typed-arena = "2.0" diff --git a/vendor/morphic_lib/src/analyze.rs b/vendor/morphic_lib/src/analyze.rs new file mode 100644 index 0000000000..fbb69785ef --- /dev/null +++ b/vendor/morphic_lib/src/analyze.rs @@ -0,0 +1,1770 @@ +use smallvec::SmallVec; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::convert::TryInto; +use typed_arena::Arena; + +use crate::api; +use crate::ir; +use crate::name_cache::{EntryPointId, FuncId}; +use crate::type_cache::{TypeCache, TypeData, TypeId}; +use crate::util::flat_slices::FlatSlices; +use crate::util::id_type::Count; +use crate::util::id_vec::IdVec; +use crate::util::norm_pair::NormPair; +use crate::util::op_graph; +use crate::util::replace_none::replace_none; +use crate::util::strongly_connected::{strongly_connected, SccKind}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct SubSlots { + end_indices: SmallVec<[u32; 10]>, +} + +impl SubSlots { + fn from_slot_counts(slot_counts: impl Iterator) -> Self { + let mut total = 0; + let end_indices = slot_counts + .map(|count| { + total += count; + total + }) + .collect(); + Self { end_indices } + } + + fn slot_count(&self) -> u32 { + self.end_indices.last().cloned().unwrap_or(0) + } + + /// Returns bounds `a, b` for a range of slot indices `a..b` + fn sub_slots(&self, index: u32) -> (u32, u32) { + let start = if index == 0 { + 0 + } else { + self.end_indices[index as usize - 1] + }; + let end = self.end_indices[index as usize]; + (start, end) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum TypeSlots { + Named, + Tuple { field_slots: SubSlots }, + Union { variant_slots: SubSlots }, + HeapCell, + Bag { item_slots: u32 }, +} + +impl TypeSlots { + fn slot_count(&self) -> u32 { + match self { + TypeSlots::Named => 1, + TypeSlots::Tuple { field_slots } => field_slots.slot_count(), + TypeSlots::Union { variant_slots } => variant_slots.slot_count(), + TypeSlots::HeapCell => 1, + TypeSlots::Bag { item_slots } => *item_slots, + } + } +} + +#[derive(Clone, Debug)] +struct SlotCache { + type_cache: TypeCache, + slots: IdVec, +} + +impl SlotCache { + fn new(type_cache: TypeCache) -> Self { + let mut slots: IdVec<_, TypeSlots> = IdVec::new(); + // NOTE: This only works because 'type_cache.types' is guaranteed to assign ids in + // topological order. + for (id, type_) in type_cache.types.iter() { + let this_slots = match type_ { + TypeData::Named { named: _ } => TypeSlots::Named, + TypeData::Tuple { fields } => { + let field_slots = SubSlots::from_slot_counts( + fields.iter().map(|field| slots[field].slot_count()), + ); + TypeSlots::Tuple { field_slots } + } + TypeData::Union { variants } => { + let variant_slots = SubSlots::from_slot_counts( + variants.iter().map(|variant| slots[variant].slot_count()), + ); + TypeSlots::Union { variant_slots } + } + TypeData::HeapCell => TypeSlots::HeapCell, + TypeData::Bag { item } => { + let item_slots = slots[item].slot_count(); + TypeSlots::Bag { item_slots } + } + }; + let pushed_id = slots.push(this_slots); + debug_assert_eq!(pushed_id, id); + } + Self { type_cache, slots } + } + + fn type_cache(&self) -> &TypeCache { + &self.type_cache + } + + fn slots(&self) -> &IdVec { + &self.slots + } +} + +id_type! { + HeapCellId(u32); +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum QueryPoint { + Update(api::UpdateModeVarId), + EntryArg(u32), + CallArg(api::CalleeSpecVarId, u32), + CallRet(api::CalleeSpecVarId, u32), +} + +#[derive(Clone, Debug)] +struct BackRefState { + // TODO: use a more efficient representation + overlay: HashMap>, + parents: Vec, +} + +id_type! { + BackRefStateVersionId(u32); +} + +#[derive(Clone, Copy, Debug)] +struct CallInfo { + callee: FuncId, + ret_slots: u32, +} + +#[derive(Clone)] +struct ForwardState<'a> { + slots_arena: &'a Arena, + value_slots: IdVec>, + // Represents value slots for "iteration n - 1" in an SCC + // TODO: Should this be array-of-structs instead of struct-of-arrays? + value_slots_inductive: IdVec>, + call_arg_aliases: IdVec>>>, + call_arg_origins: IdVec>>, + // TODO: Find a better place to store the data mapping in `calls` + calls: IdVec>, + update_origins: IdVec>, + arg_slots: Option<&'a [HeapCellId]>, + heap_cells: IdVec, + back_ref_states: IdVec, + block_versions: IdVec>, + block_versions_inductive: IdVec>, + entry_version: BackRefStateVersionId, + fates: HashMap, +} + +type Set = HashSet; + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Origin { + /// This heap cell might have been obtained from a `const_ref` op. + /// In this case we don't care what arg slots it might have also been obtained from, because we + /// definitely can't mutate it. + FromConst, + /// This heap cell was definitely not obtained from a `const_ref` op. + /// In this case we care about the (potentially empty) set of arg slots we might have obtained + /// it from. + FromArgSlots(Set), +} + +impl Origin { + pub fn union_with(&mut self, other: &Origin) { + match (&mut *self, other) { + (Origin::FromConst, _) => {} + (Origin::FromArgSlots(_), Origin::FromConst) => *self = Origin::FromConst, + (Origin::FromArgSlots(slots1), Origin::FromArgSlots(slots2)) => slots1.extend(slots2), + } + } +} + +impl Default for Origin { + fn default() -> Self { + Origin::FromArgSlots(Set::new()) + } +} + +#[derive(Clone, Debug)] +struct ForwardData { + origin: Origin, + // invariant: does not contain current heap cell id (all heap cells implicitly alias themselves, + // so storing reflexive alias edges would be redundant). + // invariant: aliases are symmetric; if we alias another heap cell, that heap cell should also + // alias us. + aliases: Set, +} + +fn result_slot_count(sc: &mut SlotCache, val: &op_graph::Node) -> u32 { + sc.slots()[val.op.result_type].slot_count() +} + +fn id_result_slot_count(sc: &mut SlotCache, graph: &ir::Graph, val_id: ir::ValueId) -> u32 { + sc.slots()[graph.values().node(val_id).op.result_type].slot_count() +} + +type HeapCellSlotMapping = HashMap>; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ForwardSccSlotSummary { + pre_aliases: Set, + inductive_aliases: Set<(ir::ValueId, u32)>, + internal_aliases: Set<(ir::ValueId, u32)>, + back_refs: HashSet, +} + +type ForwardSccSummary = HashMap>; + +fn block_values_inclusive( + graph: &ir::Graph, + block: ir::BlockId, +) -> impl Iterator + '_ { + graph + .blocks() + .block_info(block) + .param + .iter() // iterator impl on Option + .cloned() + .chain(graph.blocks().block_values(block)) +} + +impl<'a> ForwardState<'a> { + fn add_heap_cell(&mut self) -> HeapCellId { + self.heap_cells.push(ForwardData { + origin: Origin::default(), + aliases: Set::new(), + }) + } + + // Factored into a separate function to allow use when other (disjoint) fields of 'self' are borrowed + fn back_refs_in_states( + back_ref_states: &mut IdVec, + version: BackRefStateVersionId, + heap_cell: HeapCellId, + ) -> &mut HashSet { + // TODO: Optimize this so that it does not traverse the whole parent chain when the heap + // cell is guaranteed to not have any back ref annotations before a certain point (i.e., + // when we have some information about when the heap cell was created). + // TODO: Remove query points from back ref sets when they are set to 'DirectTouch'. + if back_ref_states[version].overlay.contains_key(&heap_cell) { + return back_ref_states[version] + .overlay + .get_mut(&heap_cell) + .unwrap(); + } + let back_refs = match &back_ref_states[version].parents as &[_] { + &[parent] => Self::back_refs_in_states(back_ref_states, parent, heap_cell).clone(), + parents => { + let num_parents = parents.len(); + let mut back_refs = HashSet::new(); + for parent_i in 0..num_parents { + let parent = back_ref_states[version].parents[parent_i]; + let parent_back_refs = + Self::back_refs_in_states(back_ref_states, parent, heap_cell); + back_refs.extend(parent_back_refs.iter()); + } + back_refs + } + }; + back_ref_states[version] + .overlay + .entry(heap_cell) + .or_insert(back_refs) // always inserts + } + + fn back_refs( + &mut self, + version: BackRefStateVersionId, + heap_cell: HeapCellId, + ) -> &mut HashSet { + Self::back_refs_in_states(&mut self.back_ref_states, version, heap_cell) + } + + fn add_heap_cells(&mut self, n: u32) -> &'a mut [HeapCellId] { + self.slots_arena + .alloc_extend(std::iter::repeat_with(|| self.add_heap_cell()).take(n as usize)) + } + + fn copy_heap_cell(&mut self, cell: HeapCellId, n: u32) -> &'a mut [HeapCellId] { + self.slots_arena + .alloc_extend(std::iter::repeat(cell).take(n as usize)) + } + + fn add_alias(&mut self, cell1: HeapCellId, cell2: HeapCellId) { + if cell1 == cell2 { + return; + } + self.heap_cells[cell1].aliases.insert(cell2); + self.heap_cells[cell2].aliases.insert(cell1); + } + + fn copy_aliases(&mut self, src: HeapCellId, dst: HeapCellId) { + self.add_alias(src, dst); + + // A trick so that we can iterate over `aliases` and call `add_alias` at the same time. At + // the end of the function we put `src_aliases` back in place. + // + // TODO: revist this if we start using "small" sets + let src_aliases = std::mem::take(&mut self.heap_cells[src].aliases); + for &other in &src_aliases { + debug_assert_ne!(other, src); + self.add_alias(other, dst); + } + self.heap_cells[src].aliases = src_aliases; + } + + fn copy_non_alias_data( + &mut self, + src_version: BackRefStateVersionId, + src: HeapCellId, + dst_version: BackRefStateVersionId, + dst: HeapCellId, + ) { + if src == dst { + return; + } + + let (src_data, dst_data) = self.heap_cells.get2_mut(src, dst).unwrap(); + src_data.origin.union_with(&dst_data.origin); + + let src_back_refs = std::mem::take(self.back_refs(src_version, src)); + let dst_back_refs = self.back_refs(dst_version, dst); + dst_back_refs.extend(src_back_refs.iter()); + debug_assert!(self.back_refs(src_version, src).is_empty()); + *self.back_refs(src_version, src) = src_back_refs; + } + + fn copy_data( + &mut self, + src_version: BackRefStateVersionId, + src: HeapCellId, + dst_version: BackRefStateVersionId, + dst: HeapCellId, + ) { + self.copy_non_alias_data(src_version, src, dst_version, dst); + self.copy_aliases(src, dst); + } + + fn touch(&mut self, version: BackRefStateVersionId, heap_cell: HeapCellId) { + let back_refs = std::mem::take(self.back_refs(version, heap_cell)); + for &query_point in &back_refs { + self.fates.insert(query_point, Fate::DirectTouch); + } + *self.back_refs(version, heap_cell) = back_refs; + } + + fn recursive_touch(&mut self, version: BackRefStateVersionId, heap_cells: &[HeapCellId]) { + for &heap_cell in heap_cells { + self.touch(version, heap_cell); + } + } + + fn add_back_refs( + &mut self, + version: BackRefStateVersionId, + heap_cell: HeapCellId, + query_point: QueryPoint, + mut other_filter: impl FnMut(HeapCellId) -> bool, + ) { + let aliases = std::mem::take(&mut self.heap_cells[heap_cell].aliases); + for other in std::iter::once(heap_cell).chain(aliases.iter().cloned()) { + if other_filter(other) { + self.back_refs(version, other).insert(query_point); + } + } + self.heap_cells[heap_cell].aliases = aliases; + } + + fn analyze_value( + &mut self, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + version: BackRefStateVersionId, + val_id: ir::ValueId, + ) { + let val_node = graph.values().node(val_id); + let input_slot_arrs: SmallVec<[_; 16]> = val_node + .inputs + .iter() + .map(|input| { + self.value_slots[input].expect("values should be processed in topological order") + }) + .collect(); + let op = match &val_node.op.kind { + ir::ValueKind::Op(op) => op, + ir::ValueKind::BlockParam => { + unreachable!("block param should never appear in the values of a block") + } + }; + let ret_slots: &[_] = match op { + ir::OpKind::UnknownWith => { + let new_cell = self.add_heap_cell(); + self.heap_cells[new_cell].origin = Origin::FromConst; + for input_slots in input_slot_arrs { + self.recursive_touch(version, input_slots); + for &input_cell in input_slots { + self.copy_aliases(input_cell, new_cell); + } + } + let slot_count = result_slot_count(sc, &val_node); + self.copy_heap_cell(new_cell, slot_count) + } + + ir::OpKind::Call { + callee_spec_var, + callee, + } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let arg_slots = input_slot_arrs[0]; + // TODO: optimize this entire case! + let mut heap_cell_slots = HashMap::>::new(); + for (slot_i, &heap_cell) in arg_slots.iter().enumerate() { + heap_cell_slots + .entry(heap_cell) + .or_insert_with(SmallVec::new) + .push(slot_i.try_into().unwrap()); + } + let mut arg_aliases = HashSet::new(); + for (heap_cell, slot_indices) in &heap_cell_slots { + // Wire up to ocurrences of the same heap cell in the argument slots + for (i, &slot_i) in slot_indices.iter().enumerate() { + for &slot_j in &slot_indices[..i] { + arg_aliases.insert(NormPair::new(slot_i, slot_j)); + } + } + // Wire up to distinct aliased heap cells in the argument slots + for &other in &self.heap_cells[heap_cell].aliases { + if let Some(other_slot_indices) = heap_cell_slots.get(&other) { + for &this_slot_i in slot_indices { + for &other_slot_i in other_slot_indices { + arg_aliases.insert(NormPair::new(this_slot_i, other_slot_i)); + } + } + } + } + } + + let ret_slots: &[_] = self.add_heap_cells(result_slot_count(sc, &val_node)); + + if let Some(basic_analysis) = ctx.get_analysis(sc, *callee, None) { + for (arg_slot_i, slot_analysis) in basic_analysis.arg_slots.iter().enumerate() { + if matches!(slot_analysis.fate, Fate::DirectTouch) { + self.touch(version, arg_slots[arg_slot_i]); + } + } + for (ret_slot_i, slot_analysis) in basic_analysis.ret_slots.iter().enumerate() { + let ret_heap_cell = ret_slots[ret_slot_i]; + if slot_analysis.from_const { + self.heap_cells[ret_heap_cell].origin = Origin::FromConst; + } + // Temporarily violate symmetry invariant + for &arg_slot_i in &slot_analysis.arg_aliases { + let arg_heap_cell = arg_slots[arg_slot_i as usize]; + self.heap_cells[ret_heap_cell].aliases.insert(arg_heap_cell); + let (arg_heap_cell_data, ret_heap_cell_data) = self + .heap_cells + .get2_mut(arg_heap_cell, ret_heap_cell) + .unwrap(); + for &alias_of_arg in &arg_heap_cell_data.aliases { + ret_heap_cell_data.aliases.insert(alias_of_arg); + } + self.copy_non_alias_data( + version, + arg_heap_cell, + version, + ret_heap_cell, + ); + } + for &other_ret_slot_i in &slot_analysis.ret_aliases { + self.heap_cells[ret_heap_cell] + .aliases + .insert(ret_slots[other_ret_slot_i as usize]); + } + } + } + + for &arg_alias in &arg_aliases { + if let Some(part_analysis) = ctx.get_analysis(sc, *callee, Some(arg_alias)) { + for (arg_slot_i, slot_analysis) in + part_analysis.arg_slots.iter().enumerate() + { + if matches!(slot_analysis.fate, Fate::DirectTouch) { + self.touch(version, arg_slots[arg_slot_i]); + } + } + for (ret_slot_i, slot_analysis) in + part_analysis.ret_slots.iter().enumerate() + { + // Temporarily violate symmetry invariant + let ret_heap_cell = ret_slots[ret_slot_i]; + for &arg_slot_i in &slot_analysis.arg_aliases { + let arg_heap_cell = arg_slots[arg_slot_i as usize]; + self.heap_cells[ret_heap_cell].aliases.insert(arg_heap_cell); + } + for &other_ret_slot_i in &slot_analysis.ret_aliases { + self.heap_cells[ret_heap_cell] + .aliases + .insert(ret_slots[other_ret_slot_i as usize]); + } + } + } + } + + // Restore symmetry invariant + for &ret_heap_cell in ret_slots { + let aliases = std::mem::take(&mut self.heap_cells[ret_heap_cell].aliases); + for &other in &aliases { + debug_assert_ne!(other, ret_heap_cell); + self.heap_cells[other].aliases.insert(ret_heap_cell); + } + debug_assert!(self.heap_cells[ret_heap_cell].aliases.is_empty()); + self.heap_cells[ret_heap_cell].aliases = aliases; + } + + // We don't use 'replace_none' here because we may write these values multiple times + // during fixed-point iteration. + self.call_arg_aliases[callee_spec_var] = Some(arg_aliases); + self.call_arg_origins[callee_spec_var] = Some( + arg_slots + .iter() + .map(|heap_cell| self.heap_cells[heap_cell].origin.clone()) + .collect(), + ); + self.calls[callee_spec_var] = Some(CallInfo { + callee: *callee, + ret_slots: ret_slots.len().try_into().unwrap(), + }); + + for (arg_slot_i, &arg_heap_cell) in arg_slots.iter().enumerate() { + self.add_back_refs( + version, + arg_heap_cell, + QueryPoint::CallArg(*callee_spec_var, arg_slot_i.try_into().unwrap()), + // TODO: don't use a linear search here + |other| !ret_slots.contains(&other), + ); + } + for (ret_slot_i, &ret_heap_cell) in ret_slots.iter().enumerate() { + self.add_back_refs( + version, + ret_heap_cell, + QueryPoint::CallRet(*callee_spec_var, ret_slot_i.try_into().unwrap()), + // TODO: don't use a linear search here + |other| !arg_slots.contains(&other), + ); + } + + ret_slots + } + + ir::OpKind::ConstRef { const_: _ } => { + debug_assert_eq!(input_slot_arrs.len(), 0); + let slot_count = result_slot_count(sc, &val_node); + let new_heap_cells: &[_] = self.add_heap_cells(slot_count); + for heap_cell in new_heap_cells { + self.heap_cells[heap_cell].origin = Origin::FromConst; + } + new_heap_cells + } + + ir::OpKind::NewHeapCell => { + debug_assert_eq!(input_slot_arrs.len(), 0); + let new_cell = self.add_heap_cell(); + std::slice::from_ref(self.slots_arena.alloc(new_cell)) + } + + ir::OpKind::RecursiveTouch => { + debug_assert_eq!(input_slot_arrs.len(), 1); + self.recursive_touch(version, input_slot_arrs[0]); + &[] + } + + ir::OpKind::UpdateWriteOnly { update_mode_var } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + debug_assert_eq!(input_slot_arrs[0].len(), 1); + let heap_cell = input_slot_arrs[0][0]; + // We don't use 'replace_none' here because we may write this value multiple times + // during fixed-point iteration. + self.update_origins[*update_mode_var] = + Some(self.heap_cells[heap_cell].origin.clone()); + self.add_back_refs( + version, + heap_cell, + QueryPoint::Update(*update_mode_var), + |_| true, + ); + &[] + } + + ir::OpKind::EmptyBag => { + debug_assert_eq!(input_slot_arrs.len(), 0); + let slot_count = result_slot_count(sc, &val_node); + self.add_heap_cells(slot_count) + } + + ir::OpKind::BagInsert => { + debug_assert_eq!(input_slot_arrs.len(), 2); + let slot_count = result_slot_count(sc, &val_node); + let slots = self.add_heap_cells(slot_count); + for input_slots in input_slot_arrs { + for (&input_cell, &new_cell) in input_slots.iter().zip(slots.iter()) { + self.copy_data(version, input_cell, version, new_cell); + } + } + slots + } + + ir::OpKind::BagGet => { + debug_assert_eq!(input_slot_arrs.len(), 1); + input_slot_arrs[0] + } + + ir::OpKind::BagRemove => { + debug_assert_eq!(input_slot_arrs.len(), 1); + self.slots_arena.alloc_extend( + input_slot_arrs[0] + .iter() + .chain(input_slot_arrs[0].iter()) + .cloned(), + ) + } + + ir::OpKind::MakeTuple => self.slots_arena.alloc_extend( + input_slot_arrs + .iter() + .flat_map(|slots| slots.iter().cloned()), + ), + + ir::OpKind::GetTupleField { field_idx } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let input_type = graph.values().node(val_node.inputs[0]).op.result_type; + let field_slots = if let TypeSlots::Tuple { field_slots } = &sc.slots()[input_type] + { + field_slots + } else { + unreachable!() + }; + let (start, end) = field_slots.sub_slots(*field_idx); + &input_slot_arrs[0][start as usize..end as usize] + } + + ir::OpKind::MakeUnion { variant_idx } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let variant_slots = if let TypeSlots::Union { variant_slots } = + &sc.slots()[val_node.op.result_type] + { + variant_slots + } else { + unreachable!() + }; + let (start, end) = variant_slots.sub_slots(*variant_idx); + debug_assert_eq!((end - start) as usize, input_slot_arrs[0].len()); + self.slots_arena + .alloc_extend((0..variant_slots.slot_count()).map(|i| { + if start <= i && i < end { + input_slot_arrs[0][(i - start) as usize] + } else { + self.add_heap_cell() + } + })) + } + + ir::OpKind::UnwrapUnion { variant_idx } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let input_type = graph.values().node(val_node.inputs[0]).op.result_type; + let variant_slots = + if let TypeSlots::Union { variant_slots } = &sc.slots()[input_type] { + variant_slots + } else { + unreachable!() + }; + let (start, end) = variant_slots.sub_slots(*variant_idx); + &input_slot_arrs[0][start as usize..end as usize] + } + + ir::OpKind::MakeNamed => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let new_cell = self.add_heap_cell(); + for &input_cell in input_slot_arrs[0] { + self.copy_data(version, input_cell, version, new_cell); + } + let slot_count = result_slot_count(sc, &val_node); + self.copy_heap_cell(new_cell, slot_count) + } + + ir::OpKind::UnwrapNamed => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let slot_count = result_slot_count(sc, &val_node); + self.copy_heap_cell(input_slot_arrs[0][0], slot_count) + } + }; + replace_none(&mut self.value_slots[val_id], ret_slots).unwrap(); + } + + fn target_arg_slots(&self, graph: &ir::Graph, pred: ir::Predecessor) -> &'a [HeapCellId] { + match pred { + ir::Predecessor::Block(block) => { + self.value_slots[graph.blocks().block_info(block).target_arg.unwrap()].unwrap() + } + ir::Predecessor::Entry => self.arg_slots.unwrap(), + } + } + + fn merge_slots( + &mut self, + new_version: BackRefStateVersionId, + slot_count: u32, + sources: impl Iterator, + ) -> &'a [HeapCellId] + where + F: for<'b> FnMut(&'b Self) -> BackRefStateVersionId, + G: for<'b> FnMut(&'b Self) -> I, + I: Iterator, + { + let min_new_id = self.heap_cells.count(); + let merged_slots: &[_] = self.add_heap_cells(slot_count); + for (mut source_version_fn, mut source) in sources { + let source_version = source_version_fn(self); + // We need to add all of the alias edges up front between slots in the source and slots + // in the value under construction because we might only know that two slots in the + // value under construction alias because of their transitive relationship through + // source slots. For instance, consider the following code: + // + // let x = []; + // let result: (_, _) = choice { (x, x) } or { ([], []) }; + // + // We know that the two slots in result (the value under construction) might alias + // because they alias the first and second tuple elements in the first choice branch + // respectively, and those elements alias. + let mut i = 0; + for source_heap_cell in source(self) { + let merged_heap_cell = merged_slots[i as usize]; + self.copy_data( + source_version, + source_heap_cell, + new_version, + merged_heap_cell, + ); + i += 1; + } + debug_assert_eq!(i, slot_count); + + // Consider the following code: + // + // let x = []; + // let result: (_, _) = choice { (x, []) } or { ([], x) }; + // + // The first and second slots in result (the value under construction) cannot alias. If + // we do not remove the edge between the first slot of result and the first slot of the + // tuple in the first choice branch, then on the next iteration of the loop the + // algorithm will see a transitive alias edge between them. + // + // Note also that we need to remove *all* symmetric edges pointing back from heap cells + // which predate the value under construction, not just symmetric edges pointing back + // from the heap cells which appear directly in the predecessor value. For example, + // consider the following code: + // + // let x = []; + // let y = /* something that aliases x, but has a distinct heap cell */; + // let result: (_, _) = choice { (x, []) } or { ([], y) }; + // + // After processing the first branch of the choice, we need to remove the newly-created + // edges pointing back from both x *and* y. + for &merged_heap_cell in merged_slots { + let merged_aliases = std::mem::take(&mut self.heap_cells[merged_heap_cell].aliases); + for &other in &merged_aliases { + // This removes edges back from heap cells which predate the value under + // construction, but does not remove edges between heap cells in the value under + // construction. Preserving edges within the value under construction is + // important for handling cases like the following example (also discussed + // above) correctly: + // + // let x = []; + // let result: (_, _) = choice { (x, x) } or { ([], []) }; + if other < min_new_id { + // Temporarily violate symmetry invariant + self.heap_cells[other].aliases.remove(&merged_heap_cell); + } + } + debug_assert!(self.heap_cells[merged_heap_cell].aliases.is_empty()); + self.heap_cells[merged_heap_cell].aliases = merged_aliases; + } + } + for &merged_heap_cell in merged_slots { + let merged_aliases = std::mem::take(&mut self.heap_cells[merged_heap_cell].aliases); + for &other in &merged_aliases { + if other < min_new_id { + // Restore symmetry invariant + self.heap_cells[other].aliases.insert(merged_heap_cell); + } + } + debug_assert!(self.heap_cells[merged_heap_cell].aliases.is_empty()); + self.heap_cells[merged_heap_cell].aliases = merged_aliases; + } + merged_slots + } + + fn predecessor_back_refs(&self, pred: ir::Predecessor) -> BackRefStateVersionId { + match pred { + ir::Predecessor::Entry => self.entry_version, + ir::Predecessor::Block(block) => self.block_versions[block].unwrap(), + } + } + + fn analyze_block( + &mut self, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + block: ir::BlockId, + ) { + let block_info = graph.blocks().block_info(block); + let new_version = self.back_ref_states.push(BackRefState { + overlay: HashMap::new(), + parents: block_info + .predecessors + .iter() + .map(|&pred| self.predecessor_back_refs(pred)) + .collect(), + }); + if let Some(param_id) = block_info.param { + let param_slots = if block_info.predecessors.len() == 1 { + self.target_arg_slots(graph, block_info.predecessors[0]) + } else { + let slot_count = id_result_slot_count(sc, graph, param_id); + self.merge_slots( + new_version, + slot_count, + block_info.predecessors.iter().map(|&pred| { + let pred_version = move |this: &Self| this.predecessor_back_refs(pred); + // TODO: is this correct if a block is its own predecessor, without any + // indirection? + let pred_slots = + move |this: &Self| this.target_arg_slots(graph, pred).iter().cloned(); + (pred_version, pred_slots) + }), + ) + }; + replace_none(&mut self.value_slots[param_id], param_slots).unwrap(); + } + for val_id in graph.blocks().block_values(block) { + self.analyze_value(sc, ctx, graph, new_version, val_id); + } + replace_none(&mut self.block_versions[block], new_version).unwrap(); + } + + // TODO: Everything to do with SCC analysis can be significantly optimized + + fn heap_cell_slot_mapping( + &self, + graph: &ir::Graph, + blocks: impl Iterator, + ) -> HeapCellSlotMapping { + let mut heap_cell_to_slots = HashMap::new(); + for block in blocks { + for val_id in block_values_inclusive(graph, block) { + for (i, &heap_cell) in self.value_slots[val_id].unwrap().iter().enumerate() { + heap_cell_to_slots + .entry(heap_cell) + .or_insert_with(SmallVec::new) + .push((val_id, i.try_into().unwrap())); + } + } + } + heap_cell_to_slots + } + + fn summarize_scc( + &mut self, + graph: &ir::Graph, + blocks: impl Iterator, + min_new_id: Count, + heap_cell_slots_inductive: &HeapCellSlotMapping, + heap_cell_slots_current: &HeapCellSlotMapping, + ) -> ForwardSccSummary { + let mut summary = ForwardSccSummary::new(); + for block in blocks { + for val_id in block_values_inclusive(graph, block) { + let block_version = self.block_versions[block].unwrap(); + let slot_summaries = self.value_slots[val_id] + .unwrap() + .iter() + .enumerate() + .map(|(slot_i, &heap_cell)| { + let mut val_summary = ForwardSccSlotSummary { + pre_aliases: Set::new(), + inductive_aliases: Set::new(), + internal_aliases: Set::new(), + back_refs: Self::back_refs_in_states( + &mut self.back_ref_states, + block_version, + heap_cell, + ) + .clone(), + }; + let aliased_heap_cells = std::iter::once(heap_cell) + .chain(self.heap_cells[heap_cell].aliases.iter().cloned()); + for aliased in aliased_heap_cells { + if aliased < min_new_id { + val_summary.pre_aliases.insert(aliased); + } + for &aliased_slot in heap_cell_slots_current + .get(&aliased) + .iter() + .cloned() + .flatten() + { + if aliased_slot == (val_id, slot_i.try_into().unwrap()) { + continue; + } + val_summary.internal_aliases.insert(aliased_slot); + } + for &aliased_slot in heap_cell_slots_inductive + .get(&aliased) + .iter() + .cloned() + .flatten() + { + if aliased_slot == (val_id, slot_i.try_into().unwrap()) { + continue; + } + val_summary.inductive_aliases.insert(aliased_slot); + } + } + val_summary + }) + .collect(); + summary.insert(val_id, slot_summaries); + } + } + summary + } + + fn disconnect_heap_cell(&mut self, heap_cell: HeapCellId) { + let aliases = std::mem::take(&mut self.heap_cells[heap_cell].aliases); + for &other in &aliases { + self.heap_cells[other].aliases.remove(&heap_cell); + } + } + + fn analyze_block_scc( + &mut self, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + scc_id: ir::SccId, + ) { + let scc = graph.sccs().get(scc_id); + match scc.info { + SccKind::Acyclic => { + debug_assert!(scc.items.len() == 1); + self.analyze_block(sc, ctx, graph, scc.items[0]); + } + SccKind::Cyclic => { + let init_version_parents = scc + .items + .iter() + .flat_map(|&block| { + graph + .blocks() + .block_info(block) + .predecessors + .iter() + .filter_map(|&pred| match pred { + ir::Predecessor::Entry => Some(self.entry_version), + ir::Predecessor::Block(pred_block) => { + self.block_versions[pred_block] + } + }) + }) + .collect::>() + .into_iter() + .collect::>(); + let init_version = self.back_ref_states.push(BackRefState { + overlay: HashMap::new(), + parents: init_version_parents, + }); + + let min_new_id = self.heap_cells.count(); + for &block in scc.items { + replace_none(&mut self.block_versions[block], init_version).unwrap(); + for val_id in block_values_inclusive(graph, block) { + let slot_count = id_result_slot_count(sc, graph, val_id); + let init_slots = self.add_heap_cells(slot_count); + replace_none(&mut self.value_slots[val_id], init_slots).unwrap(); + } + } + let mut prev_iter_summary = None; + let mut prev_iter_heap_cell_slot_mapping = + self.heap_cell_slot_mapping(graph, scc.items.iter().cloned()); + let mut prev_iter_min_new_id = min_new_id; + loop { + let curr_iter_min_new_id = self.heap_cells.count(); + // Now: + // - Main layer stores previous iteration state + // - Inductive layer stores irrelevant data + for &block in scc.items { + let version = std::mem::take(&mut self.block_versions[block]); + debug_assert!(version.is_some()); + self.block_versions_inductive[block] = version; + for val_id in block_values_inclusive(graph, block) { + let slots = std::mem::take(&mut self.value_slots[val_id]); + debug_assert!(slots.is_some()); + self.value_slots_inductive[val_id] = slots; + } + // Now: + // - Main layer stores previous iteration state, except for current block, + // for which it stores 'None' + // - Inductive layer stores a mix of irrelevant data and current iteration + // state, except for current block, for which it stores previous iteration + // state + self.analyze_block(sc, ctx, graph, block); + // Now: + // - Main layer stores previous iteration state, except for current block, + // for which it stores current iteration state + // - Inductive layer stores a mix of irrelevant data and current iteration + // state, except for current block, for which it stores previous iteration + // state + std::mem::swap( + &mut self.block_versions[block], + &mut self.block_versions_inductive[block], + ); + for val_id in block_values_inclusive(graph, block) { + std::mem::swap( + &mut self.value_slots[val_id], + &mut self.value_slots_inductive[val_id], + ); + } + // Now: + // - Main layer stores previous iteration state + // - Inductive layer stores a mix of irrelevant data and current iteration + // state. In particular, for all blocks processed so far (including this + // one) it stores current iteration state. + } + // Now: + // - Main layer stores previous iteration state + // - Inductive layer stores current iteration state + for &block in scc.items { + std::mem::swap( + &mut self.block_versions[block], + &mut self.block_versions_inductive[block], + ); + for val_id in block_values_inclusive(graph, block) { + std::mem::swap( + &mut self.value_slots[val_id], + &mut self.value_slots_inductive[val_id], + ); + } + } + // Now: + // - Main layer stores current iteration state + // - Inductive layer stores previous iteration state + let curr_iter_heap_cell_slot_mapping = + self.heap_cell_slot_mapping(graph, scc.items.iter().cloned()); + let curr_iter_summary = self.summarize_scc( + graph, + scc.items.iter().cloned(), + min_new_id, + &prev_iter_heap_cell_slot_mapping, + &curr_iter_heap_cell_slot_mapping, + ); + if Some(&curr_iter_summary) == prev_iter_summary.as_ref() { + break; + } + + // Garbage collect connectsions to irrelevant heap cells from previous iteration + for heap_cell in + (prev_iter_min_new_id.0 .0..curr_iter_min_new_id.0 .0).map(HeapCellId) + { + self.disconnect_heap_cell(heap_cell); + } + + prev_iter_summary = Some(curr_iter_summary); + prev_iter_heap_cell_slot_mapping = curr_iter_heap_cell_slot_mapping; + prev_iter_min_new_id = curr_iter_min_new_id; + } + } + } + } + + fn analyze_graph( + slots_arena: &'a Arena, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + arg_alias: Option>, + ) -> (Self, &'a [HeapCellId]) { + let mut heap_cells = IdVec::new(); + let arg_slots = graph + .blocks() + .block_info(graph.entry_block()) + .param + .map(|arg_val_id| { + let slot_count = id_result_slot_count(sc, graph, arg_val_id); + let arg_slots: &[_] = slots_arena.alloc_extend((0..slot_count).map(|i| { + let mut origin_arg_slots = Set::new(); + origin_arg_slots.insert(i); + heap_cells.push(ForwardData { + origin: Origin::FromArgSlots(origin_arg_slots), + aliases: Set::new(), + }) + })); + if let Some(arg_alias) = arg_alias { + let fst = arg_slots[*arg_alias.fst() as usize]; + let snd = arg_slots[*arg_alias.snd() as usize]; + heap_cells[fst].aliases.insert(snd); + heap_cells[snd].aliases.insert(fst); + } + arg_slots + }); + + let mut back_ref_states = IdVec::new(); + let entry_version = back_ref_states.push(BackRefState { + overlay: arg_slots + .iter() + .cloned() + .flatten() + .enumerate() + .map(|(slot_i, &heap_cell)| { + let mut heap_cell_back_refs = HashSet::new(); + heap_cell_back_refs.insert(QueryPoint::EntryArg(slot_i.try_into().unwrap())); + (heap_cell, heap_cell_back_refs) + }) + .collect(), + parents: Vec::new(), + }); + + let mut state = ForwardState { + slots_arena, + value_slots: IdVec::filled_with(graph.values().count(), || None), + value_slots_inductive: IdVec::filled_with(graph.values().count(), || None), + call_arg_aliases: IdVec::filled_with(graph.callee_spec_vars(), || None), + call_arg_origins: IdVec::filled_with(graph.callee_spec_vars(), || None), + calls: IdVec::filled_with(graph.callee_spec_vars(), || None), + update_origins: IdVec::filled_with(graph.update_mode_vars(), || None), + arg_slots, + heap_cells, + back_ref_states, + block_versions: IdVec::filled_with(graph.blocks().block_count(), || None), + block_versions_inductive: IdVec::filled_with(graph.blocks().block_count(), || None), + entry_version, + fates: HashMap::new(), + }; + + for scc_id in graph.sccs().count().iter() { + state.analyze_block_scc(sc, ctx, graph, scc_id); + } + + let exit_version = state.back_ref_states.push(BackRefState { + overlay: HashMap::new(), + parents: graph + .exit_blocks() + .iter() + .map(|&block| state.block_versions[block].unwrap()) + .collect(), + }); + let ret_slot_count = sc.slots()[graph.ret_type()].slot_count(); + let ret_heap_cells = state.merge_slots( + exit_version, + ret_slot_count, + graph.exit_blocks().iter().map(|&block| { + let block_version = move |this: &Self| this.block_versions[block].unwrap(); + let block_slots = move |this: &Self| { + this.target_arg_slots(graph, ir::Predecessor::Block(block)) + .iter() + .cloned() + }; + (block_version, block_slots) + }), + ); + + for (ret_slot_i, &ret_heap_cell) in ret_heap_cells.iter().enumerate() { + for &query_point in + Self::back_refs_in_states(&mut state.back_ref_states, exit_version, ret_heap_cell) + .iter() + { + if let Fate::Other { ret_slots, .. } = state.fates.entry(query_point).or_default() { + ret_slots.insert(ret_slot_i.try_into().unwrap()); + } + } + } + + (state, ret_heap_cells) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Fate { + DirectTouch, + Other { + indirect_touch: bool, + ret_slots: Set, + }, +} + +impl Fate { + fn union_with(&mut self, other: &Fate) { + match (&mut *self, other) { + (Fate::DirectTouch, _) => {} + (Fate::Other { .. }, Fate::DirectTouch) => { + *self = Fate::DirectTouch; + } + ( + Fate::Other { + indirect_touch: indirect_touch_1, + ret_slots: ret_slots_1, + }, + Fate::Other { + indirect_touch: indirect_touch_2, + ret_slots: ret_slots_2, + }, + ) => { + *indirect_touch_1 = *indirect_touch_1 || *indirect_touch_2; + ret_slots_1.extend(ret_slots_2); + } + } + } +} + +impl Default for Fate { + fn default() -> Self { + Fate::Other { + indirect_touch: false, + ret_slots: Set::new(), + } + } +} + +fn analyze_func( + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + func_def: &ir::FuncDef, + arg_alias: Option>, +) -> FuncAnalysis { + let slots_arena = Arena::new(); + let (forward, ret_slots) = + ForwardState::analyze_graph(&slots_arena, sc, ctx, &func_def.graph, arg_alias); + let mut heap_cell_to_arg_slot = HashMap::::new(); + for (slot_i, &heap_cell) in forward.arg_slots.unwrap().iter().enumerate() { + let existing = heap_cell_to_arg_slot.insert(heap_cell, slot_i.try_into().unwrap()); + debug_assert!(existing.is_none()); + } + let mut heap_cell_to_ret_slots = HashMap::>::new(); + for (slot_i, &heap_cell) in ret_slots.iter().enumerate() { + heap_cell_to_ret_slots + .entry(heap_cell) + .or_insert_with(SmallVec::new) + .push(slot_i.try_into().unwrap()); + } + let arg_slot_analyses = (0..forward.arg_slots.unwrap().len()) + .map(|arg_slot_i| ArgSlotAnalysis { + fate: forward + .fates + .get(&QueryPoint::EntryArg(arg_slot_i.try_into().unwrap())) + .cloned() + .unwrap_or_default(), + }) + .collect(); + let ret_slot_analyses = ret_slots + .iter() + .enumerate() + .map(|(this_ret_slot_i, &heap_cell)| { + let mut arg_aliases = Set::new(); + let mut ret_aliases = Set::new(); + for other in std::iter::once(heap_cell) + .chain(forward.heap_cells[heap_cell].aliases.iter().cloned()) + { + if let Some(&arg_slot_i) = heap_cell_to_arg_slot.get(&other) { + arg_aliases.insert(arg_slot_i); + } + if let Some(ret_slots_i) = heap_cell_to_ret_slots.get(&other) { + for &ret_slot_i in ret_slots_i { + if ret_slot_i as usize != this_ret_slot_i { + ret_aliases.insert(ret_slot_i); + } + } + } + } + RetSlotAnalysis { + from_const: matches!(forward.heap_cells[heap_cell].origin, Origin::FromConst), + arg_aliases, + ret_aliases, + } + }) + .collect(); + FuncAnalysis { + graph_analysis: GraphAnalysis { + updates: IdVec::filled_with_indexed( + func_def.graph.update_mode_vars(), + |update_mode_var| UpdateAnalysis { + origin: forward.update_origins[update_mode_var] + .as_ref() + .unwrap() + .clone(), + fate: forward + .fates + .get(&QueryPoint::Update(update_mode_var)) + .cloned() + .unwrap_or_default(), + }, + ), + calls: IdVec::filled_with_indexed( + func_def.graph.callee_spec_vars(), + |callee_spec_var| { + // let call_fates = backward.call_fates[callee_spec_var].as_ref().unwrap(); + let call_info = forward.calls[callee_spec_var].unwrap(); + CallAnalysis { + callee: call_info.callee, + arg_aliases: forward.call_arg_aliases[callee_spec_var] + .as_ref() + .unwrap() + .clone(), + arg_slots: forward.call_arg_origins[callee_spec_var] + .as_ref() + .unwrap() + .iter() + .enumerate() + .map(|(arg_slot_i, origin)| ArgAnalysis { + origin: origin.clone(), + fate: forward + .fates + .get(&QueryPoint::CallArg( + callee_spec_var, + arg_slot_i.try_into().unwrap(), + )) + .cloned() + .unwrap_or_default(), + }) + .collect(), + ret_slots: (0..call_info.ret_slots) + .map(|ret_slot_i| { + forward + .fates + .get(&QueryPoint::CallRet(callee_spec_var, ret_slot_i)) + .cloned() + .unwrap_or_default() + }) + .collect(), + } + }, + ), + }, + arg_slots: arg_slot_analyses, + ret_slots: ret_slot_analyses, + } +} + +id_type! { + FuncSccId(u32); +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct UpdateAnalysis { + origin: Origin, + fate: Fate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ArgAnalysis { + origin: Origin, + fate: Fate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct CallAnalysis { + // Find a better place to store the callee + callee: FuncId, + arg_aliases: Set>, + arg_slots: SmallVec<[ArgAnalysis; 4]>, + ret_slots: SmallVec<[Fate; 4]>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct GraphAnalysis { + updates: IdVec, + calls: IdVec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ArgSlotAnalysis { + fate: Fate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct RetSlotAnalysis { + from_const: bool, + arg_aliases: Set, + ret_aliases: Set, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct FuncAnalysis { + graph_analysis: GraphAnalysis, + arg_slots: SmallVec<[ArgSlotAnalysis; 4]>, + ret_slots: SmallVec<[RetSlotAnalysis; 4]>, +} + +#[derive(Clone, Debug)] +struct GlobalAnalysisContext<'a> { + func_defs: &'a IdVec, + sccs: &'a FlatSlices, + func_to_scc: &'a IdVec, + committed: IdVec>, FuncAnalysis>>, +} + +impl<'a> GlobalAnalysisContext<'a> { + fn analyze( + &mut self, + sc: &mut SlotCache, + func: FuncId, + arg_alias: Option>, + ) -> &FuncAnalysis { + debug_assert!(!self.committed[func].contains_key(&arg_alias)); + let scc = self.func_to_scc[func]; + let scc_kind = *self.sccs.get(scc).info; + let mut scc_ctx = SccAnalysisContext { + global: &mut *self, + scc, + prev_iter: HashMap::new(), + curr_iter: HashMap::new(), + }; + match scc_kind { + SccKind::Acyclic => { + scc_ctx.get_analysis(sc, func, arg_alias); + debug_assert_eq!(scc_ctx.curr_iter.len(), 1); + } + SccKind::Cyclic => loop { + scc_ctx.get_analysis(sc, func, arg_alias); + // TODO: only compare "signature" information here, not internal annotations on body + // values. + if scc_ctx.curr_iter == scc_ctx.prev_iter { + break; + } + scc_ctx.prev_iter = std::mem::take(&mut scc_ctx.curr_iter); + }, + }; + let results = scc_ctx.curr_iter; + for ((analyzed_func, analyzed_arg_alias), analysis) in results { + let existing = self.committed[analyzed_func] + .insert(analyzed_arg_alias, analysis.unwrap_complete()); + debug_assert!(existing.is_none()); + } + &self.committed[func][&arg_alias] + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, PartialEq, Eq)] +enum AnalysisState { + Pending, + Complete(FuncAnalysis), +} + +impl AnalysisState { + fn unwrap_complete(self) -> FuncAnalysis { + match self { + AnalysisState::Pending => unreachable!(), + AnalysisState::Complete(analysis) => analysis, + } + } + + fn unwrap_complete_ref(&self) -> &FuncAnalysis { + match self { + AnalysisState::Pending => unreachable!(), + AnalysisState::Complete(analysis) => analysis, + } + } +} + +#[derive(Debug)] +struct SccAnalysisContext<'a, 'b> { + global: &'b mut GlobalAnalysisContext<'a>, + scc: FuncSccId, + // Invariant: 'prev_iter' should contain no 'Pending' analyses + prev_iter: HashMap<(FuncId, Option>), AnalysisState>, + curr_iter: HashMap<(FuncId, Option>), AnalysisState>, +} + +impl<'a, 'b> SccAnalysisContext<'a, 'b> { + fn get_analysis<'c>( + &'c mut self, + sc: &mut SlotCache, + func: FuncId, + arg_alias: Option>, + ) -> Option<&'c FuncAnalysis> { + if self.global.committed[func].contains_key(&arg_alias) { + // TODO: is there a way to avoid the double lookup here while passing the borrow + // checker? + return Some(&self.global.committed[func][&arg_alias]); + } + if self.global.func_to_scc[func] != self.scc { + return Some(self.global.analyze(sc, func, arg_alias)); + } + // TODO: can we resolve this clippy error while passing the borrow checker? + #[allow(clippy::map_entry)] + if self.curr_iter.contains_key(&(func, arg_alias)) { + // TODO: as above, can we avoid the double lookup? + match &self.curr_iter[&(func, arg_alias)] { + AnalysisState::Complete(analysis) => Some(analysis), + AnalysisState::Pending => self + .prev_iter + .get(&(func, arg_alias)) + .map(AnalysisState::unwrap_complete_ref), + } + } else { + self.curr_iter + .insert((func, arg_alias), AnalysisState::Pending); + let analysis = analyze_func(sc, self, &self.global.func_defs[func], arg_alias); + match self.curr_iter.entry((func, arg_alias)) { + std::collections::hash_map::Entry::Occupied(mut occupied) => { + *occupied.get_mut() = AnalysisState::Complete(analysis); + Some(occupied.into_mut().unwrap_complete_ref()) + } + std::collections::hash_map::Entry::Vacant(_) => { + unreachable!() + } + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct Query { + // TODO: improve sparsity of contextual information, prune everything inessential + arg_aliases: BTreeSet>, + // For the purposes of `arg_slots_touched`, an arg slot being 'FromConst' is the same as being + // touched after the call. + arg_slots_touched: SmallVec<[bool; 8]>, + ret_slots_touched: SmallVec<[bool; 8]>, +} + +impl Query { + fn to_spec(&self, func: FuncId) -> api::FuncSpec { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(func.0.to_le_bytes()); + hasher.update((self.arg_aliases.len() as u64).to_le_bytes()); + for arg_alias in &self.arg_aliases { + hasher.update(arg_alias.fst().to_le_bytes()); + hasher.update(arg_alias.snd().to_le_bytes()); + } + hasher.update((self.arg_slots_touched.len() as u64).to_le_bytes()); + for &arg_touched in &self.arg_slots_touched { + hasher.update(&[arg_touched as u8]); + } + hasher.update((self.ret_slots_touched.len() as u64).to_le_bytes()); + for &ret_touched in &self.ret_slots_touched { + hasher.update(&[ret_touched as u8]); + } + api::FuncSpec(hasher.finalize().into()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct FuncSolution { + pub(crate) update_modes: IdVec, + pub(crate) callee_specs: IdVec, +} + +#[derive(Clone, Debug)] +pub(crate) struct FuncSolutions { + pub(crate) solutions: IdVec>>, +} + +fn resolve_origin<'a>(query: &Query, mut origins: impl Iterator) -> bool { + origins.any(|origin| match origin { + Origin::FromConst => true, + Origin::FromArgSlots(arg_slots) => arg_slots + .iter() + .any(|&arg_slot| query.arg_slots_touched[arg_slot as usize]), + }) +} + +fn resolve_fate<'a>(query: &Query, mut fates: impl Iterator) -> bool { + fates.any(|fate| match fate { + Fate::DirectTouch => true, + Fate::Other { + indirect_touch, + ret_slots, + } => { + *indirect_touch + || ret_slots + .iter() + .any(|&ret_slot| query.ret_slots_touched[ret_slot as usize]) + } + }) +} + +impl FuncSolutions { + fn resolve( + &mut self, + analyses: &IdVec>, FuncAnalysis>>, + func: FuncId, + query: &Query, + ) -> api::FuncSpec { + let spec = query.to_spec(func); + if let std::collections::hash_map::Entry::Vacant(vacant) = self.solutions[func].entry(spec) + { + let func_analyses = &analyses[func]; + let basic_analysis = &func_analyses[&None].graph_analysis; + vacant.insert(None); + let query_analyses: SmallVec<[&GraphAnalysis; 8]> = std::iter::once(basic_analysis) + .chain( + query + .arg_aliases + .iter() + .map(|&arg_alias| &func_analyses[&Some(arg_alias)].graph_analysis), + ) + .collect(); + let update_modes = + IdVec::filled_with_indexed(basic_analysis.updates.count(), |update_mode_var| { + let touched = resolve_origin( + query, + query_analyses + .iter() + .map(|analysis| &analysis.updates[update_mode_var].origin), + ) || resolve_fate( + query, + query_analyses + .iter() + .map(|analysis| &analysis.updates[update_mode_var].fate), + ); + if touched { + api::UpdateMode::Immutable + } else { + api::UpdateMode::InPlace + } + }); + let callee_specs = + IdVec::filled_with_indexed(basic_analysis.calls.count(), |callee_spec_var| { + let mut sub_arg_aliases = BTreeSet::new(); + for analysis in &query_analyses { + sub_arg_aliases.extend(&analysis.calls[callee_spec_var].arg_aliases); + } + let num_arg_slots = basic_analysis.calls[callee_spec_var].arg_slots.len(); + let num_ret_slots = basic_analysis.calls[callee_spec_var].ret_slots.len(); + let sub_arg_slots_touched = (0..num_arg_slots) + .map(|arg_slot_i| { + resolve_origin( + query, + query_analyses.iter().map(|analysis| { + &analysis.calls[callee_spec_var].arg_slots[arg_slot_i].origin + }), + ) || resolve_fate( + query, + query_analyses.iter().map(|analysis| { + &analysis.calls[callee_spec_var].arg_slots[arg_slot_i].fate + }), + ) + }) + .collect(); + let sub_ret_slots_touched = (0..num_ret_slots) + .map(|ret_slot_i| { + resolve_fate( + query, + query_analyses.iter().map(|analysis| { + &analysis.calls[callee_spec_var].ret_slots[ret_slot_i] + }), + ) + }) + .collect(); + let sub_query = Query { + arg_aliases: sub_arg_aliases, + arg_slots_touched: sub_arg_slots_touched, + ret_slots_touched: sub_ret_slots_touched, + }; + self.resolve( + analyses, + basic_analysis.calls[callee_spec_var].callee, + &sub_query, + ) + }); + let solution = FuncSolution { + update_modes, + callee_specs, + }; + self.solutions[func].insert(spec, Some(solution)); + } + spec + } +} + +#[derive(Clone, Debug)] +pub(crate) struct ProgramSolutions { + pub(crate) funcs: FuncSolutions, + pub(crate) entry_points: IdVec, +} + +pub(crate) fn analyze(tc: TypeCache, program: &ir::Program) -> ProgramSolutions { + let mut sc = SlotCache::new(tc); + + let func_sccs: FlatSlices = + strongly_connected(program.funcs.count(), |func_id| { + let func_def = &program.funcs[func_id]; + let values = func_def.graph.values(); + values + .count() + .iter() + .filter_map(move |val_id| match &values.node(val_id).op.kind { + ir::ValueKind::Op(ir::OpKind::Call { + callee, + callee_spec_var: _, + }) => Some(*callee), + + _ => None, + }) + }); + + let mut func_to_scc = IdVec::filled_with(program.funcs.count(), || FuncSccId(u32::MAX)); + for scc_id in func_sccs.count().iter() { + for &func in func_sccs.get(scc_id).items { + func_to_scc[func] = scc_id; + } + } + + let mut ctx = GlobalAnalysisContext { + func_defs: &program.funcs, + sccs: &func_sccs, + func_to_scc: &func_to_scc, + committed: IdVec::filled_with(program.funcs.count(), HashMap::new), + }; + + for (_, &func) in &program.entry_points { + if !ctx.committed[func].contains_key(&None) { + ctx.analyze(&mut sc, func, None); + } + } + + let mut func_solutions = FuncSolutions { + solutions: IdVec::filled_with(program.funcs.count(), HashMap::new), + }; + + let entry_point_solutions = program.entry_points.map(|_, &func| { + func_solutions.resolve( + &ctx.committed, + func, + &Query { + arg_aliases: BTreeSet::new(), + arg_slots_touched: SmallVec::new(), + ret_slots_touched: SmallVec::new(), + }, + ) + }); + + ProgramSolutions { + funcs: func_solutions, + entry_points: entry_point_solutions, + } +} diff --git a/vendor/morphic_lib/src/api.rs b/vendor/morphic_lib/src/api.rs index 6dc57d1c6f..893e2a1c48 100644 --- a/vendor/morphic_lib/src/api.rs +++ b/vendor/morphic_lib/src/api.rs @@ -1,7 +1,9 @@ use sha2::{digest::Digest, Sha256}; use smallvec::SmallVec; use std::collections::{btree_map::Entry, BTreeMap}; +use std::rc::Rc; +use crate::analyze; use crate::preprocess; use crate::render_api_ir; use crate::util::blocks::Blocks; @@ -1372,13 +1374,15 @@ pub enum UpdateMode { pub const SPEC_HASH_BYTES: usize = 32; #[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct FuncSpec(pub [u8; SPEC_HASH_BYTES]); /// The solution table for an individual specialization. pub struct FuncSpecSolutions { - func_def: FuncDef, - callee_specs: IdVec, + // TODO: eliminate the RC here (this will require introducing a lifetime, and is therefore a + // breaking API change) + func_def: Rc, + solution: analyze::FuncSolution, } impl FuncSpecSolutions { @@ -1392,7 +1396,7 @@ impl FuncSpecSolutions { .callee_spec_vars .get_by_val(&var.into()) { - Some(id) => Ok(self.callee_specs[id]), + Some(id) => Ok(self.solution.callee_specs[id]), None => Err(ErrorKind::CalleeSpecVarNotFound(var.into()).into()), } } @@ -1407,7 +1411,7 @@ impl FuncSpecSolutions { .update_mode_vars .get_by_val(&var.into()) { - Some(_id) => Ok(UpdateMode::Immutable), + Some(id) => Ok(self.solution.update_modes[id]), None => Err(ErrorKind::UpdateModeVarNotFound(var.into()).into()), } } @@ -1416,20 +1420,20 @@ impl FuncSpecSolutions { /// Zero or more specializations for a single function, and the solution table for each /// specialization. pub struct FuncSolutions { - spec: FuncSpec, - spec_solutions: FuncSpecSolutions, + spec_solutions: BTreeMap, } impl FuncSolutions { pub fn specs(&self) -> impl Iterator { - std::iter::once(&self.spec) + self.spec_solutions.keys() } pub fn spec(&self, spec: &FuncSpec) -> Result<&FuncSpecSolutions> { - if &self.spec != spec { - return Err(ErrorKind::FuncSpecNotFound(*spec).into()); + if let Some(solution) = self.spec_solutions.get(spec) { + Ok(solution) + } else { + Err(ErrorKind::FuncSpecNotFound(*spec).into()) } - Ok(&self.spec_solutions) } } @@ -1501,7 +1505,7 @@ impl ModSolutions { /// Specializations and solution tables generated for the entire program. pub struct Solutions { mods: BTreeMap, - entry_points: BTreeMap, + entry_points: BTreeMap, } impl Solutions { @@ -1521,9 +1525,8 @@ impl Solutions { // TODO: The clone here is unnecessary -- avoid it! // (might require something like a transmute) match self.entry_points.get(&entry_point.into()) { - Some((mod_name, func_name)) => { - let spec = hash_func_name(mod_name.borrowed(), func_name.borrowed()); - Ok((mod_name.borrowed(), func_name.borrowed(), spec)) + Some((mod_name, func_name, spec)) => { + Ok((mod_name.borrowed(), func_name.borrowed(), *spec)) } None => Err(ErrorKind::EntryPointNotFound(entry_point.into()).into()), } @@ -1552,11 +1555,14 @@ fn populate_specs( results.into_mapped(|_, spec| spec.unwrap()) } -pub fn solve(program: Program) -> Result { - preprocess::preprocess(&program).map_err(ErrorKind::PreprocessError)?; +pub fn solve(api_program: Program) -> Result { + let (nc, tc, program) = + preprocess::preprocess(&api_program).map_err(ErrorKind::PreprocessError)?; + + let mut solutions = analyze::analyze(tc, &program); Ok(Solutions { - mods: program + mods: api_program .mods .into_iter() .map(|(mod_name, mod_def)| { @@ -1565,16 +1571,27 @@ pub fn solve(program: Program) -> Result { .func_defs .into_iter() .map(|(func_name, func_def)| { - let callee_specs = populate_specs( - func_def.builder.expr_builder.callee_spec_vars.count(), - &func_def.builder.expr_builder.vals, - ); + // TODO: avoid the clones here + let func_id = nc + .funcs + .get_by_val(&(mod_name.clone(), func_name.clone())) + .unwrap(); + let func_def = Rc::new(func_def); let func_sols = FuncSolutions { - spec: hash_func_name(mod_name.borrowed(), func_name.borrowed()), - spec_solutions: FuncSpecSolutions { - func_def, - callee_specs, - }, + spec_solutions: std::mem::take( + &mut solutions.funcs.solutions[func_id], + ) + .into_iter() + .map(|(spec, solution)| { + ( + spec, + FuncSpecSolutions { + func_def: func_def.clone(), + solution: solution.unwrap(), + }, + ) + }) + .collect(), }; (func_name, func_sols) }) @@ -1600,7 +1617,15 @@ pub fn solve(program: Program) -> Result { (mod_name, mod_sols) }) .collect(), - entry_points: program.entry_points, + entry_points: api_program + .entry_points + .into_iter() + .map(|(entry_point_name, (mod_name, func_name))| { + let entry_point_id = nc.entry_points.get_by_val(&entry_point_name).unwrap(); + let spec = solutions.entry_points[entry_point_id]; + (entry_point_name, (mod_name, func_name, spec)) + }) + .collect(), }) } diff --git a/vendor/morphic_lib/src/ir.rs b/vendor/morphic_lib/src/ir.rs index b6408ebc98..d283d84bed 100644 --- a/vendor/morphic_lib/src/ir.rs +++ b/vendor/morphic_lib/src/ir.rs @@ -6,10 +6,12 @@ use smallvec::SmallVec; use crate::api::{CalleeSpecVarId, UpdateModeVarId}; -use crate::name_cache::{ConstId, FuncId}; +use crate::name_cache::{ConstId, EntryPointId, FuncId, NamedTypeId}; use crate::type_cache::TypeId; use crate::util::blocks::Blocks; -use crate::util::flat_slices::{FlatSlices, Slice}; +use crate::util::flat_slices::FlatSlices; +use crate::util::id_type::Count; +use crate::util::id_vec::IdVec; use crate::util::op_graph::OpGraph; use crate::util::strongly_connected::{strongly_connected, SccKind}; @@ -123,6 +125,13 @@ pub(crate) enum JumpTarget { Ret, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum Predecessor { + Block(BlockId), + Entry, +} + +pub(crate) const PREDECESSORS_INLINE_COUNT: usize = 8; pub(crate) const JUMP_TARGETS_INLINE_COUNT: usize = 8; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -132,6 +141,8 @@ pub(crate) struct BlockInfo { /// /// Invariant: If `param` is `Some`, it must point to a `BlockParam` value, not an `Op`. pub(crate) param: Option, + /// Blocks which jump to this block + pub(crate) predecessors: SmallVec<[Predecessor; PREDECESSORS_INLINE_COUNT]>, /// List of zero or more jump targets to nondeterministically choose from. pub(crate) jump_targets: SmallVec<[JumpTarget; JUMP_TARGETS_INLINE_COUNT]>, /// Optional argument which will be passed to the chosen jump target. @@ -149,6 +160,7 @@ id_type! { pub(crate) struct GraphBuilder { values: OpGraph, blocks: Blocks, + exit_blocks: SmallVec<[BlockId; 1]>, } impl GraphBuilder { @@ -156,6 +168,7 @@ impl GraphBuilder { GraphBuilder { values: OpGraph::new(), blocks: Blocks::new(), + exit_blocks: SmallVec::new(), } } @@ -182,6 +195,7 @@ impl GraphBuilder { self.values.count().0, BlockInfo { param: None, + predecessors: SmallVec::new(), jump_targets: SmallVec::new(), target_arg: None, }, @@ -200,6 +214,7 @@ impl GraphBuilder { self.values.count().0, BlockInfo { param: Some(param_id), + predecessors: SmallVec::new(), jump_targets: SmallVec::new(), target_arg: None, }, @@ -213,8 +228,22 @@ impl GraphBuilder { target_arg: Option, jump_targets: SmallVec<[JumpTarget; JUMP_TARGETS_INLINE_COUNT]>, ) { + for target in &jump_targets { + match target { + &JumpTarget::Block(successor) => { + self.blocks + .block_info_mut(successor) + .predecessors + .push(Predecessor::Block(block)); + } + JumpTarget::Ret => { + self.exit_blocks.push(block); + } + } + } let info = self.blocks.block_info_mut(block); info.target_arg = target_arg; + debug_assert!(info.jump_targets.is_empty()); info.jump_targets = jump_targets; } @@ -226,23 +255,37 @@ impl GraphBuilder { &self.blocks } - pub(crate) fn build(self, entry_block: BlockId) -> Graph { + pub(crate) fn build( + mut self, + entry_block: BlockId, + ret_type: TypeId, + update_mode_vars: Count, + callee_spec_vars: Count, + ) -> Graph { debug_assert!(entry_block < self.blocks.block_count()); - let rev_sccs = strongly_connected(self.blocks.block_count(), |block| { + self.blocks + .block_info_mut(entry_block) + .predecessors + .push(Predecessor::Entry); + let sccs = strongly_connected(self.blocks.block_count(), |block| { self.blocks .block_info(block) - .jump_targets + .predecessors .iter() - .filter_map(|&jump_target| match jump_target { - JumpTarget::Ret => None, - JumpTarget::Block(target) => Some(target), + .filter_map(|&pred| match pred { + Predecessor::Entry => None, + Predecessor::Block(pred_block) => Some(pred_block), }) }); Graph { values: self.values, blocks: self.blocks, entry_block, - rev_sccs, + exit_blocks: self.exit_blocks, + ret_type, + sccs, + update_mode_vars, + callee_spec_vars, } } } @@ -252,14 +295,15 @@ pub(crate) struct Graph { values: OpGraph, blocks: Blocks, entry_block: BlockId, - - // Invariant: `rev_sccs` must be stored in *reverse* topological order. If an SCC 'A' can jump - // to an SCC 'B', then 'A' must appear *after* 'B' in `rev_sccs`. - // - // We don't store the SCCs in topological order because control flow graph edges point from - // *source block* to *target block*, so running Tarjan's algorithm on the control flow graph - // gives us a reverse topological sort rather than a topological sort. - rev_sccs: FlatSlices, + // We use an inline capacity of 1 here because, in the current implementation of `preprocess`, + // there is always exactly one exit block per function. However, this is no fundamental reason + // this must be so. + exit_blocks: SmallVec<[BlockId; 1]>, + ret_type: TypeId, + // Invariant: `sccs` is strored in topological order. + sccs: FlatSlices, + update_mode_vars: Count, + callee_spec_vars: Count, } impl Graph { @@ -275,20 +319,24 @@ impl Graph { self.entry_block } - pub(crate) fn rev_sccs(&self) -> &FlatSlices { - &self.rev_sccs + pub(crate) fn exit_blocks(&self) -> &[BlockId] { + &self.exit_blocks } - /// Iterate over sccs in topological order. - /// - /// IF an SCC 'A' can jump to an SCC 'B', then 'A' is guaranteed to appear *before* 'B' in the - /// returned iterator. - pub(crate) fn iter_sccs(&self) -> impl Iterator> + '_ { - self.rev_sccs - .count() - .iter() - .rev() - .map(move |scc_id| self.rev_sccs.get(scc_id)) + pub(crate) fn ret_type(&self) -> TypeId { + self.ret_type + } + + pub(crate) fn sccs(&self) -> &FlatSlices { + &self.sccs + } + + pub(crate) fn update_mode_vars(&self) -> Count { + self.update_mode_vars + } + + pub(crate) fn callee_spec_vars(&self) -> Count { + self.callee_spec_vars } } @@ -301,3 +349,11 @@ pub(crate) struct FuncDef { pub(crate) struct ConstDef { pub(crate) graph: Graph, } + +#[derive(Clone, Debug)] +pub(crate) struct Program { + pub(crate) named_types: IdVec, + pub(crate) funcs: IdVec, + pub(crate) consts: IdVec, + pub(crate) entry_points: IdVec, +} diff --git a/vendor/morphic_lib/src/lib.rs b/vendor/morphic_lib/src/lib.rs index a1470770dc..3b72a6227b 100644 --- a/vendor/morphic_lib/src/lib.rs +++ b/vendor/morphic_lib/src/lib.rs @@ -4,6 +4,7 @@ #[macro_use] mod util; +mod analyze; mod api; mod bindings; mod ir; diff --git a/vendor/morphic_lib/src/preprocess.rs b/vendor/morphic_lib/src/preprocess.rs index f52e0f648b..36eccb21bd 100644 --- a/vendor/morphic_lib/src/preprocess.rs +++ b/vendor/morphic_lib/src/preprocess.rs @@ -265,7 +265,9 @@ struct FuncSig { ret_type: TypeId, } -pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { +pub(crate) fn preprocess( + program: &api::Program, +) -> Result<(NameCache, TypeCache, ir::Program), Error> { let mut nc = NameCache::default(); let mut tc = TypeCache::default(); @@ -348,15 +350,15 @@ pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { const_sigs: &const_sigs, }; - for (func_id, func_def) in &funcs { + let preprocessed_funcs = funcs.try_map(|func_id, func_def| { preprocess_func_def(&mut tc, ctx, func_def, &func_body_types[func_id]) - .map_err(Error::annotate_func_def(&nc, func_id))?; - } + .map_err(Error::annotate_func_def(&nc, func_id)) + })?; - for (const_id, const_def) in &consts { + let preprocessed_consts = consts.try_map(|const_id, const_def| { preprocess_const_def(&mut tc, ctx, const_def, &const_body_types[const_id]) - .map_err(Error::annotate_const_def(&nc, const_id))?; - } + .map_err(Error::annotate_const_def(&nc, const_id)) + })?; let mut entry_points = IdVec::::new(); for (entry_point_name, (mod_, func)) in &program.entry_points { @@ -383,7 +385,16 @@ pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { debug_assert_eq!(nc_id, pushed_id); } - Ok(()) + Ok(( + nc, + tc, + ir::Program { + named_types: typedef_contents, + funcs: preprocessed_funcs, + consts: preprocessed_consts, + entry_points, + }, + )) } #[derive(Clone, Copy, Debug)] @@ -1165,7 +1176,12 @@ fn preprocess_func_def( )?; graph_builder.set_jump_targets(final_block, Some(ret_val), smallvec![ir::JumpTarget::Ret]); Ok(ir::FuncDef { - graph: graph_builder.build(entry_block), + graph: graph_builder.build( + entry_block, + body_types[func_def.ret_type], + func_def.builder.expr_builder.update_mode_vars.count(), + func_def.builder.expr_builder.callee_spec_vars.count(), + ), }) } @@ -1199,6 +1215,11 @@ fn preprocess_const_def( )?; graph_builder.set_jump_targets(final_block, Some(ret_val), smallvec![ir::JumpTarget::Ret]); Ok(ir::ConstDef { - graph: graph_builder.build(entry_block), + graph: graph_builder.build( + entry_block, + body_types[const_def.type_], + const_def.builder.expr_builder.update_mode_vars.count(), + const_def.builder.expr_builder.callee_spec_vars.count(), + ), }) } diff --git a/vendor/morphic_lib/src/type_cache.rs b/vendor/morphic_lib/src/type_cache.rs index beb16b182b..d9b1b9dd18 100644 --- a/vendor/morphic_lib/src/type_cache.rs +++ b/vendor/morphic_lib/src/type_cache.rs @@ -7,6 +7,8 @@ id_type! { pub TypeId(u32); } +// TODO: Add slot information + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum TypeData { Named { named: NamedTypeId }, diff --git a/vendor/morphic_lib/src/util/blocks.rs b/vendor/morphic_lib/src/util/blocks.rs index 22c182cbfa..56a1ce60a0 100644 --- a/vendor/morphic_lib/src/util/blocks.rs +++ b/vendor/morphic_lib/src/util/blocks.rs @@ -14,6 +14,7 @@ struct BlockFrag { min_val: ValId, /// Exclusive bound max_val: ValId, + prev: Option, next: Option, } @@ -57,6 +58,7 @@ impl Blocks { let frag = BlockFrag { min_val: start_hint.clone(), max_val: start_hint, + prev: None, next: None, }; let frag_id = self.frags.push(frag); @@ -77,6 +79,7 @@ impl Blocks { let new_tail = BlockFrag { min_val: val_id.clone(), max_val: ValId::from_index_or_panic(val_id.to_index() + 1), + prev: Some(block.tail), next: None, }; let new_tail_id = self.frags.push(new_tail); @@ -113,4 +116,24 @@ impl Blocks { Some(this_val) }) } + + pub fn block_values_rev(&self, block_id: BlockId) -> impl Iterator + '_ { + let mut frag = &self.frags[self.blocks[block_id].tail]; + let mut val = frag.max_val.clone(); + std::iter::from_fn(move || { + while val.to_index() <= frag.min_val.to_index() { + match frag.prev { + Some(prev) => { + frag = &self.frags[prev]; + val = frag.max_val.clone(); + } + None => { + return None; + } + } + } + val = ValId::from_index_unchecked(val.to_index() - 1); + Some(val.clone()) + }) + } } diff --git a/vendor/morphic_lib/src/util/flat_slices.rs b/vendor/morphic_lib/src/util/flat_slices.rs index fe72cce7e8..ea56c2a624 100644 --- a/vendor/morphic_lib/src/util/flat_slices.rs +++ b/vendor/morphic_lib/src/util/flat_slices.rs @@ -99,14 +99,16 @@ impl FlatSlices { items: &mut self.flat_data[start..end], } } + + pub fn iter(&self) -> impl Iterator)> { + self.count().iter().map(move |i| (i.clone(), self.get(i))) + } } impl fmt::Debug for FlatSlices { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map() - .entries(self.count().iter().map(|i| (i.clone(), self.get(i)))) - .finish() + f.debug_map().entries(self.iter()).finish() } } diff --git a/vendor/morphic_lib/src/util/get2_mut.rs b/vendor/morphic_lib/src/util/get2_mut.rs new file mode 100644 index 0000000000..8359e118ab --- /dev/null +++ b/vendor/morphic_lib/src/util/get2_mut.rs @@ -0,0 +1,16 @@ +use std::cmp::Ordering; + +// inspired by https://docs.rs/generational-arena/0.2.8/generational_arena/struct.Arena.html#method.get2_mut +pub fn get2_mut(slice: &mut [T], i: usize, j: usize) -> Option<(&mut T, &mut T)> { + match i.cmp(&j) { + Ordering::Less => { + let (l, r) = slice.split_at_mut(j); + Some((&mut l[i], &mut r[0])) + } + Ordering::Greater => { + let (l, r) = slice.split_at_mut(i); + Some((&mut r[0], &mut l[j])) + } + Ordering::Equal => None, + } +} diff --git a/vendor/morphic_lib/src/util/id_bi_map.rs b/vendor/morphic_lib/src/util/id_bi_map.rs index 00b04c2142..876574c49f 100644 --- a/vendor/morphic_lib/src/util/id_bi_map.rs +++ b/vendor/morphic_lib/src/util/id_bi_map.rs @@ -67,4 +67,8 @@ impl IdBiMap { pub fn get_by_val(&self, val: &V) -> Option { self.val_to_key.get(val).cloned() } + + pub fn iter(&self) -> impl Iterator { + self.key_to_val.iter() + } } diff --git a/vendor/morphic_lib/src/util/id_vec.rs b/vendor/morphic_lib/src/util/id_vec.rs index ab222559ea..52c6553502 100644 --- a/vendor/morphic_lib/src/util/id_vec.rs +++ b/vendor/morphic_lib/src/util/id_vec.rs @@ -6,6 +6,7 @@ use std::ops::{Index, IndexMut}; use std::slice; use std::vec; +use crate::util::get2_mut::get2_mut; use crate::util::id_type::{Count, Id}; #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -129,6 +130,13 @@ impl IdVec { } } + pub fn filled_with_indexed(count: Count, mut f: impl FnMut(K) -> V) -> Self { + IdVec { + key: PhantomData, + items: count.iter().map(|k| f(k)).collect(), + } + } + pub fn items(&self) -> &[V] { &self.items } @@ -234,6 +242,10 @@ impl IdVec { } Some(Self::from_items(items)) } + + pub fn get2_mut(&mut self, i: K, j: K) -> Option<(&mut V, &mut V)> { + get2_mut(&mut self.items, i.to_index(), j.to_index()) + } } impl> Index for IdVec { diff --git a/vendor/morphic_lib/src/util/mod.rs b/vendor/morphic_lib/src/util/mod.rs index d5fb572b08..a7f5146c48 100644 --- a/vendor/morphic_lib/src/util/mod.rs +++ b/vendor/morphic_lib/src/util/mod.rs @@ -9,8 +9,10 @@ pub mod forward_trait; pub mod blocks; pub mod flat_slices; +pub mod get2_mut; pub mod id_bi_map; pub mod id_vec; +pub mod norm_pair; pub mod op_graph; pub mod replace_none; pub mod strongly_connected; diff --git a/vendor/morphic_lib/src/util/norm_pair.rs b/vendor/morphic_lib/src/util/norm_pair.rs new file mode 100644 index 0000000000..f370b40cfa --- /dev/null +++ b/vendor/morphic_lib/src/util/norm_pair.rs @@ -0,0 +1,33 @@ +/// A normalized unordered pair, where the first component is always <= the second +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct NormPair(T, T); + +impl NormPair { + pub fn new(fst: T, snd: T) -> Self { + if fst <= snd { + NormPair(fst, snd) + } else { + NormPair(snd, fst) + } + } + + pub fn fst(&self) -> &T { + &self.0 + } + + pub fn snd(&self) -> &T { + &self.1 + } + + pub fn into_fst(self) -> T { + self.0 + } + + pub fn into_snd(self) -> T { + self.1 + } + + pub fn into_tuple(self) -> (T, T) { + (self.0, self.1) + } +} diff --git a/vendor/morphic_lib/tests/recursive.rs b/vendor/morphic_lib/tests/recursive.rs index 9e50172a50..629e19852d 100644 --- a/vendor/morphic_lib/tests/recursive.rs +++ b/vendor/morphic_lib/tests/recursive.rs @@ -1,6 +1,6 @@ use morphic_lib::{ BlockExpr, CalleeSpecVar, EntryPointName, Error, ExprContext, FuncDefBuilder, FuncName, - ModDefBuilder, ModName, ProgramBuilder, TypeContext, UpdateModeVar, + ModDefBuilder, ModName, ProgramBuilder, TypeContext, UpdateMode, UpdateModeVar, }; #[test] @@ -83,7 +83,8 @@ fn test_recursive() { .func_solutions(FuncName(b"rec"))? .spec(&rec_spec)?; - let _update_mode = rec_sol.update_mode(UpdateModeVar(b"mode"))?; + let update_mode = rec_sol.update_mode(UpdateModeVar(b"mode"))?; + assert_eq!(update_mode, UpdateMode::InPlace); Ok(()) } diff --git a/vendor/morphic_lib/tests/structures.rs b/vendor/morphic_lib/tests/structures.rs new file mode 100644 index 0000000000..7143e3f9e1 --- /dev/null +++ b/vendor/morphic_lib/tests/structures.rs @@ -0,0 +1,73 @@ +use morphic_lib::{ + BlockExpr, EntryPointName, Error, ExprContext, FuncDefBuilder, FuncName, ModDefBuilder, + ModName, ProgramBuilder, TypeContext, UpdateMode, UpdateModeVar, +}; + +#[test] +fn test_structures() { + fn run() -> Result<(), Error> { + let main_def = { + let mut f = FuncDefBuilder::new(); + let b = f.add_block(); + let h1 = f.add_new_heap_cell(b)?; + let h2 = f.add_new_heap_cell(b)?; + let t = f.add_make_tuple(b, &[h1, h2])?; + let heap_cell_type = f.add_heap_cell_type(); + let u1 = f.add_make_union(b, &[heap_cell_type, heap_cell_type], 0, h1)?; + let u2 = f.add_make_union(b, &[heap_cell_type, heap_cell_type], 1, h2)?; + let h3 = f.add_get_tuple_field(b, t, 0)?; + let h4 = f.add_get_tuple_field(b, t, 1)?; + let h5 = f.add_unwrap_union(b, u1, 0)?; + let h6 = f.add_unwrap_union(b, u2, 1)?; + f.add_touch(b, h3)?; + f.add_update_write_only(b, UpdateModeVar(b"mode1"), h1)?; + f.add_update_write_only(b, UpdateModeVar(b"mode2"), h2)?; + f.add_update_write_only(b, UpdateModeVar(b"mode3"), h5)?; + f.add_update_write_only(b, UpdateModeVar(b"mode4"), h6)?; + f.add_touch(b, h4)?; + let unit = f.add_make_tuple(b, &[])?; + let unit_type = f.add_tuple_type(&[])?; + f.build(unit_type, unit_type, BlockExpr(b, unit))? + }; + + let main_mod = { + let mut m = ModDefBuilder::new(); + m.add_func(FuncName(b"main"), main_def)?; + m.build()? + }; + + let program = { + let mut p = ProgramBuilder::new(); + p.add_mod(ModName(b"main"), main_mod)?; + p.add_entry_point(EntryPointName(b"main"), ModName(b"main"), FuncName(b"main"))?; + p.build()? + }; + + let program_sol = morphic_lib::solve(program)?; + + let (_, _, main_spec) = program_sol.entry_point_solution(EntryPointName(b"main"))?; + + let main_mod_sol = program_sol.mod_solutions(ModName(b"main"))?; + + let main_def_sol = main_mod_sol + .func_solutions(FuncName(b"main"))? + .spec(&main_spec)?; + + let mode1 = main_def_sol.update_mode(UpdateModeVar(b"mode1"))?; + let mode2 = main_def_sol.update_mode(UpdateModeVar(b"mode2"))?; + let mode3 = main_def_sol.update_mode(UpdateModeVar(b"mode3"))?; + let mode4 = main_def_sol.update_mode(UpdateModeVar(b"mode4"))?; + + assert_eq!(mode1, UpdateMode::InPlace); + assert_eq!(mode2, UpdateMode::Immutable); + assert_eq!(mode3, UpdateMode::InPlace); + assert_eq!(mode4, UpdateMode::Immutable); + + Ok(()) + } + + let result = run(); + if let Err(err) = result { + panic!("error: {}", err); + } +} From 79a4f2bbf7d039ca23ba63fe142ed2a3762f7222 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 2 Oct 2021 22:07:12 +0100 Subject: [PATCH 108/136] Refactor SymbolStorage local_id code, and copy_memory SymbolStorage had some rather ad-hoc methods for extracting pieces of data. This change makes that more intentional and organised. Also cleaned up the API for the related function copy_memory, as it had lots of positional arguments with the same types. Created a struct for this just to name them and make the code clearer. --- compiler/gen_wasm/src/backend.rs | 204 ++++++++++++++++--------------- compiler/gen_wasm/src/lib.rs | 50 ++++---- compiler/gen_wasm/src/storage.rs | 146 +++++++++++++--------- 3 files changed, 217 insertions(+), 183 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index b9c6cd6900..b6b837acf4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,8 +13,8 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::{StackMemoryLocation, SymbolStorage}; use crate::{ - pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, MemoryCopy, PTR_SIZE, - PTR_TYPE, + copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, CopyMemoryConfig, + LocalId, PTR_SIZE, PTR_TYPE, }; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. @@ -164,7 +164,7 @@ impl<'a> WasmBackend<'a> { wasm_layout: WasmLayout, symbol: Symbol, kind: LocalKind, - ) -> SymbolStorage { + ) -> Option { let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); match kind { @@ -176,32 +176,33 @@ impl<'a> WasmBackend<'a> { } } - let storage = match wasm_layout { - WasmLayout::LocalOnly(value_type, size) => SymbolStorage::Local { - local_id: next_local_id, - value_type, - size, - }, + let (maybe_local_id, storage) = match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => ( + Some(next_local_id), + SymbolStorage::Local { + local_id: next_local_id, + value_type, + size, + }, + ), - WasmLayout::HeapMemory => SymbolStorage::Local { - local_id: next_local_id, - value_type: PTR_TYPE, - size: PTR_SIZE, - }, + WasmLayout::HeapMemory => ( + Some(next_local_id), + SymbolStorage::Local { + local_id: next_local_id, + value_type: PTR_TYPE, + size: PTR_SIZE, + }, + ), WasmLayout::StackMemory { size, alignment_bytes, } => { let location = match kind { - LocalKind::Parameter => StackMemoryLocation::ExternalPointer(next_local_id), + LocalKind::Parameter => StackMemoryLocation::CallerFrame(next_local_id), LocalKind::Variable => { - let offset = - round_up_to_alignment(self.stack_memory, alignment_bytes as i32); - - self.stack_memory = offset + size as i32; - match self.stack_frame_pointer { Some(_) => {} None => { @@ -209,20 +210,29 @@ impl<'a> WasmBackend<'a> { } }; - StackMemoryLocation::InternalOffset(offset as u32) + let offset = + round_up_to_alignment(self.stack_memory, alignment_bytes as i32); + + self.stack_memory = offset + size as i32; + + StackMemoryLocation::OwnFrame(offset as u32) } }; - SymbolStorage::StackMemory { - location, - size, - alignment_bytes, - } + + ( + None, + SymbolStorage::StackMemory { + location, + size, + alignment_bytes, + }, + ) } }; - self.symbol_storage_map.insert(symbol, storage.clone()); + self.symbol_storage_map.insert(symbol, storage); - storage + maybe_local_id } fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage { @@ -236,20 +246,42 @@ impl<'a> WasmBackend<'a> { fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId { let storage = self.get_symbol_storage(sym); - storage.local_id(self.stack_frame_pointer) + match storage { + SymbolStorage::Local { local_id, .. } => *local_id, + _ => { + panic!("{:?} does not have a local_id", sym); + } + } } + /// Load a symbol, e.g. for passing to a function call fn load_symbol(&mut self, sym: &Symbol) { - let storage = self.get_symbol_storage(sym); - let index: u32 = storage.local_id(self.stack_frame_pointer).0; - self.instructions.push(GetLocal(index)); + let storage = self.get_symbol_storage(sym).to_owned(); + match storage { + SymbolStorage::Local { local_id, .. } + | SymbolStorage::StackMemory { + location: StackMemoryLocation::CallerFrame(local_id), + .. + } => { + self.instructions.push(GetLocal(local_id.0)); + } + + SymbolStorage::StackMemory { + location: StackMemoryLocation::OwnFrame(offset), + .. + } => { + self.instructions.extend([ + GetLocal(self.stack_frame_pointer.unwrap().0), + I32Const(offset as i32), + I32Add, + ]); + } + } } /// start a loop that leaves a value on the stack fn start_loop_with_return(&mut self, value_type: ValueType) { self.block_depth += 1; - - // self.instructions.push(Loop(BlockType::NoResult)); self.instructions.push(Loop(BlockType::Value(value_type))); } @@ -276,7 +308,7 @@ impl<'a> WasmBackend<'a> { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later let storage = SymbolStorage::StackMemory { - location: StackMemoryLocation::ExternalPointer(LocalId(0)), + location: StackMemoryLocation::CallerFrame(LocalId(0)), size, alignment_bytes, }; @@ -289,16 +321,12 @@ impl<'a> WasmBackend<'a> { Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - let local_id = self - .insert_local(wasm_layout, *sym, LocalKind::Variable) - .local_id(self.stack_frame_pointer); + let maybe_local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable); self.build_expr(sym, expr, layout)?; - // If this local is shared with the stack frame pointer, it's already assigned - match self.stack_frame_pointer { - Some(sfp) if sfp == local_id => {} - _ => self.instructions.push(SetLocal(local_id.0)), + if let Some(local_id) = maybe_local_id { + self.instructions.push(SetLocal(local_id.0)); } self.build_stmt(following, ret_layout)?; @@ -317,21 +345,23 @@ impl<'a> WasmBackend<'a> { alignment_bytes, } => { let (from_ptr, from_offset) = match location { - StackMemoryLocation::ExternalPointer(local_id) => (*local_id, 0), - StackMemoryLocation::InternalOffset(offset) => { + StackMemoryLocation::CallerFrame(local_id) => (*local_id, 0), + StackMemoryLocation::OwnFrame(offset) => { (self.stack_frame_pointer.unwrap(), *offset) } }; - let copy = MemoryCopy { - from_ptr, - from_offset, - to_ptr: LocalId(0), - to_offset: 0, - size: *size, - alignment_bytes: *alignment_bytes, - }; - copy.generate(&mut self.instructions); + copy_memory( + &mut self.instructions, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr: LocalId(0), + to_offset: 0, + size: *size, + alignment_bytes: *alignment_bytes, + }, + ); } Local { local_id, .. } => { @@ -400,11 +430,10 @@ impl<'a> WasmBackend<'a> { let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); - let local_id = self - .insert_local(wasm_layout, parameter.symbol, LocalKind::Variable) - .local_id(self.stack_frame_pointer); - - jp_parameter_local_ids.push(local_id); + let maybe_local_id = + self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable); + let jp_param_id = maybe_local_id.unwrap(); + jp_parameter_local_ids.push(jp_param_id); } self.start_block(BlockType::NoResult); @@ -526,9 +555,22 @@ impl<'a> WasmBackend<'a> { let storage = self.get_symbol_storage(sym).to_owned(); if let Layout::Struct(field_layouts) = layout { - let (local_id, size) = match storage { - SymbolStorage::StackMemory { size, .. } => { - (storage.local_id(self.stack_frame_pointer), size) + match storage { + SymbolStorage::StackMemory { location, size, .. } => { + if size > 0 { + let (local_id, struct_offset) = + location.local_and_offset(self.stack_frame_pointer); + let mut field_offset = struct_offset; + for (field, _) in fields.iter().zip(field_layouts.iter()) { + field_offset += self.copy_symbol_to_pointer_at_offset( + local_id, + field_offset, + field, + ); + } + } else { + return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); + } } _ => { return Err(format!( @@ -537,20 +579,14 @@ impl<'a> WasmBackend<'a> { )); } }; - - if size > 0 { - let mut relative_offset = 0; - for (field, _) in fields.iter().zip(field_layouts.iter()) { - relative_offset += - self.copy_symbol_to_pointer_at_offset(local_id, relative_offset, field); - } - } else { - return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); - } } else { // Struct expression but not Struct layout => single element. Copy it. let field_storage = self.get_symbol_storage(&fields[0]).to_owned(); - self.copy_storage(&storage, &field_storage); + storage.copy_from( + &field_storage, + &mut self.instructions, + self.stack_frame_pointer, + ); } Ok(()) } @@ -570,30 +606,6 @@ impl<'a> WasmBackend<'a> { ) } - fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) { - let has_stack_memory = to.has_stack_memory(); - debug_assert!(from.has_stack_memory() == has_stack_memory); - - if !has_stack_memory { - debug_assert!(from.value_type() == to.value_type()); - self.instructions - .push(GetLocal(from.local_id(self.stack_frame_pointer).0)); - self.instructions - .push(SetLocal(to.local_id(self.stack_frame_pointer).0)); - } else { - let (size, alignment_bytes) = from.stack_size_and_alignment(); - let copy = MemoryCopy { - from_ptr: from.local_id(self.stack_frame_pointer), - to_ptr: to.local_id(self.stack_frame_pointer), - from_offset: from.address_offset().unwrap(), - to_offset: to.address_offset().unwrap(), - size, - alignment_bytes, - }; - copy.generate(&mut self.instructions); - } - } - fn build_call_low_level( &mut self, lowlevel: &LowLevel, diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 394b59b840..a5517cb4e4 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -121,7 +121,7 @@ fn encode_alignment(bytes: u32) -> u32 { } } -pub struct MemoryCopy { +pub struct CopyMemoryConfig { from_ptr: LocalId, from_offset: u32, to_ptr: LocalId, @@ -130,31 +130,29 @@ pub struct MemoryCopy { alignment_bytes: u32, } -impl MemoryCopy { - pub fn generate(&self, instructions: &mut Vec) { - let alignment_flag = encode_alignment(self.alignment_bytes); - let mut i = 0; - while self.size - i >= 8 { - instructions.push(GetLocal(self.to_ptr.0)); - instructions.push(GetLocal(self.from_ptr.0)); - instructions.push(I64Load(alignment_flag, i + self.from_offset)); - instructions.push(I64Store(alignment_flag, i + self.to_offset)); - i += 8; - } - if self.size - i >= 4 { - instructions.push(GetLocal(self.to_ptr.0)); - instructions.push(GetLocal(self.from_ptr.0)); - instructions.push(I32Load(alignment_flag, i + self.from_offset)); - instructions.push(I32Store(alignment_flag, i + self.to_offset)); - i += 4; - } - while self.size - i > 0 { - instructions.push(GetLocal(self.to_ptr.0)); - instructions.push(GetLocal(self.from_ptr.0)); - instructions.push(I32Load8U(alignment_flag, i + self.from_offset)); - instructions.push(I32Store8(alignment_flag, i + self.to_offset)); - i += 1; - } +pub fn copy_memory(instructions: &mut Vec, config: CopyMemoryConfig) { + let alignment_flag = encode_alignment(config.alignment_bytes); + let mut i = 0; + while config.size - i >= 8 { + instructions.push(GetLocal(config.to_ptr.0)); + instructions.push(GetLocal(config.from_ptr.0)); + instructions.push(I64Load(alignment_flag, i + config.from_offset)); + instructions.push(I64Store(alignment_flag, i + config.to_offset)); + i += 8; + } + if config.size - i >= 4 { + instructions.push(GetLocal(config.to_ptr.0)); + instructions.push(GetLocal(config.from_ptr.0)); + instructions.push(I32Load(alignment_flag, i + config.from_offset)); + instructions.push(I32Store(alignment_flag, i + config.to_offset)); + i += 4; + } + while config.size - i > 0 { + instructions.push(GetLocal(config.to_ptr.0)); + instructions.push(GetLocal(config.from_ptr.0)); + instructions.push(I32Load8U(alignment_flag, i + config.from_offset)); + instructions.push(I32Store8(alignment_flag, i + config.to_offset)); + i += 1; } } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index f1eb565097..cc70956b84 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,14 +1,25 @@ -use crate::{LocalId, MemoryCopy, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use crate::{copy_memory, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; #[derive(Debug, Clone)] pub enum StackMemoryLocation { - ExternalPointer(LocalId), - InternalOffset(u32), + CallerFrame(LocalId), + OwnFrame(u32), +} + +impl StackMemoryLocation { + pub fn local_and_offset(&self, stack_frame_pointer: Option) -> (LocalId, u32) { + match self { + Self::CallerFrame(local_id) => (*local_id, 0), + Self::OwnFrame(offset) => (stack_frame_pointer.unwrap(), *offset), + } + } } #[derive(Debug, Clone)] pub enum SymbolStorage { + // TODO: implicit storage in the VM stack + // TODO: const data storage Local { local_id: LocalId, value_type: ValueType, @@ -22,58 +33,69 @@ pub enum SymbolStorage { } impl SymbolStorage { - pub fn local_id(&self, stack_frame_pointer: Option) -> LocalId { - use StackMemoryLocation::*; - match self { - Self::Local { local_id, .. } => *local_id, - Self::StackMemory { location, .. } => match *location { - ExternalPointer(local_id) => local_id, - InternalOffset(_) => stack_frame_pointer.unwrap(), - }, - } - } - - pub fn value_type(&self) -> ValueType { - match self { - Self::Local { value_type, .. } => *value_type, - Self::StackMemory { .. } => ValueType::I32, - } - } - - pub fn has_stack_memory(&self) -> bool { - match self { - Self::Local { .. } => false, - Self::StackMemory { .. } => true, - } - } - - pub fn address_offset(&self) -> Option { - use StackMemoryLocation::*; - match self { - Self::Local { .. } => None, - Self::StackMemory { location, .. } => match *location { - ExternalPointer(_) => Some(0), - InternalOffset(offset) => Some(offset), - }, - } - } - - pub fn stack_size_and_alignment(&self) -> (u32, u32) { - match self { - Self::StackMemory { - size, - alignment_bytes, - .. - } => (*size, *alignment_bytes), - - _ => (0, 0), + /// generate code to copy from another storage of the same type + pub fn copy_from( + &self, + from: &Self, + instructions: &mut Vec, + stack_frame_pointer: Option, + ) { + match (self, from) { + ( + Self::Local { + local_id: to_local_id, + .. + }, + Self::Local { + local_id: from_local_id, + .. + }, + ) => { + instructions.push(GetLocal(from_local_id.0)); + instructions.push(SetLocal(to_local_id.0)); + } + ( + Self::StackMemory { + location: to_location, + size: to_size, + alignment_bytes: to_alignment_bytes, + }, + Self::StackMemory { + location: from_location, + size: from_size, + alignment_bytes: from_alignment_bytes, + }, + ) => { + let (from_ptr, from_offset) = from_location.local_and_offset(stack_frame_pointer); + let (to_ptr, to_offset) = to_location.local_and_offset(stack_frame_pointer); + debug_assert!(*to_size == *from_size); + debug_assert!(*to_alignment_bytes == *from_alignment_bytes); + copy_memory( + instructions, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size: *from_size, + alignment_bytes: *from_alignment_bytes, + }, + ); + } + _ => { + panic!( + "Cannot copy different storage types {:?} to {:?}", + from, self + ); + } } } + /// Generate code to copy to a memory address (such as a struct index) pub fn copy_to_memory( &self, instructions: &mut Vec, - to_pointer: LocalId, + to_ptr: LocalId, to_offset: u32, stack_frame_pointer: Option, ) -> u32 { @@ -95,27 +117,29 @@ impl SymbolStorage { panic!("Cannot store {:?} with alignment of {:?}", value_type, size); } }; - instructions.push(GetLocal(to_pointer.0)); + instructions.push(GetLocal(to_ptr.0)); instructions.push(GetLocal(local_id.0)); instructions.push(store_instruction); *size } Self::StackMemory { + location, size, alignment_bytes, - .. } => { - let local_id = self.local_id(stack_frame_pointer); - let copy = MemoryCopy { - from_ptr: local_id, - from_offset: 0, - to_ptr: to_pointer, - to_offset, - size: *size, - alignment_bytes: *alignment_bytes, - }; - copy.generate(instructions); + let (from_ptr, from_offset) = location.local_and_offset(stack_frame_pointer); + copy_memory( + instructions, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size: *size, + alignment_bytes: *alignment_bytes, + }, + ); *size } } From 5a8547f2d4dfe666a83861b5ebcafcc8e8dd3578 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:05:17 +0200 Subject: [PATCH 109/136] trick morphic into specializing closure callers --- compiler/mono/src/alias_analysis.rs | 112 +++++++++++++++++++++------- 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index bff15284a7..5a7e3e1e86 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -9,8 +9,10 @@ use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use std::convert::TryFrom; -use crate::ir::{Call, CallType, Expr, ListLiteralElement, Literal, ModifyRc, Proc, Stmt}; -use crate::layout::{Builtin, Layout, ListLayout, UnionLayout}; +use crate::ir::{ + Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, Proc, Stmt, +}; +use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout}; // just using one module for now pub const MOD_APP: ModName = ModName(b"UserApp"); @@ -145,25 +147,33 @@ where }; m.add_const(STATIC_LIST_NAME, static_list_def)?; - // the entry point wrapper - let roc_main_bytes = func_name_bytes_help( - entry_point.symbol, - entry_point.layout.arguments.iter().copied(), - entry_point.layout.result, - ); - let roc_main = FuncName(&roc_main_bytes); - - let entry_point_function = build_entry_point(entry_point.layout, roc_main)?; - let entry_point_name = FuncName(ENTRY_POINT_NAME); - m.add_func(entry_point_name, entry_point_function)?; - let mut type_definitions = MutSet::default(); + let mut host_exposed_functions = Vec::new(); // all other functions for proc in procs { let bytes = func_name_bytes(proc); let func_name = FuncName(&bytes); + if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts { + for (_, (symbol, top_level, layout)) in aliases { + match layout { + RawFunctionLayout::Function(_, _, _) => { + let it = top_level.arguments.iter().copied(); + let bytes = func_name_bytes_help(*symbol, it, top_level.result); + + host_exposed_functions.push((bytes, top_level.arguments)); + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + let it = std::iter::once(Layout::Struct(&[])); + let bytes = func_name_bytes_help(*symbol, it, top_level.result); + + host_exposed_functions.push((bytes, top_level.arguments)); + } + } + } + } + if DEBUG { eprintln!( "{:?}: {:?} with {:?} args", @@ -180,6 +190,19 @@ where m.add_func(func_name, spec)?; } + // the entry point wrapper + let roc_main_bytes = func_name_bytes_help( + entry_point.symbol, + entry_point.layout.arguments.iter().copied(), + entry_point.layout.result, + ); + let roc_main = FuncName(&roc_main_bytes); + + let entry_point_function = + build_entry_point(entry_point.layout, roc_main, &host_exposed_functions)?; + let entry_point_name = FuncName(ENTRY_POINT_NAME); + m.add_func(entry_point_name, entry_point_function)?; + for union_layout in type_definitions { let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); let type_name = TypeName(&type_name_bytes); @@ -219,23 +242,62 @@ where morphic_lib::solve(program) } -fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Result { +fn build_entry_point( + layout: crate::ir::ProcLayout, + func_name: FuncName, + host_exposed_functions: &[([u8; SIZE], &[Layout])], +) -> Result { let mut builder = FuncDefBuilder::new(); - let block = builder.add_block(); + let outer_block = builder.add_block(); - // to the modelling language, the arguments appear out of thin air - let argument_type = build_tuple_type(&mut builder, layout.arguments)?; - let argument = builder.add_unknown_with(block, &[], argument_type)?; + let mut cases = Vec::new(); - let name_bytes = [0; 16]; - let spec_var = CalleeSpecVar(&name_bytes); - let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?; + { + let block = builder.add_block(); + + // to the modelling language, the arguments appear out of thin air + let argument_type = build_tuple_type(&mut builder, layout.arguments)?; + let argument = builder.add_unknown_with(block, &[], argument_type)?; + + let name_bytes = [0; 16]; + let spec_var = CalleeSpecVar(&name_bytes); + let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?; + + // to the modelling language, the result disappears into the void + let unit_type = builder.add_tuple_type(&[])?; + let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; + + cases.push(BlockExpr(block, unit_value)); + } + + // add fake calls to host-exposed functions so they are specialized + for (name_bytes, layouts) in host_exposed_functions { + let host_exposed_func_name = FuncName(name_bytes); + + if host_exposed_func_name == func_name { + continue; + } + + let block = builder.add_block(); + + let type_id = layout_spec(&mut builder, &Layout::Struct(layouts))?; + + let argument = builder.add_unknown_with(block, &[], type_id)?; + + let spec_var = CalleeSpecVar(name_bytes); + let result = + builder.add_call(block, spec_var, MOD_APP, host_exposed_func_name, argument)?; + + let unit_type = builder.add_tuple_type(&[])?; + let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; + + cases.push(BlockExpr(block, unit_value)); + } - // to the modelling language, the result disappears into the void let unit_type = builder.add_tuple_type(&[])?; - let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; + let unit_value = builder.add_choice(outer_block, &cases)?; - let root = BlockExpr(block, unit_value); + let root = BlockExpr(outer_block, unit_value); let spec = builder.build(unit_type, unit_type, root)?; Ok(spec) From 7e6a3431e224002337d761bbfd9901aca9cd1784 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:07:38 +0200 Subject: [PATCH 110/136] trick morphic into updating a value that comes from the host --- compiler/mono/src/alias_analysis.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 5a7e3e1e86..96d6ddb292 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -242,6 +242,22 @@ where morphic_lib::solve(program) } +/// if you want an "escape hatch" which allows you construct "best-case scenario" values +/// of an arbitrary type in much the same way that 'unknown_with' allows you to construct +/// "worst-case scenario" values of an arbitrary type, you can use the following terrible hack: +/// use 'add_make_union' to construct an instance of variant 0 of a union type 'union {(), your_type}', +/// and then use 'add_unwrap_union' to extract variant 1 from the value you just constructed. +/// In the current implementation (but not necessarily in future versions), +/// I can promise this will effectively give you a value of type 'your_type' +/// all of whose heap cells are considered unique and mutable. +fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId) -> Result { + let variant_types = vec![builder.add_tuple_type(&[])?, type_id]; + let unit = builder.add_make_tuple(block, &[])?; + let value = builder.add_make_union(block, &variant_types, 0, unit)?; + + builder.add_unwrap_union(block, value, 1) +} + fn build_entry_point( layout: crate::ir::ProcLayout, func_name: FuncName, @@ -257,7 +273,12 @@ fn build_entry_point( // to the modelling language, the arguments appear out of thin air let argument_type = build_tuple_type(&mut builder, layout.arguments)?; - let argument = builder.add_unknown_with(block, &[], argument_type)?; + + // does not make any assumptions about the input + // let argument = builder.add_unknown_with(block, &[], argument_type)?; + + // assumes the input can be updated in-place + let argument = terrible_hack(&mut builder, block, argument_type)?; let name_bytes = [0; 16]; let spec_var = CalleeSpecVar(&name_bytes); From fa57ff88a53dd544dd38d5deead045427db42151 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:16:52 +0200 Subject: [PATCH 111/136] disable list of constants in read-only section --- compiler/gen_llvm/src/llvm/build.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 9864f277a8..28e21e930a 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -2185,7 +2185,10 @@ fn list_literal<'a, 'ctx, 'env>( let list_length = elems.len(); let list_length_intval = env.ptr_int().const_int(list_length as _, false); - if element_type.is_int_type() { + // TODO re-enable, currently causes morphic segfaults because it tries to update + // constants in-place... + // if element_type.is_int_type() { + if false { let element_type = element_type.into_int_type(); let element_width = elem_layout.stack_size(env.ptr_bytes); let size = list_length * element_width as usize; @@ -2228,15 +2231,16 @@ fn list_literal<'a, 'ctx, 'env>( let val = load_symbol(scope, symbol); let intval = val.into_int_value(); - if intval.is_const() { - global_elements.push(intval); - } else { - is_all_constant = false; + // here we'd like to furthermore check for intval.is_const(). + // if all elements are const for LLVM, we could make the array a constant. + // BUT morphic does not know about this, and could allow us to modify that + // array in-place. That would cause a segfault. So, we'll have to find + // constants ourselves and cannot lean on LLVM here. + is_all_constant = false; - runtime_evaluated_elements.push((index, val)); + runtime_evaluated_elements.push((index, val)); - global_elements.push(element_type.get_undef()); - } + global_elements.push(element_type.get_undef()); } }; } From 3d7b42deba89961f74b9333b43e981768b193da4 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:17:15 +0200 Subject: [PATCH 112/136] better error message for when morphic did not specialize --- compiler/gen_llvm/src/llvm/build.rs | 33 ++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 28e21e930a..e1558897e9 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -4109,19 +4109,28 @@ pub fn build_proc<'a, 'ctx, 'env>( let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let mut it = func_solutions.specs(); - let func_spec = it.next().unwrap(); - debug_assert!( - it.next().is_none(), - "we expect only one specialization of this symbol" - ); + let evaluator = match it.next() { + Some(func_spec) => { + debug_assert!( + it.next().is_none(), + "we expect only one specialization of this symbol" + ); - let evaluator = function_value_by_func_spec( - env, - *func_spec, - symbol, - top_level.arguments, - &top_level.result, - ); + function_value_by_func_spec( + env, + *func_spec, + symbol, + top_level.arguments, + &top_level.result, + ) + } + None => { + // morphic did not generate a specialization for this function, + // therefore it must actually be unused. + // An example is our closure callers + panic!("morphic did not specialize {:?}", symbol); + } + }; let ident_string = proc.name.as_str(&env.interns); let fn_name: String = format!("{}_1", ident_string); From dbb8acbe6753c3678b1d8632f6207bef83c360ed Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:25:35 +0200 Subject: [PATCH 113/136] add in-place List.swap --- compiler/builtins/bitcode/src/list.zig | 16 +++++++++++----- compiler/gen_llvm/src/llvm/build.rs | 11 +++++++---- compiler/gen_llvm/src/llvm/build_list.rs | 12 ++++++++++++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 22e76054ea..09890b5b12 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -763,18 +763,24 @@ pub fn listSwap( element_width: usize, index_1: usize, index_2: usize, + can_update_in_place: bool, ) callconv(.C) RocList { const size = list.len(); - if (index_1 >= size or index_2 >= size) { + if (index_1 == index_2 or index_1 >= size or index_2 >= size) { // Either index out of bounds so we just return return list; } - const newList = list.makeUnique(alignment, element_width); + const newList = blk: { + if (can_update_in_place) { + break :blk list; + } else { + break :blk list.makeUnique(alignment, element_width); + } + }; - if (newList.bytes) |source_ptr| { - swapElements(source_ptr, element_width, index_1, index_2); - } + const source_ptr = @ptrCast([*]u8, newList.bytes); + swapElements(source_ptr, element_width, index_1, index_2); return newList; } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index e1558897e9..f47a6e7046 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -934,7 +934,9 @@ pub fn build_exp_call<'a, 'ctx, 'env>( CallType::LowLevel { op, update_mode } => { let bytes = update_mode.to_bytes(); let update_var = UpdateModeVar(&bytes); - let update_mode = func_spec_solutions.update_mode(update_var).ok(); + let update_mode = func_spec_solutions + .update_mode(update_var) + .unwrap_or(UpdateMode::Immutable); run_low_level( env, @@ -2229,13 +2231,13 @@ fn list_literal<'a, 'ctx, 'env>( } ListLiteralElement::Symbol(symbol) => { let val = load_symbol(scope, symbol); - let intval = val.into_int_value(); // here we'd like to furthermore check for intval.is_const(). // if all elements are const for LLVM, we could make the array a constant. // BUT morphic does not know about this, and could allow us to modify that // array in-place. That would cause a segfault. So, we'll have to find // constants ourselves and cannot lean on LLVM here. + is_all_constant = false; runtime_evaluated_elements.push((index, val)); @@ -4790,7 +4792,7 @@ fn run_low_level<'a, 'ctx, 'env>( layout: &Layout<'a>, op: LowLevel, args: &[Symbol], - update_mode: Option, + update_mode: UpdateMode, // expect_failed: *const (), ) -> BasicValueEnum<'ctx> { use LowLevel::*; @@ -4990,6 +4992,7 @@ fn run_low_level<'a, 'ctx, 'env>( index_1.into_int_value(), index_2.into_int_value(), element_layout, + update_mode, ), _ => unreachable!("Invalid layout {:?} in List.swap", list_layout), } @@ -5317,7 +5320,7 @@ fn run_low_level<'a, 'ctx, 'env>( index.into_int_value(), element, element_layout, - update_mode.unwrap(), + update_mode, ), _ => unreachable!("invalid dict layout"), } diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 2fe94d4cb5..93be451ddb 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -17,6 +17,16 @@ use morphic_lib::UpdateMode; use roc_builtins::bitcode; use roc_mono::layout::{Builtin, Layout, LayoutIds}; +fn pass_update_mode<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + update_mode: UpdateMode, +) -> BasicValueEnum<'ctx> { + match update_mode { + UpdateMode::Immutable => env.context.bool_type().const_zero().into(), + UpdateMode::InPlace => env.context.bool_type().const_int(1, false).into(), + } +} + fn list_returned_from_zig<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, output: BasicValueEnum<'ctx>, @@ -267,6 +277,7 @@ pub fn list_swap<'a, 'ctx, 'env>( index_1: IntValue<'ctx>, index_2: IntValue<'ctx>, element_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { call_bitcode_fn_returns_list( env, @@ -276,6 +287,7 @@ pub fn list_swap<'a, 'ctx, 'env>( layout_width(env, element_layout), index_1.into(), index_2.into(), + pass_update_mode(env, update_mode), ], bitcode::LIST_SWAP, ) From 9e97a09a8720307546ab6da320a0e0c9f0319e67 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:57:54 +0200 Subject: [PATCH 114/136] check if exposed function is defined already --- compiler/gen_llvm/src/llvm/build.rs | 178 ++++++++++++++++------------ 1 file changed, 102 insertions(+), 76 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index f47a6e7046..17c79cc3a3 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -5637,89 +5637,115 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( let builder = env.builder; let context = env.context; - // Here we build two functions: - // - // - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine` - // This is just a type signature that we make available to the linker, - // and can use in the wrapper - // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper` + let fastcc_function_name = format!("{}_fastcc_wrapper", foreign.as_str()); - let return_type = basic_type_from_layout(env, ret_layout); - let cc_return = to_cc_return(env, ret_layout); + let (fastcc_function, arguments) = match env.module.get_function(fastcc_function_name.as_str()) + { + Some(function_value) => { + let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); - let mut cc_argument_types = Vec::with_capacity_in(argument_symbols.len() + 1, env.arena); - let mut fastcc_argument_types = Vec::with_capacity_in(argument_symbols.len(), env.arena); - let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); + for symbol in argument_symbols { + let (value, _) = load_symbol_and_layout(scope, symbol); - for symbol in argument_symbols { - let (value, layout) = load_symbol_and_layout(scope, symbol); + arguments.push(value); + } - cc_argument_types.push(to_cc_type(env, layout)); - - let basic_type = basic_type_from_layout(env, layout); - fastcc_argument_types.push(basic_type); - - arguments.push(value); - } - - let cc_type = match cc_return { - CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false), - CCReturn::ByPointer => { - cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); - env.context.void_type().fn_type(&cc_argument_types, false) + (function_value, arguments) + } + None => { + // Here we build two functions: + // + // - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine` + // This is just a type signature that we make available to the linker, + // and can use in the wrapper + // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper` + + let return_type = basic_type_from_layout(env, ret_layout); + let cc_return = to_cc_return(env, ret_layout); + + let mut cc_argument_types = + Vec::with_capacity_in(argument_symbols.len() + 1, env.arena); + let mut fastcc_argument_types = + Vec::with_capacity_in(argument_symbols.len(), env.arena); + let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); + + for symbol in argument_symbols { + let (value, layout) = load_symbol_and_layout(scope, symbol); + + cc_argument_types.push(to_cc_type(env, layout)); + + let basic_type = basic_type_from_layout(env, layout); + fastcc_argument_types.push(basic_type); + + arguments.push(value); + } + + let cc_type = match cc_return { + CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false), + CCReturn::ByPointer => { + cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); + env.context.void_type().fn_type(&cc_argument_types, false) + } + CCReturn::Return => return_type.fn_type(&cc_argument_types, false), + }; + + let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); + + let fastcc_type = return_type.fn_type(&fastcc_argument_types, false); + + let fastcc_function = add_func( + env.module, + &fastcc_function_name, + fastcc_type, + Linkage::Private, + FAST_CALL_CONV, + ); + + let old = builder.get_insert_block().unwrap(); + + let entry = context.append_basic_block(fastcc_function, "entry"); + { + builder.position_at_end(entry); + let return_pointer = env.builder.build_alloca(return_type, "return_value"); + + let fastcc_parameters = fastcc_function.get_params(); + let mut cc_arguments = + Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); + + for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) + { + if param.get_type() == *cc_type { + cc_arguments.push(param); + } else { + let as_cc_type = + complex_bitcast(env.builder, param, *cc_type, "to_cc_type"); + cc_arguments.push(as_cc_type); + } + } + + if let CCReturn::ByPointer = cc_return { + cc_arguments.push(return_pointer.into()); + } + + let call = env.builder.build_call(cc_function, &cc_arguments, "tmp"); + call.set_call_convention(C_CALL_CONV); + + let return_value = match cc_return { + CCReturn::Return => call.try_as_basic_value().left().unwrap(), + + CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"), + CCReturn::Void => return_type.const_zero(), + }; + + builder.build_return(Some(&return_value)); + } + + builder.position_at_end(old); + + (fastcc_function, arguments) } - CCReturn::Return => return_type.fn_type(&cc_argument_types, false), }; - let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); - - let fastcc_type = return_type.fn_type(&fastcc_argument_types, false); - - let fastcc_function = add_func( - env.module, - &format!("{}_fastcc_wrapper", foreign.as_str()), - fastcc_type, - Linkage::Private, - FAST_CALL_CONV, - ); - - let old = builder.get_insert_block().unwrap(); - - let entry = context.append_basic_block(fastcc_function, "entry"); - { - builder.position_at_end(entry); - let return_pointer = env.builder.build_alloca(return_type, "return_value"); - - let fastcc_parameters = fastcc_function.get_params(); - let mut cc_arguments = Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); - - for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) { - if param.get_type() == *cc_type { - cc_arguments.push(param); - } else { - let as_cc_type = complex_bitcast(env.builder, param, *cc_type, "to_cc_type"); - cc_arguments.push(as_cc_type); - } - } - - if let CCReturn::ByPointer = cc_return { - cc_arguments.push(return_pointer.into()); - } - - let call = env.builder.build_call(cc_function, &cc_arguments, "tmp"); - call.set_call_convention(C_CALL_CONV); - - let return_value = match cc_return { - CCReturn::Return => call.try_as_basic_value().left().unwrap(), - - CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"), - CCReturn::Void => return_type.const_zero(), - }; - - builder.build_return(Some(&return_value)); - } - - builder.position_at_end(old); let call = env.builder.build_call(fastcc_function, &arguments, "tmp"); call.set_call_convention(FAST_CALL_CONV); return call.try_as_basic_value().left().unwrap(); From 018348bd830dafe93c1172182b7a11129d8600ec Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 12:13:17 +0200 Subject: [PATCH 115/136] make Str.fromUtf8 in-place --- compiler/builtins/bitcode/src/list.zig | 13 ++++++++-- compiler/builtins/bitcode/src/str.zig | 30 ++++++++++++++++++------ compiler/builtins/bitcode/src/utils.zig | 5 ++++ compiler/gen_llvm/src/llvm/build.rs | 2 +- compiler/gen_llvm/src/llvm/build_list.rs | 6 ++--- compiler/gen_llvm/src/llvm/build_str.rs | 7 +++++- 6 files changed, 49 insertions(+), 14 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 09890b5b12..d5224eac3c 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -1,6 +1,7 @@ const std = @import("std"); const utils = @import("utils.zig"); const RocResult = utils.RocResult; +const UpdateMode = utils.UpdateMode; const mem = std.mem; const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; @@ -52,6 +53,14 @@ pub const RocList = extern struct { }; } + pub fn makeUniqueExtra(self: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) RocList { + if (update_mode == .InPlace) { + return self; + } else { + return self.makeUnique(alignment, element_width); + } + } + pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList { if (self.isEmpty()) { return self; @@ -763,7 +772,7 @@ pub fn listSwap( element_width: usize, index_1: usize, index_2: usize, - can_update_in_place: bool, + update_mode: update_mode, ) callconv(.C) RocList { const size = list.len(); if (index_1 == index_2 or index_1 >= size or index_2 >= size) { @@ -772,7 +781,7 @@ pub fn listSwap( } const newList = blk: { - if (can_update_in_place) { + if (update_mode == .InPlace) { break :blk list; } else { break :blk list.makeUnique(alignment, element_width); diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index ac2b3f6399..d04bf0086b 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -1,5 +1,6 @@ const utils = @import("utils.zig"); const RocList = @import("list.zig").RocList; +const UpdateMode = utils.UpdateMode; const std = @import("std"); const mem = std.mem; const always_inline = std.builtin.CallOptions.Modifier.always_inline; @@ -1177,11 +1178,11 @@ const CountAndStart = extern struct { start: usize, }; -pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void { - output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{arg}); +pub fn fromUtf8C(arg: RocList, update_mode: UpdateMode, output: *FromUtf8Result) callconv(.C) void { + output.* = fromUtf8(arg, update_mode); } -fn fromUtf8(arg: RocList) FromUtf8Result { +inline fn fromUtf8(arg: RocList, update_mode: UpdateMode) FromUtf8Result { const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length]; if (unicode.utf8ValidateSlice(bytes)) { @@ -1194,13 +1195,23 @@ fn fromUtf8(arg: RocList) FromUtf8Result { const data_bytes = arg.len(); utils.decref(arg.bytes, data_bytes, RocStr.alignment); - return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; + return FromUtf8Result{ + .is_ok = true, + .string = string, + .byte_index = 0, + .problem_code = Utf8ByteProblem.InvalidStartByte, + }; } else { - const byte_list = arg.makeUnique(RocStr.alignment, @sizeOf(u8)); + const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode); const string = RocStr{ .str_bytes = byte_list.bytes, .str_len = byte_list.length }; - return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; + return FromUtf8Result{ + .is_ok = true, + .string = string, + .byte_index = 0, + .problem_code = Utf8ByteProblem.InvalidStartByte, + }; } } else { const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length); @@ -1209,7 +1220,12 @@ fn fromUtf8(arg: RocList) FromUtf8Result { const data_bytes = arg.len(); utils.decref(arg.bytes, data_bytes, RocStr.alignment); - return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem }; + return FromUtf8Result{ + .is_ok = false, + .string = RocStr.empty(), + .byte_index = temp.index, + .problem_code = temp.problem, + }; } } diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 67240bdb2d..1371afe674 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -256,3 +256,8 @@ pub const Ordering = enum(u8) { GT = 1, LT = 2, }; + +pub const UpdateMode = extern enum(u8) { + Immutable = 0, + InPlace = 1, +}; diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 17c79cc3a3..722f0b4376 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -4848,7 +4848,7 @@ fn run_low_level<'a, 'ctx, 'env>( let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); - str_from_utf8(env, parent, original_wrapper) + str_from_utf8(env, parent, original_wrapper, update_mode) } StrFromUtf8Range => { debug_assert_eq!(args.len(), 2); diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 93be451ddb..f159759153 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -17,13 +17,13 @@ use morphic_lib::UpdateMode; use roc_builtins::bitcode; use roc_mono::layout::{Builtin, Layout, LayoutIds}; -fn pass_update_mode<'a, 'ctx, 'env>( +pub fn pass_update_mode<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { match update_mode { - UpdateMode::Immutable => env.context.bool_type().const_zero().into(), - UpdateMode::InPlace => env.context.bool_type().const_int(1, false).into(), + UpdateMode::Immutable => env.context.i8_type().const_zero().into(), + UpdateMode::InPlace => env.context.i8_type().const_int(1, false).into(), } } diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 02098150b7..564f35625e 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -1,9 +1,12 @@ use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn}; use crate::llvm::build::{complex_bitcast, Env, Scope}; -use crate::llvm::build_list::{allocate_list, call_bitcode_fn_returns_list, store_list}; +use crate::llvm::build_list::{ + allocate_list, call_bitcode_fn_returns_list, pass_update_mode, store_list, +}; use inkwell::builder::Builder; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::AddressSpace; +use morphic_lib::UpdateMode; use roc_builtins::bitcode; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; @@ -350,6 +353,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, _parent: FunctionValue<'ctx>, original_wrapper: StructValue<'ctx>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { let builder = env.builder; @@ -365,6 +369,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( env.str_list_c_abi().into(), "to_i128", ), + pass_update_mode(env, update_mode), result_ptr.into(), ], bitcode::STR_FROM_UTF8, From 486f1d540fa62928661e6aa68b92caa13c6693ef Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 13:22:03 +0200 Subject: [PATCH 116/136] add specifications for fromUtf8 and toUtf8 --- compiler/builtins/bitcode/src/list.zig | 2 +- compiler/builtins/bitcode/src/str.zig | 4 ++-- compiler/mono/src/alias_analysis.rs | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index d5224eac3c..6bfc5b70de 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -772,7 +772,7 @@ pub fn listSwap( element_width: usize, index_1: usize, index_2: usize, - update_mode: update_mode, + update_mode: UpdateMode, ) callconv(.C) RocList { const size = list.len(); if (index_1 == index_2 or index_1 >= size or index_2 >= size) { diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index d04bf0086b..5506d4b654 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -1148,10 +1148,10 @@ test "RocStr.joinWith: result is big" { // Str.toUtf8 pub fn strToUtf8C(arg: RocStr) callconv(.C) RocList { - return @call(.{ .modifier = always_inline }, strToBytes, .{arg}); + return strToBytes(arg); } -fn strToBytes(arg: RocStr) RocList { +inline fn strToBytes(arg: RocStr) RocList { if (arg.isEmpty()) { return RocList.empty(); } else if (arg.isSmallStr()) { diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 96d6ddb292..f0dc294a95 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -933,6 +933,27 @@ fn lowlevel_spec( let new_cell = builder.add_new_heap_cell(block)?; builder.add_make_tuple(block, &[new_cell, bag]) } + StrToUtf8 => { + let string = env.symbols[&arguments[0]]; + + let u8_type = builder.add_tuple_type(&[])?; + let bag = builder.add_empty_bag(block, u8_type)?; + let cell = builder.add_get_tuple_field(block, string, LIST_CELL_INDEX)?; + + builder.add_make_tuple(block, &[cell, bag]) + } + StrFromUtf8 => { + let list = env.symbols[&arguments[0]]; + + let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + let string = builder.add_make_tuple(block, &[cell])?; + + let byte_index = builder.add_make_tuple(block, &[])?; + let is_ok = builder.add_make_tuple(block, &[])?; + let problem_code = builder.add_make_tuple(block, &[])?; + + builder.add_make_tuple(block, &[byte_index, string, is_ok, problem_code]) + } DictEmpty => { match layout { Layout::Builtin(Builtin::EmptyDict) => { From 13d480d5f37d3ba529b778e747fa75945c312edc Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 13:27:47 +0200 Subject: [PATCH 117/136] spec for list append --- compiler/builtins/bitcode/src/list.zig | 5 ++++- compiler/gen_llvm/src/llvm/build.rs | 2 +- compiler/gen_llvm/src/llvm/build_list.rs | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 6bfc5b70de..d83f888e22 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -729,10 +729,13 @@ pub fn listSingle(alignment: u32, element: Opaque, element_width: usize) callcon return output; } -pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList { +pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList { const old_length = list.len(); var output = list.reallocate(alignment, old_length + 1, element_width); + // we'd need capacity to use update_mode here + _ = update_mode; + if (output.bytes) |target| { if (element) |source| { @memcpy(target + old_length * element_width, source, element_width); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 722f0b4376..4720653867 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -4972,7 +4972,7 @@ fn run_low_level<'a, 'ctx, 'env>( let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); let (elem, elem_layout) = load_symbol_and_layout(scope, &args[1]); - list_append(env, original_wrapper, elem, elem_layout) + list_append(env, original_wrapper, elem, elem_layout, update_mode) } ListSwap => { // List.swap : List elem, Nat, Nat -> List elem diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index f159759153..5c42d24c52 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -238,6 +238,7 @@ pub fn list_append<'a, 'ctx, 'env>( original_wrapper: StructValue<'ctx>, element: BasicValueEnum<'ctx>, element_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { call_bitcode_fn_returns_list( env, @@ -246,6 +247,7 @@ pub fn list_append<'a, 'ctx, 'env>( env.alignment_intvalue(element_layout), pass_element_as_opaque(env, element), layout_width(env, element_layout), + pass_update_mode(env, update_mode), ], bitcode::LIST_APPEND, ) From 06906331163a872093e78427ed9c81509c47bf75 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 13:34:09 +0200 Subject: [PATCH 118/136] spec for List.reverse --- compiler/builtins/bitcode/src/list.zig | 4 ++-- compiler/gen_llvm/src/llvm/build.rs | 2 +- compiler/gen_llvm/src/llvm/build_list.rs | 2 ++ compiler/mono/src/alias_analysis.rs | 11 +++++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index d83f888e22..6e6351a6e7 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -141,14 +141,14 @@ const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; -pub fn listReverse(list: RocList, alignment: u32, element_width: usize) callconv(.C) RocList { +pub fn listReverse(list: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList { if (list.bytes) |source_ptr| { const size = list.len(); var i: usize = 0; const end: usize = size - 1; - if (list.isUnique()) { + if (update_mode == .InPlace or list.isUnique()) { // Working from the front and back so // we only need to go ~(n / 2) iterations. diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 4720653867..0694299329 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -4930,7 +4930,7 @@ fn run_low_level<'a, 'ctx, 'env>( let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - list_reverse(env, list, list_layout) + list_reverse(env, list, list_layout, update_mode) } ListConcat => { debug_assert_eq!(args.len(), 2); diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 5c42d24c52..d46d454458 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -172,6 +172,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { let element_layout = match *list_layout { Layout::Builtin(Builtin::EmptyList) => { @@ -190,6 +191,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( pass_list_cc(env, list), env.alignment_intvalue(&element_layout), layout_width(env, &element_layout), + pass_update_mode(env, update_mode), ], bitcode::LIST_REVERSE, ) diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index f0dc294a95..b218875425 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -918,6 +918,17 @@ fn lowlevel_spec( let new_cell = builder.add_new_heap_cell(block)?; builder.add_make_tuple(block, &[new_cell, bag]) } + ListReverse => { + let list = env.symbols[&arguments[0]]; + + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let _unit = builder.add_update(block, update_mode_var, cell)?; + + let new_cell = builder.add_new_heap_cell(block)?; + builder.add_make_tuple(block, &[new_cell, bag]) + } ListAppend => { let list = env.symbols[&arguments[0]]; let to_insert = env.symbols[&arguments[1]]; From 313bc71f48623f148ec9e636af0401a1ebe816ab Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 14:06:40 +0200 Subject: [PATCH 119/136] disable RBTreeDel test/benchmark --- cli/benches/time_bench.rs | 2 +- cli/cli_utils/src/bench_utils.rs | 1 + cli/tests/cli_run.rs | 142 ++++++++++++------------ nightly_benches/benches/events_bench.rs | 4 +- 4 files changed, 75 insertions(+), 74 deletions(-) diff --git a/cli/benches/time_bench.rs b/cli/benches/time_bench.rs index 56395acc75..ffbcabfb66 100644 --- a/cli/benches/time_bench.rs +++ b/cli/benches/time_bench.rs @@ -29,7 +29,7 @@ fn bench_group_wall_time(c: &mut Criterion) { bench_cfold, // e = mkExpr 17 1 bench_deriv, // nest deriv 8 f bench_rbtree_ck, // ms = makeMap 5 80000 - bench_rbtree_delete, // m = makeMap 100000 + // bench_rbtree_delete, // m = makeMap 100000 bench_quicksort, // list size 10000 ]; diff --git a/cli/cli_utils/src/bench_utils.rs b/cli/cli_utils/src/bench_utils.rs index 146f500380..b01ec495fd 100644 --- a/cli/cli_utils/src/bench_utils.rs +++ b/cli/cli_utils/src/bench_utils.rs @@ -131,6 +131,7 @@ pub fn bench_rbtree_ck(bench_group_opt: Option<&mut BenchmarkGro ); } +#[allow(dead_code)] pub fn bench_rbtree_delete(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( &example_file("benchmarks", "RBTreeDel.roc"), diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index aa7020700f..79a64047ea 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -435,77 +435,77 @@ mod cli_run { } benchmarks! { - nqueens => Example { - filename: "NQueens.roc", - executable_filename: "nqueens", - stdin: &["6"], - expected_ending: "4\n", - use_valgrind: true, - }, - cfold => Example { - filename: "CFold.roc", - executable_filename: "cfold", - stdin: &["3"], - expected_ending: "11 & 11\n", - use_valgrind: true, - }, - deriv => Example { - filename: "Deriv.roc", - executable_filename: "deriv", - stdin: &["2"], - expected_ending: "1 count: 6\n2 count: 22\n", - use_valgrind: true, - }, - rbtree_ck => Example { - filename: "RBTreeCk.roc", - executable_filename: "rbtree-ck", - stdin: &["100"], - expected_ending: "10\n", - use_valgrind: true, - }, - rbtree_insert => Example { - filename: "RBTreeInsert.roc", - executable_filename: "rbtree-insert", - stdin: &[], - expected_ending: "Node Black 0 {} Empty Empty\n", - use_valgrind: true, - }, - rbtree_del => Example { - filename: "RBTreeDel.roc", - executable_filename: "rbtree-del", - stdin: &["420"], - expected_ending: "30\n", - use_valgrind: true, - }, - astar => Example { - filename: "TestAStar.roc", - executable_filename: "test-astar", - stdin: &[], - expected_ending: "True\n", - use_valgrind: false, - }, - base64 => Example { - filename: "TestBase64.roc", - executable_filename: "test-base64", - stdin: &[], - expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", - use_valgrind: true, - }, - closure => Example { - filename: "Closure.roc", - executable_filename: "closure", - stdin: &[], - expected_ending: "", - use_valgrind: true, - }, - quicksort_app => Example { - filename: "QuicksortApp.roc", - executable_filename: "quicksortapp", - stdin: &[], - expected_ending: "todo put the correct quicksort answer here", - use_valgrind: true, - }, - } + nqueens => Example { + filename: "NQueens.roc", + executable_filename: "nqueens", + stdin: &["6"], + expected_ending: "4\n", + use_valgrind: true, + }, + cfold => Example { + filename: "CFold.roc", + executable_filename: "cfold", + stdin: &["3"], + expected_ending: "11 & 11\n", + use_valgrind: true, + }, + deriv => Example { + filename: "Deriv.roc", + executable_filename: "deriv", + stdin: &["2"], + expected_ending: "1 count: 6\n2 count: 22\n", + use_valgrind: true, + }, + rbtree_ck => Example { + filename: "RBTreeCk.roc", + executable_filename: "rbtree-ck", + stdin: &["100"], + expected_ending: "10\n", + use_valgrind: true, + }, + rbtree_insert => Example { + filename: "RBTreeInsert.roc", + executable_filename: "rbtree-insert", + stdin: &[], + expected_ending: "Node Black 0 {} Empty Empty\n", + use_valgrind: true, + }, + // rbtree_del => Example { + // filename: "RBTreeDel.roc", + // executable_filename: "rbtree-del", + // stdin: &["420"], + // expected_ending: "30\n", + // use_valgrind: true, + // }, + astar => Example { + filename: "TestAStar.roc", + executable_filename: "test-astar", + stdin: &[], + expected_ending: "True\n", + use_valgrind: false, + }, + base64 => Example { + filename: "TestBase64.roc", + executable_filename: "test-base64", + stdin: &[], + expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", + use_valgrind: true, + }, + closure => Example { + filename: "Closure.roc", + executable_filename: "closure", + stdin: &[], + expected_ending: "", + use_valgrind: true, + }, + quicksort_app => Example { + filename: "QuicksortApp.roc", + executable_filename: "quicksortapp", + stdin: &[], + expected_ending: "todo put the correct quicksort answer here", + use_valgrind: true, + }, + } #[cfg(not(debug_assertions))] fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) { diff --git a/nightly_benches/benches/events_bench.rs b/nightly_benches/benches/events_bench.rs index c722eeae7c..be889672f2 100644 --- a/nightly_benches/benches/events_bench.rs +++ b/nightly_benches/benches/events_bench.rs @@ -1,6 +1,6 @@ // Keep this benchmark. It's commented because it requires nightly rust. use cli_utils::bench_utils::{ - bench_cfold, bench_deriv, bench_nqueens, bench_rbtree_ck, bench_rbtree_delete, bench_quicksort + bench_cfold, bench_deriv, bench_nqueens, bench_quicksort, bench_rbtree_ck, bench_rbtree_delete, }; use criterion_perf_events::Perf; use perfcnt::linux::HardwareEventType as Hardware; @@ -18,7 +18,7 @@ fn bench_group(c: &mut Criterion, hw_event_str: &str) { bench_cfold, bench_deriv, bench_rbtree_ck, - bench_rbtree_delete, + // bench_rbtree_delete, bench_quicksort, ]; From f8ac85195b7178c4dde21760d4e4acbcb2bee65e Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 14:18:48 +0200 Subject: [PATCH 120/136] fix zig test --- compiler/builtins/bitcode/src/str.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 5506d4b654..9ffb9a1375 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -1308,11 +1308,11 @@ pub const Utf8ByteProblem = enum(u8) { }; fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result { - return fromUtf8(RocList{ .bytes = bytes, .length = length }); + return fromUtf8(RocList{ .bytes = bytes, .length = length }, .Immutable); } fn validateUtf8BytesX(str: RocList) FromUtf8Result { - return fromUtf8(str); + return fromUtf8(str, .Immutable); } fn expectOk(result: FromUtf8Result) !void { From 13d64c48bdcad566446513b49d03818d1fe8f11b Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 3 Oct 2021 17:17:23 +0200 Subject: [PATCH 121/136] fmt --- cli/benches/time_bench.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/benches/time_bench.rs b/cli/benches/time_bench.rs index ffbcabfb66..f4c8f6e8be 100644 --- a/cli/benches/time_bench.rs +++ b/cli/benches/time_bench.rs @@ -25,12 +25,12 @@ fn bench_group_wall_time(c: &mut Criterion) { group.sample_size(nr_of_runs); let bench_funcs: Vec>) -> ()> = vec![ - bench_nqueens, // queens 11 - bench_cfold, // e = mkExpr 17 1 - bench_deriv, // nest deriv 8 f - bench_rbtree_ck, // ms = makeMap 5 80000 + bench_nqueens, // queens 11 + bench_cfold, // e = mkExpr 17 1 + bench_deriv, // nest deriv 8 f + bench_rbtree_ck, // ms = makeMap 5 80000 // bench_rbtree_delete, // m = makeMap 100000 - bench_quicksort, // list size 10000 + bench_quicksort, // list size 10000 ]; for bench_func in bench_funcs.iter() { From f4ea4e4ad7217f0a4fc06d35f6ba75eb476c558a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 3 Oct 2021 17:26:11 +0200 Subject: [PATCH 122/136] just want to run tests --- Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Earthfile b/Earthfile index c6b754ce64..fd77785c8a 100644 --- a/Earthfile +++ b/Earthfile @@ -101,7 +101,7 @@ test-all: BUILD +test-rust BUILD +verify-no-git-changes -# compile everything needed for benchmarks and output a self-contained folder +# compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run. prep-bench-folder: FROM +copy-dirs ARG BENCH_SUFFIX=branch From 09917b1f01a019e878f77f8ea1bc1a5327f95cfe Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 3 Oct 2021 18:34:04 +0200 Subject: [PATCH 123/136] ignore missing RBTreeDel test --- cli/tests/cli_run.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 79a64047ea..925af2335d 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -562,10 +562,10 @@ mod cli_run { file.read_exact(buf).unwrap(); // Only app modules in this directory are considered benchmarks. - if "app".as_bytes() == buf { + if "app".as_bytes() == buf && !benchmark_file_name.contains("RBTreeDel") { all_benchmarks.remove(benchmark_file_name.as_str()).unwrap_or_else(|| { - panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name); - }); + panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name); + }); } } } From 3baff93a975d1e40e361ad849892d1290e869622 Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sat, 2 Oct 2021 20:03:07 -0500 Subject: [PATCH 124/136] add first version of List.dropAt * adds an implementation with no uniqueness/mutability --- compiler/builtins/bitcode/src/list.zig | 48 ++++++++++++++++++++++++ compiler/builtins/bitcode/src/main.zig | 1 + compiler/builtins/docs/List.roc | 7 +++- compiler/builtins/src/bitcode.rs | 1 + compiler/builtins/src/std.rs | 7 ++++ compiler/can/src/builtins.rs | 24 ++++++++++++ compiler/gen_llvm/src/llvm/build.rs | 28 ++++++++++++-- compiler/gen_llvm/src/llvm/build_list.rs | 25 +++++++++++- compiler/module/src/low_level.rs | 15 ++++---- compiler/module/src/symbol.rs | 1 + compiler/mono/src/borrow.rs | 1 + 11 files changed, 145 insertions(+), 13 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 6e6351a6e7..2224ac53cb 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -833,6 +833,54 @@ pub fn listDrop( } } +// GIESCH add type inference test +// GIESCH add unit tests +// GIESCH do a uniqueness check, and reuse the same array if possible +// GIESCH figure out where to specify uniqueness of output, update builtins readme +pub fn listDropAt( + list: RocList, + alignment: u32, + element_width: usize, + drop_index: usize, + dec: Dec, +) callconv(.C) RocList { + if (list.bytes) |source_ptr| { + const size = list.len(); + + if (drop_index >= size) { + return RocList.empty(); + } + + if (drop_index < size) { + const element = source_ptr + drop_index * element_width; + dec(element); + } + + // GIESCH is this necessary? + if (size < 2 and drop_index == 0) { + return RocList.empty(); + } + + const output = RocList.allocate(alignment, (size - 1), element_width); + const target_ptr = output.bytes orelse unreachable; + + const head_size = drop_index * element_width; + @memcpy(target_ptr, source_ptr, head_size); + + const tail_target = target_ptr + drop_index * element_width; + const tail_source = source_ptr + (drop_index + 1) * element_width; + const tail_size = (size - drop_index - 1) * element_width; + @memcpy(tail_target, tail_source, tail_size); + + // GIESCH what's the difference between this and Dec? + utils.decref(list.bytes, size * element_width, alignment); + + return output; + } else { + return RocList.empty(); + } +} + pub fn listRange(width: utils.IntWidth, low: Opaque, high: Opaque) callconv(.C) RocList { return switch (width) { .U8 => helper1(u8, low, high), diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 0841a8e8f5..d82b8542a8 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -39,6 +39,7 @@ comptime { exportListFn(list.listSortWith, "sort_with"); exportListFn(list.listConcat, "concat"); exportListFn(list.listDrop, "drop"); + exportListFn(list.listDropAt, "drop_at"); exportListFn(list.listSet, "set"); exportListFn(list.listSetInPlace, "set_in_place"); exportListFn(list.listSwap, "swap"); diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index f8f9a2df43..3be184ff5b 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -422,15 +422,18 @@ min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* ## If the given index is outside the bounds of the list, returns the original ## list unmodified. ## -## To drop the element at a given index, instead of replacing it, see [List.drop]. +## To drop the element at a given index, instead of replacing it, see [List.dropAt]. set : List elem, Nat, elem -> List elem +# GIESCH figure out if we should add docs for List.drop; +# what's the relationship with List.dropFirst, below? +# GIESCH add docs re: uniqueness and performance ## Drops the element at the given index from the list. ## ## This has no effect if the given index is outside the bounds of the list. ## ## To replace the element at a given index, instead of dropping it, see [List.set]. -drop : List elem, Nat -> List elem +dropAt : List elem, Nat -> List elem ## Adds a new element to the end of the list. ## diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index fd1ac7b17c..e451a1e68d 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -63,6 +63,7 @@ pub const LIST_REPEAT: &str = "roc_builtins.list.repeat"; pub const LIST_APPEND: &str = "roc_builtins.list.append"; pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_DROP: &str = "roc_builtins.list.drop"; +pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at"; pub const LIST_SWAP: &str = "roc_builtins.list.swap"; pub const LIST_SINGLE: &str = "roc_builtins.list.single"; pub const LIST_JOIN: &str = "roc_builtins.list.join"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 7615879227..0374e228e2 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -920,6 +920,13 @@ pub fn types() -> MutMap { Box::new(list_type(flex(TVAR1))), ); + // dropAt : List elem, Nat -> List elem + add_top_level_function_type!( + Symbol::LIST_DROP_AT, + vec![list_type(flex(TVAR1)), nat_type()], + Box::new(list_type(flex(TVAR1))), + ); + // swap : List elem, Nat, Nat -> List elem add_top_level_function_type!( Symbol::LIST_SWAP, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index d4fff2ef74..ea0bde537a 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -87,6 +87,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_MAP2 => list_map2, LIST_MAP3 => list_map3, LIST_DROP => list_drop, + LIST_DROP_AT => list_drop_at, LIST_SWAP => list_swap, LIST_MAP_WITH_INDEX => list_map_with_index, LIST_KEEP_IF => list_keep_if, @@ -1979,6 +1980,29 @@ fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// List.dropAt : List elem, Nat -> List elem +fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let index_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListDropAt, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (index_var, Var(Symbol::ARG_2)), + ], + ret_var: list_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1), (index_var, Symbol::ARG_2)], + var_store, + body, + list_var, + ) +} + /// List.append : List elem, elem -> List elem fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 0694299329..505c2b924f 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -9,9 +9,10 @@ use crate::llvm::build_dict::{ use crate::llvm::build_hash::generic_hash; use crate::llvm::build_list::{ self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, - list_contains, list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if, - list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, list_prepend, - list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, list_swap, + list_contains, list_drop, list_drop_at, list_get_unsafe, list_join, list_keep_errs, + list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, + list_prepend, list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, + list_swap, }; use crate::llvm::build_str::{ empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, @@ -5018,6 +5019,27 @@ fn run_low_level<'a, 'ctx, 'env>( _ => unreachable!("Invalid layout {:?} in List.drop", list_layout), } } + ListDropAt => { + // List.dropAt : List elem, Nat -> List elem + debug_assert_eq!(args.len(), 2); + + let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); + let original_wrapper = list.into_struct_value(); + + let count = load_symbol(scope, &args[1]); + + match list_layout { + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::List(element_layout)) => list_drop_at( + env, + layout_ids, + original_wrapper, + count.into_int_value(), + element_layout, + ), + _ => unreachable!("Invalid layout {:?} in List.dropAt", list_layout), + } + } ListPrepend => { // List.prepend : List elem, elem -> List elem debug_assert_eq!(args.len(), 2); diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index d46d454458..2428dd6000 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -297,7 +297,7 @@ pub fn list_swap<'a, 'ctx, 'env>( ) } -/// List.drop : List elem, Nat, Nat -> List elem +/// List.drop : List elem, Nat -> List elem pub fn list_drop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -319,6 +319,29 @@ pub fn list_drop<'a, 'ctx, 'env>( ) } +// GIESCH ask about how this calling/linking to compiled zig works +/// List.dropAt : List elem, Nat -> List elem +pub fn list_drop_at<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + original_wrapper: StructValue<'ctx>, + count: IntValue<'ctx>, + element_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout); + call_bitcode_fn_returns_list( + env, + &[ + pass_list_cc(env, original_wrapper.into()), + env.alignment_intvalue(element_layout), + layout_width(env, element_layout), + count.into(), + dec_element_fn.as_global_value().as_pointer_value().into(), + ], + bitcode::LIST_DROP_AT, + ) +} + /// List.set : List elem, Nat, elem -> List elem pub fn list_set<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 7dec1f4ccf..8753d21af0 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -41,6 +41,7 @@ pub enum LowLevel { ListKeepErrs, ListSortWith, ListDrop, + ListDropAt, ListSwap, DictSize, DictEmpty, @@ -116,13 +117,13 @@ impl LowLevel { StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe - | ListSet | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat - | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap - | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe - | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference - | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap - | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt - | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf + | ListSet | ListDrop | ListDropAt | ListSingle | ListRepeat | ListReverse + | ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange + | ListSwap | DictSize | DictEmpty | DictInsert | DictRemove | DictContains + | DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection + | DictDifference | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub + | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte + | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 6e825a9953..8591e27bf2 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -964,6 +964,7 @@ define_builtins! { 31 LIST_SORT_WITH: "sortWith" 32 LIST_DROP: "drop" 33 LIST_SWAP: "swap" + 34 LIST_DROP_AT: "dropAt" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 45f5eb9c0d..e6221e798b 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -993,6 +993,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { // List.append should own its first argument ListAppend => arena.alloc_slice_copy(&[owned, owned]), ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]), + ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]), ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), From 700ab20a8c3cf66e4eb3b3f255b47ea732634f70 Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sat, 2 Oct 2021 21:44:35 -0500 Subject: [PATCH 125/136] add tests for List.dropAt --- compiler/builtins/bitcode/src/list.zig | 10 ++-------- compiler/solve/tests/solve_expr.rs | 12 ++++++++++++ compiler/test_gen/src/gen_list.rs | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 2224ac53cb..0a7c725e92 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -833,8 +833,6 @@ pub fn listDrop( } } -// GIESCH add type inference test -// GIESCH add unit tests // GIESCH do a uniqueness check, and reuse the same array if possible // GIESCH figure out where to specify uniqueness of output, update builtins readme pub fn listDropAt( @@ -848,7 +846,8 @@ pub fn listDropAt( const size = list.len(); if (drop_index >= size) { - return RocList.empty(); + // GIESCH should this still copy/reallocate if non-unique? + return list; } if (drop_index < size) { @@ -856,11 +855,6 @@ pub fn listDropAt( dec(element); } - // GIESCH is this necessary? - if (size < 2 and drop_index == 0) { - return RocList.empty(); - } - const output = RocList.allocate(alignment, (size - 1), element_width); const target_ptr = output.bytes orelse unreachable; diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 4f93fe449e..e43ac44ebc 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3709,6 +3709,18 @@ mod solve_expr { ); } + #[test] + fn list_drop_at() { + infer_eq_without_problem( + indoc!( + r#" + List.dropAt + "# + ), + "List a, Nat -> List a", + ); + } + #[test] fn function_that_captures_nothing_is_not_captured() { // we should make sure that a function that doesn't capture anything it not itself captured diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 758603f2c1..ba9bbc1561 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -198,6 +198,21 @@ fn list_drop() { assert_evals_to!("List.drop [1,2] 5", RocList::from_slice(&[]), RocList); } +#[test] +fn list_drop_at() { + assert_evals_to!( + "List.dropAt [1, 2, 3] 0", + RocList::from_slice(&[2, 3]), + RocList + ); + assert_evals_to!( + "List.dropAt [0, 0, 0] 3", + RocList::from_slice(&[1, 2, 3]), + RocList + ); + assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList); +} + #[test] fn list_swap() { assert_evals_to!("List.swap [] 0 1", RocList::from_slice(&[]), RocList); From 65821d6a9f67262185dad6668ce6dfc640191c3d Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 09:59:32 -0500 Subject: [PATCH 126/136] remove giesch/todo tags, add List.drop doc --- compiler/builtins/bitcode/src/list.zig | 4 ---- compiler/builtins/docs/List.roc | 7 ++++--- compiler/gen_llvm/src/llvm/build_list.rs | 1 - 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 0a7c725e92..e6f0ca8c5d 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -833,8 +833,6 @@ pub fn listDrop( } } -// GIESCH do a uniqueness check, and reuse the same array if possible -// GIESCH figure out where to specify uniqueness of output, update builtins readme pub fn listDropAt( list: RocList, alignment: u32, @@ -846,7 +844,6 @@ pub fn listDropAt( const size = list.len(); if (drop_index >= size) { - // GIESCH should this still copy/reallocate if non-unique? return list; } @@ -866,7 +863,6 @@ pub fn listDropAt( const tail_size = (size - drop_index - 1) * element_width; @memcpy(tail_target, tail_source, tail_size); - // GIESCH what's the difference between this and Dec? utils.decref(list.bytes, size * element_width, alignment); return output; diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index 3be184ff5b..964affa15b 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -31,6 +31,7 @@ interface List range, sortWith, drop, + dropAt, swap ] imports [] @@ -425,9 +426,9 @@ min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* ## To drop the element at a given index, instead of replacing it, see [List.dropAt]. set : List elem, Nat, elem -> List elem -# GIESCH figure out if we should add docs for List.drop; -# what's the relationship with List.dropFirst, below? -# GIESCH add docs re: uniqueness and performance +## Drops n elements from the beginning of the list. +drop : List elem, Nat -> List elem + ## Drops the element at the given index from the list. ## ## This has no effect if the given index is outside the bounds of the list. diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 2428dd6000..ddc6cd645b 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -319,7 +319,6 @@ pub fn list_drop<'a, 'ctx, 'env>( ) } -// GIESCH ask about how this calling/linking to compiled zig works /// List.dropAt : List elem, Nat -> List elem pub fn list_drop_at<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, From d1ee9c90b2885b1533401bd44a72834c65a6b89a Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 11:07:11 -0500 Subject: [PATCH 127/136] fix copy/paste error in test --- compiler/test_gen/src/gen_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index ba9bbc1561..ce99435505 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -207,7 +207,7 @@ fn list_drop_at() { ); assert_evals_to!( "List.dropAt [0, 0, 0] 3", - RocList::from_slice(&[1, 2, 3]), + RocList::from_slice(&[0, 0, 0]), RocList ); assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList); From 2a724391175a06455b7e479a3c0977982c7365c1 Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 13:54:47 -0500 Subject: [PATCH 128/136] WIP adding unique mutable case --- compiler/builtins/bitcode/src/list.zig | 28 +++++++++++++++++++++++++- compiler/test_gen/src/gen_list.rs | 19 +++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index e6f0ca8c5d..395fbcbc45 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -852,7 +852,33 @@ pub fn listDropAt( dec(element); } - const output = RocList.allocate(alignment, (size - 1), element_width); + // NOTE + // we need to return an empty list explicitly, + // because we rely on the pointer field being null if the list is empty + // which also requires duplicating the utils.decref call to spend the RC token + if (size < 2) { + utils.decref(list.bytes, size * element_width, alignment); + return RocList.empty(); + } + + if (list.isUnique()) { + var i = drop_index; + while (i < size) : (i += 1) { + const copy_target = source_ptr + i * element_width; + const copy_source = copy_target + element_width; + @memcpy(copy_target, copy_source, element_width); + } + + var new_list = list; + + new_list.length -= 1; + return new_list; + } + + const stdout = std.io.getStdOut().writer(); + stdout.print("Hit non-unique branch with list, {any}!\n", .{list}) catch unreachable; + + const output = RocList.allocate(alignment, size - 1, element_width); const target_ptr = output.bytes orelse unreachable; const head_size = drop_index * element_width; diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index ce99435505..bb3fb4dda4 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -211,6 +211,25 @@ fn list_drop_at() { RocList ); assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList); + assert_evals_to!("List.dropAt [0] 0", RocList::from_slice(&[]), RocList); + + assert_evals_to!( + indoc!( + r#" + list : List I64 + list = [ 1, 2, 3 ] + + { newList: List.dropAt list 0, original: list } + "# + ), + ( + // new_list + RocList::from_slice(&[2, 3]), + // original + RocList::from_slice(&[1, 2, 3]), + ), + (RocList, RocList,) + ); } #[test] From 25215cb3e39c0468e0dd7e087137f9265e7f59de Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 14:09:07 -0500 Subject: [PATCH 129/136] cleanup mutable case, split test --- compiler/builtins/bitcode/src/list.zig | 3 --- compiler/test_gen/src/gen_list.rs | 9 ++++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 395fbcbc45..2b83458408 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -875,9 +875,6 @@ pub fn listDropAt( return new_list; } - const stdout = std.io.getStdOut().writer(); - stdout.print("Hit non-unique branch with list, {any}!\n", .{list}) catch unreachable; - const output = RocList.allocate(alignment, size - 1, element_width); const target_ptr = output.bytes orelse unreachable; diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index bb3fb4dda4..47ac4b3e96 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -212,21 +212,24 @@ fn list_drop_at() { ); assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList); assert_evals_to!("List.dropAt [0] 0", RocList::from_slice(&[]), RocList); +} +#[test] +fn list_drop_at_mutable() { assert_evals_to!( indoc!( r#" list : List I64 - list = [ 1, 2, 3 ] + list = [ if True then 4 else 4, 5, 6 ] { newList: List.dropAt list 0, original: list } "# ), ( // new_list - RocList::from_slice(&[2, 3]), + RocList::from_slice(&[5, 6]), // original - RocList::from_slice(&[1, 2, 3]), + RocList::from_slice(&[4, 5, 6]), ), (RocList, RocList,) ); From b83336d3c305455ac5c613f47fdc701926959f70 Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 14:39:35 -0500 Subject: [PATCH 130/136] remove outdated uniqueness doc --- compiler/builtins/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md index 8e1eb0e291..7780ccb9ac 100644 --- a/compiler/builtins/README.md +++ b/compiler/builtins/README.md @@ -62,12 +62,6 @@ Its one thing to actually write these functions, its _another_ thing to let the ### builtins/mono/src/borrow.rs After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelvant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail. -## Specifying the uniqueness of a function -### builtins/src/unique.rs -One of the cool things about Roc is that it evaluates if a value in memory is shared between scopes or if it is used in just one place. If the value is used in one place then it is 'unique', and it therefore can be mutated in place. For a value created by a function, the uniqueness of the output is determined in part by the uniqueness of the input arguments. For example `List.single : elem -> List elem` can return a unique list if the `elem` is also unique. - -We have to define the uniqueness constraints of a function just like we have to define a type signature. That is what happens in `unique.rs`. This can be tricky so it would be a good step to ask for help on if it is confusing. - ## Testing it ### solve/tests/solve_expr.rs To make sure that Roc is properly inferring the type of the new builtin, add a test to this file simlar to: From 2756425eb9952a8ead7dcaa9c9cbe7878c725795 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 3 Oct 2021 21:52:27 +0100 Subject: [PATCH 131/136] Assert storage matches when copying --- compiler/gen_wasm/src/storage.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index cc70956b84..63213e5101 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -44,13 +44,17 @@ impl SymbolStorage { ( Self::Local { local_id: to_local_id, - .. + value_type: to_value_type, + size: to_size, }, Self::Local { local_id: from_local_id, - .. + value_type: from_value_type, + size: from_size, }, ) => { + debug_assert!(to_value_type == from_value_type); + debug_assert!(to_size == from_size); instructions.push(GetLocal(from_local_id.0)); instructions.push(SetLocal(to_local_id.0)); } From cc6f83f284a841ac96442cea4670056c2592d6e4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 3 Oct 2021 21:53:18 +0100 Subject: [PATCH 132/136] Clearer variant names for StackMemoryLocation --- compiler/gen_wasm/src/backend.rs | 14 +++++++------- compiler/gen_wasm/src/storage.rs | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index b6b837acf4..4512203625 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -200,7 +200,7 @@ impl<'a> WasmBackend<'a> { alignment_bytes, } => { let location = match kind { - LocalKind::Parameter => StackMemoryLocation::CallerFrame(next_local_id), + LocalKind::Parameter => StackMemoryLocation::PointerArg(next_local_id), LocalKind::Variable => { match self.stack_frame_pointer { @@ -215,7 +215,7 @@ impl<'a> WasmBackend<'a> { self.stack_memory = offset + size as i32; - StackMemoryLocation::OwnFrame(offset as u32) + StackMemoryLocation::FrameOffset(offset as u32) } }; @@ -260,14 +260,14 @@ impl<'a> WasmBackend<'a> { match storage { SymbolStorage::Local { local_id, .. } | SymbolStorage::StackMemory { - location: StackMemoryLocation::CallerFrame(local_id), + location: StackMemoryLocation::PointerArg(local_id), .. } => { self.instructions.push(GetLocal(local_id.0)); } SymbolStorage::StackMemory { - location: StackMemoryLocation::OwnFrame(offset), + location: StackMemoryLocation::FrameOffset(offset), .. } => { self.instructions.extend([ @@ -308,7 +308,7 @@ impl<'a> WasmBackend<'a> { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later let storage = SymbolStorage::StackMemory { - location: StackMemoryLocation::CallerFrame(LocalId(0)), + location: StackMemoryLocation::PointerArg(LocalId(0)), size, alignment_bytes, }; @@ -345,8 +345,8 @@ impl<'a> WasmBackend<'a> { alignment_bytes, } => { let (from_ptr, from_offset) = match location { - StackMemoryLocation::CallerFrame(local_id) => (*local_id, 0), - StackMemoryLocation::OwnFrame(offset) => { + StackMemoryLocation::PointerArg(local_id) => (*local_id, 0), + StackMemoryLocation::FrameOffset(offset) => { (self.stack_frame_pointer.unwrap(), *offset) } }; diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 63213e5101..80c0583234 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -3,15 +3,15 @@ use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; #[derive(Debug, Clone)] pub enum StackMemoryLocation { - CallerFrame(LocalId), - OwnFrame(u32), + FrameOffset(u32), + PointerArg(LocalId), } impl StackMemoryLocation { pub fn local_and_offset(&self, stack_frame_pointer: Option) -> (LocalId, u32) { match self { - Self::CallerFrame(local_id) => (*local_id, 0), - Self::OwnFrame(offset) => (stack_frame_pointer.unwrap(), *offset), + Self::PointerArg(local_id) => (*local_id, 0), + Self::FrameOffset(offset) => (stack_frame_pointer.unwrap(), *offset), } } } From 3ff134e1ce23b1c7ea78f8099ee1a028d17809cd Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 4 Oct 2021 09:42:02 +0200 Subject: [PATCH 133/136] removed erroneous imports --- editor/src/editor/ed_error.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index f0090e7609..1ebd3a88e6 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,5 +1,4 @@ -use crate::lang::parse::ASTNodeId; -use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; +use crate::{ui::text::text_pos::TextPos}; use colored::*; use roc_ast::ast_error::ASTError; use roc_ast::lang::core::ast::ASTNodeId; From 9a88ba72d7c7230887c51707b87e548e1f7d132a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 4 Oct 2021 09:56:35 +0200 Subject: [PATCH 134/136] fmt --- editor/src/editor/ed_error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index 1ebd3a88e6..357693bb69 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,4 +1,4 @@ -use crate::{ui::text::text_pos::TextPos}; +use crate::ui::text::text_pos::TextPos; use colored::*; use roc_ast::ast_error::ASTError; use roc_ast::lang::core::ast::ASTNodeId; From e6ec1ded22cf32a4ee95f6e0d9cfd39a471b85ce Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 4 Oct 2021 08:23:43 -0400 Subject: [PATCH 135/136] Add mapJoin and mapOrDrop to List --- compiler/builtins/docs/List.roc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index f8f9a2df43..65fd34d248 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -258,6 +258,18 @@ mapWithIndex : List before, (before, Nat -> after) -> List after ## cancel the entire operation immediately, and return that #Err. mapOrCancel : List before, (before -> Result after err) -> Result (List after) err +## Like [List.map], except the transformation function specifies whether to +## `Keep` or `Drop` each element from the final [List]. +## +## You may know a similar function named `filterMap` in other languages. +mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after + +## Like [List.map], except the transformation function wraps the return value +## in a list. At the end, all the lists get joined together into one list. +## +## You may know a similar function named `concatMap` in other languages. +mapJoin : List before, (before -> List after) -> List after + ## This works like [List.map], except only the transformed values that are ## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped. ## @@ -322,10 +334,6 @@ concat : List elem, List elem -> List elem ## >>> List.join [] join : List (List elem) -> List elem -## Like [List.map], except the transformation function wraps the return value -## in a list. At the end, all the lists get joined together into one list. -joinMap : List before, (before -> List after) -> List after - ## Like [List.join], but only keeps elements tagged with `Ok`. Elements ## tagged with `Err` are dropped. ## From cfe7d5afbb7466dd45df4b5e3f7855d684493e44 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 4 Oct 2021 08:39:09 -0400 Subject: [PATCH 136/136] Document List.map2 and List.map3 --- compiler/builtins/docs/List.roc | 43 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index 65fd34d248..6418c5f31a 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -6,7 +6,6 @@ interface List get, set, append, - map, len, walkBackwards, concat, @@ -23,9 +22,12 @@ interface List last, keepOks, keepErrs, - mapWithIndex, + map, map2, map3, + mapWithIndex, + mapOrDrop, + mapJoin, product, walkUntil, range, @@ -250,6 +252,21 @@ sortDesc : List elem, (elem -> Num *) -> List elem ## See for example `Set.map`, `Dict.map`, and [Result.map]. map : List before, (before -> after) -> List after +## Run a transformation function on the first element of each list, +## and use that as the first element in the returned list. +## Repeat until a list runs out of elements. +## +## Some languages have a function named `zip`, which does something similar to +## calling [List.map2] passing two lists and `Pair`: +## +## >>> zipped = List.map2 [ "a", "b", "c" ] [ 1, 2, 3 ] Pair +map2 : List a, List b, (a, b -> c) -> List c + +## Run a transformation function on the first element of each list, +## and use that as the first element in the returned list. +## Repeat until a list runs out of elements. +map3 : List a, List b, List c, (a, b, c -> d) -> List d + ## This works like [List.map], except it also passes the index ## of the element to the conversion function. mapWithIndex : List before, (before, Nat -> after) -> List after @@ -349,28 +366,6 @@ join : List (List elem) -> List elem ## so we're sticking with `Result` for now. oks : List (Result elem *) -> List elem -## Iterates over the shortest of the given lists and returns a list of `Pair` -## tags, each wrapping one of the elements in that list, along with the elements -## in the same index in # the other lists. -## -## >>> List.zip [ "a1", "b1" "c1" ] [ "a2", "b2" ] [ "a3", "b3", "c3" ] -## -## Accepts up to 8 lists. -## -## > For a generalized version that returns whatever you like, instead of a `Pair`, -## > see `zipMap`. -zip : List a, List b -> List [ Pair a b ]* - -## Like `zip` but you can specify what to do with each element. -## -## More specifically, [repeat what zip's docs say here] -## -## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3 -## -## Accepts up to 8 lists. -zipMap : List a, List b, (a, b -> c) -> List c - - ## Filter ## Run the given function on each element of a list, and return all the