Skip to content

Commit f1bc852

Browse files
committed
[Syntax] Use BumpPtrAllocator for Syntax node internals
Rework `Syntax` internals. "Red" tree is now bump-pointer allocated and visited children are cached. * `Syntax` is a pair of the allocator (strong reference to `SyntaxDataArena` class) and a pointer to the allocated data (`SyntaxData` struct). * All the red-tree data (parent and positions) are bump-pointer-allocated and cached. * The arena and the tree are 1:1. Each mutation creates a new arena. * `child(at:)` is now O(1) because the red-tree children are cached in the arena. * `root` is now O(1) regardless of the depth because the arena holds the reference. * Simplify `SyntaxChildren`. `SyntaxChildrenIndex` is now just a `Int` Remove some hacks as they aren't needed anymore: * `UnownedSyntax` because `SyntaxDataReference` replaces it. * `SyntaxNodeFactory` because `Syntax.Info` is gone. Additionally: * Flatten `AbsoluteSyntaxInfo` to simplify the data and clarify what `advancedBySibling(_:)` and `advancedToFirstChild()` do. * Remove `RawSyntaxChildren` and `RawNonNilSyntaxChildren` as they were implementation detail of `SyntaxChildren`. * Remove `AbsoluteRawSyntax` and `AbsoluteSyntaxPosition` as nobody was really using it. * Rename `Syntax.indexInParent` to `layoutIndexInParent` because it was confusing with `SyntaxProtocol.indexInParent: SyntaxChildrenIndex`
1 parent eae92fe commit f1bc852

23 files changed

