Skip to content

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:

FieldTypeDescription
EntryFunctionIrFunctionTop-level expression (_start_main)
FunctionsList<IrFunction>Lifted lambdas and named functions
StringLiteralsList<IrStringLiteral>All string constants with labels
UsesPrintIntboolWhether PrintInt is used
UsesPrintStrboolWhether PrintStr is used
UsesPrintBoolboolWhether PrintBool is used
UsesConcatStrboolWhether ConcatStr is used
UsesClosuresboolWhether closures are created
UsesAsyncboolWhether async/await is used
CapabilityHandlerGlobalsintNumber of declared capabilities (one handler-evidence global each)

The Uses* flags allow the backend to omit unused runtime helpers.

IrFunction

A single function with a flat instruction list:

FieldTypeDescription
LabelstringUnique name (e.g., _start_main, lambda_0)
InstructionsList<IrInst>Linear instruction sequence
LocalCountintNumber of local variable stack slots
TempCountintNumber of temporary registers
HasEnvAndArgParamsbooltrue for lambdas (implicit env+arg at slots 0, 1)
CoroutineCoroutineInfo?Non-null for async coroutine functions

CoroutineInfo

Metadata for coroutine functions generated from async blocks:

FieldTypeDescription
StateCountintNumber of states (N await points = N+1 states)
StateStructSizeintTotal size of the task/state struct in bytes
CaptureCountintNumber 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:

FieldTypeDescription
LabelstringReference name (e.g., str_0)
ValuestringThe string content

Referenced by LoadConstStr. The backend emits these as read-only globals using the ordinary String payload layout with the view bit set.


Registers and Locals

Instructions use integer indices to address values:

  • Temporaries (Target, Source, Left, Right) — virtual registers allocated per-function by NewTemp().
  • Locals (Slot) — stack slots allocated by NewLocal() 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.


Instruction Reference

Constants

InstructionFieldsDescription
LoadConstIntTarget, Value: longLoad integer literal
LoadConstFloatTarget, Value: doubleLoad floating-point literal
LoadConstBoolTarget, Value: boolLoad boolean literal
LoadConstStrTarget, StrLabel: stringLoad string literal by label
LoadProgramArgsTargetLoad command-line arguments list

Local Variables

InstructionFieldsDescription
LoadLocalTarget, SlotLoad value from local stack slot
StoreLocalSlot, SourceStore value into local stack slot

Environment and Memory

InstructionFieldsDescription
LoadEnvTarget, IndexLoad captured value from closure environment
StoreMemOffsetBasePtr, OffsetBytes, SourceStore value at [base + offset]
LoadMemOffsetTarget, BasePtr, OffsetBytesLoad value from [base + offset]
AllocTarget, SizeBytes, RuntimeManagedAllocate a raw payload in the scoped arena or, when runtime-managed, behind an RC header
SaveArenaStatecursor/end slotsRecord a scoped-region watermark
RestoreArenaStatecursor/end/pre-restore slotsReset to a saved watermark
ReclaimArenaChunkssaved/pre-restore end slotsReturn abandoned region chunks

Ownership and Lifetime

InstructionFieldsDescription
BorrowTarget, SourceTempCreate a non-owning compiler-tracked alias
RcDupTarget, SourceTemp, RuntimeManagedSplit ownership; increments the count for an RC value
RcDropSourceTemp, TypeName, OwnerSlot, RuntimeManagedEnd one ordinary ownership path; runtime-managed forms perform type-directed RC release
RcIsUniqueTarget, SourceTempTest whether an RC value has count 1
CleanupResourceSourceTemp, TypeNameDeterministically 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.

Integer Arithmetic

InstructionFieldsDescription
AddIntTarget, Left, RightTarget = Left + Right
SubIntTarget, Left, RightTarget = Left - Right
MulIntTarget, Left, RightTarget = Left * Right
DivIntTarget, Left, RightTarget = Left / Right

Float Arithmetic

