Skip to content

Commit 6af38f3

Browse files
ahoppennkcsgexi
authored andcommitted
[libSyntax] Make RawSyntax nodes hold a strong reference to their arena
This allows an elegant design in which we can still allocate RawSyntax nodes using a bump allocator but are able to automatically free that buffer once the last RawSyntax node within that buffer is freed. This also resolves a memory leak of RawSyntax nodes that was caused by ParserUnit not freeing its underlying ASTContext.
1 parent 1c7f800 commit 6af38f3

15 files changed

+290
-274
lines changed

include/swift/AST/ASTContext.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ class ASTContext final {
392392
}
393393

394394
/// Retrive the syntax node memory manager for this context.
395-
syntax::SyntaxArena &getSyntaxArena() const;
395+
llvm::IntrusiveRefCntPtr<syntax::SyntaxArena> getSyntaxArena() const;
396396

397397
/// Retrieve the lazy resolver for this context.
398398
LazyResolver *getLazyResolver() const;

include/swift/Parse/SyntaxParsingContext.h

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,74 @@
1818
#include "swift/Syntax/Syntax.h"
1919
#include "swift/Syntax/TokenSyntax.h"
2020

21+
namespace swift {
22+
23+
using namespace swift::syntax;
24+
25+
/// Cache node for RawSyntax.
26+
class RawSyntaxCacheNode : public llvm::FoldingSetNode {
27+
28+
friend llvm::FoldingSetTrait<RawSyntaxCacheNode>;
29+
30+
/// Associated RawSyntax.
31+
RC<RawSyntax> Obj;
32+
/// FoldingSet node identifier of the associated RawSyntax.
33+
llvm::FoldingSetNodeIDRef IDRef;
34+
35+
public:
36+
RawSyntaxCacheNode(RC<RawSyntax> Obj, const llvm::FoldingSetNodeIDRef IDRef)
37+
: Obj(Obj), IDRef(IDRef) {}
38+
39+
/// Retrieve assciated RawSyntax.
40+
RC<RawSyntax> get() { return Obj; }
41+
42+
// Only allow allocation of Node using the allocator in SyntaxArena.
43+
void *operator new(size_t Bytes, RC<SyntaxArena> Arena,
44+
unsigned Alignment = alignof(RawSyntaxCacheNode)) {
45+
return Arena->Allocate(Bytes, Alignment);
46+
}
47+
48+
void *operator new(size_t Bytes) throw() = delete;
49+
void operator delete(void *Data) throw() = delete;
50+
};
51+
52+
class RawSyntaxTokenCache {
53+
llvm::FoldingSet<RawSyntaxCacheNode> CachedTokens;
54+
std::vector<RawSyntaxCacheNode *> CacheNodes;
55+
56+
public:
57+
RC<RawSyntax> getToken(RC<SyntaxArena> Arena, tok TokKind, OwnedString Text,
58+
llvm::ArrayRef<TriviaPiece> LeadingTrivia,
59+
llvm::ArrayRef<TriviaPiece> TrailingTrivia);
60+
61+
~RawSyntaxTokenCache();
62+
};
63+
64+
} // namespace swift
65+
66+
namespace llvm {
67+
68+
using swift::RawSyntaxCacheNode;
69+
70+
/// FoldingSet traits for RawSyntax wrapper.
71+
template <> struct FoldingSetTrait<RawSyntaxCacheNode> {
72+
73+
static inline void Profile(RawSyntaxCacheNode &X, FoldingSetNodeID &ID) {
74+
ID.AddNodeID(X.IDRef);
75+
}
76+
77+
static inline bool Equals(RawSyntaxCacheNode &X, const FoldingSetNodeID &ID,
78+
unsigned, FoldingSetNodeID &) {
79+
return ID == X.IDRef;
80+
}
81+
static inline unsigned ComputeHash(RawSyntaxCacheNode &X,
82+
FoldingSetNodeID &) {
83+
return X.IDRef.ComputeHash();
84+
}
85+
};
86+
87+
} // namespace llvm
88+
2189
namespace swift {
2290
class SourceFile;
2391
class SyntaxParsingCache;
@@ -75,15 +143,19 @@ class alignas(1 << SyntaxAlignInBits) SyntaxParsingContext {
75143
// Storage for Collected parts.
76144
std::vector<RC<RawSyntax>> Storage;
77145

78-
SyntaxArena &Arena;
146+
RC<SyntaxArena> Arena;
79147

80148
/// A cache of nodes that can be reused when creating the current syntax
81149
/// tree
82150
SyntaxParsingCache *SyntaxCache = nullptr;
83151

152+
/// Tokens nodes that have already been created and may be reused in other
153+
/// parts of the syntax tree.
154+
RawSyntaxTokenCache TokenCache;
155+
84156
RootContextData(SourceFile &SF, DiagnosticEngine &Diags,
85157
SourceManager &SourceMgr, unsigned BufferID,
86-
SyntaxArena &Arena, SyntaxParsingCache *SyntaxCache)
158+
RC<SyntaxArena> Arena, SyntaxParsingCache *SyntaxCache)
87159
: SF(SF), Diags(Diags), SourceMgr(SourceMgr), BufferID(BufferID),
88160
Arena(Arena), SyntaxCache(SyntaxCache) {}
89161
};
@@ -217,7 +289,9 @@ class alignas(1 << SyntaxAlignInBits) SyntaxParsingContext {
217289
return getRootData()->SyntaxCache;
218290
}
219291

