-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[libSyntax] Don't reference count RawSyntax #36165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
auto MinusOne = SyntaxFactory::makePrefixOperatorExpr(Minus, | ||
SyntaxFactory::makeIntegerLiteralExpr(OneDigits)); | ||
RC<SyntaxArena> Arena = SyntaxArena::make(); | ||
auto ReturnKW = SyntaxFactory::makeReturnKeyword("", " ", Arena); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not for this PR, but maybe it's time we make SyntaxFactory
's methods instance methods and have it own the area in which new nodes are created...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like it might be a good idea. I’ll take a look at it later.
5ee74e1
to
1764ccf
Compare
@swift-ci Please test |
@swift-ci Please ASAN test |
lib/Syntax/SyntaxNodes.cpp.gyb
Outdated
raw = ${make_missing_child(child)}; | ||
{ | ||
// make_missing_child access the 'Arena' variable. Create it. | ||
const RC<SyntaxArena> &Arena = getRaw()->getArena(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this is reference &
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It used to be a reference to avoid ref-counting the arena. But of course it shouldn’t be since RawSyntax::getArena
is no longer returning a reference. Good catch.
llvm::Optional<SyntaxNodeId> NodeId); | ||
RawSyntax(SyntaxKind Kind, ArrayRef<const RawSyntax *> Layout, | ||
size_t TextLength, SourcePresence Presence, | ||
const RC<SyntaxArena> &Arena, llvm::Optional<SyntaxNodeId> NodeId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain why Arena
must be ref counted here`?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn’t technically have to be. It’s more a design choice.
I always think of SyntaxArena
as a ref-counted object and thus, we should write RC<SyntaxArena>
. RawSyntax
's use of SyntaxArena *
is more like an unowned reference in Swift, which I consider an implementation detail of RawSyntax
. As such, I decided that all methods on RawSyntax
should operate on RC<SyntaxArena>
instead of SyntaxArena *
.
This also meant that the API is easier to use since we have isolated the conversion of RC<SyntaxArena>
-> SyntaxArena *
to two location instead of having to call RC<SyntaxArena>::get
whenever invoking one of these functions.
Does this sound good to you or do you disagree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. No objection. Thank you :)
const RC<SyntaxArena> &Arena = SyntaxArena::make(), | ||
static const RawSyntax * | ||
make(SyntaxKind Kind, ArrayRef<const RawSyntax *> Layout, size_t TextLength, | ||
SourcePresence Presence, const RC<SyntaxArena> &Arena, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
|
||
static const RawSyntax * | ||
makeAndCalcLength(SyntaxKind Kind, ArrayRef<const RawSyntax *> Layout, | ||
SourcePresence Presence, const RC<SyntaxArena> &Arena, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
/// The \c SyntaxArena's in this set are manually retained when added to this | ||
/// list by \c addChildArena and manually released in the destructor of this | ||
/// arena. | ||
llvm::SmallPtrSet<SyntaxArena *, 4> ChildArenas; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a mechanism to prevent circular references?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, there’s not. But I can’t think of a real-world scenario where a circular reference might occur since newer arenas only ever reference older arenas (older = from a previous parsing run). Might be worth adding a check in assert builds though, just to be safe for the future.
return make(TokKind, Text, /*TextLength=*/0, {}, {}, | ||
SourcePresence::Missing, Arena); | ||
} | ||
|
||
/// @} | ||
|
||
/// Return the arena in which this \c RawSyntax node has been allocated. | ||
/// Keep in mind that the \c RawSyntax node *does not* retain the arena. | ||
RC<SyntaxArena> getArena() const { return RC<SyntaxArena>(Arena); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like we should just return a pointer here.
include/swift/Syntax/SyntaxData.h
Outdated
/// Create a new root node | ||
SyntaxData(AbsoluteRawSyntax AbsoluteRaw, const RC<SyntaxArena> &Arena) | ||
: AbsoluteRaw(AbsoluteRaw), Parent(nullptr), Arena(Arena) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
SyntaxData(AbsoluteRawSyntax AbsoluteRaw)
: AbsoluteRaw(AbsoluteRaw), Arena(AbsoluteRaw.getRaw()->getArena()) {}
is safer if there's no need to refer a different Arena
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are absolutely right!
Instead, only reference count the SyntaxArena that the RawSyntax nodes live in. The user of RawSyntax nodes must guarantee that the SyntaxArena stays alive as long as the RawSyntax nodes are being accessed. During parse time, the SyntaxTreeCreator holds on to the SyntaxArena in which it creates RawSyntax nodes. When inspecting a syntax tree, the root SyntaxData node keeps the SyntaxArena alive. The change should be mostly invisible to the users of the public libSyntax API. This change significantly decreases the overall reference-counting overhead. Since we were not able to free individual RawSyntax nodes anyway, performing the reference-counting on the level of the SyntaxArena feels natural.
This fixes the SwiftSyntaxTests that were broken because the SyntaxFactory API now requires an Arena in which the nodes should be created.
1764ccf
to
9ff88b2
Compare
@swift-ci Please test |
Build failed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great!
RC<RefCountedBox<SyntaxData>> Parent; | ||
|
||
/// If this node is the root of a Syntax tree (i.e. \c Parent is \c nullptr ), | ||
/// the arena in which this node's \c RawSyntax node has been allocated. | ||
/// This keeps this \c RawSyntax nodes referenced by this tree alive. | ||
RC<SyntaxArena> Arena; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not in this PR: since Parent
and Arena
are mutually exclusive, it would be great if these can be a tagged union.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There’s a reason why it’s not a union. In the next PR, I’ll split SyntaxData
into a SyntaxDataRef
that doesn’t retain the arena (but has a parent) and a subclass SyntaxData
which retains the arena. So I can’t easily reuse the storage.
@swift-ci Please test macOS |
@swift-ci Please test Windows |
Instead, only reference count the
SyntaxArena
that theRawSyntax
nodes live in. The user ofRawSyntax
nodes must guarantee that theSyntaxArena
stays alive as long as theRawSyntax
nodes are being accessed.During parse time, the
SyntaxTreeCreator
holds on to theSyntaxArena
in which it createsRawSyntax
nodes. When inspecting a syntax tree, the rootSyntaxData
node keeps theSyntaxArena
alive. The change should be mostly invisible to the users of the public libSyntax API.This change significantly decreases the overall reference-counting overhead. Since we were not able to free individual
RawSyntax
nodes anyway, performing the reference-counting on the level of theSyntaxArena
feels natural.70% of all changes are just adjustments in
unittests/Syntax
The actual logic change is fairly small (<400 LOC).