From b64f80d551c27170a0cc73f145ed93b5d97a9829 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 16 Dec 2024 16:38:59 -0800 Subject: [PATCH 1/2] [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` --- .../swiftsyntax/SyntaxRewriterFile.swift | 18 +- .../swiftsyntax/SyntaxVisitorFile.swift | 13 +- Release Notes/602.md | 6 + Sources/SwiftSyntax/AbsoluteRawSyntax.swift | 47 -- Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift | 69 +-- Sources/SwiftSyntax/CMakeLists.txt | 2 - Sources/SwiftSyntax/MemoryLayout.swift | 6 +- Sources/SwiftSyntax/Raw/RawSyntax.swift | 13 +- Sources/SwiftSyntax/Syntax.swift | 401 ++++++++++------ .../SyntaxArenaAllocatedBuffer.swift | 18 +- Sources/SwiftSyntax/SyntaxChildren.swift | 443 ++---------------- Sources/SwiftSyntax/SyntaxCollection.swift | 59 +-- Sources/SwiftSyntax/SyntaxIdentifier.swift | 48 +- Sources/SwiftSyntax/SyntaxNodeFactory.swift | 85 ---- Sources/SwiftSyntax/SyntaxProtocol.swift | 60 ++- .../generated/SyntaxRewriter.swift | 14 +- .../SwiftSyntax/generated/SyntaxVisitor.swift | 9 +- Sources/_SwiftSyntaxCShims/CMakeLists.txt | 6 +- Sources/_SwiftSyntaxCShims/PlatformMutex.c | 85 ++++ Sources/_SwiftSyntaxCShims/dummy.c | 1 - .../include/{AtomicBool.h => Atomics.h} | 12 + .../include/PlatformMutex.h | 34 ++ .../include/SwiftSyntaxCShims.h | 3 +- .../_SwiftSyntaxCShims/include/_bridging.h | 22 + .../include/module.modulemap | 6 +- .../include/swiftsyntax_errno.h | 4 +- .../include/swiftsyntax_stdio.h | 8 +- Tests/SwiftSyntaxTest/MemoryLayoutTest.swift | 16 +- 28 files changed, 606 insertions(+), 902 deletions(-) delete mode 100644 Sources/SwiftSyntax/AbsoluteRawSyntax.swift delete mode 100644 Sources/SwiftSyntax/SyntaxNodeFactory.swift create mode 100644 Sources/_SwiftSyntaxCShims/PlatformMutex.c delete mode 100644 Sources/_SwiftSyntaxCShims/dummy.c rename Sources/_SwiftSyntaxCShims/include/{AtomicBool.h => Atomics.h} (77%) create mode 100644 Sources/_SwiftSyntaxCShims/include/PlatformMutex.h create mode 100644 Sources/_SwiftSyntaxCShims/include/_bridging.h diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift index 86f2cbaaea1..d8005fde045 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift @@ -42,13 +42,6 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { """ ) - DeclSyntax( - """ - /// 'Syntax' object factory recycling 'Syntax.Info' instances. - private let nodeFactory: SyntaxNodeFactory = SyntaxNodeFactory() - """ - ) - DeclSyntax( """ public init(viewMode: SyntaxTreeViewMode = .sourceAccurate) { @@ -330,11 +323,11 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { // with 'Syntax' var rewrittens: ContiguousArray = [] - for case let (child?, info) in RawSyntaxChildren(node) where viewMode.shouldTraverse(node: child) { + for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) { // Build the Syntax node to rewrite - var childNode = visitImpl(nodeFactory.create(parent: node, raw: child, absoluteInfo: info)) - if childNode.raw.id != child.id { + let childNode = visitImpl(Syntax(arena: node.arena, dataRef: childDataRef)) + if childNode.raw.id != childDataRef.pointee.raw.id { // The node was rewritten, let's handle it if newLayout.baseAddress == nil { @@ -345,13 +338,10 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { } // Update the rewritten child. - newLayout[Int(info.indexInParent)] = childNode.raw + newLayout[Int(childDataRef.pointee.absoluteInfo.layoutIndexInParent)] = childNode.raw // Retain the syntax arena of the new node until it's wrapped with Syntax node. rewrittens.append(childNode.raw.arenaReference.retained) } - - // Recycle 'childNode.info' - nodeFactory.dispose(&childNode) } if newLayout.baseAddress != nil { diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxVisitorFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxVisitorFile.swift index 430feaa6393..00dac528f50 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxVisitorFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxVisitorFile.swift @@ -32,13 +32,6 @@ let syntaxVisitorFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { try! ClassDeclSyntax("open class SyntaxVisitor") { DeclSyntax("public let viewMode: SyntaxTreeViewMode") - DeclSyntax( - """ - /// 'Syntax' object factory recycling 'Syntax.Info' instances. - private let nodeFactory: SyntaxNodeFactory = SyntaxNodeFactory() - """ - ) - DeclSyntax( """ public init(viewMode: SyntaxTreeViewMode) { @@ -221,10 +214,8 @@ let syntaxVisitorFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { DeclSyntax( """ private func visitChildren(_ node: Syntax) { - for case let (child?, info) in RawSyntaxChildren(node) where viewMode.shouldTraverse(node: child) { - var childNode = nodeFactory.create(parent: node, raw: child, absoluteInfo: info) - dispatchVisit(childNode) - nodeFactory.dispose(&childNode) + for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) { + dispatchVisit(Syntax(arena: node.arena, dataRef: childDataRef)) } } """ diff --git a/Release Notes/602.md b/Release Notes/602.md index 92e173fbb08..e4a072d5f0d 100644 --- a/Release Notes/602.md +++ b/Release Notes/602.md @@ -20,6 +20,12 @@ - Migration steps: Do not use `SyntaxArena` or `ParsingSyntaxArena` directly. - Notes: Although the type itself was `public`, most initializers were already SPI and there was no way to retrive them from existing types via public API. +- `SyntaxChildrenIndex` is no longer `ExpressibleByNilLiteral` + - 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. + - Pull Request: https://github.com/swiftlang/swift-syntax/pull/2925 + - Migration steps: Use `SyntaxChildren.endIndex` instead. + - 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. + ## Template - *Affected API or two word description* diff --git a/Sources/SwiftSyntax/AbsoluteRawSyntax.swift b/Sources/SwiftSyntax/AbsoluteRawSyntax.swift deleted file mode 100644 index a3f5aec25e7..00000000000 --- a/Sources/SwiftSyntax/AbsoluteRawSyntax.swift +++ /dev/null @@ -1,47 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -struct AbsoluteRawSyntax: Sendable { - let raw: RawSyntax - let info: AbsoluteSyntaxInfo - - /// Returns first `present` child. - func firstChild(viewMode: SyntaxTreeViewMode) -> AbsoluteRawSyntax? { - guard let layoutView = raw.layoutView else { return nil } - var curInfo = info.advancedToFirstChild() - for childOpt in layoutView.children { - if let child = childOpt, viewMode.shouldTraverse(node: child) { - return AbsoluteRawSyntax(raw: child, info: curInfo) - } - curInfo = curInfo.advancedBySibling(childOpt) - } - return nil - } - - /// Returns next `present` sibling. - func nextSibling(parent: AbsoluteRawSyntax, viewMode: SyntaxTreeViewMode) -> AbsoluteRawSyntax? { - var curInfo = info.advancedBySibling(raw) - for siblingOpt in parent.raw.layoutView!.children.dropFirst(Int(info.indexInParent + 1)) { - if let sibling = siblingOpt, viewMode.shouldTraverse(node: sibling) { - return AbsoluteRawSyntax(raw: sibling, info: curInfo) - } - curInfo = curInfo.advancedBySibling(siblingOpt) - } - return nil - } - - func replacingSelf(_ newRaw: RawSyntax, newRootId: UInt) -> AbsoluteRawSyntax { - let nodeId = SyntaxIdentifier(rootId: newRootId, indexInTree: info.nodeId.indexInTree) - let newInfo = AbsoluteSyntaxInfo(position: info.position, nodeId: nodeId) - return .init(raw: newRaw, info: newInfo) - } -} diff --git a/Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift b/Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift index 0d281f63a8a..8f21dfd9d11 100644 --- a/Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift +++ b/Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -10,49 +10,52 @@ // //===----------------------------------------------------------------------===// -struct AbsoluteSyntaxPosition: Sendable { - /// The UTF-8 offset of the syntax node in the source file - let offset: UInt32 - let indexInParent: UInt32 - - func advancedBySibling(_ raw: RawSyntax?) -> AbsoluteSyntaxPosition { - let newOffset = self.offset + UInt32(truncatingIfNeeded: raw?.totalLength.utf8Length ?? 0) - let newIndexInParent = self.indexInParent + 1 - return .init(offset: newOffset, indexInParent: newIndexInParent) - } - - func advancedToFirstChild() -> AbsoluteSyntaxPosition { - return .init(offset: self.offset, indexInParent: 0) - } - - static var forRoot: AbsoluteSyntaxPosition { - return .init(offset: 0, indexInParent: 0) - } -} - /// `AbsoluteSyntaxInfo` represents the information that relates a `RawSyntax` /// to a source file tree, like its absolute source offset. struct AbsoluteSyntaxInfo: Sendable { - let position: AbsoluteSyntaxPosition - let nodeId: SyntaxIdentifier + /// The UTF-8 offset at which the syntax node’s leading trivia start in the source file. + let offset: UInt32 + + /// Index in parent's layout. Note that this counts `nil` children. + let layoutIndexInParent: UInt32 - /// The UTF-8 offset of the syntax node in the source file - var offset: UInt32 { return position.offset } - var indexInParent: UInt32 { return position.indexInParent } + /// Index of the node when traversing the syntax tree using a depth-first traversal. + /// This skips `nil` children in the parent's layout. + let indexInTree: UInt32 func advancedBySibling(_ raw: RawSyntax?) -> AbsoluteSyntaxInfo { - let newPosition = position.advancedBySibling(raw) - let newNodeId = nodeId.advancedBySibling(raw) - return .init(position: newPosition, nodeId: newNodeId) + if let raw { + // '&+' operations are safe because we have the preconditions in 'forRoot(_:)'. + return AbsoluteSyntaxInfo( + offset: offset &+ UInt32(truncatingIfNeeded: raw.totalLength.utf8Length), + layoutIndexInParent: layoutIndexInParent &+ 1, + indexInTree: indexInTree &+ UInt32(truncatingIfNeeded: raw.totalNodes) + ) + } else { + return AbsoluteSyntaxInfo( + offset: offset, + layoutIndexInParent: layoutIndexInParent &+ 1, + indexInTree: indexInTree + ) + } } func advancedToFirstChild() -> AbsoluteSyntaxInfo { - let newPosition = position.advancedToFirstChild() - let newNodeId = nodeId.advancedToFirstChild() - return .init(position: newPosition, nodeId: newNodeId) + return AbsoluteSyntaxInfo( + offset: offset, + layoutIndexInParent: 0, + indexInTree: indexInTree &+ 1 + ) } static func forRoot(_ raw: RawSyntax) -> AbsoluteSyntaxInfo { - return .init(position: .forRoot, nodeId: .forRoot(raw)) + // These checks ensure the safety of the unchecked arithmetic operations in 'advancedBySibling(_:)'. + precondition(raw.totalLength.utf8Length <= UInt32.max, "too long") + precondition(raw.totalNodes <= UInt32.max, "too many nodes") + return AbsoluteSyntaxInfo( + offset: 0, + layoutIndexInParent: 0, + indexInTree: 0 + ) } } diff --git a/Sources/SwiftSyntax/CMakeLists.txt b/Sources/SwiftSyntax/CMakeLists.txt index dd77da30625..8001de25c4e 100644 --- a/Sources/SwiftSyntax/CMakeLists.txt +++ b/Sources/SwiftSyntax/CMakeLists.txt @@ -8,7 +8,6 @@ add_swift_syntax_library(SwiftSyntax AbsolutePosition.swift - AbsoluteRawSyntax.swift AbsoluteSyntaxInfo.swift Assert.swift BumpPtrAllocator.swift @@ -31,7 +30,6 @@ add_swift_syntax_library(SwiftSyntax SyntaxCollection.swift SyntaxHashable.swift SyntaxIdentifier.swift - SyntaxNodeFactory.swift SyntaxNodeStructure.swift SyntaxProtocol.swift SyntaxText.swift diff --git a/Sources/SwiftSyntax/MemoryLayout.swift b/Sources/SwiftSyntax/MemoryLayout.swift index e4666e52dd1..89e35bc1109 100644 --- a/Sources/SwiftSyntax/MemoryLayout.swift +++ b/Sources/SwiftSyntax/MemoryLayout.swift @@ -13,9 +13,9 @@ // See `MemoryLayoutTest.swift`. @_spi(Testing) public enum SyntaxMemoryLayout: Sendable { public struct Value: Equatable, Sendable { - var size: Int - var stride: Int - var alignment: Int + public let size: Int + public let stride: Int + public let alignment: Int public init(size: Int, stride: Int, alignment: Int) { self.size = size diff --git a/Sources/SwiftSyntax/Raw/RawSyntax.swift b/Sources/SwiftSyntax/Raw/RawSyntax.swift index d271f5ea7bc..5d24a782003 100644 --- a/Sources/SwiftSyntax/Raw/RawSyntax.swift +++ b/Sources/SwiftSyntax/Raw/RawSyntax.swift @@ -186,10 +186,10 @@ extension RawSyntaxData.ParsedToken { extension RawSyntaxData.MaterializedToken { var leadingTrivia: RawTriviaPieceBuffer { - triviaPieces[..=6) +@_implementationOnly private import _SwiftSyntaxCShims +#else +@_implementationOnly import _SwiftSyntaxCShims +#endif + +// `Syntax` is a user facing tree wrapping `RawSyntax` tree. A value is a pair +// of strong reference to the `SyntaxDataArena` and a pointer to the `SyntaxData` +// allocated in the arena. +// The arena is shared between the all node in the tree, but only in the tree. +// Whenever the tree is modified, a new arena is created and it forms a new "tree". + /// A Syntax node represents a tree of nodes with tokens at the leaves. /// Each node has accessors for its known children, and allows efficient /// iteration over the children through its `children` property. public struct Syntax: SyntaxProtocol, SyntaxHashable { - /// We need a heap indirection to store a syntax node's parent. We could use an indirect enum here but explicitly - /// modelling it using a class allows us to re-use these heap-allocated objects in `SyntaxVisitor`. - /// - /// - Note: `@unchecked Sendable` because `info` is mutable. In Swift 6 and above the variable can be declared as - /// `nonisolated(unsafe)` but that attribute doesn't exist in previous Swift versions and a checked Sendable - /// conformance generates a warning. - final class Info: @unchecked Sendable { - // For root node. - struct Root: Sendable { - private var arena: RetainedSyntaxArena - - init(arena: RetainedSyntaxArena) { - self.arena = arena - } - } - - // For non-root nodes. - struct NonRoot: Sendable { - var parent: Syntax - var absoluteInfo: AbsoluteSyntaxInfo - } + let arena: SyntaxDataArena + let dataRef: SyntaxDataReference - enum InfoImpl: Sendable { - case root(Root) - case nonRoot(NonRoot) - } - - init(_ info: InfoImpl) { - self.info = info - } - - /// The actual stored information that references the parent or the tree's root. - /// - /// - Important: Must only be set to `nil` when `Syntax.Info` is used in a memory recycling pool - /// (eg. in `SyntaxVisitor`). In that case the `Syntax.Info` is considered garbage memory that can be re-used - /// later. `info` needs to be set to a real value when `Syntax.Info` is recycled from the memory recycling pool. - #if compiler(>=6.0) - nonisolated(unsafe) var info: InfoImpl! - #else - var info: InfoImpl! - #endif + /// "designated" memberwise initializer of `Syntax`. + @_transparent // Inline early to enable certain optimzation. + init(arena: __shared SyntaxDataArena, dataRef: SyntaxDataReference) { + self.arena = arena + self.dataRef = dataRef } - /// Reference to the node's parent or, if this node is the root of a tree, a reference to the `SyntaxArena` to keep - /// the syntax tree alive. - /// - /// - Important: In almost all use cases you should not access this directly. Prefer accessors like `parent`. - /// - Important: Must only be set to `nil` when this `Syntax` node is known to get destroyed and the `Info` should be - /// stored in a memory recycling pool (eg. in `SyntaxVisitor`). After setting `info` to `nil`, this `Syntax` node - /// is considered garbage and should not be accessed anymore in any way. - var info: Info! - let raw: RawSyntax - - private var rootInfo: Info.Root { - switch info.info! { - case .root(let info): return info - case .nonRoot(let info): return info.parent.rootInfo - } + private var data: SyntaxData { + @_transparent unsafeAddress { dataRef.pointer } } - private var nonRootInfo: Info.NonRoot? { - switch info.info! { - case .root(_): return nil - case .nonRoot(let info): return info - } + @inline(__always) + var raw: RawSyntax { + data.raw } - public var root: Syntax { - return self.withUnownedSyntax { - var node = $0 - while let parent = node.parent { - node = parent - } - return node.value - } + @inline(__always) + public var root: Self { + return Self(arena: arena, dataRef: arena.root) } + @inline(__always) public var parent: Syntax? { - nonRootInfo?.parent + guard let parentDataRef = data.parent else { + return nil + } + return Syntax(arena: arena, dataRef: parentDataRef) } - var absoluteInfo: AbsoluteSyntaxInfo { - nonRootInfo?.absoluteInfo ?? .forRoot(raw) + @inline(__always) + public var hasParent: Bool { + data.parent != nil } - var absoluteRaw: AbsoluteRawSyntax { - AbsoluteRawSyntax(raw: raw, info: absoluteInfo) + var absoluteInfo: AbsoluteSyntaxInfo { + data.absoluteInfo } - var indexInParent: Int { - Int(absoluteInfo.indexInParent) + /// Index in parent's layout. `nil` slots are counted. + var layoutIndexInParent: Int { + Int(absoluteInfo.layoutIndexInParent) } public var id: SyntaxIdentifier { - absoluteInfo.nodeId + SyntaxIdentifier( + rootId: UInt(rawID: arena.root.pointee.raw.id), + indexInTree: SyntaxIdentifier.SyntaxIndexInTree(indexInTree: absoluteInfo.indexInTree) + ) } /// The position of the start of this node's leading trivia @@ -131,27 +99,6 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { position + raw.totalLength } - /// "designated" memberwise initializer of `Syntax`. - // transparent because normal inlining is too late for eliminating ARC traffic for Info. - // FIXME: Remove @_transparent after OSSA enabled. - @_transparent - init(_ raw: RawSyntax, info: __shared Info) { - self.raw = raw - self.info = info - } - - init(_ raw: RawSyntax, parent: Syntax, absoluteInfo: AbsoluteSyntaxInfo) { - self.init(raw, info: Info(.nonRoot(.init(parent: parent, absoluteInfo: absoluteInfo)))) - } - - /// Creates a `Syntax` with the provided raw syntax and parent. - /// - Parameters: - /// - absoluteRaw: The underlying `AbsoluteRawSyntax` of this node. - /// - parent: The parent of this node, or `nil` if this node is the root. - init(_ absoluteRaw: AbsoluteRawSyntax, parent: Syntax) { - self.init(absoluteRaw.raw, parent: parent, absoluteInfo: absoluteRaw.info) - } - /// Creates a ``Syntax`` for a root raw node. /// /// - Parameters: @@ -161,30 +108,32 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { /// has a chance to retain it. static func forRoot(_ raw: RawSyntax, rawNodeArena: RetainedSyntaxArena) -> Syntax { precondition(rawNodeArena == raw.arenaReference) - return Syntax(raw, info: Info(.root(.init(arena: rawNodeArena)))) + let arena = SyntaxDataArena(raw: raw, rawNodeArena: rawNodeArena) + return Self(arena: arena, dataRef: arena.root) } static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> Syntax { - precondition(rawNodeArena == raw.arenaReference) - return Syntax(raw, info: Info(.root(.init(arena: RetainedSyntaxArena(rawNodeArena))))) + return forRoot(raw, rawNodeArena: RetainedSyntaxArena(rawNodeArena)) + } + + /// References to the children data. + /// + /// - Note: The buffer is managed by the arena, and thus only valid while the arena is alive. + var layoutBuffer: SyntaxDataReferenceBuffer { + self.arena.layout(for: self.dataRef) } - /// Returns the child data at the provided index in this data's layout. - /// - Note: This has O(n) performance, prefer using a proper Sequence type - /// if applicable, instead of this. - /// - Note: This function traps if the index is out of the bounds of the - /// data's layout. + /// Returns the child node at the provided index in this node's layout. + /// - Note: This function traps if the node is a token, or the index is out of the bounds of the layout. /// /// - Parameter index: The index to create and cache. - /// - Parameter parent: The parent to associate the child with. This is - /// normally the Syntax node that this `Syntax` belongs to. - /// - Returns: The child's data at the provided index. + /// - Returns: The child's node at the provided index. func child(at index: Int) -> Syntax? { - if raw.layoutView!.children[index] == nil { return nil } - var iter = RawSyntaxChildren(absoluteRaw).makeIterator() - for _ in 0..(mapping map: (Syntax) -> T?) -> T? { + var dataRef = self.dataRef + while true { + if let mapped = map(Syntax(arena: self.arena, dataRef: dataRef)) { + return mapped + } + guard let parent = dataRef.pointee.parent else { + return nil + } + dataRef = parent + } + } + /// Needed for the conformance to ``SyntaxProtocol``. Just returns `self`. public var _syntaxNode: Syntax { return self @@ -372,56 +332,184 @@ extension Syntax { } } -/// Temporary non-owning Syntax. +typealias SyntaxDataReference = SyntaxArenaAllocatedPointer +typealias SyntaxDataReferenceBuffer = SyntaxArenaAllocatedBufferPointer + +/// Node data for a `Syntax`, allocated and managed by `SyntaxDataArena`. /// -/// This can be used for handling Syntax node without ARC traffic. -struct UnownedSyntax { - private let raw: RawSyntax - private let info: Unmanaged +/// - Note: This type must be trivial as it is allocated by ‘BumpPtrAllocator’. +struct SyntaxData: Sendable { + /// Underlying node of the `Syntax` node. + let raw: RawSyntax + /// Reference to the parent data. Or `nil` if this is the root. + let parent: SyntaxDataReference? + /// Index and position info in the tree. + let absoluteInfo: AbsoluteSyntaxInfo + /// Length of the children layout. + /// This is a cached information, equals to `raw.layoutView?.children.count ?? 0`. + /// This 4 bytes fits nicely after the 12 bytes `absoluteInfo`. + let childCount: UInt32 + + // If the childCount > 0, a pointer to the layout buffer (`UnsafePointer?`) is tail allocated. +} - @_transparent - init(_ node: __shared Syntax) { - self.raw = node.raw - self.info = .passUnretained(node.info.unsafelyUnwrapped) +/// `SyntaxDataArena` manages the entire data of a `Syntax` tree. +final class SyntaxDataArena: @unchecked Sendable { + /// Mutex for locking the data when populating layout buffers. + private let mutex: PlatformMutex + + /// Allocator. + private let allocator: BumpPtrAllocator + + /// Retaining reference to the arena of the _root_ ``RawSyntax`` + private let rawArena: RetainedSyntaxArena + + /// Root node. + let root: SyntaxDataReference + + init(raw: RawSyntax, rawNodeArena: RetainedSyntaxArena) { + precondition(rawNodeArena == raw.arenaReference) + + self.mutex = PlatformMutex.create() + self.allocator = BumpPtrAllocator(initialSlabSize: Self.slabSize(for: raw)) + self.rawArena = rawNodeArena + self.root = Self.createDataImpl(allocator: allocator, raw: raw, parent: nil, absoluteInfo: .forRoot(raw)) } - /// Extract the Syntax value. - @inline(__always) - var value: Syntax { - Syntax(raw, info: info.takeUnretainedValue()) + deinit { + // Debug print for re-evaluating `slabSize(for:)` + // print("nodeCount: \(root.pointee.raw.totalNodes), slabSize: \(Self.slabSize(for: root.pointee.raw)), allocated: \(allocator.totalByteSizeAllocated), overflowed: \(Self.slabSize(for: root.pointee.raw) < allocator.totalByteSizeAllocated)") + self.mutex.destroy() } - /// Get the parent of the Syntax value, but without retaining it. - @inline(__always) - var parent: UnownedSyntax? { - return info._withUnsafeGuaranteedRef { - switch $0.info.unsafelyUnwrapped { - case .nonRoot(let info): - return UnownedSyntax(info.parent) - case .root(_): - return nil - } + /// Return the childen data of the given node. + /// + /// The layout buffer is created and cached when it's first accessed. + func layout(for parent: SyntaxDataReference) -> SyntaxDataReferenceBuffer { + let childCount = Int(truncatingIfNeeded: parent.pointee.childCount) + + // Return empty buffer for the node with no children. + guard childCount != 0 else { + return SyntaxDataReferenceBuffer() } + + // The storage of the buffer address is allocated next to the SyntaxData. + let baseAddressRef = parent.advanced(by: 1) + .unsafeRawPointer + .assumingMemoryBound(to: AtomicPointer.self) + + // If the buffer is already populated, return it. + if let baseAddress = swiftsyntax_atomic_pointer_get(baseAddressRef)?.assumingMemoryBound( + to: SyntaxDataReference?.self + ) { + return SyntaxDataReferenceBuffer(UnsafeBufferPointer(start: baseAddress, count: childCount)) + } + + mutex.lock() + defer { mutex.unlock() } + + // Recheck, maybe some other thread has populated the buffer during acquiring the lock. + if let baseAddress = swiftsyntax_atomic_pointer_get(baseAddressRef)?.assumingMemoryBound( + to: SyntaxDataReference?.self + ) { + return SyntaxDataReferenceBuffer(UnsafeBufferPointer(start: baseAddress, count: childCount)) + } + + let buffer = createLayoutDataImpl(parent) + assert(buffer.count == childCount, "childCount doesn't match with RawSyntax layout length?") + // Remeber the base address of the created buffer. + swiftsyntax_atomic_pointer_set( + UnsafeMutablePointer(mutating: baseAddressRef), + UnsafeRawPointer(buffer.baseAddress) + ) + + return SyntaxDataReferenceBuffer(buffer) } - /// Temporarily use the Syntax value. - @inline(__always) - func withValue(_ body: (Syntax) -> T) -> T { - info._withUnsafeGuaranteedRef { - body(Syntax(self.raw, info: $0)) + /// Create the layout buffer of the node. + private func createLayoutDataImpl(_ parent: SyntaxDataReference) -> UnsafeBufferPointer { + let rawChildren = parent.pointee.raw.layoutView!.children + let allocated = self.allocator.allocate(SyntaxDataReference?.self, count: rawChildren.count) + + var ptr = allocated.baseAddress! + var absoluteInfo = parent.pointee.absoluteInfo.advancedToFirstChild() + for raw in rawChildren { + let dataRef: SyntaxDataReference? + if let raw { + dataRef = Self.createDataImpl(allocator: self.allocator, raw: raw, parent: parent, absoluteInfo: absoluteInfo) + } else { + dataRef = nil + } + ptr.initialize(to: dataRef) + absoluteInfo = absoluteInfo.advancedBySibling(raw) + ptr += 1 } + return UnsafeBufferPointer(allocated) } -} -extension SyntaxProtocol { - /// Execute the `body` with ``UnownedSyntax`` of `node`. + /// Calculate the recommended slab size of `BumpPtrAllocator`. /// - /// This guarantees the life time of the `node` during the `body` is executed. - @inline(__always) - func withUnownedSyntax(_ body: (UnownedSyntax) -> T) -> T { - return withExtendedLifetime(self) { - body(UnownedSyntax(Syntax($0))) + /// Estimate the total allocation size assuming the client visits every node in + /// the tree. Return the estimated size, or 4096 if it's larger than 4096. + /// + /// Each node consumes `SyntaxData` size at least. Non-empty layout node tail + /// allocates a pointer storage for the base address of the layout buffer. + /// + /// For layout buffers, each child element consumes a `SyntaxDataReference` in + /// the parent's layout. But non-collection layout nodes, the layout is usually + /// sparse, so we can't calculate the exact memory size until we see the RawSyntax. + /// That being said, `SytnaxData` + 4 pointer size looks like an enough estimation. + private static func slabSize(for raw: RawSyntax) -> Int { + let dataSize = MemoryLayout.stride + let pointerSize = MemoryLayout.stride + + let nodeCount = raw.totalNodes + assert(nodeCount != 0, "The tree needs to contain at least the root node") + let totalSize = dataSize + (dataSize + pointerSize * 4) * (nodeCount &- 1) + // Power of 2 might look nicer, but 'BumpPtrAllocator' doesn't require that. + return min(totalSize, 4096) + } + + /// Allocate and initialize `SyntaxData` with the trailing pointer storage for the references to the children. + private static func createDataImpl( + allocator: BumpPtrAllocator, + raw: RawSyntax, + parent: SyntaxDataReference?, + absoluteInfo: AbsoluteSyntaxInfo + ) -> SyntaxDataReference { + let childCount = raw.layoutView?.children.count ?? 0 + + // Allocate 'SyntaxData' + storage for the reference to the children data. + // NOTE: If you change the memory layout, revisit 'slabSize(for:)' too. + var totalSize = MemoryLayout.stride + if childCount != 0 { + // Tail allocate the storage for the pointer to the lazily allocated layout data. + totalSize &+= MemoryLayout.size } + let alignment = MemoryLayout.alignment + let allocated = allocator.allocate(byteCount: totalSize, alignment: alignment).baseAddress! + + // Initialize the data. + let dataRef = allocated.bindMemory(to: SyntaxData.self, capacity: 1) + dataRef.initialize( + to: SyntaxData( + raw: raw, + parent: parent, + absoluteInfo: absoluteInfo, + childCount: UInt32(truncatingIfNeeded: childCount) + ) + ) + + if childCount != 0 { + // Initialize the tail allocated storage with 'nil'. + let layoutBufferBaseAddressRef = + allocated + .advanced(by: MemoryLayout.stride) + .bindMemory(to: AtomicPointer.self, capacity: 1) + swiftsyntax_atomic_pointer_set(layoutBufferBaseAddressRef, nil) + } + + return SyntaxDataReference(UnsafePointer(dataRef)) } } @@ -433,7 +521,8 @@ public struct SyntaxNode {} /// See `SyntaxMemoryLayout`. let SyntaxMemoryLayouts: [String: SyntaxMemoryLayout.Value] = [ "Syntax": .init(Syntax.self), - "Syntax.Info": .init(Syntax.Info.self), - "Syntax.Info.Root": .init(Syntax.Info.Root.self), - "Syntax.Info.NonRoot": .init(Syntax.Info.NonRoot.self), + "SyntaxData": .init(SyntaxData.self), + "AbsoluteSyntaxInfo": .init(AbsoluteSyntaxInfo.self), + "SyntaxDataReference?": .init(SyntaxDataReference?.self), + "AtomicPointer": .init(AtomicPointer.self), ] diff --git a/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift b/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift index d2dd2412b6d..3a2fbfe6342 100644 --- a/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift +++ b/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift @@ -27,14 +27,18 @@ public struct SyntaxArenaAllocatedPointer: @unchecked Sendabl /// - Important: The client needs to ensure sure that the buffer is indeed /// allocated by a ``SyntaxArena`` and that the ``SyntaxArena`` will outlive /// any users of this ``SyntaxArenaAllocatedBufferPointer``. - init(_ buffer: UnsafePointer) { - self.pointer = buffer + init(_ pointer: UnsafePointer) { + self.pointer = pointer } var pointee: Element { @_transparent unsafeAddress { pointer } } + func advanced(by n: Int) -> Self { + Self(pointer.advanced(by: n)) + } + var unsafeRawPointer: UnsafeRawPointer { return UnsafeRawPointer(pointer) } @@ -66,10 +70,12 @@ public struct SyntaxArenaAllocatedBufferPointer: RandomAccess self.buffer = buffer } - public subscript>( - range: RangeType - ) -> SyntaxArenaAllocatedBufferPointer { - return SyntaxArenaAllocatedBufferPointer(UnsafeBufferPointer(rebasing: self.buffer[range])) + /// Create a buffer over the same memory as the given buffer slice. + public init(rebasing slice: Self.SubSequence) { + self.buffer = UnsafeBufferPointer( + start: slice.base.baseAddress?.advanced(by: slice.startIndex), + count: slice.count + ) } public subscript(_ index: Int) -> Element { diff --git a/Sources/SwiftSyntax/SyntaxChildren.swift b/Sources/SwiftSyntax/SyntaxChildren.swift index 5457797679a..e02bdb4fda9 100644 --- a/Sources/SwiftSyntax/SyntaxChildren.swift +++ b/Sources/SwiftSyntax/SyntaxChildren.swift @@ -12,415 +12,23 @@ // MARK: - Index -/// The data for an index in a syntax children collection that is not the end -/// index. See ``SyntaxChildrenIndex`` for the representation of the end index. -struct SyntaxChildrenIndexData: Hashable, Comparable, Sendable { - /// The UTF-8 offset of the item at this index in the source file - /// See `AbsoluteSyntaxPosition.offset` - let offset: UInt32 - /// The index of the node in the parent. - /// See `AbsoluteSyntaxPosition.indexInParent` - let indexInParent: UInt32 - /// Unique value for a node within its own tree. - /// See ``SyntaxIdentifier/indexInTree`` - let indexInTree: SyntaxIdentifier.SyntaxIndexInTree - - static func < ( - lhs: SyntaxChildrenIndexData, - rhs: SyntaxChildrenIndexData - ) -> Bool { - return lhs.indexInParent < rhs.indexInParent - } - - fileprivate init( - offset: UInt32, - indexInParent: UInt32, - indexInTree: SyntaxIdentifier.SyntaxIndexInTree - ) { - self.offset = offset - self.indexInParent = indexInParent - self.indexInTree = indexInTree - } - - init(_ absoluteSyntaxInfo: AbsoluteSyntaxInfo) { - self.offset = absoluteSyntaxInfo.offset - self.indexInParent = absoluteSyntaxInfo.indexInParent - self.indexInTree = absoluteSyntaxInfo.nodeId.indexInTree - } -} - /// An index in a syntax children collection. -public struct SyntaxChildrenIndex: Hashable, Comparable, ExpressibleByNilLiteral, Sendable { - /// Construct the `endIndex` of a ``SyntaxChildren`` collection. - public init(nilLiteral: ()) { - self.data = nil - } - - // Performance considerations: - // It is faster to use a special end index than computing a materialized index - // that stores past the end of the collection if forward iteration is the only - // thing that's needed. - - /// `nil` represents the end index and `.some` represents a materialized index - /// that points into a collection. - /// It is faster to use `Optional` here rather than making - /// `SyntaxChildrenIndex` an enum because the optional value can be - /// force-unwrapped when we know that an index is not the end index, saving a - /// switch case comparison. - let data: SyntaxChildrenIndexData? - - fileprivate init( - offset: UInt32, - indexInParent: UInt32, - indexInTree: SyntaxIdentifier.SyntaxIndexInTree - ) { - self.data = SyntaxChildrenIndexData( - offset: offset, - indexInParent: indexInParent, - indexInTree: indexInTree - ) - } +public struct SyntaxChildrenIndex: Hashable, Comparable, Sendable { + /// The index in a `SyntaxDataReferenceBuffer` that is being referenced by this `SyntaxChildrenIndex`. + let value: Int - internal init(_ absoluteSyntaxInfo: AbsoluteSyntaxInfo) { - self.data = SyntaxChildrenIndexData(absoluteSyntaxInfo) + init(value: Int) { + self.value = value } /// Returns `true` if `lhs` occurs before `rhs`. public static func < (lhs: SyntaxChildrenIndex, rhs: SyntaxChildrenIndex) -> Bool { - switch (lhs.data, rhs.data) { - case (.some(let lhsData), .some(let rhsData)): - return lhsData < rhsData - case (.some(_), .none): - return true - case (.none, .some(_)): - return false - case (.none, .none): - return false - } - } -} - -fileprivate extension AbsoluteSyntaxInfo { - /// Construct `AbsoluteSyntaxInfo` from the given index data and a `rootId`. - init(index: SyntaxChildrenIndexData, rootId: UInt) { - let position = AbsoluteSyntaxPosition( - offset: index.offset, - indexInParent: index.indexInParent - ) - let identifier = SyntaxIdentifier( - rootId: rootId, - indexInTree: index.indexInTree - ) - self.init(position: position, nodeId: identifier) + lhs.value < rhs.value } } // MARK: - Collections -/// Collection that contains the child nodes of a raw node (including missing -/// nodes), along with their associated `AbsoluteSyntaxInfo`. -struct RawSyntaxChildren: BidirectionalCollection, Sendable { - typealias Element = (raw: RawSyntax?, syntaxInfo: AbsoluteSyntaxInfo) - typealias Index = SyntaxChildrenIndex - - struct Iterator: IteratorProtocol { - let collection: RawSyntaxChildren - var nextIndex: SyntaxChildrenIndex - - init(collection: RawSyntaxChildren) { - self.collection = collection - self.nextIndex = collection.startIndex - } - - mutating func next() -> RawSyntaxChildren.Element? { - guard nextIndex != collection.endIndex else { - return nil - } - // Re-use the fetched child to compute the next index, eliminating one - // access to the underlying collection - let child = collection[nextIndex] - nextIndex = collection.index(nextIndex, advancedBy: child.raw) - return child - } - } - - /// The node whose children shall be accessed - private let parent: RawSyntax - - private var parentLayoutView: RawSyntaxLayoutView { - return parent.layoutView! - } - - /// The rootId of the tree the child nodes belong to - private let rootId: UInt - - /// The number of children in `parent`. Cached to avoid reaching into `parent` for every index - /// advancement - private let numberOfChildren: Int - - let startIndex: SyntaxChildrenIndex - var endIndex: SyntaxChildrenIndex { - return nil - } - - func makeIterator() -> Iterator { - return Iterator(collection: self) - } - - /// Advance the given index by the given ``RawSyntax`` node. - func index(_ index: Index, advancedBy node: RawSyntax?) -> Index { - // We can assume a non-end index since advancing the end index is undefined - // behaviour. - let index = index.data! - - if index.indexInParent + 1 < numberOfChildren { - // Compute the next materialized index - let nodeLength = UInt32(node?.totalLength.utf8Length ?? 0) - let advancedIndexInTree = index.indexInTree.advancedBy(node) - return SyntaxChildrenIndex( - offset: index.offset + nodeLength, - indexInParent: index.indexInParent + 1, - indexInTree: advancedIndexInTree - ) - } else { - // We have reached the end of the collection. Return the end index. - return nil - } - } - - func index(after index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - guard let indexData = index.data else { - preconditionFailure("Cannot get the index after the end index") - } - let node = parent.layoutView!.children[Int(indexData.indexInParent)] - return self.index(index, advancedBy: node) - } - - func index(before index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - if let index = index.data { - // We are reversing a non-end index. - let previousNode = parent.layoutView!.children[Int(index.indexInParent - 1)] - let previousNodeLength = UInt32(previousNode?.totalLength.utf8Length ?? 0) - let reversedIndexInTree = index.indexInTree.reversedBy(previousNode) - return SyntaxChildrenIndex( - offset: index.offset - previousNodeLength, - indexInParent: index.indexInParent - 1, - indexInTree: reversedIndexInTree - ) - } else { - // We need to reverse the end index. For this we need to compute a - // materialized version of the end index that points one past the end of - // the collection. After we have that materialized index, we can reverse - // it using the above logic. - - // If the start index is nil, then the collection is empty and we are - // reversing before the start index. That is undefined behaviour, so we - // can assume a non-end index. - let startIndex = self.startIndex.data! - - // Compute a materialized index. - let offset = startIndex.offset + UInt32(parent.totalLength.utf8Length) - let indexInParent = startIndex.indexInParent + UInt32(parentLayoutView.children.count) - let indexInTree = startIndex.indexInTree.indexInTree + UInt32(parent.totalNodes) - 1 - let syntaxIndexInTree = SyntaxIdentifier.SyntaxIndexInTree(indexInTree: indexInTree) - let materialized = SyntaxChildrenIndex( - offset: offset, - indexInParent: indexInParent, - indexInTree: syntaxIndexInTree - ) - - // Reverse index based on the above logic - return self.index(before: materialized) - } - } - - func distance(from start: SyntaxChildrenIndex, to end: SyntaxChildrenIndex) -> Int { - switch (start.data, end.data) { - case (.some(let start), .some(let end)): - return Int(end.indexInParent - start.indexInParent) - case (.some(let start), .none): - return parentLayoutView.children.count - Int(start.indexInParent) - case (.none, .some(let end)): - return Int(end.indexInParent) - parentLayoutView.children.count - case (.none, .none): - return 0 - } - } - - subscript(index: SyntaxChildrenIndex) -> (raw: RawSyntax?, syntaxInfo: AbsoluteSyntaxInfo) { - // Accessing the end index is undefined. So we can assume a non-end index. - let index = index.data! - - let child = parent.layoutView!.children[Int(index.indexInParent)] - let info = AbsoluteSyntaxInfo(index: index, rootId: rootId) - return (child, info) - } - - init(_ parent: AbsoluteRawSyntax) { - self.parent = parent.raw - self.rootId = parent.info.nodeId.rootId - switch parent.raw.view { - case .layout(let layoutView): - self.numberOfChildren = layoutView.children.count - case .token: - self.numberOfChildren = 0 - } - - if self.numberOfChildren == 0 { - self.startIndex = nil - } else { - let startPosition = parent.info.advancedToFirstChild() - self.startIndex = SyntaxChildrenIndex(startPosition) - } - } - - init(_ base: __shared Syntax) { - self.init(base.absoluteRaw) - } -} - -/// Collection that contains the `present` child nodes of an -/// `AbsoluteRawSyntax` node. -struct NonNilRawSyntaxChildren: BidirectionalCollection, Sendable { - typealias Element = AbsoluteRawSyntax - typealias Index = SyntaxChildrenIndex - - struct Iterator: IteratorProtocol { - var iterator: RawSyntaxChildren.Iterator - let viewMode: SyntaxTreeViewMode - - init(allChildren: RawSyntaxChildren, viewMode: SyntaxTreeViewMode) { - self.iterator = allChildren.makeIterator() - self.viewMode = viewMode - } - - mutating func next() -> AbsoluteRawSyntax? { - // Advance the underlying RawSyntaxChildren.Iterator until we find a - // present node. - while true { - guard let (node, info) = self.iterator.next() else { - return nil - } - if let node, viewMode.shouldTraverse(node: node) { - return AbsoluteRawSyntax(raw: node, info: info) - } - } - } - } - - let viewMode: SyntaxTreeViewMode - - /// The underlying collection which contains all children. The present - /// children are filtered from this collection. - private var allChildren: RawSyntaxChildren - - let startIndex: SyntaxChildrenIndex - var endIndex: SyntaxChildrenIndex { - return allChildren.endIndex - } - - func makeIterator() -> Iterator { - return Iterator(allChildren: allChildren, viewMode: viewMode) - } - - /// Advances the index to the next present node in the given collection. If - /// the node at the given index is already present, it is not advanced. - /// If no present node exists in the given collection after the index, the - /// collection's `endIndex` is returned. - private static func presentIndex( - after index: SyntaxChildrenIndex, - in children: RawSyntaxChildren, - viewMode: SyntaxTreeViewMode - ) -> SyntaxChildrenIndex { - var advancedIndex = index - while true { - if advancedIndex != children.endIndex { - let node = children[advancedIndex].raw - if let node = node, viewMode.shouldTraverse(node: node) { - // Found a present node. Return its index. - return advancedIndex - } - // Continue advancing - advancedIndex = children.index(advancedIndex, advancedBy: node) - } else { - // Reached the end of the collection. Don't advance further. - return advancedIndex - } - } - } - - /// Reverses the index to the previous present node in the given collection. - /// If the node at the given index is already present, it is not reversed. - /// Behavior is undefined if no present index exists before the given index. - private static func presentIndex( - before index: SyntaxChildrenIndex, - in children: RawSyntaxChildren, - viewMode: SyntaxTreeViewMode - ) -> SyntaxChildrenIndex { - var reversedIndex = index - while true { - if reversedIndex < children.endIndex, - let node = children[reversedIndex].raw, - viewMode.shouldTraverse(node: node) - { - return reversedIndex - } - // Reversing any further would result in undefined behaviour of - // index(before:) - precondition( - reversedIndex != children.startIndex, - "presentIndex(before:) must not be called if there is no " + "present index before the given one" - ) - reversedIndex = children.index(before: reversedIndex) - } - } - - func index(after index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - let nextIndex = allChildren.index(after: index) - return Self.presentIndex( - after: nextIndex, - in: allChildren, - viewMode: viewMode - ) - } - - func index(before index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - // presentIndex(before:) must have a valid previous present node. By - // contract of the index(before:) function we are not called on the start - // index. The start index points to the first present node. Hence there is - // a present node before us. - return Self.presentIndex( - before: allChildren.index(before: index), - in: allChildren, - viewMode: viewMode - ) - } - - subscript(position: SyntaxChildrenIndex) -> AbsoluteRawSyntax { - let (node, info) = allChildren[position] - // Valid indices always point to present nodes. Thus safe to force unwrap. - return AbsoluteRawSyntax(raw: node!, info: info) - } - - init(_ parent: AbsoluteRawSyntax, viewMode: SyntaxTreeViewMode) { - let allChildren = RawSyntaxChildren(parent) - - self.allChildren = allChildren - self.startIndex = Self.presentIndex( - after: allChildren.startIndex, - in: allChildren, - viewMode: viewMode - ) - self.viewMode = viewMode - } - - /// - Note: Inline so we don't retain `Syntax.Info` when creating `NonNilRawSyntaxChildren` from a `Syntax`. - @inline(__always) - init(_ node: Syntax, viewMode: SyntaxTreeViewMode) { - self.init(node.absoluteRaw, viewMode: viewMode) - } -} - /// Collection that contains the present child ``Syntax`` nodes of the given node. public struct SyntaxChildren: BidirectionalCollection, Sendable { /// ``SyntaxChildren`` is indexed by ``SyntaxChildrenIndex``. @@ -429,38 +37,57 @@ public struct SyntaxChildren: BidirectionalCollection, Sendable { /// ``SyntaxChildren`` contains ``Syntax`` nodes. public typealias Element = Syntax - /// The collection that contains the raw child nodes. ``Syntax`` nodes are - /// generated from these. - private let rawChildren: NonNilRawSyntaxChildren + private let viewMode: SyntaxTreeViewMode /// The parent node of the children. Used to build the ``Syntax`` nodes. private let parent: Syntax + /// Pre-fetched layout buffer. Because accessing `Syntax.layoutBuffer` may not be trivial. + private let layoutBuffer: SyntaxDataReferenceBuffer + /// The index of the first child in this collection. - public var startIndex: SyntaxChildrenIndex { return rawChildren.startIndex } + public let startIndex: SyntaxChildrenIndex /// The index that’s one after the last element in the collection. - public var endIndex: SyntaxChildrenIndex { return rawChildren.endIndex } + public var endIndex: SyntaxChildrenIndex { Index(value: layoutBuffer.endIndex) } + + private static func shouldTraverse(_ dataRef: SyntaxDataReference?, viewMode: SyntaxTreeViewMode) -> Bool { + guard let dataRef else { + return false + } + return viewMode.shouldTraverse(node: dataRef.pointee.raw) + } /// The index for the child that’s after the child at `index`. public func index(after index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - return rawChildren.index(after: index) + precondition(index < endIndex) + return Index( + value: layoutBuffer[(index.value + 1)...] + .firstIndex(where: { Self.shouldTraverse($0, viewMode: viewMode) }) ?? layoutBuffer.endIndex + ) } /// The index for the child that’s before the child at `index`. public func index(before index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - return rawChildren.index(before: index) + precondition(index > startIndex) + return Index( + value: layoutBuffer[.. Syntax { - let child = rawChildren[index] - return Syntax(child, parent: parent) + return Syntax(arena: parent.arena, dataRef: layoutBuffer[index.value]!) } internal init(_ node: Syntax, viewMode: SyntaxTreeViewMode) { - self.rawChildren = NonNilRawSyntaxChildren(node, viewMode: viewMode) self.parent = node + self.layoutBuffer = node.layoutBuffer + self.viewMode = viewMode + self.startIndex = Index( + value: layoutBuffer.firstIndex(where: { Self.shouldTraverse($0, viewMode: viewMode) }) ?? layoutBuffer.endIndex + ) } /// Return the index of `node` within this collection. diff --git a/Sources/SwiftSyntax/SyntaxCollection.swift b/Sources/SwiftSyntax/SyntaxCollection.swift index e17be71f842..a6af1f39597 100644 --- a/Sources/SwiftSyntax/SyntaxCollection.swift +++ b/Sources/SwiftSyntax/SyntaxCollection.swift @@ -224,22 +224,33 @@ extension SyntaxCollection { /// An iterator over a ``SyntaxCollection``. public struct SyntaxCollectionIterator: IteratorProtocol { - private let parent: Syntax public typealias Element = E - private var iterator: RawSyntaxChildren.Iterator + /// The arena in which `SyntaxData` get allocated as the children are traversed. + private let arena: SyntaxDataArena - init(parent: Syntax, rawChildren: RawSyntaxChildren) { - self.parent = parent - self.iterator = rawChildren.makeIterator() + /// The buffer containing the children that are iterated by this iterator. + private let layoutBuffer: SyntaxDataReferenceBuffer + + /// The index in `layoutBuffer` that will be returned when `next` is called. + private var index: Int + + init(_ node: Node) where Node.Element == Element { + let syntax = Syntax(node) + self.arena = syntax.arena + self.layoutBuffer = syntax.layoutBuffer + self.index = layoutBuffer.startIndex } public mutating func next() -> Element? { - guard let (raw, info) = self.iterator.next() else { + guard self.index < self.layoutBuffer.count else { return nil } - let absoluteRaw = AbsoluteRawSyntax(raw: raw!, info: info) - return Syntax(absoluteRaw, parent: parent).cast(Element.self) + defer { + self.index += 1 + } + // 'SyntaxCollection' always has non-nil children. We can thus force-unwrap the element at 'index'. + return Syntax(arena: arena, dataRef: layoutBuffer[self.index]!).cast(Element.self) } } @@ -263,8 +274,8 @@ extension SyntaxCollection { // Keep `newElements` alive so their arena doesn't get deallocated. withExtendedLifetime(newElements) { var newLayout = layoutView.formLayoutArray() - let layoutRangeLowerBound = (subrange.lowerBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex - let layoutRangeUpperBound = (subrange.upperBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex + let layoutRangeLowerBound = subrange.lowerBound.value + let layoutRangeUpperBound = subrange.upperBound.value newLayout.replaceSubrange(layoutRangeLowerBound.. SyntaxCollectionIterator { - return SyntaxCollectionIterator(parent: Syntax(self), rawChildren: rawChildren) + return SyntaxCollectionIterator(self) } - private var rawChildren: RawSyntaxChildren { - // We know children in a syntax collection cannot be missing. So we can - // use the low-level and faster RawSyntaxChildren collection instead of - // NonNilRawSyntaxChildren. - return RawSyntaxChildren(Syntax(self).absoluteRaw) + var elements: SyntaxDataReferenceBuffer { + Syntax(self).layoutBuffer } public var startIndex: SyntaxChildrenIndex { - return rawChildren.startIndex + return SyntaxChildrenIndex(value: 0) } public var endIndex: SyntaxChildrenIndex { - return rawChildren.endIndex + return SyntaxChildrenIndex(value: elements.count) } public func index(after index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - return rawChildren.index(after: index) + return Index(value: elements.index(after: index.value)) } public func index(before index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - return rawChildren.index(before: index) + return Index(value: elements.index(before: index.value)) } public func distance(from start: SyntaxChildrenIndex, to end: SyntaxChildrenIndex) -> Int { - return rawChildren.distance(from: start, to: end) + return elements.distance(from: start.value, to: end.value) } public subscript(position: SyntaxChildrenIndex) -> Element { get { - let (raw, info) = rawChildren[position] - let absoluteRaw = AbsoluteRawSyntax(raw: raw!, info: info) - return Syntax(absoluteRaw, parent: Syntax(self)).cast(Element.self) + // 'SyntaxCollection' always has non-nil children. We can thus force-unwrap the element at 'position.value' + return Syntax(arena: Syntax(self).arena, dataRef: elements[position.value]!).cast(Element.self) } set { - guard let indexToReplace = (position.data?.indexInParent).map(Int.init) else { - preconditionFailure("Cannot replace element at the end index") - } + let indexToReplace = position.value var newLayout = layoutView.formLayoutArray() /// Make sure the index is a valid index for replacing precondition( diff --git a/Sources/SwiftSyntax/SyntaxIdentifier.swift b/Sources/SwiftSyntax/SyntaxIdentifier.swift index 55d7f52d105..4a7a00af2f5 100644 --- a/Sources/SwiftSyntax/SyntaxIdentifier.swift +++ b/Sources/SwiftSyntax/SyntaxIdentifier.swift @@ -31,25 +31,6 @@ public struct SyntaxIdentifier: Comparable, Hashable, Sendable { /// When traversing the syntax tree using a depth-first traversal, the index at which the node will be visited. let indexInTree: UInt32 - /// Assuming that this index points to the start of `raw`, advance it so that it points to the next sibling of - /// `raw`. - func advancedBy(_ raw: RawSyntax?) -> SyntaxIndexInTree { - let newIndexInTree = self.indexInTree + UInt32(truncatingIfNeeded: raw?.totalNodes ?? 0) - return .init(indexInTree: newIndexInTree) - } - - /// Assuming that this index points to the next sibling of `raw`, reverse it so that it points to the start of - /// `raw`. - func reversedBy(_ raw: RawSyntax?) -> SyntaxIndexInTree { - let newIndexInTree = self.indexInTree - UInt32(truncatingIfNeeded: raw?.totalNodes ?? 0) - return .init(indexInTree: newIndexInTree) - } - - func advancedToFirstChild() -> SyntaxIndexInTree { - let newIndexInTree = self.indexInTree + 1 - return .init(indexInTree: newIndexInTree) - } - init(indexInTree: UInt32) { self.indexInTree = indexInTree } @@ -79,28 +60,6 @@ public struct SyntaxIdentifier: Comparable, Hashable, Sendable { /// Unique value for a node within its own tree. public let indexInTree: SyntaxIndexInTree - /// Returns the `UInt` that is used as the root ID for the given raw syntax node. - private static func rootId(of raw: RawSyntax) -> UInt { - return UInt(bitPattern: raw.pointer.unsafeRawPointer) - } - - func advancedBySibling(_ raw: RawSyntax?) -> SyntaxIdentifier { - let newIndexInTree = indexInTree.advancedBy(raw) - return SyntaxIdentifier(rootId: self.rootId, indexInTree: newIndexInTree) - } - - func advancedToFirstChild() -> SyntaxIdentifier { - let newIndexInTree = self.indexInTree.advancedToFirstChild() - return SyntaxIdentifier(rootId: self.rootId, indexInTree: newIndexInTree) - } - - static func forRoot(_ raw: RawSyntax) -> SyntaxIdentifier { - return SyntaxIdentifier( - rootId: Self.rootId(of: raw), - indexInTree: SyntaxIndexInTree(indexInTree: 0) - ) - } - /// Forms a ``SyntaxIdentifier`` from an ``SyntaxIdentifier/SyntaxIndexInTree`` inside a ``Syntax`` node that /// constitutes the tree's root. /// @@ -118,14 +77,11 @@ public struct SyntaxIdentifier: Comparable, Hashable, Sendable { _ indexInTree: SyntaxIndexInTree, relativeToRoot root: some SyntaxProtocol ) -> SyntaxIdentifier? { - guard !root.hasParent else { - return nil - } - guard indexInTree.indexInTree < SyntaxIndexInTree(indexInTree: 0).advancedBy(root.raw).indexInTree else { + guard !root.hasParent, Int(truncatingIfNeeded: indexInTree.indexInTree) < root.raw.totalNodes else { return nil } - return SyntaxIdentifier(rootId: Self.rootId(of: root.raw), indexInTree: indexInTree) + return SyntaxIdentifier(rootId: UInt(rawID: root.raw.id), indexInTree: indexInTree) } /// A ``SyntaxIdentifier`` compares less than another ``SyntaxIdentifier`` if the node at that identifier occurs first diff --git a/Sources/SwiftSyntax/SyntaxNodeFactory.swift b/Sources/SwiftSyntax/SyntaxNodeFactory.swift deleted file mode 100644 index 9246603e892..00000000000 --- a/Sources/SwiftSyntax/SyntaxNodeFactory.swift +++ /dev/null @@ -1,85 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -/// Reusable 'Syntax.Info' storage. -private struct SyntaxInfoRepository { - private final class _Buffer: ManagedBuffer { - deinit { - self.withUnsafeMutablePointers { headerPtr, elementPtr in - _ = elementPtr.deinitialize(count: headerPtr.pointee) - } - } - } - - /// Fixed capacity which is enough for most use cases. - static var capacity: Int { 64 } - - private let buffer: _Buffer - - init() { - let buffer = _Buffer.create(minimumCapacity: Self.capacity, makingHeaderWith: { _ in 0 }) - self.buffer = buffer as! _Buffer - } - - /// Take the 'Syntax.Info' object from the address. - func push(_ info: UnsafeMutablePointer) { - buffer.withUnsafeMutablePointers { headerPtr, elementPtr in - guard headerPtr.pointee < Self.capacity else { - return - } - assert(info.pointee != nil, "tried to push 'nil' info") - elementPtr.advanced(by: headerPtr.pointee).moveInitialize(from: info, count: 1) - info.initialize(to: nil) - headerPtr.pointee += 1 - } - } - - /// Vend a 'Swift.Info' object if available. - func pop() -> Syntax.Info? { - return buffer.withUnsafeMutablePointers { headerPtr, elementPtr in - guard headerPtr.pointee > 0 else { - return nil - } - headerPtr.pointee -= 1 - return elementPtr.advanced(by: headerPtr.pointee).move() - } - } -} - -/// 'Syntax' object factory. This may hold some stocks of recycled 'Syntax.Info'. -struct SyntaxNodeFactory { - private let syntaxInfoRepo: SyntaxInfoRepository = SyntaxInfoRepository() - - /// Create a 'Syntax' instance using the supplied info. - /// - /// If this factory has a recycled 'Syntax.Info', use one of them. Otherwise, just create a instance by allocating a new one. - @inline(__always) - func create(parent: Syntax, raw: RawSyntax, absoluteInfo: AbsoluteSyntaxInfo) -> Syntax { - if let info = syntaxInfoRepo.pop() { - info.info = .nonRoot(.init(parent: parent, absoluteInfo: absoluteInfo)) - return Syntax(raw, info: info) - } else { - return Syntax(raw, parent: parent, absoluteInfo: absoluteInfo) - } - } - - /// Dispose a 'Syntax' object. - /// - /// 'node.info' is collected for future reuse. 'node' is not usable after calling this. - @inline(__always) - func dispose(_ node: inout Syntax) { - if isKnownUniquelyReferenced(&node.info) { - node.info.unsafelyUnwrapped.info = nil - syntaxInfoRepo.push(&node.info) - } - } -} diff --git a/Sources/SwiftSyntax/SyntaxProtocol.swift b/Sources/SwiftSyntax/SyntaxProtocol.swift index 1eba89b48f5..da561ca0b3a 100644 --- a/Sources/SwiftSyntax/SyntaxProtocol.swift +++ b/Sources/SwiftSyntax/SyntaxProtocol.swift @@ -156,6 +156,9 @@ extension SyntaxProtocol { /// Return this subtree with this node as the root, ie. detach this node /// from its parent. public var detached: Self { + if !self.hasParent { + return self + } // Make sure `self` (and thus the arena of `self.raw`) can’t get deallocated // before the detached node can be created. return withExtendedLifetime(self) { @@ -202,7 +205,7 @@ extension SyntaxProtocol { /// The index of this node in a ``SyntaxChildren`` collection. internal var indexInParent: SyntaxChildrenIndex { - return SyntaxChildrenIndex(Syntax(self).absoluteInfo) + return SyntaxChildrenIndex(value: Syntax(self).layoutIndexInParent) } /// The parent of this syntax node, or `nil` if this node is the root. @@ -217,7 +220,7 @@ extension SyntaxProtocol { /// Whether or not this node has a parent. public var hasParent: Bool { - return parent != nil + return Syntax(self).hasParent } public var keyPathInParent: AnyKeyPath? { @@ -227,7 +230,7 @@ extension SyntaxProtocol { guard case .layout(let childrenKeyPaths) = parent.kind.syntaxNodeType.structure else { return nil } - return childrenKeyPaths[Syntax(self).indexInParent] + return childrenKeyPaths[Syntax(self).layoutIndexInParent] } @available(*, deprecated, message: "Use previousToken(viewMode:) instead") @@ -240,18 +243,7 @@ extension SyntaxProtocol { /// /// If no node has a non-`nil` mapping, returns `nil`. public func ancestorOrSelf(mapping map: (Syntax) -> T?) -> T? { - return self.withUnownedSyntax { - var node = $0 - while true { - if let mapped = node.withValue(map) { - return mapped - } - guard let parent = node.parent else { - return nil - } - node = parent - } - } + Syntax(self).ancestorOrSelf(mapping: map) } } @@ -264,12 +256,11 @@ extension SyntaxProtocol { guard let parent = self.parent else { return nil } - let siblings = NonNilRawSyntaxChildren(parent, viewMode: viewMode) + let siblings = parent.children(viewMode: viewMode) // `self` could be a missing node at index 0 and `viewMode` be `.sourceAccurate`. // In that case `siblings` skips over the missing `self` node and has a `startIndex > 0`. - if self.indexInParent >= siblings.startIndex { - for absoluteRaw in siblings[.. Syntax? { - guard self.id <= syntaxIdentifier && syntaxIdentifier < self.id.advancedBySibling(self.raw) else { - // The syntax identifier is not part of this tree. + let syntax = Syntax(self) + guard UInt(rawID: syntax.raw.id) == syntaxIdentifier.rootId else { return nil } - if self.id == syntaxIdentifier { - return Syntax(self) - } - for child in children(viewMode: .all) { - if let node = child.node(at: syntaxIdentifier) { + + func _node(at indexInTree: UInt32, in node: Syntax) -> Syntax? { + let i = node.absoluteInfo.indexInTree + if i == indexInTree { return node } + guard i < indexInTree, indexInTree < i &+ UInt32(truncatingIfNeeded: node.raw.totalNodes) else { + return nil + } + for child in node.children(viewMode: .all) { + if let node = _node(at: indexInTree, in: child) { + return node + } + } + preconditionFailure("syntaxIdentifier is covered by this node but not any of its children?") } - - preconditionFailure("syntaxIdentifier is covered by this node but not any of its children?") + return _node(at: syntaxIdentifier.indexInTree.indexInTree, in: syntax) } } diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index 8c503ed4910..9090c0a3ab5 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -31,9 +31,6 @@ open class SyntaxRewriter { /// intermediate nodes should be allocated. private let arena: SyntaxArena? - /// 'Syntax' object factory recycling 'Syntax.Info' instances. - private let nodeFactory: SyntaxNodeFactory = SyntaxNodeFactory() - public init(viewMode: SyntaxTreeViewMode = .sourceAccurate) { self.viewMode = viewMode self.arena = nil @@ -4769,11 +4766,11 @@ open class SyntaxRewriter { // with 'Syntax' var rewrittens: ContiguousArray = [] - for case let (child?, info) in RawSyntaxChildren(node) where viewMode.shouldTraverse(node: child) { + for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) { // Build the Syntax node to rewrite - var childNode = visitImpl(nodeFactory.create(parent: node, raw: child, absoluteInfo: info)) - if childNode.raw.id != child.id { + let childNode = visitImpl(Syntax(arena: node.arena, dataRef: childDataRef)) + if childNode.raw.id != childDataRef.pointee.raw.id { // The node was rewritten, let's handle it if newLayout.baseAddress == nil { @@ -4784,13 +4781,10 @@ open class SyntaxRewriter { } // Update the rewritten child. - newLayout[Int(info.indexInParent)] = childNode.raw + newLayout[Int(childDataRef.pointee.absoluteInfo.layoutIndexInParent)] = childNode.raw // Retain the syntax arena of the new node until it's wrapped with Syntax node. rewrittens.append(childNode.raw.arenaReference.retained) } - - // Recycle 'childNode.info' - nodeFactory.dispose(&childNode) } if newLayout.baseAddress != nil { diff --git a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift index 23ca802e1c9..511790c81fd 100644 --- a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift @@ -24,9 +24,6 @@ public enum SyntaxVisitorContinueKind { open class SyntaxVisitor { public let viewMode: SyntaxTreeViewMode - /// 'Syntax' object factory recycling 'Syntax.Info' instances. - private let nodeFactory: SyntaxNodeFactory = SyntaxNodeFactory() - public init(viewMode: SyntaxTreeViewMode) { self.viewMode = viewMode } @@ -6951,10 +6948,8 @@ open class SyntaxVisitor { #endif private func visitChildren(_ node: Syntax) { - for case let (child?, info) in RawSyntaxChildren(node) where viewMode.shouldTraverse(node: child) { - var childNode = nodeFactory.create(parent: node, raw: child, absoluteInfo: info) - dispatchVisit(childNode) - nodeFactory.dispose(&childNode) + for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) { + dispatchVisit(Syntax(arena: node.arena, dataRef: childDataRef)) } } } diff --git a/Sources/_SwiftSyntaxCShims/CMakeLists.txt b/Sources/_SwiftSyntaxCShims/CMakeLists.txt index 39cfa9294c2..9505468103b 100644 --- a/Sources/_SwiftSyntaxCShims/CMakeLists.txt +++ b/Sources/_SwiftSyntaxCShims/CMakeLists.txt @@ -1,5 +1,7 @@ set(target ${SWIFTSYNTAX_TARGET_NAMESPACE}_SwiftSyntaxCShims) -add_library(${target} INTERFACE) -target_include_directories(${target} INTERFACE "include") +add_library(${target} STATIC + PlatformMutex.c +) +target_include_directories(${target} PUBLIC "include") set_property(GLOBAL APPEND PROPERTY SWIFT_EXPORTS ${target}) install(TARGETS ${target} EXPORT SwiftSyntaxTargets) diff --git a/Sources/_SwiftSyntaxCShims/PlatformMutex.c b/Sources/_SwiftSyntaxCShims/PlatformMutex.c new file mode 100644 index 00000000000..039401df2da --- /dev/null +++ b/Sources/_SwiftSyntaxCShims/PlatformMutex.c @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "PlatformMutex.h" +#include + +#if defined(__APPLE__) +#include + +PlatformMutex swiftsyntax_platform_mutex_create() { + PlatformMutex m; + m.opaque = malloc(sizeof(os_unfair_lock)); + *(os_unfair_lock_t)m.opaque = OS_UNFAIR_LOCK_INIT; + return m; +} + +void swiftsyntax_platform_mutex_lock(PlatformMutex m) { + os_unfair_lock_lock(m.opaque); +} + +void swiftsyntax_platform_mutex_unlock(PlatformMutex m) { + os_unfair_lock_unlock(m.opaque); +} + +void swiftsyntax_platform_mutex_destroy(PlatformMutex m) { + free(m.opaque); +} + +#elif defined(_WIN32) +#include + +PlatformMutex swiftsyntax_platform_mutex_create() { + PlatformMutex m; + m.opaque = malloc(sizeof(SRWLOCK)); + InitializeSRWLock(m.opaque); + return m; +} + +void swiftsyntax_platform_mutex_lock(PlatformMutex m) { + AcquireSRWLockExclusive(m.opaque); +} + +void swiftsyntax_platform_mutex_unlock(PlatformMutex m) { + ReleaseSRWLockExclusive(m.opaque); +} + +void swiftsyntax_platform_mutex_destroy(PlatformMutex m) { + free(m.opaque); +} + +#elif __has_include() +#include + +PlatformMutex swiftsyntax_platform_mutex_create() { + PlatformMutex m; + m.opaque = malloc(sizeof(pthread_mutex_t)); + pthread_mutex_init(m.opaque, 0); + return m; +} + +void swiftsyntax_platform_mutex_lock(PlatformMutex m) { + pthread_mutex_lock(m.opaque); +} + +void swiftsyntax_platform_mutex_unlock(PlatformMutex m) { + pthread_mutex_unlock(m.opaque); +} + +void swiftsyntax_platform_mutex_destroy(PlatformMutex m) { + pthread_mutex_destroy(m.opaque); + free(m.opaque); +} + +#else +#error "platfrom mutex implementation is not available" +#endif diff --git a/Sources/_SwiftSyntaxCShims/dummy.c b/Sources/_SwiftSyntaxCShims/dummy.c deleted file mode 100644 index 8b137891791..00000000000 --- a/Sources/_SwiftSyntaxCShims/dummy.c +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Sources/_SwiftSyntaxCShims/include/AtomicBool.h b/Sources/_SwiftSyntaxCShims/include/Atomics.h similarity index 77% rename from Sources/_SwiftSyntaxCShims/include/AtomicBool.h rename to Sources/_SwiftSyntaxCShims/include/Atomics.h index afa3caf4db9..cdbc4b68f7b 100644 --- a/Sources/_SwiftSyntaxCShims/include/AtomicBool.h +++ b/Sources/_SwiftSyntaxCShims/include/Atomics.h @@ -38,4 +38,16 @@ static inline void swiftsyntax_atomic_bool_destroy(AtomicBool *_Nonnull atomic) free(atomic); } +typedef struct { + _Atomic(const void *_Nullable) value; +} AtomicPointer; + +static inline const void *_Nullable swiftsyntax_atomic_pointer_get(const AtomicPointer *_Nonnull atomic) { + return atomic->value; +} + +static inline void swiftsyntax_atomic_pointer_set(AtomicPointer *_Nonnull atomic, const void *_Nullable newValue) { + atomic->value = newValue; +} + #endif // SWIFTSYNTAX_ATOMICBOOL_H diff --git a/Sources/_SwiftSyntaxCShims/include/PlatformMutex.h b/Sources/_SwiftSyntaxCShims/include/PlatformMutex.h new file mode 100644 index 00000000000..3288a0f1746 --- /dev/null +++ b/Sources/_SwiftSyntaxCShims/include/PlatformMutex.h @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFTSYNTAX_PLATFORMMUTEX_H +#define SWIFTSYNTAX_PLATFORMMUTEX_H + +#include "_bridging.h" + +typedef struct PlatformMutex { + void *opaque; +} PlatformMutex; + +SWIFT_NAME_S("PlatformMutex.create()") +PlatformMutex swiftsyntax_platform_mutex_create(void); + +SWIFT_NAME_S("PlatformMutex.lock(self:)") +void swiftsyntax_platform_mutex_lock(PlatformMutex m); + +SWIFT_NAME_S("PlatformMutex.unlock(self:)") +void swiftsyntax_platform_mutex_unlock(PlatformMutex m); + +SWIFT_NAME_S("PlatformMutex.destroy(self:)") +void swiftsyntax_platform_mutex_destroy(PlatformMutex m); + +#endif // SWIFTSYNTAX_PLATFORMMUTEX_H diff --git a/Sources/_SwiftSyntaxCShims/include/SwiftSyntaxCShims.h b/Sources/_SwiftSyntaxCShims/include/SwiftSyntaxCShims.h index 1db9283cdfb..cbe705c5808 100644 --- a/Sources/_SwiftSyntaxCShims/include/SwiftSyntaxCShims.h +++ b/Sources/_SwiftSyntaxCShims/include/SwiftSyntaxCShims.h @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "_includes.h" -#include "AtomicBool.h" +#include "Atomics.h" +#include "PlatformMutex.h" #include "swiftsyntax_errno.h" #include "swiftsyntax_stdio.h" diff --git a/Sources/_SwiftSyntaxCShims/include/_bridging.h b/Sources/_SwiftSyntaxCShims/include/_bridging.h new file mode 100644 index 00000000000..5d4c83f2771 --- /dev/null +++ b/Sources/_SwiftSyntaxCShims/include/_bridging.h @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFTSYNTAX_BRIDGING_H +#define SWIFTSYNTAX_BRIDGING_H + +#if __has_attribute(swift_name) +#define SWIFT_NAME_S(NAME) __attribute__((swift_name(NAME))) +#else +#define SWIFT_NAME_S(NAME) +#endif + +#endif // SWIFTSYNTAX_BRIDGING_H diff --git a/Sources/_SwiftSyntaxCShims/include/module.modulemap b/Sources/_SwiftSyntaxCShims/include/module.modulemap index 010d951f292..a37c41fa2f9 100644 --- a/Sources/_SwiftSyntaxCShims/include/module.modulemap +++ b/Sources/_SwiftSyntaxCShims/include/module.modulemap @@ -1,7 +1,11 @@ module _SwiftSyntaxCShims { header "_includes.h" - header "AtomicBool.h" + header "Atomics.h" + header "PlatformMutex.h" header "swiftsyntax_errno.h" header "swiftsyntax_stdio.h" + + textual header "_bridging.h" + export * } diff --git a/Sources/_SwiftSyntaxCShims/include/swiftsyntax_errno.h b/Sources/_SwiftSyntaxCShims/include/swiftsyntax_errno.h index 589fde7eaed..f21a17793c0 100644 --- a/Sources/_SwiftSyntaxCShims/include/swiftsyntax_errno.h +++ b/Sources/_SwiftSyntaxCShims/include/swiftsyntax_errno.h @@ -13,9 +13,11 @@ #ifndef SWIFTSYNTAX_ERRNO_H #define SWIFTSYNTAX_ERRNO_H +#include "_bridging.h" + #include -__attribute__((swift_name("getter:_errno()"))) +SWIFT_NAME_S("getter:_errno()") static inline int swiftsyntax_errno(void) { return errno; } diff --git a/Sources/_SwiftSyntaxCShims/include/swiftsyntax_stdio.h b/Sources/_SwiftSyntaxCShims/include/swiftsyntax_stdio.h index a7f24c3dfee..8bcc29e2a34 100644 --- a/Sources/_SwiftSyntaxCShims/include/swiftsyntax_stdio.h +++ b/Sources/_SwiftSyntaxCShims/include/swiftsyntax_stdio.h @@ -13,19 +13,21 @@ #ifndef SWIFTSYNTAX_STDIO_H #define SWIFTSYNTAX_STDIO_H +#include "_bridging.h" + #include -__attribute__((swift_name("getter:_stdout()"))) +SWIFT_NAME_S("getter:_stdout()") static inline FILE *swiftsyntax_stdout(void) { return stdout; } -__attribute__((swift_name("getter:_stdin()"))) +SWIFT_NAME_S("getter:_stdin()") static inline FILE *swiftsyntax_stdin(void) { return stdin; } -__attribute__((swift_name("getter:_stderr()"))) +SWIFT_NAME_S("getter:_stderr()") static inline FILE *swiftsyntax_stderr(void) { return stderr; } diff --git a/Tests/SwiftSyntaxTest/MemoryLayoutTest.swift b/Tests/SwiftSyntaxTest/MemoryLayoutTest.swift index af45e08f7fb..ae8317f3411 100644 --- a/Tests/SwiftSyntaxTest/MemoryLayoutTest.swift +++ b/Tests/SwiftSyntaxTest/MemoryLayoutTest.swift @@ -33,9 +33,10 @@ final class MemoryLayoutTest: XCTestCase { "RawSyntax?": .init(size: 8, stride: 8, alignment: 8), "Syntax": .init(size: 16, stride: 16, alignment: 8), - "Syntax.Info": .init(size: 8, stride: 8, alignment: 8), - "Syntax.Info.Root": .init(size: 8, stride: 8, alignment: 8), - "Syntax.Info.NonRoot": .init(size: 36, stride: 40, alignment: 8), + "SyntaxData": .init(size: 32, stride: 32, alignment: 8), + "AbsoluteSyntaxInfo": .init(size: 12, stride: 12, alignment: 4), + "SyntaxDataReference?": .init(size: 8, stride: 8, alignment: 8), + "AtomicPointer": .init(size: 8, stride: 8, alignment: 8), ] let values = SyntaxMemoryLayout.values @@ -45,4 +46,13 @@ final class MemoryLayoutTest: XCTestCase { XCTAssertEqual(actualValue, exp.value, "Matching '\(exp.key)' values") } } + + func testSyntaxDataTailAllocation() throws { + #if !arch(x86_64) && !arch(arm64) + throw XCTSkip("Only runs on x86_64 and arm64") + #endif + let values = SyntaxMemoryLayout.values + // This ensures 'AtomicPointer' is safe to tail allocate right after 'SyntaxData.stride' + XCTAssertGreaterThanOrEqual(values["SyntaxData"]!.alignment, values["AtomicPointer"]!.alignment) + } } From 8fff0deb40f36bda0baff654dfafaf48f25e23a9 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 23 Dec 2024 22:34:35 -0800 Subject: [PATCH 2/2] Workaround for rdar://141977987 --- Sources/SwiftSyntax/Syntax.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftSyntax/Syntax.swift b/Sources/SwiftSyntax/Syntax.swift index b4bbfc69e42..55f68315780 100644 --- a/Sources/SwiftSyntax/Syntax.swift +++ b/Sources/SwiftSyntax/Syntax.swift @@ -73,8 +73,10 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { } public var id: SyntaxIdentifier { - SyntaxIdentifier( - rootId: UInt(rawID: arena.root.pointee.raw.id), + // This var is a workaround for a potential compiler bug (rdar://141977987) + let rootDataRef = arena.root + return SyntaxIdentifier( + rootId: UInt(rawID: rootDataRef.pointee.raw.id), indexInTree: SyntaxIdentifier.SyntaxIndexInTree(indexInTree: absoluteInfo.indexInTree) ) }