220-
SyntaxArena &getArena() const { return getRootData()->Arena; }
292+
RawSyntaxTokenCache &getTokenCache() { return getRootData()->TokenCache; }
293+
294+
RC<SyntaxArena> getArena() const { return getRootData()->Arena; }
221295

222296
const SyntaxParsingContext *getRoot() const;
223297

include/swift/Syntax/RawSyntax.h

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
#include "swift/Basic/InlineBitfield.h"
3333
#include "swift/Syntax/References.h"
34+
#include "swift/Syntax/SyntaxArena.h"
3435
#include "swift/Syntax/SyntaxKind.h"
3536
#include "swift/Syntax/TokenKinds.h"
3637
#include "swift/Syntax/Trivia.h"
@@ -218,8 +219,7 @@ typedef unsigned SyntaxNodeId;
218219
///
219220
/// This is implementation detail - do not expose it in public API.
220221
class RawSyntax final
221-
: public llvm::ThreadSafeRefCountedBase<RawSyntax>,
222-
private llvm::TrailingObjects<RawSyntax, RC<RawSyntax>, OwnedString,
222+
: private llvm::TrailingObjects<RawSyntax, RC<RawSyntax>, OwnedString,
223223
TriviaPiece> {
224224
friend TrailingObjects;
225225

@@ -230,18 +230,20 @@ class RawSyntax final
230230
/// An ID of this node that is stable across incremental parses
231231
SyntaxNodeId NodeId;
232232

233+
/// If this node was allocated using a \c SyntaxArena's bump allocator, a
234+
/// reference to the arena to keep the underlying memory buffer of this node
235+
/// alive. If this is a \c nullptr, the node owns its own memory buffer.
236+
RC<SyntaxArena> Arena;
237+
233238
union {
234239
uint64_t OpaqueBits;
235240
struct {
236241
/// The kind of syntax this node represents.
237242
unsigned Kind : bitmax(NumSyntaxKindBits, 8);
238243
/// Whether this piece of syntax was actually present in the source.
239244
unsigned Presence : 1;
240-
/// Whether this piece of syntax was constructed with manually managed
241-
/// memory.
242-
unsigned ManualMemory : 1;
243245
} Common;
244-
enum { NumRawSyntaxBits = bitmax(NumSyntaxKindBits, 8) + 1 + 1 };
246+
enum { NumRawSyntaxBits = bitmax(NumSyntaxKindBits, 8) + 1 };
245247

246248
// For "layout" nodes.
247249
struct {
@@ -282,18 +284,23 @@ class RawSyntax final
282284
: 0;
283285
}
284286

285-
/// Constructor for creating layout nodes
287+
/// Constructor for creating layout nodes.
288+
/// If the node has been allocated inside the bump allocator of a
289+
/// \c SyntaxArena, that arena must be passed as \p Arena to retain the node's
290+
/// underlying storage.
286291
/// If \p NodeId is \c None, the next free NodeId is used, if it is passed,
287292
/// the caller needs to assure that the node ID has not been used yet.
288293
RawSyntax(SyntaxKind Kind, ArrayRef<RC<RawSyntax>> Layout,
289-
SourcePresence Presence, bool ManualMemory,
294+
SourcePresence Presence, RC<SyntaxArena> Arena,
290295
llvm::Optional<SyntaxNodeId> NodeId);
291296
/// Constructor for creating token nodes
297+
/// \c SyntaxArena, that arena must be passed as \p Arena to retain the node's
298+
/// underlying storage.
292299
/// If \p NodeId is \c None, the next free NodeId is used, if it is passed,
293300
/// the caller needs to assure that the NodeId has not been used yet.
294301
RawSyntax(tok TokKind, OwnedString Text, ArrayRef<TriviaPiece> LeadingTrivia,
295302
ArrayRef<TriviaPiece> TrailingTrivia, SourcePresence Presence,
296-
bool ManualMemory, llvm::Optional<SyntaxNodeId> NodeId);
303+
RC<SyntaxArena> Arena, llvm::Optional<SyntaxNodeId> NodeId);
297304

298305
/// Compute the node's text length by summing up the length of its childern
299306
size_t computeTextLength() {
@@ -307,18 +314,30 @@ class RawSyntax final
307314
return TextLength;
308315
}
309316

317+
mutable std::atomic<int> RefCount;
318+
310319
public:
311320
~RawSyntax();
312321

322+
// This is a copy-pased implementation of llvm::ThreadSafeRefCountedBase with
323+
// the difference that we do not delete the RawSyntax node's memory if the
324+
// node was allocated within a SyntaxArena and thus doesn't own its memory.
325+
void Retain() const { RefCount.fetch_add(1, std::memory_order_relaxed); }
326+
313327
void Release() const {
314-
if (Bits.Common.ManualMemory)
315-
return;
316-
return llvm::ThreadSafeRefCountedBase<RawSyntax>::Release();
317-
}
318-
void Retain() const {
319-
if (Bits.Common.ManualMemory)
320-
return;
321-
return llvm::ThreadSafeRefCountedBase<RawSyntax>::Retain();
328+
int NewRefCount = RefCount.fetch_sub(1, std::memory_order_acq_rel) - 1;
329+
assert(NewRefCount >= 0 && "Reference count was already zero.");
330+
if (NewRefCount == 0) {
331+
if (Arena) {
332+
// The node was allocated inside a SyntaxArena and thus doesn't own its
333+
// own memory region. Hence we cannot free it. It will be deleted once
334+
// the last RawSyntax node allocated with it will release its reference
335+
// to the arena.
336+
this->~RawSyntax();
337+
} else {
338+
delete this;
339+
}
340+
}
322341
}
323342

324343
/// \name Factory methods.
@@ -327,33 +346,29 @@ class RawSyntax final
327346
/// Make a raw "layout" syntax node.
328347
static RC<RawSyntax> make(SyntaxKind Kind, ArrayRef<RC<RawSyntax>> Layout,
329348
SourcePresence Presence,
330-
SyntaxArena *Arena = nullptr,
349+
RC<SyntaxArena> Arena = nullptr,
331350
llvm::Optional<SyntaxNodeId> NodeId = llvm::None);
332351

333352
/// Make a raw "token" syntax node.
334353
static RC<RawSyntax> make(tok TokKind, OwnedString Text,
335354
ArrayRef<TriviaPiece> LeadingTrivia,
336355
ArrayRef<TriviaPiece> TrailingTrivia,
337356
SourcePresence Presence,
338-
SyntaxArena *Arena = nullptr,
357+
RC<SyntaxArena> Arena = nullptr,
339358
llvm::Optional<SyntaxNodeId> NodeId = llvm::None);
340359

341360
/// Make a missing raw "layout" syntax node.
342-
static RC<RawSyntax> missing(SyntaxKind Kind, SyntaxArena *Arena = nullptr) {
361+
static RC<RawSyntax> missing(SyntaxKind Kind,
362+
RC<SyntaxArena> Arena = nullptr) {
343363
return make(Kind, {}, SourcePresence::Missing, Arena);
344364
}
345365

346366
/// Make a missing raw "token" syntax node.
347367
static RC<RawSyntax> missing(tok TokKind, OwnedString Text,
348-
SyntaxArena *Arena = nullptr) {
368+
RC<SyntaxArena> Arena = nullptr) {
349369
return make(TokKind, Text, {}, {}, SourcePresence::Missing, Arena);
350370
}
351371

352-
static RC<RawSyntax> getToken(SyntaxArena &Arena, tok TokKind,
353-
OwnedString Text,
354-
ArrayRef<TriviaPiece> LeadingTrivia,
355-
ArrayRef<TriviaPiece> TrailingTrivia);
356-
357372
/// @}
358373

359374
SourcePresence getPresence() const {

include/swift/Syntax/SyntaxArena.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,26 @@
1717
#ifndef SWIFT_SYNTAX_SYNTAXARENA_H
1818
#define SWIFT_SYNTAX_SYNTAXARENA_H
1919

20+
#include "llvm/ADT/IntrusiveRefCntPtr.h"
2021
#include "llvm/Support/Allocator.h"
2122

2223
namespace swift {
2324
namespace syntax {
2425

2526
/// Memory manager for Syntax nodes.
26-
class SyntaxArena {
27+
class SyntaxArena : public llvm::ThreadSafeRefCountedBase<SyntaxArena> {
2728
SyntaxArena(const SyntaxArena &) = delete;
2829
void operator=(const SyntaxArena &) = delete;
2930

30-
public:
31-
struct Implementation;
32-
Implementation &Impl;
31+
llvm::BumpPtrAllocator Allocator;
3332

34-
SyntaxArena();
35-
~SyntaxArena();
33+
public:
34+
SyntaxArena() {}
3635

37-
llvm::BumpPtrAllocator &getAllocator() const;
38-
void *Allocate(size_t size, size_t alignment);
39-
void *AllocateRawSyntax(size_t size, size_t alignment);
36+
llvm::BumpPtrAllocator &getAllocator() { return Allocator; }
37+
void *Allocate(size_t size, size_t alignment) {
38+
return Allocator.Allocate(size, alignment);
39+
}
4040
};
4141

4242
} // namespace syntax

include/swift/Syntax/SyntaxBuilders.h.gyb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class SyntaxArena;
3333
% if node.is_buildable():
3434
% child_count = len(node.children)
3535
class ${node.name}Builder {
36-
SyntaxArena *Arena = nullptr;
36+
RC<SyntaxArena> Arena = nullptr;
3737
RC<RawSyntax> Layout[${child_count}] = {
3838
% for child in node.children:
3939
nullptr,
@@ -42,7 +42,7 @@ class ${node.name}Builder {
4242

4343
public:
4444
${node.name}Builder() = default;
45-
${node.name}Builder(SyntaxArena &Arena) : Arena(&Arena) {}
45+
${node.name}Builder(RC<SyntaxArena> Arena) : Arena(Arena) {}
4646

4747
% for child in node.children:
4848
${node.name}Builder &use${child.name}(${child.type_name} ${child.name});

0 commit comments

Comments
 (0)