Skip to content

Replace SyntaxTree caching with IDE document state preservation#50490

Merged
16 commits merged intodotnet:mainfrom
sharwell:cache-sg-api
Mar 25, 2021
Merged

Replace SyntaxTree caching with IDE document state preservation#50490
16 commits merged intodotnet:mainfrom
sharwell:cache-sg-api

Conversation

@sharwell
Copy link
Copy Markdown
Contributor

@sharwell sharwell commented Jan 14, 2021

Supersedes #50171

Fixes AB#1254804

  • Create a public API for syntax tree caching
  • Move caching to the Workspaces/Host (IDE scenarios), and disable caching for command line builds
  • Remove compiler caching of SyntaxTree from source generators
  • Update GeneratorDriver to return lazy SyntaxTree instances instead of parsing them eagerly
  • Update the IDE layers to reuse previous text, version, and trees if the inputs didn't change

@ghost ghost added the Area-Compilers label Jan 14, 2021
@sharwell sharwell changed the title Create an API to configure caching of syntax trees Replace SyntaxTree caching with IDE document state preservation Jan 15, 2021
SourceText text,
CSharpParseOptions? options = null,
string path = "",
CancellationToken cancellationToken = default)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Being internal, consider making all the args required?

SolutionState solution,
Compilation compilation,
ImmutableArray<SourceGeneratedDocumentState>? authoritativeGeneratedDocuments,
ImmutableArray<SourceGeneratedDocumentState> nonAuthoritativeGeneratedDocuments,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Need @jasonmalinowski 's views on this. I forget what FinalizeCompilation means. I thought it had meant: move the compilation to the final state. Which means it seems odd to be using incorrect states here. Is that not the case? Is FinalizeCompilation for a different purpose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

still need @jasonmalinowski 's input here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So yes, FinalizeCompilation is the final (hah!) called when we're producing a compilation. I think what might address your concern is if instead of nonAuthoritativeGeneratedDocuments it was called priorGeneratedDocumentsThatMayBeReused or something along those lines to clarify that:

  1. These are from a prior run, and may be stale/old.
  2. It's not required that they are actually consumed.

@sharwell sharwell marked this pull request as ready for review January 29, 2021 16:07
@sharwell sharwell requested review from a team as code owners January 29, 2021 16:07
@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

@jasonmalinowski can you PTAL as this hits generators, compilation trackers, and is a big net perf win for an important scenario.

// We can reuse the previous TextAndVersion if the checksum from the text matches.
if (previousState is null
|| !previousState.TryGetTextAndVersion(out var textAndVersion)
|| Checksum.From(textAndVersion.Text.GetChecksum()) != Checksum.From(generatedSourceText.GetChecksum()))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: cnosider just a function that compares the results of GetChecksum so you don't have to wrap in another Checksum object.

Comment thread src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentState.cs Outdated
// recoverable text but not a new tree, it would mean tree.GetText() could still potentially return
// the non-recoverable text, but asking the document directly for it's text would give a recoverable
// text with a different object identity. We only need to create a recoverable text if the source
// changed; otherwise, the reused text source would be recoverable.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wow. do we have a test to validate this :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't know if it's even possible to validate this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In the Workspace tests we have helpers that create workspace instances both with and without recoverable trees, so you have plenty of tools at your disposal.

(That said I think a greater simplification here can eliminate a bunch of this...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If this is staying around, consider adding an assert that CreateRecoverableText isn't called with a recoverable text to start with? or if it is, just make it no-op?

Base automatically changed from master to main March 3, 2021 23:53
- Don't take a cancellation token, since the whole point is not to
  do expensive work
- Just keep fields and parameters in a consistent order
Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

So I've simplified the Create() method and added some tests, which did catch one issue of this not working in the mainline path. So I think this is now good to take, but somebody else shoudl review my work or otherwise this is somewhat cheating.

private readonly ImmutableDictionary<string, ReportDiagnostic> _diagnosticOptions;
private CSharpSyntaxNode? _lazyRoot;

internal LazySyntaxTree(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm somewhat on the fence about introducing a new type of SyntaxTree but I agree it makes sense given the API we have today.

I actually think we should avoid returning realized SyntaxTree instances from the generator at all, and instead have a method that does it lazily. Given that that is a breaking change however, lets punt it down the road, and take this approach for now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agreed; I think this is the best approach for now, and we can consider using the next major version to just break the API.

@jasonmalinowski jasonmalinowski requested a review from cston March 25, 2021 00:30

_text = text;
_options = options;
_path = path ?? string.Empty;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

path [](start = 24, length = 4)

path is declared as not null.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So for regular trees this is what we're doing as well, which seems odd. We always silently converted a null going in to an empty string. When we annotated the methods we then kept them as non-nullable to discourage people from passing null (since it's probably not entirely what they wanted....) but it was still allowed and silently changed.

This is matching the other types. I can change this, but it'll be inconsistent unless I change the other types as well. Thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's keep it as is, and consistent. Thanks for the explanation.


In reply to: 601792055 [](ancestors = 601792055)

Comment thread src/Compilers/VisualBasic/Portable/Syntax/VisualBasicSyntaxTree.LazySyntaxTree.vb Outdated
Copy link
Copy Markdown

@ghost ghost left a comment

Choose a reason for hiding this comment

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

Auto-approval

@ghost ghost merged commit 989327e into dotnet:main Mar 25, 2021
@ghost ghost added this to the Next milestone Mar 25, 2021
@sharwell sharwell deleted the cache-sg-api branch March 26, 2021 16:39
@allisonchou allisonchou modified the milestones: Next, 16.10.P2 Mar 29, 2021
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants