Skip to content

Memory Management and Garbage Collection ​

Implementation status: ARC for class (Phase 73 · class ObjectRef retain/release · VM + WASM tier-2D + Native LLVM direct emit); ManagedHeap v4 generational tracing GC unified across VM / WASM / Native (Phase 75); Inkwell LLVM default on (Phase 77); syx-rt direct emit tier-3 (Phase 77); Borrow checking (Phase 74 · SE-0461 + async/actor subset)

The SYX memory model is a dual track of ARC + ManagedHeap generational tracing GC: classes go through ARC reference counting (semantics similar to Swift, but managed underneath by the host bridge layer), while the remaining heap objects (Array / String / Dictionary / closures, …) go through a unified generational tracing GC. This chapter explains the memory cooperation of the three back ends (VM / WASM / Native), the relationship between gc and deinit, borrow checking, and lifetime annotations.

Memory Model Overview ​

Back endclass referencesHeap objects (Array/String/Dict/closures)Reclamation model
VMARC (class ObjectRef retain/release)ManagedHeap v4 generational GCStrong refcount zero → deinit; heap objects → GC
WASMARC (tier-2D)ManagedHeap v4 generational GC (unified in Phase 75)Same as above
Native (LLVM)ARC (tier-3 direct emit)ManagedHeap v4 generational GC (unified barriers)Same as above

Unified GC LTS (Phase 75): the VM, WASM and Native back ends share ManagedHeap v4 compact + the unified HeapSlot Array/String implementation; mark-sweep + slide compact.

ARC (class Reference Semantics) ​

A class instance is heap allocated + reference counted. ObjectRef tracks retain / release:

swift
class User {
    let name: String
    init(name: String) { self.name = name }
}

func main() {
    let a = User(name: "Alice")             // retain: 1
    var b: User? = a                         // retain: 2
    var c: User? = a                         // retain: 3

    b = nil                                  // release
    c = nil                                  // release
}                                            // release → deinit

deinit trigger: Type_deinit is called when the refcount reaches zero (an explicit class deinit). See Chapter 16, Deinitialization.

FeatureStatus
class ObjectRefImplemented (Phase 73)
retain / releaseImplemented
ARC zeroing → deinitImplemented (Phase 102/103 MVP)
Complete weak varPartially implemented (lexically reserved; zeroing judgment still evolving)
unowned varPartially implemented

ManagedHeap Generational Tracing GC ​

Non-class heap objects (Array<T> / String / Dictionary<K,V> / closures) go through a generational tracing GC:

MechanismStatus
Mark-sweepImplemented (Phase 73)
Generational young/oldImplemented (unified in Phase 75)
PromotionImplemented
Remembered set / card tableImplemented (three-back-end linkage)
Slide compactImplemented (Phase 75 v4)
Relocating GCImplemented (Phase 37 ADR-037)
Write barrierImplemented (a cross-generation write triggers a remember update)

Three-back-end linkage: ADR-045 (Phase 34) unified the GC barriers of VM / WASM / Native; MIR v23 LICM unified barrier placement across the three back ends.

swift
func makeLargeArray() -> Array<Int> {
    var arr: Array<Int> = []
    for i in 0..<10_000 { arr.append(i) }
    return arr                              // young gen allocation; reclaimed by GC when it leaves scope
}

GC Configuration (Environment Variables) ​

VariableEffect
SYX_UNIFIED_GC_LTS=1Enables Unified GC LTS (prod by default)
SYX_UNIFIED_GC_LTS_PROD=1Forces prod mode
SYX_WASI_GC_PHASE33=1Enables WASI generational GC (Phase 33)
SYX_VM_NATIVE_GC_PHASE33=1Enables VM/native generational GC (Phase 33)
SYX_GC_NURSERY=1048576Nursery size (bytes, default 1 MB)

When GC Triggers ​

  • Automatic: allocation rate / heap occupancy threshold / nursery full
  • Manual: import Runtime; Runtime.gc() forces collection (Partially implemented)
  • Scope exit: ARC zeroing triggers deinit immediately; heap objects are collected by GC later

deinit and GC ​

