Skip to content

Commit 79841ca

Browse files
committed
[project] Initial commit
TODO: Could we support unsized types so long as we never call offset_of! on the trailing unsized field? gherrit-pr-id: I54eb863c96f056738f58727fc0fec4e1630bf97a
1 parent af67d9a commit 79841ca

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ pub mod layout;
333333
mod macros;
334334
#[doc(hidden)]
335335
pub mod pointer;
336+
#[doc(hidden)]
337+
pub mod project;
336338
mod r#ref;
337339
// TODO(#252): If we make this pub, come up with a better name.
338340
mod wrappers;

src/project.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright 2024 The Fuchsia Authors
2+
//
3+
// Licensed under the 2-Clause BSD License <LICENSE-BSD or
4+
// https://opensource.org/license/bsd-2-clause>, Apache License, Version 2.0
5+
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
6+
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
7+
// This file may not be copied, modified, or distributed except according to
8+
// those terms.
9+
10+
// NOTE: `NAME_HASH` is not semver-stable - it just needs to be consistent
11+
// within a single compilation.
12+
//
13+
// TODO: `#[diagnostic::on_unimplemented]`
14+
#[doc(hidden)]
15+
pub unsafe trait Field<const NAME_HASH: u128> {
16+
type Type;
17+
const OFFSET: isize;
18+
}
19+
20+
pub unsafe trait Projectable {
21+
type Of<T>;
22+
type Inner;
23+
}
24+
25+
unsafe impl<T> Projectable for core::mem::ManuallyDrop<T> {
26+
type Of<U> = core::mem::ManuallyDrop<U>;
27+
type Inner = T;
28+
}
29+
30+
// TODO: Use `Ptr` to make this generic over `&`, `&mut`, `Box`, etc.
31+
#[doc(hidden)]
32+
#[allow(clippy::must_use_candidate)]
33+
#[inline(never)]
34+
pub const fn project<P, const NAME_HASH: u128>(
35+
ptr: *const P,
36+
) -> *const P::Of<<P::Inner as Field<{ NAME_HASH }>>::Type>
37+
where
38+
P: Projectable,
39+
P::Inner: Field<{ NAME_HASH }>,
40+
{
41+
// NOTE: Doing the actual cast inside this function makes the `project!`
42+
// macro simpler, but it also means that we can't handle both const code and
43+
// unsized types at the same time. In order to be const, we can't call trait
44+
// methods, but in order to support unsized types, we'd need to provide a
45+
// trait method to perform certain pointer casts (if we were using unsized
46+
// types, Rust wouldn't be smart enough to know that all of these casts had
47+
// the same vtable kinds).
48+
//
49+
// An alternative is to perform the casts in the macro body itself, but that
50+
// turns out to be unsound. In particular, despite `Field` guaranteeing that
51+
// `P::Inner` has a field of the given name, if we use the `addr_of!` macro
52+
// to perform field projection, it could still be the case that:
53+
// - The field is private, and so inaccessible
54+
// - The type implements `Deref`
55+
// - The `Deref::Target` type has a field of the same name
56+
//
57+
// In this case, implicit deref still happens despite our fancy `Field`
58+
// machinery. This is demonstrated in practice in [1].
59+
//
60+
// Here, we perform the casts manually using the associated `OFFSET` const,
61+
// which is not vulnerable to this issue. Note that we *could* use the
62+
// `OFFSET` const directly in the macro instead, but it would still not
63+
// allow us to support unsized types, as `offset_of!` doesn't support taking
64+
// the offset of unsized fields.
65+
//
66+
// [1] https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=978de749dca4f67ed24707ac049a5e4f
67+
let inner = ptr.cast::<P::Inner>();
68+
let field_inner = unsafe { inner.byte_offset(<P::Inner as Field<{ NAME_HASH }>>::OFFSET) };
69+
let field_inner = field_inner.cast::<<P::Inner as Field<{ NAME_HASH }>>::Type>();
70+
field_inner.cast()
71+
}
72+
73+
// TODO: Implement a stronger hash function so we can basically just ignore
74+
// collisions. If users encounter collisions in practice, we can just deal with
75+
// it then, publish a new version, and tell them to upgrade.
76+
#[doc(hidden)]
77+
#[must_use]
78+
#[inline(always)]
79+
#[allow(clippy::as_conversions, clippy::indexing_slicing, clippy::arithmetic_side_effects)]
80+
pub const fn hash_field_name(field_name: &str) -> u128 {
81+
let field_name = field_name.as_bytes();
82+
let mut hash = 0u64;
83+
let mut i = 0;
84+
while i < field_name.len() {
85+
const K: u64 = 0x517cc1b727220a95;
86+
hash = (hash.rotate_left(5) ^ (field_name[i] as u64)).wrapping_mul(K);
87+
i += 1;
88+
}
89+
hash as u128
90+
}
91+
92+
#[macro_export]
93+
macro_rules! projectable {
94+
// It's unlikely that anyone would ever call this macro on a field-less
95+
// struct, but might as well support it for completeness.
96+
($(#[$attr:meta])* $vis:vis struct $name:ident;) => {};
97+
($(#[$attr:meta])* $vis:vis struct $name:ident {
98+
$($field:ident: $field_ty:ty),* $(,)?
99+
}) => {
100+
$(#[$attr])*
101+
$vis struct $name {
102+
$($field: $field_ty,)*
103+
}
104+
105+
$(
106+
$crate::projectable!(@inner $name . $field: $field_ty);
107+
)*
108+
};
109+
($(#[$attr:meta])* $vis:vis struct $name:ident (
110+
$($field_ty:ty),* $(,)?
111+
);) => {
112+
$(#[$attr])*
113+
$vis struct $name (
114+
$($field_ty,)*
115+
);
116+
117+
// TODO: How to generate the idents `0`, `1`, etc in order to call
118+
// `projectable!(@inner ...)`?
119+
};
120+
(@inner $name:ident . $field:ident: $field_ty:ty) => {
121+
unsafe impl $crate::project::Field<{
122+
$crate::project::hash_field_name(stringify!($field))
123+
}> for $name {
124+
type Type = $field_ty;
125+
const OFFSET: isize = $crate::util::macro_util::core_reexport::mem::offset_of!($name, $field) as isize;
126+
}
127+
};
128+
}
129+
130+
#[macro_export]
131+
macro_rules! project {
132+
($outer:ident $(. $field:ident)*) => { $crate::project!(($outer) . $field) };
133+
(($outer:expr) . $field:ident) => {{
134+
let outer: &_ = $outer;
135+
let outer: *const _ = outer;
136+
let field = $crate::project::project::<_, {
137+
$crate::project::hash_field_name(stringify!($field))
138+
}>(outer);
139+
unsafe { &*field }
140+
}};
141+
(($outer:expr) $(. $field:ident)+) => {{
142+
let outer = $outer;
143+
$(
144+
let outer = project!((outer).$field);
145+
)+
146+
outer
147+
}};
148+
}
149+
150+
#[cfg(test)]
151+
mod tests {
152+
projectable! {
153+
struct Foo {
154+
a: u8,
155+
b: u16,
156+
c: u32,
157+
}
158+
}
159+
160+
#[test]
161+
fn test_project() {
162+
use core::mem::ManuallyDrop;
163+
164+
let f = ManuallyDrop::new(Foo { a: 0, b: 1, c: 2 });
165+
let p = project!((&f).c);
166+
assert_eq!(p, &ManuallyDrop::new(2));
167+
}
168+
}

0 commit comments

Comments
 (0)