// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Emit; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { /// /// Manages anonymous types created on module level. All requests for anonymous type symbols /// go via the instance of this class, the symbol will be either created or returned from cache. /// internal sealed partial class AnonymousTypeManager { /// /// Cache of created anonymous type templates used as an implementation of anonymous /// types in emit phase. /// private ConcurrentDictionary _lazyAnonymousTypeTemplates; /// /// Maps delegate signature shape (number of parameters and their ref-ness) to a synthesized generic delegate symbol. /// Unlike anonymous types synthesized delegates are not available through symbol APIs. They are only used in lowered bound trees. /// Currently used for dynamic call-site sites whose signature doesn't match any of the well-known Func or Action types. /// private ConcurrentDictionary _lazySynthesizedDelegates; private struct SynthesizedDelegateKey : IEquatable { private readonly BitVector _byRefs; private readonly ushort _parameterCount; private readonly bool _returnsVoid; private readonly int _generation; public SynthesizedDelegateKey(int parameterCount, BitVector byRefs, bool returnsVoid, int generation) { _parameterCount = (ushort)parameterCount; _returnsVoid = returnsVoid; _generation = generation; _byRefs = byRefs; } public string MakeTypeName() { return GeneratedNames.MakeDynamicCallSiteDelegateName(_byRefs, _returnsVoid, _generation); } public override bool Equals(object obj) { return obj is SynthesizedDelegateKey && Equals((SynthesizedDelegateKey)obj); } public bool Equals(SynthesizedDelegateKey other) { return _parameterCount == other._parameterCount && _returnsVoid == other._returnsVoid && _generation == other._generation && _byRefs.Equals(other._byRefs); } public override int GetHashCode() { return Hash.Combine( Hash.Combine((int)_parameterCount, _generation), Hash.Combine(_returnsVoid.GetHashCode(), _byRefs.GetHashCode())); } } private struct SynthesizedDelegateValue { public readonly SynthesizedDelegateSymbol Delegate; // the manager that created this delegate: public readonly AnonymousTypeManager Manager; public SynthesizedDelegateValue(AnonymousTypeManager manager, SynthesizedDelegateSymbol @delegate) { Debug.Assert(manager != null && (object)@delegate != null); this.Manager = manager; this.Delegate = @delegate; } } #if DEBUG /// /// Holds a collection of all the locations of anonymous types and delegates from source /// private readonly ConcurrentDictionary _sourceLocationsSeen = new ConcurrentDictionary(); #endif [Conditional("DEBUG")] private void CheckSourceLocationSeen(AnonymousTypePublicSymbol anonymous) { #if DEBUG Location location = anonymous.Locations[0]; if (location.IsInSource) { if (this.AreTemplatesSealed) { Debug.Assert(_sourceLocationsSeen.ContainsKey(location)); } else { _sourceLocationsSeen.TryAdd(location, true); } } #endif } private ConcurrentDictionary AnonymousTypeTemplates { get { // Lazily create a template types cache if (_lazyAnonymousTypeTemplates == null) { CSharpCompilation previousSubmission = this.Compilation.PreviousSubmission; // TODO (tomat): avoid recursion var previousCache = (previousSubmission == null) ? null : previousSubmission.AnonymousTypeManager.AnonymousTypeTemplates; Interlocked.CompareExchange(ref _lazyAnonymousTypeTemplates, previousCache == null ? new ConcurrentDictionary() : new ConcurrentDictionary(previousCache), null); } return _lazyAnonymousTypeTemplates; } } private ConcurrentDictionary SynthesizedDelegates { get { if (_lazySynthesizedDelegates == null) { CSharpCompilation previousSubmission = this.Compilation.PreviousSubmission; // TODO (tomat): avoid recursion var previousCache = (previousSubmission == null) ? null : previousSubmission.AnonymousTypeManager._lazySynthesizedDelegates; Interlocked.CompareExchange(ref _lazySynthesizedDelegates, previousCache == null ? new ConcurrentDictionary() : new ConcurrentDictionary(previousCache), null); } return _lazySynthesizedDelegates; } } internal SynthesizedDelegateSymbol SynthesizeDelegate(int parameterCount, BitVector byRefParameters, bool returnsVoid, int generation) { // parameterCount doesn't include return type Debug.Assert(byRefParameters.IsNull || parameterCount == byRefParameters.Capacity); var key = new SynthesizedDelegateKey(parameterCount, byRefParameters, returnsVoid, generation); SynthesizedDelegateValue result; if (this.SynthesizedDelegates.TryGetValue(key, out result)) { return result.Delegate; } // NOTE: the newly created template may be thrown away if another thread wins return this.SynthesizedDelegates.GetOrAdd(key, new SynthesizedDelegateValue( this, new SynthesizedDelegateSymbol( this.Compilation.Assembly.GlobalNamespace, key.MakeTypeName(), this.System_Object, Compilation.GetSpecialType(SpecialType.System_IntPtr), returnsVoid ? Compilation.GetSpecialType(SpecialType.System_Void) : null, parameterCount, byRefParameters))).Delegate; } /// /// Given anonymous type provided constructs an implementation type symbol to be used in emit phase; /// if the anonymous type has at least one field the implementation type symbol will be created based on /// a generic type template generated for each 'unique' anonymous type structure, otherwise the template /// type will be non-generic. /// private NamedTypeSymbol ConstructAnonymousTypeImplementationSymbol(AnonymousTypePublicSymbol anonymous) { Debug.Assert(ReferenceEquals(this, anonymous.Manager)); CheckSourceLocationSeen(anonymous); AnonymousTypeDescriptor typeDescr = anonymous.TypeDescriptor; typeDescr.AssertIsGood(); // Get anonymous type template AnonymousTypeTemplateSymbol template; if (!this.AnonymousTypeTemplates.TryGetValue(typeDescr.Key, out template)) { // NOTE: the newly created template may be thrown away if another thread wins template = this.AnonymousTypeTemplates.GetOrAdd(typeDescr.Key, new AnonymousTypeTemplateSymbol(this, typeDescr)); } // Adjust template location if the template is owned by this manager if (ReferenceEquals(template.Manager, this)) { template.AdjustLocation(typeDescr.Location); } // In case template is not generic, just return it if (template.Arity == 0) { return template; } // otherwise construct type using the field types var typeArguments = typeDescr.Fields.SelectAsArray(f => f.Type); return template.Construct(typeArguments); } private AnonymousTypeTemplateSymbol CreatePlaceholderTemplate(Microsoft.CodeAnalysis.Emit.AnonymousTypeKey key) { var fields = key.Fields.SelectAsArray(f => new AnonymousTypeField(f.Name, Location.None, (TypeSymbol)null)); var typeDescr = new AnonymousTypeDescriptor(fields, Location.None); return new AnonymousTypeTemplateSymbol(this, typeDescr); } /// /// Resets numbering in anonymous type names and compiles the /// anonymous type methods. Also seals the collection of templates. /// public void AssignTemplatesNamesAndCompile(MethodCompiler compiler, PEModuleBuilder moduleBeingBuilt, DiagnosticBag diagnostics) { // Ensure all previous anonymous type templates are included so the // types are available for subsequent edit and continue generations. foreach (var key in moduleBeingBuilt.GetPreviousAnonymousTypes()) { var templateKey = AnonymousTypeDescriptor.ComputeKey(key.Fields, f => f.Name); this.AnonymousTypeTemplates.GetOrAdd(templateKey, k => this.CreatePlaceholderTemplate(key)); } // Get all anonymous types owned by this manager var builder = ArrayBuilder.GetInstance(); GetCreatedAnonymousTypeTemplates(builder); // If the collection is not sealed yet we should assign // new indexes to the created anonymous type templates if (!this.AreTemplatesSealed) { // If we are emitting .NET module, include module's name into type's name to ensure // uniqueness across added modules. string moduleId; if (moduleBeingBuilt.OutputKind == OutputKind.NetModule) { moduleId = moduleBeingBuilt.Name; string extension = OutputKind.NetModule.GetDefaultExtension(); if (moduleId.EndsWith(extension, StringComparison.OrdinalIgnoreCase)) { moduleId = moduleId.Substring(0, moduleId.Length - extension.Length); } moduleId = MetadataHelpers.MangleForTypeNameIfNeeded(moduleId); } else { moduleId = string.Empty; } int nextIndex = moduleBeingBuilt.GetNextAnonymousTypeIndex(); foreach (var template in builder) { string name; int index; if (!moduleBeingBuilt.TryGetAnonymousTypeName(template, out name, out index)) { index = nextIndex++; name = GeneratedNames.MakeAnonymousTypeTemplateName(index, this.Compilation.GetSubmissionSlotIndex(), moduleId); } // normally it should only happen once, but in case there is a race // NameAndIndex.set has an assert which guarantees that the // template name provided is the same as the one already assigned template.NameAndIndex = new NameAndIndex(name, index); } this.SealTemplates(); } if (builder.Count > 0 && !ReportMissingOrErroneousSymbols(diagnostics)) { // Process all the templates foreach (var template in builder) { foreach (var method in template.SpecialMembers) { moduleBeingBuilt.AddSynthesizedDefinition(template, method); } compiler.Visit(template, null); } } builder.Free(); var synthesizedDelegates = ArrayBuilder.GetInstance(); GetCreatedSynthesizedDelegates(synthesizedDelegates); foreach (var synthesizedDelegate in synthesizedDelegates) { compiler.Visit(synthesizedDelegate, null); } synthesizedDelegates.Free(); } /// /// The set of anonymous type templates created by /// this AnonymousTypeManager, in fixed order. /// private void GetCreatedAnonymousTypeTemplates(ArrayBuilder builder) { Debug.Assert(!builder.Any()); var anonymousTypes = _lazyAnonymousTypeTemplates; if (anonymousTypes != null) { foreach (var template in anonymousTypes.Values) { if (ReferenceEquals(template.Manager, this)) { builder.Add(template); } } // Sort type templates using smallest location builder.Sort(new AnonymousTypeComparer(this.Compilation)); } } /// /// The set of synthesized delegates created by /// this AnonymousTypeManager. /// private void GetCreatedSynthesizedDelegates(ArrayBuilder builder) { Debug.Assert(!builder.Any()); var delegates = _lazySynthesizedDelegates; if (delegates != null) { foreach (var template in delegates.Values) { if (ReferenceEquals(template.Manager, this)) { builder.Add(template.Delegate); } } builder.Sort(SynthesizedDelegateSymbolComparer.Instance); } } private class SynthesizedDelegateSymbolComparer : IComparer { public static readonly SynthesizedDelegateSymbolComparer Instance = new SynthesizedDelegateSymbolComparer(); public int Compare(SynthesizedDelegateSymbol x, SynthesizedDelegateSymbol y) { return x.MetadataName.CompareTo(y.MetadataName); } } internal static Microsoft.CodeAnalysis.Emit.AnonymousTypeKey GetAnonymousTypeKey(NamedTypeSymbol type) { return ((AnonymousTypeTemplateSymbol)type).GetAnonymousTypeKey(); } internal IReadOnlyDictionary GetAnonymousTypeMap() { var result = new Dictionary(); var templates = ArrayBuilder.GetInstance(); // Get anonymous types but not synthesized delegates. (Delegate types are // not reused across generations since reuse would add complexity (such // as parsing delegate type names from metadata) without a clear benefit.) GetCreatedAnonymousTypeTemplates(templates); foreach (var template in templates) { var nameAndIndex = template.NameAndIndex; var key = template.GetAnonymousTypeKey(); var value = new Microsoft.CodeAnalysis.Emit.AnonymousTypeValue(nameAndIndex.Name, nameAndIndex.Index, template); result.Add(key, value); } templates.Free(); return result; } /// /// Returns all templates owned by this type manager /// internal ImmutableArray GetAllCreatedTemplates() { // NOTE: templates may not be sealed in case metadata is being emitted without IL var builder = ArrayBuilder.GetInstance(); var anonymousTypes = ArrayBuilder.GetInstance(); GetCreatedAnonymousTypeTemplates(anonymousTypes); builder.AddRange(anonymousTypes); anonymousTypes.Free(); var synthesizedDelegates = ArrayBuilder.GetInstance(); GetCreatedSynthesizedDelegates(synthesizedDelegates); builder.AddRange(synthesizedDelegates); synthesizedDelegates.Free(); return builder.ToImmutableAndFree(); } /// /// Returns true if the named type is an implementation template for an anonymous type /// internal static bool IsAnonymousTypeTemplate(NamedTypeSymbol type) { return type is AnonymousTypeTemplateSymbol; } /// /// Retrieves methods of anonymous type template which are not placed to symbol table. /// In current implementation those are overridden 'ToString', 'Equals' and 'GetHashCode' /// internal static ImmutableArray GetAnonymousTypeHiddenMethods(NamedTypeSymbol type) { Debug.Assert((object)type != null); return ((AnonymousTypeTemplateSymbol)type).SpecialMembers; } /// /// Translates anonymous type public symbol into an implementation type symbol to be used in emit. /// internal static NamedTypeSymbol TranslateAnonymousTypeSymbol(NamedTypeSymbol type) { Debug.Assert((object)type != null); Debug.Assert(type.IsAnonymousType); var anonymous = (AnonymousTypePublicSymbol)type; return anonymous.Manager.ConstructAnonymousTypeImplementationSymbol(anonymous); } /// /// Translates anonymous type method symbol into an implementation method symbol to be used in emit. /// internal static MethodSymbol TranslateAnonymousTypeMethodSymbol(MethodSymbol method) { Debug.Assert((object)method != null); NamedTypeSymbol translatedType = TranslateAnonymousTypeSymbol(method.ContainingType); // find a method in anonymous type template by name foreach (var member in ((NamedTypeSymbol)translatedType.OriginalDefinition).GetMembers(method.Name)) { if (member.Kind == SymbolKind.Method) { // found a method definition, get a constructed method return ((MethodSymbol)member).AsMember(translatedType); } } throw ExceptionUtilities.Unreachable; } /// /// Comparator being used for stable ordering in anonymous type indices. /// private sealed class AnonymousTypeComparer : IComparer { private readonly CSharpCompilation _compilation; public AnonymousTypeComparer(CSharpCompilation compilation) { _compilation = compilation; } public int Compare(AnonymousTypeTemplateSymbol x, AnonymousTypeTemplateSymbol y) { if ((object)x == (object)y) { return 0; } // We compare two anonymous type templated by comparing their locations and descriptor keys // NOTE: If anonymous type got to this phase it must have the location set int result = this.CompareLocations(x.SmallestLocation, y.SmallestLocation); if (result == 0) { // It is still possible for two templates to have the same smallest location // in case they are implicitly created and use the same syntax for location result = string.CompareOrdinal(x.TypeDescriptorKey, y.TypeDescriptorKey); } return result; } private int CompareLocations(Location x, Location y) { if (x == y) { return 0; } else if (x == Location.None) { return -1; } else if (y == Location.None) { return 1; } else { return _compilation.CompareSourceLocations(x, y); } } } } }