|
| 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 | + let inner = ptr.cast::<P::Inner>(); |
| 42 | + let field_inner = unsafe { inner.byte_offset(<P::Inner as Field<{ NAME_HASH }>>::OFFSET) }; |
| 43 | + let field_inner = field_inner.cast::<<P::Inner as Field<{ NAME_HASH }>>::Type>(); |
| 44 | + field_inner.cast() |
| 45 | +} |
| 46 | + |
| 47 | +// TODO: Implement a stronger hash function so we can basically just ignore |
| 48 | +// collisions. If users encounter collisions in practice, we can just deal with |
| 49 | +// it then, publish a new version, and tell them to upgrade. |
| 50 | +#[doc(hidden)] |
| 51 | +#[must_use] |
| 52 | +#[inline(always)] |
| 53 | +#[allow(clippy::as_conversions, clippy::indexing_slicing, clippy::arithmetic_side_effects)] |
| 54 | +pub const fn hash_field_name(field_name: &str) -> u128 { |
| 55 | + let field_name = field_name.as_bytes(); |
| 56 | + let mut hash = 0u64; |
| 57 | + let mut i = 0; |
| 58 | + while i < field_name.len() { |
| 59 | + const K: u64 = 0x517cc1b727220a95; |
| 60 | + hash = (hash.rotate_left(5) ^ (field_name[i] as u64)).wrapping_mul(K); |
| 61 | + i += 1; |
| 62 | + } |
| 63 | + hash as u128 |
| 64 | +} |
| 65 | + |
| 66 | +#[macro_export] |
| 67 | +macro_rules! projectable { |
| 68 | + // It's unlikely that anyone would ever call this macro on a field-less |
| 69 | + // struct, but might as well support it for completeness. |
| 70 | + ($(#[$attr:meta])* $vis:vis struct $name:ident;) => {}; |
| 71 | + ($(#[$attr:meta])* $vis:vis struct $name:ident { |
| 72 | + $($field:ident: $field_ty:ty),* $(,)? |
| 73 | + }) => { |
| 74 | + $(#[$attr])* |
| 75 | + $vis struct $name { |
| 76 | + $($field: $field_ty,)* |
| 77 | + } |
| 78 | + |
| 79 | + $( |
| 80 | + $crate::projectable!(@inner $name . $field: $field_ty); |
| 81 | + )* |
| 82 | + }; |
| 83 | + ($(#[$attr:meta])* $vis:vis struct $name:ident ( |
| 84 | + $($field_ty:ty),* $(,)? |
| 85 | + );) => { |
| 86 | + $(#[$attr])* |
| 87 | + $vis struct $name ( |
| 88 | + $($field_ty,)* |
| 89 | + ); |
| 90 | + |
| 91 | + // TODO: How to generate the idents `0`, `1`, etc in order to call |
| 92 | + // `projectable!(@inner ...)`? |
| 93 | + }; |
| 94 | + (@inner $name:ident . $field:ident: $field_ty:ty) => { |
| 95 | + unsafe impl $crate::project::Field<{ |
| 96 | + $crate::project::hash_field_name(stringify!($field)) |
| 97 | + }> for $name { |
| 98 | + type Type = $field_ty; |
| 99 | + const OFFSET: isize = $crate::util::macro_util::core_reexport::mem::offset_of!($name, $field) as isize; |
| 100 | + } |
| 101 | + }; |
| 102 | +} |
| 103 | + |
| 104 | +#[macro_export] |
| 105 | +macro_rules! project { |
| 106 | + ($outer:ident $(. $field:ident)*) => { $crate::project!(($outer) . $field) }; |
| 107 | + (($outer:expr) . $field:ident) => {{ |
| 108 | + let outer: &_ = $outer; |
| 109 | + let outer: *const _ = outer; |
| 110 | + let field = $crate::project::project::<_, { |
| 111 | + $crate::project::hash_field_name(stringify!($field)) |
| 112 | + }>(outer); |
| 113 | + unsafe { &*field } |
| 114 | + }}; |
| 115 | + (($outer:expr) $(. $field:ident)+) => {{ |
| 116 | + let outer = $outer; |
| 117 | + $( |
| 118 | + let outer = project!((outer).$field); |
| 119 | + )+ |
| 120 | + outer |
| 121 | + }}; |
| 122 | +} |
| 123 | + |
| 124 | +#[cfg(test)] |
| 125 | +mod tests { |
| 126 | + projectable! { |
| 127 | + struct Foo { |
| 128 | + a: u8, |
| 129 | + b: u16, |
| 130 | + c: u32, |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + #[test] |
| 135 | + fn test_project() { |
| 136 | + use core::mem::ManuallyDrop; |
| 137 | + |
| 138 | + let f = ManuallyDrop::new(Foo { a: 0, b: 1, c: 2 }); |
| 139 | + let p = project!((&f).c); |
| 140 | + assert_eq!(p, &ManuallyDrop::new(2)); |
| 141 | + } |
| 142 | +} |
0 commit comments