Quartz v5.25

Quartz Memory Model

Version: v5.25.0-alpha
Status: DRAFT — Formal ownership and memory specification
Audience: Compiler implementers and language specification readers


1. Runtime Representation

1.1 The i64 Universal Type

All Quartz values are represented as i64 at runtime. There is no runtime type information.

Source TypeRuntime Representation
Inti64 direct value
Booli64 (0 = false, nonzero = true)
Stringi64 pointer to heap-allocated string
struct Si64 pointer to heap-allocated fields
enum Ei64 pointer to heap-allocated tag + payload
Vec<T>i64 pointer to vector handle
Fn(...): Ri64 function pointer or tagged closure pointer
CPtri64 raw pointer value
Voidi64 (value is 0, ignored)

1.2 Struct Layout

Standard structs are heap-allocated. Each field occupies 8 bytes (i64):

struct S { f₁: T₁, ..., fₙ: Tₙ }

Heap layout:
  [ptr + 0×8]: f₁ (i64)
  [ptr + 1×8]: f₂ (i64)
  ...
  [ptr + (n-1)×8]: fₙ (i64)

Total allocation: n × 8 bytes via malloc()

1.3 @value Structs

Structs annotated with @value use stack allocation and are passed by value:

@value struct Point { x: Int, y: Int }

Stack layout: 2 × i64 contiguous on stack (no malloc)

1.4 @repr(C) Structs

Structs annotated with @repr(C) follow C ABI layout rules:

  • Fields use their natural C sizes (CInt = 4 bytes, CChar = 1 byte)
  • Alignment padding is inserted per C ABI rules
  • @packed suppresses padding

1.5 Enum Layout

enum E { V₁(p₁: T₁), V₂, V₃(p₂: T₂, p₃: T₃) }

Heap layout:
  [ptr + 0]: tag (i64, variant index: V₁=0, V₂=1, V₃=2)
  [ptr + 8]: payload field 1 (if present)
  [ptr + 16]: payload field 2 (if present)
  ...

2. Value Categories

2.1 Owned Values

Every value has exactly one owner — the variable or expression it is bound to. Owned values are destroyed when their owner goes out of scope (unless moved).

var x = S { f: 42 }    -- x owns the struct
                         -- struct is freed when x goes out of scope

2.2 Borrowed Values (Shared Reference)

A shared borrow &x creates a read-only alias. Multiple shared borrows may coexist.

var x = 42
var r = &x              -- r is a shared borrow of x
var s = &x              -- s is also a shared borrow of x (OK)

2.3 Borrowed Values (Exclusive Reference)

An exclusive borrow &mut x creates a read-write alias. Only one exclusive borrow may exist at a time, and it conflicts with all shared borrows.

var x = 42
var r = &mut x          -- r is an exclusive borrow of x

3. Ownership Rules

3.1 Move Semantics

For types that implement Drop, passing the value to a function or assigning it to another variable transfers ownership (move). The original binding becomes invalid.

Γ ⊢ x: T    T: Drop    Γ ⊢ f(x) ⇓ v
─────────────────────────────────────────
Γ ⊢ x is MOVED after f(x)              [MOVE]

After a move, using the original variable is a compile error (QZ1212).

3.2 Copy Trait

Types implementing Copy are duplicated rather than moved:

Γ ⊢ x: T    T: Copy    Γ ⊢ f(x) ⇓ v
─────────────────────────────────────────
Γ ⊢ x is still valid after f(x)        [COPY]

Invariant: A type cannot implement both Copy and Drop (QZ1215).

3.3 Move in Loops

Moving a value inside a loop body is rejected unless the value is re-initialized before the next iteration:

-- REJECTED (QZ1212): move without reinit
while cond
  consume(x)            -- x moved
end                     -- next iteration: x is invalid

-- ACCEPTED: move with reinit
while cond
  consume(x)            -- x moved
  x = new_value()       -- x re-initialized
end

3.4 Partial Moves

Moving a field of a struct partially invalidates the struct:

consume(p.x)            -- p.x moved (partial move)
use(p)                  -- ERROR (QZ1216): p is partially moved

3.5 Re-initialization After Move

A moved variable can be re-initialized:

var x = Res { n: 1 }
consume(x)              -- x is moved
x = Res { n: 2 }        -- x is re-initialized (valid)
consume(x)              -- OK

4. Borrowing Rules

4.1 The Five Rules

These are the complete set of borrow rules. They are checked at compile time.

#RuleError Code
1Multiple & on same variable: allowed
2Multiple &mut on same variable: rejectedQZ1205
3& and &mut on same variable: rejectedQZ1206
4Cannot mutate while borrowed (=, +=, etc.)QZ1209
5&mut requires var bindingQZ1208

