Skip to content

Commit d8b0994

Browse files
committed
Make underlying data structures of SyntaxData.Info classes insted of structs
This will allow us to add more data to `SyntaxData.Info.Root` without increasing the size of `SyntaxData`. It shouldn’t have a big performance impact at the moment since `Root.arena` is never accessed and only exists to keep `arena` a live. x
1 parent a654949 commit d8b0994

File tree

3 files changed

+49
-9
lines changed

3 files changed

+49
-9
lines changed

Sources/SwiftSyntax/Syntax.swift

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,58 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
2323
final class Info: @unchecked Sendable {
2424
// For root node.
2525
struct Root: Sendable {
26-
private var arena: RetainedSyntaxArena
26+
private let arena: RetainedSyntaxArena
2727

2828
init(arena: RetainedSyntaxArena) {
2929
self.arena = arena
3030
}
3131
}
3232

33+
/// Class that owns a `Root`, is responsible for keeping it alive and also offers an unsafe pointer way to
34+
/// access it.
35+
///
36+
/// This way, the root node of a tree can own the root info and all other nodes in the tree can have an unsafe
37+
/// pointer reference to the root info, which doesn't involve ref counting.
38+
final class RefCountedRoot: Sendable {
39+
/// `nonisolated(unsafe)` if fine because there are only two ways this gets accessed:
40+
/// - `pointer`: Here we reference `value` via inout to get a pointer to `Root` but the pointer is not mutable
41+
/// so no mutation happens here
42+
#if swift(>=6)
43+
private nonisolated(unsafe) var value: Root
44+
#else
45+
private var value: Root
46+
#endif
47+
48+
fileprivate init(_ value: Root) {
49+
self.value = value
50+
}
51+
52+
fileprivate var pointer: UnsafePointer<Root> {
53+
return withUnsafePointer(to: &value) { $0 }
54+
}
55+
}
56+
3357
// For non-root nodes.
3458
struct NonRoot: Sendable {
3559
var parent: Syntax
3660
var absoluteInfo: AbsoluteSyntaxInfo
61+
// `nonisolated(unsafe)` is fine because `Root` is owned by `RefCountedRoot` and `RefCountedRoot` guarantees that
62+
// `Root` is not changing after the tree has been created.
63+
#if swift(>=6)
64+
nonisolated(unsafe) var rootInfo: UnsafePointer<Root>
65+
#else
66+
var rootInfo: UnsafePointer<Root>
67+
#endif
68+
69+
init(parent: Syntax, absoluteInfo: AbsoluteSyntaxInfo, rootInfo: UnsafePointer<Root>) {
70+
self.parent = parent
71+
self.absoluteInfo = absoluteInfo
72+
self.rootInfo = rootInfo
73+
}
3774
}
3875

3976
enum InfoImpl: Sendable {
40-
case root(Root)
77+
case root(RefCountedRoot)
4178
case nonRoot(NonRoot)
4279
}
4380

@@ -67,9 +104,9 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
67104
var info: Info!
68105
let raw: RawSyntax
69106

70-
private var rootInfo: Info.Root {
107+
var rootInfo: UnsafePointer<Info.Root> {
71108
switch info.info! {
72-
case .root(let info): return info
109+
case .root(let info): return info.pointer
73110
case .nonRoot(let info): return info.parent.rootInfo
74111
}
75112
}
@@ -135,7 +172,7 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
135172
}
136173

137174
init(_ raw: RawSyntax, parent: Syntax, absoluteInfo: AbsoluteSyntaxInfo) {
138-
self.init(raw, info: Info(.nonRoot(.init(parent: parent, absoluteInfo: absoluteInfo))))
175+
self.init(raw, info: Info(.nonRoot(.init(parent: parent, absoluteInfo: absoluteInfo, rootInfo: parent.rootInfo))))
139176
}
140177

141178
/// Creates a `Syntax` with the provided raw syntax and parent.
@@ -155,12 +192,15 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
155192
/// has a chance to retain it.
156193
static func forRoot(_ raw: RawSyntax, rawNodeArena: RetainedSyntaxArena) -> Syntax {
157194
precondition(rawNodeArena == raw.arenaReference)
158-
return Syntax(raw, info: Info(.root(.init(arena: rawNodeArena))))
195+
return Syntax(raw, info: Info(.root(Syntax.Info.RefCountedRoot(Syntax.Info.Root(arena: rawNodeArena)))))
159196
}
160197

161198
static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> Syntax {
162199
precondition(rawNodeArena == raw.arenaReference)
163-
return Syntax(raw, info: Info(.root(.init(arena: RetainedSyntaxArena(rawNodeArena)))))
200+
return Syntax(
201+
raw,
202+
info: Info(.root(Syntax.Info.RefCountedRoot(Syntax.Info.Root(arena: RetainedSyntaxArena(rawNodeArena)))))
203+
)
164204
}
165205

166206
/// Returns the child data at the provided index in this data's layout.

Sources/SwiftSyntax/SyntaxNodeFactory.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ struct SyntaxNodeFactory {
6565
@inline(__always)
6666
func create(parent: Syntax, raw: RawSyntax, absoluteInfo: AbsoluteSyntaxInfo) -> Syntax {
6767
if let info = syntaxInfoRepo.pop() {
68-
info.info = .nonRoot(.init(parent: parent, absoluteInfo: absoluteInfo))
68+
info.info = .nonRoot(.init(parent: parent, absoluteInfo: absoluteInfo, rootInfo: parent.rootInfo))
6969
return Syntax(raw, info: info)
7070
} else {
7171
return Syntax(raw, parent: parent, absoluteInfo: absoluteInfo)

Tests/SwiftSyntaxTest/MemoryLayoutTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class MemoryLayoutTest: XCTestCase {
3535
"Syntax": .init(size: 16, stride: 16, alignment: 8),
3636
"Syntax.Info": .init(size: 8, stride: 8, alignment: 8),
3737
"Syntax.Info.Root": .init(size: 8, stride: 8, alignment: 8),
38-
"Syntax.Info.NonRoot": .init(size: 36, stride: 40, alignment: 8),
38+
"Syntax.Info.NonRoot": .init(size: 48, stride: 48, alignment: 8),
3939
]
4040

4141
let values = SyntaxMemoryLayout.values

0 commit comments

Comments
 (0)