Skip to content

Commit 0048c1c

Browse files
committed
[embedded] Document the details of the Embedded Swift's refcounting scheme
1 parent f766900 commit 0048c1c

File tree

1 file changed

+61
-0
lines changed

1 file changed

+61
-0
lines changed

stdlib/public/core/EmbeddedRuntime.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,67 @@ public struct ClassMetadata {
2828
var ivarDestroyer: UnsafeRawPointer?
2929
}
3030

31+
/*
32+
Embedded Swift Refcounting Scheme
33+
=================================
34+
35+
The scheme for storing and maintaining a refcount on heap objects is very simple in Embedded Swift, and is much
36+
simpler than regular Swift's. This is mainly due to the fact that we currently only maintain the regular ("strong")
37+
refcount and we don't allow weak references, unowned references and we don't track refcount during deinit of the
38+
object.
39+
40+
The refcount is always stored directly inline in the heap object, in the `refcount` field (see HeapObject struct
41+
below). This field has the following structure (on 32-bit, and similar on other bitwidths):
42+
43+
┌──────────────┬──────────────────────────────────────────────┐
44+
│ b31 │ b30:b0 │
45+
├──────────────┼──────────────────────────────────────────────┤
46+
│ doNotFreeBit │ actual number of references │
47+
└──────────────┴──────────────────────────────────────────────┘
48+
49+
If the highest bit (doNotFreeBit) is set, the behavior of dropping the last reference (release operation where
50+
refcount ends up being 0) is altered to avoid calling free() on the object (deinit is still run). This is crutial for
51+
class instances that are promoted by the compiler from being heap-allocated to instead be located on the stack
52+
(see swift_initStackObject).
53+
54+
To retrieve the actual number of references from the `refcount` field, refcountMask needs to be applied, which masks
55+
off the doNotFreeBit.
56+
57+
The actual number of references has one possible value that has a special meaning, immortalRefCount (all bits set,
58+
i.e. 0x7fff_ffff on 32-bit systems). When used, retain and release operations do nothing, references are not counted,
59+
and the object can never be deinit'd / free'd. This is used for class instances that are promoted by the compiler to
60+
be allocated statically in global memory (see swift_initStaticObject). Note that there are two different scenarios for
61+
this currently:
62+
63+
- In most cases, a class instance that is promoted to a global, is still dynamically initialized with a runtime call
64+
to swift_initStaticObject. This function will set the refcount field to immortalRefCount | doNotFreeBit.
65+
- As a special case to allow arrays be fully statically initialized without runtime overhead, instances of
66+
_ContiguousArrayStorage can be promoted to __StaticArrayStorage with the HeapObject header emitted directly by the
67+
compiler and refcount field directly set to immortalRefCount | doNotFreeBit (see irgen::emitConstantObject).
68+
69+
Tne immortalRefCount is additionally also used as a placeholder value for objects (heap-allocated or stack-allocated)
70+
when they're currently inside their deinit(). This is done to prevent further retains and releases inside deinit from
71+
triggering deinitialization again, without the need to reserve another bit for this purpose. Retains and releases in
72+
deinit() are allowed, as long as they are balanced at the end, i.e. the object is not escaped (user's responsibility)
73+
and not over-released (this can only be caused by unsafe code).
74+
75+
The following table summarizes the meaning of the possible combinations of doNotFreeBit and have immortal refcount
76+
value:
77+
78+
┌───────────╥──────────╥─────────────────────────────────────────────────┐
79+
│ doNotFree ║ immortal ║ │
80+
╞═══════════╬══════════╬═════════════════════════════════════════════════╡
81+
│ 0 ║ no ║ regular class instance │
82+
├───────────╫──────────╫─────────────────────────────────────────────────┤
83+
│ 0 ║ yes ║ regular class instance during deinit() │
84+
├───────────╫──────────╫─────────────────────────────────────────────────┤
85+
│ 1 ║ no ║ stack-allocated │
86+
├───────────╫──────────╫─────────────────────────────────────────────────┤
87+
│ 1 ║ yes ║ global-allocated, no need to track references, │
88+
│ ║ ║ or stack-allocated instance during deinit() │
89+
└───────────╨──────────╨─────────────────────────────────────────────────┘
90+
*/
91+
3192
public struct HeapObject {
3293
// There is no way to express the custom ptrauth signature on the metadata
3394
// field, so let's use UnsafeRawPointer and a helper function in C instead

0 commit comments

Comments
 (0)