Skip to content

Commit 71a37ab

Browse files
authored
Redesign create_thread to avoid dynamic allocation. (#94)
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.
1 parent 53be46b commit 71a37ab

File tree

12 files changed

+242
-94
lines changed

12 files changed

+242
-94
lines changed

example-crates/basic/src/main.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ fn main() {
99
eprintln!("Hello from a main-thread at_thread_exit handler")
1010
}));
1111

12-
let thread = create_thread(
13-
Box::new(|| {
14-
eprintln!("Hello from child thread");
15-
at_thread_exit(Box::new(|| {
16-
eprintln!("Hello from child thread's at_thread_exit handler")
17-
}));
18-
None
19-
}),
20-
default_stack_size(),
21-
default_guard_size(),
22-
)
23-
.unwrap();
12+
let thread = unsafe {
13+
create_thread(
14+
|_args| {
15+
eprintln!("Hello from child thread");
16+
at_thread_exit(Box::new(|| {
17+
eprintln!("Hello from child thread's at_thread_exit handler")
18+
}));
19+
None
20+
},
21+
&[],
22+
default_stack_size(),
23+
default_guard_size(),
24+
)
25+
.unwrap()
26+
};
2427

2528
unsafe {
2629
join_thread(thread);

example-crates/external-start/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ unsafe fn origin_main(_argc: usize, _argv: *mut *mut u8, _envp: *mut *mut u8) ->
6565
}));
6666

6767
let thread = create_thread(
68-
Box::new(|| {
68+
|_args| {
6969
eprintln!("Hello from child thread");
7070
at_thread_exit(Box::new(|| {
7171
eprintln!("Hello from child thread's at_thread_exit handler")
7272
}));
7373
None
74-
}),
74+
},
75+
&[],
7576
default_stack_size(),
7677
default_guard_size(),
7778
)

example-crates/no-std/src/main.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,21 @@ fn start(_argc: isize, _argv: *const *const u8) -> isize {
3434
eprintln!("Hello from a main-thread at_thread_exit handler")
3535
}));
3636

37-
let thread = create_thread(
38-
Box::new(|| {
39-
eprintln!("Hello from child thread");
40-
at_thread_exit(Box::new(|| {
41-
eprintln!("Hello from child thread's at_thread_exit handler")
42-
}));
43-
None
44-
}),
45-
default_stack_size(),
46-
default_guard_size(),
47-
)
48-
.unwrap();
37+
let thread = unsafe {
38+
create_thread(
39+
|_args| {
40+
eprintln!("Hello from child thread");
41+
at_thread_exit(Box::new(|| {
42+
eprintln!("Hello from child thread's at_thread_exit handler")
43+
}));
44+
None
45+
},
46+
&[],
47+
default_stack_size(),
48+
default_guard_size(),
49+
)
50+
.unwrap()
51+
};
4952

5053
unsafe {
5154
join_thread(thread);

example-crates/origin-start-lto/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ unsafe fn origin_main(_argc: usize, _argv: *mut *mut u8, _envp: *mut *mut u8) ->
3636
}));
3737

3838
let thread = create_thread(
39-
Box::new(|| {
39+
|_args| {
4040
eprintln!("Hello from child thread");
4141
at_thread_exit(Box::new(|| {
4242
eprintln!("Hello from child thread's at_thread_exit handler")
4343
}));
4444
None
45-
}),
45+
},
46+
&[],
4647
default_stack_size(),
4748
default_guard_size(),
4849
)

example-crates/origin-start/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ unsafe fn origin_main(_argc: usize, _argv: *mut *mut u8, _envp: *mut *mut u8) ->
3636
}));
3737

3838
let thread = create_thread(
39-
Box::new(|| {
39+
|_args| {
4040
eprintln!("Hello from child thread");
4141
at_thread_exit(Box::new(|| {
4242
eprintln!("Hello from child thread's at_thread_exit handler")
4343
}));
4444
None
45-
}),
45+
},
46+
&[],
4647
default_stack_size(),
4748
default_guard_size(),
4849
)

src/arch/aarch64.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ use linux_raw_sys::general::__NR_rt_sigreturn;
88
use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
99
#[cfg(feature = "origin-thread")]
1010
use {
11-
alloc::boxed::Box,
12-
core::any::Any,
1311
core::ffi::c_void,
1412
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
1513
rustix::thread::RawPid,
@@ -131,6 +129,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
131129
assert_eq!(r0, 0);
132130
}
133131

