Skip to content

Commit f9b6c7e

Browse files
committed
[SyntaxData] Alternative allocation strategy
* Lazily allocate children buffer * Tail allocate a storage for child buffer base address.
1 parent 12dea90 commit f9b6c7e

File tree

1 file changed

+37
-41
lines changed

1 file changed

+37
-41
lines changed

Sources/SwiftSyntax/Syntax.swift

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -389,45 +389,36 @@ final class SyntaxDataArena: @unchecked Sendable {
389389
return SyntaxDataReferenceBuffer()
390390
}
391391

392-
// The storage to the pointer to the buffer is allocated next to the SyntaxData.
393-
let baseAddress = parent.advanced(by: 1)
392+
// The storage of the buffer address is allocated next to the SyntaxData.
393+
let baseAddressRef = parent.advanced(by: 1)
394394
.unsafeRawPointer
395-
.assumingMemoryBound(to: SyntaxDataReference?.self)
396-
let buffer = UnsafeBufferPointer(start: baseAddress, count: childCount)
397-
398-
// The _last_ element is initially filled with `~0` indicating not populated.
399-
@inline(__always) func isPopulated() -> Bool {
400-
baseAddress
401-
.advanced(by: childCount - 1)
402-
.withMemoryRebound(to: UInt.self, capacity: 1) { pointer in
403-
pointer.pointee != ~0
404-
}
405-
}
395+
.assumingMemoryBound(to: UnsafePointer<SyntaxDataReference?>?.self)
406396

