Skip to content

Commit e664473

Browse files
committed
[SyntaxRewriter] Improve new layout node creation
Use manually allocated 'UnsafeBufferPointer' to avoid 'ContiguousArray' overhead. Pre-initialize the buffer with the "old" layout at once, and update each element only when updated.
1 parent b6898d1 commit e664473

File tree

3 files changed

+32
-64
lines changed

3 files changed

+32
-64
lines changed

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

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
314314
315315
// newLayout is nil until the first child node is rewritten and rewritten
316316
// nodes are being collected.
317-
var newLayout: ContiguousArray<RawSyntax?>?
317+
var newLayout: UnsafeMutableBufferPointer<RawSyntax?> = .init(start: nil, count: 0)
318318
319319
// Keep 'SyntaxArena' of rewritten nodes alive until they are wrapped
320320
// with 'Syntax'
@@ -328,12 +328,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
328328
defer { childIndex += 1 }
329329
330330
guard let child = raw, viewMode.shouldTraverse(node: child) else {
331-
// Node does not exist or should not be visited. If we are collecting
332-
// rewritten nodes, we need to collect this one as well, otherwise we
333-
// can ignore it.
334-
if newLayout != nil {
335-
newLayout!.append(raw)
336-
}
331+
// Node does not exist or should not be visited.
337332
continue
338333
}
339334
@@ -343,44 +338,31 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
343338
dispatchVisit(&childNode)
344339
if childNode.raw.id != child.id {
345340
// The node was rewritten, let's handle it
346-
if newLayout == nil {
341+
342+
if newLayout.baseAddress == nil {
347343
// We have not yet collected any previous rewritten nodes. Initialize
348-
// the new layout with the previous nodes of the parent. This is
349-
// possible, since we know they were not rewritten.
350-
351-
// The below implementation is based on Collection.map but directly
352-
// reserves enough capacity for the entire layout.
353-
newLayout = ContiguousArray<RawSyntax?>()
354-
newLayout!.reserveCapacity(node.raw.layoutView!.children.count)
355-
for j in 0..<childIndex {
356-
newLayout!.append(node.raw.layoutView!.children[j])
357-
}
344+
// the new layout with the previous nodes of the parent.
345+
newLayout = .allocate(capacity: node.raw.layoutView!.children.count)
346+
_ = newLayout.initialize(fromContentsOf: node.raw.layoutView!.children)
358347
}
359348
360-
// Now that we know we have a new layout in which we collect rewritten
361-
// nodes, add it.
349+
// Update the rewritten child.
350+
newLayout[childIndex] = childNode.raw
351+
// Retain the syntax arena of the new node until it's wrapped with Syntax node.
362352
rewrittens.append(childNode.raw.arenaReference.retained)
363-
newLayout!.append(childNode.raw)
364-
} else {
365-
// The node was not changed by the rewriter. Only store it if a previous
366-
// node has been rewritten and we are collecting a rewritten layout.
367-
if newLayout != nil {
368-
newLayout!.append(raw)
369-
}
370353
}
371354
372355
// Recycle 'childNode.info'
373356
nodeFactory.dispose(&childNode)
374357
}
375358
376-
if let newLayout {
359+
if newLayout.baseAddress != nil {
377360
// A child node was rewritten. Build the updated node.
378361
379-
// Sanity check, ensure the new children are the same length.
380-
precondition(newLayout.count == node.raw.layoutView!.children.count)
381-
382362
let arena = self.arena ?? SyntaxArena()
383-
let newRaw = node.raw.layoutView!.replacingLayout(with: Array(newLayout), arena: arena)
363+
let newRaw = node.raw.layoutView!.replacingLayout(with: newLayout, arena: arena)
364+
newLayout.deinitialize()
365+
newLayout.deallocate()
384366
// 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
385367
return withExtendedLifetime(rewrittens) {
386368
Syntax(raw: newRaw, rawNodeArena: arena).cast(SyntaxType.self)

Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,8 @@ public struct SyntaxArenaAllocatedBufferPointer<Element: Sendable>: RandomAccess
107107
var unsafeRawBufferPointer: UnsafeRawBufferPointer {
108108
return UnsafeRawBufferPointer(buffer)
109109
}
110+
111+
public func withContiguousStorageIfAvailable<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R? {
112+
try body(buffer)
113+
}
110114
}

Sources/SwiftSyntax/generated/SyntaxRewriter.swift

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3911,7 +3911,7 @@ open class SyntaxRewriter {
39113911

39123912
// newLayout is nil until the first child node is rewritten and rewritten
39133913
// nodes are being collected.
3914-
var newLayout: ContiguousArray<RawSyntax?>?
3914+
var newLayout: UnsafeMutableBufferPointer<RawSyntax?> = .init(start: nil, count: 0)
39153915

39163916
// Keep 'SyntaxArena' of rewritten nodes alive until they are wrapped
39173917
// with 'Syntax'
@@ -3927,12 +3927,7 @@ open class SyntaxRewriter {
39273927
}
39283928

39293929
guard let child = raw, viewMode.shouldTraverse(node: child) else {
3930-
// Node does not exist or should not be visited. If we are collecting
3931-
// rewritten nodes, we need to collect this one as well, otherwise we
3932-
// can ignore it.
3933-
if newLayout != nil {
3934-
newLayout!.append(raw)
3935-
}
3930+
// Node does not exist or should not be visited.
39363931
continue
39373932
}
39383933

@@ -3942,44 +3937,31 @@ open class SyntaxRewriter {
39423937
dispatchVisit(&childNode)
39433938
if childNode.raw.id != child.id {
39443939
// The node was rewritten, let's handle it
3945-
if newLayout == nil {
3946-
// We have not yet collected any previous rewritten nodes. Initialize
3947-
// the new layout with the previous nodes of the parent. This is
3948-
// possible, since we know they were not rewritten.
39493940

3950-
// The below implementation is based on Collection.map but directly
3951-
// reserves enough capacity for the entire layout.
3952-
newLayout = ContiguousArray<RawSyntax?>()
3953-
newLayout!.reserveCapacity(node.raw.layoutView!.children.count)
3954-
for j in 0 ..< childIndex {
3955-
newLayout!.append(node.raw.layoutView!.children[j])
3956-
}
3941+
if newLayout.baseAddress == nil {
3942+
// We have not yet collected any previous rewritten nodes. Initialize
3943+
// the new layout with the previous nodes of the parent.
3944+
newLayout = .allocate(capacity: node.raw.layoutView!.children.count)
3945+
_ = newLayout.initialize(fromContentsOf: node.raw.layoutView!.children)
39573946
}
39583947

3959-
// Now that we know we have a new layout in which we collect rewritten
3960-
// nodes, add it.
3948+
// Update the rewritten child.
3949+
newLayout[childIndex] = childNode.raw
3950+
// Retain the syntax arena of the new node until it's wrapped with Syntax node.
39613951
rewrittens.append(childNode.raw.arenaReference.retained)
3962-
newLayout!.append(childNode.raw)
3963-
} else {
3964-
// The node was not changed by the rewriter. Only store it if a previous
3965-
// node has been rewritten and we are collecting a rewritten layout.
3966-
if newLayout != nil {
3967-
newLayout!.append(raw)
3968-
}
39693952
}
39703953

39713954
// Recycle 'childNode.info'
39723955
nodeFactory.dispose(&childNode)
39733956
}
39743957

3975-
if let newLayout {
3958+
if newLayout.baseAddress != nil {
39763959
// A child node was rewritten. Build the updated node.
39773960

3978-
// Sanity check, ensure the new children are the same length.
3979-
precondition(newLayout.count == node.raw.layoutView!.children.count)
3980-
39813961
let arena = self.arena ?? SyntaxArena()
3982-
let newRaw = node.raw.layoutView!.replacingLayout(with: Array(newLayout), arena: arena)
3962+
let newRaw = node.raw.layoutView!.replacingLayout(with: newLayout, arena: arena)
3963+
newLayout.deinitialize()
3964+
newLayout.deallocate()
39833965
// 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
39843966
return withExtendedLifetime(rewrittens) {
39853967
Syntax(raw: newRaw, rawNodeArena: arena).cast(SyntaxType.self)

0 commit comments

Comments
 (0)