Skip to content

Commit 9268852

Browse files
authored
Merge pull request #2893 from rintaro/perf-syntaxbytes
[Perf] Optimize SourceLocationConverter
2 parents 3b939e0 + e6f3d73 commit 9268852

File tree

5 files changed

+70
-54
lines changed

5 files changed

+70
-54
lines changed

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -361,16 +361,23 @@ extension RawSyntax {
361361
}
362362

363363
extension RawTriviaPiece {
364-
func withSyntaxText(body: (SyntaxText) throws -> Void) rethrows {
364+
/// Call `body` with the syntax text of this trivia piece.
365+
///
366+
/// If `isEphemeral` is `true`, the ``SyntaxText`` argument is only guaranteed
367+
/// to be valid within the call.
368+
func withSyntaxText(body: (SyntaxText, _ isEphemeral: Bool) throws -> Void) rethrows {
365369
if let syntaxText = storedText {
366-
try body(syntaxText)
370+
try body(syntaxText, /*isEphemeral*/ false)
367371
return
368372
}
369373

370374
var description = ""
371375
write(to: &description)
372376
try description.withUTF8 { buffer in
373-
try body(SyntaxText(baseAddress: buffer.baseAddress, count: buffer.count))
377+
try body(
378+
SyntaxText(baseAddress: buffer.baseAddress, count: buffer.count),
379+
/*isEphemeral*/ true
380+
)
374381
}
375382
}
376383
}
@@ -382,21 +389,21 @@ extension RawSyntax {
382389
/// Unlike `description`, this provides a source-accurate representation
383390
/// even in the presence of malformed UTF-8 in the input source.
384391
///
385-
/// The ``SyntaxText`` arguments passed to the visitor are only guaranteed
386-
/// to be valid within that call. It is unsafe to escape the `SyntaxValue`
387-
/// values outside of the closure.
388-
public func withEachSyntaxText(body: (SyntaxText) throws -> Void) rethrows {
392+
/// If `isEphemeral` is `true`, the ``SyntaxText`` arguments passed to the
393+
/// visitor are only guaranteed to be valid within that call. Otherwise, they
394+
/// are valid as long as the raw syntax is alive.
395+
public func withEachSyntaxText(body: (SyntaxText, _ isEphemeral: Bool) throws -> Void) rethrows {
389396
switch rawData.payload {
390397
case .parsedToken(let dat):
391398
if dat.presence == .present {
392-
try body(dat.wholeText)
399+
try body(dat.wholeText, /*isEphemeral*/ false)
393400
}
394401
case .materializedToken(let dat):
395402
if dat.presence == .present {
396403
for p in dat.leadingTrivia {
397404
try p.withSyntaxText(body: body)
398405
}
399-
try body(dat.tokenText)
406+
try body(dat.tokenText, /*isEphemeral*/ false)
400407
for p in dat.trailingTrivia {
401408
try p.withSyntaxText(body: body)
402409
}
@@ -412,9 +419,20 @@ extension RawSyntax {
412419
/// source even in the presence of invalid UTF-8.
413420
public var syntaxTextBytes: [UInt8] {
414421
var result: [UInt8] = []
415-
withEachSyntaxText { syntaxText in
416-
result.append(contentsOf: syntaxText)
422+
var buf: SyntaxText = ""
423+
withEachSyntaxText { syntaxText, isEphemeral in
424+
if isEphemeral {
425+
result.append(contentsOf: buf)
426+
result.append(contentsOf: syntaxText)
427+
buf = ""
428+
} else if let base = buf.baseAddress, base + buf.count == syntaxText.baseAddress {
429+
buf = SyntaxText(baseAddress: base, count: buf.count + syntaxText.count)
430+
} else {
431+
result.append(contentsOf: buf)
432+
buf = syntaxText
433+
}
417434
}
435+
result.append(contentsOf: buf)
418436
return result
419437
}
420438
}

Sources/SwiftSyntax/SourceLocation.swift

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -522,38 +522,33 @@ fileprivate extension SyntaxText {
522522
prefix: SourceLength = .zero,
523523
body: (SourceLength) -> ()
524524
) -> SourceLength {
525-
// let startIndex = utf8.startIndex
526-
// let endIndex = utf8.endIndex
527525
var curIdx = startIndex
528-
var lineLength = prefix
529-
let advanceLengthByOne = { () -> () in
530-
lineLength += SourceLength(utf8Length: 1)
531-
curIdx = self.index(after: curIdx)
532-
}
526+
let endIdx = endIndex
527+
var lineStart = curIdx - prefix.utf8Length
533528

534-
while curIdx < endIndex {
535-
let char = self[curIdx]
536-
advanceLengthByOne()
529+
while curIdx != endIdx {
530+
let chr = self[curIdx]
531+
curIdx &+= 1
537532

538533
/// From https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#grammar_line-break
539534
/// * line-break → U+000A
540535
/// * line-break → U+000D
541536
/// * line-break → U+000D followed by U+000A
542-
let isNewline = { () -> Bool in
543-
if char == 10 { return true }
544-
if char == 13 {
545-
if curIdx < endIndex && self[curIdx] == 10 { advanceLengthByOne() }
546-
return true
537+
switch chr {
538+
case UInt8(ascii: "\n"):
539+
break
540+
case UInt8(ascii: "\r"):
541+
if curIdx != endIdx && self[curIdx] == UInt8(ascii: "\n") {
542+
curIdx &+= 1
547543
}
548-
return false
549-
}
550-
551-
if isNewline() {
552-
body(lineLength)
553-
lineLength = .zero
544+
break
545+
default:
546+
continue
554547
}
548+
body(SourceLength(utf8Length: curIdx - lineStart))
549+
lineStart = curIdx
555550
}
556-
return lineLength
551+
return SourceLength(utf8Length: curIdx - lineStart)
557552
}
558553

559554
func containsSwiftNewline() -> Bool {

Sources/SwiftSyntax/Syntax.swift

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -365,20 +365,6 @@ extension Syntax: Identifiable {
365365
}
366366

367367
extension Syntax {
368-
/// Enumerate all of the syntax text present in this node, and all
369-
/// of its children, to give a source-accurate view of the bytes.
370-
///
371-
/// Unlike `description`, this provides a source-accurate representation
372-
/// even in the presence of malformed UTF-8 in the input source.
373-
///
374-
/// The ``SyntaxText`` arguments passed to the visitor are only guaranteed
375-
/// to be valid within that call. It is unsafe to escape the `SyntaxValue`
376-
/// values outside of the closure.
377-
@_spi(RawSyntax)
378-
public func withEachSyntaxText(body: (SyntaxText) throws -> Void) rethrows {
379-
try raw.withEachSyntaxText(body: body)
380-
}
381-
382368
/// Retrieve the syntax text as an array of bytes that models the input
383369
/// source even in the presence of invalid UTF-8.
384370
public var syntaxTextBytes: [UInt8] {

Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
/// reference its contents, we know that the pointer's contents won't get
1818
/// deallocated while being accessed and thus we can add an unchecked `Sendable`
1919
/// conformance.
20-
@_spi(RawSyntax) public struct SyntaxArenaAllocatedPointer<Element: Sendable>: @unchecked Sendable {
20+
@_spi(RawSyntax)
21+
public struct SyntaxArenaAllocatedPointer<Element: Sendable>: @unchecked Sendable {
2122
private let pointer: UnsafePointer<Element>
2223

2324
/// Create a pointer from an `UnsafePointer` that was allocated inside a
@@ -61,22 +62,21 @@ public struct SyntaxArenaAllocatedBufferPointer<Element: Sendable>: RandomAccess
6162
/// - Important: The client needs to ensure sure that the buffer is indeed
6263
/// allocated by a ``SyntaxArena`` and that the ``SyntaxArena`` will outlive
6364
/// any users of this ``SyntaxArenaAllocatedBufferPointer``.
64-
@_spi(RawSyntax) public init(_ buffer: UnsafeBufferPointer<Element>) {
65+
public init(_ buffer: UnsafeBufferPointer<Element>) {
6566
self.buffer = buffer
6667
}
6768

68-
@_spi(RawSyntax)
6969
public subscript<RangeType: RangeExpression<Int>>(
7070
range: RangeType
7171
) -> SyntaxArenaAllocatedBufferPointer<Element> {
7272
return SyntaxArenaAllocatedBufferPointer(UnsafeBufferPointer(rebasing: self.buffer[range]))
7373
}
7474

75-
@_spi(RawSyntax) public subscript(_ index: Int) -> Element {
75+
public subscript(_ index: Int) -> Element {
7676
return self.buffer[index]
7777
}
7878

79-
@_spi(RawSyntax) public func makeIterator() -> UnsafeBufferPointer<Element>.Iterator {
79+
public func makeIterator() -> UnsafeBufferPointer<Element>.Iterator {
8080
return buffer.makeIterator()
8181
}
8282

@@ -111,4 +111,8 @@ public struct SyntaxArenaAllocatedBufferPointer<Element: Sendable>: RandomAccess
111111
public func withContiguousStorageIfAvailable<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R? {
112112
try body(buffer)
113113
}
114+
115+
public func _copyContents(initializing ptr: UnsafeMutableBufferPointer<Element>) -> (Iterator, Int) {
116+
buffer._copyContents(initializing: ptr)
117+
}
114118
}

Sources/SwiftSyntax/SyntaxText.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@ import Musl
4747
/// replacement character (`\u{FFFD}`).
4848
@_spi(RawSyntax)
4949
public struct SyntaxText: Sendable {
50-
var buffer: SyntaxArenaAllocatedBufferPointer<UInt8>
50+
public typealias Buffer = SyntaxArenaAllocatedBufferPointer<UInt8>
51+
var buffer: Buffer
5152

5253
/// Construct a ``SyntaxText`` whose text is represented by the given `buffer`.
53-
public init(buffer: SyntaxArenaAllocatedBufferPointer<UInt8>) {
54+
public init(buffer: Buffer) {
5455
self.buffer = buffer
5556
}
5657

@@ -180,6 +181,18 @@ extension SyntaxText: RandomAccessCollection {
180181
public subscript(index: Index) -> Element {
181182
get { return buffer[index] }
182183
}
184+
185+
public func makeIterator() -> Buffer.Iterator {
186+
buffer.makeIterator()
187+
}
188+
189+
public func withContiguousStorageIfAvailable<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R? {
190+
try buffer.withContiguousStorageIfAvailable(body)
191+
}
192+
193+
public func _copyContents(initializing ptr: UnsafeMutableBufferPointer<Element>) -> (Iterator, Int) {
194+
buffer._copyContents(initializing: ptr)
195+
}
183196
}
184197

185198
extension SyntaxText: Hashable {

0 commit comments

Comments
 (0)