132+
/// The required alignment for the stack pointer.
133+
#[cfg(feature = "origin-thread")]
134+
pub(super) const STACK_ALIGNMENT: usize = 16;
135+
134136
/// A wrapper around the Linux `clone` system call.
135137
///
136138
/// This can't be implemented in `rustix` because the child starts executing at
@@ -144,7 +146,8 @@ pub(super) unsafe fn clone(
144146
parent_tid: *mut RawPid,
145147
child_tid: *mut RawPid,
146148
newtls: *mut c_void,
147-
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
149+
fn_: extern "C" fn(),
150+
num_args: usize,
148151
) -> isize {
149152
let r0;
150153
asm!(
@@ -153,6 +156,8 @@ pub(super) unsafe fn clone(
153156

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

163168
entry = sym super::thread::entry,
164169
fn_ = in(reg) fn_,
170+
num_args = in(reg) num_args,
165171
in("x8") __NR_clone,
166172
inlateout("x0") flags as usize => r0,
167173
in("x1") child_stack,

src/arch/arm.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
88
use linux_raw_sys::general::{__NR_rt_sigreturn, __NR_sigreturn};
99
#[cfg(feature = "origin-thread")]
1010
use {
11-
alloc::boxed::Box,
12-
core::any::Any,
1311
core::ffi::c_void,
1412
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
1513
rustix::thread::RawPid,
@@ -131,6 +129,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
131129
assert_eq!(r0, 0);
132130
}
133131

132+
/// The required alignment for the stack pointer.
133+
#[cfg(feature = "origin-thread")]
134+
pub(super) const STACK_ALIGNMENT: usize = 4;
135+
134136
/// A wrapper around the Linux `clone` system call.
135137
///
136138
/// This can't be implemented in `rustix` because the child starts executing at
@@ -144,7 +146,8 @@ pub(super) unsafe fn clone(
144146
parent_tid: *mut RawPid,
145147
child_tid: *mut RawPid,
146148
newtls: *mut c_void,
147-
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
149+
fn_: extern "C" fn(),
150+
num_args: usize,
148151
) -> isize {
149152
let r0;
150153
asm!(
@@ -154,6 +157,8 @@ pub(super) unsafe fn clone(
154157

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

164169
entry = sym super::thread::entry,
165170
fn_ = in(reg) fn_,
171+
num_args = in(reg) num_args,
166172
in("r7") __NR_clone,
167173
inlateout("r0") flags as usize => r0,
168174
in("r1") child_stack,

src/arch/riscv64.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ use core::arch::asm;
77
use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
88
#[cfg(feature = "origin-thread")]
99
use {
10-
alloc::boxed::Box,
11-
core::any::Any,
1210
core::ffi::c_void,
1311
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
1412
rustix::thread::RawPid,
@@ -131,6 +129,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
131129
assert_eq!(r0, 0);
132130
}
133131

132+
/// The required alignment for the stack pointer.
133+
#[cfg(feature = "origin-thread")]
134+
pub(super) const STACK_ALIGNMENT: usize = 16;
135+
134136
/// A wrapper around the Linux `clone` system call.
135137
///
136138
/// This can't be implemented in `rustix` because the child starts executing at
@@ -144,7 +146,8 @@ pub(super) unsafe fn clone(
144146
parent_tid: *mut RawPid,
145147
child_tid: *mut RawPid,
146148
newtls: *mut c_void,
147-
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
149+
fn_: extern "C" fn(),
150+
num_args: usize,
148151
) -> isize {
149152
let r0;
150153
asm!(
@@ -153,6 +156,8 @@ pub(super) unsafe fn clone(
153156

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

163168
entry = sym super::thread::entry,
164169
fn_ = in(reg) fn_,
170+
num_args = in(reg) num_args,
165171
in("a7") __NR_clone,
166172
inlateout("a0") flags as usize => r0,
167173
in("a1") child_stack,

src/arch/x86.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
88
use linux_raw_sys::general::{__NR_rt_sigreturn, __NR_sigreturn};
99
#[cfg(feature = "origin-thread")]
1010
use {
11-
alloc::boxed::Box,
12-
core::any::Any,
1311
core::ffi::c_void,
1412
core::ptr::invalid_mut,
1513
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
@@ -136,6 +134,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
136134
assert_eq!(r0, 0);
137135
}
138136

137+
/// The required alignment for the stack pointer.
138+
#[cfg(feature = "origin-thread")]
139+
pub(super) const STACK_ALIGNMENT: usize = 16;
140+
139141
/// A wrapper around the Linux `clone` system call.
140142
///
141143
/// This can't be implemented in `rustix` because the child starts executing at
@@ -149,7 +151,8 @@ pub(super) unsafe fn clone(
149151
parent_tid: *mut RawPid,
150152
child_tid: *mut RawPid,
151153
newtls: *mut c_void,
152-
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
154+
fn_: extern "C" fn(),
155+
num_args: usize,
153156
) -> isize {
154157
let mut gs: u32 = 0;
155158
asm!("mov {0:x}, gs", inout(reg) gs);
@@ -171,6 +174,11 @@ pub(super) unsafe fn clone(
171174
user_desc.set_useable(1);
172175
let newtls: *const _ = &user_desc;
173176

177+
// Push `fn_` to the child stack, since after all the `clone` args, and
178+
// `num_args` in `ebp`, there are no more free registers.
179+
let child_stack = child_stack.cast::<*mut c_void>().sub(1);
180+
child_stack.write(fn_ as _);
181+
174182
// See the comments for x86's `syscall6` in `rustix`. Inline asm isn't
175183
// allowed to name ebp or esi as operands, so we have to jump through
176184
// extra hoops here.
@@ -179,7 +187,7 @@ pub(super) unsafe fn clone(
179187
"push esi", // Save incoming register value.
180188
"push ebp", // Save incoming register value.
181189

182-
// Pass `fn_` to the child in ebp.
190+
// Pass `num_args` to the child in `ebp`.
183191
"mov ebp, [eax+8]",
184192

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

195203
// Child thread.
196-
"push eax", // Pad for alignment.
197-
"push eax", // Pad for alignment.
198-
"push eax", // Pad for alignment.
199-
"push ebp", // Pass `fn` as the first argument.
204+
"pop edi", // Load `fn_` from the child stack.
205+
"mov esi, esp", // Snapshot the args pointer.
206+
"push eax", // Pad for stack pointer alignment.
207+
"push ebp", // Pass `num_args` as the third argument.
208+
"push esi", // Pass the args pointer as the second argument.
209+
"push edi", // Pass `fn_` as the first argument.
200210
"xor ebp, ebp", // Zero the frame address.
201211
"push eax", // Zero the return address.
202212
"jmp {entry}", // Call `entry`.
@@ -210,7 +220,7 @@ pub(super) unsafe fn clone(
210220
inout("eax") &[
211221
newtls.cast::<c_void>().cast_mut(),
212222
invalid_mut(__NR_clone as usize),
213-
fn_.cast::<c_void>()
223+
invalid_mut(num_args)
214224
] => r0,
215225
in("ebx") flags,
216226
in("ecx") child_stack,

src/arch/x86_64.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ use linux_raw_sys::general::__NR_rt_sigreturn;
1010
use linux_raw_sys::general::{__NR_mprotect, PROT_READ};
1111
#[cfg(feature = "origin-thread")]
1212
use {
13-
alloc::boxed::Box,
14-
core::any::Any,
1513
linux_raw_sys::general::{__NR_clone, __NR_exit, __NR_munmap},
1614
rustix::thread::RawPid,
1715
};
@@ -134,6 +132,10 @@ pub(super) unsafe fn relocation_mprotect_readonly(ptr: usize, len: usize) {
134132
assert_eq!(r0, 0);
135133
}
136134

135+
/// The required alignment for the stack pointer.
136+
#[cfg(feature = "origin-thread")]
137+
pub(super) const STACK_ALIGNMENT: usize = 16;
138+
137139
/// A wrapper around the Linux `clone` system call.
138140
///
139141
/// This can't be implemented in `rustix` because the child starts executing at
@@ -147,7 +149,8 @@ pub(super) unsafe fn clone(
147149
parent_tid: *mut RawPid,
148150
child_tid: *mut RawPid,
149151
newtls: *mut c_void,
150-
fn_: *mut Box<dyn FnOnce() -> Option<Box<dyn Any>> + Send>,
152+
fn_: extern "C" fn(),
153+
num_args: usize,
151154
) -> isize {
152155
let r0;
153156
asm!(
@@ -156,8 +159,10 @@ pub(super) unsafe fn clone(
156159
"jnz 0f",
157160

158161
// Child thread.
159-
"xor ebp, ebp", // Zero the frame address.
160162
"mov rdi, r9", // Pass `fn_` as the first argument.
163+
"mov rsi, rsp", // Pass the args pointer as the second argument.
164+
"mov rdx, r12", // Pass `num_args` as the third argument.
165+
"xor ebp, ebp", // Zero the frame address.
161166
"push rax", // Zero the return address.
162167
"jmp {entry}", // Call `entry`.
163168

@@ -172,6 +177,7 @@ pub(super) unsafe fn clone(
172177
in("r10") child_tid,
173178
in("r8") newtls,
174179
in("r9") fn_,
180+
in("r12") num_args,
175181
lateout("rcx") _,
176182
lateout("r11") _,
177183
options(nostack)

0 commit comments

Comments
 (0)