File: UnsafeStackAllocator.swift

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (245 lines) | stat: -rw-r--r-- 8,215 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation

private struct Bytes8 { var storage: (UInt64) = (0) }
private struct Bytes16 { var storage: (Bytes8, Bytes8) = (.init(), .init()) }
private struct Bytes32 { var storage: (Bytes16, Bytes16) = (.init(), .init()) }
private struct Bytes64 { var storage: (Bytes32, Bytes32) = (.init(), .init()) }
private struct Bytes128 { var storage: (Bytes64, Bytes64) = (.init(), .init()) }
private struct Bytes256 { var storage: (Bytes128, Bytes128) = (.init(), .init()) }
private struct Bytes512 { var storage: (Bytes256, Bytes256) = (.init(), .init()) }
private struct Bytes1024 { var storage: (Bytes512, Bytes512) = (.init(), .init()) }
private struct Bytes2048 { var storage: (Bytes1024, Bytes1024) = (.init(), .init()) }
private struct Bytes4096 { var storage: (Bytes2048, Bytes2048) = (.init(), .init()) }
private struct Bytes8192 { var storage: (Bytes4096, Bytes4096) = (.init(), .init()) }

package struct UnsafeStackAllocator {
  private typealias Storage = Bytes8192
  private var storage = Storage()
  private static let pageSize = 64
  private static let storageCapacity = MemoryLayout<Storage>.size
  private var pagesAllocated = 0
  private var pagesAvailable: Int {
    pagesCapacity - pagesAllocated
  }

  private init() {
  }

  package static func withUnsafeStackAllocator<R>(body: (inout Self) throws -> R) rethrows -> R {
    var allocator = Self()
    defer { assert(allocator.pagesAllocated == 0) }
    return try body(&allocator)
  }

  private let pagesCapacity = Self.storageCapacity / Self.pageSize

  private func pages<Element>(for type: Element.Type, maximumCapacity: Int) -> Int {
    let bytesNeeded = MemoryLayout<Element>.stride * maximumCapacity
    return ((bytesNeeded - 1) / Self.pageSize) + 1
  }

  private mutating func allocate<Element>(
    of type: Element.Type,
    maximumCapacity: Int
  ) -> UnsafeMutablePointer<Element> {
    // Avoid dealing with alignment for now.
    assert(MemoryLayout<Element>.alignment <= MemoryLayout<Storage>.alignment)
    // Avoid dealing with alignment for now.
    assert(MemoryLayout<Element>.alignment <= Self.pageSize)
    let pagesNeeded = pages(for: type, maximumCapacity: maximumCapacity)
    if pagesNeeded < pagesAvailable {
      return withUnsafeMutableBytes(of: &storage) { arena in
        let start = arena.baseAddress!.advanced(by: pagesAllocated * Self.pageSize).bindMemory(
          to: Element.self,
          capacity: maximumCapacity
        )
        pagesAllocated += pagesNeeded
        return start
      }
    } else {
      return UnsafeMutablePointer<Element>.allocate(capacity: maximumCapacity)
    }
  }

  mutating func allocateBuffer<Element>(of type: Element.Type, count: Int) -> UnsafeMutableBufferPointer<Element> {
    return UnsafeMutableBufferPointer(start: allocate(of: type, maximumCapacity: count), count: count)
  }

  mutating func allocateUnsafeArray<Element>(
    of type: Element.Type,
    maximumCapacity: Int
  ) -> UnsafeStackArray<Element> {
    UnsafeStackArray(base: allocate(of: type, maximumCapacity: maximumCapacity), capacity: maximumCapacity)
  }

  mutating private func deallocate<Element>(_ base: UnsafePointer<Element>, capacity: Int) {
    let arrayStart = UnsafeRawPointer(base)
    let arrayPages = pages(for: Element.self, maximumCapacity: capacity)
    withUnsafeBytes(of: &storage) { arena in
      let arenaStart = UnsafeRawPointer(arena.baseAddress)!
      let arenaEnd = arenaStart.advanced(by: Self.storageCapacity)
      if (arrayStart >= arenaStart) && (arrayStart < arenaEnd) {
        let projectedArrayStart = arenaStart.advanced(by: (pagesAllocated - arrayPages) * Self.pageSize)
        assert(projectedArrayStart == arrayStart, "deallocate(...) must be called in FIFO order.")
        pagesAllocated -= arrayPages
      } else {
        arrayStart.deallocate()
      }
    }
  }

  /// - Note: `buffer.count` must the be the same as from the original allocation.
  /// - Note: deiniting buffer contents is caller's responsibility.
  mutating func deallocate<Element>(_ buffer: inout UnsafeBufferPointer<Element>) {
    if let baseAddress = buffer.baseAddress {
      deallocate(baseAddress, capacity: buffer.count)
      buffer = UnsafeBufferPointer(start: nil, count: 0)
    }
  }

  /// - Note: `buffer.count` must the be the same as from the original allocation.
  /// - Note: deiniting buffer contents is caller's responsibility.
  mutating func deallocate<Element>(_ buffer: inout UnsafeMutableBufferPointer<Element>) {
    if let baseAddress = buffer.baseAddress {
      deallocate(baseAddress, capacity: buffer.count)
      buffer = UnsafeMutableBufferPointer(start: nil, count: 0)
    }
  }

  mutating func deallocate<Element>(_ array: inout UnsafeStackArray<Element>) {
    array.prepareToDeallocate()
    deallocate(array.base, capacity: array.capacity)
    array = UnsafeStackArray(base: array.base, capacity: 0)
  }

  package mutating func withStackArray<Element, R>(
    of elementType: Element.Type,
    maximumCapacity: Int,
    body: (inout UnsafeStackArray<Element>) throws -> R
  ) rethrows -> R {
    var stackArray = allocateUnsafeArray(of: elementType, maximumCapacity: maximumCapacity)
    defer { deallocate(&stackArray) }
    return try body(&stackArray)
  }
}

