Skip to content

Commit 2d9fa20

Browse files
committed
implement ManagerBuffer.reallocated to allow realloc'ing the storage
1 parent c4aed1a commit 2d9fa20

File tree

5 files changed

+101
-1
lines changed

5 files changed

+101
-1
lines changed

stdlib/public/core/Builtin.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,9 @@ internal func _isUnique<T>(_ object: inout T) -> Bool {
658658
return Bool(Builtin.isUnique(&object))
659659
}
660660

661+
@_silgen_name("_swift_reallocObject")
662+
internal func _reallocObject(_ object: UnsafeMutableRawPointer, _ newSizeInBytes: Int) -> UnsafeMutableRawPointer?
663+
661664
/// Returns `true` if `object` is uniquely referenced.
662665
/// This provides sanity checks on top of the Builtin.
663666
@_transparent

stdlib/public/core/ManagedBuffer.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,30 @@ open class ManagedBuffer<Header, Element> {
139139
}
140140
}
141141

142+
@inline(never)
143+
public func tryReallocateUniquelyReferenced<Header, Element, Buffer: ManagedBuffer<Header, Element>>(
144+
buffer: inout Buffer,
145+
newMinimumCapacity: Int
146+
) -> Bool {
147+
precondition(_isBitwiseTakable(Header.self))
148+
precondition(_isBitwiseTakable(Element.self))
149+
precondition(isKnownUniquelyReferenced(&buffer))
150+
151+
let newSizeInBytes = MemoryLayout<Header>.stride
152+
+ newMinimumCapacity * MemoryLayout<Element>.stride
153+
154+
return withUnsafeMutablePointer(to: &buffer) {
155+
$0.withMemoryRebound(to: UnsafeMutableRawPointer.self, capacity: 1) {
156+
if let reallocdObject = _reallocObject($0.pointee, newSizeInBytes) {
157+
$0.pointee = reallocdObject
158+
return true
159+
} else {
160+
return false
161+
}
162+
}
163+
}
164+
}
165+
142166
/// Contains a buffer object, and provides access to an instance of
143167
/// `Header` and contiguous storage for an arbitrary number of
144168
/// `Element` instances stored in that buffer.

stdlib/public/runtime/SwiftObject.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,11 @@ NSString *getDescription(OpaqueValue *value, const Metadata *type);
8888

8989
#endif
9090

91+
namespace swift {
92+
93+
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_SPI
94+
HeapObject *_swift_reallocObject(HeapObject *obj, size_t size);
95+
96+
}
97+
9198
#endif

stdlib/public/runtime/SwiftObject.mm

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ static uintptr_t computeISAMask() {
8282
#endif
8383
}
8484

85+
bool isObjCPinned(HeapObject *obj) {
86+
#if SWIFT_OBJC_INTEROP
87+
/* future: implement checking the relevant objc runtime bits */
88+
return true;
89+
#else
90+
return false;
91+
#endif
92+
}
93+
94+
// returns non-null if realloc was successful
95+
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_SPI
96+
HeapObject *swift::_swift_reallocObject(HeapObject *obj, size_t size) {
97+
if (isObjCPinned(obj) || obj->refCounts.hasSideTable()) {
98+
return nullptr;
99+
}
100+
return (HeapObject *)realloc(obj, size);
101+
}
102+
85103
#if SWIFT_OBJC_INTEROP
86104

87105
/// \brief Replacement for ObjC object_isClass(), which is unavailable on

test/stdlib/ManagedBuffer.swift

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extension ManagedBufferPointer
4040

4141
struct CountAndCapacity {
4242
var count: LifetimeTracked
43-
let capacity: Int
43+
var capacity: Int
4444
}
4545

4646
// An example of ManagedBuffer, very similar to what Array will use.
@@ -96,6 +96,23 @@ final class TestManagedBuffer<T> : ManagedBuffer<CountAndCapacity, T> {
9696
}
9797
self.count = count + 2
9898
}
99+
100+
class func tryGrow(_ buffer: inout TestManagedBuffer<T>, newCapacity: Int) -> Bool {
101+
guard isKnownUniquelyReferenced(&buffer) else {
102+
return false
103+
}
104+
guard newCapacity > buffer.capacity else {
105+
return false
106+
}
107+
108+
if tryReallocateUniquelyReferenced(buffer: &buffer,
109+
newMinimumCapacity: newCapacity) {
110+
buffer.header.capacity = newCapacity
111+
return true
112+
} else {
113+
return false
114+
}
115+
}
99116
}
100117

101118
class MyBuffer<T> {
@@ -241,4 +258,35 @@ tests.test("isKnownUniquelyReferenced") {
241258
_fixLifetime(s2)
242259
}
243260

261+
tests.test("canGrowUsingRealloc") {
262+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
263+
return // realloc currently unsupported on Darwin
264+
#endif
265+
func testGrow(_ buffer: inout TestManagedBuffer<LifetimeTracked>,
266+
newCapacity: Int,
267+
shouldSucceed: Bool = true) {
268+
let s = TestManagedBuffer.tryGrow(&buffer, newCapacity: newCapacity)
269+
expectEqual(s, shouldSucceed)
270+
if shouldSucceed {
271+
expectLE(newCapacity, buffer.myCapacity)
272+
expectGE((newCapacity + 1) * 2, buffer.myCapacity)
273+
}
274+
repeat {
275+
buffer.append(LifetimeTracked(buffer.count))
276+
} while buffer.count < buffer.myCapacity - 2
277+
}
278+
279+
var b = TestManagedBuffer<LifetimeTracked>.create(0)
280+
// allow some over-allocation
281+
expectLE(0, b.myCapacity)
282+
expectGE(3, b.myCapacity)
283+
284+
testGrow(&b, newCapacity: 5)
285+
testGrow(&b, newCapacity: 8)
286+
testGrow(&b, newCapacity: 1000)
287+
testGrow(&b, newCapacity: 16000)
288+
var b2 = b
289+
testGrow(&b, newCapacity: 2000, shouldSucceed: false)
290+
}
291+
244292
runAllTests()

0 commit comments

Comments
 (0)