Merge pull request #1742 from rtfeldman/test-linker

Test linker
This commit is contained in:
Richard Feldman 2021-10-03 23:26:15 -04:00 committed by GitHub
commit 696a8273ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 187 additions and 259 deletions

1
.gitignore vendored
View file

@ -4,6 +4,7 @@ zig-cache
.direnv
*.rs.bk
*.o
*.tmp
# llvm human-readable output
*.ll

View file

@ -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,

View file

@ -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",

View file

@ -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" }

View file

@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-lib=dylib=app");
println!("cargo:rustc-link-search=.");
}

View file

@ -1,7 +1,3 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}
int main() { return rust_main(); }

View file

@ -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);

View file

@ -0,0 +1,3 @@
fn main() {
std::process::exit(host::rust_main());
}

View file

@ -1 +1 @@
hello-world
hello-rust

View file

@ -1,4 +1,4 @@
app "hello-world"
app "hello-rust"
packages { base: "platform" }
imports []
provides [ main ] to base

View file

@ -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" }

View file

@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-lib=dylib=app");
println!("cargo:rustc-link-search=.");
}

View file

@ -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); }
int main() { return rust_main(); }

View file

@ -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();

View file

@ -0,0 +1,3 @@
fn main() {
std::process::exit(host::rust_main());
}

View file

@ -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.

View file

@ -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) {

View file

@ -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,
}

View file

@ -1,11 +0,0 @@
fib
zig-cache
zig-out
*.o
dynhost
preprocessedhost
metadata
libapp.so

View file

@ -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)

View file

@ -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.

View file

@ -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

View file

@ -1 +0,0 @@
export fn roc__mainForHost_1_exposed(_i: i64, _result: *u64) void {}

View file

@ -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);
}

View file

@ -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;
}