Skip to content

Commit 0f4d91e

Browse files
authored
Add 'BumpPtrAllocator'
For future usage. The implementation is basically Swift copy of 'llvm::BumpPtrAllocator', but without customizable slab size etc.
1 parent c9042c0 commit 0f4d91e

File tree

3 files changed

+187
-0
lines changed

3 files changed

+187
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//===---------- BumpPtrAllocator.swift - Bump-pointer Allocaiton ----------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Bump-pointer allocation.
14+
@_spi(Testing)
15+
public class BumpPtrAllocator {
16+
typealias Slab = UnsafeMutableRawBufferPointer
17+
18+
static private var SLAB_SIZE: Int = 4096
19+
static private var GLOWTH_DELAY: Int = 128
20+
static private var SLAB_ALIGNMENT: Int = 8
21+
22+
private var slabs: [Slab]
23+
/// Points to the next unused address in `slabs.last`.
24+
private var currentPtr: UnsafeMutableRawPointer?
25+
/// Points to the end address of `slabs.last`. If `slabs` is not empty, this
26+
/// is equivalent to `slabs.last!.baseAddress! + slabs.last!.count`
27+
private var endPtr: UnsafeMutableRawPointer?
28+
private var customSizeSlabs: [Slab]
29+
private var _totalBytesAllocated: Int
30+
31+
public init() {
32+
slabs = []
33+
currentPtr = nil
34+
endPtr = nil
35+
customSizeSlabs = []
36+
_totalBytesAllocated = 0
37+
}
38+
39+
deinit {
40+
/// Deallocate all memory.
41+
_totalBytesAllocated = 0
42+
currentPtr = nil
43+
endPtr = nil
44+
while let slab = slabs.popLast() {
45+
slab.deallocate()
46+
}
47+
while let slab = customSizeSlabs.popLast() {
48+
slab.deallocate()
49+
}
50+
}
51+
52+
/// Calculate the size of the slab at the index.
53+
private static func slabSize(at index: Int) -> Int {
54+
// Double the slab size every 'GLOWTH_DELAY' slabs.
55+
return SLAB_SIZE * (1 << min(30, index / GLOWTH_DELAY))
56+
}
57+
58+
private func startNewSlab() {
59+
let newSlabSize = Self.slabSize(at: slabs.count)
60+
let newSlab = Slab.allocate(
61+
byteCount: newSlabSize, alignment: Self.SLAB_ALIGNMENT)
62+
slabs.append(newSlab)
63+
currentPtr = newSlab.baseAddress!
64+
endPtr = currentPtr!.advanced(by: newSlabSize)
65+
}
66+
67+
/// Allocate 'byteCount' of memory.
68+
///
69+
/// The returned buffer is not bound to any types, nor initialized.
70+
/// Clients should never call `deallocate()` on the returned buffer.
71+
public func allocate(byteCount: Int, alignment: Int) -> UnsafeMutableRawBufferPointer {
72+
73+
precondition(alignment <= Self.SLAB_ALIGNMENT)
74+
guard byteCount > 0 else {
75+
return .init(start: nil, count: 0)
76+
}
77+
78+
// Track the total size we allocated.
79+
_totalBytesAllocated += byteCount
80+
81+
// Check if the current slab has enough space.
82+
if !slabs.isEmpty {
83+
let aligned = currentPtr!.alignedUp(toMultipleOf: alignment)
84+
if aligned + byteCount <= endPtr! {
85+
currentPtr = aligned + byteCount
86+
return .init(start: aligned, count: byteCount)
87+
}
88+
}
89+
90+
// If the size is too big, allocate a dedicated slab for it.
91+
if byteCount >= Self.SLAB_SIZE {
92+
let customSlab = Slab.allocate(
93+
byteCount: byteCount, alignment: alignment)
94+
customSizeSlabs.append(customSlab)
95+
return customSlab
96+
}
97+
98+
// Otherwise, start a new slab and try again.
99+
startNewSlab()
100+
let aligned = currentPtr!.alignedUp(toMultipleOf: alignment)
101+
assert(aligned + byteCount <= endPtr!, "Unable to allocate memory!")
102+
currentPtr = aligned + byteCount
103+
return .init(start: aligned, count: byteCount)
104+
}
105+
106+
/// Allocate a chunk of bound memory of `MemoryLayout<T>.stride * count'.
107+
///
108+
/// The returned buffer is bound to the type, but not initialized.
109+
/// Clients should never call `deallocate()` on the returned buffer.
110+
/// In general, using `BumpPtrAllocator` for placing non-trivial values (e.g.
111+
/// class instances, existentials, etc.) is strongly discouraged because they
112+
/// are not automatically deinitialized.
113+
public func allocate<T>(_: T.Type, count: Int) -> UnsafeMutableBufferPointer<T> {
114+
let allocated = allocate(byteCount: MemoryLayout<T>.stride * count,
115+
alignment: MemoryLayout<T>.alignment)
116+
return allocated.bindMemory(to: T.self)
117+
}
118+
119+
/// Check if the address is managed by this allocator.
120+
public func contains(address: UnsafeRawPointer) -> Bool {
121+
func test(_ slab: Slab) -> Bool {
122+
slab.baseAddress! <= address && address < slab.baseAddress! + slab.count
123+
}
124+
return slabs.contains(where: test(_:)) || customSizeSlabs.contains(where: test(_:))
125+
}
126+
127+
/// Estimated total memory size this allocator itself is consuming.
128+
///
129+
/// This is always bigger than or equal to `totalByteSizeAllocated`.
130+
public var totalMemorySize: Int {
131+
var size = 0
132+
/// Slab sizes.
133+
size = slabs.reduce(size, { $0 + $1.count })
134+
size = customSizeSlabs.reduce(size, { $0 + $1.count })
135+
/// And the size of slab storage.
136+
size += MemoryLayout<Slab>.stride * (slabs.capacity + customSizeSlabs.capacity)
137+
return size
138+
}
139+
140+
/// Total number of bytes allocated to the clients.
141+
///
142+
/// This allocator is wasting `totalMemorySize - totalByteSizeAllocated` bytes
143+
/// of memory.
144+
public var totalByteSizeAllocated: Int {
145+
_totalBytesAllocated
146+
}
147+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import XCTest
2+
@_spi(Testing) import SwiftSyntax
3+
4+
final class BumpPtrAllocatorTests: XCTestCase {
5+
6+
func testBasic() throws {
7+
let allocator = BumpPtrAllocator()
8+
9+
let byteBuffer = allocator.allocate(byteCount: 42, alignment: 4)
10+
XCTAssertNotNil(byteBuffer.baseAddress)
11+
XCTAssertEqual(byteBuffer.count, 42)
12+
XCTAssertEqual(byteBuffer.baseAddress?.alignedUp(toMultipleOf: 4),
13+
byteBuffer.baseAddress)
14+
15+
let emptyBuffer = allocator.allocate(byteCount: 0, alignment: 8)
16+
XCTAssertNil(emptyBuffer.baseAddress)
17+
XCTAssertEqual(emptyBuffer.count, 0)
18+
19+
let typedBuffer = allocator.allocate(UInt64.self, count: 12)
20+
XCTAssertNotNil(typedBuffer.baseAddress)
21+
XCTAssertEqual(typedBuffer.count, 12)
22+
XCTAssertEqual(UnsafeRawBufferPointer(typedBuffer).count,
23+
MemoryLayout<UInt64>.stride * 12)
24+
XCTAssertEqual(byteBuffer.baseAddress?.alignedUp(toMultipleOf: MemoryLayout<UInt64>.alignment),
25+
byteBuffer.baseAddress)
26+
27+
let typedEmptyBuffer = allocator.allocate(String.self, count: 0)
28+
XCTAssertNil(typedEmptyBuffer.baseAddress)
29+
XCTAssertEqual(typedEmptyBuffer.count, 0)
30+
31+
XCTAssertTrue(allocator.contains(address: typedBuffer.baseAddress!))
32+
XCTAssertTrue(allocator.contains(address: typedBuffer.baseAddress!.advanced(by: typedBuffer.count)))
33+
34+
XCTAssertEqual(allocator.totalByteSizeAllocated,
35+
42 + MemoryLayout<UInt64>.stride * 12)
36+
XCTAssert(allocator.totalMemorySize >= allocator.totalByteSizeAllocated)
37+
}
38+
}
39+

utils/group.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@
4848
"CNodes.swift",
4949
"AtomicCounter.swift",
5050
"SyntaxVerifier.swift",
51+
"BumpPtrAllocator.swift",
5152
]
5253
}

0 commit comments

Comments
 (0)