407397
// If the buffer is already populated, return it.
408-
if isPopulated() {
409-
return SyntaxDataReferenceBuffer(buffer)
398+
if let baseAddress = baseAddressRef.pointee {
399+
return SyntaxDataReferenceBuffer(UnsafeBufferPointer(start: baseAddress, count: childCount))
410400
}
411401

412402
mutex.lock()
413403
defer { mutex.unlock() }
414404

415-
// Recheck before populating, maybe some other thread has populated the buffer
416-
// during acquiring the lock.
417-
if !isPopulated() {
418-
populateDataLayoutImpl(parent)
405+
// Recheck, maybe some other thread has populated the buffer during acquiring the lock.
406+
if let baseAddress = baseAddressRef.pointee {
407+
return SyntaxDataReferenceBuffer(UnsafeBufferPointer(start: baseAddress, count: childCount))
419408
}
420409

410+
let buffer = createLayoutDataImpl(parent)
411+
// Remeber the base address of the created buffer.
412+
UnsafeMutablePointer(mutating: baseAddressRef).pointee = buffer.baseAddress
413+
421414
return SyntaxDataReferenceBuffer(buffer)
422415
}
423416

424-
/// Fill the layout buffer of the node.
425-
private func populateDataLayoutImpl(_ parent: SyntaxDataReference) {
426-
let baseAddress = parent.advanced(by: 1)
427-
.unsafeRawPointer
428-
.assumingMemoryBound(to: SyntaxDataReference?.self)
417+
/// Create the layout buffer of the node.
418+
private func createLayoutDataImpl(_ parent: SyntaxDataReference) -> UnsafeBufferPointer<SyntaxDataReference?> {
419+
let allocated = self.allocator.allocate(SyntaxDataReference?.self, count: Int(truncatingIfNeeded: parent.pointee.childCount))
429420

430-
var ptr = UnsafeMutablePointer(mutating: baseAddress)
421+
var ptr = allocated.baseAddress!
431422
var absoluteInfo = parent.pointee.absoluteInfo.advancedToFirstChild()
432423
for raw in parent.pointee.raw.layoutView!.children {
433424
let dataRef = raw.map {
@@ -437,25 +428,29 @@ final class SyntaxDataArena: @unchecked Sendable {
437428
absoluteInfo = absoluteInfo.advancedBySibling(raw)
438429
ptr += 1
439430
}
431+
return UnsafeBufferPointer(allocated)
440432
}
441433

442434
/// Calculate the recommended slab size of `BumpPtrAllocator`.
443435
///
444-
/// Estimate the total allocation size assuming the client visits every nodes.
445-
/// Return the estimated size, or 4096 if it's larger than 4096.
436+
/// Estimate the total allocation size assuming the client visits every node in
437+
/// the tree. Return the estimated size, or 4096 if it's larger than 4096.
446438
///
447-
/// Each node consumes `SyntaxData` size at least. In addition to that, each syntax collection
448-
/// element consumes `SyntaxDataReference` in the parent's layout. For non-collection layout
449-
/// nodes, the layout is usually sparse, so we can't calculate the exact memory consumption
450-
/// until we see the syntax kind. But 4 slots per each node looks like an enough estimation.
439+
/// Each node consumes `SyntaxData` size at least. Non-empty layout node tail
440+
/// allocates a pointer storage for the base address of the layout buffer.
441+
///
442+
/// For layout buffers, each child element consumes a `SyntaxDataReference` in
443+
/// the parent's layout. But non-collection layout nodes, the layout is usually
444+
/// sparse, so we can't calculate the exact memory size until we see the RawSyntax.
445+
/// That being said, `SytnaxData` + 4 pointer size looks like an enough estimation.
451446
private static func slabSize(for raw: RawSyntax) -> Int {
452447
let dataSize = MemoryLayout<SyntaxData>.stride
453-
let slotSize = MemoryLayout<SyntaxDataReference?>.stride
448+
let pointerSize = MemoryLayout<UnsafeRawPointer>.stride
454449

455450
let nodeCount = raw.totalNodes
456451
var totalSize = dataSize
457-
if nodeCount > 1 {
458-
totalSize += (dataSize + slotSize * 4) * (nodeCount &- 1)
452+
if nodeCount != 0 {
453+
totalSize += (dataSize + pointerSize * 4) * (nodeCount &- 1)
459454
}
460455
// Power of 2 might look nicer, but 'BumpPtrAllocator' doesn't require that.
461456
return min(totalSize, 4096)
@@ -472,7 +467,11 @@ final class SyntaxDataArena: @unchecked Sendable {
472467

473468
// Allocate 'SyntaxData' + buffer for child data.
474469
// NOTE: If you change the memory layout, revisit 'slabSize(for:)' too.
475-
let totalSize = MemoryLayout<SyntaxData>.stride &+ MemoryLayout<SyntaxDataReference?>.stride * childCount
470+
var totalSize = MemoryLayout<SyntaxData>.stride
471+
if childCount != 0 {
472+
// Tail allocate the storage for the pointer to the lazily allocated layout data.
473+
totalSize &+= MemoryLayout<UnsafePointer<SyntaxDataReference?>?>.size
474+
}
476475
let alignment = MemoryLayout<SyntaxData>.alignment
477476
let allocated = allocator.allocate(byteCount: totalSize, alignment: alignment).baseAddress!
478477

@@ -488,14 +487,11 @@ final class SyntaxDataArena: @unchecked Sendable {
488487
)
489488

490489
if childCount != 0 {
491-
// Fill the _last_ element with '~0' to indicate it's not populated.
490+
// Initlaize the tail allocated storage with nil.
492491
allocated
493492
.advanced(by: MemoryLayout<SyntaxData>.stride)
494-
.bindMemory(to: SyntaxDataReference?.self, capacity: childCount)
495-
.advanced(by: childCount - 1)
496-
.withMemoryRebound(to: UInt.self, capacity: 1) {
497-
$0.initialize(to: ~0)
498-
}
493+
.bindMemory(to: UnsafePointer<SyntaxDataReference?>?.self, capacity: 1)
494+
.initialize(to: nil)
499495
}
500496

501497
return SyntaxDataReference(UnsafePointer(dataRef))

0 commit comments

Comments
 (0)