InstructionFieldsDescription
AddFloatTarget, Left, RightTarget = Left + Right
SubFloatTarget, Left, RightTarget = Left - Right
MulFloatTarget, Left, RightTarget = Left * Right
DivFloatTarget, Left, RightTarget = Left / Right

Integer Comparisons

InstructionFieldsDescription
CmpIntGeTarget, Left, RightTarget = (Left >= Right) ? 1 : 0
CmpIntLeTarget, Left, RightTarget = (Left <= Right) ? 1 : 0
CmpIntEqTarget, Left, RightTarget = (Left == Right) ? 1 : 0
CmpIntNeTarget, Left, RightTarget = (Left != Right) ? 1 : 0

Float Comparisons

InstructionFieldsDescription
CmpFloatGeTarget, Left, RightTarget = (Left >= Right) ? 1 : 0
CmpFloatLeTarget, Left, RightTarget = (Left <= Right) ? 1 : 0
CmpFloatEqTarget, Left, RightTarget = (Left == Right) ? 1 : 0
CmpFloatNeTarget, Left, RightTarget = (Left != Right) ? 1 : 0

String Comparisons

InstructionFieldsDescription
CmpStrEqTarget, Left, RightTarget = (Left == Right) ? 1 : 0
CmpStrNeTarget, Left, RightTarget = (Left != Right) ? 1 : 0

String Operations

InstructionFieldsDescription
ConcatStrTarget, Left, Right, RuntimeManagedTarget = Left ++ Right; the flag selects arena or RC allocation
ConcatStrTipTarget, Left, Right, reservation slots, RuntimeManagedAffine string append with geometric headroom; the RC form consumes Left

Closures

InstructionFieldsDescription
MakeClosureTarget, FuncLabel, EnvPtrTemp, EnvSizeBytes, ownership flagsAllocate a closure payload, optionally behind an RC header
CallClosureTarget, ClosureTemp, ArgTemp, RuntimeManagedArgumentFlagTempCall closure with an optional retained-RC argument ownership flag

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. 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.

Algebraic Data Types (ADTs)

