Skip to content

Commit e07287b

Browse files
authored
Merge pull request #1063 from rintaro/arena-for-parsing
2 parents 8219bbc + b087dd3 commit e07287b

File tree

7 files changed

+81
-61
lines changed

7 files changed

+81
-61
lines changed

Sources/SwiftParser/Parser.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ extension Parser {
118118
/// operates on a copy of the lexical stream, no input tokens are lost..
119119
public struct Parser: TokenConsumer {
120120
@_spi(RawSyntax)
121-
public var arena: SyntaxArena
121+
public var arena: ParsingSyntaxArena
122122
/// A view of the sequence of lexemes in the input.
123123
var lexemes: Lexer.LexemeSequence
124124
/// The current token. If there was no input, this token will have a kind of `.eof`.
@@ -153,7 +153,7 @@ public struct Parser: TokenConsumer {
153153
/// arena is created automatically, and `input` copied into the
154154
/// arena. If non-`nil`, `input` must be the registered source
155155
/// buffer of `arena` or a slice of the source buffer.
156-
public init(_ input: UnsafeBufferPointer<UInt8>, maximumNestingLevel: Int? = nil, arena: SyntaxArena? = nil) {
156+
public init(_ input: UnsafeBufferPointer<UInt8>, maximumNestingLevel: Int? = nil, arena: ParsingSyntaxArena? = nil) {
157157
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
158158

159159
var sourceBuffer: UnsafeBufferPointer<UInt8>
@@ -162,7 +162,7 @@ public struct Parser: TokenConsumer {
162162
sourceBuffer = input
163163
assert(arena.contains(text: SyntaxText(baseAddress: input.baseAddress, count: input.count)))
164164
} else {
165-
self.arena = SyntaxArena(
165+
self.arena = ParsingSyntaxArena(
166166
parseTriviaFunction: TriviaParser.parseTrivia(_:position:))
167167
sourceBuffer = self.arena.internSourceBuffer(input)
168168
}

Sources/SwiftSyntax/BumpPtrAllocator.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
public class BumpPtrAllocator {
1616
typealias Slab = UnsafeMutableRawBufferPointer
1717

18-
static private var SLAB_SIZE: Int = 4096
1918
static private var GLOWTH_DELAY: Int = 128
2019
static private var SLAB_ALIGNMENT: Int = 8
2120

21+
/// Initial slab size.
22+
private var slabSize: Int
23+
2224
private var slabs: [Slab]
2325
/// Pair of pointers in the current slab.
2426
/// - pointer: Points to the next unused address in `slabs.last`.
@@ -30,7 +32,8 @@ public class BumpPtrAllocator {
3032
private var customSizeSlabs: [Slab]
3133
private var _totalBytesAllocated: Int
3234

33-
public init() {
35+
public init(slabSize: Int) {
36+
self.slabSize = slabSize
3437
slabs = []
3538
current = nil
3639
customSizeSlabs = []
@@ -50,13 +53,13 @@ public class BumpPtrAllocator {
5053
}
5154

5255
/// Calculate the size of the slab at the index.
53-
private static func slabSize(at index: Int) -> Int {
56+
private func slabSize(at index: Int) -> Int {
5457
// Double the slab size every 'GLOWTH_DELAY' slabs.
55-
return SLAB_SIZE * (1 << min(30, index / GLOWTH_DELAY))
58+
return self.slabSize * (1 << min(30, index / Self.GLOWTH_DELAY))
5659
}
5760

5861
private func startNewSlab() {
59-
let newSlabSize = Self.slabSize(at: slabs.count)
62+
let newSlabSize = self.slabSize(at: slabs.count)
6063
let newSlab = Slab.allocate(
6164
byteCount: newSlabSize, alignment: Self.SLAB_ALIGNMENT)
6265
let pointer = newSlab.baseAddress!
@@ -103,7 +106,7 @@ public class BumpPtrAllocator {
103106
}
104107

105108
// If the size is too big, allocate a dedicated slab for it.
106-
if byteCount >= Self.SLAB_SIZE {
109+
if byteCount >= self.slabSize {
107110
let customSlab = Slab.allocate(
108111
byteCount: byteCount, alignment: alignment)
109112
customSizeSlabs.append(customSlab)

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,8 @@ extension RawSyntax {
459459
) -> RawSyntax {
460460
assert(arena.contains(text: wholeText),
461461
"token text must be managed by the arena")
462+
assert(arena is ParsingSyntaxArena || textRange == wholeText.indices,
463+
"arena must be able to parse trivia")
462464
let payload = RawSyntaxData.ParsedToken(
463465
tokenKind: kind,
464466
wholeText: wholeText,

Sources/SwiftSyntax/Raw/RawSyntaxTokenView.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public struct RawSyntaxTokenView {
9595
public var leadingRawTriviaPieces: [RawTriviaPiece] {
9696
switch raw.rawData.payload {
9797
case .parsedToken(let dat):
98-
return raw.arena.parseTrivia(source: dat.leadingTriviaText, position: .leading)
98+
let arena = raw.arena as! ParsingSyntaxArena
99+
return arena.parseTrivia(source: dat.leadingTriviaText, position: .leading)
99100
case .materializedToken(let dat):
100101
return Array(dat.leadingTrivia)
101102
case .layout(_):
@@ -107,7 +108,8 @@ public struct RawSyntaxTokenView {
107108
public var trailingRawTriviaPieces: [RawTriviaPiece] {
108109
switch raw.rawData.payload {
109110
case .parsedToken(let dat):
110-
return raw.arena.parseTrivia(source: dat.trailingTriviaText, position: .trailing)
111+
let arena = raw.arena as! ParsingSyntaxArena
112+
return arena.parseTrivia(source: dat.trailingTriviaText, position: .trailing)
111113
case .materializedToken(let dat):
112114
return Array(dat.trailingTrivia)
113115
case .layout(_):

Sources/SwiftSyntax/SyntaxArena.swift

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,74 +11,38 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
public class SyntaxArena {
14-
15-
@_spi(RawSyntax)
16-
public typealias ParseTriviaFunction = (_ source: SyntaxText, _ position: TriviaPosition) -> [RawTriviaPiece]
17-
1814
/// Bump-pointer allocator for all "intern" methods.
19-
private let allocator: BumpPtrAllocator
20-
/// Source file buffer the Syntax tree represents.
21-
private var sourceBuffer: UnsafeBufferPointer<UInt8>
15+
fileprivate let allocator: BumpPtrAllocator
2216

2317
/// If the syntax tree that’s allocated in this arena references nodes from
2418
/// other arenas, `childRefs` contains references to the arenas. Child arenas
2519
/// are retained in `addChild()` and are released in `deinit`.
2620
private var childRefs: Set<SyntaxArenaRef>
27-
private var parseTriviaFunction: ParseTriviaFunction
2821

2922
#if DEBUG
3023
/// Whether or not this arena has been added to other arenas as a child.
3124
/// Used to make sure we don’t introduce retain cycles between arenas.
3225
private var hasParent: Bool
3326
#endif
3427

35-
@_spi(RawSyntax)
36-
public init(parseTriviaFunction: @escaping ParseTriviaFunction) {
37-
self.allocator = BumpPtrAllocator()
28+
public convenience init() {
29+
self.init(slabSize: 128)
30+
}
31+
32+
fileprivate init(slabSize: Int) {
33+
self.allocator = BumpPtrAllocator(slabSize: slabSize)
3834
self.childRefs = []
39-
self.sourceBuffer = .init(start: nil, count: 0)
40-
self.parseTriviaFunction = parseTriviaFunction
4135
#if DEBUG
4236
self.hasParent = false
4337
#endif
4438
}
4539

46-
public convenience init() {
47-
self.init(parseTriviaFunction: _defaultParseTriviaFunction(_:_:))
48-
}
49-
5040
deinit {
5141
for child in childRefs {
5242
child.release()
5343
}
5444
}
5545

56-
/// Copies a source buffer in to the memory this arena manages, and returns
57-
/// the interned buffer.
58-
///
59-
/// The interned buffer is guaranteed to be null-terminated.
60-
/// `contains(address _:)` is faster if the address is inside the memory
61-
/// range this function returned.
62-
public func internSourceBuffer(_ buffer: UnsafeBufferPointer<UInt8>) -> UnsafeBufferPointer<UInt8> {
63-
let allocated = allocator.allocate(
64-
UInt8.self, count: buffer.count + /* for NULL */1)
65-
precondition(sourceBuffer.baseAddress == nil, "SourceBuffer should only be set once.")
66-
_ = allocated.initialize(from: buffer)
67-
68-
// NULL terminate.
69-
allocated.baseAddress!.advanced(by: buffer.count).initialize(to: 0)
70-
71-
sourceBuffer = UnsafeBufferPointer(start: allocated.baseAddress!, count: buffer.count)
72-
return sourceBuffer
73-
}
74-
75-
/// Checks if the given memory address is inside the memory range returned
76-
/// from `internSourceBuffer(_:)` method.
77-
func sourceBufferContains(_ address: UnsafePointer<UInt8>) -> Bool {
78-
guard let sourceStart = sourceBuffer.baseAddress else { return false }
79-
return sourceStart <= address && address < sourceStart.advanced(by: sourceBuffer.count)
80-
}
81-
8246
/// Allocates a buffer of `RawSyntax?` with the given count, then returns the
8347
/// uninitlialized memory range as a `UnsafeMutableBufferPointer<RawSyntax?>`.
8448
func allocateRawSyntaxBuffer(count: Int) -> UnsafeMutableBufferPointer<RawSyntax?> {
@@ -173,10 +137,63 @@ public class SyntaxArena {
173137
@_spi(RawSyntax)
174138
public func contains(text: SyntaxText) -> Bool {
175139
return (text.isEmpty ||
176-
sourceBufferContains(text.baseAddress!) ||
177140
allocator.contains(address: text.baseAddress!))
178141
}
142+
}
143+
144+
/// SyntaxArena for parsing.
145+
public class ParsingSyntaxArena: SyntaxArena {
146+
@_spi(RawSyntax)
147+
public typealias ParseTriviaFunction = (_ source: SyntaxText, _ position: TriviaPosition) -> [RawTriviaPiece]
148+
149+
/// Source file buffer the Syntax tree represents.
150+
private var sourceBuffer: UnsafeBufferPointer<UInt8>
151+
152+
/// Function to parse trivia.
153+
private var parseTriviaFunction: ParseTriviaFunction
154+
155+
@_spi(RawSyntax)
156+
public init(parseTriviaFunction: @escaping ParseTriviaFunction) {
157+
self.sourceBuffer = .init(start: nil, count: 0)
158+
self.parseTriviaFunction = parseTriviaFunction
159+
super.init(slabSize: 4096)
160+
}
179161

162+
/// Copies a source buffer in to the memory this arena manages, and returns
163+
/// the interned buffer.
164+
///
165+
/// The interned buffer is guaranteed to be null-terminated.
166+
/// `contains(address _:)` is faster if the address is inside the memory
167+
/// range this function returned.
168+
public func internSourceBuffer(_ buffer: UnsafeBufferPointer<UInt8>) -> UnsafeBufferPointer<UInt8> {
169+
let allocated = allocator.allocate(
170+
UInt8.self, count: buffer.count + /* for NULL */1)
171+
precondition(sourceBuffer.baseAddress == nil, "SourceBuffer should only be set once.")
172+
_ = allocated.initialize(from: buffer)
173+
174+
// NULL terminate.
175+
allocated.baseAddress!.advanced(by: buffer.count).initialize(to: 0)
176+
177+
sourceBuffer = UnsafeBufferPointer(start: allocated.baseAddress!, count: buffer.count)
178+
return sourceBuffer
179+
}
180+
181+
@_spi(RawSyntax)
182+
public override func contains(text: SyntaxText) -> Bool {
183+
if let addr = text.baseAddress, self.sourceBufferContains(addr) {
184+
return true
185+
}
186+
return super.contains(text: text)
187+
}
188+
189+
/// Checks if the given memory address is inside the memory range returned
190+
/// from `internSourceBuffer(_:)` method.
191+
func sourceBufferContains(_ address: UnsafePointer<UInt8>) -> Bool {
192+
guard let sourceStart = sourceBuffer.baseAddress else { return false }
193+
return sourceStart <= address && address < sourceStart.advanced(by: sourceBuffer.count)
194+
}
195+
196+
/// Parse `source` into a list of `RawTriviaPiece` using `parseTriviaFunction`.
180197
@_spi(RawSyntax)
181198
public func parseTrivia(source: SyntaxText, position: TriviaPosition) -> [RawTriviaPiece] {
182199
return self.parseTriviaFunction(source, position)
@@ -217,7 +234,3 @@ struct SyntaxArenaRef: Hashable {
217234
return lhs._value.toOpaque() == rhs._value.toOpaque()
218235
}
219236
}
220-
221-
private func _defaultParseTriviaFunction(_ source: SyntaxText, _ position: TriviaPosition) -> [RawTriviaPiece] {
222-
preconditionFailure("Trivia parsing not supported")
223-
}

Tests/SwiftSyntaxTest/BumpPtrAllocatorTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import XCTest
1616
final class BumpPtrAllocatorTests: XCTestCase {
1717

1818
func testBasic() {
19-
let allocator = BumpPtrAllocator()
19+
let allocator = BumpPtrAllocator(slabSize: 4096)
2020

2121
let byteBuffer = allocator.allocate(byteCount: 42, alignment: 4)
2222
XCTAssertNotNil(byteBuffer.baseAddress)

Tests/SwiftSyntaxTest/RawSyntaxTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ final class RawSyntaxTests: XCTestCase {
104104
return [.unexpectedText(source)]
105105
}
106106

107-
withExtendedLifetime(SyntaxArena(parseTriviaFunction: dummyParseToken)) { arena in
107+
withExtendedLifetime(ParsingSyntaxArena(parseTriviaFunction: dummyParseToken)) { arena in
108108
let ident = RawTokenSyntax(
109109
kind: .identifier, wholeText: arena.intern("\nfoo "), textRange: 1..<4,
110110
presence: .present,

0 commit comments

Comments
 (0)