@@ -42,11 +42,31 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
42
42
"""
43
43
)
44
44
45
+ DeclSyntax (
46
+ """
47
+ /// `Syntax.Info` salvaged from the node being deinitialized in 'visitChildren'.
48
+ ///
49
+ /// Instead of deallocating them and allocating memory for new syntax nodes, store the allocated memory in an array.
50
+ /// We can then re-use them to create new syntax nodes.
51
+ ///
52
+ /// The array's size should be a typical nesting depth of a Swift file. That way we can store all allocated syntax
53
+ /// nodes when unwinding the visitation stack.
54
+ ///
55
+ /// The actual `info` stored in the `Syntax.Info` objects is garbage. It needs to be set when any of the `Syntax.Info`
56
+ /// objects get re-used.
57
+ ///
58
+ /// Note: making the element non-nil causes 'swift::runtime::SwiftTLSContext::get()' traffic somehow.
59
+ private var recyclableNodeInfos: ContiguousArray<Syntax.Info?>
60
+ """
61
+ )
62
+
45
63
DeclSyntax (
46
64
"""
47
65
public init(viewMode: SyntaxTreeViewMode = .sourceAccurate) {
48
66
self.viewMode = viewMode
49
67
self.arena = nil
68
+ self.recyclableNodeInfos = []
69
+ self.recyclableNodeInfos.reserveCapacity(64)
50
70
}
51
71
"""
52
72
)
@@ -57,6 +77,8 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
57
77
public init(viewMode: SyntaxTreeViewMode = .sourceAccurate, arena: SyntaxArena? = nil) {
58
78
self.viewMode = viewMode
59
79
self.arena = arena
80
+ self.recyclableNodeInfos = []
81
+ self.recyclableNodeInfos.reserveCapacity(64)
60
82
}
61
83
"""
62
84
)
@@ -65,7 +87,8 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
65
87
"""
66
88
/// Rewrite `node`, keeping its parent unless `detach` is `true`.
67
89
public func rewrite(_ node: some SyntaxProtocol, detach: Bool = false) -> Syntax {
68
- let rewritten = self.dispatchVisit(Syntax(node))
90
+ var rewritten = Syntax(node)
91
+ self.dispatchVisit(&rewritten)
69
92
if detach {
70
93
return rewritten
71
94
}
@@ -126,15 +149,19 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
126
149
/// - Returns: the rewritten node
127
150
@available(*, deprecated, renamed: " rewrite(_:detach:) " )
128
151
public func visit(_ node: Syntax) -> Syntax {
129
- return dispatchVisit(node)
152
+ var rewritten = node
153
+ dispatchVisit(&rewritten)
154
+ return rewritten
130
155
}
131
156
"""
132
157
)
133
158
134
159
DeclSyntax (
135
160
"""
136
161
public func visit<T: SyntaxChildChoices>(_ node: T) -> T {
137
- return dispatchVisit(Syntax(node)).cast(T.self)
162
+ var rewritten = Syntax(node)
163
+ dispatchVisit(&rewritten)
164
+ return rewritten.cast(T.self)
138
165
}
139
166
"""
140
167
)
@@ -177,7 +204,9 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
177
204
/// - Returns: the rewritten node
178
205
\( baseNode. apiAttributes ( ) ) \
179
206
public func visit(_ node: \( baseKind. syntaxType) ) -> \( baseKind. syntaxType) {
180
- return dispatchVisit(Syntax(node)).cast( \( baseKind. syntaxType) .self)
207
+ var node: Syntax = Syntax(node)
208
+ dispatchVisit(&node)
209
+ return node.cast( \( baseKind. syntaxType) .self)
181
210
}
182
211
"""
183
212
)
@@ -187,21 +216,16 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
187
216
"""
188
217
/// Interpret `node` as a node of type `nodeType`, visit it, calling
189
218
/// the `visit` to transform the node.
219
+ @inline(__always)
190
220
private func visitImpl<NodeType: SyntaxProtocol>(
191
- _ node: Syntax,
221
+ _ node: inout Syntax,
192
222
_ nodeType: NodeType.Type,
193
223
_ visit: (NodeType) -> some SyntaxProtocol
194
- ) -> Syntax {
195
- let castedNode = node.cast(NodeType.self)
196
- // Accessing _syntaxNode directly is faster than calling Syntax(node)
197
- visitPre(node)
198
- defer {
199
- visitPost(node)
200
- }
201
- if let newNode = visitAny(node) {
202
- return newNode
203
- }
204
- return Syntax(visit(castedNode))
224
+ ) {
225
+ let origNode = node
226
+ visitPre(origNode)
227
+ node = visitAny(origNode) ?? Syntax(visit(origNode.cast(NodeType.self)))
228
+ visitPost(origNode)
205
229
}
206
230
"""
207
231
)
@@ -242,26 +266,26 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
242
266
/// that determines the correct visitation function will be popped of the
243
267
/// stack before the function is being called, making the switch's stack
244
268
/// space transient instead of having it linger in the call stack.
245
- private func visitationFunc(for node: Syntax) -> ((Syntax) -> Syntax )
269
+ private func visitationFunc(for node: Syntax) -> ((inout Syntax) -> Void )
246
270
"""
247
271
) {
248
272
try SwitchExprSyntax ( " switch node.raw.kind " ) {
249
273
SwitchCaseSyntax ( " case .token: " ) {
250
- StmtSyntax ( " return { self.visitImpl($0, TokenSyntax.self, self.visit) } " )
274
+ StmtSyntax ( " return { self.visitImpl(& $0, TokenSyntax.self, self.visit) } " )
251
275
}
252
276
253
277
for node in NON_BASE_SYNTAX_NODES {
254
278
SwitchCaseSyntax ( " case . \( node. varOrCaseName) : " ) {
255
- StmtSyntax ( " return { self.visitImpl($0, \( node. kind. syntaxType) .self, self.visit) } " )
279
+ StmtSyntax ( " return { self.visitImpl(& $0, \( node. kind. syntaxType) .self, self.visit) } " )
256
280
}
257
281
}
258
282
}
259
283
}
260
284
261
285
DeclSyntax (
262
286
"""
263
- private func dispatchVisit(_ node: Syntax) -> Syntax {
264
- return visitationFunc(for: node)(node)
287
+ private func dispatchVisit(_ node: inout Syntax) {
288
+ visitationFunc(for: node)(& node)
265
289
}
266
290
"""
267
291
)
@@ -272,15 +296,15 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
272
296
poundKeyword: . poundElseToken( ) ,
273
297
elements: . statements(
274
298
CodeBlockItemListSyntax {
275
- try ! FunctionDeclSyntax ( " private func dispatchVisit(_ node: Syntax) -> Syntax " ) {
299
+ try ! FunctionDeclSyntax ( " private func dispatchVisit(_ node: inout Syntax) " ) {
276
300
try SwitchExprSyntax ( " switch node.raw.kind " ) {
277
301
SwitchCaseSyntax ( " case .token: " ) {
278
- StmtSyntax ( " return visitImpl(node, TokenSyntax.self, visit) " )
302
+ StmtSyntax ( " return visitImpl(& node, TokenSyntax.self, visit) " )
279
303
}
280
304
281
305
for node in NON_BASE_SYNTAX_NODES {
282
306
SwitchCaseSyntax ( " case . \( node. varOrCaseName) : " ) {
283
- StmtSyntax ( " return visitImpl(node, \( node. kind. syntaxType) .self, visit) " )
307
+ StmtSyntax ( " return visitImpl(& node, \( node. kind. syntaxType) .self, visit) " )
284
308
}
285
309
}
286
310
}
@@ -307,9 +331,9 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
307
331
// nodes are being collected.
308
332
var newLayout: ContiguousArray<RawSyntax?>?
309
333
310
- // Rewritten children just to keep their 'SyntaxArena' alive until they are
311
- // wrapped with 'Syntax'
312
- var rewrittens: ContiguousArray<Syntax > = []
334
+ // Keep 'SyntaxArena' of rewritten nodes alive until they are wrapped
335
+ // with 'Syntax'
336
+ var rewrittens: ContiguousArray<RetainedSyntaxArena > = []
313
337
314
338
let syntaxNode = node._syntaxNode
315
339
@@ -329,10 +353,18 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
329
353
}
330
354
331
355
// Build the Syntax node to rewrite
332
- let absoluteRaw = AbsoluteRawSyntax(raw: child, info: info)
356
+ var childNode: Syntax
357
+ if !recyclableNodeInfos.isEmpty {
358
+ let recycledInfo: Syntax.Info = recyclableNodeInfos.removeLast()!
359
+ recycledInfo.info = .nonRoot(.init(parent: Syntax(node), absoluteInfo: info))
360
+ childNode = Syntax(child, info: recycledInfo)
361
+ } else {
362
+ let absoluteRaw = AbsoluteRawSyntax(raw: child, info: info)
363
+ childNode = Syntax(absoluteRaw, parent: syntaxNode)
364
+ }
333
365
334
- let rewritten = dispatchVisit(Syntax(absoluteRaw, parent: syntaxNode) )
335
- if rewritten. id != info.nodeId {
366
+ dispatchVisit(&childNode )
367
+ if childNode.raw. id != child.id {
336
368
// The node was rewritten, let's handle it
337
369
if newLayout == nil {
338
370
// We have not yet collected any previous rewritten nodes. Initialize
@@ -350,15 +382,24 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
350
382
351
383
// Now that we know we have a new layout in which we collect rewritten
352
384
// nodes, add it.
353
- rewrittens.append(rewritten )
354
- newLayout!.append(rewritten .raw)
385
+ rewrittens.append(childNode.raw.arenaReference.retained )
386
+ newLayout!.append(childNode .raw)
355
387
} else {
356
388
// The node was not changed by the rewriter. Only store it if a previous
357
389
// node has been rewritten and we are collecting a rewritten layout.
358
390
if newLayout != nil {
359
391
newLayout!.append(raw)
360
392
}
361
393
}
394
+
395
+ if recyclableNodeInfos.capacity > recyclableNodeInfos.count,
396
+ isKnownUniquelyReferenced(&childNode.info) {
397
+ var info: Syntax.Info! = nil
398
+ // 'swap' to avoid ref-counting traffic.
399
+ swap(&childNode.info, &info)
400
+ info.info = nil
401
+ recyclableNodeInfos.append(info)
402
+ }
362
403
}
363
404
364
405
if let newLayout {
0 commit comments