InstructionFieldsDescription
AllocAdtTarget, Tag, FieldCount, RuntimeManagedAllocate ADT payload [tag, fields...], optionally behind an RC header
DropReuseTarget, SourceTemp, FieldCount, RuntimeManagedConsume a dead cell into a compatible reuse token, or return null after decrementing a shared RC cell
AllocReusingTarget, Tag, FieldCount, TokenTemp, RuntimeManaged, ListCellOverwrite a compatible tagged ADT or untagged list-cell token; runtime null falls back to fresh RC allocation of the same layout
SetAdtFieldPtr, FieldIndex, Source*(Ptr + 8 + Index*8) = Source
GetAdtTagTarget, PtrTarget = *(Ptr + 0)
GetAdtFieldTarget, Ptr, FieldIndexTarget = *(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:

PurposeContract
RcNormalizationConstruct an independently owned RC graph
ArenaScopeBoundaryPreserve scheduler/capability state across a scope reset
ArenaCallBoundaryPreserve scheduler/capability state across a call reset
ArenaTcoCompactionPreserve live state at a region-managed TCO edge
IndependentCloneExplicit deep copy, worker publication, or reuse defense

AllocAdtToSpace and CopyOutArenaToSpace are separate instructions for the persistent Map/HashMap specialization and do not represent general ordinary-value lifetime.

Console I/O

InstructionFieldsDescription
PrintIntSourcePrint integer with newline to stdout
PrintStrSourcePrint string with newline to stdout
PrintBoolSourcePrint boolean with newline to stdout
WriteStrSourceWrite string to stdout (no newline)
ReadLineTargetRead line from stdin → Maybe<String>
PanicStrSourcePrint error message and terminate

ReadLine returns a Maybe<String> ADT: Some(line) on success, None on EOF.

File I/O

InstructionFieldsDescription
FileReadTextTarget, PathTempRead file → Result<String>
FileWriteTextTarget, PathTemp, TextTempWrite file → Result<Unit>
FileExistsTarget, PathTempCheck existence → Result<Bool>

All file operations return Result ADTs: Ok(value) on success, Error(message) on failure.

Text Parsing

InstructionFieldsDescription
TextUnconsTarget, TextTempSplit front scalar → Maybe((Str, Str))
TextParseIntTarget, TextTempParse decimal integer → Result<Int>
TextParseFloatTarget, TextTempParse decimal float → Result<Float>

These instructions return the existing Maybe and Result ADTs.

Text Formatting

InstructionFieldsDescription
TextFromIntTarget, ValueTempFormat integer → Str
TextFromFloatTarget, ValueTempFormat finite float → Str
TextToHexTarget, ValueTempFormat integer as hexadecimal → Str

HTTP

InstructionFieldsDescription
HttpGetTarget, UrlTempHTTP GET → Result<String>
HttpPostTarget, UrlTemp, BodyTempHTTP POST → Result<String>

TCP Networking

InstructionFieldsDescription
NetTcpConnectTarget, HostTemp, PortTempConnect → Result<Socket>
NetTcpSendTarget, SocketTemp, TextTempSend data → Result<Unit>
NetTcpReceiveTarget, SocketTemp, MaxBytesTempReceive → Result<String>
NetTcpCloseTarget, SocketTempClose socket → Result<Unit>

Control Flow

InstructionFieldsDescription
LabelNameDefine a jump target
JumpTargetUnconditional jump to label
JumpIfFalseCondTemp, TargetJump to label if CondTemp == 0
ReturnSourceReturn 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.

InstructionFieldsDescription
LoadCapabilityHandlerTarget, CapabilityIndexLoad the capability's current handler frame pointer
StoreCapabilityHandlerCapabilityIndex, SourceStore a handler frame pointer into the capability global

Async / Task

InstructionFieldsDescription
CreateTaskTarget, ClosureTemp, StateStructSize, CaptureCountAllocate task/state struct from closure
CreateCompletedTaskTarget, ResultTempAllocate pre-completed task (state = -1)
AwaitTaskTarget, TaskTempAwait a sub-task inside a coroutine
RunTaskTarget, TaskTempSynchronously drive a task to completion
AsyncSleepTarget, MillisecondsTempCreate a sleep task (state = -2)
SuspendStateStructTemp, NextState, AwaitedTaskTemp, SaveVarsState machine suspend point
ResumeStateStructTemp, ResultTemp, RestoreVarsState 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.

Task/state struct layout (TaskStructLayout):

OffsetFieldDescription
0-40coreState, coroutine, result, awaited task, task link, sleep duration
48-88leaf waitTwo I/O arguments, wait kind/handle, and two wait scratch slots
96FrameSizeBytesFull frame size including captures and live slots
104-112private arenaDetached root cursor and end
120-136scheduler linksReady-next, waiter, and arena owner
144LoopResetOkWhether an async TCO restart may reset its region
152+captures/live varsCaptures followed by variables live across suspension

Lowering Overview

The Lowering class transforms the typed AST into IR:

  1. Lowering.Lower(Expr) is the entry point. It walks the expression tree recursively, appending instructions to a flat list.

  2. Each LowerExpr() call returns (int Temp, TypeRef Type) — the temporary holding the result and its inferred type.

  3. Lambdas are lifted into separate IrFunction entries. Free variables are captured into a heap-allocated environment via Alloc + StoreMemOffset, then accessed inside the lambda body via LoadEnv.

  4. Pattern matching generates a series of GetAdtTag checks, JumpIfFalse branches, and GetAdtField extractions, with labels for each arm and a join point after the match.

  5. 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:

  1. Pre-creates LLVM BasicBlock entries for every Label instruction.
  2. Iterates through instructions, calling EmitInstruction() which pattern-matches on the IrInst variant and emits corresponding LLVM IR builder calls.
  3. Temps and locals are mapped to LLVM alloca stack slots.
  4. The Uses* flags on IrProgram control which runtime helpers (print routines, string concatenation, etc.) are included.

Memory Layout Summary

StructurePayload 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.

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.