-
Notifications
You must be signed in to change notification settings - Fork 468
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
Analyzer suggestion: Improper incremental pipeline #6352
Comments
I think checking every step is too strong of a constraint (some pipelines need to combine syntax w/symbol + compilation + analyzer options, etc), but having some mechanism to say "this step should be incremental" for a given intermediate step would be good. Maybe introduce an attribute that could be placed on a constant value? Then when that value is used through the constant in a call to |
From previous discussions with @333fred, it's bad to have symbols as part of the whole pipeline (any step, if I understand correctly). |
It's only bad to have symbols if you provide a custom comparer that compares symbols from different compilations as equal. Without the custom comparer, they don't root the Compilation instance across multiple runs of the generator. With a custom comparer, it can cause the Compilation objects to be rooted for an indeterminate amount of time. I think having an info-level diagnostic for symbols anywhere in the pipeline is okay but having it as a warning would cause a number of warnings in cases where it's currently impossible to fix (and handled at later steps that actually produce a cache-friendly state object). |
@jkoritzinsky this is absolutely not true. You should not have symbols in the pipeline, as that can root previous compilations and prevent large swaths of memory from being freed. |
Yeah, symbols in any part of teh pipeline as values in the tables just won't work. They will always compare unequal, causing all entries to be recomputed, and all further steps to happen. So, in effect, you can have them in the tables... it just means you have no incrementality there. :) Note: source symbols root compilations. And metadata symbols root metadata assemblies. The latter is not great, but the former is bad baddy bad bad :)
Can you give examples? |
@jkoritzinsky here's the thing though. That intermediary table you're generating ends up serving no purpose. It literally will be 100% dumped/recomputed on every run. So if you have a later step that produces an actual cache-friendly state object, you should just have that be the result of this stage instead of having an intermediary stage that does nothing :) |
That brings me to the same question I've asked multiple times and never gotten an answer for. How should I write a source generator such that I combine the information from an This is the case in the LibraryImportGenerator, JSImportGenerator, JSExportGenerator, and VTableIndexStubGenerator (all of the interop generators). I've run this specifically past both of you and you've both said that it's fine. I understand that the intermediate tables are thrown away and I'm fine with that. They're literally only there because I can't get all of the information into a single step at once. I have a step that produced the cache-friendly object as soon as I have all of the necessary information available in a single step. I can't have it all in the "select" function in If there's a solution here that doesn't cause any problems, please tell me. I've asked literally every time this has come up and we go through this same conversation, whether on Discord or GitHub. |
Also, please explain to me how putting a symbol that points at source into a table that will be thrown away on the next execution (due to the fact that we're not using a custom comparer) is any worse for keeping Compilations alive than having a table for a node constructed with I want to follow best practices with source generators, but it's getting increasingly difficult when I'm told that what we're doing is "fine" one day and "not fine" the next, especially when the same person tells me it's fine again a few hours later only for it to come up again in a few months. |
First, teh answer may always be: there may be no way to accomplish what you want. Generators are not a fully flexible system for all purposes. They have limitations, and some things are def out of scope of them. In those cases, the best path forward is to make an official request for a suitable API (if one can even be built), have is scheduled into Roslyn's upcoming schedule, and then wait for it to be ready before writing the generator. Now, wrt to the above. First, i'm not sure what AnalyzerConfigurationProvider. Can you give some links on that. Second, you can certainly use IMethodSymbols and the Compilation. But you'd do so in the single step off the context.CompilationProvider that goes from that to hte model objects you want to use later down the pipeline.
Could you point to where you're doing this?
Yup. Seems problematic. Do you have to use generators? It sounds like we shoudl make an explicit feature request here on the generator system to provide something suitable for your needs. Have these generators shipped out to customers? If not, can we postpone them from doing so until we can get to a good place wrt to generators :) Thanks! |
So tehre's a difference between convos on Discord (which are basically just the team putting in their spare time to try to help out), versus an official request for support from one team to another through our normal channels. If you have a team need for this, it can be asked for, with a specific timeline, and our leads can hammer out how to actually fit that into the schedule and what needs to be shuffled around to make it happen. Right now, anything else is just us monitoring the situation and going "Welp... no good solutions... hope that's not too much of an issue in practice". :) I'm personally just tangentially interested here. Me and fred have no official responsibilities otherwise in the generator space. So we're advising, but leaving it to teh SG owners to actually make decisions/adjustments. And that is best served with official requests for specific functionality that is needed by partner teams to unblock scenarios. |
I've been trying to work as closely as possible with the Roslyn team including @chsienki and @sharwell to make sure we're implementing the generators correctly in the .NET interop space (I drove the design and provided the implementation of the "incremental step tracking API"). The last time this came up (links to our conversation below), you specifically said that symbols can be used if they are not part of your model. As stated, they aren't part of our model, but they are passed between steps on the way to making our model. https://discord.com/channels/143867839282020352/598678594750775301/1016794466952155190 When we spoke about introducing new APIs the last time, the only idea that came up was to provide new APIs that effectively combine
Sorry, I was referring to the
How do I get from the public void Initialize(IncrementalGeneratorInitializationContext context)
{
var cachedNode = context.ForAttributeWithMetadataName("MyAttribute",
static (node, ct) => node is MethodDeclarationSyntax,
static (context, ct) => context.TargetSymbol is IMethodSymbol methodSymbol
? methodSymbol
: null)
.Where(
static modelData => modelData is not null)
.Combine(context.CompilationProvider)
.Select((data, ct) => ProduceNonSymbol(data.Left, data.Right))
.WithTrackingName("ContextWithNoSymbols");
}
private ContextWithNoSymbols ProduceNonSymbol(IMethodSymbol method, Compilation compliation) { /* ... */ }
private sealed record ContextWithNoSymbols { /* ... */ }
Yes we need to use generators as we make heavy usage of type system information to provide our marshalling model. The entire new direction of .NET interop is heavily built on the Roslyn source generation technology, and we've been involved with Roslyn team since the v1 API was first implemented and along every step of the way. LibraryImportGenerator, JSImportGenerator, and JSExportGenerator all shipped in .NET 7. VtableIndexStubGenerator is likely shipping in .NET 8 (along with the incoming ComInterfaceGenerator). Here's a snippet of the LibraryImportGenerator: In lines 50-97, we calculate all of the information for each of our intermediate steps. In the step defined on line 98, we produce our no-symbol model (there is a bug today that in one case we keep symbols, but that is in the process of being fixed in dotnet/runtime#79051). We expect zero incrementality before the "CalculateStubInformation" step, and we have basic tests to validate incrementality of the "CalculateStubInformation" step using the step tracking API to ensure we don't regress (with the test suite expanding with more regression tests whenever we find an issue, including liveness tests to ensure we don't root additional Compilation objects on top of what the GeneratorDriver is already rooting). It is untenable for us to grab all possible information we could ever need from the symbols as we'd effectively have to build our own copy of the symbol API to ensure we grab all the information we could possibly need.
I'm willing to work with the team to improve the space (as I've done before), but when people tell me things are fine and then come back and tell me "You should have made an official request to improve this even though we told you it was fine then but it's not and it never was" it makes it difficult to make sure we're doing the right thing. It also doesn't help that when I contribute fixes to the SG space in Roslyn, sometimes they're just ignored for months until someone on the Roslyn team stumbles on the same bug and fixes it in their own PR. |
Note that this one is not sufficient due to dotnet/runtime#77183 (comment). TL;DR |
Describe the problem you are trying to solve
Incremental source generators are sometimes designed in a non-cache-friendly way. This happens when pipeline contains types that are not equatable across different compilations. These types can be
ISymbol
s, orCompilation
s. Maybe alsoSyntaxNode
? Not sure.Describe suggestions on how to achieve the rule
The analyzer will look for incremental generator pipelines that are not cache-friendly, and issue a diagnostic.
More detailed, the analyzer will look for
Register[Implementation]SourceOutput<T>
invocations whereT
violates incrementality. Violation is defined as follows:T
doesn't implementIEquatable<T>
(this includesCompilation
)T
isISymbol
.T
isn'tImmutableArray<T>
T
is a tuple where one of the tuple elements violates incrementality.T
is a user-defined model where one of the members violates incrementality.Alternatively, and more strongly, we can detect any part of the pipeline that violates incrementality, not only
Register[Implementation]SourceOutput
. This should exclude the rootcontext.CompilationProvider
, etc.Additional context
I have seen this a lot, and in the dotnet organization. e.g, dotnet/runtime#76119 and https://github.com/dotnet/winforms/blob/117451b053c2fbda2f9caf9e1ef280f412b5aad1/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationGenerator.cs#L74-L81
The text was updated successfully, but these errors were encountered: