IR Reference
This document is the authoritative reference for the Ashes intermediate representation (IR). The IR is a flat, register-based instruction set defined in Ashes.Semantics/Ir.cs. The Lowering pass converts the typed AST into an IrProgram, which the LLVM backend consumes.
Program Structure
IrProgram
The root container for a compiled Ashes program:
| Field | Type | Description |
|---|---|---|
EntryFunction | IrFunction | Top-level expression (_start_main) |
Functions | List<IrFunction> | Lifted lambdas and named functions |
StringLiterals | List<IrStringLiteral> | All string constants with labels |
ExternalFunctions | List<IrExternalFunction> | Validated external declarations used by lowering and linking |
ExternalOpaqueTypes | Set<string> | Opaque native types referenced by the program |
UsesPrintInt | bool | Whether PrintInt is used |
UsesPrintStr | bool | Whether PrintStr is used |
UsesPrintBool | bool | Whether PrintBool is used |
UsesConcatStr | bool | Whether ConcatStr is used |
UsesClosures | bool | Whether closures are created |
UsesAsync | bool | Whether async/await is used |
CapabilityHandlerGlobals | int | Number of declared capabilities (one handler-evidence global each) |
TraitEvidence | TraitEvidenceAnnotations | Stable dictionary-ABI and concrete-resolution facts for reports |
The Uses* flags allow the backend to omit unused runtime helpers.
IrFunction
A single function with a flat instruction list:
| Field | Type | Description |
|---|---|---|
Label | string | Unique name (e.g., _start_main, lambda_0) |
Instructions | List<IrInst> | Linear instruction sequence |
LocalCount | int | Number of local variable stack slots |
TempCount | int | Number of temporary registers |
HasEnvAndArgParams | bool | true for lambdas (implicit env+arg at slots 0, 1) |
Coroutine | CoroutineInfo? | Non-null for async coroutine functions |
LocalNames | IReadOnlyDictionary<int, string>? | Optional source names for local slots |
LocalTypes | IReadOnlyDictionary<int, TypeRef>? | Optional inferred types for local slots |
Origin | IrFunctionOrigin? | Stable source/generated lineage for compiler reporting |
LifetimesPlaced | bool | true once lifetime markers are placed; the program-wide pass skips these |
Function origin metadata
Production lowering assigns an IrFunctionOrigin to the entry function and every lowered or synthesized IrFunction. This is immutable reporting metadata, not part of execution semantics:
GeneratedLabelis the unique emitted IR label.Kindis a typedIrFunctionOriginKind, distinguishing source functions, closure helpers, reuse and parallel specializations, mutual-recursion dispatchers/wrappers, coroutines, external thunks, closure-environment normalizers, structural droppers, and deep-copy helpers.Source, when present, is aSourceFunctionOrigincontaining the declaration's source name, module-qualified name where known, declaration location, and deterministic combined-source offset.ParentGeneratedLabellinks a generated artifact to its immediate generated parent. Source-derived artifacts retain the sameSourceidentity.CompilerOwnergives shared artifacts without one source-function parent a typed program, type, external, runtime-layout, or mutual-recursion-group owner.StableDiscriminatorandGenerationLocationdistinguish multiple generated artifacts of the same kind without making callers parse label suffixes.
FunctionOwnershipSummary uses the same SourceFunctionOrigin boundary, while its internal analysis lookup remains keyed by binder identity. This keeps source and qualified-name filtering independent of compiler-generated labels.
IR rewrites preserve Origin through record copies, including the optimizer and Perceus lifetime placement. The LLVM backend deliberately ignores the metadata, so adding or retaining origins does not alter generated code. Manually constructed IR used by tests or embedding callers may leave Origin unset.
CoroutineInfo
Metadata for coroutine functions generated from async blocks:
| Field | Type | Description |
|---|---|---|
StateCount | int | Number of states (N await points = N+1 states) |
StateStructSize | int | Total size of the task/state struct in bytes |
CaptureCount | int | Number of captured environment variables |
The entry function has HasEnvAndArgParams: false. Lambda functions have true, meaning slot 0 holds the closure environment pointer and slot 1 holds the argument.
IrStringLiteral
Maps a label to a string constant:
| Field | Type | Description |
|---|---|---|
Label | string | Reference name (e.g., str_0) |
Value | string | The string content |
Referenced by LoadConstStr. The backend emits these as read-only globals using the ordinary String payload layout with the view bit set.
Text dumps and function selection
--emit-ir lowered and --emit-ir final use the same deterministic text shape. Lifted functions are rendered in their existing IrProgram.Functions order, followed by EntryFunction; no label sort or instruction ordinal is introduced. A function filter is an ordinal, ASCII-case-insensitive substring match over the emitted label and, when origin metadata exists, its generated label, source name, qualified source name, and immediate generated-parent label. An absent filter selects every function.
Each ordinary instruction prints its opcode in a 22-column field followed by its meaningful operands. Null, false, empty-string, and optional integer -1 values are omitted, while zero and long -1 remain visible. Collections print their stable element count rather than their contents. Source locations use path:line:column; labels are left-aligned as control-flow anchors. Trait dictionary and resolution annotations precede the functions in their stored order. The pure-Ashes implementation models this format exhaustively so its output does not depend on runtime reflection.
Registers and Locals
Instructions use integer indices to address values:
- Temporaries (
Target,Source,Left,Right) — virtual registers allocated per-function byNewTemp(). - Locals (
Slot) — stack slots allocated byNewLocal()for named bindings.
Each instruction that produces a value writes to a Target temporary. Each instruction that consumes values reads from Source, Left, Right, or named parameter temporaries.
Every instruction also carries an optional source Location for debug information. It is immutable metadata attached before the instruction enters a function and does not affect execution semantics.
Instruction Reference
Constants
| Instruction | Fields | Description |
|---|---|---|
LoadConstInt | Target, Value: long | Load integer literal |
LoadConstFloat | Target, Value: double | Load floating-point literal |
LoadConstBool | Target, Value: bool | Load boolean literal |
LoadConstStr | Target, StrLabel: string | Load string literal by label |
LoadProgramArgs | Target | Load command-line arguments list |
Local Variables
| Instruction | Fields | Description |
|---|---|---|
LoadLocal | Target, Slot | Load value from local stack slot |
StoreLocal | Slot, Source | Store value into local stack slot |
Environment and Memory
| Instruction | Fields | Description |
|---|---|---|
LoadEnv | Target, Index | Load captured value from closure environment |
StoreMemOffset | BasePtr, OffsetBytes, Source | Store value at [base + offset] |
LoadMemOffset | Target, BasePtr, OffsetBytes | Load value from [base + offset] |
Alloc | Target, SizeBytes, RuntimeManaged | Allocate a raw payload in the scoped arena or, when runtime-managed, behind an RC header |
SaveArenaState | cursor/end slots | Record a scoped-region watermark |
RestoreArenaState | cursor/end/pre-restore slots | Reset to a saved watermark |
ReclaimArenaChunks | saved/pre-restore end slots | Return abandoned region chunks |
Ownership and Lifetime
| Instruction | Fields | Description |
|---|---|---|
Borrow | Target, SourceTemp | Create a non-owning compiler-tracked alias |
RcDup | Target, SourceTemp, RuntimeManaged, MayBeEmpty | Split ownership; increments the count for an RC value |
RcDrop | SourceTemp, TypeName, OwnerSlot, RuntimeManaged, MayBeEmpty | End one ordinary ownership path; runtime-managed forms perform type-directed RC release |
RcIsUnique | Target, SourceTemp | Test whether an RC value has count 1 |
IsReferenceCounted | Target, SourceTemp | Test whether a value of statically unknown representation lies in the reference-counted heap |
CleanupResource | SourceTemp, TypeName | Deterministically close/reap a language resource; distinct from ordinary RC |
PerceusLifetimePlacement consumes the OwnerSlot provenance on lexical anchors and places drops after last use or at dead branch entry. Constructor, match, closure, and TCO lowering emit additional shape-aware ownership operations. RuntimeManaged: false marks a compiler fact used by a scoped or specialized region; it is not an instruction to read an RC header.
MayBeEmpty records that the value's resolved type admits the empty-list representation, which is the null pointer and carries no reference-count header.
IsReferenceCounted asks about an address, not a header, so any word is a valid source and an empty, scalar, static, stack or arena value answers 0. A runtime whose reference-counted blocks do not live in one reserved region answers 0 for everything, which is why every consumer of the test must be written to copy when the answer is 0, exactly as it did before the test existed. Codegen then skips the count update instead of reading a header 16 bytes below address zero. Lowering computes the fact from the resolved type at the one place a marker is promoted to runtime RC; codegen never re-derives it, and the duplicate stays identity-preserving so an empty value is its own result.
Integer Arithmetic
| Instruction | Fields | Description |
|---|---|---|
AddInt | Target, Left, Right | Target = Left + Right |
SubInt | Target, Left, Right | Target = Left - Right |
MulInt | Target, Left, Right | Target = Left * Right |
DivInt | Target, Left, Right | Target = Left / Right |
Float Arithmetic
| Instruction | Fields | Description |
|---|---|---|
AddFloat | Target, Left, Right | Target = Left + Right |
SubFloat | Target, Left, Right | Target = Left - Right |
MulFloat | Target, Left, Right | Target = Left * Right |
DivFloat | Target, Left, Right | Target = Left / Right |
Integer Comparisons
| Instruction | Fields | Description |
|---|---|---|
CmpIntGe | Target, Left, Right | Target = (Left >= Right) ? 1 : 0 |
CmpIntLe | Target, Left, Right | Target = (Left <= Right) ? 1 : 0 |
CmpIntEq | Target, Left, Right | Target = (Left == Right) ? 1 : 0 |
CmpIntNe | Target, Left, Right | Target = (Left != Right) ? 1 : 0 |
Float Comparisons
| Instruction | Fields | Description |
|---|---|---|
CmpFloatGe | Target, Left, Right | Target = (Left >= Right) ? 1 : 0 |
CmpFloatLe | Target, Left, Right | Target = (Left <= Right) ? 1 : 0 |
CmpFloatEq | Target, Left, Right | Target = (Left == Right) ? 1 : 0 |
CmpFloatNe | Target, Left, Right | Target = (Left != Right) ? 1 : 0 |
String Comparisons
| Instruction | Fields | Description |
|---|---|---|
CmpStrEq | Target, Left, Right | Target = (Left == Right) ? 1 : 0 |
CmpStrNe | Target, Left, Right | Target = (Left != Right) ? 1 : 0 |
String Operations
| Instruction | Fields | Description |
|---|---|---|
ConcatStr | Target, Left, Right, RuntimeManaged | Target = Left ++ Right; the flag selects arena or RC allocation |
ConcatStrTip | Target, Left, Right, reservation slots, RuntimeManaged | Affine string append with geometric headroom; the RC form consumes Left |
Closures
| Instruction | Fields | Description |
|---|---|---|
MakeClosure | Target, FuncLabel, EnvPtrTemp, EnvSizeBytes, ownership flags | Allocate a closure payload, optionally behind an RC header |
CallClosure | Target, ClosureTemp, ArgTemp, RuntimeManagedArgumentFlagTemp | Call closure with an optional hidden ownership word |
CallKnown | Target, FuncLabel, EnvTemp, ArgTemp, ownership word, EnvironmentIsStackAllocated | Devirtualized closure call with explicit environment lifetime provenance |
LoadArgumentOwnership | Target | Read the hidden ownership word the caller passed |
A closure payload is 32 bytes: [code, env, packed_env_size_and_ownership, dropper]. The packed word uses bit 63 for runtime-managed result ownership, bit 62 for RC-argument adoption, and the low 62 bits for the environment size.
Every lifted function takes a hidden third parameter, the ownership word, which a call passes through RuntimeManagedArgumentFlagTemp (zero when the call passes none). Bit 0 set means the caller transferred a retained runtime-managed argument, which an RC-normalizing entry adopts instead of copying. Bit 1 set means the caller cannot own a runtime-managed result: a generic body applying a closure parameter has no static layout for the result and its own caller deep-copies the whole result out later, so a callee whose result is reference-counted deep-copies it into the arena (ArenaResultBoundary) and releases the original before returning. The dropper releases moved resources or RC captures. Supported captured ordinary graphs also have code-label metadata for normalizing the complete environment when a closure crosses into RC ownership. CallClosure loads the code and environment pointers and calls code(env, arg, owns_arg). A normalizing direct-parameter entry adopts a transferred RC root when owns_arg is set and otherwise performs the defensive arena-to-RC graph copy. The caller retains a non-fresh root before transfer; fresh owned results can transfer their existing reference. Curried parameters captured in closure environments cannot consume this direct-argument flag. Indirect closure calls are native notail calls because their environment may live in the current frame. A devirtualized CallKnown is eligible for a native tail call only when EnvironmentIsStackAllocated is false; recursive source-level TCO remains the explicit IR back-edge transformation and does not depend on this backend optimization.
Algebraic Data Types (ADTs)
| Instruction | Fields | Description |
|---|---|---|
AllocAdt | Target, Tag, FieldCount, RuntimeManaged | Allocate ADT payload [tag, fields...], optionally behind an RC header |
DropReuse | Target, SourceTemp, FieldCount, RuntimeManaged | Consume a dead cell into a compatible reuse token, or return null after decrementing a shared RC cell |
AllocReusing | Target, Tag, FieldCount, TokenTemp, RuntimeManaged, ListCell | Overwrite a compatible tagged ADT or untagged list-cell token; runtime null falls back to fresh RC allocation of the same layout |
SetAdtField | Ptr, FieldIndex, Source | *(Ptr + 8 + Index*8) = Source |
GetAdtTag | Target, Ptr | Target = *(Ptr + 0) |
GetAdtField | Target, Ptr, FieldIndex | Target = *(Ptr + 8 + Index*8) |
ADT values are heap-allocated cells. The first 8 bytes hold an integer tag identifying the variant. Each field occupies 8 bytes. Total size is (1 + FieldCount) * 8 bytes.
Graph Normalization and Region Copies
CopyOutArena, CopyOutList, CopyOutClosure, and CopyOutTcoListCell all carry a required CopyOutPurpose:
| Purpose | Contract |
|---|---|
RcNormalization | Construct an independently owned RC graph |
ArenaScopeBoundary | Preserve scheduler/capability state across a scope reset |
ArenaCallBoundary | Preserve scheduler/capability state across a call reset |
ArenaTcoCompaction | Preserve live state at a region-managed TCO edge |
IndependentClone | Explicit deep copy, worker publication, or reuse defense |
ArenaResultBoundary | A reference-counted result deep-copied into the arena for a caller that cannot own it; the original is released |
AllocAdtToSpace and CopyOutArenaToSpace are separate instructions for the persistent Map/HashMap specialization and do not represent general ordinary-value lifetime.
Foreign calls
| Instruction | Fields | Description |
|---|---|---|
ToCString | Target, StrTemp | Produce a null-terminated pointer for a call-scoped Str argument |
AllocFfiOut | Target, ElementType | Allocate and null-initialize a non-escaping opaque/pointer output slot |
CallExternal | Target, symbol/library, argument temps, parameter types, return type | Invoke a declared native function using its target ABI |
LoadFfiOut | Target, SlotTemp, ElementType | Load an output slot exactly once after its external call |
CopyFfiString | Target, PointerTemp, StringType | Validate and copy a native NUL-terminated UTF-8 string into an Ashes Result |
CopyFfiBytes | Target, PointerTemp, LengthTemp | Bounds-check and immediately copy a foreign byte range into Result(Str, Bytes) |
AllocFfiOut produces the address passed at the corresponding FfiType.Out position. Lowering materializes each loaded null as None and each non-null value as Some(value); the slot address never becomes a source-language pointer or survives the direct call. CopyFfiString scans at most 1 GiB, validates UTF-8, and copies before returning. For owned contracts it invokes the validated destructor exactly once for every non-null pointer, including conversion failures; nullable contracts map null to Ok(None) and never dispose null. CopyFfiBytes accepts null only for a zero-length range, rejects lengths above 1 GiB before touching the pointer, and materializes an owned byte buffer before control returns to source code.
Immutable binary construction
| Instruction | Fields | Description |
|---|---|---|
BytesAllocate | Target, LengthTemp | Allocate a checked, zero-filled owned buffer |
BytesCopyRange | Target, destination/offset, source/offset, length, ownership flags | Replace one checked byte range |
BytesSet | Target, bytes, offset, value, ownership flags | Replace one checked byte |
BytesSetU16Le / BytesSetU32Le / BytesSetU64Le | Target, bytes, offset, value, ownership flags | Patch a checked little-endian field |
Update lowering normalizes the destination to reference-counted ownership and marks only a freshly produced, unaliased update temporary as reusable. Code generation forwards and patches that storage; a named, borrowed, or otherwise potentially shared destination is copied first, preserving aliases.
Console I/O
| Instruction | Fields | Description |
|---|---|---|
PrintInt | Source | Print integer with newline to stdout |
PrintStr | Source | Print string with newline to stdout |
PrintBool | Source | Print boolean with newline to stdout |
WriteStr | Source | Write string to stdout (no newline) |
WriteErrorStr | Source, AppendNewline | Write string to stderr |
ExitProcess | Source | Terminate with controlled exit code |
ReadLine | Target | Read line from stdin → Maybe<String> |
PanicStr | Source | Print error message and terminate |
ReadLine returns a Maybe<String> ADT: Some(line) on success, None on EOF.
File I/O
| Instruction | Fields | Description |
|---|---|---|
FileReadText | Target, PathTemp | Read file → Result<String> |
FileReadAllBytes | Target, PathTemp | Read file → Result<Bytes> |
FileMmap | Target, PathTemp | Map file → Result<Bytes> |
FileWriteText | Target, PathTemp, TextTemp | Write file → Result<Unit> |
FileWriteBytes | Target, PathTemp, BytesTemp | Write bytes → Result<Unit> |
FileExists | Target, PathTemp | Check existence → Result<Bool> |
FileReplace | Target, SourceTemp, DestinationTemp | Atomically replace file → Result<Unit> |
FileMakeExecutable | Target, PathTemp | Prepare regular file for execution → Result<Unit> |
DirectoryEntries | Target, PathTemp | Enumerate sorted basenames → Result<List<String>> |
DirectoryCreateAll | Target, PathTemp | Recursively create directories → Result<Unit> |
DirectoryRemoveTree | Target, PathTemp | Recursively remove without following symlinks → Result<Unit> |
FileOpen | Target, PathTemp | Open file → Result<FileHandle> |
FileReadChunk | Target, HandleTemp, CountTemp | Read bounded chunk → Result<Bytes> |
FileReadLine | Target, HandleTemp | Read line → Result<Maybe<String>> |
FileClose | Target, HandleTemp | Close handle → Result<Unit> |
All file operations return Result ADTs: Ok(value) on success, Error(message) on failure.
Text Parsing
| Instruction | Fields | Description |
|---|---|---|
TextUncons | Target, TextTemp | Split front scalar → Maybe((Str, Str)) |
TextParseInt | Target, TextTemp | Parse decimal integer → Result<Int> |
TextParseFloat | Target, TextTemp | Parse decimal float → Result<Float> |
These instructions return the existing Maybe and Result ADTs.
Text Formatting
| Instruction | Fields | Description |
|---|---|---|
TextFromInt | Target, ValueTemp | Format integer → Str |
TextFromFloat | Target, ValueTemp | Format finite float → Str |
TextToHex | Target, ValueTemp | Format integer as hexadecimal → Str |
HTTP
| Instruction | Fields | Description |
|---|---|---|
HttpGet | Target, UrlTemp | HTTP GET → Result<String> |
HttpPost | Target, UrlTemp, BodyTemp | HTTP POST → Result<String> |
TCP Networking
| Instruction | Fields | Description |
|---|---|---|
NetTcpConnect | Target, HostTemp, PortTemp | Connect → Result<Socket> |
NetTcpSend | Target, SocketTemp, TextTemp | Send data → Result<Unit> |
NetTcpReceive | Target, SocketTemp, MaxBytesTemp | Receive → Result<String> |
NetTcpClose | Target, SocketTemp | Close socket → Result<Unit> |
Control Flow
| Instruction | Fields | Description |
|---|---|---|
Label | Name | Define a jump target |
Jump | Target | Unconditional jump to label |
JumpIfFalse | CondTemp, Target | Jump to label if CondTemp == 0 |
Return | Source | Return value from function |
Capabilities
Handler evidence for capabilities: one module global per declared capability (__ashes_capability_handler_<i>, created when IrProgram.CapabilityHandlerGlobals > 0) holds a pointer to the innermost installed handler frame for that capability, 0 when none. See Architecture for the frame layout and perform/handle sequences.
| Instruction | Fields | Description |
|---|---|---|
LoadCapabilityHandler | Target, CapabilityIndex | Load the capability's current handler frame pointer |
StoreCapabilityHandler | CapabilityIndex, Source | Store a handler frame pointer into the capability global |
Async / Task
| Instruction | Fields | Description |
|---|---|---|
CreateTask | Target, ClosureTemp, StateStructSize, CaptureCount, FrameDropperLabel, LoopResetEligible | Allocate task/state struct from closure |
CreateCompletedTask | Target, ResultTemp | Allocate pre-completed task (state = -1) |
AwaitTask | Target, TaskTemp | Await a sub-task inside a coroutine |
RunTask | Target, TaskTemp | Synchronously drive a task to completion |
AsyncSleep | Target, MillisecondsTemp | Create a sleep task (state = -2) |
Suspend | StateStructTemp, NextState, AwaitedTaskTemp, SaveVars | State machine suspend point |
Resume | StateStructTemp, ResultTemp, RestoreVars | State machine resume point |
AwaitTask appears in the IR before the state machine transform. The transform replaces each AwaitTask with a Suspend/Resume pair that saves and restores live temps and locals across the await point. A suspend hands its saved values to the frame and the matching resume clears each word as it restores it, so exactly one owner holds each reference: while the task is parked the frame owns them and FrameDropper releases them on cancellation; once resumed the body owns them again and its ordinary lifetime markers do. Perceus lifetime placement runs on that pre-transform body, where the await is still an ordinary control-flow edge; an owner whose placed RcDrop follows an await is therefore live across it and enters the transform's save/restore set.
Task/state struct layout (TaskStructLayout):
| Offset | Field | Description |
|---|---|---|
| 0-40 | core | State, coroutine, result, awaited task, task link, sleep duration |
| 48-88 | leaf wait | Two I/O arguments, wait kind/handle, and two wait scratch slots |
| 96 | FrameSizeBytes | Full frame size including captures and live slots |
| 104-112 | private arena | Detached root cursor and end |
| 120-136 | scheduler links | Ready-next, waiter, and arena owner |
| 144 | LoopResetOk | Whether an async TCO restart may reset its region |
| 152 | FrameDropper | Frame teardown helper, or 0 when the frame owns nothing |
| 160+ | captures/live vars | Captures followed by variables live across suspension |
Lowering Overview
The Lowering class transforms the typed AST into IR:
Lowering.Lower(Expr)is the entry point. It walks the expression tree recursively, appending instructions to a flat list.Each
LowerExpr()call returns(int Temp, TypeRef Type)— the temporary holding the result and its inferred type.Lambdas are lifted into separate
IrFunctionentries. Free variables are captured into a heap-allocated environment viaAlloc+StoreMemOffset, then accessed inside the lambda body viaLoadEnv.Pattern matching generates a series of
GetAdtTagchecks,JumpIfFalsebranches, andGetAdtFieldextractions, with labels for each arm and a join point after the match.Let bindings allocate a local slot (
StoreLocal) and make it available in the body scope (LoadLocal).
Backend Consumption
The LLVM backend (LlvmCodegen) processes each IrFunction:
- Pre-creates LLVM
BasicBlockentries for everyLabelinstruction. - Iterates through instructions, calling
EmitInstruction()which pattern-matches on theIrInstvariant and emits corresponding LLVM IR builder calls. - Temps and locals are mapped to LLVM
allocastack slots. - The
Uses*flags onIrProgramcontrol which runtime helpers (print routines, string concatenation, etc.) are included.
Memory Layout Summary
| Structure | Payload addressed by value pointer |
|---|---|
| String / Bytes | [length_and_view_flag:i64][bytes...] |
| BigInt | [sign_and_limb_count:i64][limbs...] |
| List cons | [head:i64][tail:i64]; nil is zero |
| Closure | [code:i64][env:i64][packed_env_size_and_ownership:i64][dropper:i64] |
| ADT / record | [tag:i64][field0:i64]...[fieldN:i64] |
| Tuple / environment | [word0:i64][word1:i64]... |
Runtime-managed values have [reference_count:i64][allocation_size:i64] immediately before the payload. Small RC cells use a dense per-thread region plus exact-size free-list reuse; large cells use direct OS allocation. Scoped arena instructions remain for proven scratch and explicit scheduler/specialized regions. See the architecture memory model for ownership and boundary invariants.
Semantic lowering describes these payloads with one cycle-guarded ordinary heap-layout capability. ADT child offsets are relative to the public value pointer shown above, so the same constructor-specific descriptor drives recursive drops, TCO normalization, and runtime-reuse cleanup independently of the optional RC header.
A TCO back edge may reuse an older runtime-owned List(record) graph as the normalization destination when the replacement is fresh and every record field is copied inline. It first checks equal spine length and uniqueness of every old cons cell and record head; only a complete successful preflight permits field overwrite. Otherwise the ordinary graph normalization and recursive drop path remains in effect.
