Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ homepage = "https://github.com/pnkfelix/cee-scape"
documentation = "https://docs.rs/cee-scape/"
repository = "https://github.com/pnkfelix/cee-scape"
readme = "README.md"
build = "build/main.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down
22 changes: 0 additions & 22 deletions build.rs

This file was deleted.

9 changes: 9 additions & 0 deletions build/get_riscv64_consts.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <setjmp.h>

#if defined __riscv_float_abi_double
CEE_SCAPE_FLOAT_ABI_DOUBLE
#endif

#if defined __riscv_float_abi_soft
CEE_SCAPE_FLOAT_ABI_SOFT
#endif
62 changes: 62 additions & 0 deletions build/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use std::{fs::File, io::Write, path::PathBuf};

fn main() {
if std::env::var("CARGO_FEATURE_TEST_C_INTEGRATION")
.ok()
.is_some()
{
cc::Build::new()
.file("src/test_c_integration.c")
.compile("test_c_integration");
} else {
panic!("did not see it");
}

if std::env::var("CARGO_FEATURE_USE_C_TO_INTERFACE_WITH_SETJMP")
.ok()
.is_some()
{
cc::Build::new()
.file("src/interop_via_c.c")
.compile("interop_via_c");
} else {
}

if cfg!(target_arch = "riscv64") {
generate_riscv64_consts();
}
}

fn generate_riscv64_consts() {
println!("cargo:rerun-if-changed=build/get_riscv64_consts.c");

let expanded = cc::Build::new().file("build/get_riscv64_consts.c").expand();
let expanded = String::from_utf8(expanded).unwrap();

let mut float_abi_double = false;
let mut float_abi_soft = false;
for line in expanded.lines() {
match line.trim() {
"CEE_SCAPE_FLOAT_ABI_DOUBLE" => float_abi_double = true,
"CEE_SCAPE_FLOAT_ABI_SOFT" => float_abi_soft = true,
_ => {}
}
}

let out_dir: PathBuf = std::env::var("OUT_DIR")
.expect("OUT_DIR env variable should be available")
.into();
println!("cargo::rustc-env=OUT_DIR={}", out_dir.display());

let mut riscv64_consts_file = File::create(out_dir.join("riscv64_consts.rs"))
.expect("unable to create riscv64_consts.rs");
writeln!(
riscv64_consts_file,
"const FLOAT_ABI_DOUBLE: bool = {float_abi_double};\n\
const FLOAT_ABI_SOFT: bool = {float_abi_soft};"
)
.expect("unable to write to riscv64_consts.rs");
riscv64_consts_file
.flush()
.expect("unable to write to riscv64_consts.rs");
}
30 changes: 29 additions & 1 deletion src/asm_based.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@

use crate::{JmpBuf, JmpBufFields, JmpBufStruct};
use crate::{SigJmpBuf, SigJmpBufFields, SigJmpBufStruct};
use libc::c_int;
use core::mem::MaybeUninit;
use libc::c_int;

#[cfg(target_arch = "x86_64")]
macro_rules! maybesig_setjmp_asm {
Expand Down Expand Up @@ -118,6 +118,34 @@ macro_rules! maybesig_setjmp_asm {
}
}

#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
macro_rules! maybesig_setjmp_asm {
($sigsetjmp:ident, $jbuf_ptr:ident, $ifsig_savemask:ident, $closure_env_ptr:ident, $c2r:ident, $ret:ident) => {
core::arch::asm!(
// savemask, if needed, is already in a1
"mv a0, s3", // move saved jbuf_ptr to sigsetjmp param position. (savemask is already in position)
"jalr 0({tmp})", // fills in jbuf; future longjmp calls go here.
"bnez a0, 1f", // if return value non-zero, skip the callback invocation
// (and if return value non-zero, we cannot assume register state has been restored!)
"mv a0, s3", // move saved jmp buf into callback's arg position
"mv a1, s2", // move saved closure env into callback's arg position
"jalr 0(s4)", // invoke the callback
"1:", // at this point, a0 carries the return value (from either outcome)
// we let compiler pick this register since we don't need to preseve
// it across the first call.
tmp = in(reg) $sigsetjmp,
in("a1") $ifsig_savemask, // savemask arg position for sigsetjmp
out("a0") $ret, // the output will be stored here.
// [s2,s3,s4] are all callee-save registers for the normal sigsetjmp return.
// we will have them to reference.
in("s2") $closure_env_ptr,
in("s3") $jbuf_ptr,
in("s4") $c2r,
clobber_abi("C"), // clobber abi reflects call effects, including {x1,x5,x6,x7}...
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just as a heads up, I think this might be inheriting a known bug in other asm code in cee-scape, namely #14

but I'll be satisfied landing this and then fixing all the instances of #14 in one fell swoop (or filing follow-up issues for the cases that I don't initially fix on my own).

);
}
}

