Skip to content

Commit

Permalink
Implement support for emitting PrivateImplementationDetails to EnC de…
Browse files Browse the repository at this point in the history
…ltas (#69485)
  • Loading branch information
tmat authored Aug 23, 2023
1 parent f37534b commit d83f0cc
Show file tree
Hide file tree
Showing 39 changed files with 1,732 additions and 1,328 deletions.
10 changes: 6 additions & 4 deletions src/Compilers/CSharp/Portable/CodeGen/EmitArrayInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,10 @@ private bool EnableEnumArrayBlockInitialization

private ArrayInitializerStyle ShouldEmitBlockInitializer(TypeSymbol elementType, ImmutableArray<BoundExpression> inits)
{
if (!_module.SupportsPrivateImplClass)
if (_module.IsEncDelta)
{
// Avoid using FieldRva table. Can be allowed if tested on all supported runtimes.
// Consider removing: https://github.com/dotnet/roslyn/issues/69480
return ArrayInitializerStyle.Element;
}

Expand Down Expand Up @@ -440,10 +442,10 @@ private bool TryEmitReadonlySpanAsBlobWrapper(NamedTypeSymbol spanType, BoundExp
avoidInPlace = false;
SpecialType specialElementType = SpecialType.None;

if (!_module.SupportsPrivateImplClass)
if (_module.IsEncDelta)
{
// The implementation stores blobs and possibly cached arrays on the private implementation class.
// If it's not supported, the optimizations can't be applied.
// Avoid using FieldRva table. Can be allowed if tested on all supported runtimes.
// Consider removing: https://github.com/dotnet/roslyn/issues/69480
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@ private void EmitStackAllocInitializers(TypeSymbol type, BoundArrayInitializatio

private ArrayInitializerStyle ShouldEmitBlockInitializerForStackAlloc(TypeSymbol elementType, ImmutableArray<BoundExpression> inits)
{
if (!_module.SupportsPrivateImplClass)
if (_module.IsEncDelta)
{
// Avoid using FieldRva table. Can be allowed if tested on all supported runtimes.
// Consider removing: https://github.com/dotnet/roslyn/issues/69480
return ArrayInitializerStyle.Element;
}

Expand Down
4 changes: 1 addition & 3 deletions src/Compilers/CSharp/Portable/CodeGen/EmitStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1465,10 +1465,8 @@ private void EmitStringSwitchJumpTable(
LocalDefinition keyHash = null;

// Condition is necessary, but not sufficient (e.g. might be missing a special or well-known member).
if (SwitchStringJumpTableEmitter.ShouldGenerateHashTableSwitch(_module, switchCaseLabels.Length))
if (SwitchStringJumpTableEmitter.ShouldGenerateHashTableSwitch(switchCaseLabels.Length))
{
Debug.Assert(_module.SupportsPrivateImplClass);

var privateImplClass = _module.GetPrivateImplClass(syntaxNode, _diagnostics.DiagnosticBag);
Cci.IReference stringHashMethodRef = privateImplClass.GetMethod(
isSpanOrReadOnlySpan
Expand Down
6 changes: 2 additions & 4 deletions src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,10 @@ public static void CompileMethodBodies(

methodCompiler.WaitForWorkers();

var privateImplClass = moduleBeingBuiltOpt.PrivateImplClass;
// all threads that were adding methods must be finished now, we can freeze the class:
var privateImplClass = moduleBeingBuiltOpt.FreezePrivateImplementationDetails();
if (privateImplClass != null)
{
// all threads that were adding methods must be finished now, we can freeze the class:
privateImplClass.Freeze();

methodCompiler.CompileSynthesizedMethods(privateImplClass, diagnostics);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,16 @@ bool isCorrespondingType(TypeWithAnnotations type, TypeWithAnnotations expectedT
private Symbol? VisitNamedTypeMember<T>(T member, Func<T, T, bool> predicate)
where T : Symbol
{
if (member.ContainingType is null)
{
// ContainingType is null for synthesized PrivateImplementationDetails helpers.
// For simplicity, these helpers are not reused across generations.
// Instead new ones are regenerated as needed.
Debug.Assert(member is ISynthesizedGlobalMethodSymbol);

return null;
}

var otherType = (NamedTypeSymbol?)Visit(member.ContainingType);

// Containing type may be null for synthesized
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,16 +247,6 @@ internal CSharpDefinitionMap PreviousDefinitions
get { return _previousDefinitions; }
}

internal override bool SupportsPrivateImplClass
{
get
{
// Disable <PrivateImplementationDetails> in ENC since the
// CLR does not support adding non-private members.
return false;
}
}

public IReadOnlyDictionary<AnonymousTypeKey, AnonymousTypeValue> GetAnonymousTypeMap()
{
var anonymousTypes = this.Compilation.AnonymousTypeManager.GetAnonymousTypeMap();
Expand All @@ -282,17 +272,7 @@ public IReadOnlyDictionary<string, AnonymousTypeValue> GetAnonymousDelegatesWith
}

public override IEnumerable<Cci.INamespaceTypeDefinition> GetTopLevelTypeDefinitions(EmitContext context)
{
foreach (var typeDef in GetAnonymousTypeDefinitions(context))
{
yield return typeDef;
}

foreach (var typeDef in GetTopLevelTypeDefinitionsCore(context))
{
yield return typeDef;
}
}
=> GetTopLevelTypeDefinitionsCore(context);

public override IEnumerable<Cci.INamespaceTypeDefinition> GetTopLevelSourceTypeDefinitions(EmitContext context)
{
Expand Down
32 changes: 1 addition & 31 deletions src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1845,36 +1845,6 @@ internal void EnsureScopedRefAttributeExists()
}

#nullable enable
/// <summary>
/// Creates the ThrowIfNull and Throw helpers if needed.
/// </summary>
/// <remarks>
/// The ThrowIfNull and Throw helpers are modeled off of the helpers on ArgumentNullException.
/// https://github.com/dotnet/runtime/blob/22663769611ba89cd92d14cfcb76e287f8af2335/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs#L56-L69
/// </remarks>
internal MethodSymbol EnsureThrowIfNullFunctionExists(SyntaxNode syntaxNode, SyntheticBoundNodeFactory factory, DiagnosticBag diagnostics)
{
var privateImplClass = GetPrivateImplClass(syntaxNode, diagnostics);
var throwIfNullAdapter = privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowIfNullFunctionName);
if (throwIfNullAdapter is null)
{
TypeSymbol returnType = factory.SpecialType(SpecialType.System_Void);
TypeSymbol argumentType = factory.SpecialType(SpecialType.System_Object);
TypeSymbol paramNameType = factory.SpecialType(SpecialType.System_String);
var sourceModule = SourceModule;

// use add-then-get pattern to ensure the symbol exists, and then ensure we use the single "canonical" instance added by whichever thread won the race.
privateImplClass.TryAddSynthesizedMethod(new SynthesizedThrowMethod(sourceModule, privateImplClass, returnType, paramNameType).GetCciAdapter());
var actuallyAddedThrowMethod = (SynthesizedThrowMethod)privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowFunctionName)!.GetInternalSymbol()!;
privateImplClass.TryAddSynthesizedMethod(new SynthesizedThrowIfNullMethod(sourceModule, privateImplClass, actuallyAddedThrowMethod, returnType, argumentType, paramNameType).GetCciAdapter());
throwIfNullAdapter = privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowIfNullFunctionName)!;
}

var internalSymbol = (SynthesizedThrowIfNullMethod)throwIfNullAdapter.GetInternalSymbol()!;
// the ThrowMethod referenced by the ThrowIfNullMethod must be the same instance as the ThrowMethod contained in the PrivateImplementationDetails
Debug.Assert((object?)privateImplClass.GetMethod(PrivateImplementationDetails.SynthesizedThrowFunctionName)!.GetInternalSymbol() == internalSymbol.ThrowMethod);
return internalSymbol;
}

/// <summary>
/// Creates the ThrowSwitchExpressionException helper if needed.
Expand Down Expand Up @@ -1975,7 +1945,7 @@ internal NamedTypeSymbol EnsureInlineArrayTypeExists(SyntaxNode syntaxNode, Synt
Debug.Assert(Compilation.Assembly.RuntimeSupportsInlineArrayTypes);
Debug.Assert(arrayLength > 0);

string typeName = $"<>{(char)GeneratedNameKind.InlineArrayType}__InlineArray{arrayLength}";
string typeName = GeneratedNames.MakeSynthesizedInlineArrayName(arrayLength, CurrentGenerationOrdinal);
var privateImplClass = GetPrivateImplClass(syntaxNode, diagnostics);
var typeAdapter = privateImplClass.GetSynthesizedType(typeName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ private void EnsureStringHashFunction(int labelsCount, SyntaxNode syntaxNode, St
// table based jump table or a non hash jump table, i.e. linear string comparisons
// with each case label. We use the Dev10 Heuristic to determine this
// (see SwitchStringJumpTableEmitter.ShouldGenerateHashTableSwitch() for details).
if (!CodeAnalysis.CodeGen.SwitchStringJumpTableEmitter.ShouldGenerateHashTableSwitch(module, labelsCount))
if (!CodeAnalysis.CodeGen.SwitchStringJumpTableEmitter.ShouldGenerateHashTableSwitch(labelsCount))
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,15 @@ internal static bool TryParseSynthesizedDelegateName(string name, out RefKindVec
return true;
}

internal static string MakeSynthesizedInlineArrayName(int arrayLength, int generation)
{
Debug.Assert((char)GeneratedNameKind.InlineArrayType == 'y');
var name = "<>y__InlineArray" + arrayLength;

// Synthesized inline arrays need to have unique name across generations because they are not reused.
return (generation > 0) ? name + GenerationSeparator + generation : name;
}

internal static string AsyncBuilderFieldName()
{
// Microsoft.VisualStudio.VIL.VisualStudioHost.AsyncReturnStackFrame depends on this name.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@

#nullable disable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Symbols;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Symbols
Expand All @@ -21,7 +18,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols
/// that must be emitted in the compiler generated
/// PrivateImplementationDetails class
/// </summary>
internal abstract class SynthesizedGlobalMethodSymbol : MethodSymbol
internal abstract class SynthesizedGlobalMethodSymbol : MethodSymbol, ISynthesizedGlobalMethodSymbol
{
private readonly ModuleSymbol _containingModule;
private readonly PrivateImplementationDetails _privateImplType;
Expand Down Expand Up @@ -113,7 +110,7 @@ public sealed override NamedTypeSymbol ContainingType
}
}

internal PrivateImplementationDetails ContainingPrivateImplementationDetailsType
public PrivateImplementationDetails ContainingPrivateImplementationDetailsType
{
get { return _privateImplType; }
}
Expand Down

This file was deleted.

This file was deleted.

Loading

0 comments on commit d83f0cc

Please sign in to comment.