Skip to content

Commit 8a6ffad

Browse files
authored
Evaluate the layouts for the tasks at compile time (#30)
1 parent e8b536c commit 8a6ffad

File tree

6 files changed

+141
-52
lines changed

6 files changed

+141
-52
lines changed

.clippy.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
msrv = "1.39"
1+
msrv = "1.47"

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ jobs:
4545
matrix:
4646
# When updating this, the reminder to update the minimum supported
4747
# Rust version in Cargo.toml and .clippy.toml.
48-
rust: ['1.39']
48+
rust: ['1.47']
4949
steps:
5050
- uses: actions/checkout@v3
5151
- name: Install Rust

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ name = "async-task"
66
version = "4.2.0"
77
authors = ["Stjepan Glavina <stjepang@gmail.com>"]
88
edition = "2018"
9-
rust-version = "1.39"
9+
rust-version = "1.47"
1010
license = "Apache-2.0 OR MIT"
1111
repository = "https://github.com/smol-rs/async-task"
1212
description = "Task abstraction for building executors"

src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@
7474

7575
extern crate alloc;
7676

77+
/// We can't use `?` in const contexts yet, so this macro acts
78+
/// as a workaround.
79+
macro_rules! leap {
80+
($x: expr) => {{
81+
match ($x) {
82+
Some(val) => val,
83+
None => return None,
84+
}
85+
}};
86+
}
87+
7788
mod header;
7889
mod raw;
7990
mod runnable;

src/raw.rs

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use alloc::alloc::Layout;
1+
use alloc::alloc::Layout as StdLayout;
22
use core::cell::UnsafeCell;
33
use core::future::Future;
44
use core::mem::{self, ManuallyDrop};
@@ -9,7 +9,7 @@ use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
99

1010
use crate::header::Header;
1111
use crate::state::*;
12-
use crate::utils::{abort, abort_on_panic, extend};
12+
use crate::utils::{abort, abort_on_panic, max, Layout};
1313
use crate::Runnable;
1414

1515
/// The vtable for a task.
@@ -45,7 +45,7 @@ pub(crate) struct TaskVTable {
4545
#[derive(Clone, Copy)]
4646
pub(crate) struct TaskLayout {
4747
/// Memory layout of the whole task.
48-
pub(crate) layout: Layout,
48+
pub(crate) layout: StdLayout,
4949

5050
/// Offset into the task at which the schedule function is stored.
5151
pub(crate) offset_s: usize,
@@ -80,6 +80,39 @@ impl<F, T, S> Clone for RawTask<F, T, S> {
8080
}
8181
}
8282

83+
impl<F, T, S> RawTask<F, T, S> {
84+
const TASK_LAYOUT: Option<TaskLayout> = Self::eval_task_layout();
85+
86+
/// Computes the memory layout for a task.
87+
#[inline]
88+
const fn eval_task_layout() -> Option<TaskLayout> {
89+
// Compute the layouts for `Header`, `S`, `F`, and `T`.
90+
let layout_header = Layout::new::<Header>();
91+
let layout_s = Layout::new::<S>();
92+
let layout_f = Layout::new::<F>();
93+
let layout_r = Layout::new::<T>();
94+
95+
// Compute the layout for `union { F, T }`.
96+
let size_union = max(layout_f.size(), layout_r.size());
97+
let align_union = max(layout_f.align(), layout_r.align());
98+
let layout_union = Layout::from_size_align(size_union, align_union);
99+
100+
// Compute the layout for `Header` followed `S` and `union { F, T }`.
101+
let layout = layout_header;
102+
let (layout, offset_s) = leap!(layout.extend(layout_s));
103+
let (layout, offset_union) = leap!(layout.extend(layout_union));
104+
let offset_f = offset_union;
105+
let offset_r = offset_union;
106+
107+
Some(TaskLayout {
108+
layout: unsafe { layout.into_std() },
109+
offset_s,
110+
offset_f,
111+
offset_r,
112+
})
113+
}
114+
}
115+
83116
impl<F, T, S> RawTask<F, T, S>
84117
where
85118
F: Future<Output = T>,
@@ -97,7 +130,9 @@ where
97130
/// It is assumed that initially only the `Runnable` and the `Task` exist.
98131
pub(crate) fn allocate(future: F, schedule: S) -> NonNull<()> {
99132
// Compute the layout of the task for allocation. Abort if the computation fails.
100-
let task_layout = abort_on_panic(|| Self::task_layout());
133+
//
134+
// n.b. notgull: task_layout now automatically aborts instead of panicking
135+
let task_layout = Self::task_layout();
101136

102137
unsafe {
103138
// Allocate enough space for the entire task.
@@ -149,32 +184,12 @@ where
149184
}
150185
}
151186

152-
/// Returns the memory layout for a task.
187+
/// Returns the layout of the task.
153188
#[inline]
154189
fn task_layout() -> TaskLayout {
155-
// Compute the layouts for `Header`, `S`, `F`, and `T`.
156-
let layout_header = Layout::new::<Header>();
157-
let layout_s = Layout::new::<S>();
158-
let layout_f = Layout::new::<F>();
159-
let layout_r = Layout::new::<T>();
160-
161-
// Compute the layout for `union { F, T }`.
162-
let size_union = layout_f.size().max(layout_r.size());
163-
let align_union = layout_f.align().max(layout_r.align());
164-
let layout_union = unsafe { Layout::from_size_align_unchecked(size_union, align_union) };
165-
166-
// Compute the layout for `Header` followed `S` and `union { F, T }`.
167-
let layout = layout_header;
168-
let (layout, offset_s) = extend(layout, layout_s);
169-
let (layout, offset_union) = extend(layout, layout_union);
170-
let offset_f = offset_union;
171-
let offset_r = offset_union;
172-
173-
TaskLayout {
174-
layout,
175-
offset_s,
176-
offset_f,
177-
offset_r,
190+
match Self::TASK_LAYOUT {
191+
Some(tl) => tl,
192+
None => abort(),
178193
}
179194
}
180195

src/utils.rs

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use core::alloc::Layout;
1+
use core::alloc::Layout as StdLayout;
22
use core::mem;
33

44
/// Aborts the process.
@@ -36,29 +36,92 @@ pub(crate) fn abort_on_panic<T>(f: impl FnOnce() -> T) -> T {
3636
t
3737
}
3838

39-
/// Returns the layout for `a` followed by `b` and the offset of `b`.
40-
///
41-
/// This function was adapted from the currently unstable `Layout::extend()`:
42-
/// https://doc.rust-lang.org/nightly/std/alloc/struct.Layout.html#method.extend
43-
#[inline]
44-
pub(crate) fn extend(a: Layout, b: Layout) -> (Layout, usize) {
45-
let new_align = a.align().max(b.align());
46-
let pad = padding_needed_for(a, b.align());
39+
/// A version of `alloc::alloc::Layout` that can be used in the const
40+
/// position.
41+
#[derive(Clone, Copy, Debug)]
42+
pub(crate) struct Layout {
43+
size: usize,
44+
align: usize,
45+
}
4746

48-
let offset = a.size().checked_add(pad).unwrap();
49-
let new_size = offset.checked_add(b.size()).unwrap();
47+
impl Layout {
48+
/// Creates a new `Layout` with the given size and alignment.
49+
#[inline]
50+
pub(crate) const fn from_size_align(size: usize, align: usize) -> Self {
51+
Self { size, align }
52+
}
53+
54+
/// Creates a new `Layout` for the given sized type.
55+
#[inline]
56+
pub(crate) const fn new<T>() -> Self {
57+
Self::from_size_align(mem::size_of::<T>(), mem::align_of::<T>())
58+
}
5059

51-
let layout = Layout::from_size_align(new_size, new_align).unwrap();
52-
(layout, offset)
60+
/// Convert this into the standard library's layout type.
61+
///
62+
/// # Safety
63+
///
64+
/// - `align` must be non-zero and a power of two.
65+
/// - When rounded up to the nearest multiple of `align`, the size
66+
/// must not overflow.
67+
#[inline]
68+
pub(crate) const unsafe fn into_std(self) -> StdLayout {
69+
StdLayout::from_size_align_unchecked(self.size, self.align)
70+
}
71+
72+
/// Get the alignment of this layout.
73+
#[inline]
74+
pub(crate) const fn align(&self) -> usize {
75+
self.align
76+
}
77+
78+
/// Get the size of this layout.
79+
#[inline]
80+
pub(crate) const fn size(&self) -> usize {
81+
self.size
82+
}
83+
84+
/// Returns the layout for `a` followed by `b` and the offset of `b`.
85+
///
86+
/// This function was adapted from the currently unstable `Layout::extend()`:
87+
/// https://doc.rust-lang.org/nightly/std/alloc/struct.Layout.html#method.extend
88+
#[inline]
89+
pub(crate) const fn extend(self, other: Layout) -> Option<(Layout, usize)> {
90+
let new_align = max(self.align(), other.align());
91+
let pad = self.padding_needed_for(other.align());
92+
93+
let offset = leap!(self.size().checked_add(pad));
94+
let new_size = leap!(offset.checked_add(other.size()));
95+
96+
// return None if any of the following are true:
97+
// - align is 0 (implied false by is_power_of_two())
98+
// - align is not a power of 2
99+
// - size rounded up to align overflows
100+
if !new_align.is_power_of_two() || new_size > core::usize::MAX - (new_align - 1) {
101+
return None;
102+
}
103+
104+
let layout = Layout::from_size_align(new_size, new_align);
105+
Some((layout, offset))
106+
}
107+
108+
/// Returns the padding after `layout` that aligns the following address to `align`.
109+
///
110+
/// This function was adapted from the currently unstable `Layout::padding_needed_for()`:
111+
/// https://doc.rust-lang.org/nightly/std/alloc/struct.Layout.html#method.padding_needed_for
112+
#[inline]
113+
pub(crate) const fn padding_needed_for(self, align: usize) -> usize {
114+
let len = self.size();
115+
let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
116+
len_rounded_up.wrapping_sub(len)
117+
}
53118
}
54119

55-
/// Returns the padding after `layout` that aligns the following address to `align`.
56-
///
57-
/// This function was adapted from the currently unstable `Layout::padding_needed_for()`:
58-
/// https://doc.rust-lang.org/nightly/std/alloc/struct.Layout.html#method.padding_needed_for
59120
#[inline]
60-
pub(crate) fn padding_needed_for(layout: Layout, align: usize) -> usize {
61-
let len = layout.size();
62-
let len_rounded_up = len.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1);
63-
len_rounded_up.wrapping_sub(len)
121+
pub(crate) const fn max(left: usize, right: usize) -> usize {
122+
if left > right {
123+
left
124+
} else {
125+
right
126+
}
64127
}

0 commit comments

Comments
 (0)