4.2 Borrow Lifetimes (NLL-lite)

Borrows have non-lexical lifetimes. A borrow expires at its last use, not at scope exit:

var x = 42
var r = &x              -- borrow starts
var y = r + 1           -- last use of r → borrow expires
x = 99                  -- OK: borrow has expired

If the borrow is used after a conflicting operation, it remains live:

var x = 42
var r = &x              -- borrow starts
x = 99                  -- ERROR (QZ1209): r still live (used below)
var y = r + 1           -- r used after mutation

4.3 Ephemeral Borrows

Borrows passed directly as function arguments are ephemeral — they are released when the call returns:

def peek(r: &Int): Int = 0
var x = 42
peek(&x)                -- borrow created, then released
var p = &mut x          -- OK: no outstanding borrow

4.4 Reassignment Releases Borrows

Reassigning a variable that holds a borrow releases the old borrow:

var x = 42
var p = &x              -- p borrows x (shared)
p = 0                   -- p reassigned → borrow on x released
var q = &mut x          -- OK: x is no longer borrowed

4.5 Borrows Cannot Escape

EscapeErrorCode
Return borrow of localDangling referenceQZ1210
Store borrow in struct fieldStruct may outlive sourceQZ1211

5. Drop Semantics

5.1 Automatic Drop

When a value implementing Drop goes out of scope, its drop method is called automatically. This is the only automatic destructor mechanism.

Γ ⊢ var x: T = v    T: Drop
x goes out of scope
─────────────────────────
call T$drop(x)                              [DROP]

5.2 Drop Order

Values are dropped in reverse declaration order within a scope (LIFO):

def f()
  var a = Res { ... }   -- dropped 3rd (last)
  var b = Res { ... }   -- dropped 2nd
  var c = Res { ... }   -- dropped 1st (first)
end

5.3 Drop and Moves

Moved values are not dropped. The compiler tracks which values have been moved and skips their drop:

var x = Res { ... }
consume(x)              -- x moved → no drop for x

5.4 Drop Glue

Structs containing fields that implement Drop get implicit drop glue generated by the compiler, even if the struct itself doesn’t implement Drop:

struct Context
  alloc: Arena          -- Arena implements Drop
  name: String
end
-- compiler generates drop glue that calls Arena$drop on ctx.alloc

5.5 Defer and Drop Interaction

defer expressions run before automatic drops at scope exit:

sequence on scope exit:
  1. All defer expressions (LIFO order)
  2. All drops (LIFO order, excluding moved values)

6. Memory Allocation Strategies

6.1 Default: malloc/free

Standard structs and enums use malloc for allocation. The Drop trait automates free at scope exit.

6.2 Arenas

Arena allocators provide bulk allocation with single-point deallocation:

arena scratch do
  p = @scratch Point { x: 1, y: 2 }  -- allocated from arena
end                                     -- entire arena freed

Arena-allocated values are not individually freed. The entire arena is destroyed at scope exit.

6.3 @heap Placement

@heap explicitly allocates on the heap:

arr = @heap [1, 2, 3]              -- heap-allocated array
p = @heap Point { x: 10, y: 20 }   -- heap-allocated struct

6.4 Vec Storage Model

Vec uses width-aware storage for narrow types:

Element TypeStorage WidthMemory
Int, String, …8 bytesStandard
U8, I81 byteNarrow
I16, U162 bytesNarrow
I32, U32, F324 bytesNarrow

7. Concurrency Model

7.1 Thread Safety

Quartz provides thread-level concurrency via spawn / thread_join. There is no implicit sharing — data must be explicitly passed to threads.

7.2 Atomics

Atomic operations follow the LLVM memory model:

OrderingConstantSemantics
Relaxed0No ordering guarantee
Acquire2Reads-from synchronizes
Release3Synchronizes-with reads
AcqRel4Both acquire and release
SeqCst5Total order (default)

7.3 Volatile Access

volatile_load<T> and volatile_store<T> prevent compiler reordering and elimination. Used for memory-mapped I/O and hardware registers.


8. Error Code Reference

CodeErrorCategory
QZ1205Cannot create exclusive borrowBorrow
QZ1206Conflicting borrows (shared + exclusive)Borrow
QZ1208Cannot &mut immutable bindingBorrow
QZ1209Cannot mutate while borrowedBorrow
QZ1210Cannot return borrow of localBorrow
QZ1211Cannot store borrow in struct fieldBorrow
QZ1212Use of moved valueMove
QZ1215Copy + Drop conflictTrait
QZ1216Use of partially moved valueMove