diff --git a/.gitignore b/.gitignore index 239f4d05dc..6ad9f80e0a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ zig-cache .direnv *.rs.bk *.o +*.tmp # llvm human-readable output *.ll diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 925af2335d..725b62dba2 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,19 @@ mod cli_run { example.expected_ending, example.use_valgrind, ); + + // Also check with the surgical linker. + + if TEST_SURGICAL_LINKER { + check_output_with_stdin( + &file_name, + example.stdin, + example.executable_filename, + &["--roc-linker"], + example.expected_ending, + example.use_valgrind, + ); + } } )* @@ -228,7 +247,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/compiler/build/src/link.rs b/compiler/build/src/link.rs index d7a894838c..ff22fd9b00 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -380,7 +380,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) { @@ -388,30 +388,30 @@ 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.env("RUSTFLAGS", "-C link-dead-code"); + 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, @@ -428,7 +428,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..d316e264d8 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() -> i32 { 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..51175f934b --- /dev/null +++ b/examples/cli/platform/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(host::rust_main()); +} 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/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..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; @@ -12,12 +11,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 +26,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 +44,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() -> 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 new file mode 100644 index 0000000000..51175f934b --- /dev/null +++ b/examples/hello-rust/platform/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(host::rust_main()); +} diff --git a/linker/README.md b/linker/README.md index 0d1e2d77ee..e5342c64ea 100644 --- a/linker/README.md +++ b/linker/README.md @@ -31,8 +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. This means we likely need to do a lot of refactoring to minimize the duplicate code. @@ -41,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. diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 1a043fbce1..be4aa891c9 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,9 +197,9 @@ 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), format!("roc__{}_1_Fx_caller", sym), format!("roc__{}_1_Fx_size", sym), format!("roc__{}_1_Fx_result_size", sym), @@ -316,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 @@ -367,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 => { @@ -379,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() @@ -387,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 + }) + .flatten() + .collect(); + for sym in app_syms.iter() { let name = sym.name().unwrap().to_string(); md.app_functions.push(name.clone()); @@ -536,7 +558,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, }); } @@ -878,7 +900,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); @@ -890,6 +912,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, + }); + } + } + } } } @@ -1461,7 +1505,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); @@ -1471,7 +1515,7 @@ fn surgery_impl( if verbose { println!( "Updating calls to {} to the address: {:+x}", - &func_name, virt_offset + &func_name, func_virt_offset ); } @@ -1479,11 +1523,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); } @@ -1492,6 +1538,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); @@ -1505,7 +1561,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); @@ -1524,7 +1581,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/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; -}