Skip to content

Commit 2944364

Browse files
committed
[workerd-cxx] add kj::Maybe to kj-rs and workerd-cxx
1 parent 440afe9 commit 2944364

25 files changed

+745
-14
lines changed

gen/src/write.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
233233
Type::SliceRef(_) => out.builtin.rust_slice = true,
234234
Type::Array(_) => out.include.array = true,
235235
Type::Ref(_) | Type::Void(_) | Type::Ptr(_) => {}
236-
Type::Future(_) | Type::Own(_) => out.include.kj_rs = true,
236+
Type::Future(_) | Type::Maybe(_) | Type::Own(_) => out.include.kj_rs = true,
237237
}
238238
}
239239
}
@@ -1263,6 +1263,11 @@ fn write_type(out: &mut OutFile, ty: &Type) {
12631263
write_type(out, &ptr.inner);
12641264
write!(out, ">");
12651265
}
1266+
Type::Maybe(ptr) => {
1267+
write!(out, "::kj::Maybe<");
1268+
write_type(out, &ptr.inner);
1269+
write!(out, ">");
1270+
}
12661271
Type::CxxVector(ty) => {
12671272
write!(out, "::std::vector<");
12681273
write_type(out, &ty.inner);
@@ -1357,6 +1362,7 @@ fn write_space_after_type(out: &mut OutFile, ty: &Type) {
13571362
| Type::SharedPtr(_)
13581363
| Type::WeakPtr(_)
13591364
| Type::Str(_)
1365+
| Type::Maybe(_)
13601366
| Type::CxxVector(_)
13611367
| Type::RustVec(_)
13621368
| Type::SliceRef(_)
@@ -1431,6 +1437,7 @@ fn write_generic_instantiations(out: &mut OutFile) {
14311437
ImplKey::RustVec(ident) => write_rust_vec_extern(out, ident),
14321438
ImplKey::UniquePtr(ident) => write_unique_ptr(out, ident),
14331439
ImplKey::Own(ident) => write_kj_own(out, ident),
1440+
ImplKey::Maybe(ident) => write_kj_maybe(out, ident),
14341441
ImplKey::SharedPtr(ident) => write_shared_ptr(out, ident),
14351442
ImplKey::WeakPtr(ident) => write_weak_ptr(out, ident),
14361443
ImplKey::CxxVector(ident) => write_cxx_vector(out, ident),
@@ -1669,6 +1676,17 @@ fn write_kj_own(out: &mut OutFile, key: NamedImplKey) {
16691676
);
16701677
}
16711678

1679+
// cxx boilerplate function for possible drop trait
1680+
// TODO: Add asserts and function generation
1681+
fn write_kj_maybe(out: &mut OutFile, key: NamedImplKey) {
1682+
let ident = key.rust;
1683+
let resolve = out.types.resolve(ident);
1684+
let _inner = resolve.name.to_fully_qualified();
1685+
1686+
out.include.utility = true;
1687+
out.include.kj_rs = true;
1688+
}
1689+
16721690
fn write_unique_ptr(out: &mut OutFile, key: NamedImplKey) {
16731691
let ty = UniquePtr::Ident(key.rust);
16741692
write_unique_ptr_common(out, ty);

kj-rs/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use awaiter::WakerRef;
44
pub use crate::ffi::KjWaker;
55
pub use awaiter::PromiseAwaiter;
66
pub use future::FuturePollStatus;
7+
pub use maybe::repr::Maybe;
78
pub use own::repr::Own;
89
pub use promise::KjPromise;
910
pub use promise::KjPromiseNodeImpl;
@@ -13,12 +14,14 @@ pub use promise::new_callbacks_promise_future;
1314

1415
mod awaiter;
1516
mod future;
17+
pub mod maybe;
1618
mod own;
1719
mod promise;
1820
mod waker;
1921

2022
pub mod repr {
2123
pub use crate::future::repr::*;
24+
pub use crate::maybe::repr::*;
2225
pub use crate::own::repr::*;
2326
}
2427

kj-rs/maybe.rs

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
use repr::Maybe;
2+
use std::mem::MaybeUninit;
3+
4+
/// # Safety
5+
/// This trait should only be implemented in `workerd-cxx` on types
6+
/// which contain a specialization of `kj::Maybe` that needs to be represented in
7+
/// Rust.
8+
unsafe trait HasNiche: Sized {
9+
fn is_niche(value: &MaybeUninit<Self>) -> bool;
10+
}
11+
12+
// In `kj`, null pointers are considered niche, and `kj::none` if null
13+
unsafe impl<T> HasNiche for *const T {
14+
fn is_niche(value: &MaybeUninit<*const T>) -> bool {
15+
unsafe { value.assume_init().is_null() }
16+
}
17+
}
18+
19+
unsafe impl<T> HasNiche for *mut T {
20+
fn is_niche(value: &MaybeUninit<*mut T>) -> bool {
21+
unsafe { value.assume_init().is_null() }
22+
}
23+
}
24+
25+
// In Rust, references are not allowed to be null, so a null `MaybeUninit<&T>` is a niche
26+
unsafe impl<T> HasNiche for &T {
27+
fn is_niche(value: &MaybeUninit<&T>) -> bool {
28+
unsafe {
29+
std::mem::transmute_copy::<MaybeUninit<&T>, MaybeUninit<*const T>>(value)
30+
// This is to avoid potentially zero-initalizing a reference, which is always undefined behavior
31+
.assume_init()
32+
.is_null()
33+
}
34+
}
35+
}
36+
37+
unsafe impl<T> HasNiche for &mut T {
38+
fn is_niche(value: &MaybeUninit<&mut T>) -> bool {
39+
unsafe {
40+
std::mem::transmute_copy::<MaybeUninit<&mut T>, MaybeUninit<*mut T>>(value)
41+
// This is to avoid potentially zero-initalizing a reference, which is always undefined behavior
42+
.assume_init()
43+
.is_null()
44+
}
45+
}
46+
}
47+
48+
// In `kj`, `kj::Own<T>` are considered `none` in a `Maybe` if the pointer is null
49+
unsafe impl<T> HasNiche for crate::repr::Own<T> {
50+
fn is_niche(value: &MaybeUninit<crate::repr::Own<T>>) -> bool {
51+
unsafe { value.assume_init_ref().as_ptr().is_null() }
52+
}
53+
}
54+
55+
/// Trait that is used as the bounds for what can be in a Maybe
56+
///
57+
/// # Safety
58+
/// This trait should only be implemented from macro expansion and should
59+
/// never be manually implemented.
60+
pub unsafe trait MaybeItem: Sized {
61+
type Discriminant: Copy;
62+
fn is_some(value: &Maybe<Self>) -> bool;
63+
fn is_none(value: &Maybe<Self>) -> bool;
64+
fn from_option(value: Option<Self>) -> Maybe<Self>;
65+
fn drop_in_place(value: &mut Maybe<Self>) {
66+
if <Self as MaybeItem>::is_some(value) {
67+
unsafe {
68+
value.some.assume_init_drop();
69+
}
70+
}
71+
}
72+
}
73+
74+
/// Macro to implement [`MaybeItem`] for `T` which implment [`HasNiche`]
75+
macro_rules! impl_maybe_item_for_has_niche {
76+
($ty:ty) => {
77+
unsafe impl<T> MaybeItem for $ty {
78+
type Discriminant = ();
79+
80+
fn is_some(value: &Maybe<Self>) -> bool {
81+
!<$ty as HasNiche>::is_niche(&value.some)
82+
}
83+
84+
fn is_none(value: &Maybe<Self>) -> bool {
85+
<$ty as HasNiche>::is_niche(&value.some)
86+
}
87+
88+
fn from_option(value: Option<Self>) -> Maybe<Self> {
89+
match value {
90+
None => Maybe {
91+
is_set: (),
92+
some: MaybeUninit::zeroed(),
93+
},
94+
Some(val) => Maybe {
95+
is_set: (),
96+
some: MaybeUninit::new(val),
97+
}
98+
}
99+
}
100+
}
101+
};
102+
($ty:ty, $($tail:ty),+) => {
103+
impl_maybe_item_for_has_niche!($ty);
104+
impl_maybe_item_for_has_niche!($($tail),*);
105+
};
106+
}
107+
108+
/// Macro to implement [`MaybeItem`] for primitives
109+
macro_rules! impl_maybe_item_for_primitive {
110+
($ty:ty) => {
111+
unsafe impl MaybeItem for $ty {
112+
type Discriminant = bool;
113+
114+
fn is_some(value: &Maybe<Self>) -> bool {
115+
value.is_set
116+
}
117+
118+
fn is_none(value: &Maybe<Self>) -> bool {
119+
!value.is_set
120+
}
121+
122+
fn from_option(value: Option<Self>) -> Maybe<Self> {
123+
match value {
124+
None => Maybe {
125+
is_set: false,
126+
some: MaybeUninit::zeroed(),
127+
},
128+
Some(val) => Maybe {
129+
is_set: true,
130+
some: MaybeUninit::new(val),
131+
}
132+
}
133+
}
134+
}
135+
};
136+
($ty:ty, $($tail:ty),+) => {
137+
impl_maybe_item_for_primitive!($ty);
138+
impl_maybe_item_for_primitive!($($tail),*);
139+
};
140+
}
141+
142+
impl_maybe_item_for_has_niche!(crate::Own<T>, &T, &mut T, *const T, *mut T);
143+
impl_maybe_item_for_primitive!(
144+
u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize, bool
145+
);
146+
147+
pub(crate) mod repr {
148+
use super::MaybeItem;
149+
use static_assertions::assert_eq_size;
150+
use std::fmt::{Debug, Display};
151+
use std::mem::MaybeUninit;
152+
153+
/// A [`Maybe`] represents bindings to the `kj::Maybe` class.
154+
/// It is an optional type, but represented using a struct, for alignment with kj.
155+
///
156+
/// # Layout
157+
/// In kj, `Maybe` has 3 specializations, one without niche value optimization, and
158+
/// two with it. In order to maintain an identical layout in Rust, we include an associated type
159+
/// in the [`MaybeItem`] trait, which determines the discriminant of the `Maybe<T: MaybeItem>`.
160+
///
161+
/// ## Niche Value Optimization
162+
/// This discriminant is used in tandem with the [`crate::maybe::HasNiche`] to implement
163+
/// [`MaybeItem`] properly for values which have a niche, which uses a discriminant of [`()`],
164+
/// the unit type. All other types use [`bool`].
165+
#[repr(C)]
166+
pub struct Maybe<T: MaybeItem> {
167+
pub(super) is_set: T::Discriminant,
168+
pub(super) some: MaybeUninit<T>,
169+
}
170+
171+
assert_eq_size!(Maybe<isize>, [usize; 2]);
172+
assert_eq_size!(Maybe<&isize>, usize);
173+
assert_eq_size!(Maybe<crate::Own<isize>>, [usize; 2]);
174+
assert_eq_size!(Maybe<*const isize>, usize);
175+
176+
impl<T: MaybeItem> Maybe<T> {
177+
/// # Safety
178+
/// This function shouldn't be used except by macro generation.
179+
pub unsafe fn is_set(&self) -> T::Discriminant {
180+
self.is_set
181+
}
182+
183+
/// # Safety
184+
/// This function shouldn't be used except by macro generation.
185+
pub unsafe fn from_parts_unchecked(
186+
is_set: T::Discriminant,
187+
some: MaybeUninit<T>,
188+
) -> Maybe<T> {
189+
Maybe { is_set, some }
190+
}
191+
192+
pub fn is_some(&self) -> bool {
193+
T::is_some(self)
194+
}
195+
196+
pub fn is_none(&self) -> bool {
197+
T::is_none(self)
198+
}
199+
}
200+
201+
impl<T: MaybeItem> From<Maybe<T>> for Option<T> {
202+
fn from(value: Maybe<T>) -> Self {
203+
if value.is_some() {
204+
// We can't move out of value so we copy it and forget it in
205+
// order to perform a "manual" move out of value
206+
let ret = unsafe { Some(value.some.assume_init_read()) };
207+
std::mem::forget(value);
208+
ret
209+
} else {
210+
None
211+
}
212+
}
213+
}
214+
215+
impl<T: MaybeItem> From<Option<T>> for Maybe<T> {
216+
fn from(value: Option<T>) -> Self {
217+
<T as MaybeItem>::from_option(value)
218+
}
219+
}
220+
221+
impl<T: MaybeItem + Debug> Debug for Maybe<T> {
222+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223+
if self.is_none() {
224+
write!(f, "kj::None")
225+
} else {
226+
write!(f, "kj::Some({:?})", unsafe { self.some.assume_init_ref() })
227+
}
228+
}
229+
}
230+
231+
impl<T: MaybeItem + Display> Display for Maybe<T> {
232+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233+
if self.is_none() {
234+
write!(f, "kj::None")
235+
} else {
236+
write!(f, "kj::Some({})", unsafe { self.some.assume_init_ref() })
237+
}
238+
}
239+
}
240+
241+
impl<T: MaybeItem> Drop for Maybe<T> {
242+
fn drop(&mut self) {
243+
T::drop_in_place(self);
244+
}
245+
}
246+
}

kj-rs/own.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,51 @@
11
//! The `workerd-cxx` module containing the [`Own<T>`] type, which is bindings to the `kj::Own<T>` C++ type
22
33
use static_assertions::{assert_eq_align, assert_eq_size};
4+
use std::{fmt, marker::PhantomData};
45

56
assert_eq_size!(repr::Own<()>, [*const (); 2]);
67
assert_eq_align!(repr::Own<()>, *const ());
78

9+
/// When we want to use an `Own`, we want the guarantee of being not null only
10+
/// in direct `Own<T>`, not Maybe<Own<T>>, and using a [`NonNull`] in `Own`
11+
/// but allowing Nulls for Niche Value Optimization is undefined behavior.
12+
#[repr(transparent)]
13+
pub(crate) struct NonNullExceptMaybe<T>(pub(crate) *mut T, PhantomData<T>);
14+
15+
impl<T> NonNullExceptMaybe<T> {
16+
pub fn as_ptr(&self) -> *const T {
17+
self.0.cast()
18+
}
19+
20+
pub unsafe fn as_ref(&self) -> &T {
21+
// Safety:
22+
// This value will only be null when in a [`Maybe<T>`], which does niche value optimization
23+
// for a null pointer, so the inner [`Own<T>`] can never be accessed if it is null
24+
unsafe { self.0.as_ref().unwrap() }
25+
}
26+
27+
pub unsafe fn as_mut(&mut self) -> &mut T {
28+
// Safety:
29+
// This value will only be null when in a [`Maybe<T>`], which does niche value optimization
30+
// for a null pointer, so the inner [`Own<T>`] can never be accessed if it is null
31+
unsafe { self.0.as_mut().unwrap() }
32+
}
33+
}
34+
35+
impl<T> fmt::Pointer for NonNullExceptMaybe<T> {
36+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37+
fmt::Pointer::fmt(&self.0, f)
38+
}
39+
}
40+
841
pub mod repr {
42+
use super::NonNullExceptMaybe;
943
use std::ffi::c_void;
1044
use std::fmt::{self, Debug, Display};
1145
use std::hash::{Hash, Hasher};
1246
use std::ops::Deref;
1347
use std::ops::DerefMut;
1448
use std::pin::Pin;
15-
use std::ptr::NonNull;
1649

1750
/// A [`Own<T>`] represents the `kj::Own<T>`. It is a smart pointer to an opaque C++ type.
1851
/// Safety:
@@ -22,10 +55,11 @@ pub mod repr {
2255
/// to Rust
2356
#[repr(C)]
2457
pub struct Own<T> {
25-
disposer: *const c_void,
26-
ptr: NonNull<T>,
58+
pub(crate) disposer: *const c_void,
59+
pub(crate) ptr: NonNullExceptMaybe<T>,
2760
}
2861

62+
/// This type allows me to expose the same API that [`NonNull`] does while maintaining the invariant
2963
/// Public-facing Own api
3064
impl<T> Own<T> {
3165
/// Returns a mutable pinned reference to the object owned by this [`Own`]

0 commit comments

Comments
 (0)