package struct UnsafeStackArray<Element> {
  private(set) var base: UnsafeMutablePointer<Element>
  fileprivate let capacity: Int
  package private(set) var count = 0

  fileprivate init(base: UnsafeMutablePointer<Element>, capacity: Int) {
    self.base = base
    self.capacity = capacity
  }

  fileprivate mutating func prepareToDeallocate() {
    removeAll()  // Contained elements may need de-init
  }

  // Assume the memory is initialized with whatever is there. Only safe on trivial types.
  mutating func initializeWithContainedGarbage() {
    count = capacity
  }

  package mutating func fill(with element: Element) {
    while count < capacity {
      append(element)
    }
  }

  package mutating func removeAll() {
    base.deinitialize(count: count)
    count = 0
  }

  mutating func append(contentsOf sequence: some Sequence<Element>) {
    for element in sequence {
      append(element)
    }
  }

  package mutating func append(_ element: Element) {
    assert(count < capacity)
    (base + count).initialize(to: element)
    count += 1
  }

  mutating func push(_ element: Element) {
    append(element)
  }

  package mutating func removeLast() {
    assert(count > 0)
    (base + count - 1).deinitialize(count: 1)
    count -= 1
  }

  package subscript(_ index: Int) -> Element {
    get {
      assert(index < count)
      return (base + index).pointee
    }
    set {
      assert(index < count)
      (base + index).pointee = newValue
    }
  }

  package mutating func truncate(to countLimit: Int) {
    assert(countLimit >= 0)
    if count > countLimit {
      (base + countLimit).deinitialize(count: count - countLimit)
      count = countLimit
    }
  }

  mutating func truncateLeavingGarbage(to countLimit: Int) {
    assert(countLimit >= 0)
    count = countLimit
  }

  var contiguousStorage: UnsafeBufferPointer<Element> {
    UnsafeBufferPointer(start: base, count: count)
  }

  func contiguousStorage(count viewCount: Int) -> UnsafeBufferPointer<Element> {
    assert(viewCount <= count)
    return UnsafeBufferPointer(start: base, count: viewCount)
  }
}

extension UnsafeStackArray: RandomAccessCollection {
  package var startIndex: Int {
    0
  }

  package var endIndex: Int {
    count
  }
}

extension UnsafeStackArray: MutableCollection {
}

extension UnsafeStackArray {
  package mutating func popLast() -> Element? {
    let last = last
    if hasContent {
      removeLast()
    }
    return last
  }
}