Skip to content

Commit 3990b31

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

25 files changed

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

kj-rs/own.rs

Lines changed: 36 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,8 +55,8 @@ 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

2962
/// Public-facing Own api

0 commit comments

Comments
 (0)