Skip to content

Commit e674fb2

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

25 files changed

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

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)