Skip to content

Compiler: Clean up and reduce allocations in declaration intermediate nodes #12024

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

DustinCampbell
Copy link
Member

@DustinCampbell DustinCampbell commented Jul 8, 2025

This pull request cleans up and reduces allocations in declaration-level intermediate nodes: NamespaceDeclarationIntermediateNode, ClassDeclarationIntermediateNode, MethodDeclarationIntermediateNode, FieldDeclarationIntermediateNode, PropertyDeclarationIntermediateNode, and related types. In particular, various collection properties are no longer types as IList<T> and pre-assigned with a List<T> instance. Instead, these properties now return ImmutableArray<T>.

Truth be told, this doesn't result in a large reduction in allocations, but this pull request paves the way for future changes.


CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2745495&view=results
Toolset Run: https://dev.azure.com/dnceng/internal/_build/results?buildId=2745496&view=results

@DustinCampbell DustinCampbell requested review from a team as code owners July 8, 2025 20:30
visitor.VisitClassDeclaration(this);
}
public void UpdateModifiers(params ImmutableArray<string> modifiers)
=> _modifiers = modifiers.NullToEmpty();
Copy link
Contributor

Choose a reason for hiding this comment

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

modifiers.NullToEmpty();

Is a default IA possible here due to it being a params list?

Copy link
Member Author

@DustinCampbell DustinCampbell Jul 8, 2025

Choose a reason for hiding this comment

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

Sure. Any caller can specifically pass "default" with no compiler warning.

for (var i = 0; i < typeParameters.Count; i++)
var first = true;

foreach (var typeParameter in typeParameters)
Copy link
Contributor

Choose a reason for hiding this comment

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

foreach (var typeParameter in typeParameters)

Could this use the has* technique used later in the method to not need the first local or check per loop iteration?

Copy link
Contributor

Choose a reason for hiding this comment

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

redacted. Ignore me. :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Absolutely not! 😄

}
else
{
writer.Write((string)typeParameter.ParameterName);
Copy link
Contributor

Choose a reason for hiding this comment

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

typeParameter.ParameterName

Dumb question: why is the cast needed?

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's definitely not. Good catch!

}

for (var i = 0; i < suppressWarnings.Count; i++)
for (var i = 0; i < suppressWarnings.Length; i++)
Copy link
Contributor

Choose a reason for hiding this comment

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

for

nit: could use foreach

method.Modifiers.Add("public");
method.Modifiers.Add("async");
method.Modifiers.Add("override");
method.UpdateModifiers("public", "async", "override");
Copy link
Contributor

Choose a reason for hiding this comment

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

method.UpdateModifiers("public", "async", "override");

I think this is right, but most of the other places that called the Update* methods were replacing code that did a Clear potentially followed by some Adds. I'm not seeing a Clear in the replaced code here, but I assume it was going to be empty anyways,.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup, that's right.

Copy link
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

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

:shipit:

public string ClassName { get; set; }
public ImmutableArray<string> Modifiers
{
get => _modifiers;
Copy link
Member

Choose a reason for hiding this comment

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

Is field available here? If so, maybe we could use it for these properties?


visitor.VisitClassDeclaration(this);
}
public void UpdateModifiers(params ImmutableArray<string> modifiers)
Copy link
Member

Choose a reason for hiding this comment

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

What's the benefit to having these methods over just having a set instead of an init on the property? I lean towards just having the setter, even if that means not being able to use the params, simply to avoid the duplication of calling NullToEmpty multiple times.

Copy link
Member Author

@DustinCampbell DustinCampbell Jul 10, 2025

Choose a reason for hiding this comment

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

Generally, I was trying to get away from the get; set; pattern used for intermediate nodes because the state of the data is super unclear. It's very hard to determine what data is or isn't supposed to be set. I'm happy to use field and do validation and NullToEmpty in the setters (and the initializer?), but I was hoping to make setters super rare in the intermediate nodes and use "Update" methods for properties that are supposed to be update.

