Skip to content

Commit

Permalink
Redesign create_thread to avoid dynamic allocation. (#94)
Browse files Browse the repository at this point in the history
Change `create_thread` from taking a boxed closure to taking a function
pointer, as well as an array of pointers to be copied into the new thread
to pass to the function. This avoids the need to dynamically allocate for
a `Box`, and in particular it allows c-ward's `pthread_create` to be used
from within `malloc` implementations.

And, implement thread return values, which is simpler with the removal of
`Option<Box<dyn Any>>`.

These changes do create more work for users of the API, however it also
gives them more control.

Fixes #85.
  • Loading branch information
sunfishcode authored Nov 9, 2023
1 parent 53be46b commit 71a37ab
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 94 deletions.
27 changes: 15 additions & 12 deletions example-crates/basic/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ fn main() {
eprintln!("Hello from a main-thread at_thread_exit handler")
}));

let thread = create_thread(
Box::new(|| {
eprintln!("Hello from child thread");
at_thread_exit(Box::new(|| {
eprintln!("Hello from child thread's at_thread_exit handler")
}));
None
}),
default_stack_size(),
default_guard_size(),
)
.unwrap();
let thread = unsafe {
create_thread(
|_args| {
eprintln!("Hello from child thread");
at_thread_exit(Box::new(|| {
eprintln!("Hello from child thread's at_thread_exit handler")
}));
None
},
&[],
default_stack_size(),
default_guard_size(),
)
.unwrap()
};

unsafe {
join_thread(thread);
Expand Down
5 changes: 3 additions & 2 deletions example-crates/external-start/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,14 @@ unsafe fn origin_main(_argc: usize, _argv: *mut *mut u8, _envp: *mut *mut u8) ->
}));

let thread = create_thread(
Box::new(|| {
|_args| {
eprintln!("Hello from child thread");
at_thread_exit(Box::new(|| {
eprintln!("Hello from child thread's at_thread_exit handler")
}));
None
}),
},
&[],
default_stack_size(),
default_guard_size(),
)
Expand Down
27 changes: 15 additions & 12 deletions example-crates/no-std/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,21 @@ fn start(_argc: isize, _argv: *const *const u8) -> isize {
eprintln!("Hello from a main-thread at_thread_exit handler")
}));

let thread = create_thread(
Box::new(|| {
eprintln!("Hello from child thread");
at_thread_exit(Box::new(|| {
eprintln!("Hello from child thread's at_thread_exit handler")
}));
None
}),
default_stack_size(),
default_guard_size(),
)
.unwrap();
let thread = unsafe {
create_thread(
|_args| {
eprintln!("Hello from child thread");
at_thread_exit(Box::new(|| {
eprintln!("Hello from child thread's at_thread_exit handler")
}));
None
},
&[],
default_stack_size(),
default_guard_size(),
)
.unwrap()
};

unsafe {
join_thread(thread);
Expand Down
5 changes: 3 additions & 2 deletions example-crates/origin-start-lto/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ unsafe fn origin_main(_argc: usize, _argv: *mut *mut u8, _envp: *mut *mut u8) ->
}));

let thread = create_thread(
Box::new(|| {
|_args| {
eprintln!("Hello from child thread");
at_thread_exit(Box::new(|| {
eprintln!("Hello from child thread's at_thread_exit handler")
}));
None
}),
},
&[],
default_stack_size(),
default_guard_size(),
)
Expand Down
5 changes: 3 additions & 2 deletions example-crates/origin-start/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ unsafe fn origin_main(_argc: usize, _argv: *mut *mut u8, _envp: *mut *mut u8) ->
}));

let thread = create_thread(
Box::new(|| {
|_args| {
eprintln!("Hello from child thread");
at_thread_exit(Box::new(|| {
eprintln!("Hello from child thread's at_thread_exit handler")
}));
None
}),
},
&[],
default_stack_size(),
default_guard_size(),
)
Expand Down
12 changes: 9 additions & 3 deletions src/arch/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ use linux_raw_sys::general::__NR_rt_sigreturn;
use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
#[cfg(feature = "origin-thread")]
use {
alloc::boxed::Box,
core::any::Any,
core::ffi::c_void,
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
rustix::thread::RawPid,
Expand Down Expand Up @@ -131,6 +129,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
assert_eq!(r0, 0);
}

/// The required alignment for the stack pointer.
#[cfg(feature = "origin-thread")]
pub(super) const STACK_ALIGNMENT: usize = 16;

