|
| 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 | +} |
0 commit comments