Skip to content

Commit b4a202c

Browse files
authored
Merge pull request #32055 from eeckstein/array-changes
stdlib: some small Array improvements to prepare for COW representation
2 parents 1a770a3 + 68728dc commit b4a202c

File tree

9 files changed

+139
-67
lines changed

9 files changed

+139
-67
lines changed

stdlib/public/core/Array.swift

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,7 @@ extension Array {
346346
@_semantics("array.make_mutable")
347347
internal mutating func _makeMutableAndUnique() {
348348
if _slowPath(!_buffer.isMutableAndUniquelyReferenced()) {
349-
_createNewBuffer(bufferIsUnique: false, minimumCapacity: count,
350-
growForAppend: false)
349+
_buffer = _buffer._consumeAndCreateNew()
351350
}
352351
}
353352

@@ -1049,34 +1048,13 @@ extension Array: RangeReplaceableCollection {
10491048
/// If `growForAppend` is true, the new capacity is calculated using
10501049
/// `_growArrayCapacity`, but at least kept at `minimumCapacity`.
10511050
@_alwaysEmitIntoClient
1052-
@inline(never)
10531051
internal mutating func _createNewBuffer(
10541052
bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
10551053
) {
1056-
let newCapacity = _growArrayCapacity(oldCapacity: _getCapacity(),
1057-
minimumCapacity: minimumCapacity,
1058-
growForAppend: growForAppend)
1059-
let count = _getCount()
1060-
_internalInvariant(newCapacity >= count)
1061-
1062-
let newBuffer = _ContiguousArrayBuffer<Element>(
1063-
_uninitializedCount: count, minimumCapacity: newCapacity)
1064-
1065-
if bufferIsUnique {
1066-
_internalInvariant(_buffer.isUniquelyReferenced())
1067-
1068-
// As an optimization, if the original buffer is unique, we can just move
1069-
// the elements instead of copying.
1070-
let dest = newBuffer.firstElementAddress
1071-
dest.moveInitialize(from: _buffer.firstElementAddress,
1072-
count: count)
1073-
_buffer.count = 0
1074-
} else {
1075-
_buffer._copyContents(
1076-
subRange: 0..<count,
1077-
initializing: newBuffer.firstElementAddress)
1078-
}
1079-
_buffer = _Buffer(_buffer: newBuffer, shiftedToStartIndex: 0)
1054+
_internalInvariant(!bufferIsUnique || _buffer.isUniquelyReferenced())
1055+
_buffer = _buffer._consumeAndCreateNew(bufferIsUnique: bufferIsUnique,
1056+
minimumCapacity: minimumCapacity,
1057+
growForAppend: growForAppend)
10801058
}
10811059

10821060
/// Copy the contents of the current buffer to a new unique mutable buffer.

stdlib/public/core/ArrayBuffer.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,60 @@ extension _ArrayBuffer {
125125
return _fastPath(_isNative) ? _native._asCocoaArray() : _nonNative.buffer
126126
}
127127

128+
/// Creates and returns a new uniquely referenced buffer which is a copy of
129+
/// this buffer.
130+
///
131+
/// This buffer is consumed, i.e. it's released.
132+
@_alwaysEmitIntoClient
133+
@inline(never)
134+
@_semantics("optimize.sil.specialize.owned2guarantee.never")
135+
internal __consuming func _consumeAndCreateNew() -> _ArrayBuffer {
136+
return _consumeAndCreateNew(bufferIsUnique: false,
137+
minimumCapacity: count,
138+
growForAppend: false)
139+
}
140+
141+
/// Creates and returns a new uniquely referenced buffer which is a copy of
142+
/// this buffer.
143+
///
144+
/// If `bufferIsUnique` is true, the buffer is assumed to be uniquely
145+
/// referenced and the elements are moved - instead of copied - to the new
146+
/// buffer.
147+
/// The `minimumCapacity` is the lower bound for the new capacity.
148+
/// If `growForAppend` is true, the new capacity is calculated using
149+
/// `_growArrayCapacity`, but at least kept at `minimumCapacity`.
150+
///
151+
/// This buffer is consumed, i.e. it's released.
152+
@_alwaysEmitIntoClient
153+
@inline(never)
154+
@_semantics("optimize.sil.specialize.owned2guarantee.never")
155+
internal __consuming func _consumeAndCreateNew(
156+
bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
157+
) -> _ArrayBuffer {
158+
let newCapacity = _growArrayCapacity(oldCapacity: capacity,
159+
minimumCapacity: minimumCapacity,
160+
growForAppend: growForAppend)
161+
let c = count
162+
_internalInvariant(newCapacity >= c)
163+
164+
let newBuffer = _ContiguousArrayBuffer<Element>(
165+
_uninitializedCount: c, minimumCapacity: newCapacity)
166+
167+
if bufferIsUnique {
168+
// As an optimization, if the original buffer is unique, we can just move
169+
// the elements instead of copying.
170+
let dest = newBuffer.firstElementAddress
171+
dest.moveInitialize(from: firstElementAddress,
172+
count: c)
173+
_native.count = 0
174+
} else {
175+
_copyContents(
176+
subRange: 0..<c,
177+
initializing: newBuffer.firstElementAddress)
178+
}
179+
return _ArrayBuffer(_buffer: newBuffer, shiftedToStartIndex: 0)
180+
}
181+
128182
/// If this buffer is backed by a uniquely-referenced mutable
129183
/// `_ContiguousArrayBuffer` that can be grown in-place to allow the self
130184
/// buffer store minimumCapacity elements, returns that buffer.

stdlib/public/core/ArrayBufferProtocol.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ extension _ArrayBufferProtocol where Indices == Range<Int>{
150150
let eraseCount = subrange.count
151151

152152
let growth = newCount - eraseCount
153-
self.count = oldCount + growth
153+
// This check will prevent storing a 0 count to the empty array singleton.
154+
if growth != 0 {
155+
self.count = oldCount + growth
156+
}
154157

155158
let elements = self.subscriptBaseAddress
156159
let oldTailIndex = subrange.upperBound

stdlib/public/core/ContiguousArray.swift

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ extension ContiguousArray {
6767
@_semantics("array.make_mutable")
6868
internal mutating func _makeMutableAndUnique() {
6969
if _slowPath(!_buffer.isMutableAndUniquelyReferenced()) {
70-
_createNewBuffer(bufferIsUnique: false, minimumCapacity: count,
71-
growForAppend: false)
70+
_buffer = _buffer._consumeAndCreateNew()
7271
}
7372
}
7473

@@ -690,30 +689,10 @@ extension ContiguousArray: RangeReplaceableCollection {
690689
internal mutating func _createNewBuffer(
691690
bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
692691
) {
693-
let newCapacity = _growArrayCapacity(oldCapacity: _getCapacity(),
694-
minimumCapacity: minimumCapacity,
695-
growForAppend: growForAppend)
696-
let count = _getCount()
697-
_internalInvariant(newCapacity >= count)
698-
699-
let newBuffer = _ContiguousArrayBuffer<Element>(
700-
_uninitializedCount: count, minimumCapacity: newCapacity)
701-
702-
if bufferIsUnique {
703-
_internalInvariant(_buffer.isUniquelyReferenced())
704-
705-
// As an optimization, if the original buffer is unique, we can just move
706-
// the elements instead of copying.
707-
let dest = newBuffer.firstElementAddress
708-
dest.moveInitialize(from: _buffer.firstElementAddress,
709-
count: count)
710-
_buffer.count = 0
711-
} else {
712-
_buffer._copyContents(
713-
subRange: 0..<count,
714-
initializing: newBuffer.firstElementAddress)
715-
}
716-
_buffer = _Buffer(_buffer: newBuffer, shiftedToStartIndex: 0)
692+
_internalInvariant(!bufferIsUnique || _buffer.isUniquelyReferenced())
693+
_buffer = _buffer._consumeAndCreateNew(bufferIsUnique: bufferIsUnique,
694+
minimumCapacity: minimumCapacity,
695+
growForAppend: growForAppend)
717696
}
718697

719698
/// Copy the contents of the current buffer to a new unique mutable buffer.

stdlib/public/core/ContiguousArrayBuffer.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,60 @@ internal struct _ContiguousArrayBuffer<Element>: _ArrayBufferProtocol {
511511
return _isUnique(&_storage)
512512
}
513513

514+
/// Creates and returns a new uniquely referenced buffer which is a copy of
515+
/// this buffer.
516+
///
517+
/// This buffer is consumed, i.e. it's released.
518+
@_alwaysEmitIntoClient
519+
@inline(never)
520+
@_semantics("optimize.sil.specialize.owned2guarantee.never")
521+
internal __consuming func _consumeAndCreateNew() -> _ContiguousArrayBuffer {
522+
return _consumeAndCreateNew(bufferIsUnique: false,
523+
minimumCapacity: count,
524+
growForAppend: false)
525+
}
526+
527+
/// Creates and returns a new uniquely referenced buffer which is a copy of
528+
/// this buffer.
529+
///
530+
/// If `bufferIsUnique` is true, the buffer is assumed to be uniquely
531+
/// referenced and the elements are moved - instead of copied - to the new
532+
/// buffer.
533+
/// The `minimumCapacity` is the lower bound for the new capacity.
534+
/// If `growForAppend` is true, the new capacity is calculated using
535+
/// `_growArrayCapacity`, but at least kept at `minimumCapacity`.
536+
///
537+
/// This buffer is consumed, i.e. it's released.
538+
@_alwaysEmitIntoClient
539+
@inline(never)
540+
@_semantics("optimize.sil.specialize.owned2guarantee.never")
541+
internal __consuming func _consumeAndCreateNew(
542+
bufferIsUnique: Bool, minimumCapacity: Int, growForAppend: Bool
543+
) -> _ContiguousArrayBuffer {
544+
let newCapacity = _growArrayCapacity(oldCapacity: capacity,
545+
minimumCapacity: minimumCapacity,
546+
growForAppend: growForAppend)
547+
let c = count
548+
_internalInvariant(newCapacity >= c)
549+
550+
let newBuffer = _ContiguousArrayBuffer<Element>(
551+
_uninitializedCount: c, minimumCapacity: newCapacity)
552+
553+
if bufferIsUnique {
554+
// As an optimization, if the original buffer is unique, we can just move
555+
// the elements instead of copying.
556+
let dest = newBuffer.firstElementAddress
557+
dest.moveInitialize(from: firstElementAddress,
558+
count: c)
559+
count = 0
560+
} else {
561+
_copyContents(
562+
subRange: 0..<c,
563+
initializing: newBuffer.firstElementAddress)
564+
}
565+
return newBuffer
566+
}
567+
514568
#if _runtime(_ObjC)
515569

516570
/// Convert to an NSArray.

stdlib/public/core/SetAlgebra.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,8 @@ extension SetAlgebra {
409409
public init<S: Sequence>(_ sequence: __owned S)
410410
where S.Element == Element {
411411
self.init()
412+
// Needed to fully optimize OptionSet literals.
413+
_onFastPath()
412414
for e in sequence { insert(e) }
413415
}
414416

test/IRGen/multithread_module.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ public func mutateBaseArray(_ arr: inout [Base], _ x: Base) {
6868

6969
// Check if all specializations from stdlib functions are created in the same LLVM module.
7070

71-
// CHECK-MAINLL-DAG: define {{.*}} @"$sSa16_createNewBuffer14bufferIsUnique15minimumCapacity13growForAppendySb_SiSbtF4test8MyStructV_Tg5"
72-
// CHECK-MAINLL-DAG: define {{.*}} @"$sSa16_createNewBuffer14bufferIsUnique15minimumCapacity13growForAppendySb_SiSbtF4test4BaseC_Tg5"
71+
// CHECK-MAINLL-DAG: define {{.*}} @"$ss{{(12_|22_Contiguous)}}ArrayBufferV20_consumeAndCreateNew14bufferIsUnique15minimumCapacity13growForAppendAByxGSb_SiSbtF4test8MyStructV_Tg5"
72+
// CHECK-MAINLL-DAG: define {{.*}} @"$ss{{(12_|22_Contiguous)}}ArrayBufferV20_consumeAndCreateNew14bufferIsUnique15minimumCapacity13growForAppendAByxGSb_SiSbtF4test4BaseC_Tg5"
7373

7474
// Check if the DI filename is correct and not "<unknown>".
7575

test/SILOptimizer/array_contentof_opt.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -O -sil-verify-all -emit-sil -Xllvm '-sil-inline-never-functions=$sSa6append' %s | %FileCheck %s
1+
// RUN: %target-swift-frontend -O -sil-verify-all -emit-sil -Xllvm '-sil-inline-never-functions=$sSa6appendyy' %s | %FileCheck %s
22
// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib
33

44
// This is an end-to-end test of the Array.append(contentsOf:) ->
@@ -24,7 +24,7 @@ public func testInt(_ a: inout [Int]) {
2424
}
2525

2626
// CHECK-LABEL: sil @{{.*}}testThreeInts
27-
// CHECK-DAG: [[FR:%[0-9]+]] = function_ref @${{(sSa15reserveCapacityyySiFSi_Tg5|sSa16_createNewBuffer)}}
27+
// CHECK-DAG: [[FR:%[0-9]+]] = function_ref @${{.*(reserveCapacity|_createNewBuffer)}}
2828
// CHECK-DAG: apply [[FR]]
2929
// CHECK-DAG: [[F:%[0-9]+]] = function_ref @$sSa6appendyyxnFSi_Tg5
3030
// CHECK-DAG: apply [[F]]
@@ -37,7 +37,7 @@ public func testThreeInts(_ a: inout [Int]) {
3737

3838
// CHECK-LABEL: sil @{{.*}}testTooManyInts
3939
// CHECK-NOT: apply
40-
// CHECK: [[F:%[0-9]+]] = function_ref @$sSa6append10contentsOfyqd__n_t7ElementQyd__RszSTRd__lFSi_SaySiGTg5
40+
// CHECK: [[F:%[0-9]+]] = function_ref @${{.*append.*contentsOf.*}}
4141
// CHECK-NOT: apply
4242
// CHECK: apply [[F]]
4343
// CHECK-NOT: apply
@@ -65,12 +65,12 @@ public func dontPropagateContiguousArray(_ a: inout ContiguousArray<UInt8>) {
6565

6666
// Check if the specialized Array.append<A>(contentsOf:) is reasonably optimized for Array<Int>.
6767

68-
// CHECK-LABEL: sil shared {{.*}}@$sSa6append10contentsOfyqd__n_t7ElementQyd__RszSTRd__lFSi_SaySiGTg5Tf4gn_n
68+
// CHECK-LABEL: sil shared {{.*}}@$sSa6append10contentsOfyqd__n_t7ElementQyd__RszSTRd__lFSi_SaySiGTg5
6969

7070
// There should only be a single call to _createNewBuffer or reserveCapacityForAppend/reserveCapacityImpl.
7171

7272
// CHECK-NOT: apply
73-
// CHECK: [[F:%[0-9]+]] = function_ref @{{.*(_createNewBuffer|reserveCapacity).*}}
73+
// CHECK: [[F:%[0-9]+]] = function_ref @{{.*(_consumeAndCreateNew|reserveCapacity).*}}
7474
// CHECK-NEXT: apply [[F]]
7575
// CHECK-NOT: apply
7676

test/SILOptimizer/optionset.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public struct TestOptions: OptionSet {
1414

1515
// CHECK: sil @{{.*}}returnTestOptions{{.*}}
1616
// CHECK-NEXT: bb0:
17+
// CHECK-NEXT: builtin
1718
// CHECK-NEXT: integer_literal {{.*}}, 15
1819
// CHECK-NEXT: struct $Int
1920
// CHECK-NEXT: struct $TestOptions
@@ -22,18 +23,19 @@ public func returnTestOptions() -> TestOptions {
2223
return [.first, .second, .third, .fourth]
2324
}
2425

25-
// CHECK: sil @{{.*}}returnEmptyTestOptions{{.*}}
26-
// CHECK-NEXT: bb0:
27-
// CHECK-NEXT: integer_literal {{.*}}, 0
28-
// CHECK-NEXT: struct $Int
29-
// CHECK-NEXT: struct $TestOptions
30-
// CHECK-NEXT: return
26+
// CHECK: sil @{{.*}}returnEmptyTestOptions{{.*}}
27+
// CHECK: [[ZERO:%[0-9]+]] = integer_literal {{.*}}, 0
28+
// CHECK: [[ZEROINT:%[0-9]+]] = struct $Int ([[ZERO]]
29+
// CHECK: [[TO:%[0-9]+]] = struct $TestOptions ([[ZEROINT]]
30+
// CHECK: return [[TO]]
31+
// CHECK: } // end sil function {{.*}}returnEmptyTestOptions{{.*}}
3132
public func returnEmptyTestOptions() -> TestOptions {
3233
return []
3334
}
3435

3536
// CHECK: alloc_global @{{.*}}globalTestOptions{{.*}}
3637
// CHECK-NEXT: global_addr
38+
// CHECK-NEXT: builtin
3739
// CHECK-NEXT: integer_literal {{.*}}, 15
3840
// CHECK-NEXT: struct $Int
3941
// CHECK-NEXT: struct $TestOptions

0 commit comments

Comments
 (0)