Build1 publisher3 min readPublished
Delete one strong_release and Swift leaks 145 MB: ARC as a compile-time contract
A Swift 6.3.1 experiment removes a single SIL release instruction and watches deinit vanish while peak RSS climbs from about 2.5 MB to 145 MB. The chain is inspectable at four layers.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened
- A source-to-runtime experiment with Swift 6.3.1: inspect ownership in SIL, delete one strong_release, measure the resulting leak, compare weak-reference lowering, and follow the call stack into the Swift runtime.
- Swift ARC is sometimes explained as if it were a background service that periodically scans memory and frees objects no longer used; the author argues that description fits a tracing garbage collector more closely than Swift's reference-counting model.
- In Swift, the compiler analyzes ownership and value lifetimes and represents those rules with SIL operations such as copy_value, destroy_value, strong_retain, and strong_release.
- Those SIL operations are lowered to runtime behavior such as swift_retain and swift_release.
- When a release brings an object's strong reference count to zero, destruction begins on that execution path.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
Someone took a Swift 6.3.1 toolchain, found the exact SIL instruction that ends one object's lifetime, deleted it, and measured what happened [s1c1]. The object's `deinit` stopped running and a repeated-allocation workload went from roughly 2.5 MB to 145 MB maximum resident set size [s1c8], which is about a 58x increase in peak footprint [s1c9].
That matters because of how ARC is often described: as a background service that periodically scans memory and frees what is no longer used. The write-up argues that story describes a tracing garbage collector more closely than Swift's model [s1c2]. In Swift, the compiler analyzes ownership and value lifetimes and encodes the rules as SIL operations such as `copy_value`, `destroy_value`, `strong_retain`, and `strong_release` [s1c3]. Those lower to runtime calls including `swift_retain` and `swift_release` [s1c4], and when a release drives an object's strong count to zero, destruction begins on that execution path [s1c5]. There is no periodic traversal asking which objects are still reachable from a root set; the counter moves only on ownership events [s1c6].
The value of the experiment is that it is falsifiable at four layers: source through SILGen, SIL, and LLVM IR; a causal intervention that removes exactly one release; runtime observation via `deinit`, maximum RSS, and an LLDB backtrace; and Swift's open-source reference-counting implementation [s1c7]. Delete the instruction, lose the destruction. Nothing else in the process notices.
The test case is deliberately boring: a `do` block that creates `TrackedObject(id: 1)`, passes it to `consume`, and prints before and after scope exit [s1c15]. `TrackedObject` prints from `init` and `deinit`, and `consume` is marked `@inline(never)` so the object has an observable use [s1c16]. At the SILGen level the result carries `@owned`, gets a `move_value [lexical] [var_decl]`, is lent through `begin_borrow` / `end_borrow` to a `@guaranteed` parameter, and ends at `destroy_value` [s1c17]. `@owned` means the value must be consumed exactly once on every control-flow path [s1c18]; the borrow lends it without transferring or duplicating ownership [s1c20]; `destroy_value` ends the lifetime without yet requiring a literal `swift_release` call [s1c21]. The SIL ownership specification states the same invariant: consumed exactly once, by `destroy_value` or another consuming instruction [s1c22]. After ownership lowering the same lifetime ending gets more concrete [s1c23].
Two caveats the author states plainly. The experiment uses `-disable-arc-opts`, an internal compiler option chosen to make ARC operations legible, and not a recommended production setting [s1c12]; with ARC optimization on, the compiler may delete balanced retain/release pairs, move releases, pass ownership through calling conventions, or inline runtime operations [s1c13]. And SIL is an implementation-level representation, so instruction selection, temporary value numbers, and release placement can shift between compiler versions or optimization levels; if a checked-in patch stops applying, regenerate the SIL and find the corresponding release again [s1c11]. The runs were verified on Apple Swift 6.3.1, target arm64-apple-macosx26.0, Xcode 26.4.1, macOS 26.5.1, with libswiftCore.dylib 6.3.2 loaded at execution [s1c10].
What to watch: the same repository examines how `weak` storage is lowered and builds a strong reference cycle to contrast reference counting against tracing collection [s1c24]. That is the harder half of the argument, because a cycle is exactly the case where no ownership event ever fires and no scanner arrives to clean up. Source code, reproducible experiments, and a Korean version are published alongside [s1c25].