From 6e1f86cb5a833bfe7fafab79ab89a66a3b1cceb5 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 29 Aug 2018 15:45:17 -0700 Subject: [PATCH 1/2] Make RawSyntax a class --- Sources/SwiftSyntax/RawSyntax.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftSyntax/RawSyntax.swift b/Sources/SwiftSyntax/RawSyntax.swift index bab3d5ba94b..0ce14c36147 100644 --- a/Sources/SwiftSyntax/RawSyntax.swift +++ b/Sources/SwiftSyntax/RawSyntax.swift @@ -85,7 +85,7 @@ fileprivate enum RawSyntaxData { /// Represents the raw tree structure underlying the syntax tree. These nodes /// have no notion of identity and only provide structure to the tree. They /// are immutable and can be freely shared between syntax nodes. -struct RawSyntax { +final class RawSyntax { fileprivate let data: RawSyntaxData let presence: SourcePresence @@ -124,6 +124,13 @@ struct RawSyntax { } }).value } + + /// Creates a copy of `other`. + init(_ other: RawSyntax) { + self.data = other.data + self.presence = other.presence + self.id = other.id + } init(kind: SyntaxKind, layout: [RawSyntax?], presence: SourcePresence, id: SyntaxNodeId? = nil) { @@ -336,7 +343,7 @@ extension RawSyntax { extension RawSyntax: Codable { /// Creates a RawSyntax from the provided Foundation Decoder. - init(from decoder: Decoder) throws { + required convenience init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let id = try container.decodeIfPresent(SyntaxNodeId.self, forKey: .id) let omitted = try container.decodeIfPresent(Bool.self, forKey: .omitted) ?? false @@ -352,7 +359,7 @@ extension RawSyntax: Codable { guard let lookupNode = lookupFunc(id) else { throw IncrementalDecodingError.nodeLookupFailed(id) } - self = lookupNode + self.init(lookupNode) return } From 0b303c097ea41ca52e1a5b05e09f4e6c0e60cc85 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Wed, 29 Aug 2018 15:47:41 -0700 Subject: [PATCH 2/2] [swiftSyntax] Store weak references to RawSyntax nodes in the nodeLookupTable This way we don't continue to retain RawSyntax nodes that are no longer needed for incremental transfer. --- Sources/SwiftSyntax/RawSyntax.swift | 2 +- Sources/SwiftSyntax/SwiftSyntax.swift | 35 +++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftSyntax/RawSyntax.swift b/Sources/SwiftSyntax/RawSyntax.swift index 0ce14c36147..182b91e91ed 100644 --- a/Sources/SwiftSyntax/RawSyntax.swift +++ b/Sources/SwiftSyntax/RawSyntax.swift @@ -343,7 +343,7 @@ extension RawSyntax { extension RawSyntax: Codable { /// Creates a RawSyntax from the provided Foundation Decoder. - required convenience init(from decoder: Decoder) throws { + convenience init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let id = try container.decodeIfPresent(SyntaxNodeId.self, forKey: .id) let omitted = try container.decodeIfPresent(Bool.self, forKey: .omitted) ?? false diff --git a/Sources/SwiftSyntax/SwiftSyntax.swift b/Sources/SwiftSyntax/SwiftSyntax.swift index df96532e98e..eca5916b8c8 100644 --- a/Sources/SwiftSyntax/SwiftSyntax.swift +++ b/Sources/SwiftSyntax/SwiftSyntax.swift @@ -46,6 +46,14 @@ public enum SerializationFormat { case byteTree } +fileprivate struct WeakReference { + weak private(set) var value: T? + + init(_ value: T) { + self.value = value + } +} + /// Deserializes the syntax tree from its serialized form to an object tree in /// Swift. To deserialize incrementally transferred syntax trees, the same /// instance of the deserializer must be used for all subsequent @@ -56,7 +64,14 @@ public final class SyntaxTreeDeserializer { /// Syntax nodes that have already been parsed and are able to be reused if /// they were omitted in an incremental syntax tree transfer - private var nodeLookupTable: [SyntaxNodeId: RawSyntax] = [:] + private var nodeLookupTable: [SyntaxNodeId: WeakReference] = [:] + + /// Keep a strong reference to the syntax tree that contains the nodes in the + /// `nodeLookupTable`. Because `nodeLookupTable` only holds a weak reference + /// to the RawSyntax nodes, all retired `RawSyntax` nodes will be deallocated + /// once we set a new tree. The weak references in `nodeLookupTable` will then + /// become `nil` but will also never be accessed again. + private var nodeLookupTree: RawSyntax? = nil /// The IDs of the nodes that were reused as part of incremental syntax /// parsing during the last deserialization @@ -70,7 +85,9 @@ public final class SyntaxTreeDeserializer { let decoder = JSONDecoder() decoder.userInfo[.rawSyntaxDecodedCallback] = self.addToLookupTable decoder.userInfo[.omittedNodeLookupFunction] = self.lookupNode - return try decoder.decode(RawSyntax.self, from: data) + let tree = try decoder.decode(RawSyntax.self, from: data) + self.nodeLookupTree = tree + return tree } /// Deserialize the given data as a ByteTree encoded syntax tree @@ -78,11 +95,13 @@ public final class SyntaxTreeDeserializer { var userInfo: [ByteTreeUserInfoKey: Any] = [:] userInfo[.rawSyntaxDecodedCallback] = self.addToLookupTable userInfo[.omittedNodeLookupFunction] = self.lookupNode - return try ByteTreeReader.read(RawSyntax.self, from: data, + let tree = try ByteTreeReader.read(RawSyntax.self, from: data, userInfo: &userInfo) { (version: ByteTreeProtocolVersion) in return version.major == 1 } + self.nodeLookupTree = tree + return tree } /// Decode a serialized form of SourceFileSyntax to a syntax tree. @@ -112,11 +131,17 @@ public final class SyntaxTreeDeserializer { private func lookupNode(id: SyntaxNodeId) -> RawSyntax? { reusedNodeIds.insert(id) - return nodeLookupTable[id] + guard let weakRef = nodeLookupTable[id] else { + return nil + } + guard let value = weakRef.value else { + fatalError("Trying to retrieve a node that has since been deallocated") + } + return value } private func addToLookupTable(_ node: RawSyntax) { - nodeLookupTable[node.id] = node + nodeLookupTable[node.id] = WeakReference(node) } }