tor_memquota/
memory_cost_derive.rs

1//! Deriving `HasMemoryCost`
2
3use crate::internal_prelude::*;
4
5//---------- main public items ----------
6
7/// Types whose `HasMemoryCost` is derived structurally
8///
9/// Usually implemented using
10/// [`#[derive_deftly(HasMemoryCost)]`](crate::derive_deftly_template_HasMemoryCost).
11///
12/// For `Copy` types, it can also be implemented with
13/// `memory_cost_structural_copy!`.
14///
15/// When this trait is implemented, a blanket impl provides [`HasMemoryCost`].
16///
17/// ### Structural memory cost
18///
19/// We call the memory cost "structural"
20/// when it is derived from the type's structure.
21///
22/// The memory cost of a `HasMemoryCostStructural` type is:
23///
24/// - The number of bytes in its own size [`size_of`]; plus
25///
26/// - The (structural) memory cost of all the out-of-line data that it owns;
27///   that's what's returned by
28///   [`indirect_memory_cost`](HasMemoryCostStructural::indirect_memory_cost)
29///
30/// For example, `String`s out-of-line memory cost is just its capacity,
31/// so its memory cost is the size of its three word layout plus its capacity.
32///
33/// This calculation is performed by the blanket impl of `HasMemoryCost`.
34///
35/// ### Shared data - non-`'static` types, `Arc`
36///
37/// It is probably a mistake to implement this trait (or `HasMemoryCost`)
38/// for types with out-of-line data that they don't exclusively own.
39/// After all, the memory cost must be known and fixed,
40/// and if there is shared data it's not clear how it should be accounted.
41pub trait HasMemoryCostStructural {
42    /// Memory cost of data stored out-of-line
43    ///
44    /// The total memory cost is the cost of the layout of `self` plus this.
45    fn indirect_memory_cost(&self, _: EnabledToken) -> usize;
46}
47
48/// Compile-time check for `Copy + 'static` - helper for macros
49///
50/// Used by `#[deftly(has_memory_cost(copy))]`
51/// and `memory_cost_structural_copy!`
52/// to check that the type really is suitable.
53pub fn assert_copy_static<T: Copy + 'static>(_: &T) {}
54
55impl<T: HasMemoryCostStructural> HasMemoryCost for T {
56    fn memory_cost(&self, et: EnabledToken) -> usize {
57        size_of::<T>() //
58            .saturating_add(
59                //
60                <T as HasMemoryCostStructural>::indirect_memory_cost(self, et),
61            )
62    }
63}
64
65//---------- specific implementations ----------
66
67/// Implement [`HasMemoryCostStructural`] for `Copy` types
68///
69/// The [`indirect_memory_cost`](HasMemoryCostStructural::indirect_memory_cost)
70/// of a `Copy + 'static` type is zero.
71///
72/// This macro implements that.
73///
74/// Ideally, we would `impl <T: Copy + 'static> MemoryCostStructural for T`.
75/// But that falls foul of trait coherence rules.
76/// So instead we provide `memory_cost_structural_copy!`
77/// and the `#[deftly(has_memory_cost(copy))]` attribute.
78///
79/// This macro can only be used within `tor-memquota`, or for types local to your crate.
80/// For other types, use `#[deftly(has_memory_cost(copy))]` on each field of that type.
81//
82// Unfortunately we can't provide a blanket impl of `HasMemoryCostStructural`
83// for all `Copy` types, because we want to provide `HasMemoryCostStructural`
84// for `Vec` and `Box` -
85// and rustic thinks that those might become `Copy` in the future.
86#[macro_export]
87macro_rules! memory_cost_structural_copy { { $($ty:ty),* $(,)? } => { $(
88    impl $crate::HasMemoryCostStructural for $ty {
89        fn indirect_memory_cost(&self, _et: $crate::EnabledToken) -> usize {
90            $crate::assert_copy_static::<$ty>(self);
91            0
92        }
93    }
94)* } }
95
96memory_cost_structural_copy! {
97    u8, u16, u32, u64, usize,
98    i8, i16, i32, i64, isize,
99    // TODO MSRV 1.79: use std::num::NonZero<_> and avoid all these qualified
100    // names
101    std::num::NonZeroU8, std::num::NonZeroU16, std::num::NonZeroU32, std::num::NonZeroU64,
102    std::num::NonZeroI8, std::num::NonZeroI16, std::num::NonZeroI32, std::num::NonZeroI64,
103    std::num::NonZeroUsize,
104    std::num::NonZeroIsize,
105    std::net::IpAddr, std::net::Ipv4Addr, std::net::Ipv6Addr,
106}
107
108/// Implement HasMemoryCost for tuples
109macro_rules! memory_cost_structural_tuples { {
110    // Recursive case: do base case for this input, and then the next inputs
111    $($T:ident)* - $U0:ident $($UN:ident)*
112} => {
113    memory_cost_structural_tuples! { $($T)* - }
114    memory_cost_structural_tuples! { $($T)* $U0 - $($UN)* }
115}; {
116    // Base case, implement for the tuple with contents types $T
117    $($T:ident)* -
118} => { paste! {
119    impl < $(
120        $T: HasMemoryCostStructural,
121    )* > HasMemoryCostStructural for ( $(
122        $T,
123    )* ) {
124        fn indirect_memory_cost(&self, #[allow(unused)] et: EnabledToken) -> usize {
125            let ( $(
126                [< $T:lower >],
127            )* ) = self;
128            0_usize $(
129                .saturating_add([< $T:lower >].indirect_memory_cost(et))
130            )*
131        }
132    }
133} } }
134memory_cost_structural_tuples! { - A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }
135
136impl<T: HasMemoryCostStructural> HasMemoryCostStructural for Option<T> {
137    fn indirect_memory_cost(&self, et: EnabledToken) -> usize {
138        if let Some(t) = self {
139            <T as HasMemoryCostStructural>::indirect_memory_cost(t, et)
140        } else {
141            0
142        }
143    }
144}
145
146impl<T: HasMemoryCostStructural, const N: usize> HasMemoryCostStructural for [T; N] {
147    fn indirect_memory_cost(&self, et: EnabledToken) -> usize {
148        self.iter()
149            .map(|t| t.indirect_memory_cost(et))
150            .fold(0, usize::saturating_add)
151    }
152}
153
154impl<T: HasMemoryCostStructural> HasMemoryCostStructural for Box<T> {
155    fn indirect_memory_cost(&self, et: EnabledToken) -> usize {
156        <T as HasMemoryCost>::memory_cost(&**self, et)
157    }
158}
159
160impl<T: HasMemoryCostStructural> HasMemoryCostStructural for Vec<T> {
161    fn indirect_memory_cost(&self, et: EnabledToken) -> usize {
162        chain!(
163            [size_of::<T>().saturating_mul(self.capacity())],
164            self.iter().map(|t| t.indirect_memory_cost(et)),
165        )
166        .fold(0, usize::saturating_add)
167    }
168}
169
170impl HasMemoryCostStructural for String {
171    fn indirect_memory_cost(&self, _et: EnabledToken) -> usize {
172        self.capacity()
173    }
174}
175
176//------------------- derive macro ----------
177
178define_derive_deftly! {
179    /// Derive `HasMemoryCost`
180    ///
181    /// Each field must implement [`HasMemoryCostStructural`].
182    ///
183    /// Valid for structs and enums.
184    ///
185    /// ### Top-level attributes
186    ///
187    ///  * **`#[deftly(has_memory_cost(bounds = "BOUNDS"))]`**:
188    ///    Additional bounds to apply to the implementation.
189    ///
190    /// ### Field attributes
191    ///
192    ///  * **`#[deftly(has_memory_cost(copy))]`**:
193    ///    This field is `Copy + 'static` so does not reference any data that should be accounted.
194    ///  * **`#[deftly(has_memory_cost(indirect_fn = "FUNCTION"))]`**:
195    ///    `FUNCTION` is a function with the signature and semantics of
196    ///    [`HasMemoryCostStructural::indirect_memory_cost`],
197    ///  * **`#[deftly(has_memory_cost(indirect_size = "EXPR"))]`**:
198    ///    `EXPR` is an expression of type usize with the semantics of a return value from
199    ///    [`HasMemoryCostStructural::indirect_memory_cost`].
200    ///
201    /// With one of these, the field doesn't need to implement `HasMemoryCostStructural`.
202    ///
203    /// # Example
204    ///
205    /// ```
206    /// use derive_deftly::Deftly;
207    /// use std::mem::size_of;
208    /// use tor_memquota::{HasMemoryCost, HasMemoryCostStructural};
209    /// use tor_memquota::derive_deftly_template_HasMemoryCost;
210    ///
211    /// #[derive(Deftly)]
212    /// #[derive_deftly(HasMemoryCost)]
213    /// #[deftly(has_memory_cost(bounds = "Data: HasMemoryCostStructural"))]
214    /// struct Struct<Data> {
215    ///     data: Data,
216    ///
217    ///     #[deftly(has_memory_cost(indirect_size = "0"))] // this is a good guess
218    ///     num: serde_json::Number,
219    ///
220    ///     #[deftly(has_memory_cost(copy))]
221    ///     msg: &'static str,
222    ///
223    ///     #[deftly(has_memory_cost(indirect_fn = "|info, _et| String::capacity(info)"))]
224    ///     info: safelog::Sensitive<String>,
225    /// }
226    ///
227    /// let s = Struct {
228    ///     data: String::with_capacity(33),
229    ///     num: serde_json::Number::from_f64(0.0).unwrap(),
230    ///     msg: "hello",
231    ///     info: String::with_capacity(12).into(),
232    /// };
233    ///
234    /// let Some(et) = tor_memquota::EnabledToken::new_if_compiled_in() else { return };
235    ///
236    /// assert_eq!(
237    ///     s.memory_cost(et),
238    ///     size_of::<Struct<String>>() + 33 + 12,
239    /// );
240    /// ```
241    export HasMemoryCost expect items:
242
243    impl<$tgens> $crate::HasMemoryCostStructural for $ttype
244    where $twheres ${if tmeta(has_memory_cost(bounds)) {
245              ${tmeta(has_memory_cost(bounds)) as token_stream}
246    }}
247    {
248        fn indirect_memory_cost(&self, #[allow(unused)] et: $crate::EnabledToken) -> usize {
249            ${define F_INDIRECT_COST {
250                ${select1
251                    fmeta(has_memory_cost(copy)) {
252                        {
253                            $crate::assert_copy_static::<$ftype>(&$fpatname);
254                            0
255                        }
256                    }
257                    fmeta(has_memory_cost(indirect_fn)) {
258                        ${fmeta(has_memory_cost(indirect_fn)) as expr}(&$fpatname, et)
259                    }
260                    fmeta(has_memory_cost(indirect_size)) {
261                        ${fmeta(has_memory_cost(indirect_size)) as expr}
262                    }
263                    else {
264     <$ftype as $crate::HasMemoryCostStructural>::indirect_memory_cost(&$fpatname, et)
265                    }
266                }
267            }}
268
269            match self {
270                $(
271                    $vpat => {
272                        0_usize
273                            ${for fields {
274                                .saturating_add( $F_INDIRECT_COST )
275                            }}
276                    }
277                )
278            }
279        }
280    }
281}
282
283#[cfg(all(test, feature = "memquota"))]
284mod test {
285    // @@ begin test lint list maintained by maint/add_warning @@
286    #![allow(clippy::bool_assert_comparison)]
287    #![allow(clippy::clone_on_copy)]
288    #![allow(clippy::dbg_macro)]
289    #![allow(clippy::mixed_attributes_style)]
290    #![allow(clippy::print_stderr)]
291    #![allow(clippy::print_stdout)]
292    #![allow(clippy::single_char_pattern)]
293    #![allow(clippy::unwrap_used)]
294    #![allow(clippy::unchecked_duration_subtraction)]
295    #![allow(clippy::useless_vec)]
296    #![allow(clippy::needless_pass_by_value)]
297    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
298    #![allow(clippy::arithmetic_side_effects)] // don't mind potential panicking ops in tests
299
300    use super::*;
301
302    #[derive(Deftly)]
303    #[derive_deftly(HasMemoryCost)]
304    enum E {
305        U(usize),
306        B(Box<u32>),
307    }
308
309    #[derive(Deftly, Default)]
310    #[derive_deftly(HasMemoryCost)]
311    struct S {
312        u: usize,
313        b: Box<u32>,
314        v: Vec<u32>,
315        ev: Vec<E>,
316    }
317
318    const ET: EnabledToken = EnabledToken::new();
319
320    // The size of a u32 is always 4 bytes, so we just write "4" rather than u32::SIZE.
321
322    #[test]
323    fn structs() {
324        assert_eq!(S::default().memory_cost(ET), size_of::<S>() + 4);
325        assert_eq!(E::U(0).memory_cost(ET), size_of::<E>());
326        assert_eq!(E::B(Box::default()).memory_cost(ET), size_of::<E>() + 4);
327    }
328
329    #[test]
330    fn values() {
331        let mut v: Vec<u32> = Vec::with_capacity(10);
332        v.push(1);
333
334        let s = S {
335            u: 0,
336            b: Box::new(42),
337            v,
338            ev: vec![],
339        };
340
341        assert_eq!(
342            s.memory_cost(ET),
343            size_of::<S>() + 4 /* b */ + 10 * 4, /* v buffer */
344        );
345    }
346
347    #[test]
348    #[allow(clippy::identity_op)]
349    fn nest() {
350        let mut ev = Vec::with_capacity(10);
351        ev.push(E::U(42));
352        ev.push(E::B(Box::new(42)));
353
354        let s = S { ev, ..S::default() };
355
356        assert_eq!(
357            s.memory_cost(ET),
358            size_of::<S>() + 4 /* b */ + 0 /* v */ + size_of::<E>() * 10 /* ev buffer */ + 4 /* E::B */
359        );
360    }
361}