diff --git a/Src/Compilers/Core/Source/CodeAnalysis.csproj b/Src/Compilers/Core/Source/CodeAnalysis.csproj index fe64abe2e4234b6458cb650ed7880cfb815632ba..dad27c5e5cf48f5d48374c20d63919a57c8bd354 100644 --- a/Src/Compilers/Core/Source/CodeAnalysis.csproj +++ b/Src/Compilers/Core/Source/CodeAnalysis.csproj @@ -355,6 +355,7 @@ + diff --git a/Src/Compilers/Core/Source/CodeGen/LocalConstantDefinition.cs b/Src/Compilers/Core/Source/CodeGen/LocalConstantDefinition.cs index bcae85e2eb665fc5c0a4e39b89007ea909a0bb5b..2b46c70500b4cd7ea7a125d5709be5f30997acaa 100644 --- a/Src/Compilers/Core/Source/CodeGen/LocalConstantDefinition.cs +++ b/Src/Compilers/Core/Source/CodeGen/LocalConstantDefinition.cs @@ -1,12 +1,8 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.Generic; using System.Collections.Immutable; using System.Diagnostics; -using Microsoft.Cci; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeGen { @@ -14,17 +10,17 @@ namespace Microsoft.CodeAnalysis.CodeGen /// We need a CCI representation for local constants because they are emitted as locals in /// PDB scopes to improve the debugging experience (see LocalScopeProvider.GetConstantsInScope). /// - internal class LocalConstantDefinition : ILocalDefinition + internal sealed class LocalConstantDefinition : Cci.ILocalDefinition { private readonly string name; private readonly Location location; - private readonly IMetadataConstant compileTimeValue; + private readonly Cci.IMetadataConstant compileTimeValue; private readonly bool isDynamic; //Gives the synthesized dynamic attributes of the local definition private readonly ImmutableArray dynamicTransformFlags; - public LocalConstantDefinition(string name, Location location, IMetadataConstant compileTimeValue, bool isDynamic = false, + public LocalConstantDefinition(string name, Location location, Cci.IMetadataConstant compileTimeValue, bool isDynamic = false, ImmutableArray dynamicTransformFlags = default(ImmutableArray)) { Debug.Assert(!string.IsNullOrEmpty(name)); @@ -47,12 +43,12 @@ public Location Location get { return location; } } - public IMetadataConstant CompileTimeValue + public Cci.IMetadataConstant CompileTimeValue { get { return compileTimeValue; } } - public ITypeReference Type + public Cci.ITypeReference Type { get { return this.compileTimeValue.Type; } } @@ -62,9 +58,9 @@ public bool IsConstant get { return true; } } - public ImmutableArray CustomModifiers + public ImmutableArray CustomModifiers { - get { return ImmutableArray.Empty; } + get { return ImmutableArray.Empty; } } public bool IsModified @@ -96,5 +92,13 @@ public ImmutableArray DynamicTransformFlags { get { return this.dynamicTransformFlags; } } + + public int SlotIndex + { + get + { + return -1; + } + } } } \ No newline at end of file diff --git a/Src/Compilers/Core/Source/CodeGen/LocalDefinition.cs b/Src/Compilers/Core/Source/CodeGen/LocalDefinition.cs index 428033db062a10e82b21b9828ccc9dd73e02bea4..83c16904a145eb33495af8e11d03b01cca83a113 100644 --- a/Src/Compilers/Core/Source/CodeGen/LocalDefinition.cs +++ b/Src/Compilers/Core/Source/CodeGen/LocalDefinition.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Collections.Immutable; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeGen { - internal sealed class LocalDefinition : Microsoft.Cci.ILocalDefinition + internal sealed class LocalDefinition : Cci.ILocalDefinition { //TODO: locals are really just typed slots. They do not have names. // name only matters for pdb generation where it is a scope-specific mapping to a slot. @@ -18,7 +17,7 @@ internal sealed class LocalDefinition : Microsoft.Cci.ILocalDefinition private readonly string name; // null if it is a temp. //data type associated with the local signature slot. - private readonly Microsoft.Cci.ITypeReference type; + private readonly Cci.ITypeReference type; // specifies whether local slot has a byref constraint and whether // the type of the local has the "pinned modifier" (7.1.2). @@ -53,7 +52,7 @@ internal sealed class LocalDefinition : Microsoft.Cci.ILocalDefinition public LocalDefinition( object identity, string name, - Microsoft.Cci.ITypeReference type, + Cci.ITypeReference type, int slot, bool isCompilerGenerated, LocalSlotConstraints constraints, @@ -147,7 +146,7 @@ public ImmutableArray DynamicTransformFlags get { return this.dynamicTransformFlags; } } - public Microsoft.Cci.ITypeReference Type + public Cci.ITypeReference Type { get { return this.type; } } diff --git a/Src/Compilers/Core/Source/Emit/CommonPEModuleBuilder.cs b/Src/Compilers/Core/Source/Emit/CommonPEModuleBuilder.cs index 12a77e6ba1e069a2d05fe9eaeade0cfe1f4cd275..b4459006359e8981aa89691294eb2d0fe8a2ca6d 100644 --- a/Src/Compilers/Core/Source/Emit/CommonPEModuleBuilder.cs +++ b/Src/Compilers/Core/Source/Emit/CommonPEModuleBuilder.cs @@ -82,6 +82,8 @@ internal abstract class PEModuleBuilder lazyExternNamespaces; + protected PEModuleBuilder( TCompilation compilation, TSourceModuleSymbol sourceModule, @@ -306,6 +308,38 @@ private static void VisitTopLevelType(Cci.NoPiaReferenceIndexer noPiaIndexer, Cc } } + private ImmutableArray CalculateExternNamespaces() + { + var result = ArrayBuilder.GetInstance(compilation.ExternalReferences.Length); + + var referenceManager = this.compilation.GetBoundReferenceManager(); + + // Enumerate external references (#r's don't define aliases) to preserve the order. + foreach (MetadataReference reference in compilation.ExternalReferences) + { + // duplicate references might have been skipped by the assembly binder: + + IAssemblySymbol symbol; + ImmutableArray aliases; + if (referenceManager.TryGetReferencedAssemblySymbol(reference, out symbol, out aliases)) + { + var displayName = symbol.Identity.GetDisplayName(); + for (int i = 0; i < aliases.Length; i++) + { + string alias = aliases[i]; + + // filter out duplicates and global aliases: + if (alias != MetadataReferenceProperties.GlobalAlias && aliases.IndexOf(alias, 0, i) < 0) + { + result.Add(new Cci.ExternNamespace(alias, displayName)); + } + } + } + } + + return result.ToImmutableAndFree(); + } + #region Synthesized Members /// @@ -687,36 +721,17 @@ IEnumerable Cci.IModule.ModuleAttributes get { return GetSourceModuleAttributes(); } } - ImmutableArray Cci.IModule.GetExternNamespaces() + ImmutableArray Cci.IModule.ExternNamespaces { - var result = ArrayBuilder.GetInstance(compilation.ExternalReferences.Length); - - var referenceManager = this.compilation.GetBoundReferenceManager(); - - // Enumerate external references (#r's don't define aliases) to preserve the order. - foreach (MetadataReference reference in compilation.ExternalReferences) + get { - // duplicate references might have been skipped by the assembly binder: - - IAssemblySymbol symbol; - ImmutableArray aliases; - if (referenceManager.TryGetReferencedAssemblySymbol(reference, out symbol, out aliases)) + if (lazyExternNamespaces.IsDefault) { - var displayName = symbol.Identity.GetDisplayName(); - for (int i = 0; i < aliases.Length; i++) - { - string alias = aliases[i]; - - // filter out duplicates and global aliases: - if (alias != MetadataReferenceProperties.GlobalAlias && aliases.IndexOf(alias, 0, i) < 0) - { - result.Add(new Cci.ExternNamespace(alias, displayName)); - } - } + ImmutableInterlocked.InterlockedCompareExchange(ref lazyExternNamespaces, CalculateExternNamespaces(), default(ImmutableArray)); } - } - return result.ToImmutableAndFree(); + return this.lazyExternNamespaces; + } } // PE entry point, only available for console and windows apps: diff --git a/Src/Compilers/Core/Source/PEWriter/CustomDebugInfoWriter.cs b/Src/Compilers/Core/Source/PEWriter/CustomDebugInfoWriter.cs new file mode 100644 index 0000000000000000000000000000000000000000..17d5e741772772548200528af60d8d9ed5a8227a --- /dev/null +++ b/Src/Compilers/Core/Source/PEWriter/CustomDebugInfoWriter.cs @@ -0,0 +1,390 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis; +using Roslyn.Utilities; + +namespace Microsoft.Cci +{ + internal sealed class CustomDebugInfoWriter + { + private uint methodTokenWithModuleInfo; + private IMethodBody methodBodyWithModuleInfo; + + private uint previousMethodTokenWithUsingInfo; + private IMethodBody previousMethodBodyWithUsingInfo; + + public CustomDebugInfoWriter() + { + } + + /// + /// Returns true if the namespace scope for this method should be forwarded to another method. + /// Returns non-null if the forwarding should be done directly via UsingNamespace, + /// null if the forwarding is done via custom debug info. + /// + public bool ShouldForwardNamespaceScopes(IMethodBody methodBody, uint methodToken, out IMethodDefinition forwardToMethod) + { + if (ShouldForwardToPreviousMethodWithUsingInfo(methodBody) || methodBody.NamespaceScopes.IsEmpty) + { + // SerializeNamespaceScopeMetadata will do the actual forwarding in case this is a CSharp method. + // VB on the other hand adds a "@methodtoken" to the scopes instead. + if (methodBody.CustomDebugInfoKind == CustomDebugInfoKind.VisualBasicStyle) + { + forwardToMethod = this.previousMethodBodyWithUsingInfo.MethodDefinition; + } + else + { + forwardToMethod = null; + } + + return true; + } + + this.previousMethodBodyWithUsingInfo = methodBody; + this.previousMethodTokenWithUsingInfo = methodToken; + forwardToMethod = null; + return false; + } + + public byte[] SerializeMethodDebugInfo(IModule module, IMethodBody methodBody, uint methodToken, out bool emitExternNamespaces) + { + emitExternNamespaces = false; + + // CONSIDER: this may not be the same "first" method as in Dev10, but + // it shouldn't matter since all methods will still forward to a method + // containing the appropriate information. + if (this.methodBodyWithModuleInfo == null) //UNDONE: || edit-and-continue + { + // This module level information could go on every method (and does in + // the edit-and-continue case), but - as an optimization - we'll just + // put it on the first method we happen to encounter and then put a + // reference to the first method's token in every other method (so they + // can find the information). + if (module.ExternNamespaces.Any()) + { + this.methodTokenWithModuleInfo = methodToken; + this.methodBodyWithModuleInfo = methodBody; + emitExternNamespaces = true; + } + } + + var customDebugInfo = ArrayBuilder.GetInstance(); + + SerializeIteratorClassMetadata(methodBody, customDebugInfo); + + // NOTE: This is an attempt to match Dev10's apparent behavior. For iterator methods (i.e. the method + // that appears in source, not the synthesized ones), Dev10 only emits the ForwardIterator and IteratorLocal + // custom debug info (e.g. there will be no information about the usings that were in scope). + // NOTE: There seems to be an unusual behavior in ISymUnmanagedWriter where, if all the methods in a type are + // iterator methods, no custom debug info is emitted for any method. Adding a single non-iterator + // method causes the custom debug info to be produced for all methods (including the iterator methods). + // Since we are making the same ISymUnmanagedWriter calls as Dev10, we see the same behavior (i.e. this + // is not a regression). + if (methodBody.IteratorClassName == null) + { + SerializeNamespaceScopeMetadata(methodBody, customDebugInfo); + SerializeIteratorLocalScopes(methodBody, customDebugInfo); + } + + SerializeDynamicLocalInfo(methodBody, customDebugInfo); + + byte[] result; + if (methodBody.CustomDebugInfoKind == CustomDebugInfoKind.CSharpStyle) + { + result = SerializeCustomDebugMetadata(customDebugInfo); + } + else + { + result = null; + } + + customDebugInfo.Free(); + return result; + } + + private static void SerializeIteratorClassMetadata(IMethodBody methodBody, ArrayBuilder customDebugInfo) + { + SerializeReferenceToIteratorClass(methodBody.IteratorClassName, customDebugInfo); + } + + private static void SerializeReferenceToIteratorClass(string iteratorClassName, ArrayBuilder customDebugInfo) + { + if (iteratorClassName == null) return; + MemoryStream customMetadata = new MemoryStream(); + BinaryWriter cmw = new BinaryWriter(customMetadata, true); + cmw.WriteByte(4); // version + cmw.WriteByte(4); // kind: ForwardIterator + cmw.Align(4); + uint length = 10 + (uint)iteratorClassName.Length * 2; + if ((length & 3) != 0) length += 4 - (length & 3); + cmw.WriteUint(length); + cmw.WriteString(iteratorClassName, true); + cmw.Align(4); + Debug.Assert(customMetadata.Position == length); + customDebugInfo.Add(customMetadata); + } + + private static void SerializeIteratorLocalScopes(IMethodBody methodBody, ArrayBuilder customDebugInfo) + { + ImmutableArray scopes = methodBody.IteratorScopes; + uint numberOfScopes = (uint)scopes.Length; + if (numberOfScopes == 0) + { + return; + } + + MemoryStream customMetadata = new MemoryStream(); + BinaryWriter cmw = new BinaryWriter(customMetadata); + cmw.WriteByte(4); // version + cmw.WriteByte(3); // kind: IteratorLocals + cmw.Align(4); + cmw.WriteUint(12 + numberOfScopes * 8); + cmw.WriteUint(numberOfScopes); + foreach (var scope in scopes) + { + cmw.WriteUint(scope.Offset); + cmw.WriteUint(scope.Offset + scope.Length); + } + + customDebugInfo.Add(customMetadata); + } + + private static void SerializeDynamicLocalInfo(IMethodBody methodBody, ArrayBuilder customDebugInfo) + { + if (!methodBody.HasDynamicLocalVariables) + { + return; //There are no dynamic locals + } + + var dynamicLocals = ArrayBuilder.GetInstance(); + + foreach (ILocalDefinition local in methodBody.LocalVariables) + { + if (local.IsDynamic) + { + dynamicLocals.Add(local); + } + } + + int dynamicVariableCount = dynamicLocals.Count; + + foreach (var currentScope in methodBody.LocalScopes) + { + foreach (var localConstant in currentScope.Constants) + { + if (localConstant.IsDynamic) + { + dynamicLocals.Add(localConstant); + } + } + } + + Debug.Assert(dynamicLocals.Any()); // There must be atleast one dynamic local if this point is reached + + const int blobSize = 200;//DynamicAttribute - 64, DynamicAttributeLength - 4, SlotIndex -4, IdentifierName - 128 + MemoryStream customMetadata = new MemoryStream(); + BinaryWriter cmw = new BinaryWriter(customMetadata, true); + cmw.WriteByte(4);//Version + cmw.WriteByte(5);//Kind : Dynamic Locals + cmw.Align(4); + // size = Version,Kind + size + cBuckets + (dynamicCount * sizeOf(Local Blob)) + cmw.WriteUint(4 + 4 + 4 + (uint)dynamicLocals.Count * blobSize);//Size of the Dynamic Block + cmw.WriteUint((uint)dynamicLocals.Count); + + int localIndex = 0; + foreach (ILocalDefinition local in dynamicLocals) + { + if (local.Name.Length > 63)//Ignore and push empty information + { + cmw.WriteBytes(0, blobSize); + continue; + } + + var dynamicTransformFlags = local.DynamicTransformFlags; + if (!dynamicTransformFlags.IsDefault && dynamicTransformFlags.Length <= 64) + { + byte[] flag = new byte[64]; + for (int k = 0; k < dynamicTransformFlags.Length; k++) + { + if ((bool)dynamicTransformFlags[k].Value) + { + flag[k] = (byte)1; + } + } + cmw.WriteBytes(flag); //Written Flag + cmw.WriteUint((uint)dynamicTransformFlags.Length); //Written Length + } + else + { + cmw.WriteBytes(0, 68); //Empty flag array and size. + } + + if (localIndex < dynamicVariableCount) + { + // Dynamic variable + cmw.WriteUint((uint)local.SlotIndex); + } + else + { + // Dynamic constant + cmw.WriteUint(0); + } + + char[] localName = new char[64]; + local.Name.CopyTo(0, localName, 0, local.Name.Length); + cmw.WriteChars(localName); + + localIndex++; + } + + dynamicLocals.Free(); + customDebugInfo.Add(customMetadata); + } + + private static byte[] SerializeCustomDebugMetadata(ArrayBuilder customDebugInfo) + { + if (customDebugInfo.Count == 0) + { + return null; + } + + MemoryStream customMetadata = MemoryStream.GetInstance(); + BinaryWriter cmw = new BinaryWriter(customMetadata); + cmw.WriteByte(4); // version + cmw.WriteByte((byte)customDebugInfo.Count); // count + cmw.Align(4); + foreach (MemoryStream ms in customDebugInfo) + { + ms.WriteTo(customMetadata); + } + + var result = customMetadata.ToArray(); + customMetadata.Free(); + return result; + } + + private void SerializeNamespaceScopeMetadata(IMethodBody methodBody, ArrayBuilder customDebugInfo) + { + if (ShouldForwardToPreviousMethodWithUsingInfo(methodBody)) + { + Debug.Assert(!ReferenceEquals(this.previousMethodBodyWithUsingInfo, methodBody)); + SerializeReferenceToPreviousMethodWithUsingInfo(customDebugInfo); + return; + } + + MemoryStream customMetadata = new MemoryStream(); + List usingCounts = new List(); + BinaryWriter cmw = new BinaryWriter(customMetadata); + foreach (NamespaceScope namespaceScope in methodBody.NamespaceScopes) + { + usingCounts.Add((ushort)namespaceScope.UsedNamespaces.Length); + } + + // ACASEY: This originally wrote (uint)12, (ushort)1, (ushort)0 in the + // case where usingCounts was empty, but I'm not sure why. + if (usingCounts.Count > 0) + { + uint streamLength = 0; + cmw.WriteByte(4); // version + cmw.WriteByte(0); // kind: UsingInfo + cmw.Align(4); + + cmw.WriteUint(streamLength = PeWriter.Aligned((uint)usingCounts.Count * 2 + 10, 4)); + cmw.WriteUshort((ushort)usingCounts.Count); + foreach (ushort uc in usingCounts) + { + cmw.WriteUshort(uc); + } + + cmw.Align(4); + Debug.Assert(streamLength == customMetadata.Length); + customDebugInfo.Add(customMetadata); + } + + if (this.methodBodyWithModuleInfo != null && !ReferenceEquals(this.methodBodyWithModuleInfo, methodBody)) + { + SerializeReferenceToMethodWithModuleInfo(customDebugInfo); + } + } + + private bool ShouldForwardToPreviousMethodWithUsingInfo(IMethodBody methodBody) + { + if (this.previousMethodBodyWithUsingInfo == null || ReferenceEquals(this.previousMethodBodyWithUsingInfo, methodBody)) + { + return false; + } + + // CONSIDER: is there a more efficient way to check if the scopes are the same? + // CONSIDER: might want to cache the list of scopes. + var previousScopes = this.previousMethodBodyWithUsingInfo.NamespaceScopes; + return methodBody.NamespaceScopes.SequenceEqual(previousScopes, NamespaceScopeComparer.Instance); + } + + private void SerializeReferenceToMethodWithModuleInfo(ArrayBuilder customDebugInfo) + { + MemoryStream customMetadata = new MemoryStream(12); + BinaryWriter cmw = new BinaryWriter(customMetadata); + cmw.WriteByte(4); // version + cmw.WriteByte(2); // kind: ForwardToModuleInfo + cmw.Align(4); + cmw.WriteUint(12); + cmw.WriteUint(this.methodTokenWithModuleInfo); + customDebugInfo.Add(customMetadata); + } + + private void SerializeReferenceToPreviousMethodWithUsingInfo(ArrayBuilder customDebugInfo) + { + MemoryStream customMetadata = new MemoryStream(12); + BinaryWriter cmw = new BinaryWriter(customMetadata); + cmw.WriteByte(4); // version + cmw.WriteByte(1); // kind: ForwardInfo + cmw.Align(4); + cmw.WriteUint(12); + cmw.WriteUint(this.previousMethodTokenWithUsingInfo); + customDebugInfo.Add(customMetadata); + } + + private class NamespaceScopeComparer : IEqualityComparer + { + public static readonly IEqualityComparer Instance = new NamespaceScopeComparer(); + + public bool Equals(NamespaceScope x, NamespaceScope y) + { + Debug.Assert(x != null); + Debug.Assert(y != null); + return x.UsedNamespaces.SequenceEqual(y.UsedNamespaces, UsedNamespaceOrTypeComparer.Instance); + } + + public int GetHashCode(NamespaceScope obj) + { + throw ExceptionUtilities.Unreachable; + } + } + + private class UsedNamespaceOrTypeComparer : IEqualityComparer + { + public static readonly IEqualityComparer Instance = new UsedNamespaceOrTypeComparer(); + + public bool Equals(UsedNamespaceOrType x, UsedNamespaceOrType y) + { + Debug.Assert(x != null); + Debug.Assert(y != null); + return x.Kind == y.Kind && + x.Alias == y.Alias && + x.TargetName == y.TargetName && + x.ExternAlias == y.ExternAlias && + x.ProjectLevel == y.ProjectLevel; + } + + public int GetHashCode(UsedNamespaceOrType obj) + { + Debug.Assert(false); + return 0; + } + } + } +} diff --git a/Src/Compilers/Core/Source/PEWriter/Members.cs b/Src/Compilers/Core/Source/PEWriter/Members.cs index a4e294cb3fb8a99c80a7ccaa88a9fefd5ca1bcb8..572c50387fbd74e5a70e2660fac92b4671574897 100644 --- a/Src/Compilers/Core/Source/PEWriter/Members.cs +++ b/Src/Compilers/Core/Source/PEWriter/Members.cs @@ -301,6 +301,11 @@ ImmutableArray CustomModifiers /// Use rather than null. /// Location Location { get; } + + /// + /// Slot index or -1 if not applicable. + /// + int SlotIndex { get; } } /// diff --git a/Src/Compilers/Core/Source/PEWriter/PeDebugDirectory.cs b/Src/Compilers/Core/Source/PEWriter/PeDebugDirectory.cs index 12d703f74075a64e58b7c34fb500ea09e5104a78..cac3d63df1c1ee6f78f0f0835166f66e8c84bb22 100644 --- a/Src/Compilers/Core/Source/PEWriter/PeDebugDirectory.cs +++ b/Src/Compilers/Core/Source/PEWriter/PeDebugDirectory.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - namespace Microsoft.Cci +namespace Microsoft.Cci { internal class PeDebugDirectory { diff --git a/Src/Compilers/Core/Source/PEWriter/PeWriter.cs b/Src/Compilers/Core/Source/PEWriter/PeWriter.cs index e62808a2b76413e5bcee134ad583597f5508fe35..7c3a0ff3d978c45560e6eead63fe3f3a83e77ec6 100644 --- a/Src/Compilers/Core/Source/PEWriter/PeWriter.cs +++ b/Src/Compilers/Core/Source/PEWriter/PeWriter.cs @@ -15,7 +15,6 @@ using System.Text; using System.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.Collections; using Roslyn.Utilities; using EmitContext = Microsoft.CodeAnalysis.Emit.EmitContext; @@ -499,9 +498,7 @@ protected virtual void OnSerializedMetadataTables() // A map of method body to RVA. private readonly Dictionary possiblyDuplicateMethodBodies; private readonly MemoryStream methodStream = new MemoryStream(32 * 1024); - private uint tokenOfMethodWithModuleInfo; - private IMethodBody previousMethodBodyWithUsingInfo; - + private byte blobIndexSize; private byte stringIndexSize; private byte guidIndexSize; @@ -820,7 +817,7 @@ private void PopulateTablesAndSerializeMethodBodies(bool separateMethodIL) FillInSectionHeaders(); // Do this here so that tables and win32 resources can contain actual RVAs without the need for fixups. } - private static uint Aligned(uint position, uint alignment) + internal static uint Aligned(uint position, uint alignment) { uint result = position & ~(alignment - 1); if (result == position) @@ -2687,7 +2684,7 @@ public static uint GetTypeDefFlags(ITypeDefinition typeDef, EmitContext context) result |= TypeAttributes.UnicodeClass; break; - case Cci.Constants.CharSet_Auto: + case Constants.CharSet_Auto: result |= TypeAttributes.AutoClass; break; } @@ -5232,6 +5229,8 @@ private void SerializeMethodBodies(bool separateMethodIL) writer.WriteUint(0); } + var customDebugInfoWriter = new CustomDebugInfoWriter(); + foreach (IMethodDefinition method in this.GetMethodDefs()) { cancellationToken.ThrowIfCancellationRequested(); @@ -5244,7 +5243,7 @@ private void SerializeMethodBodies(bool separateMethodIL) IMethodBody body = method.GetBody(Context); if (body != null) { - this.SerializeMethodBody(body, writer); + this.SerializeMethodBody(body, writer, customDebugInfoWriter); this.OnSerializedMethodBody(method, body); } else if (!allowMissingMethodBodies) @@ -5254,9 +5253,7 @@ private void SerializeMethodBodies(bool separateMethodIL) } } - private readonly List customDebugMetadataForCurrentMethod = new List(); - - private void SerializeMethodBody(IMethodBody methodBody, BinaryWriter writer) + private void SerializeMethodBody(IMethodBody methodBody, BinaryWriter writer, CustomDebugInfoWriter customDebugInfoWriter) { uint localVariableSignatureToken = this.SerializeLocalVariableSignatureAndReturnToken(methodBody); @@ -5275,7 +5272,7 @@ private void SerializeMethodBody(IMethodBody methodBody, BinaryWriter writer) if (possiblyDuplicateMethodBodies.TryGetValue(il, out ilBeginningOffset)) { this.methodBodyIndex[methodBody.MethodDefinition] = ilBeginningOffset; - SerializeDebugInfo(methodBody, (uint)il.Length); + SerializeDebugInfo(methodBody, (uint)il.Length, customDebugInfoWriter); return; } @@ -5312,22 +5309,13 @@ private void SerializeMethodBody(IMethodBody methodBody, BinaryWriter writer) this.SerializeMethodBodyExceptionHandlerTable(methodBody, numberOfExceptionHandlers, writer); } - SerializeDebugInfo(methodBody, (uint)il.Length); + SerializeDebugInfo(methodBody, (uint)il.Length, customDebugInfoWriter); } - private void SerializeDebugInfo(IMethodBody methodBody, uint ilLength) + private void SerializeDebugInfo(IMethodBody methodBody, uint ilLength, CustomDebugInfoWriter customDebugInfoWriter) { - // NOTE: This is an attempt to match Dev10's apparent behavior. For iterator methods (i.e. the method - // that appears in source, not the synthesized ones), Dev10 only emits the ForwardIterator and IteratorLocal - // custom debug info (e.g. there will be no information about the usings that were in scope). - // NOTE: There seems to be an unusual behavior in ISymUnmanagedWriter where, if all the methods in a type are - // iterator methods, no custom debug info is emitted for any method. Adding a single non-iterator - // method causes the custom debug info to be produced for all methods (including the iterator methods). - // Since we are making the same ISymUnmanagedWriter calls as Dev10, we see the same behavior (i.e. this - // is not a regression). - bool hasIteratorClassName = methodBody.IteratorClassName != null; - - bool emitDebugInfo = this.pdbWriter != null && (hasIteratorClassName || methodBody.HasAnyLocations); + bool isIterator = methodBody.IteratorClassName != null; + bool emitDebugInfo = this.pdbWriter != null && (isIterator || methodBody.HasAnyLocations); if (!emitDebugInfo) { @@ -5341,9 +5329,7 @@ private void SerializeDebugInfo(IMethodBody methodBody, uint ilLength) var scopes = methodBody.LocalScopes; - this.customDebugMetadataForCurrentMethod.Clear(); - - // EDMAURER CCI originally didn't have the notion of the default scope that is open + // CCI originally didn't have the notion of the default scope that is open // when a method is opened. In order to reproduce CSC PDBs, this must be added. Otherwise // a seemingly unecessary scope that contains only other scopes is put in the PDB. if (scopes.Length > 0) @@ -5351,25 +5337,26 @@ private void SerializeDebugInfo(IMethodBody methodBody, uint ilLength) this.DefineScopeLocals(scopes[0]); } - // NOTE: skipping using debug info for iterator methods (see comment on hasIteratorClassName). - if (!hasIteratorClassName) + // NOTE: This is an attempt to match Dev10's apparent behavior. For iterator methods (i.e. the method + // that appears in source, not the synthesized ones), Dev10 only emits the ForwardIterator and IteratorLocal + // custom debug info (e.g. there will be no information about the usings that were in scope). + if (!isIterator) { - this.SerializeNamespaceScopes(methodBody); - } + IMethodDefinition forwardToMethod; + if (customDebugInfoWriter.ShouldForwardNamespaceScopes(methodBody, methodToken, out forwardToMethod)) + { + if (forwardToMethod != null) + { + string usingString = "@" + this.GetMethodToken(forwardToMethod); + Debug.Assert(!IsUsingStringTooLong(usingString)); + this.pdbWriter.UsingNamespace(usingString, methodBody.MethodDefinition.Name); + } - // CONSIDER: this may not be the same "first" method as in Dev10, but - // it shouldn't matter since all methods will still forward to a method - // containing the appropriate information. - if (this.tokenOfMethodWithModuleInfo == 0) //UNDONE: || edit-and-continue - { - // This module level information could go on every method (and does in - // the edit-and-continue case), but - as an optimization - we'll just - // put it on the first method we happen to encounter and then put a - // reference to the first method's token in every other method (so they - // can find the information). - if (this.EmitExternNamespaceAssemblies(this.module)) + // otherwise, the forwarding is done via custom debug info + } + else { - this.tokenOfMethodWithModuleInfo = methodToken; + this.SerializeNamespaceScopes(methodBody); } } @@ -5388,70 +5375,25 @@ private void SerializeDebugInfo(IMethodBody methodBody, uint ilLength) asyncDebugInfo.ResumeOffsets); } - this.SerializeIteratorClassMetadata(methodBody); - - // NOTE: skipping using and iterator local debug info for iterator methods (see comment on hasIteratorClassName). - if (!hasIteratorClassName) + bool emitExternNamespaces; + byte[] blob = customDebugInfoWriter.SerializeMethodDebugInfo(module, methodBody, methodToken, out emitExternNamespaces); + if (blob != null) { - this.SerializeNamespaceScopeMetadata(methodBody); - this.SerializeIteratorLocalScopes(methodBody); + this.pdbWriter.DefineCustomMetadata("MD2", blob); } - this.SerializeDynamicLocalInfo(methodBody); - - if (methodBody.CustomDebugInfoKind == CustomDebugInfoKind.CSharpStyle) + if (emitExternNamespaces) { - this.SerializeCustomDebugMetadata(); + this.EmitExternNamespaceAssemblies(this.module); } this.pdbWriter.CloseMethod(ilLength); } - private void SerializeReferenceToMethodWithModuleInfo() - { - MemoryStream customMetadata = new MemoryStream(12); - BinaryWriter cmw = new BinaryWriter(customMetadata); - cmw.WriteByte(4); // version - cmw.WriteByte(2); // kind: ForwardToModuleInfo - cmw.Align(4); - cmw.WriteUint(12); - cmw.WriteUint(this.tokenOfMethodWithModuleInfo); - this.customDebugMetadataForCurrentMethod.Add(customMetadata); - } - - private void SerializeReferenceToPreviousMethodWithUsingInfo() - { - MemoryStream customMetadata = new MemoryStream(12); - BinaryWriter cmw = new BinaryWriter(customMetadata); - cmw.WriteByte(4); // version - cmw.WriteByte(1); // kind: ForwardInfo - cmw.Align(4); - cmw.WriteUint(12); - cmw.WriteUint(this.GetMethodToken(this.previousMethodBodyWithUsingInfo.MethodDefinition)); - this.customDebugMetadataForCurrentMethod.Add(customMetadata); - } - private void SerializeNamespaceScopes(IMethodBody methodBody) { // TODO: add nopia namespaces - - ImmutableArray scopes; - if (ShouldForwardToPreviousMethodWithUsingInfo(methodBody, out scopes) || scopes.IsEmpty) - { - // SerializeNamespaceScopeMetadata will do the actual forwarding in case this is a CSharp method. - // VB on the other hand adds a "@methodtoken" to the scopes instead. - if (methodBody.CustomDebugInfoKind == CustomDebugInfoKind.VisualBasicStyle) - { - UInt32 methodToken = this.GetMethodToken(this.previousMethodBodyWithUsingInfo.MethodDefinition); - string usingString = "@" + methodToken; - Debug.Assert(!IsUsingStringTooLong(usingString)); - this.pdbWriter.UsingNamespace(usingString, methodBody.MethodDefinition.Name); - } - return; - } - - this.previousMethodBodyWithUsingInfo = methodBody; - + // in case that the using namespace is too long, the native API SymWriter::UsingNamespace() would return // an E_OUTOFMEMORY which will be translated to an OutOfMemory exception. // To avoid this we're checking the length before calling. However if _MAX_SYM_SIZE ever gets changed in @@ -5471,7 +5413,7 @@ private void SerializeNamespaceScopes(IMethodBody methodBody) // DevDiv Bug 479703: "Possible access violation because of uninitialized data in string pool of SymWriter" (Resolved in RTM) IMethodDefinition methodDefinition = methodBody.MethodDefinition; - foreach (NamespaceScope namespaceScope in scopes) + foreach (NamespaceScope namespaceScope in methodBody.NamespaceScopes) { foreach (UsedNamespaceOrType used in namespaceScope.UsedNamespaces) { @@ -5484,275 +5426,16 @@ private void SerializeNamespaceScopes(IMethodBody methodBody) } } - private bool EmitExternNamespaceAssemblies(IModule module) + private void EmitExternNamespaceAssemblies(IModule module) { - bool emitted = false; - foreach (ExternNamespace @extern in module.GetExternNamespaces()) + foreach (ExternNamespace @extern in module.ExternNamespaces) { string usingString = "Z" + @extern.NamespaceAlias + " " + @extern.AssemblyName; if (!IsUsingStringTooLong(usingString)) { this.pdbWriter.UsingNamespace(usingString, @extern.NamespaceAlias); } - - emitted = true; // Not strictly true if the name was too long, but there's no point in retrying. } - - return emitted; - } - - private void SerializeIteratorLocalScopes(IMethodBody methodBody) - { - ImmutableArray scopes = methodBody.IteratorScopes; - uint numberOfScopes = (uint)scopes.Length; - if (numberOfScopes == 0) - { - return; - } - - MemoryStream customMetadata = new MemoryStream(); - BinaryWriter cmw = new BinaryWriter(customMetadata); - cmw.WriteByte(4); // version - cmw.WriteByte(3); // kind: IteratorLocals - cmw.Align(4); - cmw.WriteUint(12 + numberOfScopes * 8); - cmw.WriteUint(numberOfScopes); - foreach (var scope in scopes) - { - cmw.WriteUint(scope.Offset); - cmw.WriteUint(scope.Offset + scope.Length); - } - - this.customDebugMetadataForCurrentMethod.Add(customMetadata); - } - - private void SerializeDynamicLocalInfo(IMethodBody methodBody) - { - if (!methodBody.HasDynamicLocalVariables) - { - return; //There are no dynamic locals - } - - IEnumerable locals = methodBody.LocalVariables; - ArrayBuilder dynamicLocals = ArrayBuilder.GetInstance(); - foreach (ILocalDefinition local in locals) - { - if (local.IsDynamic) - { - dynamicLocals.Add(local); - } - } - - foreach (var currentScope in methodBody.LocalScopes) - { - foreach (var localConstant in currentScope.Constants) - { - if (localConstant.IsDynamic) - { - dynamicLocals.Add(localConstant); - } - } - } - - Debug.Assert(dynamicLocals.Any()); // There must be atleast one dynamic local if this point is reached - - const int blobSize = 200;//DynamicAttribute - 64, DynamicAttributeLength - 4, SlotIndex -4, IdentifierName - 128 - MemoryStream customMetadata = new MemoryStream(); - BinaryWriter cmw = new BinaryWriter(customMetadata, true); - cmw.WriteByte(4);//Version - cmw.WriteByte(5);//Kind : Dynamic Locals - cmw.Align(4); - // size = Version,Kind + size + cBuckets + (dynamicCount * sizeOf(Local Blob)) - cmw.WriteUint(4 + 4 + 4 + (uint)dynamicLocals.Count * blobSize);//Size of the Dynamic Block - cmw.WriteUint((uint)dynamicLocals.Count); - - foreach (ILocalDefinition local in dynamicLocals) - { - if (local.Name.Length > 63)//Ignore and push empty information - { - cmw.WriteBytes(0, blobSize); - continue; - } - - var dynamicTransformFlags = local.DynamicTransformFlags; - if (!dynamicTransformFlags.IsDefault && dynamicTransformFlags.Length <= 64) - { - byte[] flag = new byte[64]; - for (int k = 0; k < dynamicTransformFlags.Length; k++) - { - if ((bool)dynamicTransformFlags[k].Value) - { - flag[k] = (byte)1; - } - } - cmw.WriteBytes(flag); //Written Flag - cmw.WriteUint((uint)dynamicTransformFlags.Length); //Written Length - } - else - { - cmw.WriteBytes(0, 68); //Empty flag array and size. - } - - if (local is LocalDefinition) - { - cmw.WriteUint((uint)(local as LocalDefinition).SlotIndex); //Written Slot Index - } - else - { - Debug.Assert(local is LocalConstantDefinition); - cmw.WriteUint((uint)0);// Slot number is "0" for const dynamics. Native Behavior. - } - char[] localName = new char[64]; - local.Name.CopyTo(0, localName, 0, local.Name.Length); - cmw.WriteChars(localName); - } - - dynamicLocals.Free(); - this.customDebugMetadataForCurrentMethod.Add(customMetadata); - } - - private void SerializeCustomDebugMetadata() - { - if (this.customDebugMetadataForCurrentMethod.Count == 0) - { - return; - } - - MemoryStream customMetadata = MemoryStream.GetInstance(); - BinaryWriter cmw = new BinaryWriter(customMetadata); - cmw.WriteByte(4); // version - cmw.WriteByte((byte)this.customDebugMetadataForCurrentMethod.Count); // count - cmw.Align(4); - foreach (MemoryStream ms in this.customDebugMetadataForCurrentMethod) - { - ms.WriteTo(customMetadata); - } - - this.pdbWriter.DefineCustomMetadata("MD2", customMetadata.ToArray()); - customMetadata.Free(); - } - - private void SerializeNamespaceScopeMetadata(IMethodBody methodBody) - { - ImmutableArray scopes; - if (ShouldForwardToPreviousMethodWithUsingInfo(methodBody, out scopes)) - { - Debug.Assert(!ReferenceEquals(this.previousMethodBodyWithUsingInfo, methodBody)); - this.SerializeReferenceToPreviousMethodWithUsingInfo(); - return; - } - - MemoryStream customMetadata = new MemoryStream(); - List usingCounts = new List(); - BinaryWriter cmw = new BinaryWriter(customMetadata); - foreach (NamespaceScope namespaceScope in scopes) - { - usingCounts.Add((ushort)namespaceScope.UsedNamespaces.Length); - } - - // ACASEY: This originally wrote (uint)12, (ushort)1, (ushort)0 in the - // case where usingCounts was empty, but I'm not sure why. - if (usingCounts.Count > 0) - { - uint streamLength = 0; - cmw.WriteByte(4); // version - cmw.WriteByte(0); // kind: UsingInfo - cmw.Align(4); - - cmw.WriteUint(streamLength = Aligned((uint)usingCounts.Count * 2 + 10, 4)); - cmw.WriteUshort((ushort)usingCounts.Count); - foreach (ushort uc in usingCounts) - { - cmw.WriteUshort(uc); - } - - cmw.Align(4); - Debug.Assert(streamLength == customMetadata.Length); - this.customDebugMetadataForCurrentMethod.Add(customMetadata); - } - - if (this.tokenOfMethodWithModuleInfo != 0 && this.tokenOfMethodWithModuleInfo != this.GetMethodToken(methodBody.MethodDefinition)) - { - this.SerializeReferenceToMethodWithModuleInfo(); - } - } - - private bool ShouldForwardToPreviousMethodWithUsingInfo(IMethodBody methodBody, out ImmutableArray currentScopes) - { - currentScopes = methodBody.NamespaceScopes; - - if (ReferenceEquals(this.previousMethodBodyWithUsingInfo, null) || - ReferenceEquals(this.previousMethodBodyWithUsingInfo, methodBody)) - { - return false; - } - - // CONSIDER: is there a more efficient way to check if the scopes are the same? - // CONSIDER: might want to cache the list of scopes. - var previousScopes = this.previousMethodBodyWithUsingInfo.NamespaceScopes; - return currentScopes.SequenceEqual(previousScopes, NamespaceScopeComparer.Instance); - } - - private class NamespaceScopeComparer : IEqualityComparer - { - public static readonly IEqualityComparer Instance = new NamespaceScopeComparer(); - - public bool Equals(NamespaceScope x, NamespaceScope y) - { - Debug.Assert(x != null); - Debug.Assert(y != null); - return x.UsedNamespaces.SequenceEqual(y.UsedNamespaces, UsedNamespaceOrTypeComparer.Instance); - } - - public int GetHashCode(NamespaceScope obj) - { - Debug.Assert(false); - return 0; - } - } - - private class UsedNamespaceOrTypeComparer : IEqualityComparer - { - public static readonly IEqualityComparer Instance = new UsedNamespaceOrTypeComparer(); - - public bool Equals(UsedNamespaceOrType x, UsedNamespaceOrType y) - { - Debug.Assert(x != null); - Debug.Assert(y != null); - return x.Kind == y.Kind && - x.Alias == y.Alias && - x.TargetName == y.TargetName && - x.ExternAlias == y.ExternAlias && - x.ProjectLevel == y.ProjectLevel; - } - - public int GetHashCode(UsedNamespaceOrType obj) - { - Debug.Assert(false); - return 0; - } - } - - private void SerializeIteratorClassMetadata(IMethodBody methodBody) - { - this.SerializeReferenceToIteratorClass(methodBody.IteratorClassName); - } - - private void SerializeReferenceToIteratorClass(string iteratorClassName) - { - if (iteratorClassName == null) return; - MemoryStream customMetadata = new MemoryStream(); - BinaryWriter cmw = new BinaryWriter(customMetadata, true); - cmw.WriteByte(4); // version - cmw.WriteByte(4); // kind: ForwardIterator - cmw.Align(4); - uint length = 10 + (uint)iteratorClassName.Length * 2; - if ((length & 3) != 0) length += 4 - (length & 3); - cmw.WriteUint(length); - cmw.WriteString(iteratorClassName, true); - cmw.Align(4); - Debug.Assert(customMetadata.Position == length); - this.customDebugMetadataForCurrentMethod.Add(customMetadata); } private uint ReadUint(byte[] buffer, int pos) @@ -5846,11 +5529,11 @@ private byte[] SerializeMethodBodyIL(IMethodBody methodBody) return methodBodyIL; } - private void EmitPdbScopes(ImmutableArray scopes) + private void EmitPdbScopes(ImmutableArray scopes) { // The order of OpenScope and CloseScope calls must follow the scope nesting. - var scopeStack = ArrayBuilder.GetInstance(); + var scopeStack = ArrayBuilder.GetInstance(); for (int i = 1; i < scopes.Length; i++) { @@ -5859,7 +5542,7 @@ private void EmitPdbScopes(ImmutableArray scopes) // Close any scopes that have finished. while (scopeStack.Count > 0) { - Cci.LocalScope topScope = scopeStack.Last(); + LocalScope topScope = scopeStack.Last(); if (currentScope.Offset < topScope.Offset + topScope.Length) { break; @@ -5878,7 +5561,7 @@ private void EmitPdbScopes(ImmutableArray scopes) // Close remaining scopes. for (int i = scopeStack.Count - 1; i >= 0; i--) { - Cci.LocalScope scope = scopeStack[i]; + LocalScope scope = scopeStack[i]; this.pdbWriter.CloseScope(scope.Offset + scope.Length); } @@ -6195,7 +5878,7 @@ private void SerializeMarshallingDescriptor(IMarshallingInformation marshallingI break; - case Cci.Constants.UnmanagedType_CustomMarshaler: + case Constants.UnmanagedType_CustomMarshaler: writer.WriteUshort(0); // padding object marshaller = marshallingInformation.GetCustomMarshaller(Context); diff --git a/Src/Compilers/Core/Source/PEWriter/Units.cs b/Src/Compilers/Core/Source/PEWriter/Units.cs index 7b4ab805f46085f423054b32629eb80965c10152..55c0b7034c486d21be767958f4560a025adcd148 100644 --- a/Src/Compilers/Core/Source/PEWriter/Units.cs +++ b/Src/Compilers/Core/Source/PEWriter/Units.cs @@ -390,7 +390,7 @@ ulong SizeOfStackReserve /// MultiDictionary GetSymbolToLocationMap(); - ImmutableArray GetExternNamespaces(); + ImmutableArray ExternNamespaces { get; } ushort MajorSubsystemVersion { get; } ushort MinorSubsystemVersion { get; } diff --git a/Src/Compilers/Core/Source/ReferenceManager/CommonReferenceManager.Resolution.cs b/Src/Compilers/Core/Source/ReferenceManager/CommonReferenceManager.Resolution.cs index 5d83140e05446de83d1ab29071d64d28a3156023..654de0a5ba3cebb9d7a9b7546e7b97a1427749ef 100644 --- a/Src/Compilers/Core/Source/ReferenceManager/CommonReferenceManager.Resolution.cs +++ b/Src/Compilers/Core/Source/ReferenceManager/CommonReferenceManager.Resolution.cs @@ -684,7 +684,7 @@ private static void AddModule(PEModule module, int referenceIndex, ResolvedRefer boundReferenceDirectives.Add(referenceDirective.File, boundReference); } - // Workaround for bug #531342: include facades if we reference a Portable Library: + // Workaround for bug #797360: include facades if we reference a Portable Library: referencesBuilder.AddRange(ResolveFacadeReferences(compilation)); // add external reference at the end, so that they are processed first: