Skip to content

Commit a05c783

Browse files
committed
[Syntax] Perf: Optimize 'root' and 'ancestorOrSelf'
1 parent 445486b commit a05c783

File tree

2 files changed

+76
-17
lines changed

2 files changed

+76
-17
lines changed

Diff for: Sources/SwiftSyntax/Syntax.swift

+65-6
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,13 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
8181
}
8282
}
8383

84-
private var root: Syntax {
85-
switch info.info! {
86-
case .root(_): return self
87-
case .nonRoot(let info): return info.parent.root
84+
public var root: Syntax {
85+
return self.withUnownedSyntax {
86+
var node = $0
87+
while let parent = node.parent {
88+
node = parent
89+
}
90+
return node.value
8891
}
8992
}
9093

@@ -129,7 +132,10 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
129132
}
130133

131134
/// "designated" memberwise initializer of `Syntax`.
132-
init(_ raw: RawSyntax, info: Info) {
135+
// transparent because normal inlining is too late for eliminating ARC traffic for Info.
136+
// FIXME: Remove @_transparent after OSSA enabled.
137+
@_transparent
138+
init(_ raw: RawSyntax, info: __shared Info) {
133139
self.raw = raw
134140
self.info = info
135141
}
@@ -309,7 +315,7 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
309315
/// Create a ``Syntax`` node from a specialized syntax node.
310316
// Inline always so the optimizer can optimize this to a member access on `syntax` without having to go through
311317
// generics.
312-
@inline(__always)
318+
@_transparent
313319
public init(_ syntax: __shared some SyntaxProtocol) {
314320
self = syntax._syntaxNode
315321
}
@@ -380,6 +386,59 @@ extension Syntax {
380386
}
381387
}
382388

389+
/// Temporary non-owning Syntax.
390+
///
391+
/// This can be used for handling Syntax node without ARC traffic.
392+
struct UnownedSyntax {
393+
private let raw: RawSyntax
394+
private let info: Unmanaged<Syntax.Info>
395+
396+
@_transparent
397+
init(_ node: __shared Syntax) {
398+
self.raw = node.raw
399+
self.info = .passUnretained(node.info.unsafelyUnwrapped)
400+
}
401+
402+
/// Extract the Syntax value.
403+
@inline(__always)
404+
var value: Syntax {
405+
Syntax(raw, info: info.takeUnretainedValue())
406+
}
407+
408+
/// Get the parent of the Syntax value, but without retaining it.
409+
@inline(__always)
410+
var parent: UnownedSyntax? {
411+
return info._withUnsafeGuaranteedRef {
412+
switch $0.info.unsafelyUnwrapped {
413+
case .nonRoot(let info):
414+
return UnownedSyntax(info.parent)
415+
case .root(_):
416+
return nil
417+
}
418+
}
419+
}
420+
421+
/// Temporarily use the Syntax value.
422+
@inline(__always)
423+
func withValue<T>(_ body: (Syntax) -> T) -> T {
424+
info._withUnsafeGuaranteedRef {
425+
body(Syntax(self.raw, info: $0))
426+
}
427+
}
428+
}
429+
430+
extension SyntaxProtocol {
431+
/// Execute the `body` with ``UnownedSyntax`` of `node`.
432+
///
433+
/// This guarantees the life time of the `node` during the `body` is executed.
434+
@inline(__always)
435+
func withUnownedSyntax<T>(_ body: (UnownedSyntax) -> T) -> T {
436+
return withExtendedLifetime(self) {
437+
body(UnownedSyntax(Syntax($0)))
438+
}
439+
}
440+
}
441+
383442
/// ``SyntaxNode`` used to be a pervasive type name in SwiftSyntax that has been
384443
/// replaced by the ``Syntax`` type.
385444
@available(*, unavailable, message: "use 'Syntax' instead")

Diff for: Sources/SwiftSyntax/SyntaxProtocol.swift

+11-11
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,7 @@ extension SyntaxProtocol {
212212

213213
/// The root of the tree in which this node resides.
214214
public var root: Syntax {
215-
var this = _syntaxNode
216-
while let parent = this.parent {
217-
this = parent
218-
}
219-
return this
215+
return Syntax(self).root
220216
}
221217

222218
/// Whether or not this node has a parent.
@@ -241,14 +237,18 @@ extension SyntaxProtocol {
241237

242238
/// Returns this node or the first ancestor that satisfies `condition`.
243239
public func ancestorOrSelf<T>(mapping map: (Syntax) -> T?) -> T? {
244-
var walk: Syntax? = Syntax(self)
245-
while let unwrappedParent = walk {
246-
if let mapped = map(unwrappedParent) {
247-
return mapped
240+
return self.withUnownedSyntax {
241+
var node = $0
242+
while true {
243+
if let mapped = node.withValue(map) {
244+
return mapped
245+
}
246+
guard let parent = node.parent else {
247+
return nil
248+
}
249+
node = parent
248250
}
249-
walk = unwrappedParent.parent
250251
}
251-
return nil
252252
}
253253
}
254254

0 commit comments

Comments
 (0)