Skip to content

Commit baa3736

Browse files
committed
Merge branch 'main' into output-html
2 parents de766b9 + b2975d8 commit baa3736

34 files changed

+1584
-513
lines changed

Sources/DocCCommon/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ See https://swift.org/LICENSE.txt for license information
88
#]]
99

1010
add_library(DocCCommon STATIC
11+
FixedSizeBitSet.swift
12+
Mutex.swift
1113
SourceLanguage.swift)
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2024-2025 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
/// A fixed size bit set, used for storing very small amounts of small integer values.
12+
///
13+
/// This type can only store values that are `0 ..< Storage.bitWidth` which makes it _unsuitable_ as a general purpose set-algebra type.
14+
/// However, in specialized cases where the caller can guarantee that all values are in bounds, this type can offer a memory and performance improvement.
15+
package struct _FixedSizeBitSet<Storage: FixedWidthInteger & Sendable>: Sendable {
16+
package typealias Element = Int
17+
18+
package init() {}
19+
20+
@usableFromInline
21+
private(set) var storage: Storage = 0
22+
23+
@inlinable
24+
init(storage: Storage) {
25+
self.storage = storage
26+
}
27+
}
28+
29+
// MARK: Set Algebra
30+
31+
extension _FixedSizeBitSet: SetAlgebra {
32+
private static func mask(_ number: Int) -> Storage {
33+
precondition(number < Storage.bitWidth, "Number \(number) is out of bounds (0..<\(Storage.bitWidth))")
34+
return 1 &<< number
35+
}
36+
37+
@inlinable
38+
@discardableResult
39+
mutating package func insert(_ member: Int) -> (inserted: Bool, memberAfterInsert: Int) {
40+
let newStorage = storage | _FixedSizeBitSet.mask(member)
41+
defer {
42+
storage = newStorage
43+
}
44+
return (newStorage != storage, member)
45+
}
46+
47+
@inlinable
48+
@discardableResult
49+
mutating package func remove(_ member: Int) -> Int? {
50+
let newStorage = storage & ~_FixedSizeBitSet.mask(member)
51+
defer {
52+
storage = newStorage
53+
}
54+
return newStorage != storage ? member : nil
55+
}
56+
57+
@inlinable
58+
@discardableResult
59+
mutating package func update(with member: Int) -> Int? {
60+
let (inserted, _) = insert(member)
61+
return inserted ? nil : member
62+
}
63+
64+
@inlinable
65+
package func contains(_ member: Int) -> Bool {
66+
storage & _FixedSizeBitSet.mask(member) != 0
67+
}
68+
69+
@inlinable
70+
package func isSuperset(of other: Self) -> Bool {
71+
(storage & other.storage) == other.storage
72+
}
73+
74+
@inlinable
75+
package func union(_ other: Self) -> Self {
76+
.init(storage: storage | other.storage)
77+
}
78+
79+
@inlinable
80+
package func intersection(_ other: Self) -> Self {
81+
.init(storage: storage & other.storage)
82+
}
83+
84+
@inlinable
85+
package func symmetricDifference(_ other: Self) -> Self {
86+
.init(storage: storage ^ other.storage)
87+
}
88+
89+
@inlinable
90+
mutating package func formUnion(_ other: Self) {
91+
storage |= other.storage
92+
}
93+
94+
@inlinable
95+
mutating package func formIntersection(_ other: Self) {
96+
storage &= other.storage
97+
}
98+
99+
@inlinable
100+
mutating package func formSymmetricDifference(_ other: Self) {
101+
storage ^= other.storage
102+
}
103+
104+
@inlinable
105+
package var isEmpty: Bool {
106+
storage == 0
107+
}
108+
}
109+
110+
// MARK: Sequence
111+
112+
extension _FixedSizeBitSet: Sequence {
113+
@inlinable
114+
package func makeIterator() -> some IteratorProtocol<Int> {
115+
_Iterator(set: self)
116+
}
117+
118+
private struct _Iterator: IteratorProtocol {
119+
typealias Element = Int
120+
121+
private var storage: Storage
122+
private var current: Int = -1
123+
124+
@inlinable
125+
init(set: _FixedSizeBitSet) {
126+
self.storage = set.storage
127+
}
128+
129+
@inlinable
130+
mutating func next() -> Int? {
131+
guard storage != 0 else {
132+
return nil
133+
}
134+
// If the set is somewhat sparse, we can find the next element faster by shifting to the next value.
135+
// This saves needing to do `contains()` checks for all the numbers since the previous element.
136+
let amountToShift = storage.trailingZeroBitCount &+ 1
137+
storage &>>= amountToShift
138+
139+
current &+= amountToShift
140+
return current
141+
}
142+
}
143+
}
144+
145+
// MARK: Collection
146+
147+
extension _FixedSizeBitSet: Collection {
148+
// Collection conformance requires an `Index` type, that the collection can advance, and `startIndex` and `endIndex` accessors that follow certain requirements.
149+
//
150+
// For this design, as a hidden implementation detail, the `Index` holds the bit offset to the element.
151+
152+
@inlinable
153+
package subscript(position: Index) -> Int {
154+
precondition(position.bit < Storage.bitWidth, "Index \(position.bit) out of bounds")
155+
// Because the index stores the bit offset, which is also the value, we can simply return the value without accessing the storage.
156+
return Int(position.bit)
157+
}
158+
159+
package struct Index: Comparable {
160+
// The bit offset into the storage to the value
161+
fileprivate var bit: UInt8
162+
163+
package static func < (lhs: Self, rhs: Self) -> Bool {
164+
lhs.bit < rhs.bit
165+
}
166+
}
167+
168+
@inlinable
169+
package var startIndex: Index {
170+
// This is the index (bit offset) to the smallest value in the bit set.
171+
Index(bit: UInt8(storage.trailingZeroBitCount))
172+
}
173+
174+
@inlinable
175+
package var endIndex: Index {
176+
// For a valid collection, the end index is required to be _exactly_ one past the last in-bounds index, meaning; `index(after: LAST_IN-BOUNDS_INDEX)`
177+
// If the collection implementation doesn't satisfy this requirement, it will have an infinitely long `indices` collection.
178+
// This either results in infinite implementations or hits internal preconditions in other Swift types that that collection has more elements than its `count`.
179+
180+
// See `index(after:)` below for explanation of how the index after is calculated.
181+
let lastInBoundsBit = UInt8(Storage.bitWidth &- storage.leadingZeroBitCount)
182+
return Index(bit: lastInBoundsBit &+ UInt8((storage &>> lastInBoundsBit).trailingZeroBitCount))
183+
}
184+
185+
@inlinable
186+
package func index(after currentIndex: Index) -> Index {
187+
// To advance the index we have to find the next 1 bit _after_ the current bit.
188+
// For example, consider the following 16 bits, where values are represented from right to left:
189+
// 0110 0010 0110 0010
190+
//
191+
// To go from the first index to the second index, we need to count the number of 0 bits between it and the next 1 bit.
192+
// We get this value by shifting the bits by one past the current index:
193+
// 0110 0010 0110 0010
194+
// ╰╴current index
195+
// 0001 1000 1001 1000
196+
// ~~~ 3 trailing zero bits
197+
//
198+
// The second index's absolute value is the one past the first index's value plus the number of trailing zero bits in the shifted value.
199+
//
200+
// For the third index we repeat the same process, starting by shifting the bits by one past second index:
201+
// 0110 0010 0110 0010
202+
// ╰╴current index
203+
// 0000 0001 1000 1001
204+
// 0 trailing zero bits
205+
//
206+
// This time there are no trailing zero bits in the shifted value, so the third index's absolute value is just one past the second index.
207+
let shift = currentIndex.bit &+ 1
208+
return Index(bit: shift &+ UInt8((storage &>> shift).trailingZeroBitCount))
209+
}
210+
211+
@inlinable
212+
package func formIndex(after index: inout Index) {
213+
// See `index(after:)` above for explanation.
214+
index.bit &+= 1
215+
index.bit &+= UInt8((storage &>> index.bit).trailingZeroBitCount)
216+
}
217+
218+
@inlinable
219+
package func distance(from start: Index, to end: Index) -> Int {
220+
// To compute the distance between two indices we have to find the number of 1 bits from the start index to (but excluding) the end index.
221+
// For example, consider the following 16 bits, where values are represented from right to left:
222+
// 0110 0010 0110 0010
223+
// end╶╯ ╰╴start
224+
//
225+
// To find the distance between the second index and the fourth index, we need to count the number of 0 bits between it and the next 1 bit.
226+
// We limit the calculation to this range in two steps.
227+
//
228+
// First, we mask out all the bits above the end index:
229+
// end╶╮ ╭╴start
230+
// 0110 0010 0110 0010
231+
// 0000 0011 1111 1111 mask
232+
//
233+
// Because collections can have end indices that extend out-of-bounds we need to clamp the mask from a larger integer type to avoid it wrapping around to 0.
234+
let mask = Storage(clamping: (1 &<< UInt(end.bit)) &- 1)
235+
var distance = storage & mask
236+
237+
// Then, we shift away all the bits below the start index:
238+
// end╶╮ ╭╴start
239+
// 0000 0010 0110 0010
240+
// 0000 0000 0000 1001
241+
distance &>>= start.bit
242+
243+
// The distance from start to end is the number of 1 bits in this number.
244+
return distance.nonzeroBitCount
245+
}
246+
247+
@inlinable
248+
package var first: Element? {
249+
isEmpty ? nil : storage.trailingZeroBitCount
250+
}
251+
252+
@inlinable
253+
package func min() -> Element? {
254+
first // The elements are already sorted
255+
}
256+
257+
@inlinable
258+
package func sorted() -> [Element] {
259+
Array(self) // The elements are already sorted
260+
}
261+
262+
@inlinable
263+
package var count: Int {
264+
storage.nonzeroBitCount
265+
}
266+
}
267+
268+
// MARK: Hashable
269+
270+
extension _FixedSizeBitSet: Hashable {}
271+
272+
// MARK: Combinations
273+
274+
extension _FixedSizeBitSet {
275+
/// Returns a list of all possible combinations of the elements in the set, in order of increasing number of elements.
276+
package func allCombinationsOfValues() -> [Self] {
277+
// Leverage the fact that bits of an Int represent the possible combinations.
278+
let smallest = storage.trailingZeroBitCount
279+
280+
var combinations: [Self] = []
281+
combinations.reserveCapacity((1 &<< count /*known to be less than Storage.bitWidth */) - 1)
282+
283+
for raw in 1 ... storage &>> smallest {
284+
let combination = Self(storage: Storage(raw &<< smallest))
285+
286+
// Filter out any combinations that include columns that are the same for all overloads
287+
guard self.isSuperset(of: combination) else { continue }
288+
289+
combinations.append(combination)
290+
}
291+
// The bits of larger and larger Int values won't be in order of number of bits set, so we sort them.
292+
return combinations.sorted(by: { $0.count < $1.count })
293+
}
294+
}