TypeReclamation modeldeinit timing
classARCRefcount reaches zero
actorARC + isolationRefcount reaches zero + actor shutdown
structStackNone (value type)
enumDepends on the caseDepends on the case
Array<T> and othersGCWhen GC reclaims (nondeterministic timing)
swift
class Resource {
    let id: Int
    init(id: Int) { self.id = id }
    deinit { print("bye #\(id)") }
}

class Container {
    var res: Resource?
    init() { self.res = Resource(id: 1) }   // ARC +1
    deinit { print("container bye") }
}

func main() {
    let c = Container()                      // c: ARC +1
    // c.res: ARC +1
}                                            // c: ARC -1 → deinit → c.res: ARC -1 → deinit

Borrowing &T / &mut T ​

Borrowing is a compile-time reference safety mechanism (it does not involve the runtime):

swift
func read(x: &Int) -> Int { return x }      // Immutable borrow
func write(x: &mut Int) { x = x + 1 }        // Mutable borrow

func main() {
    var n = 10
    print(read(x: &n))                       // 10
    write(x: &mut n)
    print(n)                                 // 11
}
FormMeaning
&TImmutable borrow (several at once)
&mut TMutable borrow (only one at a time)
&'a TWith a lifetime annotation (Partially implemented)

Borrow rules: a mutable borrow cannot coexist with any other borrow at the same time. A violation → S011 compile error.

swift
var n = 10
let r1 = &mut n
// let r2 = &n                              // ❌ S011: cannot borrow as immutable while mutably borrowed
// print(n)                                 // ❌ S011: cannot access through mutable borrow
print(r1)

Diagnostic: S011 (borrow/mutation violation)

Lifetimes 'a ​

swift
func id(x: &String) -> &String { return x }   // lifetime elision MVP; explicit 'a still partial
CapabilityStatus
Lifetime annotationsPartially implemented (S017)
NLL (Non-Lexical Lifetimes)Implemented
Borrows across awaitPartially implemented
Higher-ranked lifetimesNot implemented

Tests: p15_lifetime_a_*.sy · p17_lifetime_await_*.sy

Native / LLVM Back End ​

Since Phase 75, Inkwell Prod GA (ADR-044) has made the real inkwell crate IR builder the default native compilation path:

  • Unified generational GC barriers emitted directly to native
  • ARC class retain/release goes through __syx_object_* direct emit
  • The syx-rt static library provides the host bridge layer

Phase 77 enhancements:

  • materialize_array / materialize_dict go through HeapSlot
  • rt_direct_exec stack operations emitted directly
  • reflect_specialization generic specialization reflection MVP
  • obligation export (borrow machine proofs)

Toolchain Cache ​

  • target/cache/ MIR incremental compilation (syx-cache-v110)
  • If compile-time behavior looks wrong, delete the cache and retry

Differences from Swift ​

Swift ARCSYX
Refcount zeroing means immediate deinitPartially implemented (scope-exit + ARC zeroing MVP)
Complete weak / unownedPartially implemented (lexically reserved; zeroing judgment still evolving)
autoreleasepoolNot implemented (GC replaces it)
Complete move semanticsPartially implemented (borrow regions v8)
Swift 6 strict concurrencyPartially implemented (Sendable / actor / @MainActor)

Complete Example ​

swift
class Buffer {
    var data: Array<UInt8> = []
    init(size: Int) {
        for _ in 0..<size { self.data.append(0u8) }
    }
    deinit { print("Buffer freed") }
}

func process(b: &Buffer) {
    for i in 0..<b.data.count { b.data[i] = 0u8 }
}

func main() {
    let b = Buffer(size: 10)                // ARC +1
    process(b: &b)                           // Immutable borrow
    // The local variable leaves scope → ARC -1 → deinit
}

Diagnostics ​

CodeTriggerFix
S003Reassigning a borrowed letChange it to var
S011Mutable + immutable borrow at the same timeRestructure the scopes
S017Lifetime violationAnnotate explicitly or restructure the code
R011Integer overflowCheck the bounds
R016Required field not initializedComplete the init

Further Reading ​


Next chapter → Memory Safety

See also → Chapter 16, Deinitialization · Chapter 18, Concurrency · docs/10_BackendParity.md · docs/adr/architecture-decisions.md

SYX · Apache-2.0