Skip to content

Commit 091a6d8

Browse files
authored
Merge pull request #31601 from lorentey/data-append
[Foundation] Data.append: Switch to using resetBytes
2 parents fe9751f + 658effd commit 091a6d8

File tree

2 files changed

+161
-62
lines changed

2 files changed

+161
-62
lines changed

stdlib/public/Darwin/Foundation/Data.swift

Lines changed: 66 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,65 +1415,6 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
14151415
}
14161416
}
14171417

1418-
@inlinable
1419-
@_alwaysEmitIntoClient
1420-
internal mutating func _truncateOrZeroExtend(toCount newCount: Int) {
1421-
switch self {
1422-
case .empty:
1423-
if newCount == 0 {
1424-
return
1425-
} else if InlineData.canStore(count: newCount) {
1426-
self = .inline(InlineData(count: newCount))
1427-
} else if InlineSlice.canStore(count: newCount) {
1428-
self = .slice(InlineSlice(count: newCount))
1429-
} else {
1430-
self = .large(LargeSlice(count: newCount))
1431-
}
1432-
case .inline(var inline):
1433-
if newCount == 0 {
1434-
self = .empty
1435-
} else if InlineData.canStore(count: newCount) {
1436-
guard inline.count != newCount else { return }
1437-
inline.count = newCount
1438-
self = .inline(inline)
1439-
} else if InlineSlice.canStore(count: newCount) {
1440-
var slice = InlineSlice(inline)
1441-
slice.count = newCount
1442-
self = .slice(slice)
1443-
} else {
1444-
var slice = LargeSlice(inline)
1445-
slice.count = newCount
1446-
self = .large(slice)
1447-
}
1448-
case .slice(var slice):
1449-
if newCount == 0 && slice.startIndex == 0 {
1450-
self = .empty
1451-
} else if slice.startIndex == 0 && InlineData.canStore(count: newCount) {
1452-
self = .inline(InlineData(slice, count: newCount))
1453-
} else if InlineSlice.canStore(count: newCount + slice.startIndex) {
1454-
guard slice.count != newCount else { return }
1455-
self = .empty // TODO: remove this when mgottesman lands optimizations
1456-
slice.count = newCount
1457-
self = .slice(slice)
1458-
} else {
1459-
var newSlice = LargeSlice(slice)
1460-
newSlice.count = newCount
1461-
self = .large(newSlice)
1462-
}
1463-
case .large(var slice):
1464-
if newCount == 0 && slice.startIndex == 0 {
1465-
self = .empty
1466-
} else if slice.startIndex == 0 && InlineData.canStore(count: newCount) {
1467-
self = .inline(InlineData(slice, count: newCount))
1468-
} else {
1469-
guard slice.count != newCount else { return }
1470-
self = .empty // TODO: remove this when mgottesman lands optimizations
1471-
slice.count = newCount
1472-
self = .large(slice)
1473-
}
1474-
}
1475-
}
1476-
14771418
@inlinable // This is @inlinable as reasonably small.
14781419
var count: Int {
14791420
get {
@@ -1485,7 +1426,69 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
14851426
}
14861427
}
14871428
set(newValue) {
1488-
_truncateOrZeroExtend(toCount: newValue)
1429+
// HACK: The definition of this inline function takes an inout reference to self, giving the optimizer a unique referencing guarantee.
1430+
// This allows us to avoid excessive retain-release traffic around modifying enum values, and inlining the function then avoids the additional frame.
1431+
@inline(__always)
1432+
func apply(_ representation: inout _Representation, _ newValue: Int) -> _Representation? {
1433+
switch representation {
1434+
case .empty:
1435+
if newValue == 0 {
1436+
return nil
1437+
} else if InlineData.canStore(count: newValue) {
1438+
return .inline(InlineData(count: newValue))
1439+
} else if InlineSlice.canStore(count: newValue) {
1440+
return .slice(InlineSlice(count: newValue))
1441+
} else {
1442+
return .large(LargeSlice(count: newValue))
1443+
}
1444+
case .inline(var inline):
1445+
if newValue == 0 {
1446+
return .empty
1447+
} else if InlineData.canStore(count: newValue) {
1448+
guard inline.count != newValue else { return nil }
1449+
inline.count = newValue
1450+
return .inline(inline)
1451+
} else if InlineSlice.canStore(count: newValue) {
1452+
var slice = InlineSlice(inline)
1453+
slice.count = newValue
1454+
return .slice(slice)
1455+
} else {
1456+
var slice = LargeSlice(inline)
1457+
slice.count = newValue
1458+
return .large(slice)
1459+
}
1460+
case .slice(var slice):
1461+
if newValue == 0 && slice.startIndex == 0 {
1462+
return .empty
1463+
} else if slice.startIndex == 0 && InlineData.canStore(count: newValue) {
1464+
return .inline(InlineData(slice, count: newValue))
1465+
} else if InlineSlice.canStore(count: newValue + slice.startIndex) {
1466+
guard slice.count != newValue else { return nil }
1467+
representation = .empty // TODO: remove this when mgottesman lands optimizations
1468+
slice.count = newValue
1469+
return .slice(slice)
1470+
} else {
1471+
var newSlice = LargeSlice(slice)
1472+
newSlice.count = newValue
1473+
return .large(newSlice)
1474+
}
1475+
case .large(var slice):
1476+
if newValue == 0 && slice.startIndex == 0 {
1477+
return .empty
1478+
} else if slice.startIndex == 0 && InlineData.canStore(count: newValue) {
1479+
return .inline(InlineData(slice, count: newValue))
1480+
} else {
1481+
guard slice.count != newValue else { return nil}
1482+
representation = .empty // TODO: remove this when mgottesman lands optimizations
1483+
slice.count = newValue
1484+
return .large(slice)
1485+
}
1486+
}
1487+
}
1488+
1489+
if let rep = apply(&self, newValue) {
1490+
self = rep
1491+
}
14891492
}
14901493
}
14911494

