Skip to main content

rune/runtime/value/
dismantle.rs

1//! Taking a graph of values apart without recursing into it.
2
3use core::fmt;
4use core::mem::replace;
5
6use crate::runtime::{Inline, RawAnyGuard, Value};
7
8use super::Repr;
9
10/// How many values can be waiting before the worklist has to grow.
11///
12/// Taking a value apart is done without allocating for as long as few enough of
13/// its values are waiting at once, which is what nearly every value dropped by
14/// a destructor looks like.
15const INLINE: usize = 8;
16
17/// How deeply the walk is nested, so that the tests can check that a graph is
18/// taken apart without re-entering it.
19#[cfg(test)]
20mod counters {
21    use core::cell::Cell;
22
23    std::thread_local! {
24        static DEPTH: Cell<usize> = const { Cell::new(0) };
25        pub(super) static NESTING: Cell<usize> = const { Cell::new(0) };
26    }
27
28    /// Count a level of nesting for as long as it is held.
29    pub(super) struct Level;
30
31    impl Level {
32        pub(super) fn new() -> Self {
33            let depth = DEPTH.with(|d| {
34                d.set(d.get() + 1);
35                d.get()
36            });
37
38            NESTING.with(|n| n.set(n.get().max(depth)));
39            Level
40        }
41    }
42
43    impl Drop for Level {
44        fn drop(&mut self) {
45            DEPTH.with(|d| d.set(d.get() - 1));
46        }
47    }
48
49    /// Reset what has been counted, returning what it was.
50    pub(super) fn take() -> usize {
51        NESTING.with(|n| n.replace(0))
52    }
53}
54
55/// Where the values taken out of a graph wait to be taken apart themselves.
56///
57/// A value which is made of other values is a graph which is built at runtime,
58/// so how deep it is has nothing to do with how deep the source was. Dropping
59/// the outermost value of such a graph drops the values it is made of, which
60/// drops what *they* are made of, and so on, once per level - a chain of a few
61/// thousand values is enough to exhaust the call stack.
62///
63/// The walk therefore takes the values out of one value at a time and leaves
64/// them waiting here rather than descending into them, so it costs memory
65/// rather than native frames.
66///
67/// # Memory
68///
69/// This is the one place in the runtime which allocates infallibly, through
70/// [`rust_alloc`] rather than through the memory limit in effect. Taking a
71/// value apart is what *frees* memory, so failing because memory has run out
72/// leaves nothing which could be done about it, and a destructor has nobody to
73/// report the failure to.
74///
75/// What it takes is bounded by the graph being taken apart, which the limit
76/// already bounds: what waits here came out of the graph and is a fraction of
77/// what it occupied. Few enough values fit without allocating at all, which is
78/// what a destructor handed an ordinary value costs.
79pub(crate) struct Worklist {
80    /// The values which are waiting, for as long as there are few enough of
81    /// them to keep without allocating.
82    inline: [Repr; INLINE],
83    /// How many of `inline` are in use.
84    len: usize,
85    /// Where the values which do not fit are kept.
86    spilled: rust_alloc::vec::Vec<Repr>,
87}
88
89impl fmt::Debug for Worklist {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.debug_struct("Worklist")
92            .field("len", &(self.len + self.spilled.len()))
93            .field("spilled", &self.spilled.capacity())
94            .finish()
95    }
96}
97
98impl Worklist {
99    /// Construct an empty worklist, which does not allocate until it is handed
100    /// more values at once than it can keep.
101    pub(crate) const fn new() -> Self {
102        Self {
103            inline: [const { Repr::Inline(Inline::Empty) }; INLINE],
104            len: 0,
105            spilled: rust_alloc::vec::Vec::new(),
106        }
107    }
108
109    /// How much the worklist has grown beyond what it holds without
110    /// allocating, which is what a machine keeps rather than growing one for
111    /// every value it takes apart.
112    #[cfg(test)]
113    pub(crate) fn capacity(&self) -> usize {
114        self.spilled.capacity()
115    }
116
117    /// Take `value` apart, and every value it is made of.
118    ///
119    /// This takes the values out of one value at a time and leaves them
120    /// waiting, so the walk costs the memory that takes rather than the native
121    /// frames which descending into them would cost.
122    #[inline]
123    pub(crate) fn dismantle(&mut self, value: Value) {
124        let repr = value.take_repr();
125
126        // Values which cannot contain other values are the common case and
127        // need nothing to take apart.
128        if matches!(repr, Repr::Inline(..)) {
129            return;
130        }
131
132        self.dismantle_repr(repr);
133    }
134
135    /// Take the value out of `slot`, leaving it empty, and take it apart.
136    #[inline]
137    pub(crate) fn take(&mut self, slot: &mut Value) {
138        self.dismantle(Value::take(slot));
139    }
140
141    /// Write `value` into `slot`, taking apart the value which was there rather
142    /// than leaving it to its destructor.
143    #[inline]
144    pub(crate) fn replace(&mut self, slot: &mut Value, value: Value) {
145        self.dismantle(replace(slot, value));
146    }
147
148    /// Take every value in `values` apart over the same worklist.
149    ///
150    /// This is what an operation which destroys many values at once - clearing
151    /// a collection, tearing down a call frame - uses, so that they share the
152    /// memory the walk grows rather than each growing its own.
153    pub(crate) fn dismantle_all<'a>(&mut self, values: impl IntoIterator<Item = &'a mut Value>) {
154        for slot in values {
155            self.take(slot);
156        }
157    }
158
159    /// Take apart a value which has already been taken out of the [`Value`]
160    /// which held it, which is what a destructor has in hand.
161    pub(crate) fn dismantle_repr(&mut self, repr: Repr) {
162        #[cfg(test)]
163        let _level = self::counters::Level::new();
164
165        let mut current = repr;
166
167        loop {
168            // Everything inside is taken out and left waiting, so dropping what
169            // remains cannot descend into anything.
170            handover(&mut current, &mut Handover { work: self });
171            drop(current);
172
173            let Some(next) = self.pop() else {
174                break;
175            };
176
177            current = next;
178        }
179    }
180
181    /// Leave a value waiting until it is taken apart.
182    fn push(&mut self, repr: Repr) {
183        if self.len < INLINE {
184            self.inline[self.len] = repr;
185            self.len += 1;
186            return;
187        }
188
189        // Growing is what a graph which is deep or wide costs, and it is not
190        // charged to the memory limit - see the type documentation.
191        self.spilled.push(repr);
192    }
193
194    /// The value which has been waiting the shortest, since taking the most
195    /// recent one apart first is what keeps the walk from holding more of the
196    /// graph at once than it has to.
197    fn pop(&mut self) -> Option<Repr> {
198        if let Some(repr) = self.spilled.pop() {
199            return Some(repr);
200        }
201
202        let len = self.len.checked_sub(1)?;
203        self.len = len;
204        Some(replace(&mut self.inline[len], Repr::Inline(Inline::Empty)))
205    }
206}
207
208/// Take the values which `repr` is made of out of it and hand them over.
209fn handover(repr: &mut Repr, out: &mut Handover<'_>) {
210    match repr {
211        Repr::Inline(..) => {}
212        Repr::Dynamic(sequence) => {
213            // Another reference is keeping the values alive, so dropping this
214            // one does not descend into them.
215            if !sequence.is_last_owner() {
216                return;
217            }
218
219            if let Ok(mut values) = sequence.borrow_mut() {
220                out.consume_all(values.iter_mut());
221            }
222        }
223        // Which values an externally typed value is made of is only known to
224        // the type, so it is asked - see [`Dismantle`].
225        Repr::Any(any) => any.dismantle(out),
226    }
227}
228
229/// The values being taken out of a graph, handed to the value which is made of
230/// them so that it can hand them over.
231///
232/// Handing a value over takes it out of where it was and leaves it waiting to be
233/// taken apart in its own right, which is what keeps the walk from descending -
234/// and therefore from costing a native frame for every level a graph nests.
235pub struct Handover<'a> {
236    work: &'a mut Worklist,
237}
238
239impl Handover<'_> {
240    /// Hand over everything `from` is made of.
241    ///
242    /// This is what a type calls for each of the values it holds, whether that
243    /// is a [`Value`] or something which is itself made of values.
244    #[inline]
245    pub fn consume<T>(&mut self, from: &mut T)
246    where
247        T: ?Sized + Dismantle,
248    {
249        from.dismantle(self);
250    }
251
252    /// Hand over everything in `values`.
253    #[inline]
254    pub fn consume_all<'a, T>(&mut self, values: impl IntoIterator<Item = &'a mut T>)
255    where
256        T: 'a + ?Sized + Dismantle,
257    {
258        for value in values {
259            self.consume(value);
260        }
261    }
262
263    /// Hand over the value which `guard` keeps alive, if it still keeps one
264    /// alive.
265    ///
266    /// An iterator over a collection holds the collection through a guard
267    /// rather than in a slot it could hand over, so this is how it hands the
268    /// collection over.
269    #[inline]
270    pub fn consume_ref(&mut self, guard: &mut RawAnyGuard) {
271        let Some(owner) = guard.take_value() else {
272            return;
273        };
274
275        self.push(owner);
276    }
277
278    /// Hand `value` over as it is.
279    #[inline]
280    pub fn push(&mut self, value: Value) {
281        let repr = value.take_repr();
282
283        // A value which cannot contain other values is dropped here and now,
284        // since dropping it cannot descend into anything.
285        if matches!(repr, Repr::Inline(..)) {
286            return;
287        }
288
289        self.work.push(repr);
290    }
291}
292
293/// How a value stored in the virtual machine hands over the values it is made
294/// of, so that a graph of them is taken apart without recursing into it.
295///
296/// Every type which implements [`Any`] implements this as well, and the [`Any`]
297/// derive writes it: a type which is not made of values hands nothing over and
298/// is dropped in place, which is what the derive writes unless it is told
299/// otherwise.
300///
301/// A type which *does* hold [`Value`]s has to hand them over, since a script can
302/// nest such a type inside itself without any bound - `v = [v]` in a loop - and
303/// dropping what that builds costs a native frame per level otherwise, which
304/// exhausts the call stack. Mark the fields which hold them:
305///
306/// ```
307/// use rune::Any;
308/// use rune::Value;
309///
310/// #[derive(Any)]
311/// struct Pair {
312///     #[rune(dismantle)]
313///     first: Value,
314///     #[rune(dismantle)]
315///     second: Value,
316///     count: u32,
317/// }
318/// ```
319///
320/// Anything the derive cannot write - a collection, an iterator which holds what
321/// it walks through a guard - is written by hand instead, by declaring the type
322/// `#[rune(dismantle)]` and implementing this trait for it.
323///
324/// [`Any`]: derive@crate::Any
325pub trait Dismantle {
326    /// Hand over every value this is made of, leaving nothing behind which is
327    /// itself made of values.
328    ///
329    /// Whatever is left is dropped once this returns, so a value which is not
330    /// handed over is dropped where it is - which costs a native frame for
331    /// every level a script nests it.
332    fn dismantle(&mut self, out: &mut Handover<'_>);
333}
334
335/// A value is the leaf of the walk: it is handed over as it is, unless it
336/// cannot contain other values, in which case there is nothing to hand over.
337///
338/// This is what makes handing a field over the same call whatever the field
339/// holds, which is what the derive writes.
340impl Dismantle for Value {
341    #[inline]
342    fn dismantle(&mut self, out: &mut Handover<'_>) {
343        if self.is_inline() {
344            return;
345        }
346
347        out.push(Value::take(self));
348    }
349}
350
351/// A value which may or may not be there is made of the one it holds, and a
352/// script can nest one inside another - `a = Some(a)` in a loop.
353impl Dismantle for Option<Value> {
354    #[inline]
355    fn dismantle(&mut self, out: &mut Handover<'_>) {
356        out.consume_all(self.iter_mut());
357    }
358}
359
360/// Either outcome is made of the value it holds, and a script can nest one
361/// inside another - `a = Ok(a)` in a loop.
362impl Dismantle for Result<Value, Value> {
363    #[inline]
364    fn dismantle(&mut self, out: &mut Handover<'_>) {
365        let (Ok(slot) | Err(slot)) = self;
366        out.consume(slot);
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373
374    use crate::alloc::String;
375    use crate::runtime::{Object, OwnedTuple, Vec};
376    use crate::support::Result;
377    use crate::Any;
378
379    /// How deep the graphs built below are.
380    ///
381    /// Taking them apart by recursing overflowed in the thousands, and a test
382    /// runs on a thread with a much smaller stack than the main one, so this is
383    /// well past what used to abort the process.
384    ///
385    /// Under miri a graph that size is out of reach, so what runs there is only
386    /// deep enough to reach every shape a level can be. It still says the walk
387    /// does not nest - the counter below is what says that, at any depth - but
388    /// it no longer says the walk survives a graph the native stack could not.
389    const DEEP: usize = if cfg!(miri) { 128 } else { 50000 };
390
391    /// How many values the graph built below holds at one level.
392    ///
393    /// Taking a value apart used to walk over the values it had already handed
394    /// over to find the next one, which is quadratic - this is enough of them
395    /// that the test does not finish if that comes back.
396    ///
397    /// Under miri it is only enough to walk a container which holds more than a
398    /// handful, which says the walk is right rather than that it is one pass.
399    const WIDE: usize = if cfg!(miri) { 512 } else { 200000 };
400
401    /// A type which says which of its fields hold values the way an external
402    /// type does, so that what the derive writes is walked over as well.
403    #[derive(Any)]
404    #[rune(crate)]
405    struct Holder {
406        #[rune(dismantle)]
407        value: Value,
408        #[allow(unused)]
409        count: i64,
410    }
411
412    /// Build a graph which is `depth` deep, of every shape which has to be
413    /// taken apart, and hand back the value it is built around.
414    ///
415    /// Every level branches, so that more than one value has to be handed over
416    /// at each of them.
417    fn deep(depth: usize) -> Result<(Value, Value)> {
418        let innermost = Value::try_from(Vec::new())?;
419        let mut value = innermost.clone();
420
421        for n in 0..depth {
422            let mut vec = Vec::new();
423            vec.push(Value::try_from(Vec::new())?)?;
424            vec.push(Value::from(n as i64))?;
425            vec.push(value)?;
426
427            value = match n % 4 {
428                0 => Value::try_from(vec)?,
429                1 => {
430                    let mut object = Object::new();
431                    object.insert(String::try_from("a")?, Value::try_from(vec)?)?;
432                    object.insert(String::try_from("b")?, Value::from(n as i64))?;
433                    Value::try_from(object)?
434                }
435                2 => Value::new(Holder {
436                    value: Value::try_from(vec)?,
437                    count: n as i64,
438                })?,
439                _ => Value::try_from(OwnedTuple::try_from(rust_alloc::vec![
440                    Value::try_from(vec)?,
441                    Value::from(n as i64),
442                ])?)?,
443            };
444        }
445
446        Ok((value, innermost))
447    }
448
449    /// Taking a deep graph apart is what a destructor does, and it never
450    /// re-enters itself no matter how deep the graph is.
451    #[test]
452    fn dropping_takes_a_deep_graph_apart() -> Result<()> {
453        let (value, innermost) = deep(DEEP)?;
454
455        let _ = self::counters::take();
456
457        drop(value);
458
459        let nesting = self::counters::take();
460
461        assert_eq!(nesting, 1, "Taking a graph apart should not nest");
462
463        let any = innermost.as_any().expect("Innermost value should be any");
464        assert!(any.is_last_owner(), "Graph should be gone");
465        Ok(())
466    }
467
468    /// The machine takes the same graph apart over the worklist it keeps.
469    #[test]
470    fn dismantling_takes_a_deep_graph_apart() -> Result<()> {
471        let (value, innermost) = deep(DEEP)?;
472
473        Worklist::new().dismantle(value);
474
475        let any = innermost.as_any().expect("Innermost value should be any");
476        assert!(any.is_last_owner(), "Graph should be gone");
477        Ok(())
478    }
479
480    /// A value which is made of very many others is taken apart in one pass over
481    /// them rather than in one pass for each of them.
482    ///
483    /// A map cannot remove the entry it hands over, so a walk which looked for
484    /// the next value to hand over from the start every time was quadratic in
485    /// how many the map held.
486    #[test]
487    fn taking_a_wide_graph_apart_is_one_pass() -> Result<()> {
488        let mut object = Object::new();
489
490        for n in 0..WIDE {
491            let key = String::try_from(rust_alloc::format!("k{n}").as_str())?;
492            // A value which is made of other values, so that it is handed over
493            // rather than dropped where it is.
494            object.insert(key, Value::try_from(Vec::new())?)?;
495        }
496
497        drop(Value::try_from(object)?);
498        Ok(())
499    }
500
501    /// Taking apart a value which holds few enough values does not allocate,
502    /// which is what a destructor handed an ordinary value costs.
503    #[test]
504    fn taking_a_small_graph_apart_does_not_allocate() -> Result<()> {
505        let mut vec = Vec::new();
506        vec.push(Value::try_from(Vec::new())?)?;
507        vec.push(Value::try_from(Vec::new())?)?;
508
509        let mut work = Worklist::new();
510        work.dismantle(Value::try_from(vec)?);
511
512        assert_eq!(
513            work.capacity(),
514            0,
515            "Taking a small value apart should not grow the worklist"
516        );
517
518        Ok(())
519    }
520}