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 use std::num::NonZero<_> and avoid all these qualified names
100    std::num::NonZeroU8, std::num::NonZeroU16, std::num::NonZeroU32, std::num::NonZeroU64,
101    std::num::NonZeroI8, std::num::NonZeroI16, std::num::NonZeroI32, std::num::NonZeroI64,
102    std::num::NonZeroUsize,
103    std::num::NonZeroIsize,
104    std::net::IpAddr, std::net::Ipv4Addr, std::net::Ipv6Addr,
105}
106
107/// Implement HasMemoryCost for tuples
108macro_rules! memory_cost_structural_tuples { {
109    // Recursive case: do base case for this input, and then the next inputs
110    $($T:ident)* - $U0:ident $($UN:ident)*
111} => {
112    memory_cost_structural_tuples! { $($T)* - }
113    memory_cost_structural_tuples! { $($T)* $U0 - $($UN)* }
114}; {
115    // Base case, implement for the tuple with contents types $T
116    $($T:ident)* -
117} => { paste! {
118    impl < $(
119        $T: HasMemoryCostStructural,
120    )* > HasMemoryCostStructural for ( $(
121        $T,
122    )* ) {
123        fn indirect_memory_cost(&self, #[allow(unused)] et: EnabledToken) -> usize {
124            let ( $(
125                [< $T:lower >],
126            )* ) = self;
127            0_usize $(
128                .saturating_add([< $T:lower >].indirect_memory_cost(et))
129            )*
130        }
131    }
132} } }
133memory_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 }
134
135impl<T: HasMemoryCostStructural> HasMemoryCostStructural for Option<T> {
136    fn indirect_memory_cost(&self, et: EnabledToken) -> usize {
137        if let Some(t) = self {
138            <T as HasMemoryCostStructural>::indirect_memory_cost(t, et)
139        } else {
140            0
141        }
142    }
143}
144
145impl<T: HasMemoryCostStructural, const N: usize> HasMemoryCostStructural for [T; N] {
146    fn indirect_memory_cost(&self, et: EnabledToken) -> usize {
147        self.iter()
148            .map(|t| t.indirect_memory_cost(et))
149            .fold(0, usize::saturating_add)
150    }
151}
152
153impl<T: HasMemoryCostStructural> HasMemoryCostStructural for Box<T> {
154    fn indirect_memory_cost(&self, et: EnabledToken) -> usize {
155        <T as HasMemoryCost>::memory_cost(&**self, et)
156    }
157}
158
159impl<T: HasMemoryCostStructural> HasMemoryCostStructural for Vec<T> {
160    fn indirect_memory_cost(&self, et: EnabledToken) -> usize {
161        chain!(
162            [size_of::<T>().saturating_mul(self.capacity())],
163            self.iter().map(|t| t.indirect_memory_cost(et)),
164        )
165        .fold(0, usize::saturating_add)
166    }
167}
168
169impl HasMemoryCostStructural for String {
170    fn indirect_memory_cost(&self, _et: EnabledToken) -> usize {
171        self.capacity()
172    }
173}
174
175//------------------- derive macro ----------
176
177define_derive_deftly! {
178    /// Derive `HasMemoryCost`
179    ///
180    /// Each field must implement [`HasMemoryCostStructural`].
181    ///
182    /// Valid for structs and enums.
183    ///
184    /// ### Top-level attributes
185    ///
186    ///  * **`#[deftly(has_memory_cost(bounds = "BOUNDS"))]`**:
187    ///    Additional bounds to apply to the implementation.
188    ///
189    /// ### Field attributes
190    ///
191    ///  * **`#[deftly(has_memory_cost(copy))]`**:
192    ///    This field is `Copy + 'static` so does not reference any data that should be accounted.
193    ///  * **`#[deftly(has_memory_cost(indirect_fn = "FUNCTION"))]`**:
194    ///    `FUNCTION` is a function with the signature and semantics of
195    ///    [`HasMemoryCostStructural::indirect_memory_cost`],
196    ///  * **`#[deftly(has_memory_cost(indirect_size = "EXPR"))]`**:
197    ///    `EXPR` is an expression of type usize with the semantics of a return value from
198    ///    [`HasMemoryCostStructural::indirect_memory_cost`].
199    ///
200    /// With one of these, the field doesn't need to implement `HasMemoryCostStructural`.
201    ///
202    /// # Example
203    ///
204    /// ```
205    /// use derive_deftly::Deftly;
206    /// use std::mem::size_of;
207    /// use tor_memquota::{HasMemoryCost, HasMemoryCostStructural};
208    /// use tor_memquota::derive_deftly_template_HasMemoryCost;
209    ///
210    /// #[derive(Deftly)]
211    /// #[derive_deftly(HasMemoryCost)]
212    /// #[deftly(has_memory_cost(bounds = "Data: HasMemoryCostStructural"))]
213    /// struct Struct<Data> {
214    ///     data: Data,
215    ///
216    ///     #[deftly(has_memory_cost(indirect_size = "0"))] // this is a good guess
217    ///     num: serde_json::Number,
218    ///
219    ///     #[deftly(has_memory_cost(copy))]
220    ///     msg: &'static str,
221    ///
222    ///     #[deftly(has_memory_cost(indirect_fn = "|info, _et| String::capacity(info)"))]
223    ///     info: safelog::Sensitive<String>,
224    /// }
225    ///
226    /// let s = Struct {
227    ///     data: String::with_capacity(33),
228    ///     num: serde_json::Number::from_f64(0.0).unwrap(),
229    ///     msg: "hello",
230    ///     info: String::with_capacity(12).into(),
231    /// };
232    ///
233    /// let Some(et) = tor_memquota::EnabledToken::new_if_compiled_in() else { return };
234    ///
235    /// assert_eq!(
236    ///     s.memory_cost(et),
237    ///     size_of::<Struct<String>>() + 33 + 12,
238    /// );
239    /// ```
240    export HasMemoryCost expect items:
241
242    impl<$tgens> $crate::HasMemoryCostStructural for $ttype
243    where $twheres ${if tmeta(has_memory_cost(bounds)) {
244              ${tmeta(has_memory_cost(bounds)) as token_stream}
245    }}
246    {
247        fn indirect_memory_cost(&self, #[allow(unused)] et: $crate::EnabledToken) -> usize {
248            ${define F_INDIRECT_COST {
249                ${select1
250                    fmeta(has_memory_cost(copy)) {
251                        {
252                            $crate::assert_copy_static::<$ftype>(&$fpatname);
253                            0
254                        }
255                    }
256                    fmeta(has_memory_cost(indirect_fn)) {
257                        ${fmeta(has_memory_cost(indirect_fn)) as expr}(&$fpatname, et)
258                    }
259                    fmeta(has_memory_cost(indirect_size)) {
260                        ${fmeta(has_memory_cost(indirect_size)) as expr}
261                    }
262                    else {
263     <$ftype as $crate::HasMemoryCostStructural>::indirect_memory_cost(&$fpatname, et)
264                    }
265                }
266            }}
267
268            match self {
269                $(
270                    $vpat => {
271                        0_usize
272                            ${for fields {
273                                .saturating_add( $F_INDIRECT_COST )
274                            }}
275                    }
276                )
277            }
278        }
279    }
280}
281
282#[cfg(all(test, feature = "memquota"))]
283mod test {
284    // @@ begin test lint list maintained by maint/add_warning @@
285    #![allow(clippy::bool_assert_comparison)]
286    #![allow(clippy::clone_on_copy)]
287    #![allow(clippy::dbg_macro)]
288    #![allow(clippy::mixed_attributes_style)]
289    #![allow(clippy::print_stderr)]
290    #![allow(clippy::print_stdout)]
291    #![allow(clippy::single_char_pattern)]
292    #![allow(clippy::unwrap_used)]
293    #![allow(clippy::unchecked_duration_subtraction)]
294    #![allow(clippy::useless_vec)]
295    #![allow(clippy::needless_pass_by_value)]
296    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
297    #![allow(clippy::arithmetic_side_effects)] // don't mind potential panicking ops in tests
298
299    use super::*;
300
301    #[derive(Deftly)]
302    #[derive_deftly(HasMemoryCost)]
303    enum E {
304        U(usize),
305        B(Box<u32>),
306    }
307
308    #[derive(Deftly, Default)]
309    #[derive_deftly(HasMemoryCost)]
310    struct S {
311        u: usize,
312        b: Box<u32>,
313        v: Vec<u32>,
314        ev: Vec<E>,
315    }
316
317    const ET: EnabledToken = EnabledToken::new();
318
319    // The size of a u32 is always 4 bytes, so we just write "4" rather than u32::SIZE.
320
321    #[test]
322    fn structs() {
323        assert_eq!(S::default().memory_cost(ET), size_of::<S>() + 4);
324        assert_eq!(E::U(0).memory_cost(ET), size_of::<E>());
325        assert_eq!(E::B(Box::default()).memory_cost(ET), size_of::<E>() + 4);
326    }
327
328    #[test]
329    fn values() {
330        let mut v: Vec<u32> = Vec::with_capacity(10);
331        v.push(1);
332
333        let s = S {
334            u: 0,
335            b: Box::new(42),
336            v,
337            ev: vec![],
338        };
339
340        assert_eq!(
341            s.memory_cost(ET),
342            size_of::<S>() + 4 /* b */ + 10 * 4, /* v buffer */
343        );
344    }
345
346    #[test]
347    #[allow(clippy::identity_op)]
348    fn nest() {
349        let mut ev = Vec::with_capacity(10);
350        ev.push(E::U(42));
351        ev.push(E::B(Box::new(42)));
352
353        let s = S { ev, ..S::default() };
354
355        assert_eq!(
356            s.memory_cost(ET),
357            size_of::<S>() + 4 /* b */ + 0 /* v */ + size_of::<E>() * 10 /* ev buffer */ + 4 /* E::B */
358        );
359    }
360}