Skip to content

[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

Merged
merged 2 commits into from
Mar 2, 2021

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Feb 25, 2021

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.

70% of all changes are just adjustments in unittests/Syntax The actual logic change is fairly small (<400 LOC).

auto MinusOne = SyntaxFactory::makePrefixOperatorExpr(Minus,
SyntaxFactory::makeIntegerLiteralExpr(OneDigits));
RC<SyntaxArena> Arena = SyntaxArena::make();
auto ReturnKW = SyntaxFactory::makeReturnKeyword("", " ", Arena);
Copy link
Contributor

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...

Copy link
Member Author

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.

@ahoppen ahoppen force-pushed the pr/dont-ref-count-rawsyntax branch from 5ee74e1 to 1764ccf Compare February 26, 2021 16:27
@ahoppen ahoppen marked this pull request as ready for review February 26, 2021 16:32
@ahoppen ahoppen requested a review from rintaro February 26, 2021 16:32
@ahoppen
Copy link
Member Author

ahoppen commented Feb 26, 2021

@swift-ci Please test

@ahoppen
Copy link
Member Author

ahoppen commented Feb 26, 2021

@swift-ci Please ASAN test

raw = ${make_missing_child(child)};
{
// make_missing_child access the 'Arena' variable. Create it.
const RC<SyntaxArena> &Arena = getRaw()->getArena();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is reference &?

Copy link
Member Author

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);
Copy link
Member

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`?

Copy link
Member Author

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?

Copy link
Member

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,
Copy link
Member

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,
Copy link
Member

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;
Copy link
Member

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?

Copy link
Member Author

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); }
Copy link
Member

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.

Comment on lines 89 to 91
/// Create a new root node
SyntaxData(AbsoluteRawSyntax AbsoluteRaw, const RC<SyntaxArena> &Arena)
: AbsoluteRaw(AbsoluteRaw), Parent(nullptr), Arena(Arena) {}
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are absolutely right!

ahoppen added 2 commits March 1, 2021 09:43
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.
@ahoppen ahoppen force-pushed the pr/dont-ref-count-rawsyntax branch from 1764ccf to 9ff88b2 Compare March 1, 2021 08:44
@ahoppen
Copy link
Member Author

ahoppen commented Mar 1, 2021

@swift-ci Please test

@swift-ci
Copy link
Contributor

swift-ci commented Mar 1, 2021

Build failed
Swift Test OS X Platform
Git Sha - 9ff88b2

Copy link
Member

@rintaro rintaro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great!

Comment on lines 77 to +82
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;
Copy link
Member

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.

Copy link
Member Author

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.

@ahoppen
Copy link
Member Author

ahoppen commented Mar 2, 2021

@swift-ci Please test macOS

@ahoppen
Copy link
Member Author

ahoppen commented Mar 2, 2021

@swift-ci Please test Windows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants