Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,9 @@ and nothing is generated. A class marked `[IReactiveObject]` looks as if it rais
The file is never emitted.
- `BindingGenerator.Detect` reads every call site from that copy, through `CallSiteContext`. A compilation that uses
none of the attributes gets no copy, and its call sites are read from the consumer's compilation as before.
- Every call site in one file shares one semantic model of the copy (`CallSiteContext.ModelFor`). A model caches the
methods it binds, and binding one call site binds its whole method. A new model per call site binds a view's
`WhenActivated` lambda once per binding in it, which grows with the square of the bindings.

The rules mirror ReactiveUI.SourceGenerators 4.0.0. Its session agreed to announce any change to a name, type or
attribute argument. When one changes, update `SourceGeneratorsMemberExtractor` and `SourceGeneratorsDeclarationTests`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// ReactiveUI and Contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;

namespace ReactiveUI.Binding.SourceGenerators.Helpers;
Expand All @@ -15,10 +17,33 @@ namespace ReactiveUI.Binding.SourceGenerators.Helpers;
/// </remarks>
internal readonly record struct CallSiteContext(SyntaxNode Node, SemanticModel SemanticModel)
{
/// <summary>The semantic model of each file of each compilation copy, shared by every call site in the file.</summary>
/// <remarks>
/// A semantic model caches the members it binds. Call sites in one method share that cache.
/// The table holds the models only while the compilation copy is alive.
/// </remarks>
private static readonly ConditionalWeakTable<Compilation, ConcurrentDictionary<SyntaxTree, SemanticModel>> Models = new();

/// <summary>Reads a call site from the compilation that declares ReactiveUI.SourceGenerators' members, when there is one.</summary>
/// <param name="context">The syntax context the scan found the call site in.</param>
/// <param name="sourceGenerators">The compilation with those members declared, or null when the consumer uses none.</param>
/// <returns>The call site with the model to read it with.</returns>
internal static CallSiteContext From(GeneratorSyntaxContext context, Compilation? sourceGenerators) =>
new(context.Node, sourceGenerators?.GetSemanticModel(context.Node.SyntaxTree) ?? context.SemanticModel);
new(context.Node, sourceGenerators is null ? context.SemanticModel : ModelFor(sourceGenerators, context.Node.SyntaxTree));

/// <summary>Gets the one semantic model a compilation's file is read with.</summary>
/// <param name="compilation">The compilation.</param>
/// <param name="tree">The file.</param>
/// <returns>The file's model, created on first use.</returns>
/// <remarks>
/// Transforms run in parallel. A semantic model supports queries from several threads.
/// Two first readers can each create a model. Both receive the model the dictionary keeps.
/// </remarks>
internal static SemanticModel ModelFor(Compilation compilation, SyntaxTree tree)
{
var models = Models.GetValue(compilation, static _ => new());
return models.TryGetValue(tree, out var model)
? model
: models.GetOrAdd(tree, compilation.GetSemanticModel(tree));
}
}
16 changes: 16 additions & 0 deletions src/benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@ The `GcVerbose` EventPipe profiler records allocations. Analysis uses the measur
operation counts to report sampled bytes per generation and the allocation sites. NativeAOT timing runs remain
separate where EventPipe capture is unavailable.

## Views that name their controls in XAML

`XamlViewGenerationBenchmarks` builds four Avalonia-style views. Each view names its controls in an `.axaml`
page and binds every control in one `WhenActivated` lambda. Each view model has a `[Reactive]` field. Either
one makes the generator read call sites from its private copy of the compilation.

| Parameter | Values | Meaning |
|-----------|--------|---------|
| `Bindings` | 10, 50, 100 | How many controls each view names and binds. |
| `Xaml` | false, true | Whether the controls come from the pages, or the views declare them as fields. |

`Generate` runs a cold pass. `Edit` runs the pass after a one-line edit to one view, with a driver primed on
the unedited views. Setup fails when the output does not compile with the members the other generators add.

See [the issue 166 measurements](issue-166-results.md) for the comparison against the baseline.

## What the analyzer benchmark covers

`AnalyzerBenchmarks` runs each analyzer over a corpus through `CompilationWithAnalyzers`. It has two cases,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ internal static CSharpCompilation BuildCompilation(bool intercept)
syntaxTrees[i] = CSharpSyntaxTree.ParseText(reader.ReadToEnd(), parseOptions, paths[i]);
}

return BuildCompilation(CompilationAssemblyName, syntaxTrees);
}

/// <summary>Builds a compilation over the given source, referencing the framework and the runtime library.</summary>
/// <param name="assemblyName">The compilation's assembly name.</param>
/// <param name="syntaxTrees">The source.</param>
/// <returns>The compilation.</returns>
internal static CSharpCompilation BuildCompilation(string assemblyName, SyntaxTree[] syntaxTrees)
{
#if NET11_0_OR_GREATER
var references = new List<MetadataReference>(Basic.Reference.Assemblies.Net110.References.All)
#else
Expand All @@ -70,7 +79,7 @@ internal static CSharpCompilation BuildCompilation(bool intercept)
};

return CSharpCompilation.Create(
CompilationAssemblyName,
assemblyName,
syntaxTrees,
references,
new(OutputKind.DynamicallyLinkedLibrary));
Expand Down
Loading
Loading