@@ -2385,16 +2388,17 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
23852388
// Copy as much as we can in one shot.
23862389
let underestimatedCount = elements.underestimatedCount
23872390
let originalCount = _representation.count
2388-
_representation._truncateOrZeroExtend(toCount: originalCount + underestimatedCount)
2391+
resetBytes(in: self.endIndex ..< self.endIndex + underestimatedCount)
23892392
var (iter, copiedCount): (S.Iterator, Int) = _representation.withUnsafeMutableBytes { buffer in
2393+
assert(buffer.count == originalCount + underestimatedCount)
23902394
let start = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self) + originalCount
23912395
let b = UnsafeMutableBufferPointer(start: start, count: buffer.count - originalCount)
23922396
return elements._copyContents(initializing: b)
23932397
}
23942398
guard copiedCount == underestimatedCount else {
23952399
// We can't trap here. We have to allow an underfilled buffer
23962400
// to emulate the previous implementation.
2397-
_representation._truncateOrZeroExtend(toCount: originalCount + copiedCount)
2401+
_representation.replaceSubrange(startIndex + originalCount + copiedCount ..< endIndex, with: nil, count: 0)
23982402
return
23992403
}
24002404

test/stdlib/TestData.swift

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3883,6 +3883,99 @@ class TestData : TestDataSuper {
38833883
}
38843884
}
38853885
}
3886+
3887+
// This is a (potentially invalid) sequence that produces a configurable number of 42s and has a freely customizable `underestimatedCount`.
3888+
struct TestSequence: Sequence {
3889+
typealias Element = UInt8
3890+
struct Iterator: IteratorProtocol {
3891+
var _remaining: Int
3892+
init(_ count: Int) {
3893+
_remaining = count
3894+
}
3895+
mutating func next() -> UInt8? {
3896+
guard _remaining > 0 else { return nil }
3897+
_remaining -= 1
3898+
return 42
3899+
}
3900+
}
3901+
let underestimatedCount: Int
3902+
let count: Int
3903+
3904+
func makeIterator() -> Iterator {
3905+
return Iterator(count)
3906+
}
3907+
}
3908+
3909+
func test_init_TestSequence() {
3910+
// Underestimated count
3911+
do {
3912+
let d = Data(TestSequence(underestimatedCount: 0, count: 10))
3913+
expectEqual(10, d.count)
3914+
expectEqual(Array(repeating: 42 as UInt8, count: 10), Array(d))
3915+
}
3916+
3917+
// Very underestimated count (to exercise realloc path)
3918+
do {
3919+
let d = Data(TestSequence(underestimatedCount: 0, count: 1000))
3920+
expectEqual(1000, d.count)
3921+
expectEqual(Array(repeating: 42 as UInt8, count: 1000), Array(d))
3922+
}
3923+
3924+
// Exact count
3925+
do {
3926+
let d = Data(TestSequence(underestimatedCount: 10, count: 10))
3927+
expectEqual(10, d.count)
3928+
expectEqual(Array(repeating: 42 as UInt8, count: 10), Array(d))
3929+
}
3930+
3931+
// Overestimated count. This is an illegal case, so trapping would be fine.
3932+
// However, for compatibility with the implementation in Swift 5, Data
3933+
// handles this case by simply truncating itself to the actual size.
3934+
do {
3935+
let d = Data(TestSequence(underestimatedCount: 20, count: 10))
3936+
expectEqual(10, d.count)
3937+
expectEqual(Array(repeating: 42 as UInt8, count: 10), Array(d))
3938+
}
3939+
}
3940+
3941+
func test_append_TestSequence() {
3942+
let base = Data(Array(repeating: 23 as UInt8, count: 10))
3943+
3944+
// Underestimated count
3945+
do {
3946+
var d = base
3947+
d.append(contentsOf: TestSequence(underestimatedCount: 0, count: 10))
3948+
expectEqual(20, d.count)
3949+
expectEqual(Array(base) + Array(repeating: 42 as UInt8, count: 10),
3950+
Array(d))
3951+
}
3952+
3953+
// Very underestimated count (to exercise realloc path)
3954+
do {
3955+
var d = base
3956+
d.append(contentsOf: TestSequence(underestimatedCount: 0, count: 1000))
3957+
expectEqual(1010, d.count)
3958+
expectEqual(Array(base) + Array(repeating: 42 as UInt8, count: 1000), Array(d))
3959+
}
3960+
3961+
// Exact count
3962+
do {
3963+
var d = base
3964+
d.append(contentsOf: TestSequence(underestimatedCount: 10, count: 10))
3965+
expectEqual(20, d.count)
3966+
expectEqual(Array(base) + Array(repeating: 42 as UInt8, count: 10), Array(d))
3967+
}
3968+
3969+
// Overestimated count. This is an illegal case, so trapping would be fine.
3970+
// However, for compatibility with the implementation in Swift 5, Data
3971+
// handles this case by simply truncating itself to the actual size.
3972+
do {
3973+
var d = base
3974+
d.append(contentsOf: TestSequence(underestimatedCount: 20, count: 10))
3975+
expectEqual(20, d.count)
3976+
expectEqual(Array(base) + Array(repeating: 42 as UInt8, count: 10), Array(d))
3977+
}
3978+
}
38863979
}
38873980

38883981
#if !FOUNDATION_XCTEST
@@ -4211,6 +4304,8 @@ if #available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) {
42114304
}
42124305
DataTests.test("test_increaseCount") { TestData().test_increaseCount() }
42134306
DataTests.test("test_decreaseCount") { TestData().test_decreaseCount() }
4307+
DataTests.test("test_increaseCount") { TestData().test_init_TestSequence() }
4308+
DataTests.test("test_decreaseCount") { TestData().test_append_TestSequence() }
42144309

42154310

42164311
// XCTest does not have a crash detection, whereas lit does

0 commit comments

Comments
 (0)