/// Covers the usual use case for setjmp: it invokes the callback, and the code
/// of the callback can use longjmp to exit early from the call_with_setjmp.
#[inline(never)] // see https://github.com/pnkfelix/cee-scape/issues/14
Expand Down
19 changes: 15 additions & 4 deletions src/cee_based.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ use crate::{JmpBuf, JmpBufFields};
use crate::{SigJmpBuf, SigJmpBufFields};
use libc::{c_int, c_void};

#[link(name = "interop_via_c", kind="static")]
#[link(name = "interop_via_c", kind = "static")]
extern "C" {
fn call_closure_with_setjmp(closure_env_ptr: *mut c_void, closure_code: extern "C" fn(jbuf: *const JmpBufFields, env_ptr: *mut c_void) -> c_int) -> c_int;
fn call_closure_with_setjmp(
closure_env_ptr: *mut c_void,
closure_code: extern "C" fn(jbuf: *const JmpBufFields, env_ptr: *mut c_void) -> c_int,
) -> c_int;

fn call_closure_with_sigsetjmp(savemask: c_int, closure_env_ptr: *mut c_void, closure_code: extern "C" fn(jbuf: *const SigJmpBufFields, env_ptr: *mut c_void) -> c_int) -> c_int;
fn call_closure_with_sigsetjmp(
savemask: c_int,
closure_env_ptr: *mut c_void,
closure_code: extern "C" fn(jbuf: *const SigJmpBufFields, env_ptr: *mut c_void) -> c_int,
) -> c_int;
}

/// Covers the usual use case for setjmp: it invokes the callback, and the code
Expand Down Expand Up @@ -76,6 +83,10 @@ where
// Therefore, we need to forget about our own ownership of the callback now.
core::mem::forget(callback);

call_closure_with_sigsetjmp(savemask, closure_env_ptr as *mut c_void, call_from_c_to_rust::<F>)
call_closure_with_sigsetjmp(
savemask,
closure_env_ptr as *mut c_void,
call_from_c_to_rust::<F>,
)
}
}
8 changes: 7 additions & 1 deletion src/glibc_compat.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use core::marker::PhantomData;

#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
const JMP_BUF_SIZE: usize = 8;

#[cfg(target_arch = "riscv64")]
const JMP_BUF_SIZE: usize = 14 /* pc + regs + sp */ + crate::riscv64::floating_point_registers();

