Skip to content

Commit

Permalink
Merge pull request #72673 from CyrusNajmabadi/creationPolicy
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Mar 23, 2024
2 parents dbd6ab8 + f99e64d commit 24b3749
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ private abstract class CompilationTrackerState
/// whether we even have references -- it's pretty likely that running a generator might produce worse results
/// than what we originally had.</item>
/// </list>
/// This also controls if we will generate skeleton references for cross-language P2P references when
/// creating the compilation for a particular project. When entirely frozen, we do not want to do this due
/// to the enormous cost of emitting ref assemblies for cross language cases.
/// </summary>
public readonly bool IsFrozen;
public readonly CreationPolicy CreationPolicy;

/// <summary>
/// The best compilation that is available that source generators have not ran on. May be an
Expand All @@ -47,10 +50,10 @@ private abstract class CompilationTrackerState
public CompilationTrackerGeneratorInfo GeneratorInfo { get; }

protected CompilationTrackerState(
bool isFrozen,
CreationPolicy creationPolicy,
CompilationTrackerGeneratorInfo generatorInfo)
{
IsFrozen = isFrozen;
CreationPolicy = creationPolicy;
GeneratorInfo = generatorInfo;
}
}
Expand Down Expand Up @@ -80,12 +83,12 @@ private sealed class InProgressState : CompilationTrackerState
public override Compilation CompilationWithoutGeneratedDocuments => LazyCompilationWithoutGeneratedDocuments.Value;

