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}