/// `JmpBufFields` are the accessible fields when viewed via a JmpBuf pointer.
/// But also: You shouldn't be poking at these!
#[repr(C)]
pub struct JmpBufFields {
Copy link
Owner

@pnkfelix pnkfelix Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to encode the fields at this level of detail here?

I.e. the other JmpBufFields just includes an abstract pile of u64's (where the size of that type is dependent on the target OS+arch) that it expects the C runtime to manage on its own as part of the setjmp/longjmp implementation. Is there an advantage I'm not seeing to including the individual fields in the manner that you have here? (Or worse, some system invariant that I would be violating if I were to attempt a similar implementation strategy here?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I was surprised to see that the glibc code was so different between different architectures (I expected to find an array of long int/long long int -- I am also wondering why they don't just use a uint64_t, whatever), and in the end I used the same style of glibc to keep it easier to compare.

Said that, the "similarity" with the C code was the only reason behind this. There is no issue whatsoever with alignment or padding (because all the fields perfectly align to 8 bytes), and type punning is totally fine in this case (correct me if I am wrong, but I am pretty confident in this case). Therefore I can use an array of u64s similarly to x86_64 and aarch64 if you want, just let me know what you prefer! ☺️

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I agree with you that just ensuring 8-byte alignment seems like it will handle all possible issues here.

and I think it will be easier to maintain over time if we just use an array of u64s.

so if you can make that change, I'll r+ after that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now I only made the JMP_BUF_SIZE target specific. I added a very brief comment for one hardcoded value, it should be fine.

_buf: [u64; 8],
_buf: [u64; JMP_BUF_SIZE],
_neither_send_nor_sync: PhantomData<*const u8>,
}

Expand Down
35 changes: 25 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,12 @@

use libc::c_int;

#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
#[cfg(target_os = "linux")]
mod glibc_compat;
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
#[cfg(target_os = "macos")]
mod macos_compat;
#[cfg(target_arch = "riscv64")]
mod riscv64;
#[cfg(target_os = "linux")]
use glibc_compat as struct_defs;
#[cfg(target_os = "macos")]
Expand All @@ -190,8 +192,6 @@ use macos_compat as struct_defs;
pub use crate::struct_defs::{JmpBufFields, JmpBufStruct};
pub use crate::struct_defs::{SigJmpBufFields, SigJmpBufStruct};



/// This is the type of the first argument that is fed to longjmp.
pub type JmpBuf = *const JmpBufFields;

Expand Down Expand Up @@ -399,8 +399,8 @@ mod tests {

#[cfg(test)]
mod tests_of_drop_interaction {
use std::sync::atomic::{AtomicUsize, Ordering};
use super::{call_with_setjmp, call_with_sigsetjmp};
use std::sync::atomic::{AtomicUsize, Ordering};
struct IncrementOnDrop(&'static str, &'static AtomicUsize);
impl IncrementOnDrop {
fn new(name: &'static str, state: &'static AtomicUsize) -> Self {
Expand All @@ -424,15 +424,21 @@ mod tests_of_drop_interaction {
let _own_it = iod;
0
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
println!(
"callback done, drop counter: {}",
STATE.load(Ordering::Relaxed)
);
assert_eq!(STATE.load(Ordering::Relaxed), 1);
let iod = IncrementOnDrop::new("iod", &STATE);
call_with_setjmp(move |_env| {
println!("at callback 2 start: {}", iod.1.load(Ordering::Relaxed));
let _own_it = iod;
0
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
println!(
"callback done, drop counter: {}",
STATE.load(Ordering::Relaxed)
);
assert_eq!(STATE.load(Ordering::Relaxed), 2);
}

Expand All @@ -445,15 +451,21 @@ mod tests_of_drop_interaction {
let _own_it = iod;
0
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
println!(
"callback done, drop counter: {}",
STATE.load(Ordering::Relaxed)
);
assert_eq!(STATE.load(Ordering::Relaxed), 1);
let iod = IncrementOnDrop::new("iod", &STATE);
call_with_sigsetjmp(true, move |_env| {
println!("at callback 4 start: {}", iod.1.load(Ordering::Relaxed));
let _own_it = iod;
0
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
println!(
"callback done, drop counter: {}",
STATE.load(Ordering::Relaxed)
);
assert_eq!(STATE.load(Ordering::Relaxed), 2);
}

Expand All @@ -475,7 +487,10 @@ mod tests_of_drop_interaction {
let _own_it = iod;
unsafe { longjmp(env1, 4) }
});
println!("callback done, drop counter: {}", STATE.load(Ordering::Relaxed));
println!(
"callback done, drop counter: {}",
STATE.load(Ordering::Relaxed)
);
assert_eq!(STATE.load(Ordering::Relaxed), 0);
}
}
11 changes: 11 additions & 0 deletions src/riscv64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
include!(concat!(env!("OUT_DIR"), "/riscv64_consts.rs"));

pub const fn floating_point_registers() -> usize {
if FLOAT_ABI_DOUBLE {
12
} else if !FLOAT_ABI_SOFT {
panic!("unsupported number of floating point registers");
} else {
0
}
}