Add alloca helper to roc_std

This commit is contained in:
Richard Feldman 2020-11-01 22:48:47 -05:00
parent 729607e3fd
commit 251e379271
5 changed files with 165 additions and 0 deletions

2
roc_std/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.o
*.a

48
roc_std/build.rs Normal file
View file

@ -0,0 +1,48 @@
// Adapted from https://github.com/TheDan64/scoped_alloca
// by Daniel Kolsoi, licensed under the Apache License 2.0
// Thank you, Dan!
use std::env;
use std::fs::create_dir;
use std::path::Path;
use std::process::Command;
fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let cargo_dir = Path::new(manifest_dir.as_str());
let lib_dir = cargo_dir.join("lib");
let alloca_c = cargo_dir.join("src/alloca.c");
let alloca_o = lib_dir.join("alloca.o");
let liballoca_a = lib_dir.join("liballoca.a");
println!("cargo:rustc-link-search=native={}", lib_dir.display());
// No need to recompile alloca static lib every time. We could
// add a feature flag to do so if needed, though
if liballoca_a.is_file() {
return;
}
if !lib_dir.is_dir() {
create_dir(&lib_dir).unwrap();
}
let clang_output = Command::new("clang")
.arg("-c")
.arg(alloca_c)
.arg("-o")
.arg(&alloca_o)
.output()
.expect("Could not execute clang");
assert!(clang_output.status.success(), "{:?}", clang_output);
let ar_output = Command::new("ar")
.arg("-q")
.arg(liballoca_a)
.arg(alloca_o)
.output()
.expect("Could not execute ar");
assert!(ar_output.status.success(), "{:?}", ar_output);
}

9
roc_std/src/alloca.c Normal file
View file

@ -0,0 +1,9 @@
#include <alloca.h>
// From https://github.com/TheDan64/scoped_alloca
// by Daniel Kolsoi, licensed under the Apache License 2.0
// Thank you, Dan!
void *c_alloca(size_t bytes) {
return alloca(bytes);
}

104
roc_std/src/alloca.rs Normal file
View file

@ -0,0 +1,104 @@
// Adapted from https://github.com/TheDan64/scoped_alloca
// by Daniel Kolsoi, licensed under the Apache License 2.0
// Thank you, Dan!
use libc::{c_void, size_t};
#[link(name = "alloca")]
extern "C" {
#[no_mangle]
fn c_alloca(_: size_t) -> *mut c_void;
}
/// This calls C's `alloca` function to allocate some bytes on the stack,
/// then provides those bytes to the given callback function.
///
/// Note that this function is `#[inline(always)]`, which means it will allocate
/// the bytes on the stack of whoever calls this function.
///
/// Naturally, if you give this a large number of bytes, it may cause
/// stack overflows, so be careful!
///
/// Due to how Rust FFI works with inlining, in debug builds this actually
/// allocates on the heap (using `malloc`) and then deallocates the memory
/// (using `free`) before the callback returns. In debug builds, this can lead
/// to memory leaks if the callback panics - but release builds should be fine,
/// because they only ever allocate on the stack.
#[inline(always)]
pub unsafe fn with_stack_bytes<F, R>(bytes: usize, callback: F) -> R
where
F: FnOnce(*mut c_void) -> R,
{
let ptr = malloc_or_alloca(bytes);
let answer = callback(ptr);
free_or_noop(ptr);
answer
}
#[cfg(debug_assertions)]
#[inline(always)]
unsafe fn malloc_or_alloca(bytes: usize) -> *mut c_void {
libc::malloc(bytes)
}
#[cfg(not(debug_assertions))]
#[inline(always)]
unsafe fn malloc_or_alloca(bytes: usize) -> *mut c_void {
c_alloca(bytes)
}
#[cfg(debug_assertions)]
#[inline(always)]
unsafe fn free_or_noop(ptr: *mut c_void) {
libc::free(ptr)
}
#[cfg(not(debug_assertions))]
#[inline(always)]
fn free_or_noop(_ptr: *mut c_void) {
// In release builds, we'll have used alloca,
// so there's nothing to free.
}
#[cfg(test)]
mod tests {
use super::with_stack_bytes;
use core::mem::size_of;
#[repr(C)]
#[derive(Debug, PartialEq)]
struct TestStruct {
x: u8,
y: u16,
z: u64,
}
#[test]
fn test_alloca() {
let test_struct = TestStruct {
x: 123,
y: 4567,
z: 89012345678,
};
let sum: u64 = unsafe {
with_stack_bytes(size_of::<TestStruct>(), |ptr| {
let ptr = ptr as *mut TestStruct;
(*ptr).x = test_struct.x;
(*ptr).y = test_struct.y;
(*ptr).z = test_struct.z;
assert_eq!(*ptr, test_struct);
(*ptr).x as u64 + (*ptr).y as u64 + (*ptr).z as u64
})
};
assert_eq!(
sum,
test_struct.x as u64 + test_struct.y as u64 + test_struct.z as u64
);
}
}

View file

@ -2,6 +2,8 @@
#![no_std]
use core::fmt;
pub mod alloca;
// A list of C functions that are being imported
extern "C" {
pub fn printf(format: *const u8, ...) -> i32;