@DustinCampbell DustinCampbell changed the base branch from dev/dustinca/intermediate-node-visitors to main July 10, 2025 00:56
- Enable nullability
- Add constructor
- Make `IsPrimaryNamespace` property read-only
- Remove unnecessary argument null check
- Lazily create `Children` collection
- General clean up
- Enable nullability
- Add constructor
- Make `IsPrimaryClass` property read-only
- Remove unnecessary argument null check
- Lazily create `Children` collection
- Change `Modifiers` property to return an `ImmutableArray<string>` rather than an `IList<string>` (backed by a `List<string>`).
- Change `Interfaces` property to return an `ImmutableArray<IntermediateToken>` rather than an `IList<IntermediateToken>` (backed by a `List<IntermediateToken>`).
- Change `TypeParameters` property to return an `ImmutableArray<TypeParameter>` rather than an `IList<TypeParameter>` (backed by a `List<TypeParameter>`).
- General clean up
- Enable nullability
- Add constructor
- Make properties readonly
Because `CodeRenderingContext` owns the `CodeWriter` instance used for code gen, and the `BuildNamespace` extension method targets `CodeWriter` and also takes a `CodeRenderingContext`, we can change it  can target `CodeRenderingContext`.
Because `CodeRenderingContext` owns the `CodeWriter` instance used for code gen, and the `BuildClassDeclaration` extension method targets `CodeWriter` and also takes a `CodeRenderingContext`, we can change it  can target `CodeRenderingContext`.
- Enable nullability
- Add constructor
- Remove unnecessary argument null check
- Change `Modifiers` property to return an `ImmutableArray<string>` rather than an `IList<string>` (backed by a `List<string>`).
- Change `SupressWarnings` property to return an `ImmutableArray<string>` rather than an `IList<string>` (backed by a `List<string>`).
- Make `FieldName`, `FieldType`, and `IsTagHelperField` properties read-only.
- Enable nullability
- Add constructor
- Remove unnecessary argument null check
- Change `Modifiers` property to return an `ImmutableArray<string>` rather than an `IList<string>` (backed by a `List<string>`).
- Make `PropertyName`, `ProeprtyType`, and `PropertyExpression` properties read-only.
…ngContext

Because `CodeRenderingContext` owns the `CodeWriter` instance used for code gen, and the `WritePropertyDeclaration` and `WriteAutoPropertyDeclaration` extension methods target `CodeWriter` and also take a `CodeRenderingContext`, we can change them can target `CodeRenderingContext`.
…ntext

Because `CodeRenderingContext` owns the `CodeWriter` instance used for code gen, and the `BuildLinePragma` and `BuildEnhancedLinePragma` extension methods target `CodeWriter` and also take a `CodeRenderingContext`, we can change them can target `CodeRenderingContext`.

In addition, this change changes the type that's returned to readonly ref struct. This requires a bit of adjustment to some of the calling code.
Because `CodeRenderingContext` owns the `CodeWriter` instance used for code gen, and the `WritePadding` extension method targets `CodeWriter` and also takes a `CodeRenderingContext`, we can change it  can target `CodeRenderingContext`.
- Enable nullability
- Add constructors
- Make `IsPrimaryMethod` property read-only
- Remove unnecessary argument null check
- Lazily create `Children` collection
- Change `Modifiers` property to return an `ImmutableArray<string>` rather than an `IList<string>` (backed by a `List<string>`).
- Change `Parmeters` property to return an `ImmutableArray<MethodParameter>` rather than an `IList<MethodParameter>` (backed by a `List<MethodParameter>`).
- General clean up
- Enable nullability
- Add constructors
- Make properties read-only
@DustinCampbell DustinCampbell force-pushed the declaration-intermediate-nodes branch from f20d86f to 7dc5c99 Compare July 10, 2025 00:59
@DustinCampbell DustinCampbell changed the title Clean up and reduce allocations in declaration intermediate nodes Compiler: Clean up and reduce allocations in declaration intermediate nodes Jul 10, 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.

3 participants