diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift index d8005fde045..86f2cbaaea1 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift @@ -42,6 +42,13 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { """ ) + DeclSyntax( + """ + /// 'Syntax' object factory recycling 'Syntax.Info' instances. + private let nodeFactory: SyntaxNodeFactory = SyntaxNodeFactory() + """ + ) + DeclSyntax( """ public init(viewMode: SyntaxTreeViewMode = .sourceAccurate) { @@ -323,11 +330,11 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { // with 'Syntax' var rewrittens: ContiguousArray = [] - for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) { + for case let (child?, info) in RawSyntaxChildren(node) where viewMode.shouldTraverse(node: child) { // Build the Syntax node to rewrite - let childNode = visitImpl(Syntax(arena: node.arena, dataRef: childDataRef)) - if childNode.raw.id != childDataRef.pointee.raw.id { + var childNode = visitImpl(nodeFactory.create(parent: node, raw: child, absoluteInfo: info)) + if childNode.raw.id != child.id { // The node was rewritten, let's handle it if newLayout.baseAddress == nil { @@ -338,10 +345,13 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { } // Update the rewritten child. - newLayout[Int(childDataRef.pointee.absoluteInfo.layoutIndexInParent)] = childNode.raw + newLayout[Int(info.indexInParent)] = 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 00dac528f50..430feaa6393 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxVisitorFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxVisitorFile.swift @@ -32,6 +32,13 @@ 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) { @@ -214,8 +221,10 @@ let syntaxVisitorFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { DeclSyntax( """ private func visitChildren(_ node: Syntax) { - for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) { - dispatchVisit(Syntax(arena: node.arena, dataRef: childDataRef)) + 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) } } """ diff --git a/Release Notes/602.md b/Release Notes/602.md index e4a072d5f0d..92e173fbb08 100644 --- a/Release Notes/602.md +++ b/Release Notes/602.md @@ -20,12 +20,6 @@ - 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 new file mode 100644 index 00000000000..a3f5aec25e7 --- /dev/null +++ b/Sources/SwiftSyntax/AbsoluteRawSyntax.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// 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 8f21dfd9d11..0d281f63a8a 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 - 2024 Apple Inc. and the Swift project authors +// 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 @@ -10,52 +10,49 @@ // //===----------------------------------------------------------------------===// +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 { - /// 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 + let position: AbsoluteSyntaxPosition + let nodeId: SyntaxIdentifier - /// 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 + /// The UTF-8 offset of the syntax node in the source file + var offset: UInt32 { return position.offset } + var indexInParent: UInt32 { return position.indexInParent } func advancedBySibling(_ raw: RawSyntax?) -> AbsoluteSyntaxInfo { - 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 - ) - } + let newPosition = position.advancedBySibling(raw) + let newNodeId = nodeId.advancedBySibling(raw) + return .init(position: newPosition, nodeId: newNodeId) } func advancedToFirstChild() -> AbsoluteSyntaxInfo { - return AbsoluteSyntaxInfo( - offset: offset, - layoutIndexInParent: 0, - indexInTree: indexInTree &+ 1 - ) + let newPosition = position.advancedToFirstChild() + let newNodeId = nodeId.advancedToFirstChild() + return .init(position: newPosition, nodeId: newNodeId) } static func forRoot(_ raw: RawSyntax) -> AbsoluteSyntaxInfo { - // 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 - ) + return .init(position: .forRoot, nodeId: .forRoot(raw)) } } diff --git a/Sources/SwiftSyntax/CMakeLists.txt b/Sources/SwiftSyntax/CMakeLists.txt index 8001de25c4e..dd77da30625 100644 --- a/Sources/SwiftSyntax/CMakeLists.txt +++ b/Sources/SwiftSyntax/CMakeLists.txt @@ -8,6 +8,7 @@ add_swift_syntax_library(SwiftSyntax AbsolutePosition.swift + AbsoluteRawSyntax.swift AbsoluteSyntaxInfo.swift Assert.swift BumpPtrAllocator.swift @@ -30,6 +31,7 @@ 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 89e35bc1109..e4666e52dd1 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 { - public let size: Int - public let stride: Int - public let alignment: Int + var size: Int + var stride: Int + var 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 5d24a782003..d271f5ea7bc 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 { - RawTriviaPieceBuffer(rebasing: 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 { - let arena: SyntaxDataArena - let dataRef: SyntaxDataReference + /// 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 + } + } - /// "designated" memberwise initializer of `Syntax`. - @_transparent // Inline early to enable certain optimzation. - init(arena: __shared SyntaxDataArena, dataRef: SyntaxDataReference) { - self.arena = arena - self.dataRef = dataRef - } + // For non-root nodes. + struct NonRoot: Sendable { + var parent: Syntax + var absoluteInfo: AbsoluteSyntaxInfo + } + + enum InfoImpl: Sendable { + case root(Root) + case nonRoot(NonRoot) + } + + init(_ info: InfoImpl) { + self.info = info + } - private var data: SyntaxData { - @_transparent unsafeAddress { dataRef.pointer } + /// 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 } - @inline(__always) - var raw: RawSyntax { - data.raw + /// 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 + } } - @inline(__always) - public var root: Self { - return Self(arena: arena, dataRef: arena.root) + private var nonRootInfo: Info.NonRoot? { + switch info.info! { + case .root(_): return nil + case .nonRoot(let info): return info + } } - @inline(__always) - public var parent: Syntax? { - guard let parentDataRef = data.parent else { - return nil + public var root: Syntax { + return self.withUnownedSyntax { + var node = $0 + while let parent = node.parent { + node = parent + } + return node.value } - return Syntax(arena: arena, dataRef: parentDataRef) } - @inline(__always) - public var hasParent: Bool { - data.parent != nil + public var parent: Syntax? { + nonRootInfo?.parent } var absoluteInfo: AbsoluteSyntaxInfo { - data.absoluteInfo + nonRootInfo?.absoluteInfo ?? .forRoot(raw) + } + + var absoluteRaw: AbsoluteRawSyntax { + AbsoluteRawSyntax(raw: raw, info: absoluteInfo) } - /// Index in parent's layout. `nil` slots are counted. - var layoutIndexInParent: Int { - Int(absoluteInfo.layoutIndexInParent) + var indexInParent: Int { + Int(absoluteInfo.indexInParent) } public var id: SyntaxIdentifier { - // 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) - ) + absoluteInfo.nodeId } /// The position of the start of this node's leading trivia @@ -101,6 +131,27 @@ 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: @@ -110,32 +161,30 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { /// has a chance to retain it. static func forRoot(_ raw: RawSyntax, rawNodeArena: RetainedSyntaxArena) -> Syntax { precondition(rawNodeArena == raw.arenaReference) - let arena = SyntaxDataArena(raw: raw, rawNodeArena: rawNodeArena) - return Self(arena: arena, dataRef: arena.root) + return Syntax(raw, info: Info(.root(.init(arena: rawNodeArena)))) } static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> Syntax { - 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) + precondition(rawNodeArena == raw.arenaReference) + return Syntax(raw, info: Info(.root(.init(arena: RetainedSyntaxArena(rawNodeArena))))) } - /// 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. + /// 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. /// /// - Parameter index: The index to create and cache. - /// - Returns: The child's node at the provided index. + /// - 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. func child(at index: Int) -> Syntax? { - let layoutBuffer = self.layoutBuffer - guard let dataRef = layoutBuffer[index] else { - return nil - } - return Self(arena: self.arena, dataRef: dataRef) + 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``. + /// /// Needed for the conformance to ``SyntaxProtocol``. Just returns `self`. public var _syntaxNode: Syntax { return self @@ -334,184 +372,56 @@ extension Syntax { } } -typealias SyntaxDataReference = SyntaxArenaAllocatedPointer -typealias SyntaxDataReferenceBuffer = SyntaxArenaAllocatedBufferPointer - -/// Node data for a `Syntax`, allocated and managed by `SyntaxDataArena`. +/// Temporary non-owning Syntax. /// -/// - 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. -} - -/// `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 +/// This can be used for handling Syntax node without ARC traffic. +struct UnownedSyntax { + private let raw: RawSyntax + private let info: Unmanaged - /// 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)) + @_transparent + init(_ node: __shared Syntax) { + self.raw = node.raw + self.info = .passUnretained(node.info.unsafelyUnwrapped) } - 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() + /// Extract the Syntax value. + @inline(__always) + var value: Syntax { + Syntax(raw, info: info.takeUnretainedValue()) } - /// 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)) + /// 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 + } } - - 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) } - /// 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 + /// Temporarily use the Syntax value. + @inline(__always) + func withValue(_ body: (Syntax) -> T) -> T { + info._withUnsafeGuaranteedRef { + body(Syntax(self.raw, info: $0)) } - return UnsafeBufferPointer(allocated) } +} - /// Calculate the recommended slab size of `BumpPtrAllocator`. - /// - /// 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. +extension SyntaxProtocol { + /// Execute the `body` with ``UnownedSyntax`` of `node`. /// - /// 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) + /// 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))) } - - return SyntaxDataReference(UnsafePointer(dataRef)) } } @@ -523,8 +433,7 @@ public struct SyntaxNode {} /// See `SyntaxMemoryLayout`. let SyntaxMemoryLayouts: [String: SyntaxMemoryLayout.Value] = [ "Syntax": .init(Syntax.self), - "SyntaxData": .init(SyntaxData.self), - "AbsoluteSyntaxInfo": .init(AbsoluteSyntaxInfo.self), - "SyntaxDataReference?": .init(SyntaxDataReference?.self), - "AtomicPointer": .init(AtomicPointer.self), + "Syntax.Info": .init(Syntax.Info.self), + "Syntax.Info.Root": .init(Syntax.Info.Root.self), + "Syntax.Info.NonRoot": .init(Syntax.Info.NonRoot.self), ] diff --git a/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift b/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift index 3a2fbfe6342..d2dd2412b6d 100644 --- a/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift +++ b/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift @@ -27,18 +27,14 @@ 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(_ pointer: UnsafePointer) { - self.pointer = pointer + init(_ buffer: UnsafePointer) { + self.pointer = buffer } var pointee: Element { @_transparent unsafeAddress { pointer } } - func advanced(by n: Int) -> Self { - Self(pointer.advanced(by: n)) - } - var unsafeRawPointer: UnsafeRawPointer { return UnsafeRawPointer(pointer) } @@ -70,12 +66,10 @@ public struct SyntaxArenaAllocatedBufferPointer: RandomAccess self.buffer = buffer } - /// 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>( + range: RangeType + ) -> SyntaxArenaAllocatedBufferPointer { + return SyntaxArenaAllocatedBufferPointer(UnsafeBufferPointer(rebasing: self.buffer[range])) } public subscript(_ index: Int) -> Element { diff --git a/Sources/SwiftSyntax/SyntaxChildren.swift b/Sources/SwiftSyntax/SyntaxChildren.swift index e02bdb4fda9..5457797679a 100644 --- a/Sources/SwiftSyntax/SyntaxChildren.swift +++ b/Sources/SwiftSyntax/SyntaxChildren.swift @@ -12,23 +12,415 @@ // 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, Sendable { - /// The index in a `SyntaxDataReferenceBuffer` that is being referenced by this `SyntaxChildrenIndex`. - let value: Int +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 + ) + } - init(value: Int) { - self.value = value + internal init(_ absoluteSyntaxInfo: AbsoluteSyntaxInfo) { + self.data = SyntaxChildrenIndexData(absoluteSyntaxInfo) } /// Returns `true` if `lhs` occurs before `rhs`. public static func < (lhs: SyntaxChildrenIndex, rhs: SyntaxChildrenIndex) -> Bool { - lhs.value < rhs.value + 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) } } // 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``. @@ -37,57 +429,38 @@ public struct SyntaxChildren: BidirectionalCollection, Sendable { /// ``SyntaxChildren`` contains ``Syntax`` nodes. public typealias Element = Syntax - private let viewMode: SyntaxTreeViewMode + /// The collection that contains the raw child nodes. ``Syntax`` nodes are + /// generated from these. + private let rawChildren: NonNilRawSyntaxChildren /// 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 let startIndex: SyntaxChildrenIndex + public var startIndex: SyntaxChildrenIndex { return rawChildren.startIndex } /// The index that’s one after the last element in the collection. - 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) - } + public var endIndex: SyntaxChildrenIndex { return rawChildren.endIndex } /// The index for the child that’s after the child at `index`. public func index(after index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - precondition(index < endIndex) - return Index( - value: layoutBuffer[(index.value + 1)...] - .firstIndex(where: { Self.shouldTraverse($0, viewMode: viewMode) }) ?? layoutBuffer.endIndex - ) + return rawChildren.index(after: index) } /// The index for the child that’s before the child at `index`. public func index(before index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - precondition(index > startIndex) - return Index( - value: layoutBuffer[.. Syntax { - return Syntax(arena: parent.arena, dataRef: layoutBuffer[index.value]!) + let child = rawChildren[index] + return Syntax(child, parent: parent) } 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 a6af1f39597..e17be71f842 100644 --- a/Sources/SwiftSyntax/SyntaxCollection.swift +++ b/Sources/SwiftSyntax/SyntaxCollection.swift @@ -224,33 +224,22 @@ extension SyntaxCollection { /// An iterator over a ``SyntaxCollection``. public struct SyntaxCollectionIterator: IteratorProtocol { + private let parent: Syntax public typealias Element = E - /// The arena in which `SyntaxData` get allocated as the children are traversed. - private let arena: SyntaxDataArena + private var iterator: RawSyntaxChildren.Iterator - /// 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 + init(parent: Syntax, rawChildren: RawSyntaxChildren) { + self.parent = parent + self.iterator = rawChildren.makeIterator() } public mutating func next() -> Element? { - guard self.index < self.layoutBuffer.count else { + guard let (raw, info) = self.iterator.next() else { return nil } - 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) + let absoluteRaw = AbsoluteRawSyntax(raw: raw!, info: info) + return Syntax(absoluteRaw, parent: parent).cast(Element.self) } } @@ -274,8 +263,8 @@ extension SyntaxCollection { // Keep `newElements` alive so their arena doesn't get deallocated. withExtendedLifetime(newElements) { var newLayout = layoutView.formLayoutArray() - let layoutRangeLowerBound = subrange.lowerBound.value - let layoutRangeUpperBound = subrange.upperBound.value + let layoutRangeLowerBound = (subrange.lowerBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex + let layoutRangeUpperBound = (subrange.upperBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex newLayout.replaceSubrange(layoutRangeLowerBound.. SyntaxCollectionIterator { - return SyntaxCollectionIterator(self) + return SyntaxCollectionIterator(parent: Syntax(self), rawChildren: rawChildren) } - var elements: SyntaxDataReferenceBuffer { - Syntax(self).layoutBuffer + 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) } public var startIndex: SyntaxChildrenIndex { - return SyntaxChildrenIndex(value: 0) + return rawChildren.startIndex } public var endIndex: SyntaxChildrenIndex { - return SyntaxChildrenIndex(value: elements.count) + return rawChildren.endIndex } public func index(after index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - return Index(value: elements.index(after: index.value)) + return rawChildren.index(after: index) } public func index(before index: SyntaxChildrenIndex) -> SyntaxChildrenIndex { - return Index(value: elements.index(before: index.value)) + return rawChildren.index(before: index) } public func distance(from start: SyntaxChildrenIndex, to end: SyntaxChildrenIndex) -> Int { - return elements.distance(from: start.value, to: end.value) + return rawChildren.distance(from: start, to: end) } public subscript(position: SyntaxChildrenIndex) -> Element { get { - // '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) + let (raw, info) = rawChildren[position] + let absoluteRaw = AbsoluteRawSyntax(raw: raw!, info: info) + return Syntax(absoluteRaw, parent: Syntax(self)).cast(Element.self) } set { - let indexToReplace = position.value + guard let indexToReplace = (position.data?.indexInParent).map(Int.init) else { + preconditionFailure("Cannot replace element at the end index") + } 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 4a7a00af2f5..55d7f52d105 100644 --- a/Sources/SwiftSyntax/SyntaxIdentifier.swift +++ b/Sources/SwiftSyntax/SyntaxIdentifier.swift @@ -31,6 +31,25 @@ 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 } @@ -60,6 +79,28 @@ 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. /// @@ -77,11 +118,14 @@ public struct SyntaxIdentifier: Comparable, Hashable, Sendable { _ indexInTree: SyntaxIndexInTree, relativeToRoot root: some SyntaxProtocol ) -> SyntaxIdentifier? { - guard !root.hasParent, Int(truncatingIfNeeded: indexInTree.indexInTree) < root.raw.totalNodes else { + guard !root.hasParent else { + return nil + } + guard indexInTree.indexInTree < SyntaxIndexInTree(indexInTree: 0).advancedBy(root.raw).indexInTree else { return nil } - return SyntaxIdentifier(rootId: UInt(rawID: root.raw.id), indexInTree: indexInTree) + return SyntaxIdentifier(rootId: Self.rootId(of: root.raw), 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 new file mode 100644 index 00000000000..9246603e892 --- /dev/null +++ b/Sources/SwiftSyntax/SyntaxNodeFactory.swift @@ -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 +// +//===----------------------------------------------------------------------===// + +/// 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 da561ca0b3a..1eba89b48f5 100644 --- a/Sources/SwiftSyntax/SyntaxProtocol.swift +++ b/Sources/SwiftSyntax/SyntaxProtocol.swift @@ -156,9 +156,6 @@ 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) { @@ -205,7 +202,7 @@ extension SyntaxProtocol { /// The index of this node in a ``SyntaxChildren`` collection. internal var indexInParent: SyntaxChildrenIndex { - return SyntaxChildrenIndex(value: Syntax(self).layoutIndexInParent) + return SyntaxChildrenIndex(Syntax(self).absoluteInfo) } /// The parent of this syntax node, or `nil` if this node is the root. @@ -220,7 +217,7 @@ extension SyntaxProtocol { /// Whether or not this node has a parent. public var hasParent: Bool { - return Syntax(self).hasParent + return parent != nil } public var keyPathInParent: AnyKeyPath? { @@ -230,7 +227,7 @@ extension SyntaxProtocol { guard case .layout(let childrenKeyPaths) = parent.kind.syntaxNodeType.structure else { return nil } - return childrenKeyPaths[Syntax(self).layoutIndexInParent] + return childrenKeyPaths[Syntax(self).indexInParent] } @available(*, deprecated, message: "Use previousToken(viewMode:) instead") @@ -243,7 +240,18 @@ extension SyntaxProtocol { /// /// If no node has a non-`nil` mapping, returns `nil`. public func ancestorOrSelf(mapping map: (Syntax) -> T?) -> T? { - Syntax(self).ancestorOrSelf(mapping: map) + 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 + } + } } } @@ -256,11 +264,12 @@ extension SyntaxProtocol { guard let parent = self.parent else { return nil } - let siblings = parent.children(viewMode: viewMode) + let siblings = NonNilRawSyntaxChildren(parent, 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 siblings.startIndex < self.indexInParent { - for child in siblings[..= siblings.startIndex { + for absoluteRaw in siblings[.. Syntax? { - let syntax = Syntax(self) - guard UInt(rawID: syntax.raw.id) == syntaxIdentifier.rootId else { + guard self.id <= syntaxIdentifier && syntaxIdentifier < self.id.advancedBySibling(self.raw) else { + // The syntax identifier is not part of this tree. return nil } - - func _node(at indexInTree: UInt32, in node: Syntax) -> Syntax? { - let i = node.absoluteInfo.indexInTree - if i == indexInTree { + if self.id == syntaxIdentifier { + return Syntax(self) + } + for child in children(viewMode: .all) { + if let node = child.node(at: syntaxIdentifier) { 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?") } - return _node(at: syntaxIdentifier.indexInTree.indexInTree, in: syntax) + + preconditionFailure("syntaxIdentifier is covered by this node but not any of its children?") } } diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index 9090c0a3ab5..8c503ed4910 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -31,6 +31,9 @@ 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 @@ -4766,11 +4769,11 @@ open class SyntaxRewriter { // with 'Syntax' var rewrittens: ContiguousArray = [] - for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) { + for case let (child?, info) in RawSyntaxChildren(node) where viewMode.shouldTraverse(node: child) { // Build the Syntax node to rewrite - let childNode = visitImpl(Syntax(arena: node.arena, dataRef: childDataRef)) - if childNode.raw.id != childDataRef.pointee.raw.id { + var childNode = visitImpl(nodeFactory.create(parent: node, raw: child, absoluteInfo: info)) + if childNode.raw.id != child.id { // The node was rewritten, let's handle it if newLayout.baseAddress == nil { @@ -4781,10 +4784,13 @@ open class SyntaxRewriter { } // Update the rewritten child. - newLayout[Int(childDataRef.pointee.absoluteInfo.layoutIndexInParent)] = childNode.raw + newLayout[Int(info.indexInParent)] = 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 511790c81fd..23ca802e1c9 100644 --- a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift @@ -24,6 +24,9 @@ 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 } @@ -6948,8 +6951,10 @@ open class SyntaxVisitor { #endif private func visitChildren(_ node: Syntax) { - for case let childDataRef? in node.layoutBuffer where viewMode.shouldTraverse(node: childDataRef.pointee.raw) { - dispatchVisit(Syntax(arena: node.arena, dataRef: childDataRef)) + 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) } } } diff --git a/Sources/_SwiftSyntaxCShims/CMakeLists.txt b/Sources/_SwiftSyntaxCShims/CMakeLists.txt index 9505468103b..39cfa9294c2 100644 --- a/Sources/_SwiftSyntaxCShims/CMakeLists.txt +++ b/Sources/_SwiftSyntaxCShims/CMakeLists.txt @@ -1,7 +1,5 @@ set(target ${SWIFTSYNTAX_TARGET_NAMESPACE}_SwiftSyntaxCShims) -add_library(${target} STATIC - PlatformMutex.c -) -target_include_directories(${target} PUBLIC "include") +add_library(${target} INTERFACE) +target_include_directories(${target} INTERFACE "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 deleted file mode 100644 index 039401df2da..00000000000 --- a/Sources/_SwiftSyntaxCShims/PlatformMutex.c +++ /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 -// -//===----------------------------------------------------------------------===// - -#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 new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/Sources/_SwiftSyntaxCShims/dummy.c @@ -0,0 +1 @@ + diff --git a/Sources/_SwiftSyntaxCShims/include/Atomics.h b/Sources/_SwiftSyntaxCShims/include/AtomicBool.h similarity index 77% rename from Sources/_SwiftSyntaxCShims/include/Atomics.h rename to Sources/_SwiftSyntaxCShims/include/AtomicBool.h index cdbc4b68f7b..afa3caf4db9 100644 --- a/Sources/_SwiftSyntaxCShims/include/Atomics.h +++ b/Sources/_SwiftSyntaxCShims/include/AtomicBool.h @@ -38,16 +38,4 @@ 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 deleted file mode 100644 index 3288a0f1746..00000000000 --- a/Sources/_SwiftSyntaxCShims/include/PlatformMutex.h +++ /dev/null @@ -1,34 +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 -// -//===----------------------------------------------------------------------===// - -#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 cbe705c5808..1db9283cdfb 100644 --- a/Sources/_SwiftSyntaxCShims/include/SwiftSyntaxCShims.h +++ b/Sources/_SwiftSyntaxCShims/include/SwiftSyntaxCShims.h @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// #include "_includes.h" -#include "Atomics.h" -#include "PlatformMutex.h" +#include "AtomicBool.h" #include "swiftsyntax_errno.h" #include "swiftsyntax_stdio.h" diff --git a/Sources/_SwiftSyntaxCShims/include/_bridging.h b/Sources/_SwiftSyntaxCShims/include/_bridging.h deleted file mode 100644 index 5d4c83f2771..00000000000 --- a/Sources/_SwiftSyntaxCShims/include/_bridging.h +++ /dev/null @@ -1,22 +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 -// -//===----------------------------------------------------------------------===// - -#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 a37c41fa2f9..010d951f292 100644 --- a/Sources/_SwiftSyntaxCShims/include/module.modulemap +++ b/Sources/_SwiftSyntaxCShims/include/module.modulemap @@ -1,11 +1,7 @@ module _SwiftSyntaxCShims { header "_includes.h" - header "Atomics.h" - header "PlatformMutex.h" + header "AtomicBool.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 f21a17793c0..589fde7eaed 100644 --- a/Sources/_SwiftSyntaxCShims/include/swiftsyntax_errno.h +++ b/Sources/_SwiftSyntaxCShims/include/swiftsyntax_errno.h @@ -13,11 +13,9 @@ #ifndef SWIFTSYNTAX_ERRNO_H #define SWIFTSYNTAX_ERRNO_H -#include "_bridging.h" - #include -SWIFT_NAME_S("getter:_errno()") +__attribute__((swift_name("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 8bcc29e2a34..a7f24c3dfee 100644 --- a/Sources/_SwiftSyntaxCShims/include/swiftsyntax_stdio.h +++ b/Sources/_SwiftSyntaxCShims/include/swiftsyntax_stdio.h @@ -13,21 +13,19 @@ #ifndef SWIFTSYNTAX_STDIO_H #define SWIFTSYNTAX_STDIO_H -#include "_bridging.h" - #include -SWIFT_NAME_S("getter:_stdout()") +__attribute__((swift_name("getter:_stdout()"))) static inline FILE *swiftsyntax_stdout(void) { return stdout; } -SWIFT_NAME_S("getter:_stdin()") +__attribute__((swift_name("getter:_stdin()"))) static inline FILE *swiftsyntax_stdin(void) { return stdin; } -SWIFT_NAME_S("getter:_stderr()") +__attribute__((swift_name("getter:_stderr()"))) static inline FILE *swiftsyntax_stderr(void) { return stderr; } diff --git a/Tests/SwiftSyntaxTest/MemoryLayoutTest.swift b/Tests/SwiftSyntaxTest/MemoryLayoutTest.swift index ae8317f3411..af45e08f7fb 100644 --- a/Tests/SwiftSyntaxTest/MemoryLayoutTest.swift +++ b/Tests/SwiftSyntaxTest/MemoryLayoutTest.swift @@ -33,10 +33,9 @@ final class MemoryLayoutTest: XCTestCase { "RawSyntax?": .init(size: 8, stride: 8, alignment: 8), "Syntax": .init(size: 16, stride: 16, 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), + "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), ] let values = SyntaxMemoryLayout.values @@ -46,13 +45,4 @@ 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) - } }