public InProgressState(
bool isFrozen,
CreationPolicy creationPolicy,
Lazy<Compilation> compilationWithoutGeneratedDocuments,
CompilationTrackerGeneratorInfo generatorInfo,
Lazy<Compilation?> staleCompilationWithGeneratedDocuments,
ImmutableList<TranslationAction> pendingTranslationActions)
: base(isFrozen, generatorInfo)
: base(creationPolicy, generatorInfo)
{
// Note: Intermediate projects can be empty.
Contract.ThrowIfTrue(pendingTranslationActions is null);
Expand All @@ -105,13 +108,13 @@ public InProgressState(
}

public InProgressState(
bool isFrozen,
CreationPolicy creationPolicy,
Compilation compilationWithoutGeneratedDocuments,
CompilationTrackerGeneratorInfo generatorInfo,
Compilation? staleCompilationWithGeneratedDocuments,
ImmutableList<TranslationAction> pendingTranslationActions)
: this(
isFrozen,
creationPolicy,
new Lazy<Compilation>(() => compilationWithoutGeneratedDocuments),
generatorInfo,
// Extracted as a method call to prevent captures.
Expand Down Expand Up @@ -164,20 +167,20 @@ private sealed class FinalCompilationTrackerState : CompilationTrackerState
public override Compilation CompilationWithoutGeneratedDocuments { get; }

private FinalCompilationTrackerState(
bool isFrozen,
CreationPolicy creationPolicy,
Compilation finalCompilationWithGeneratedDocuments,
Compilation compilationWithoutGeneratedDocuments,
bool hasSuccessfullyLoaded,
CompilationTrackerGeneratorInfo generatorInfo,
UnrootedSymbolSet unrootedSymbolSet)
: base(isFrozen, generatorInfo)
: base(creationPolicy, generatorInfo)
{
Contract.ThrowIfNull(finalCompilationWithGeneratedDocuments);

// As a policy, all partial-state projects are said to have incomplete references, since the
// state has no guarantees.
this.CompilationWithoutGeneratedDocuments = compilationWithoutGeneratedDocuments;
HasSuccessfullyLoaded = hasSuccessfullyLoaded && !isFrozen;
HasSuccessfullyLoaded = hasSuccessfullyLoaded;
FinalCompilationWithGeneratedDocuments = finalCompilationWithGeneratedDocuments;
UnrootedSymbolSet = unrootedSymbolSet;

Expand All @@ -202,7 +205,7 @@ private FinalCompilationTrackerState(
/// <param name="projectId">Not held onto</param>
/// <param name="metadataReferenceToProjectId">Not held onto</param>
public static FinalCompilationTrackerState Create(
bool isFrozen,
CreationPolicy creationPolicy,
Compilation finalCompilationWithGeneratedDocuments,
Compilation compilationWithoutGeneratedDocuments,
bool hasSuccessfullyLoaded,
Expand All @@ -217,21 +220,23 @@ public static FinalCompilationTrackerState Create(
RecordAssemblySymbols(projectId, finalCompilationWithGeneratedDocuments, metadataReferenceToProjectId);

return new FinalCompilationTrackerState(
isFrozen,
creationPolicy,
finalCompilationWithGeneratedDocuments,
compilationWithoutGeneratedDocuments,
hasSuccessfullyLoaded,
generatorInfo,
unrootedSymbolSet);
}

public FinalCompilationTrackerState WithIsFrozen()
=> new(isFrozen: true,
FinalCompilationWithGeneratedDocuments,
CompilationWithoutGeneratedDocuments,
HasSuccessfullyLoaded,
GeneratorInfo,
UnrootedSymbolSet);
public FinalCompilationTrackerState WithCreationPolicy(CreationPolicy creationPolicy)
=> creationPolicy == this.CreationPolicy
? this
: new(creationPolicy,
FinalCompilationWithGeneratedDocuments,
CompilationWithoutGeneratedDocuments,
HasSuccessfullyLoaded,
GeneratorInfo,
UnrootedSymbolSet);

private static void RecordAssemblySymbols(ProjectId projectId, Compilation compilation, Dictionary<MetadataReference, ProjectId>? metadataReferenceToProjectId)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public ICompilationTracker Fork(
};

var newState = new InProgressState(
state.IsFrozen,
state.CreationPolicy,
compilationWithoutGeneratedDocuments,
state.GeneratorInfo,
staleCompilationWithGeneratedDocuments,
Expand Down Expand Up @@ -334,12 +334,13 @@ InProgressState BuildInProgressStateFromNoCompilationState()

var compilationWithoutGeneratedDocuments = CreateEmptyCompilation();

// We only got here when we had no compilation state at all. So we couldn't have gotten
// here from a frozen state (as a frozen state always ensures we have a
// WithCompilationTrackerState). As such, we can safely still preserve that we're not
// frozen here.
// We only got here when we had no compilation state at all. So we couldn't have gotten here
// from a frozen state (as a frozen state always ensures we have at least an InProgressState).
// As such, we want to start initially in the state where we will both run generators and create
// skeleton references for p2p references. That will ensure the most correct state for our
// compilation the first time we create it.
var allSyntaxTreesParsedState = new InProgressState(
isFrozen: false,
CreationPolicy.Create,
new Lazy<Compilation>(CreateEmptyCompilation),
CompilationTrackerGeneratorInfo.Empty,
staleCompilationWithGeneratedDocuments: s_lazyNullCompilation,
Expand Down Expand Up @@ -380,7 +381,7 @@ async Task<InProgressState> CollapseInProgressStateAsync(InProgressState initial
// generator docs back to the uncomputed state from that point onwards. We'll just keep
// whateverZ generated docs we have.
currentState = new InProgressState(
currentState.IsFrozen,
currentState.CreationPolicy,
compilationWithoutGeneratedDocuments,
generatorInfo,
staleCompilationWithGeneratedDocuments,
Expand Down Expand Up @@ -472,7 +473,7 @@ async Task<FinalCompilationTrackerState> FinalizeCompilationWorkerAsync(InProgre
Contract.ThrowIfTrue(inProgressState.PendingTranslationActions.Count > 0);

// The final state we produce will be frozen or not depending on if a frozen state was passed into it.
var isFrozen = inProgressState.IsFrozen;
var creationPolicy = inProgressState.CreationPolicy;
var generatorInfo = inProgressState.GeneratorInfo;
var compilationWithoutGeneratedDocuments = inProgressState.CompilationWithoutGeneratedDocuments;
var staleCompilationWithGeneratedDocuments = inProgressState.LazyStaleCompilationWithGeneratedDocuments.Value;
Expand Down Expand Up @@ -524,27 +525,33 @@ await compilationState.GetCompilationAsync(
{
// Not a submission. Add as a metadata reference.

if (isFrozen)
if (creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.Create)
{
// In the frozen case, attempt to get a partial reference, or fallback to the last
// successful reference for this project if we can find one.
var metadataReference = compilationState.GetPartialMetadataReference(projectReference, this.ProjectState);
var metadataReference = await compilationState.GetMetadataReferenceAsync(projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false);
AddMetadataReference(projectReference, metadataReference);
}
else
{
Contract.ThrowIfFalse(creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.CreateIfAbsent or SkeletonReferenceCreationPolicy.DoNotCreate);

// If not asked to explicit create an up to date skeleton, attempt to get a partial
// reference, or fallback to the last successful reference for this project if we can
// find one.
var metadataReference = compilationState.GetPartialMetadataReference(projectReference, this.ProjectState);
if (metadataReference is null)
{
// if we failed to get the metadata and we were frozen, check to see if we
// previously had existing metadata and reuse it instead.
var inProgressCompilationNotRef = staleCompilationWithGeneratedDocuments ?? compilationWithoutGeneratedDocuments;
metadataReference = inProgressCompilationNotRef.ExternalReferences.FirstOrDefault(
r => GetProjectId(inProgressCompilationNotRef.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol) == projectReference.ProjectId);
}

AddMetadataReference(projectReference, metadataReference);
}
else
{
// For the non-frozen case, attempt to get the full metadata reference.
var metadataReference = await compilationState.GetMetadataReferenceAsync(projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false);
// If we still failed, but our policy is to create when absent, then do the work to
// create a real skeleton here.
if (metadataReference is null && creationPolicy.SkeletonReferenceCreationPolicy is SkeletonReferenceCreationPolicy.CreateIfAbsent)
{
metadataReference = await compilationState.GetMetadataReferenceAsync(projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false);
}

AddMetadataReference(projectReference, metadataReference);
}
}
Expand All @@ -563,7 +570,7 @@ await compilationState.GetCompilationAsync(

// We will finalize the compilation by adding full contents here.
var (compilationWithGeneratedDocuments, generatedDocuments, generatorDriver) = await AddExistingOrComputeNewGeneratorInfoAsync(
isFrozen,
creationPolicy,
compilationState,
compilationWithoutGeneratedDocuments,
generatorInfo,
Expand All @@ -574,7 +581,7 @@ await compilationState.GetCompilationAsync(
var nextGeneratorInfo = new CompilationTrackerGeneratorInfo(generatedDocuments, generatorDriver);

var finalState = FinalCompilationTrackerState.Create(
isFrozen,
creationPolicy,
compilationWithGeneratedDocuments,
compilationWithoutGeneratedDocuments,
hasSuccessfullyLoaded,
Expand Down Expand Up @@ -667,13 +674,18 @@ public ICompilationTracker FreezePartialState(CancellationToken cancellationToke
{
var state = this.ReadState();

// We're freezing the solution for features where latency performance is parameter. Do not run SGs or
// create skeleton references at this point. Just use whatever we've already generated for each in the
// past.
var desiredCreationPolicy = CreationPolicy.DoNotCreate;

if (state is FinalCompilationTrackerState finalState)
{
// If we're finalized and already frozen, we can just use ourselves. Otherwise, flip the frozen bit
// so that any future forks keep things frozen.
return finalState.IsFrozen
// Attempt to transition our state to one where we do not run generators or create skeleton references.
var newFinalState = finalState.WithCreationPolicy(desiredCreationPolicy);
return newFinalState == finalState
? this
: new CompilationTracker(this.ProjectState, finalState.WithIsFrozen(), skeletonReferenceCacheToClone: _skeletonReferenceCache);
: new CompilationTracker(this.ProjectState, newFinalState, skeletonReferenceCacheToClone: _skeletonReferenceCache);
}

// Non-final state currently. Produce an in-progress-state containing the forked change. Note: we
Expand Down Expand Up @@ -722,7 +734,7 @@ public ICompilationTracker FreezePartialState(CancellationToken cancellationToke
return new CompilationTracker(
frozenProjectState,
new InProgressState(
isFrozen: true,
desiredCreationPolicy,
lazyCompilationWithoutGeneratedDocuments,
CompilationTrackerGeneratorInfo.Empty,
lazyCompilationWithGeneratedDocuments,
Expand All @@ -748,7 +760,7 @@ public ICompilationTracker FreezePartialState(CancellationToken cancellationToke
return new CompilationTracker(
frozenProjectState,
new InProgressState(
isFrozen: true,
desiredCreationPolicy,
compilationWithoutGeneratedDocuments,
generatorInfo,
compilationWithGeneratedDocuments,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ internal partial class SolutionCompilationState
private partial class CompilationTracker : ICompilationTracker
{
private async Task<(Compilation compilationWithGeneratedFiles, TextDocumentStates<SourceGeneratedDocumentState> generatedDocuments, GeneratorDriver? generatorDriver)> AddExistingOrComputeNewGeneratorInfoAsync(
bool isFrozen,
CreationPolicy creationPolicy,
SolutionCompilationState compilationState,
Compilation compilationWithoutGeneratedFiles,
CompilationTrackerGeneratorInfo generatorInfo,
Compilation? compilationWithStaleGeneratedTrees,
CancellationToken cancellationToken)
{
if (isFrozen)
if (creationPolicy.GeneratedDocumentCreationPolicy is GeneratedDocumentCreationPolicy.DoNotCreate)
{
// We're frozen. So we do not want to go through the expensive cost of running generators. Instead, we
// just whatever prior generated docs we have.
Expand Down
Loading

0 comments on commit 24b3749

Please sign in to comment.