Skip to content

Commit 78c45e7

Browse files
committed
Add a flag to allow Swift objects (such as the singleton empty collections) to ignore refcounting
1 parent 9bc5bd2 commit 78c45e7

File tree

6 files changed

+143
-36
lines changed

6 files changed

+143
-36
lines changed

stdlib/public/SwiftShims/HeapObject.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ struct HeapObject {
5656
: metadata(newMetadata)
5757
, refCounts(InlineRefCounts::Initialized)
5858
{ }
59+
60+
// Initialize a HeapObject header for an immortal object
61+
constexpr HeapObject(HeapMetadata const *newMetadata,
62+
InlineRefCounts::Immortal_t immortal)
63+
: metadata(newMetadata)
64+
, refCounts(InlineRefCounts::Immortal)
65+
{ }
5966

6067
#ifndef NDEBUG
6168
void dump() const LLVM_ATTRIBUTE_USED;

stdlib/public/SwiftShims/RefCount.h

Lines changed: 107 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -238,14 +238,11 @@ struct RefCountBitOffsets;
238238
// 32-bit out of line
239239
template <>
240240
struct RefCountBitOffsets<8> {
241-
// We reserve 1 bit (which we likely be using in future) to make the
242-
// unowned field 31 bit. The reason is that unowned overflow checking does
243-
// not work with 32 bit in the current implementation.
244-
static const size_t ReservedShift = 0;
245-
static const size_t ReservedBitCount = 1;
246-
static const uint32_t ReservedMask = maskForField(Reserved);
247-
248-
static const size_t UnownedRefCountShift = shiftAfterField(Reserved);
241+
static const size_t IsImmortalShift = 0;
242+
static const size_t IsImmortalBitCount = 1;
243+
static const uint64_t IsImmortalMask = maskForField(IsImmortal);
244+
245+
static const size_t UnownedRefCountShift = shiftAfterField(IsImmortal);
249246
static const size_t UnownedRefCountBitCount = 31;
250247
static const uint64_t UnownedRefCountMask = maskForField(UnownedRefCount);
251248

@@ -256,7 +253,7 @@ struct RefCountBitOffsets<8> {
256253
static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
257254
static const size_t StrongExtraRefCountBitCount = 30;
258255
static const uint64_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
259-
256+
260257
static const size_t UseSlowRCShift = shiftAfterField(StrongExtraRefCount);
261258
static const size_t UseSlowRCBitCount = 1;
262259
static const uint64_t UseSlowRCMask = maskForField(UseSlowRC);
@@ -274,22 +271,22 @@ struct RefCountBitOffsets<8> {
274271
// 32-bit inline
275272
template <>
276273
struct RefCountBitOffsets<4> {
277-
static const size_t ReservedShift = 0;
278-
static const size_t ReservedBitCount = 0;
279-
static const uint32_t ReservedMask = maskForField(Reserved);
280-
281-
static const size_t UnownedRefCountShift = shiftAfterField(Reserved);
282-
static const size_t UnownedRefCountBitCount = 8;
274+
static const size_t IsImmortalShift = 0;
275+
static const size_t IsImmortalBitCount = 1;
276+
static const uint64_t IsImmortalMask = maskForField(IsImmortal);
277+
278+
static const size_t UnownedRefCountShift = shiftAfterField(IsImmortal);
279+
static const size_t UnownedRefCountBitCount = 7;
283280
static const uint32_t UnownedRefCountMask = maskForField(UnownedRefCount);
284281

285282
static const size_t IsDeinitingShift = shiftAfterField(UnownedRefCount);
286283
static const size_t IsDeinitingBitCount = 1;
287284
static const uint32_t IsDeinitingMask = maskForField(IsDeiniting);
288-
285+
289286
static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
290287
static const size_t StrongExtraRefCountBitCount = 22;
291288
static const uint32_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
292-
289+
293290
static const size_t UseSlowRCShift = shiftAfterField(StrongExtraRefCount);
294291
static const size_t UseSlowRCBitCount = 1;
295292
static const uint32_t UseSlowRCMask = maskForField(UseSlowRC);
@@ -307,7 +304,7 @@ struct RefCountBitOffsets<4> {
307304

308305
// FIXME: reinstate these assertions
309306
#if 0
310-
static_assert(StrongExtraRefCountShift == IsDeinitingShift + 1,
307+
static_assert(StrongExtraRefCountShift == IsDeinitingShift + 1,
311308
"IsDeiniting must be LSB-wards of StrongExtraRefCount");
312309
static_assert(UseSlowRCShift + UseSlowRCBitCount == sizeof(bits)*8,
313310
"UseSlowRC must be MSB");
@@ -368,7 +365,20 @@ class RefCountBitsT {
368365
}
369366

370367
public:
368+
369+
enum Immortal_t { Immortal };
371370

371+
LLVM_ATTRIBUTE_ALWAYS_INLINE
372+
bool isImmortal() const {
373+
return bool(getField(IsImmortal));
374+
}
375+
376+
LLVM_ATTRIBUTE_ALWAYS_INLINE
377+
void setIsImmortal(bool value) {
378+
setField(IsImmortal, value);
379+
setField(UseSlowRC, value);
380+
}
381+
372382
LLVM_ATTRIBUTE_ALWAYS_INLINE
373383
RefCountBitsT() = default;
374384

@@ -378,6 +388,15 @@ class RefCountBitsT {
378388
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
379389
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
380390
{ }
391+
392+
LLVM_ATTRIBUTE_ALWAYS_INLINE
393+
constexpr
394+
RefCountBitsT(Immortal_t immortal)
395+
: bits((BitsType(2) << Offsets::StrongExtraRefCountShift) |
396+
(BitsType(2) << Offsets::UnownedRefCountShift) |
397+
(BitsType(1) << Offsets::IsImmortalShift) |
398+
(BitsType(1) << Offsets::UseSlowRCShift))
399+
{ }
381400

382401
LLVM_ATTRIBUTE_ALWAYS_INLINE
383402
RefCountBitsT(HeapObjectSideTableEntry* side)
@@ -414,8 +433,7 @@ class RefCountBitsT {
414433

415434
LLVM_ATTRIBUTE_ALWAYS_INLINE
416435
bool hasSideTable() const {
417-
// FIXME: change this when introducing immutable RC objects
418-
bool hasSide = getUseSlowRC();
436+
bool hasSide = getUseSlowRC() && !isImmortal();
419437

420438
// Side table refcount must not point to another side table.
421439
assert((refcountIsInline || !hasSide) &&
@@ -505,7 +523,7 @@ class RefCountBitsT {
505523
LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
506524
bool decrementStrongExtraRefCount(uint32_t dec) {
507525
#ifndef NDEBUG
508-
if (!hasSideTable()) {
526+
if (!hasSideTable() && !isImmortal()) {
509527
// Can't check these assertions with side table present.
510528

511529
if (getIsDeiniting())
@@ -537,10 +555,10 @@ class RefCountBitsT {
537555

538556
LLVM_ATTRIBUTE_ALWAYS_INLINE
539557
bool isUniquelyReferenced() {
540-
static_assert(Offsets::ReservedBitCount +
541-
Offsets::UnownedRefCountBitCount +
558+
static_assert(Offsets::UnownedRefCountBitCount +
542559
Offsets::IsDeinitingBitCount +
543560
Offsets::StrongExtraRefCountBitCount +
561+
Offsets::IsImmortalBitCount +
544562
Offsets::UseSlowRCBitCount == sizeof(bits)*8,
545563
"inspect isUniquelyReferenced after adding fields");
546564

@@ -663,6 +681,7 @@ class RefCounts {
663681

664682
public:
665683
enum Initialized_t { Initialized };
684+
enum Immortal_t { Immortal };
666685

667686
// RefCounts must be trivially constructible to avoid ObjC++
668687
// destruction overhead at runtime. Use RefCounts(Initialized)
@@ -673,6 +692,10 @@ class RefCounts {
673692
constexpr RefCounts(Initialized_t)
674693
: refCounts(RefCountBits(0, 1)) {}
675694

695+
// Refcount of an immortal object has top and bottom bits set
696+
constexpr RefCounts(Immortal_t)
697+
: refCounts(RefCountBits(RefCountBits::Immortal)) {}
698+
676699
void init() {
677700
refCounts.store(RefCountBits(0, 1), std::memory_order_relaxed);
678701
}
@@ -684,6 +707,24 @@ class RefCounts {
684707
void initForNotFreeing() {
685708
refCounts.store(RefCountBits(0, 2), std::memory_order_relaxed);
686709
}
710+
711+
// Initialize for an object which will never deallocate.
712+
void initImmortal() {
713+
refCounts.store(RefCountBits(RefCountBits::Immortal), std::memory_order_relaxed);
714+
}
715+
716+
void setIsImmortal(bool immortal) {
717+
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
718+
if (oldbits.isImmortal()) {
719+
return;
720+
}
721+
RefCountBits newbits;
722+
do {
723+
newbits = oldbits;
724+
newbits.setIsImmortal(immortal);
725+
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
726+
std::memory_order_relaxed));
727+
}
687728

688729
// Initialize from another refcount bits.
689730
// Only inline -> out-of-line is allowed (used for new side table entries).
@@ -698,8 +739,11 @@ class RefCounts {
698739
do {
699740
newbits = oldbits;
700741
bool fast = newbits.incrementStrongExtraRefCount(inc);
701-
if (!fast)
742+
if (__builtin_expect(!fast, 0)) {
743+
if (oldbits.isImmortal())
744+
return;
702745
return incrementSlow(oldbits, inc);
746+
}
703747
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
704748
std::memory_order_relaxed));
705749
}
@@ -708,8 +752,11 @@ class RefCounts {
708752
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
709753
auto newbits = oldbits;
710754
bool fast = newbits.incrementStrongExtraRefCount(inc);
711-
if (!fast)
755+
if (__builtin_expect(!fast, 0)) {
756+
if (oldbits.isImmortal())
757+
return;
712758
return incrementNonAtomicSlow(oldbits, inc);
759+
}
713760
refCounts.store(newbits, std::memory_order_relaxed);
714761
}
715762

@@ -723,8 +770,11 @@ class RefCounts {
723770

724771
newbits = oldbits;
725772
bool fast = newbits.incrementStrongExtraRefCount(1);
726-
if (!fast)
773+
if (__builtin_expect(!fast, 0)) {
774+
if (oldbits.isImmortal())
775+
return true;
727776
return tryIncrementSlow(oldbits);
777+
}
728778
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
729779
std::memory_order_relaxed));
730780
return true;
@@ -737,8 +787,11 @@ class RefCounts {
737787

738788
auto newbits = oldbits;
739789
bool fast = newbits.incrementStrongExtraRefCount(1);
740-
if (!fast)
790+
if (__builtin_expect(!fast, 0)) {
791+
if (oldbits.isImmortal())
792+
return true;
741793
return tryIncrementNonAtomicSlow(oldbits);
794+
}
742795
refCounts.store(newbits, std::memory_order_relaxed);
743796
return true;
744797
}
@@ -771,6 +824,9 @@ class RefCounts {
771824
// Precondition: the reference count must be 1
772825
void decrementFromOneNonAtomic() {
773826
auto bits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
827+
if (bits.isImmortal()) {
828+
return;
829+
}
774830
if (bits.hasSideTable())
775831
return bits.getSideTable()->decrementFromOneNonAtomic();
776832

@@ -866,7 +922,9 @@ class RefCounts {
866922
// Decrement completed normally. New refcount is not zero.
867923
deinitNow = false;
868924
}
869-
else if (oldbits.hasSideTable()) {
925+
else if (oldbits.isImmortal()) {
926+
return false;
927+
} else if (oldbits.hasSideTable()) {
870928
// Decrement failed because we're on some other slow path.
871929
return doDecrementSideTable<performDeinit>(oldbits, dec);
872930
}
@@ -903,6 +961,9 @@ class RefCounts {
903961
// Decrement completed normally. New refcount is not zero.
904962
deinitNow = false;
905963
}
964+
else if (oldbits.isImmortal()) {
965+
return false;
966+
}
906967
else if (oldbits.hasSideTable()) {
907968
// Decrement failed because we're on some other slow path.
908969
return doDecrementNonAtomicSideTable<performDeinit>(oldbits, dec);
@@ -939,9 +1000,13 @@ class RefCounts {
9391000
newbits = oldbits;
9401001
bool fast =
9411002
newbits.decrementStrongExtraRefCount(dec);
942-
if (!fast)
1003+
if (__builtin_expect(!fast, 0)) {
1004+
if (oldbits.isImmortal()) {
1005+
return false;
1006+
}
9431007
// Slow paths include side table; deinit; underflow
9441008
return doDecrementSlow<performDeinit>(oldbits, dec);
1009+
}
9451010
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
9461011
std::memory_order_release,
9471012
std::memory_order_relaxed));
@@ -960,6 +1025,8 @@ class RefCounts {
9601025
// Increment the unowned reference count.
9611026
void incrementUnowned(uint32_t inc) {
9621027
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
1028+
if (oldbits.isImmortal())
1029+
return;
9631030
RefCountBits newbits;
9641031
do {
9651032
if (oldbits.hasSideTable())
@@ -979,6 +1046,8 @@ class RefCounts {
9791046

9801047
void incrementUnownedNonAtomic(uint32_t inc) {
9811048
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
1049+
if (oldbits.isImmortal())
1050+
return;
9821051
if (oldbits.hasSideTable())
9831052
return oldbits.getSideTable()->incrementUnownedNonAtomic(inc);
9841053

@@ -997,6 +1066,8 @@ class RefCounts {
9971066
// Return true if the caller should free the object.
9981067
bool decrementUnownedShouldFree(uint32_t dec) {
9991068
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
1069+
if (oldbits.isImmortal())
1070+
return false;
10001071
RefCountBits newbits;
10011072

10021073
bool performFree;
@@ -1023,7 +1094,8 @@ class RefCounts {
10231094

10241095
bool decrementUnownedShouldFreeNonAtomic(uint32_t dec) {
10251096
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
1026-
1097+
if (oldbits.isImmortal())
1098+
return false;
10271099
if (oldbits.hasSideTable())
10281100
return oldbits.getSideTable()->decrementUnownedShouldFreeNonAtomic(dec);
10291101

@@ -1312,8 +1384,12 @@ inline bool RefCounts<InlineRefCountBits>::doDecrementNonAtomic(uint32_t dec) {
13121384

13131385
auto newbits = oldbits;
13141386
bool fast = newbits.decrementStrongExtraRefCount(dec);
1315-
if (!fast)
1387+
if (!fast) {
1388+
if (oldbits.isImmortal()) {
1389+
return false;
1390+
}
13161391
return doDecrementNonAtomicSlow<performDeinit>(oldbits, dec);
1392+
}
13171393

13181394
refCounts.store(newbits, std::memory_order_relaxed);
13191395
return false; // don't deinit

stdlib/public/SwiftShims/RuntimeShims.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ const char *_swift_stdlib_strtod_clocale(const char *nptr, double *outResult);
4545
SWIFT_RUNTIME_STDLIB_API
4646
const char *_swift_stdlib_strtof_clocale(const char *nptr, float *outResult);
4747

48+
SWIFT_RUNTIME_STDLIB_API
49+
void _swift_stdlib_immortalize(void *obj);
50+
4851
SWIFT_RUNTIME_STDLIB_API
4952
void _swift_stdlib_flockfile_stdout(void);
5053
SWIFT_RUNTIME_STDLIB_API

stdlib/public/runtime/HeapObject.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ struct InitStaticObjectContext {
117117
static void initStaticObjectWithContext(void *OpaqueCtx) {
118118
InitStaticObjectContext *Ctx = (InitStaticObjectContext *)OpaqueCtx;
119119
Ctx->object->metadata = Ctx->metadata;
120-
Ctx->object->refCounts.initForNotFreeing();
120+
Ctx->object->refCounts.initImmortal();
121121
}
122122

123123
// TODO: We could generate inline code for the fast-path, i.e. the metadata

0 commit comments

Comments
 (0)