/// A wrapper around the Linux `clone` system call.
///
/// This can't be implemented in `rustix` because the child starts executing at
Expand All @@ -144,7 +146,8 @@ pub(super) unsafe fn clone(
parent_tid: *mut RawPid,
child_tid: *mut RawPid,
newtls: *mut c_void,
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
fn_: extern "C" fn(),
num_args: usize,
) -> isize {
let r0;
asm!(
Expand All @@ -153,6 +156,8 @@ pub(super) unsafe fn clone(

// Child thread.
"mov x0, {fn_}", // Pass `fn_` as the first argument.
"mov x1, sp", // Pass the args pointer as the second argument.
"mov x2, {num_args}", // Pass `num_args` as the third argument.
"mov x29, xzr", // Zero the frame address.
"mov x30, xzr", // Zero the return address.
"b {entry}", // Call `entry`.
Expand All @@ -162,6 +167,7 @@ pub(super) unsafe fn clone(

entry = sym super::thread::entry,
fn_ = in(reg) fn_,
num_args = in(reg) num_args,
in("x8") __NR_clone,
inlateout("x0") flags as usize => r0,
in("x1") child_stack,
Expand Down
12 changes: 9 additions & 3 deletions src/arch/arm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
use linux_raw_sys::general::{__NR_rt_sigreturn, __NR_sigreturn};
#[cfg(feature = "origin-thread")]
use {
alloc::boxed::Box,
core::any::Any,
core::ffi::c_void,
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
rustix::thread::RawPid,
Expand Down Expand Up @@ -131,6 +129,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
assert_eq!(r0, 0);
}

/// The required alignment for the stack pointer.
#[cfg(feature = "origin-thread")]
pub(super) const STACK_ALIGNMENT: usize = 4;

/// A wrapper around the Linux `clone` system call.
///
/// This can't be implemented in `rustix` because the child starts executing at
Expand All @@ -144,7 +146,8 @@ pub(super) unsafe fn clone(
parent_tid: *mut RawPid,
child_tid: *mut RawPid,
newtls: *mut c_void,
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
fn_: extern "C" fn(),
num_args: usize,
) -> isize {
let r0;
asm!(
Expand All @@ -154,6 +157,8 @@ pub(super) unsafe fn clone(

// Child thread.
"mov r0, {fn_}", // Pass `fn_` as the first argument.
"mov r1, sp", // Pass the args pointer as the second argument.
"mov r2, {num_args}", // Pass `num_args` as the third argument.
"mov fp, #0", // Zero the frame address.
"mov lr, #0", // Zero the return address.
"b {entry}", // Call `entry`.
Expand All @@ -163,6 +168,7 @@ pub(super) unsafe fn clone(

entry = sym super::thread::entry,
fn_ = in(reg) fn_,
num_args = in(reg) num_args,
in("r7") __NR_clone,
inlateout("r0") flags as usize => r0,
in("r1") child_stack,
Expand Down
12 changes: 9 additions & 3 deletions src/arch/riscv64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ use core::arch::asm;
use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
#[cfg(feature = "origin-thread")]
use {
alloc::boxed::Box,
core::any::Any,
core::ffi::c_void,
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
rustix::thread::RawPid,
Expand Down Expand Up @@ -131,6 +129,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
assert_eq!(r0, 0);
}

/// The required alignment for the stack pointer.
#[cfg(feature = "origin-thread")]
pub(super) const STACK_ALIGNMENT: usize = 16;

/// A wrapper around the Linux `clone` system call.
///
/// This can't be implemented in `rustix` because the child starts executing at
Expand All @@ -144,7 +146,8 @@ pub(super) unsafe fn clone(
parent_tid: *mut RawPid,
child_tid: *mut RawPid,
newtls: *mut c_void,
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
fn_: extern "C" fn(),
num_args: usize,
) -> isize {
let r0;
asm!(
Expand All @@ -153,6 +156,8 @@ pub(super) unsafe fn clone(

// Child thread.
"mv a0, {fn_}", // Pass `fn_` as the first argument.
"mv a1, sp", // Pass the args pointer as the second argument.
"mv a2, {num_args}", // Pass `num_args` as the third argument.
"mv fp, zero", // Zero the frame address.
"mv ra, zero", // Zero the return address.
"tail {entry}", // Call `entry`.
Expand All @@ -162,6 +167,7 @@ pub(super) unsafe fn clone(

entry = sym super::thread::entry,
fn_ = in(reg) fn_,
num_args = in(reg) num_args,
in("a7") __NR_clone,
inlateout("a0") flags as usize => r0,
in("a1") child_stack,
Expand Down
28 changes: 19 additions & 9 deletions src/arch/x86.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
use linux_raw_sys::general::{__NR_rt_sigreturn, __NR_sigreturn};
#[cfg(feature = "origin-thread")]
use {
alloc::boxed::Box,
core::any::Any,
core::ffi::c_void,
core::ptr::invalid_mut,
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
Expand Down Expand Up @@ -136,6 +134,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
assert_eq!(r0, 0);
}

/// The required alignment for the stack pointer.
#[cfg(feature = "origin-thread")]
pub(super) const STACK_ALIGNMENT: usize = 16;

/// A wrapper around the Linux `clone` system call.
///
/// This can't be implemented in `rustix` because the child starts executing at
Expand All @@ -149,7 +151,8 @@ pub(super) unsafe fn clone(
parent_tid: *mut RawPid,
child_tid: *mut RawPid,
newtls: *mut c_void,
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
fn_: extern "C" fn(),
num_args: usize,
) -> isize {
let mut gs: u32 = 0;
asm!("mov {0:x}, gs", inout(reg) gs);
Expand All @@ -171,6 +174,11 @@ pub(super) unsafe fn clone(
user_desc.set_useable(1);
let newtls: *const _ = &user_desc;

// Push `fn_` to the child stack, since after all the `clone` args, and
// `num_args` in `ebp`, there are no more free registers.
let child_stack = child_stack.cast::<*mut c_void>().sub(1);
child_stack.write(fn_ as _);

// See the comments for x86's `syscall6` in `rustix`. Inline asm isn't
// allowed to name ebp or esi as operands, so we have to jump through
// extra hoops here.
Expand All @@ -179,7 +187,7 @@ pub(super) unsafe fn clone(
"push esi", // Save incoming register value.
"push ebp", // Save incoming register value.

// Pass `fn_` to the child in ebp.
// Pass `num_args` to the child in `ebp`.
"mov ebp, [eax+8]",

"mov esi, [eax+0]", // Pass `newtls` to the `int 0x80`.
Expand All @@ -193,10 +201,12 @@ pub(super) unsafe fn clone(
"jnz 0f",

// Child thread.
"push eax", // Pad for alignment.
"push eax", // Pad for alignment.
"push eax", // Pad for alignment.
"push ebp", // Pass `fn` as the first argument.
"pop edi", // Load `fn_` from the child stack.
"mov esi, esp", // Snapshot the args pointer.
"push eax", // Pad for stack pointer alignment.
"push ebp", // Pass `num_args` as the third argument.
"push esi", // Pass the args pointer as the second argument.
"push edi", // Pass `fn_` as the first argument.
"xor ebp, ebp", // Zero the frame address.
"push eax", // Zero the return address.
"jmp {entry}", // Call `entry`.
Expand All @@ -210,7 +220,7 @@ pub(super) unsafe fn clone(
inout("eax") &[
newtls.cast::<c_void>().cast_mut(),
invalid_mut(__NR_clone as usize),
fn_.cast::<c_void>()
invalid_mut(num_args)
] => r0,
in("ebx") flags,
in("ecx") child_stack,
Expand Down
14 changes: 10 additions & 4 deletions src/arch/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ use linux_raw_sys::general::__NR_rt_sigreturn;
use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
#[cfg(feature = "origin-thread")]
use {
alloc::boxed::Box,
core::any::Any,
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
rustix::thread::RawPid,
};
Expand Down Expand Up @@ -134,6 +132,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
assert_eq!(r0, 0);
}

/// The required alignment for the stack pointer.
#[cfg(feature = "origin-thread")]
pub(super) const STACK_ALIGNMENT: usize = 16;

/// A wrapper around the Linux `clone` system call.
///
/// This can't be implemented in `rustix` because the child starts executing at
Expand All @@ -147,7 +149,8 @@ pub(super) unsafe fn clone(
parent_tid: *mut RawPid,
child_tid: *mut RawPid,
newtls: *mut c_void,
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
fn_: extern "C" fn(),
num_args: usize,
) -> isize {
let r0;
asm!(
Expand All @@ -156,8 +159,10 @@ pub(super) unsafe fn clone(
"jnz 0f",

// Child thread.
"xor ebp, ebp", // Zero the frame address.
"mov rdi, r9", // Pass `fn_` as the first argument.
"mov rsi, rsp", // Pass the args pointer as the second argument.
"mov rdx, r12", // Pass `num_args` as the third argument.
"xor ebp, ebp", // Zero the frame address.
"push rax", // Zero the return address.
"jmp {entry}", // Call `entry`.

Expand All @@ -172,6 +177,7 @@ pub(super) unsafe fn clone(
in("r10") child_tid,
in("r8") newtls,
in("r9") fn_,
in("r12") num_args,
lateout("rcx") _,
lateout("r11") _,
options(nostack)
Expand Down
Loading

0 comments on commit 71a37ab

Please sign in to comment.