@@ -31,12 +31,16 @@ open class SyntaxVisitor {
3131 /// We can then re-use them to create new syntax nodes.
3232 ///
3333 /// The array's size should be a typical nesting depth of a Swift file. That way we can store all allocated syntax
34- /// nodes when unwinding the visitation stack. It shouldn't be much larger because that would mean that we need to
35- /// look through more memory to find a cache miss. 40 has been chosen empirically to strike a good balance here.
34+ /// nodes when unwinding the visitation stack.
3635 ///
3736 /// The actual `info` stored in the `Syntax.Info` objects is garbage. It needs to be set when any of the `Syntax.Info`
3837 /// objects get re-used.
39- private var recyclableNodeInfos : ContiguousArray < Syntax . Info ? > = ContiguousArray ( repeating: nil , count: 40 )
38+ private var recyclableNodeInfos : ContiguousArray < Syntax . Info ? > = ContiguousArray ( repeating: nil , count: 64 )
39+
40+ /// A bit is set to 1 if the corresponding index in `recyclableNodeInfos` is occupied and ready to be reused.
41+ ///
42+ /// The last bit in this UInt64 corresponds to index 0 in `recyclableNodeInfos`.
43+ private var recyclableNodeInfosUsageBitmap : UInt64 = 0
4044
4145 public init ( viewMode: SyntaxTreeViewMode ) {
4246 self . viewMode = viewMode
@@ -5291,11 +5295,12 @@ open class SyntaxVisitor {
52915295 for childRaw in NonNilRawSyntaxChildren ( syntaxNode, viewMode: viewMode) {
52925296 // syntaxNode gets retained here. That seems unnecessary but I don't know how to remove it.
52935297 var childNode : Syntax
5294- if let recycledInfoIndex = recyclableNodeInfos. firstIndex ( where: { $0 != nil
5295- } ) {
5298+ if let recycledInfoIndex = recyclableNodeInfosUsageBitmap. indexOfRightmostOne {
52965299 var recycledInfo : Syntax . Info ? = nil
52975300 // Use `swap` to extract the recyclable syntax node without incurring ref-counting.
52985301 swap ( & recycledInfo, & recyclableNodeInfos[ recycledInfoIndex] )
5302+ assert ( recycledInfo != nil , " Slot indicated by the bitmap did not contain a value " )
5303+ recyclableNodeInfosUsageBitmap. setBitToZero ( at: recycledInfoIndex)
52995304 // syntaxNode.info gets retained here. This is necessary because we build up the parent tree.
53005305 recycledInfo!. info = . nonRoot( . init( parent: syntaxNode, absoluteInfo: childRaw. info) )
53015306 childNode = Syntax ( childRaw. raw, info: recycledInfo!)
@@ -5307,12 +5312,39 @@ open class SyntaxVisitor {
53075312 // The node didn't get stored by the subclass's visit method. We can re-use the memory of its `Syntax.Info`
53085313 // for future syntax nodes.
53095314 childNode. info. info = nil
5310- if let emptySlot = recyclableNodeInfos. firstIndex ( where: { $0 == nil
5311- } ) {
5315+ if let emptySlot = recyclableNodeInfosUsageBitmap. indexOfRightmostZero {
53125316 // Use `swap` to store the recyclable syntax node without incurring ref-counting.
53135317 swap ( & recyclableNodeInfos[ emptySlot] , & childNode. info)
5318+ assert ( childNode. info == nil , " Slot should not have contained a value " )
5319+ recyclableNodeInfosUsageBitmap. setBitToOne ( at: emptySlot)
53145320 }
53155321 }
53165322 }
53175323 }
53185324}
5325+
5326+ fileprivate extension UInt64 {
5327+ var indexOfRightmostZero : Int ? {
5328+ return ( ~ self ) . indexOfRightmostOne
5329+ }
5330+
5331+
5332+ var indexOfRightmostOne : Int ? {
5333+ let trailingZeroCount = self . trailingZeroBitCount
5334+ if trailingZeroCount == Self . bitWidth {
5335+ // All indicies are 0
5336+ return nil
5337+ }
5338+ return trailingZeroCount
5339+ }
5340+
5341+
5342+ mutating func setBitToZero( at index: Int ) {
5343+ self &= ~ ( 1 << index)
5344+ }
5345+
5346+
5347+ mutating func setBitToOne( at index: Int ) {
5348+ self |= 1 << index
5349+ }
5350+ }
0 commit comments