mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
Add alloca helper to roc_std
This commit is contained in:
parent
729607e3fd
commit
251e379271
5 changed files with 165 additions and 0 deletions
2
roc_std/.gitignore
vendored
Normal file
2
roc_std/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.o
|
||||
*.a
|
48
roc_std/build.rs
Normal file
48
roc_std/build.rs
Normal 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
9
roc_std/src/alloca.c
Normal 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
104
roc_std/src/alloca.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue