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}