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
|
.direnv
|
||||||
*.rs.bk
|
*.rs.bk
|
||||||
*.o
|
*.o
|
||||||
|
*.tmp
|
||||||
|
|
||||||
# llvm human-readable output
|
# llvm human-readable output
|
||||||
*.ll
|
*.ll
|
||||||
|
|
|
@ -18,6 +18,13 @@ mod cli_run {
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
use roc_collections::all::MutMap;
|
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"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
const ALLOW_VALGRIND: bool = true;
|
const ALLOW_VALGRIND: bool = true;
|
||||||
|
|
||||||
|
@ -136,7 +143,6 @@ mod cli_run {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This macro does two things.
|
/// This macro does two things.
|
||||||
///
|
///
|
||||||
/// First, it generates and runs a separate test for each of the given
|
/// First, it generates and runs a separate test for each of the given
|
||||||
|
@ -184,6 +190,19 @@ mod cli_run {
|
||||||
example.expected_ending,
|
example.expected_ending,
|
||||||
example.use_valgrind,
|
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 {
|
hello_rust:"hello-rust" => Example {
|
||||||
filename: "Hello.roc",
|
filename: "Hello.roc",
|
||||||
executable_filename: "hello-world",
|
executable_filename: "hello-rust",
|
||||||
stdin: &[],
|
stdin: &[],
|
||||||
expected_ending:"Hello, World!\n",
|
expected_ending:"Hello, World!\n",
|
||||||
use_valgrind: true,
|
use_valgrind: true,
|
||||||
|
|
|
@ -380,7 +380,7 @@ pub fn rebuild_host(
|
||||||
} else if cargo_host_src.exists() {
|
} else if cargo_host_src.exists() {
|
||||||
// Compile and link Cargo.toml, if it exists
|
// Compile and link Cargo.toml, if it exists
|
||||||
let cargo_dir = host_input_path.parent().unwrap();
|
let cargo_dir = host_input_path.parent().unwrap();
|
||||||
let libhost_dir =
|
let cargo_out_dir =
|
||||||
cargo_dir
|
cargo_dir
|
||||||
.join("target")
|
.join("target")
|
||||||
.join(if matches!(opt_level, OptLevel::Optimize) {
|
.join(if matches!(opt_level, OptLevel::Optimize) {
|
||||||
|
@ -388,30 +388,30 @@ pub fn rebuild_host(
|
||||||
} else {
|
} else {
|
||||||
"debug"
|
"debug"
|
||||||
});
|
});
|
||||||
let libhost = libhost_dir.join("libhost.a");
|
|
||||||
|
|
||||||
let mut command = Command::new("cargo");
|
let mut command = Command::new("cargo");
|
||||||
command.arg("build").current_dir(cargo_dir);
|
command.arg("build").current_dir(cargo_dir);
|
||||||
if matches!(opt_level, OptLevel::Optimize) {
|
if matches!(opt_level, OptLevel::Optimize) {
|
||||||
command.arg("--release");
|
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();
|
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 shared_lib_path.is_some() {
|
||||||
// If compiling to executable, let c deal with linking as well.
|
// For surgical linking, just copy the dynamically linked rust app.
|
||||||
let output = build_c_host_native(
|
std::fs::copy(cargo_out_dir.join("host"), host_dest_native).unwrap();
|
||||||
&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);
|
|
||||||
} else {
|
} else {
|
||||||
|
// Cargo hosts depend on a c wrapper for the api. Compile host.c as well.
|
||||||
|
|
||||||
let output = build_c_host_native(
|
let output = build_c_host_native(
|
||||||
&env_path,
|
&env_path,
|
||||||
&env_home,
|
&env_home,
|
||||||
|
@ -428,7 +428,7 @@ pub fn rebuild_host(
|
||||||
.args(&[
|
.args(&[
|
||||||
"-r",
|
"-r",
|
||||||
"-L",
|
"-L",
|
||||||
libhost_dir.to_str().unwrap(),
|
cargo_out_dir.to_str().unwrap(),
|
||||||
c_host_dest.to_str().unwrap(),
|
c_host_dest.to_str().unwrap(),
|
||||||
"-lhost",
|
"-lhost",
|
||||||
"-o",
|
"-o",
|
||||||
|
|
|
@ -5,8 +5,16 @@ authors = ["The Roc Contributors"]
|
||||||
license = "UPL-1.0"
|
license = "UPL-1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
links = "app"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib"]
|
name = "host"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
crate-type = ["staticlib", "rlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "host"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
roc_std = { path = "../../../roc_std" }
|
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();
|
extern int rust_main();
|
||||||
|
|
||||||
int main() {
|
int main() { return rust_main(); }
|
||||||
return rust_main();
|
|
||||||
}
|
|
||||||
|
|
|
@ -27,12 +27,12 @@ extern "C" {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[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)
|
libc::malloc(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe fn roc_realloc(
|
pub unsafe extern "C" fn roc_realloc(
|
||||||
c_ptr: *mut c_void,
|
c_ptr: *mut c_void,
|
||||||
new_size: usize,
|
new_size: usize,
|
||||||
_old_size: usize,
|
_old_size: usize,
|
||||||
|
@ -42,12 +42,12 @@ pub unsafe fn roc_realloc(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[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)
|
libc::free(c_ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[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 {
|
match tag_id {
|
||||||
0 => {
|
0 => {
|
||||||
let slice = CStr::from_ptr(c_ptr as *const c_char);
|
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]
|
#[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 size = unsafe { roc_main_size() } as usize;
|
||||||
let layout = Layout::array::<u8>(size).unwrap();
|
let layout = Layout::array::<u8>(size).unwrap();
|
||||||
|
|
||||||
|
@ -81,7 +91,7 @@ pub fn rust_main() -> isize {
|
||||||
0
|
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 size = size_Fx_result() as usize;
|
||||||
let layout = Layout::array::<u8>(size).unwrap();
|
let layout = Layout::array::<u8>(size).unwrap();
|
||||||
let buffer = std::alloc::alloc(layout) as *mut u8;
|
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]
|
#[no_mangle]
|
||||||
pub fn roc_fx_getLine() -> RocStr {
|
pub extern "C" fn roc_fx_getLine() -> RocStr {
|
||||||
use std::io::{self, BufRead};
|
use std::io::{self, BufRead};
|
||||||
|
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
|
@ -109,7 +119,7 @@ pub fn roc_fx_getLine() -> RocStr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn roc_fx_putLine(line: RocStr) -> () {
|
pub extern "C" fn roc_fx_putLine(line: RocStr) -> () {
|
||||||
let bytes = line.as_slice();
|
let bytes = line.as_slice();
|
||||||
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
|
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
|
||||||
println!("{}", string);
|
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" }
|
packages { base: "platform" }
|
||||||
imports []
|
imports []
|
||||||
provides [ main ] to base
|
provides [ main ] to base
|
||||||
|
|
|
@ -4,9 +4,16 @@ version = "0.1.0"
|
||||||
authors = ["The Roc Contributors"]
|
authors = ["The Roc Contributors"]
|
||||||
license = "UPL-1.0"
|
license = "UPL-1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
links = "app"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib"]
|
name = "host"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
crate-type = ["staticlib", "rlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "host"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
roc_std = { path = "../../../roc_std" }
|
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();
|
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); }
|
|
|
@ -1,7 +1,6 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use core::ffi::c_void;
|
use core::ffi::c_void;
|
||||||
use core::mem::MaybeUninit;
|
|
||||||
use libc::c_char;
|
use libc::c_char;
|
||||||
use roc_std::RocStr;
|
use roc_std::RocStr;
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
@ -12,12 +11,12 @@ extern "C" {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[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);
|
return libc::malloc(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe fn roc_realloc(
|
pub unsafe extern "C" fn roc_realloc(
|
||||||
c_ptr: *mut c_void,
|
c_ptr: *mut c_void,
|
||||||
new_size: usize,
|
new_size: usize,
|
||||||
_old_size: usize,
|
_old_size: usize,
|
||||||
|
@ -27,12 +26,12 @@ pub unsafe fn roc_realloc(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[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);
|
return libc::free(c_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[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 {
|
match tag_id {
|
||||||
0 => {
|
0 => {
|
||||||
let slice = CStr::from_ptr(c_ptr as *const c_char);
|
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]
|
#[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 {
|
unsafe {
|
||||||
let roc_str = roc_main();
|
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)
|
## 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
|
- Add Macho support
|
||||||
- Honestly should be almost exactly the same code.
|
- Honestly should be almost exactly the same code.
|
||||||
This means we likely need to do a lot of refactoring to minimize the duplicate 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).
|
- 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.
|
- 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 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.
|
- Add a feature to the compiler to make this linker optional.
|
||||||
|
|
|
@ -28,6 +28,7 @@ use target_lexicon::Triple;
|
||||||
use tempfile::Builder;
|
use tempfile::Builder;
|
||||||
|
|
||||||
mod metadata;
|
mod metadata;
|
||||||
|
use metadata::VirtualOffset;
|
||||||
|
|
||||||
pub const CMD_PREPROCESS: &str = "preprocess";
|
pub const CMD_PREPROCESS: &str = "preprocess";
|
||||||
pub const CMD_SURGERY: &str = "surgery";
|
pub const CMD_SURGERY: &str = "surgery";
|
||||||
|
@ -196,9 +197,9 @@ fn generate_dynamic_lib(
|
||||||
|
|
||||||
let text_section = out_object.section_id(write::StandardSection::Text);
|
let text_section = out_object.section_id(write::StandardSection::Text);
|
||||||
for sym in exposed_to_host {
|
for sym in exposed_to_host {
|
||||||
// TODO properly generate this list.
|
|
||||||
for name in &[
|
for name in &[
|
||||||
format!("roc__{}_1_exposed", sym),
|
format!("roc__{}_1_exposed", sym),
|
||||||
|
format!("roc__{}_1_exposed_generic", sym),
|
||||||
format!("roc__{}_1_Fx_caller", sym),
|
format!("roc__{}_1_Fx_caller", sym),
|
||||||
format!("roc__{}_1_Fx_size", sym),
|
format!("roc__{}_1_Fx_size", sym),
|
||||||
format!("roc__{}_1_Fx_result_size", sym),
|
format!("roc__{}_1_Fx_result_size", sym),
|
||||||
|
@ -316,7 +317,9 @@ fn preprocess_impl(
|
||||||
for sym in exec_obj.symbols().filter(|sym| {
|
for sym in exec_obj.symbols().filter(|sym| {
|
||||||
sym.is_definition() && sym.name().is_ok() && sym.name().unwrap().starts_with("roc_")
|
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.
|
// special exceptions for memcpy and memset.
|
||||||
if &name == "roc_memcpy" {
|
if &name == "roc_memcpy" {
|
||||||
md.roc_symbol_vaddresses
|
md.roc_symbol_vaddresses
|
||||||
|
@ -367,9 +370,6 @@ fn preprocess_impl(
|
||||||
println!("PLT File Offset: {:+x}", plt_offset);
|
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() {
|
let plt_relocs = (match exec_obj.dynamic_relocations() {
|
||||||
Some(relocs) => relocs,
|
Some(relocs) => relocs,
|
||||||
None => {
|
None => {
|
||||||
|
@ -379,7 +379,7 @@ fn preprocess_impl(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|(_, reloc)| reloc)
|
.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
|
let app_syms: Vec<Symbol> = exec_obj
|
||||||
.dynamic_symbols()
|
.dynamic_symbols()
|
||||||
|
@ -387,6 +387,28 @@ fn preprocess_impl(
|
||||||
sym.is_undefined() && sym.name().is_ok() && sym.name().unwrap().starts_with("roc_")
|
sym.is_undefined() && sym.name().is_ok() && sym.name().unwrap().starts_with("roc_")
|
||||||
})
|
})
|
||||||
.collect();
|
.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() {
|
for sym in app_syms.iter() {
|
||||||
let name = sym.name().unwrap().to_string();
|
let name = sym.name().unwrap().to_string();
|
||||||
md.app_functions.push(name.clone());
|
md.app_functions.push(name.clone());
|
||||||
|
@ -536,7 +558,7 @@ fn preprocess_impl(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.push(metadata::SurgeryEntry {
|
.push(metadata::SurgeryEntry {
|
||||||
file_offset: offset,
|
file_offset: offset,
|
||||||
virtual_offset: inst.next_ip(),
|
virtual_offset: VirtualOffset::Relative(inst.next_ip()),
|
||||||
size: op_size,
|
size: op_size,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -878,7 +900,7 @@ fn preprocess_impl(
|
||||||
sec_offset as usize + md.added_byte_count as usize,
|
sec_offset as usize + md.added_byte_count as usize,
|
||||||
sec_size as usize / mem::size_of::<elf::Rela64<LittleEndian>>(),
|
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);
|
let r_offset = rel.r_offset.get(NativeEndian);
|
||||||
if virtual_shift_start <= r_offset {
|
if virtual_shift_start <= r_offset {
|
||||||
rel.r_offset = endian::U64::new(LittleEndian, r_offset + md.added_byte_count);
|
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);
|
.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;
|
let dynsym_offset = md.dynamic_symbol_table_section_offset + md.added_byte_count;
|
||||||
|
|
||||||
for func_name in md.app_functions {
|
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,
|
Some(offset) => *offset as u64,
|
||||||
None => {
|
None => {
|
||||||
println!("Function, {}, was not defined by the app", &func_name);
|
println!("Function, {}, was not defined by the app", &func_name);
|
||||||
|
@ -1471,7 +1515,7 @@ fn surgery_impl(
|
||||||
if verbose {
|
if verbose {
|
||||||
println!(
|
println!(
|
||||||
"Updating calls to {} to the address: {:+x}",
|
"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 {
|
if verbose {
|
||||||
println!("\tPerforming surgery: {:+x?}", s);
|
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 {
|
match s.size {
|
||||||
4 => {
|
4 => {
|
||||||
let target = (virt_offset as i64
|
let target = (func_virt_offset as i64 - surgery_virt_offset) as i32;
|
||||||
- (s.virtual_offset + md.added_byte_count) as i64)
|
|
||||||
as i32;
|
|
||||||
if verbose {
|
if verbose {
|
||||||
println!("\tTarget Jump: {:+x}", target);
|
println!("\tTarget Jump: {:+x}", target);
|
||||||
}
|
}
|
||||||
|
@ -1492,6 +1538,16 @@ fn surgery_impl(
|
||||||
..(s.file_offset + md.added_byte_count) as usize + 4]
|
..(s.file_offset + md.added_byte_count) as usize + 4]
|
||||||
.copy_from_slice(&data);
|
.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 => {
|
x => {
|
||||||
println!("Surgery size not yet supported: {}", x);
|
println!("Surgery size not yet supported: {}", x);
|
||||||
return Ok(-1);
|
return Ok(-1);
|
||||||
|
@ -1505,7 +1561,8 @@ fn surgery_impl(
|
||||||
let plt_off = (*plt_off + md.added_byte_count) as usize;
|
let plt_off = (*plt_off + md.added_byte_count) as usize;
|
||||||
let plt_vaddr = *plt_vaddr + md.added_byte_count;
|
let plt_vaddr = *plt_vaddr + md.added_byte_count;
|
||||||
let jmp_inst_len = 5;
|
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 {
|
if verbose {
|
||||||
println!("\tPLT: {:+x}, {:+x}", plt_off, plt_vaddr);
|
println!("\tPLT: {:+x}, {:+x}", plt_off, plt_vaddr);
|
||||||
println!("\tTarget Jump: {:+x}", target);
|
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>>(),
|
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_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(
|
sym.st_size = endian::U64::new(
|
||||||
LittleEndian,
|
LittleEndian,
|
||||||
match app_func_size_map.get(&func_name) {
|
match app_func_size_map.get(&func_name) {
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
pub enum VirtualOffset {
|
||||||
|
Absolute,
|
||||||
|
Relative(u64),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
pub struct SurgeryEntry {
|
pub struct SurgeryEntry {
|
||||||
pub file_offset: u64,
|
pub file_offset: u64,
|
||||||
pub virtual_offset: u64,
|
pub virtual_offset: VirtualOffset,
|
||||||
pub size: u8,
|
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