+507
-900
lines changed

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
4242
"""
4343
)
4444

45-
DeclSyntax(
46-
"""
47-
/// 'Syntax' object factory recycling 'Syntax.Info' instances.
48-
private let nodeFactory: SyntaxNodeFactory = SyntaxNodeFactory()
49-
"""
50-
)
51-
5245
DeclSyntax(
5346
"""
5447
public init(viewMode: SyntaxTreeViewMode = .sourceAccurate) {
@@ -322,13 +315,13 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
322315
// with 'Syntax'
323316
var rewrittens: ContiguousArray<RetainedSyntaxArena> = []
324317
325-
for case let (child?, info) in RawSyntaxChildren(node) where viewMode.shouldTraverse(node: child) {
318+
for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) {
326319
327320
// Build the Syntax node to rewrite
328-
var childNode = nodeFactory.create(parent: node, raw: child, absoluteInfo: info)
321+
var childNode = Syntax(arena: node.arena, dataRef: childDataRef)
329322
330323
dispatchVisit(&childNode)
331-
if childNode.raw.id != child.id {
324+
if childNode.raw.id != childDataRef.pointee.raw.id {
332325
// The node was rewritten, let's handle it
333326
334327
if newLayout.baseAddress == nil {
@@ -339,13 +332,10 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
339332
}
340333
341334
// Update the rewritten child.
342-
newLayout[Int(info.indexInParent)] = childNode.raw
335+
newLayout[Int(childDataRef.pointee.absoluteInfo.layoutIndexInParent)] = childNode.raw
343336
// Retain the syntax arena of the new node until it's wrapped with Syntax node.
344337
rewrittens.append(childNode.raw.arenaReference.retained)
345338
}
346-
347-
// Recycle 'childNode.info'
348-
nodeFactory.dispose(&childNode)
349339
}
350340
351341
if newLayout.baseAddress != nil {

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxVisitorFile.swift

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,6 @@ let syntaxVisitorFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
3232
try! ClassDeclSyntax("open class SyntaxVisitor") {
3333
DeclSyntax("public let viewMode: SyntaxTreeViewMode")
3434

35-
DeclSyntax(
36-
"""
37-
/// 'Syntax' object factory recycling 'Syntax.Info' instances.
38-
private let nodeFactory: SyntaxNodeFactory = SyntaxNodeFactory()
39-
"""
40-
)
41-
4235
DeclSyntax(
4336
"""
4437
public init(viewMode: SyntaxTreeViewMode) {
@@ -243,10 +236,9 @@ let syntaxVisitorFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
243236
"""
244237
/// - Note: `node` is `inout` to avoid reference counting. See comment in `visitImpl`.
245238
private func visitChildren(_ node: inout Syntax) {
246-
for case let (child?, info) in RawSyntaxChildren(node) where viewMode.shouldTraverse(node: child) {
247-
var childNode = nodeFactory.create(parent: node, raw: child, absoluteInfo: info)
248-
visit(&childNode)
249-
nodeFactory.dispose(&childNode)
239+
for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) {
240+
var child = Syntax(arena: node.arena, dataRef: childDataRef)
241+
visit(&child)
250242
}
251243
}
252244
"""

Release Notes/610.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Swift Syntax 610 Release Notes
2+
3+
## New APIs
4+
5+
## API Behavior Changes
6+
7+
## Deprecations
8+
9+
## API-Incompatible Changes
10+
11+
- `SyntaxChildrenIndex` is no longer `ExpressibleByNilLiteral`
12+
- Description: `nil` used to represent the end index. However, due to a change in the internal structure, the end index must now be retrieved from the collection.
13+
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/2925
14+
- Migration steps: Use `SyntaxChildrenIndex.endIndex` instead.
15+
- Notes: `ExpressibleByNilLiteral` was a mistake. In general, `Collection.Index` should only be created and managed by the collection itself. For example, `Collection.index(after:)` exists, but `Index.advanced(by:)` does not.
16+
17+
## Template
18+
19+
- *Affected API or two word description*
20+
- Description: *A 1-2 sentence description of the new/modified API*
21+
- Issue: *If an issue exists for this change, a link to the issue*
22+
- Pull Request: *Link to the pull request(s) that introduces this change*
23+
- Migration steps: Steps that adopters of swift-syntax should take to move to the new API (required for deprecations and API-incompatible changes).
24+
- Notes: *In case of deprecations or API-incompatible changes, the reason why this change was made and the suggested alternative*
25+
26+
*Insert entries in chronological order, with newer entries at the bottom*

Sources/SwiftSyntax/AbsoluteRawSyntax.swift

Lines changed: 0 additions & 47 deletions
This file was deleted.

Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,59 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
struct AbsoluteSyntaxPosition: Sendable {
14-
/// The UTF-8 offset of the syntax node in the source file
15-
let offset: UInt32
16-
let indexInParent: UInt32
17-
18-
func advancedBySibling(_ raw: RawSyntax?) -> AbsoluteSyntaxPosition {
19-
let newOffset = self.offset + UInt32(truncatingIfNeeded: raw?.totalLength.utf8Length ?? 0)
20-
let newIndexInParent = self.indexInParent + 1
21-
return .init(offset: newOffset, indexInParent: newIndexInParent)
22-
}
23-
24-
func advancedToFirstChild() -> AbsoluteSyntaxPosition {
25-
return .init(offset: self.offset, indexInParent: 0)
26-
}
27-
28-
static var forRoot: AbsoluteSyntaxPosition {
29-
return .init(offset: 0, indexInParent: 0)
30-
}
31-
}
32-
3313
/// `AbsoluteSyntaxInfo` represents the information that relates a `RawSyntax`
3414
/// to a source file tree, like its absolute source offset.
3515
struct AbsoluteSyntaxInfo: Sendable {
36-
let position: AbsoluteSyntaxPosition
37-
let nodeId: SyntaxIdentifier
16+
/// Byte offset in the tree.
17+
let offset: UInt32
18+
19+
/// Index in parent's layout. Note that this counts `nil` children.
20+
let layoutIndexInParent: UInt32
3821

39-
/// The UTF-8 offset of the syntax node in the source file
40-
var offset: UInt32 { return position.offset }
41-
var indexInParent: UInt32 { return position.indexInParent }
22+
/// index of the node when traversing the syntax tree using a depth-first traversal.
23+
/// This skips `nil` children in the parent's layout.
24+
let indexInTree: UInt32
4225

4326
func advancedBySibling(_ raw: RawSyntax?) -> AbsoluteSyntaxInfo {
44-
let newPosition = position.advancedBySibling(raw)
45-
let newNodeId = nodeId.advancedBySibling(raw)
46-
return .init(position: newPosition, nodeId: newNodeId)
27+
if let raw {
28+
return AbsoluteSyntaxInfo(
29+
offset: offset &+ UInt32(truncatingIfNeeded: raw.totalLength.utf8Length),
30+
layoutIndexInParent: layoutIndexInParent &+ 1,
31+
indexInTree: indexInTree &+ UInt32(truncatingIfNeeded: raw.totalNodes)
32+
)
33+
} else {
34+
return AbsoluteSyntaxInfo(
35+
offset: offset,
36+
layoutIndexInParent: layoutIndexInParent &+ 1,
37+
indexInTree: indexInTree
38+
)
39+
}
4740
}
4841

4942
func advancedToFirstChild() -> AbsoluteSyntaxInfo {
50-
let newPosition = position.advancedToFirstChild()
51-
let newNodeId = nodeId.advancedToFirstChild()
52-
return .init(position: newPosition, nodeId: newNodeId)
43+
return AbsoluteSyntaxInfo(
44+
offset: offset,
45+
layoutIndexInParent: 0,
46+
indexInTree: indexInTree &+ 1
47+
)
5348
}
5449

5550
static func forRoot(_ raw: RawSyntax) -> AbsoluteSyntaxInfo {
56-
return .init(position: .forRoot, nodeId: .forRoot(raw))
51+
// These checks ensure the safety of the unchecked arithmetic operations in 'advancedBySibling(_:)'.
52+
precondition(raw.totalLength.utf8Length <= UInt32.max, "too long")
53+
precondition(raw.totalNodes <= UInt32.max, "too many nodes")
54+
return AbsoluteSyntaxInfo(
55+
offset: 0,
56+
layoutIndexInParent: 0,
57+
indexInTree: 0
58+
)
5759
}
5860
}

Sources/SwiftSyntax/CMakeLists.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
add_swift_syntax_library(SwiftSyntax
1010
AbsolutePosition.swift
11-
AbsoluteRawSyntax.swift
1211
AbsoluteSyntaxInfo.swift
1312
Assert.swift
1413
BumpPtrAllocator.swift
@@ -31,7 +30,6 @@ add_swift_syntax_library(SwiftSyntax
3130
SyntaxCollection.swift
3231
SyntaxHashable.swift
3332
SyntaxIdentifier.swift
34-
SyntaxNodeFactory.swift
3533
SyntaxNodeStructure.swift
3634
SyntaxProtocol.swift
3735
SyntaxText.swift

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,10 @@ extension RawSyntaxData.ParsedToken {
186186

187187
extension RawSyntaxData.MaterializedToken {
188188
var leadingTrivia: RawTriviaPieceBuffer {
189-
triviaPieces[..<Int(numLeadingTrivia)]
189+
RawTriviaPieceBuffer(rebasing: triviaPieces[..<Int(numLeadingTrivia)])
190190
}
191191
var trailingTrivia: RawTriviaPieceBuffer {
192-
triviaPieces[Int(numLeadingTrivia)...]
192+
RawTriviaPieceBuffer(rebasing: triviaPieces[Int(numLeadingTrivia)...])
193193
}
194194
}
195195

@@ -215,7 +215,7 @@ public struct RawSyntax: Sendable {
215215
}
216216

217217
var rawData: RawSyntaxData {
218-
pointer.pointee
218+
@_transparent unsafeAddress { pointer.pointer }
219219
}
220220

221221
internal var arenaReference: SyntaxArenaRef {
@@ -954,7 +954,7 @@ extension RawSyntax {
954954
extension RawSyntax: Identifiable {
955955
public struct ID: Hashable, @unchecked Sendable {
956956
/// The pointer to the start of the `RawSyntax` node.
957-
private var pointer: UnsafeRawPointer
957+
fileprivate var pointer: UnsafeRawPointer
958958
fileprivate init(_ raw: RawSyntax) {
959959
self.pointer = raw.pointer.unsafeRawPointer
960960
}
@@ -965,6 +965,13 @@ extension RawSyntax: Identifiable {
965965
}
966966
}
967967

968+
extension UInt {
969+
/// Convert `RawSymtax.ID` to `UInt`. Lossless.
970+
init(rawID: RawSyntax.ID) {
971+
self.init(bitPattern: rawID.pointer)
972+
}
973+
}
974+
968975
/// See `SyntaxMemoryLayout`.
969976
let RawSyntaxDataMemoryLayouts: [String: SyntaxMemoryLayout.Value] = [
970977
"RawSyntaxData": .init(RawSyntaxData.self),

0 commit comments

Comments
 (0)