Skip to main content

cove_ir/
layout.rs

1//! What a value is made of.
2//!
3//! A [`Layout`] answers three questions about one family of values: how many
4//! words a value of it occupies, what each of those words holds, and where
5//! its parts are. That is all a frame slot, a heap object payload and a
6//! garbage collection need, and it is deliberately one vocabulary for all
7//! three — the stack region and the heap region are regions of one linear
8//! memory, and a struct inside a closure environment is laid out the way a
9//! struct in a frame is.
10//!
11//! # A value is a run of words
12//!
13//! [`docs/LINEAR_VM.md`](../../../docs/LINEAR_VM.md) states the rule:
14//!
15//! > One slot is one eight-byte word. One value may occupy one or more
16//! > consecutive slots.
17//!
18//! So a `Point { x: Int, y: Int }` is two words *where the value is*, not one
19//! word naming two words somewhere else. That is what makes ADR 0001's
20//! field-wise shallow copy a copy: two words in, two words out. The earlier
21//! design put every struct behind one address, which made an ordinary copy an
22//! alias and then needed a sharing bit and copy-on-write to conceal it —
23//! machinery that existed only to undo the representation choice.
24//!
25//! # What is inline and what is an address
26//!
27//! A value has a static width or it lives in the heap. Scalars, structs and
28//! enums have one; strings, collections, closures and erased values do not,
29//! and a value of one of those families is a single [`Repr::Ref`] word.
30//!
31//! There is no fourth case. A declaration whose layout would contain itself
32//! has no static width either, and
33//! [ADR 0035](../../../docs/adr/0035-a-value-type-may-not-contain-itself.md)
34//! decides that it is a checker error rather than something quietly given a
35//! heap representation — so a recursive cycle passes through one of the
36//! families above and is finite because that family is one word.
37//!
38//! A heap object's payload is described by a layout in exactly the same way,
39//! so a struct stored in an array element or a closure environment is inline
40//! in that payload, and the collector walks it with the same map.
41//!
42//! # This table describes families, not instantiations
43//!
44//! `Array<String>` and `Array<Point>` are one layout, because a reference is
45//! a reference. `Array<Int>` and `Array<Duration>` are two, because their
46//! words differ and a boundary has to know which. Nothing here grows a case
47//! because a program was refused, and nothing here is a runtime type
48//! universe: what an individual object *is* is a question its own header
49//! answers.
50
51use std::sync::Arc;
52
53use crate::repr::Repr;
54use crate::FunctionId;
55
56/// Names a [`Layout`] in [`crate::Program::layouts`].
57///
58/// `LayoutId(0)` is reserved for [`Layout::free`]: the sweeper writes it into
59/// the header of a reclaimed run of words so the heap stays a walkable
60/// sequence of objects. No Cove value ever has it.
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct LayoutId(pub u32);
63
64impl LayoutId {
65    /// The layout of a reclaimed run of words.
66    pub const FREE: LayoutId = LayoutId(0);
67
68    /// The index this id names.
69    pub fn index(self) -> usize {
70        self.0 as usize
71    }
72}
73
74impl std::fmt::Display for LayoutId {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        write!(f, "layout{}", self.0)
77    }
78}
79
80/// The payload word a [`Shape::Shared`] object keeps its lock in.
81///
82/// Zero is "no task holds this cell", which is what a freshly allocated cell's
83/// zeroed payload already says — the same reason a `Repr::Host` word is one
84/// past its index.
85pub const SHARED_STATE: u32 = 0;
86
87/// The payload word a [`Shape::Shared`] object's wrapped value begins at.
88///
89/// Named here rather than in either side because both need it and they must
90/// agree: the lowering forms the address of this word to hand a `lock`'s
91/// closure, and the collector traces the value's run of words from it.
92pub const SHARED_VALUE: u32 = 1;
93
94/// One field of a struct, and where it starts.
95///
96/// `at` is a word offset within the struct, so `l.from.x` is a slot number
97/// the lowering computes and not an instruction the machine runs. A field of
98/// an *inline* value costs nothing to reach.
99#[derive(Clone, Debug, PartialEq, Eq)]
100pub struct Field {
101    pub name: Arc<str>,
102    pub layout: LayoutId,
103    pub at: u32,
104}
105
106/// One part of an enum case's payload, and where it sits in the payload
107/// region.
108#[derive(Clone, Debug, PartialEq, Eq)]
109pub struct Part {
110    pub layout: LayoutId,
111    /// A word offset within the payload region, which begins *after* the
112    /// discriminant word.
113    pub at: u32,
114}
115
116/// One case of an enum.
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub struct Case {
119    pub name: Arc<str>,
120    /// The parts of this case's payload, in declaration order.
121    pub parts: Vec<Part>,
122}
123
124/// How a family's words are arranged.
125#[derive(Clone, Debug, PartialEq, Eq)]
126pub enum Shape {
127    /// A run of free words. Not a value; see [`LayoutId::FREE`].
128    Free,
129    /// One word of the given interpretation.
130    ///
131    /// The width-one case of the whole model, and the one every scalar is.
132    Word(Repr),
133    /// Consecutive fields, inline.
134    Struct {
135        fields: Vec<Field>,
136        /// Whether the declaration was `export opaque struct`.
137        ///
138        /// A fact about a *declaration* on a table that otherwise describes
139        /// families, and it is here because nothing downstream can derive it:
140        /// by the time a value is a word, the declaration is gone. What reads
141        /// it is a rendering, which shows an opaque value's name and nothing
142        /// else — its fields are the declaring module's business, and a
143        /// rendering is read by whoever the string reaches.
144        opaque: bool,
145    },
146    /// Word 0 is the case index; the words after it are the payload region.
147    ///
148    /// The region is wide enough for every case, and its per-word [`Repr`]s
149    /// are in [`Shape::Enum::payload`]. **Every case that uses a payload word
150    /// agrees on that word's `Repr`** — the lowering assigns offsets under
151    /// that constraint — because one static reference map has to be right
152    /// whatever case a value holds. A word cannot be a reference in one case
153    /// and an integer in another.
154    ///
155    /// Two things follow. Constructing a case zeroes the payload words it
156    /// does not fill, so a reference word belonging to another case reads
157    /// null. And a collection never reads the discriminant: the region's map
158    /// is static, which is one fewer thing that can be wrong.
159    ///
160    /// The cost is a region that can be wider than the widest case. That is
161    /// the price of a static map, paid in words rather than in a run-time
162    /// question.
163    Enum {
164        cases: Vec<Case>,
165        /// The payload region's words, after the discriminant.
166        payload: Vec<Repr>,
167    },
168    /// UTF-8 bytes, eight to a word, little end first. The header's `len` is
169    /// the byte count, so the payload is `len.div_ceil(8)` words and the
170    /// trailing bytes of the last word are zero.
171    Str,
172    /// Packed bytes, eight to a word, little end first, exactly like
173    /// [`Shape::Str`]'s payload — but not yet a `String`.
174    ///
175    /// [ADR 0051](../../../docs/adr/0051-a-string-is-built-as-a-byte-run.md)
176    /// gives lowering an internal construction run: an object [`crate::Inst::AllocBytes`]
177    /// allocates, [`crate::Inst::WriteByte`] and [`crate::Inst::CopyBytes`] fill, and
178    /// [`crate::Inst::FinishString`] turns into a `String` without copying. It is an
179    /// IR/runtime value, not a Cove type — no declaration names it and no
180    /// source expression produces one.
181    ///
182    /// The header's `len` is a byte count, the same as [`Shape::Str`]'s, which
183    /// is what lets the runtime's `Machine::relabel` turn a finished run into
184    /// a `String` of the same header length without touching a payload word.
185    /// Its payload holds no references — arbitrary
186    /// written bytes are never a `LayoutId` or an address — so a run that is
187    /// only half filled is exactly as safe for the collector to walk as a
188    /// finished one: [`Layout::may_hold_refs`] answers `false` for it below,
189    /// the same answer it gives [`Shape::Str`].
190    ///
191    /// This is deliberately **not** `Shape::Str`. ADR 0051 says "a run under
192    /// construction is not a `String`", and giving it a different shape is how
193    /// that is enforced without a runtime tag check on every ordinary
194    /// reference operation: `is_string` at
195    /// `crates/cove-runtime/src/vm/builtins.rs:564` matches on `Shape::Str`
196    /// alone, so a `Bytes` run fails it and every place that asks "is this
197    /// really a string" — the Host boundary, a call argument, a captured
198    /// value — refuses it for the ordinary reason a `Str`-only match already
199    /// refuses anything else, not because of a tag this shape adds.
200    Bytes,
201    /// The header's `len` elements, each `elem`'s words, contiguous.
202    ///
203    /// One shape covers `Array<T>` for every `T`, and is also what a
204    /// [`Shape::Vector`] stores its elements in — `growable` says which of
205    /// the two an object is.
206    Elements { elem: LayoutId, growable: bool },
207    /// Payload word 0 is the element count; word 1 is a reference to the
208    /// [`Shape::Elements`] object holding them.
209    ///
210    /// The indirection is what a growable value needs and an immutable one
211    /// does not. A `Vector`'s identity is observable — `is` is defined for it
212    /// and mutation through one copy is visible through every other — so
213    /// growing must not move the object a program is holding. The header
214    /// stays where it is and the store beneath it is replaced by a larger
215    /// one. An `Array` needs none of that and pays none of it.
216    Vector { elem: LayoutId },
217    /// Payload word 0 is the logical length in bytes; word 1 is a reference to
218    /// a [`Shape::Bytes`] store whose own header length is its **capacity**.
219    ///
220    /// [ADR 0052](../../../docs/adr/0052-a-growable-value-is-a-stable-owner-over-a-replaceable-run.md)'s
221    /// stable owner, for bytes. The reason it is two objects rather than one
222    /// is the reason [`Shape::Vector`] is: growth replaces the store, and the
223    /// owner does not move, so every alias and every `var` address to it is
224    /// still the same address afterwards. A run that grew by reallocating
225    /// *itself* would leave a formatter's `var out` parameter pointing at the
226    /// object it used to be, which is exactly the failure the ADR's
227    /// "if the object itself moves when it grows, every alias and `var`
228    /// address to it goes stale" names.
229    ///
230    /// The split is also what keeps capacity out of the language. A store's
231    /// header length has to be its capacity, because the allocator and the
232    /// collector walk whole physical objects; the *logical* length lives in
233    /// the owner, so the spare room `[length, capacity)` is unobservable and
234    /// exceeding an initial capacity grows rather than changing what a
235    /// program answers.
236    ///
237    /// This is the byte case of what [`Shape::Vector`] already is for word
238    /// elements. The two differ in the storage unit and in the reference map
239    /// and in nothing else: a byte run packs eight bytes to a word and holds
240    /// no references, an element run stores values at the element layout's
241    /// stride and is traced by that layout. ADR 0052's generic `Buffer<E>`
242    /// will subsume both, and this is deliberately *not* generalised before
243    /// the second case exists — the ADR's own reason for doing bytes first is
244    /// that a shared abstraction with one instance is a guess about the
245    /// second.
246    ///
247    /// Word 1 is always a reference, so [`Layout::may_hold_refs`] answers
248    /// `true` and a collection traces word 1 and only word 1: word 0 is a
249    /// length, and reading it as an address would chase an integer. The
250    /// payload is a fixed two words whatever the store's capacity, exactly as
251    /// [`Shape::Vector`]'s is — [`Layout::fixed_payload_words`] answers `2`
252    /// rather than `None`, which is what lets a static reader bound an access
253    /// into an owner without a header to consult.
254    ByteBuffer,
255    /// The header's `len` members, ascending and distinct.
256    Members { elem: LayoutId },
257    /// The header's `len` entries — key then value — ascending by key.
258    Entries { key: LayoutId, value: LayoutId },
259    /// Payload word 0 is the callee's [`FunctionId`]; the words after it are
260    /// the captures, each inline under its own layout.
261    Closure {
262        function: FunctionId,
263        captures: Vec<LayoutId>,
264    },
265    /// Payload word 0 is the cell's lock; the words after it are the wrapped
266    /// value, inline under `value`'s own layout.
267    ///
268    /// [ADR 0008](../../../docs/adr/0008-concurrent-task-execution.md) makes
269    /// `Shared<T>` the one handle that crosses a task boundary by *sharing*
270    /// rather than by copying, and this is where that sharing is: an ordinary
271    /// object in the run's one heap, whose lock is one of its own words rather
272    /// than an entry in a table keyed by address. So there is nothing to
273    /// reclaim when a cell dies and no second lifetime running beside the
274    /// collector's — a cell is swept like anything else.
275    ///
276    /// The value is **inline** for the reason a struct's fields are: a value's
277    /// words are where the value is. What that buys here is that `lock` hands
278    /// its closure the address of [`SHARED_VALUE`] — the ordinary `var` alias
279    /// the language already describes — and nothing is copied in or out.
280    ///
281    /// One layout per wrapped-value layout, interned the way `Array<T>` is.
282    /// The lock word is an `Int` in the flattened map, so a collection traces
283    /// nothing from it; the arrangement is [`Shape::Closure`]'s — one untraced
284    /// word, then a value inline — which is why it needs no idea the collector
285    /// did not already have.
286    Shared { value: LayoutId },
287    /// Payload word 0 is a [`LayoutId`]; the words after it are a value of
288    /// that layout, inline.
289    ///
290    /// This is what an intentionally erased value occupies, and it is the
291    /// only thing it is: `dyn Trait`, and a Host result a schema declared
292    /// `Any`. Erasure is where a value stops having a static width, and a
293    /// heap object is where a value without a static width lives.
294    ///
295    /// A recursive layout used to share this shape, and ADR 0035 took that
296    /// away: an implicitly recursive value type is a checker error, so
297    /// erasure and recursion no longer share a mechanism and this has one
298    /// meaning.
299    Boxed,
300}
301
302/// The description of one family of values.
303#[derive(Clone, Debug, PartialEq, Eq)]
304pub struct Layout {
305    /// What a boundary calls a value of this family.
306    ///
307    /// Qualified for a declared type — `m.geometry.Point` — because a layout
308    /// is an identity and two modules may each declare a `Point`. A rendering
309    /// shortens it, which is what the public `Display` does with the same
310    /// string.
311    pub name: Arc<str>,
312    pub shape: Shape,
313    /// The words a value of this family occupies in a frame, or inline in a
314    /// heap object's payload.
315    ///
316    /// Cached rather than computed, because computing it means walking the
317    /// layout table and every reader of it is on a path where that would be
318    /// the expensive part: a frame's reference map, a copy's width, a
319    /// collection's walk.
320    ///
321    /// One [`Repr::Ref`] for every family that lives in the heap, which is
322    /// what "a value has a static width or it lives in the heap" means when
323    /// written down.
324    pub words: Vec<Repr>,
325}
326
327impl Layout {
328    /// The layout the sweeper writes into a reclaimed run of words.
329    pub fn free() -> Layout {
330        Layout {
331            name: Arc::from("<free>"),
332            shape: Shape::Free,
333            words: Vec::new(),
334        }
335    }
336
337    /// A one-word family.
338    pub fn word(name: impl Into<Arc<str>>, repr: Repr) -> Layout {
339        Layout {
340            name: name.into(),
341            shape: Shape::Word(repr),
342            words: vec![repr],
343        }
344    }
345
346    /// A family that lives in the heap, so a value of it is one reference.
347    pub fn object(name: impl Into<Arc<str>>, shape: Shape) -> Layout {
348        Layout {
349            name: name.into(),
350            shape,
351            words: vec![Repr::Ref],
352        }
353    }
354
355    /// An inline family, whose words the caller has already flattened.
356    pub fn inline(name: impl Into<Arc<str>>, shape: Shape, words: Vec<Repr>) -> Layout {
357        Layout {
358            name: name.into(),
359            shape,
360            words,
361        }
362    }
363
364    /// How many words a value of this family occupies.
365    pub fn width(&self) -> u32 {
366        self.words.len() as u32
367    }
368
369    /// Whether a value of this family is the address of an object rather than
370    /// inline words.
371    ///
372    /// The question is asked of the *shape*, because the width cannot answer
373    /// it and the earlier version of this — "one word wide, and that word is a
374    /// [`Repr::Ref`]" — got it wrong in a way nothing caught.
375    /// `struct Error { message: String }` is one `Repr::Ref` word wide and is
376    /// an **inline struct**, not a reference to an `Error` somewhere; the one
377    /// word it occupies is its field, and reading it as the value's own
378    /// address reads the declaration away. A one-field struct is not a rare
379    /// shape, and the language ships one.
380    ///
381    /// So: a struct and an enum are inline at every width, a scalar is one
382    /// address exactly when its `Repr` is [`Repr::Ref`], and every remaining
383    /// family lives in the heap and is one. [`Shape::Free`] is not a value and
384    /// answers no.
385    ///
386    /// What turns on it is every place a walk has to choose between reading
387    /// the words in front of it and following them: the boundary's erasure
388    /// path, the ordering a `Set` and a `Map` are sorted by, and equality.
389    pub fn is_one_address(&self) -> bool {
390        match &self.shape {
391            Shape::Word(repr) => repr.is_ref(),
392            Shape::Struct { .. } | Shape::Enum { .. } | Shape::Free => false,
393            _ => true,
394        }
395    }
396
397    /// How many payload words an object of this layout with header length
398    /// `len` occupies.
399    ///
400    /// `len` means different things to different shapes — a byte count for a
401    /// string, an element count for an array, and nothing at all for a
402    /// struct — and this is the one place that difference is written down.
403    ///
404    /// A `Struct` or an `Enum` answers its own inline words, because a boxed
405    /// value's payload *is* the value.
406    pub fn payload_words(&self, len: u32, layouts: &[Layout]) -> u32 {
407        if let Some(fixed) = self.fixed_payload_words(layouts) {
408            return fixed;
409        }
410        match &self.shape {
411            Shape::Free => len,
412            Shape::Str | Shape::Bytes => len.div_ceil(8),
413            Shape::Elements { elem, .. } | Shape::Members { elem } => {
414                len * layouts[elem.index()].width()
415            }
416            Shape::Entries { key, value } => {
417                len * (layouts[key.index()].width() + layouts[value.index()].width())
418            }
419            // One word of `LayoutId` and then whatever it named, whose width
420            // this layout cannot know: the header's `len` carries it.
421            Shape::Boxed => 1 + len,
422            // Every shape whose payload the header does not decide answered
423            // above.
424            _ => self.width(),
425        }
426    }
427
428    /// The same computation, checked against a `len` this compiler did not
429    /// choose.
430    ///
431    /// [`Layout::payload_words`] does the multiplication in `u32`, which is
432    /// exactly right for the `len` every internal caller passes it — a
433    /// header's own length field, or a count `crate::lower` computed and
434    /// which [`mod@crate::verify`] has already agreed is small enough. This
435    /// is for the one caller that cannot make that assumption:
436    /// `cove_runtime`'s `Machine::allocate` takes a `len` an `Inst::Alloc`
437    /// operand supplies, and one of its three `Len` forms is a slot the
438    /// running program computed at run time. A `len` that large is rare, but
439    /// `u32 * u32` wraps silently rather than answering wrong loudly, and a
440    /// wrapped payload size is an under-allocation followed by writes sized
441    /// by the caller's original, larger `len` — so this does the same match
442    /// in `u64`, wide enough that `len` and a stride each at most `u32::MAX`
443    /// cannot overflow the multiply, and answers `None` rather than a
444    /// truncated `u32` when the true result does not fit one.
445    ///
446    /// Kept beside [`Layout::payload_words`] rather than folded into it: the
447    /// two are the same rule at two widths on purpose, not a second, weaker
448    /// copy of the first. Widening the arithmetic every internal caller
449    /// already trusts to be in range would pay a `u64` multiply and a range
450    /// check on the collector's sweep of every live object for a case that
451    /// caller cannot hit, on the one path this workspace measures for
452    /// allocation cost.
453    pub fn try_payload_words(&self, len: u32, layouts: &[Layout]) -> Option<u32> {
454        if let Some(fixed) = self.fixed_payload_words(layouts) {
455            return Some(fixed);
456        }
457        // `checked_mul`/`checked_add` throughout rather than the plain `*`
458        // and `+` a proof that `len` and one stride each at most `u32::MAX`
459        // cannot overflow `u64` would justify: `Entries`' stride is a *sum*
460        // of two widths first, and nothing here bounds a layout's width
461        // short of `u32::MAX` the way it bounds `len`. Provable headroom for
462        // one shape is not a reason to assume it for another.
463        let words: Option<u64> = match &self.shape {
464            Shape::Free => Some(u64::from(len)),
465            Shape::Str | Shape::Bytes => Some(u64::from(len).div_ceil(8)),
466            Shape::Elements { elem, .. } | Shape::Members { elem } => {
467                u64::from(len).checked_mul(u64::from(layouts[elem.index()].width()))
468            }
469            Shape::Entries { key, value } => {
470                let stride = u64::from(layouts[key.index()].width())
471                    .checked_add(u64::from(layouts[value.index()].width()))?;
472                u64::from(len).checked_mul(stride)
473            }
474            // One word of `LayoutId` and then whatever it named, whose width
475            // this layout cannot know: the header's `len` carries it.
476            Shape::Boxed => Some(1 + u64::from(len)),
477            // Every shape whose payload the header does not decide answered
478            // above.
479            _ => Some(u64::from(self.width())),
480        };
481        u32::try_from(words?).ok()
482    }
483
484    /// The same, where the answer is a fact about the layout alone.
485    ///
486    /// `None` for a shape whose payload the header's `len` decides: a
487    /// string's bytes, a run of elements, the value inside a box. The two are
488    /// separate questions because a *static* reader has no header to consult.
489    /// [`mod@crate::verify`] bounds a field access against the object whose
490    /// layout it can prove, and it can only do so where proving the layout is
491    /// enough — for a `Shape::Str` or a `Shape::Elements` it would still be
492    /// guessing at the length.
493    pub fn fixed_payload_words(&self, layouts: &[Layout]) -> Option<u32> {
494        match &self.shape {
495            // A struct or an enum stored as an object is that value's own
496            // inline words, because a boxed value's payload *is* the value.
497            Shape::Word(_) | Shape::Struct { .. } | Shape::Enum { .. } => Some(self.width()),
498            // A length and a store reference, whatever the store holds. Both
499            // growable owners answer the same two words for the same reason;
500            // see `Shape::ByteBuffer`.
501            Shape::Vector { .. } | Shape::ByteBuffer => Some(2),
502            // The lock word and then the value, inline. A fact about the
503            // layout alone, which is what lets [`mod@crate::verify`] bound the
504            // address a `lock` forms without a header to read.
505            Shape::Shared { value } => Some(SHARED_VALUE + layouts[value.index()].width()),
506            Shape::Closure { captures, .. } => Some(
507                1 + captures
508                    .iter()
509                    .map(|id| layouts[id.index()].width())
510                    .sum::<u32>(),
511            ),
512            Shape::Free
513            | Shape::Str
514            | Shape::Bytes
515            | Shape::Elements { .. }
516            | Shape::Members { .. }
517            | Shape::Entries { .. }
518            | Shape::Boxed => None,
519        }
520    }
521
522    /// Whether an object of this layout can hold a reference at all.
523    ///
524    /// The collector uses it to skip an object without looking at any of its
525    /// words: a string, an `Array<Int>` and a boxed scalar are all leaves.
526    pub fn may_hold_refs(&self, layouts: &[Layout]) -> bool {
527        match &self.shape {
528            Shape::Free | Shape::Str | Shape::Bytes => false,
529            Shape::Word(repr) => repr.is_ref(),
530            Shape::Struct { .. } | Shape::Enum { .. } => {
531                self.words.iter().any(|repr| repr.is_ref())
532            }
533            Shape::Elements { elem, .. } | Shape::Members { elem } => {
534                layouts[elem.index()].words.iter().any(|r| r.is_ref())
535            }
536            Shape::Entries { key, value } => {
537                layouts[key.index()].words.iter().any(|r| r.is_ref())
538                    || layouts[value.index()].words.iter().any(|r| r.is_ref())
539            }
540            // Word 1 is always a reference to the store. True of both growable
541            // owners, and of a `ByteBuffer` even though its *store* holds no
542            // references at all: what the collector must follow is the owner's
543            // one word naming that store, and word 0 is a length it must not.
544            Shape::Vector { .. } | Shape::ByteBuffer => true,
545            // The lock word is never one, so a `Shared<Int>` is a leaf and a
546            // `Shared<Metrics>` is whatever `Metrics` is.
547            Shape::Shared { value } => layouts[value.index()].words.iter().any(|r| r.is_ref()),
548            Shape::Closure { captures, .. } => captures
549                .iter()
550                .any(|id| layouts[id.index()].words.iter().any(|r| r.is_ref())),
551            // What a box holds is named by its own first payload word, so the
552            // collector has to look.
553            Shape::Boxed => true,
554        }
555    }
556
557    /// The field `name`, if this is a struct-shaped layout.
558    pub fn field(&self, name: &str) -> Option<&Field> {
559        match &self.shape {
560            Shape::Struct { fields, .. } => fields.iter().find(|field| &*field.name == name),
561            _ => None,
562        }
563    }
564
565    /// The case index `name` is at, if this is an enum-shaped layout.
566    pub fn case(&self, name: &str) -> Option<u32> {
567        match &self.shape {
568            Shape::Enum { cases, .. } => cases
569                .iter()
570                .position(|case| &*case.name == name)
571                .map(|at| at as u32),
572            _ => None,
573        }
574    }
575
576    /// Whether this is an `export opaque struct`.
577    pub fn is_opaque(&self) -> bool {
578        matches!(self.shape, Shape::Struct { opaque: true, .. })
579    }
580}
581
582/// Lays out a struct's fields, answering the fields and the flattened words.
583///
584/// Fields are placed in declaration order with no padding: a word is a word
585/// and there is nothing to align.
586pub fn struct_layout(
587    fields: &[(Arc<str>, LayoutId)],
588    layouts: &[Layout],
589) -> (Vec<Field>, Vec<Repr>) {
590    let mut placed = Vec::with_capacity(fields.len());
591    let mut words = Vec::new();
592    for (name, layout) in fields {
593        placed.push(Field {
594            name: name.clone(),
595            layout: *layout,
596            at: words.len() as u32,
597        });
598        words.extend_from_slice(&layouts[layout.index()].words);
599    }
600    (placed, words)
601}
602
603/// Lays out an enum's payload region, answering the cases and the region's
604/// words.
605///
606/// The one constraint is that **every case that uses a payload word agrees on
607/// that word's `Repr`**, because one static reference map has to be right
608/// whatever case a value holds. Each case's parts are placed greedily into
609/// the lowest run of payload words that is free for this case and either
610/// unassigned or already assigned the same `Repr`s.
611///
612/// A region can therefore be wider than the widest case — `A(Int, String)`
613/// and `B(Float)` need four words between them, not three. That is the price
614/// of a map a collection can read without asking which case a value is in.
615pub fn enum_layout(
616    cases: &[(Arc<str>, Vec<LayoutId>)],
617    layouts: &[Layout],
618) -> (Vec<Case>, Vec<Repr>) {
619    let mut region: Vec<Repr> = Vec::new();
620    let mut placed = Vec::with_capacity(cases.len());
621    for (name, parts) in cases {
622        let mut taken: Vec<bool> = vec![false; region.len()];
623        let mut placed_parts = Vec::with_capacity(parts.len());
624        for id in parts {
625            let want = &layouts[id.index()].words;
626            let at = fit(&mut region, &mut taken, want);
627            placed_parts.push(Part {
628                layout: *id,
629                at: at as u32,
630            });
631        }
632        placed.push(Case {
633            name: name.clone(),
634            parts: placed_parts,
635        });
636    }
637    (placed, region)
638}
639
640/// The lowest offset in `region` where `want` fits: free for this case, and
641/// either unassigned or already the same words. Extends the region if it has
642/// to.
643fn fit(region: &mut Vec<Repr>, taken: &mut Vec<bool>, want: &[Repr]) -> usize {
644    let mut at = 0;
645    'search: loop {
646        for (i, repr) in want.iter().enumerate() {
647            let word = at + i;
648            if word < region.len() && (taken[word] || region[word] != *repr) {
649                at += 1;
650                continue 'search;
651            }
652        }
653        break;
654    }
655    for (i, repr) in want.iter().enumerate() {
656        let word = at + i;
657        if word == region.len() {
658            region.push(*repr);
659            taken.push(false);
660        }
661        taken[word] = true;
662    }
663    at
664}
665
666#[cfg(test)]
667mod tests {
668    use super::*;
669
670    fn table() -> Vec<Layout> {
671        vec![
672            Layout::free(),
673            Layout::word("Int", Repr::Int),
674            Layout::word("Float", Repr::Float),
675            Layout::object("String", Shape::Str),
676        ]
677    }
678
679    const INT: LayoutId = LayoutId(1);
680    const FLOAT: LayoutId = LayoutId(2);
681    const STR: LayoutId = LayoutId(3);
682
683    #[test]
684    fn a_struct_is_the_words_of_its_fields() {
685        let layouts = table();
686        let (fields, words) =
687            struct_layout(&[(Arc::from("x"), INT), (Arc::from("y"), INT)], &layouts);
688        assert_eq!(words, vec![Repr::Int, Repr::Int]);
689        assert_eq!(fields[1].at, 1);
690    }
691
692    #[test]
693    fn nesting_is_inline_and_recursive() {
694        let mut layouts = table();
695        let (fields, words) =
696            struct_layout(&[(Arc::from("x"), INT), (Arc::from("y"), INT)], &layouts);
697        layouts.push(Layout::inline(
698            "Point",
699            Shape::Struct {
700                fields,
701                opaque: false,
702            },
703            words,
704        ));
705        let point = LayoutId(layouts.len() as u32 - 1);
706
707        let (fields, words) = struct_layout(
708            &[(Arc::from("from"), point), (Arc::from("to"), point)],
709            &layouts,
710        );
711        // Four words and no indirection: `l.to.x` is a slot offset.
712        assert_eq!(words, vec![Repr::Int; 4]);
713        assert_eq!(fields[1].at, 2);
714    }
715
716    /// ADR 0001's rule, as a layout: the `Point` words are inline and the
717    /// `Vector` is one address, so one copy makes the first independent and
718    /// leaves the second shared.
719    #[test]
720    fn a_struct_holding_a_vector_is_words_then_an_address() {
721        let mut layouts = table();
722        layouts.push(Layout::object("Vector", Shape::Vector { elem: INT }));
723        let vector = LayoutId(layouts.len() as u32 - 1);
724        let (_, words) = struct_layout(
725            &[
726                (Arc::from("a"), INT),
727                (Arc::from("b"), FLOAT),
728                (Arc::from("v"), vector),
729            ],
730            &layouts,
731        );
732        assert_eq!(words, vec![Repr::Int, Repr::Float, Repr::Ref]);
733    }
734
735    #[test]
736    fn an_enums_payload_words_agree_across_its_cases() {
737        let layouts = table();
738        // `enum E { A(Int, String), B(Float) }`. `B` can use neither of `A`'s
739        // words, so its `Float` takes a third.
740        let (cases, payload) = enum_layout(
741            &[
742                (Arc::from("A"), vec![INT, STR]),
743                (Arc::from("B"), vec![FLOAT]),
744            ],
745            &layouts,
746        );
747        assert_eq!(payload, vec![Repr::Int, Repr::Ref, Repr::Float]);
748        assert_eq!(cases[0].parts[0].at, 0);
749        assert_eq!(cases[0].parts[1].at, 1);
750        assert_eq!(cases[1].parts[0].at, 2);
751    }
752
753    #[test]
754    fn two_cases_of_one_shape_share_their_words() {
755        let layouts = table();
756        let (cases, payload) = enum_layout(
757            &[(Arc::from("Ok"), vec![INT]), (Arc::from("Err"), vec![INT])],
758            &layouts,
759        );
760        assert_eq!(payload, vec![Repr::Int]);
761        assert_eq!(cases[1].parts[0].at, 0);
762    }
763
764    #[test]
765    fn a_case_with_no_payload_costs_nothing() {
766        let layouts = table();
767        let (cases, payload) = enum_layout(
768            &[(Arc::from("None"), vec![]), (Arc::from("Some"), vec![STR])],
769            &layouts,
770        );
771        assert_eq!(payload, vec![Repr::Ref]);
772        assert!(cases[0].parts.is_empty());
773    }
774
775    #[test]
776    fn a_family_that_lives_in_the_heap_is_one_reference() {
777        let layouts = table();
778        assert_eq!(layouts[STR.index()].words, vec![Repr::Ref]);
779        assert!(layouts[STR.index()].is_one_address());
780        assert!(!layouts[INT.index()].is_one_address());
781    }
782
783    /// The case the width cannot tell apart from a reference, and the reason
784    /// the question is asked of the shape: a struct of one `String` field is
785    /// one `Repr::Ref` word and is still the struct, not its field.
786    #[test]
787    fn a_one_field_struct_is_inline_however_wide_its_field_is() {
788        let layouts = table();
789        let error = Layout::inline(
790            "Error",
791            Shape::Struct {
792                fields: vec![Field {
793                    name: Arc::from("message"),
794                    layout: STR,
795                    at: 0,
796                }],
797                opaque: false,
798            },
799            vec![Repr::Ref],
800        );
801        assert_eq!(error.words, layouts[STR.index()].words);
802        assert!(!error.is_one_address());
803    }
804
805    #[test]
806    fn a_string_pays_one_word_per_eight_bytes() {
807        let layouts = table();
808        let str_layout = &layouts[STR.index()];
809        assert_eq!(str_layout.payload_words(0, &layouts), 0);
810        assert_eq!(str_layout.payload_words(9, &layouts), 2);
811        assert!(!str_layout.may_hold_refs(&layouts));
812    }
813
814    /// A cell is a lock word and the value, inline — and both halves of that
815    /// are answers a reader takes without a header: the width, and whether a
816    /// collection has anything to follow.
817    #[test]
818    fn a_cell_is_a_lock_word_and_the_value_inline() {
819        let mut layouts = table();
820        let (fields, words) =
821            struct_layout(&[(Arc::from("x"), INT), (Arc::from("y"), STR)], &layouts);
822        layouts.push(Layout::inline(
823            "Metrics",
824            Shape::Struct {
825                fields,
826                opaque: false,
827            },
828            words,
829        ));
830        let metrics = LayoutId(layouts.len() as u32 - 1);
831
832        let scalar = Layout::object("Shared", Shape::Shared { value: INT });
833        assert_eq!(scalar.fixed_payload_words(&layouts), Some(2));
834        assert_eq!(scalar.payload_words(0, &layouts), 2);
835        // The lock word is not a reference and neither is an `Int`, so a
836        // collection skips the object without reading a word of it.
837        assert!(!scalar.may_hold_refs(&layouts));
838        // And a value of one is one address, whatever it wraps.
839        assert!(scalar.is_one_address());
840
841        let held = Layout::object("Shared", Shape::Shared { value: metrics });
842        assert_eq!(held.fixed_payload_words(&layouts), Some(3));
843        assert!(held.may_hold_refs(&layouts));
844    }
845
846    #[test]
847    fn an_array_of_multiword_elements_is_len_times_the_width() {
848        let mut layouts = table();
849        let (fields, words) =
850            struct_layout(&[(Arc::from("x"), INT), (Arc::from("y"), INT)], &layouts);
851        layouts.push(Layout::inline(
852            "Point",
853            Shape::Struct {
854                fields,
855                opaque: false,
856            },
857            words,
858        ));
859        let point = LayoutId(layouts.len() as u32 - 1);
860        layouts.push(Layout::object(
861            "Array",
862            Shape::Elements {
863                elem: point,
864                growable: false,
865            },
866        ));
867        let array = &layouts[layouts.len() - 1];
868        assert_eq!(array.payload_words(5, &layouts), 10);
869        assert!(!array.may_hold_refs(&layouts));
870    }
871
872    /// [`Layout::try_payload_words`] is the widened arithmetic
873    /// `Machine::allocate` checks an instruction operand against; it must
874    /// answer exactly what [`Layout::payload_words`] answers for every `len`
875    /// that fits, or the two have drifted apart.
876    #[test]
877    fn try_payload_words_agrees_with_payload_words_when_it_fits() {
878        let mut layouts = table();
879        let (fields, words) =
880            struct_layout(&[(Arc::from("x"), INT), (Arc::from("y"), INT)], &layouts);
881        layouts.push(Layout::inline(
882            "Point",
883            Shape::Struct {
884                fields,
885                opaque: false,
886            },
887            words,
888        ));
889        let point = LayoutId(layouts.len() as u32 - 1);
890        layouts.push(Layout::object(
891            "Array",
892            Shape::Elements {
893                elem: point,
894                growable: false,
895            },
896        ));
897        let array = layouts.last().unwrap();
898        for len in [0, 1, 5, 1000] {
899            assert_eq!(
900                array.try_payload_words(len, &layouts),
901                Some(array.payload_words(len, &layouts)),
902            );
903        }
904
905        let str_layout = &layouts[STR.index()];
906        for len in [0, 1, 9, 64] {
907            assert_eq!(
908                str_layout.try_payload_words(len, &layouts),
909                Some(str_layout.payload_words(len, &layouts)),
910            );
911        }
912    }
913
914    /// A count large enough that `count * stride` does not fit `u32`, though
915    /// the count alone does. This is what a `Len::Slot` operand can hand
916    /// `Machine::allocate` — the count is a value the running program
917    /// computed, not one this compiler bounded — and it must be rejected
918    /// rather than silently wrapped to a small, wrong allocation size.
919    #[test]
920    fn try_payload_words_rejects_a_multiply_that_overflows_u32() {
921        let mut layouts = table();
922        let (fields, words) =
923            struct_layout(&[(Arc::from("x"), INT), (Arc::from("y"), INT)], &layouts);
924        layouts.push(Layout::inline(
925            "Point",
926            Shape::Struct {
927                fields,
928                opaque: false,
929            },
930            words,
931        ));
932        let point = LayoutId(layouts.len() as u32 - 1);
933        layouts.push(Layout::object(
934            "Array",
935            Shape::Elements {
936                elem: point,
937                growable: false,
938            },
939        ));
940        let array = layouts.last().unwrap();
941        // `Point` is two words wide, so `u32::MAX * 2` overflows `u32` even
942        // though `u32::MAX` itself does not.
943        assert_eq!(array.try_payload_words(u32::MAX, &layouts), None);
944    }
945
946    /// `Shape::Boxed`'s `1 + len` overflows too, at the one `len` that makes
947    /// it possible.
948    #[test]
949    fn try_payload_words_rejects_a_boxed_header_plus_len_overflow() {
950        let layouts = table();
951        let boxed = Layout::object("Boxed", Shape::Boxed);
952        assert_eq!(boxed.try_payload_words(u32::MAX, &layouts), None);
953        assert_eq!(
954            boxed.try_payload_words(u32::MAX - 1, &layouts),
955            Some(u32::MAX)
956        );
957    }
958}