Sources/DocCCommon/Mutex.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2025 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
#if os(macOS) || os(iOS)
12+
import Darwin
13+
14+
// This type is designed to have the same API surface as 'Synchronization.Mutex'.
15+
// It's different from 'SwiftDocC.Synchronized' which requires that the wrapped value is `Copyable`.
16+
//
17+
// When we can require macOS 15.0 we can remove this custom type and use 'Synchronization.Mutex' directly on all platforms.
18+
struct Mutex<Value: ~Copyable>: ~Copyable, @unchecked Sendable {
19+
private var value: UnsafeMutablePointer<Value>
20+
private var lock: UnsafeMutablePointer<os_unfair_lock>
21+
22+
init(_ initialValue: consuming sending Value) {
23+
value = UnsafeMutablePointer<Value>.allocate(capacity: 1)
24+
value.initialize(to: initialValue)
25+
26+
lock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
27+
lock.initialize(to: os_unfair_lock())
28+
}
29+
30+
deinit {
31+
value.deallocate()
32+
lock.deallocate()
33+
}
34+
35+
borrowing func withLock<Result: ~Copyable, E: Error>(_ body: (inout sending Value) throws(E) -> sending Result) throws(E) -> sending Result {
36+
os_unfair_lock_lock(lock)
37+
defer { os_unfair_lock_unlock(lock) }
38+
39+
return try body(&value.pointee)
40+
}
41+
}
42+
#else
43+
import Synchronization
44+
45+
typealias Mutex = Synchronization.Mutex
46+
#endif

0 commit comments

Comments
 (0)