@@ -389,45 +389,36 @@ final class SyntaxDataArena: @unchecked Sendable {
389
389
return SyntaxDataReferenceBuffer ( )
390
390
}
391
391
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 )
394
394
. 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 )
406
396
407
397
// 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 ) )
410
400
}
411
401
412
402
mutex. lock ( )
413
403
defer { mutex. unlock ( ) }
414
404
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) )
419
408
}
420
409
410
+ let buffer = createLayoutDataImpl ( parent)
411
+ // Remeber the base address of the created buffer.
412
+ UnsafeMutablePointer ( mutating: baseAddressRef) . pointee = buffer. baseAddress
413
+
421
414
return SyntaxDataReferenceBuffer ( buffer)
422
415
}
423
416
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) )
429
420
430
- var ptr = UnsafeMutablePointer ( mutating : baseAddress)
421
+ var ptr = allocated . baseAddress!
431
422
var absoluteInfo = parent. pointee. absoluteInfo. advancedToFirstChild ( )
432
423
for raw in parent. pointee. raw. layoutView!. children {
433
424
let dataRef = raw. map {
@@ -437,25 +428,29 @@ final class SyntaxDataArena: @unchecked Sendable {
437
428
absoluteInfo = absoluteInfo. advancedBySibling ( raw)
438
429
ptr += 1
439
430
}
431
+ return UnsafeBufferPointer ( allocated)
440
432
}
441
433
442
434
/// Calculate the recommended slab size of `BumpPtrAllocator`.
443
435
///
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.
446
438
///
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.
451
446
private static func slabSize( for raw: RawSyntax ) -> Int {
452
447
let dataSize = MemoryLayout< SyntaxData> . stride
453
- let slotSize = MemoryLayout< SyntaxDataReference ? >. stride
448
+ let pointerSize = MemoryLayout< UnsafeRawPointer > . stride
454
449
455
450
let nodeCount = raw. totalNodes
456
451
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 )
459
454
}
460
455
// Power of 2 might look nicer, but 'BumpPtrAllocator' doesn't require that.
461
456
return min ( totalSize, 4096 )
@@ -472,7 +467,11 @@ final class SyntaxDataArena: @unchecked Sendable {
472
467
473
468
// Allocate 'SyntaxData' + buffer for child data.
474
469
// 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
+ }
476
475
let alignment = MemoryLayout< SyntaxData> . alignment
477
476
let allocated = allocator. allocate ( byteCount: totalSize, alignment: alignment) . baseAddress!
478
477
@@ -488,14 +487,11 @@ final class SyntaxDataArena: @unchecked Sendable {
488
487
)
489
488
490
489
if childCount != 0 {
491
- // Fill the _last_ element with '~0' to indicate it's not populated .
490
+ // Initlaize the tail allocated storage with nil .
492
491
allocated
493
492
. 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 )
499
495
}
500
496
501
497
return SyntaxDataReference ( UnsafePointer ( dataRef) )
0 commit comments