Skip to content

Compiler: Make intermediate nodes a bit more efficient#11931

Merged
DustinCampbell merged 24 commits into
dotnet:mainfrom
DustinCampbell:intermediate-nodes
Jun 12, 2025
Merged

Compiler: Make intermediate nodes a bit more efficient#11931
DustinCampbell merged 24 commits into
dotnet:mainfrom
DustinCampbell:intermediate-nodes

Conversation

@DustinCampbell

@DustinCampbell DustinCampbell commented Jun 9, 2025

Copy link
Copy Markdown
Member

This pull request focuses on two properties of IntermediateNode: Diagnostics and Annotations.

  • IntermediateNode.Diagnostics is constructed most of the time, even when there aren't any diagnostics. I've reworked how diagnostics are stored and collected to avoid creating any storage until a diagnostic is actually added. With this change, the RazorDiagnosticCollection type can be removed entirely.
  • IntermediateNode.Annotations works like a property bag that is backed by an ItemCollection. This is pretty inefficient. First of all, ItemCollection uses a ConcurrentDictionary under the hood, which is completely unnecessary in the compiler's synchronous code paths. Plus, all of the data stored in Annotations (some of which are effectively bools) can just be stored as properties on the strongly-typed IntermediateNode types themselves.

I recommend reviewing commit-by-commit, as I tried to keep each separate and cohesive.


CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2728082&view=results
Test Insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/642908
Toolset Run: https://dev.azure.com/dnceng/internal/_build/results?buildId=2728084&view=results

- Remove DebuggerDisplayFormatter
- Merge IntermediateNodeFormatter and IntermediateNodeFormatterBase
- Seal IntermediateNodeFormatter
- Replace TextWriter with StringBuilder
- Use pooled StringBuilder to create debugger display strings in IntermediateNode
- Cache the short name for each type in a static dictionary
- Use Debug.Asserts rather than throwing ArgumentNullException
- Avoid allocating extra intermediate strings when escaping '\r', '\n', and '\t'.
- Avoid using LINQ
- Don't use string.Join(...) to create intermediate string to append to StringBuilder
- Add nullability annotations
- Other small bits of clean up
- Add nullability annotations
- Use expression-bodied members and compound assignment for Annotations and Diagnostics properties
- Use nameof in [DebuggerDisplay]
- Rename DebuggerToString() to GetDebuggerDisplay()
- Make IntermediateNode.Diagnostics return an ImmutableArray<RazorDiagnostic> rather than exposing a lazily created RazorDiagnosticCollection.
- Add IntermediateNode.AddDiagnostic(...) method to add a diagnostic.
- Add IntedmediateNode.AddDiagnosticsFromNode(...) to add all diagnostics from another IntermediateNode.
- Cache the ImmutableArray<RazorDiagnostic> in a field rather than recomputing it each time from _diagnosticsBuilder.
This type is no longer used and can be removed.
@DustinCampbell DustinCampbell requested review from a team as code owners June 9, 2025 23:55

@ToddGrun ToddGrun left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

:shipit:

@ToddGrun

Copy link
Copy Markdown

This looks amazing to me

- IntermediateNodeFormatter: Move const to the method where it is used.
- ComponentBindLoweringPass: Add missing type pattern to switch expression.
- ComponentMarkupDiagnosticPass: Replace expressions with existing local.
@DustinCampbell DustinCampbell merged commit a81a6da into dotnet:main Jun 12, 2025
11 checks passed
@dotnet-policy-service dotnet-policy-service Bot added this to the Next milestone Jun 12, 2025
@DustinCampbell DustinCampbell deleted the intermediate-nodes branch June 12, 2025 15:41
DustinCampbell added a commit that referenced this pull request Jun 13, 2025
`ItemCollection` is essentially a property bag that results in
dictionaries being used instead of plain ol' fields, and it's been
overused in Razor. I've been trying to get rid of it for nearly a year.
#10720, #10764, #11360, and #11931 each represent changes that remove a
use of `ItemCollection`. This pull request finally gets rid of
`ItemCollection` completely.

The last use of `ItemCollection` is the `RazorCodeDocument.Items`, which
is used by the compiler to store various objects during compilation, and
tooling uses it to cache some expensive-to-create data. This change
stores all of the compiler objects on `RazorCodeDocument` directly. The
data cached by tooling is stored on the side in a
`ConditionalWeakTable`.

As part of this, I've refactoring the
`RazorCodeDocument.TryComputeNamespace` extension method to be more
understandable and maintainable.

----
CI Build:
https://dev.azure.com/dnceng/internal/_build/results?buildId=2728842&view=results
Test Insertion:
https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/643246
Toolset Run:
https://dev.azure.com/dnceng/internal/_build/results?buildId=2728843&view=results
@RikkiGibson RikkiGibson modified the milestones: Next, 18.0 P1 Aug 20, 2025
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.

6 participants