pub enum Shape {
Show 14 variants
Free,
Word(Repr),
Struct {
fields: Vec<Field>,
opaque: bool,
},
Enum {
cases: Vec<Case>,
payload: Vec<Repr>,
},
Str,
Bytes,
Elements {
elem: LayoutId,
growable: bool,
},
Vector {
elem: LayoutId,
},
ByteBuffer,
Members {
elem: LayoutId,
},
Entries {
key: LayoutId,
value: LayoutId,
},
Closure {
function: FunctionId,
captures: Vec<LayoutId>,
},
Shared {
value: LayoutId,
},
Boxed,
}Expand description
How a family’s words are arranged.
Variants§
Free
A run of free words. Not a value; see LayoutId::FREE.
Word(Repr)
One word of the given interpretation.
The width-one case of the whole model, and the one every scalar is.
Struct
Consecutive fields, inline.
Fields
opaque: boolWhether the declaration was export opaque struct.
A fact about a declaration on a table that otherwise describes families, and it is here because nothing downstream can derive it: by the time a value is a word, the declaration is gone. What reads it is a rendering, which shows an opaque value’s name and nothing else — its fields are the declaring module’s business, and a rendering is read by whoever the string reaches.
Enum
Word 0 is the case index; the words after it are the payload region.
The region is wide enough for every case, and its per-word Reprs
are in Shape::Enum::payload. Every case that uses a payload word
agrees on that word’s Repr — the lowering assigns offsets under
that constraint — because one static reference map has to be right
whatever case a value holds. A word cannot be a reference in one case
and an integer in another.
Two things follow. Constructing a case zeroes the payload words it does not fill, so a reference word belonging to another case reads null. And a collection never reads the discriminant: the region’s map is static, which is one fewer thing that can be wrong.
The cost is a region that can be wider than the widest case. That is the price of a static map, paid in words rather than in a run-time question.
Str
UTF-8 bytes, eight to a word, little end first. The header’s len is
the byte count, so the payload is len.div_ceil(8) words and the
trailing bytes of the last word are zero.
Bytes
Packed bytes, eight to a word, little end first, exactly like
Shape::Str’s payload — but not yet a String.
ADR 0051
gives lowering an internal construction run: an object crate::Inst::AllocBytes
allocates, crate::Inst::WriteByte and crate::Inst::CopyBytes fill, and
crate::Inst::FinishString turns into a String without copying. It is an
IR/runtime value, not a Cove type — no declaration names it and no
source expression produces one.
The header’s len is a byte count, the same as Shape::Str’s, which
is what lets the runtime’s Machine::relabel turn a finished run into
a String of the same header length without touching a payload word.
Its payload holds no references — arbitrary
written bytes are never a LayoutId or an address — so a run that is
only half filled is exactly as safe for the collector to walk as a
finished one: Layout::may_hold_refs answers false for it below,
the same answer it gives Shape::Str.
This is deliberately not Shape::Str. ADR 0051 says “a run under
construction is not a String”, and giving it a different shape is how
that is enforced without a runtime tag check on every ordinary
reference operation: is_string at
crates/cove-runtime/src/vm/builtins.rs:564 matches on Shape::Str
alone, so a Bytes run fails it and every place that asks “is this
really a string” — the Host boundary, a call argument, a captured
value — refuses it for the ordinary reason a Str-only match already
refuses anything else, not because of a tag this shape adds.
Elements
The header’s len elements, each elem’s words, contiguous.
One shape covers Array<T> for every T, and is also what a
Shape::Vector stores its elements in — growable says which of
the two an object is.
Vector
Payload word 0 is the element count; word 1 is a reference to the
Shape::Elements object holding them.
The indirection is what a growable value needs and an immutable one
does not. A Vector’s identity is observable — is is defined for it
and mutation through one copy is visible through every other — so
growing must not move the object a program is holding. The header
stays where it is and the store beneath it is replaced by a larger
one. An Array needs none of that and pays none of it.
ByteBuffer
Payload word 0 is the logical length in bytes; word 1 is a reference to
a Shape::Bytes store whose own header length is its capacity.
ADR 0052’s
stable owner, for bytes. The reason it is two objects rather than one
is the reason Shape::Vector is: growth replaces the store, and the
owner does not move, so every alias and every var address to it is
still the same address afterwards. A run that grew by reallocating
itself would leave a formatter’s var out parameter pointing at the
object it used to be, which is exactly the failure the ADR’s
“if the object itself moves when it grows, every alias and var
address to it goes stale” names.
The split is also what keeps capacity out of the language. A store’s
header length has to be its capacity, because the allocator and the
collector walk whole physical objects; the logical length lives in
the owner, so the spare room [length, capacity) is unobservable and
exceeding an initial capacity grows rather than changing what a
program answers.
This is the byte case of what Shape::Vector already is for word
elements. The two differ in the storage unit and in the reference map
and in nothing else: a byte run packs eight bytes to a word and holds
no references, an element run stores values at the element layout’s
stride and is traced by that layout. ADR 0052’s generic Buffer<E>
will subsume both, and this is deliberately not generalised before
the second case exists — the ADR’s own reason for doing bytes first is
that a shared abstraction with one instance is a guess about the
second.
Word 1 is always a reference, so Layout::may_hold_refs answers
true and a collection traces word 1 and only word 1: word 0 is a
length, and reading it as an address would chase an integer. The
payload is a fixed two words whatever the store’s capacity, exactly as
Shape::Vector’s is — Layout::fixed_payload_words answers 2
rather than None, which is what lets a static reader bound an access
into an owner without a header to consult.
Members
The header’s len members, ascending and distinct.
Entries
The header’s len entries — key then value — ascending by key.
Closure
Payload word 0 is the callee’s FunctionId; the words after it are
the captures, each inline under its own layout.
Payload word 0 is the cell’s lock; the words after it are the wrapped
value, inline under value’s own layout.
ADR 0008 makes
Shared<T> the one handle that crosses a task boundary by sharing
rather than by copying, and this is where that sharing is: an ordinary
object in the run’s one heap, whose lock is one of its own words rather
than an entry in a table keyed by address. So there is nothing to
reclaim when a cell dies and no second lifetime running beside the
collector’s — a cell is swept like anything else.
The value is inline for the reason a struct’s fields are: a value’s
words are where the value is. What that buys here is that lock hands
its closure the address of SHARED_VALUE — the ordinary var alias
the language already describes — and nothing is copied in or out.
One layout per wrapped-value layout, interned the way Array<T> is.
The lock word is an Int in the flattened map, so a collection traces
nothing from it; the arrangement is Shape::Closure’s — one untraced
word, then a value inline — which is why it needs no idea the collector
did not already have.
Boxed
Payload word 0 is a LayoutId; the words after it are a value of
that layout, inline.
This is what an intentionally erased value occupies, and it is the
only thing it is: dyn Trait, and a Host result a schema declared
Any. Erasure is where a value stops having a static width, and a
heap object is where a value without a static width lives.
A recursive layout used to share this shape, and ADR 0035 took that away: an implicitly recursive value type is a checker error, so erasure and recursion no longer share a mechanism and this has one meaning.
Trait Implementations§
impl Eq for Shape
impl StructuralPartialEq for Shape
Auto Trait Implementations§
impl Freeze for Shape
impl RefUnwindSafe for Shape
impl Send for Shape
impl Sync for Shape
impl Unpin for Shape
impl UnsafeUnpin for Shape
impl UnwindSafe for Shape
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.