mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 15:51:12 +00:00
commit
696a8273ee
25 changed files with 187 additions and 259 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,6 +4,7 @@ zig-cache
|
|||
.direnv
|
||||
*.rs.bk
|
||||
*.o
|
||||
*.tmp
|
||||
|
||||
# llvm human-readable output
|
||||
*.ll
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
|
4
examples/cli/platform/build.rs
Normal file
4
examples/cli/platform/build.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
fn main() {
|
||||
println!("cargo:rustc-link-lib=dylib=app");
|
||||
println!("cargo:rustc-link-search=.");
|
||||
}
|
|
@ -1,7 +1,3 @@
|
|||
#include <stdio.h>
|
||||
|
||||
extern int rust_main();
|
||||
|
||||
int main() {
|
||||
return rust_main();
|
||||
}
|
||||
int main() { return rust_main(); }
|
||||
|
|
|
@ -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::<u8>(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::<u8>(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);
|
||||
|
|
3
examples/cli/platform/src/main.rs
Normal file
3
examples/cli/platform/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
std::process::exit(host::rust_main());
|
||||
}
|
2
examples/hello-rust/.gitignore
vendored
2
examples/hello-rust/.gitignore
vendored
|
@ -1 +1 @@
|
|||
hello-world
|
||||
hello-rust
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
app "hello-world"
|
||||
app "hello-rust"
|
||||
packages { base: "platform" }
|
||||
imports []
|
||||
provides [ main ] to base
|
||||
|
|
|
@ -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" }
|
||||
|
|
4
examples/hello-rust/platform/build.rs
Normal file
4
examples/hello-rust/platform/build.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
fn main() {
|
||||
println!("cargo:rustc-link-lib=dylib=app");
|
||||
println!("cargo:rustc-link-search=.");
|
||||
}
|
|
@ -1,12 +1,3 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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); }
|
|
@ -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();
|
||||
|
||||
|
|
3
examples/hello-rust/platform/src/main.rs
Normal file
3
examples/hello-rust/platform/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
std::process::exit(host::rust_main());
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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<Symbol> = 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::<elf::Rela64<LittleEndian>>(),
|
||||
);
|
||||
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::<elf::Rela64<LittleEndian>>()
|
||||
// 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::<elf::Sym64<LittleEndian>>(),
|
||||
);
|
||||
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) {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
11
linker/tests/fib/.gitignore
vendored
11
linker/tests/fib/.gitignore
vendored
|
@ -1,11 +0,0 @@
|
|||
fib
|
||||
|
||||
zig-cache
|
||||
zig-out
|
||||
|
||||
*.o
|
||||
|
||||
dynhost
|
||||
preprocessedhost
|
||||
metadata
|
||||
libapp.so
|
|
@ -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)
|
|
@ -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.
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
export fn roc__mainForHost_1_exposed(_i: i64, _result: *u64) void {}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue