diff --git a/build/Targets/Packages.props b/build/Targets/Packages.props index 0c09c9832edf5..97015cac4a252 100644 --- a/build/Targets/Packages.props +++ b/build/Targets/Packages.props @@ -47,7 +47,7 @@ 3.13.8 15.0.26730-alpha 14.3.25407-alpha - 1.0.0-beta1-61531-03 + 1.0.0-beta1-62621-03 8.0.0.0-alpha 15.6.0-dev diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 898d0d7e41b9b..94c800cbaf647 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1346,7 +1346,7 @@ private static MethodBody GenerateMethodBody( var localSlotManager = new LocalSlotManager(variableSlotAllocatorOpt); var optimizations = compilation.Options.OptimizationLevel; - ILBuilder builder = new ILBuilder(moduleBuilder, localSlotManager, optimizations); + ILBuilder builder = new ILBuilder(moduleBuilder, localSlotManager, optimizations, method.AreLocalsZeroed); DiagnosticBag diagnosticsForThisMethod = DiagnosticBag.GetInstance(); try { @@ -1460,6 +1460,7 @@ private static MethodBody GenerateMethodBody( builder.RealizedSequencePoints, debugDocumentProvider, builder.RealizedExceptionHandlers, + builder.AreLocalsZeroed, builder.GetAllScopes(), builder.HasDynamicLocal, importScopeOpt, diff --git a/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs index 8c7a64501ddc3..8b1d98575b8bd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ErrorMethodSymbol.cs @@ -230,6 +230,11 @@ public sealed override DllImportData GetDllImportData() return null; } + public override bool AreLocalsZeroed + { + get { throw ExceptionUtilities.Unreachable; } + } + internal sealed override MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation { get { return null; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs index ba2678ebb3385..44f5ada21b8c9 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs @@ -1153,6 +1153,11 @@ internal override void AddSynthesizedReturnTypeAttributes(PEModuleBuilder module throw ExceptionUtilities.Unreachable; } + public override bool AreLocalsZeroed + { + get { throw ExceptionUtilities.Unreachable; } + } + // perf, not correctness internal override CSharpCompilation DeclaringCompilation => null; diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index 9754e641644b1..e0c8f014d08c8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -1221,6 +1221,11 @@ public virtual bool IsTupleMethod } } + /// + /// Returns true if locals are to be initialized + /// + public abstract bool AreLocalsZeroed { get; } + /// /// If this is a method of a tuple type, return corresponding underlying method from the /// tuple underlying type. Otherwise, null. diff --git a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs index ce420b290a41b..dca22c2440627 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs @@ -183,6 +183,11 @@ public override DllImportData GetDllImportData() return _reducedFrom.GetDllImportData(); } + public override bool AreLocalsZeroed + { + get { throw ExceptionUtilities.Unreachable; } + } + internal override MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation { get { return _reducedFrom.ReturnValueMarshallingInformation; } diff --git a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs index 8a7067f3b3bae..03a083fd3a813 100644 --- a/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/SignatureOnlyMethodSymbol.cs @@ -132,6 +132,8 @@ public SignatureOnlyMethodSymbol( public override bool IsExtern { get { throw ExceptionUtilities.Unreachable; } } + public override bool AreLocalsZeroed { get { throw ExceptionUtilities.Unreachable; } } + public override AssemblySymbol ContainingAssembly { get { throw ExceptionUtilities.Unreachable; } } internal override ModuleSymbol ContainingModule { get { throw ExceptionUtilities.Unreachable; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs index a4686aef4dcbd..0ce2b30f3606b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LambdaSymbol.cs @@ -136,6 +136,11 @@ public override DllImportData GetDllImportData() return null; } + public override bool AreLocalsZeroed + { + get { throw ExceptionUtilities.Unreachable; } + } + internal override MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation { get { return null; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs index 182b3db4ae931..a325df52960c8 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs @@ -340,6 +340,8 @@ internal override TypeSymbol IteratorElementType public override DllImportData GetDllImportData() => null; + public override bool AreLocalsZeroed => throw ExceptionUtilities.Unreachable; + internal override ImmutableArray GetAppliedConditionalSymbols() => ImmutableArray.Empty; internal override bool IsMetadataNewSlot(bool ignoreInterfaceImplementationChanges = false) => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs index 6066c790ae5d0..715c3337cc789 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberMethodSymbol.cs @@ -358,6 +358,15 @@ public override Symbol AssociatedSymbol } } + public override bool AreLocalsZeroed + { + get + { + var data = this.GetDecodedWellKnownAttributeData(); + return data != null ? !data.HasSkipLocalsInitAttribute : true; + } + } + #region Flags public override bool ReturnsVoid @@ -1128,6 +1137,10 @@ private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttribut arguments.Diagnostics.Add(ErrorCode.ERR_SecurityCriticalOrSecuritySafeCriticalOnAsync, arguments.AttributeSyntaxOpt.Location, arguments.AttributeSyntaxOpt.GetErrorDisplayName()); } } + else if (attribute.IsTargetAttribute(this, AttributeDescription.SkipLocalsInitAttribute)) + { + arguments.GetOrCreateData().HasSkipLocalsInitAttribute = true; + } else { var compilation = this.DeclaringCompilation; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs index adffc1b052b58..f3c0ba94d77fe 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedEntryPointSymbol.cs @@ -241,6 +241,11 @@ public override DllImportData GetDllImportData() return null; } + public override bool AreLocalsZeroed + { + get { return true; } + } + internal override MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation { get { return null; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs index 29649964c4d94..ef5c986c517a3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedGlobalMethodSymbol.cs @@ -116,6 +116,11 @@ public override DllImportData GetDllImportData() return null; } + public override bool AreLocalsZeroed + { + get { return true; } + } + internal override MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation { get { return null; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceMethodSymbol.cs index deb83e727e26b..feb93dbe09815 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedInstanceMethodSymbol.cs @@ -30,6 +30,14 @@ public sealed override bool IsImplicitlyDeclared } } + public override bool AreLocalsZeroed + { + get + { + return true; + } + } + internal override bool TryGetThisParameter(out ParameterSymbol thisParameter) { Debug.Assert(!IsStatic); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs index 544b2e958bbd4..696e6894eddfb 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedIntrinsicOperatorSymbol.cs @@ -163,6 +163,11 @@ public override DllImportData GetDllImportData() return null; } + public override bool AreLocalsZeroed + { + get { throw ExceptionUtilities.Unreachable; } + } + internal override IEnumerable GetSecurityInformation() { return SpecializedCollections.EmptyEnumerable(); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs index 6fd6822be3b90..c225277622c72 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedStaticConstructor.cs @@ -334,6 +334,11 @@ public override DllImportData GetDllImportData() return null; } + public override bool AreLocalsZeroed + { + get { return true; } + } + internal override MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation { get { return null; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs index 095f94414ea6e..0ef5df40e1017 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Wrapped/WrappedMethodSymbol.cs @@ -78,6 +78,14 @@ public override bool HidesBaseMethodsByName } } + public override bool AreLocalsZeroed + { + get + { + return UnderlyingMethod.AreLocalsZeroed; + } + } + public override ImmutableArray Locations { get diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs index 25cfab7699739..2296b94e18c2a 100644 --- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs @@ -8559,6 +8559,314 @@ public static int Main() #endregion + #region SkipLocalsInitAttribute + + [Fact] + public void SkipLocalsInitAttributeOnMethod() + { + var source = @" +namespace System.Runtime.CompilerServices +{ + public class SkipLocalsInitAttribute : System.Attribute + { + } +} + +public class C +{ + [System.Runtime.CompilerServices.SkipLocalsInitAttribute] + public void M_skip() + { + int x = 2; + x = x + x + x; + } + + public void M_init() + { + int x = 2; + x = x + x + x; + } +} +"; + + var comp = CompileAndVerify(source, verify: Verification.Fails); + + comp.VerifyIL("C.M_init", @" +{ + // Code size 9 (0x9) + .maxstack 2 + .locals init (int V_0) //x + IL_0000: ldc.i4.2 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldloc.0 + IL_0004: add + IL_0005: ldloc.0 + IL_0006: add + IL_0007: stloc.0 + IL_0008: ret +}", realIL: true); + + comp.VerifyIL("C.M_skip", @" +{ + // Code size 9 (0x9) + .maxstack 2 + .locals (int V_0) //x + IL_0000: ldc.i4.2 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldloc.0 + IL_0004: add + IL_0005: ldloc.0 + IL_0006: add + IL_0007: stloc.0 + IL_0008: ret +}", realIL: true); + + comp.VerifyIL("C.M_init", @" +{ + // Code size 9 (0x9) + .maxstack 2 + .locals init (int V_0) //x + IL_0000: ldc.i4.2 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldloc.0 + IL_0004: add + IL_0005: ldloc.0 + IL_0006: add + IL_0007: stloc.0 + IL_0008: ret +}", realIL: false); + + comp.VerifyIL("C.M_skip", @" +{ + // Code size 9 (0x9) + .maxstack 2 + .locals (int V_0) //x + IL_0000: ldc.i4.2 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldloc.0 + IL_0004: add + IL_0005: ldloc.0 + IL_0006: add + IL_0007: stloc.0 + IL_0008: ret +}", realIL: false); + } + + [Fact] + public void WhenMethodsDifferBySkipLocalsInitAttributeTheyMustHaveDifferentRVA() + { + var source = @" +namespace System.Runtime.CompilerServices +{ + public class SkipLocalsInitAttribute : System.Attribute + { + } +} + +public class C +{ + [System.Runtime.CompilerServices.SkipLocalsInitAttribute] + public unsafe void M_skip() + { + int *ptr = stackalloc int[10]; + System.Console.WriteLine(ptr[0]); + } + + [System.Runtime.CompilerServices.SkipLocalsInitAttribute] + public unsafe void M_skip_copy() + { + int *ptr = stackalloc int[10]; + System.Console.WriteLine(ptr[0]); + } + + [System.Runtime.CompilerServices.SkipLocalsInitAttribute] + public unsafe void M_skip_diff() + { + int *ptr = stackalloc int[11]; + System.Console.WriteLine(ptr[0]); + } + + public unsafe void M_init() + { + int *ptr = stackalloc int[10]; + System.Console.WriteLine(ptr[0]); + } + + public unsafe void M_init_copy() + { + int *ptr = stackalloc int[10]; + System.Console.WriteLine(ptr[0]); + } + + public unsafe void M_init_diff() + { + int *ptr = stackalloc int[11]; + System.Console.WriteLine(ptr[0]); + } +} +"; + + var comp = CreateStandardCompilation(source, options: TestOptions.UnsafeReleaseDll); + var metadata = ModuleMetadata.CreateFromStream(comp.EmitToStream()); + var peReader = metadata.Module.GetMetadataReader(); + + TypeDefinition typeC = default; + + foreach (var typeHandle in peReader.TypeDefinitions) + { + var type = peReader.GetTypeDefinition(typeHandle); + var name = peReader.GetString(type.Name); + + if (name == "C") + { + typeC = type; + break; + } + } + + Assert.NotEqual(typeC, default); + + MethodDefinition methodInit = default; + MethodDefinition methodSkip = default; + MethodDefinition methodInitCopy = default; + MethodDefinition methodSkipCopy = default; + MethodDefinition methodInitDiff = default; + MethodDefinition methodSkipDiff = default; + + foreach (var methodHandle in typeC.GetMethods()) + { + var method = peReader.GetMethodDefinition(methodHandle); + var name = peReader.GetString(method.Name); + + if (name == "M_init") + { + methodInit = method; + } + else if (name == "M_skip") + { + methodSkip = method; + } + else if (name == "M_init_copy") + { + methodInitCopy = method; + } + else if (name == "M_skip_copy") + { + methodSkipCopy = method; + } + else if (name == "M_init_diff") + { + methodInitDiff = method; + } + else if (name == "M_skip_diff") + { + methodSkipDiff = method; + } + } + + Assert.NotEqual(methodInit, default); + Assert.NotEqual(methodSkip, default); + Assert.NotEqual(methodInitCopy, default); + Assert.NotEqual(methodSkipCopy, default); + Assert.NotEqual(methodInitDiff, default); + Assert.NotEqual(methodSkipDiff, default); + + Assert.NotEqual(methodInit.RelativeVirtualAddress, methodSkip.RelativeVirtualAddress); + Assert.Equal(methodInit.RelativeVirtualAddress, methodInitCopy.RelativeVirtualAddress); + Assert.Equal(methodSkip.RelativeVirtualAddress, methodSkipCopy.RelativeVirtualAddress); + Assert.NotEqual(methodInit.RelativeVirtualAddress, methodInitDiff.RelativeVirtualAddress); + Assert.NotEqual(methodSkip.RelativeVirtualAddress, methodSkipDiff.RelativeVirtualAddress); + } + + [Fact] + public void SkipLocalsInitAttributeOnClassDoesNotPropagateToMethod() + { + var source = @" +namespace System.Runtime.CompilerServices +{ + public class SkipLocalsInitAttribute : System.Attribute + { + } +} + +[System.Runtime.CompilerServices.SkipLocalsInitAttribute] +public class C +{ + public void M() + { + int x = 2; + x = x + x + x; + } +} +"; + + var comp = CompileAndVerify(source); + + comp.VerifyIL("C.M", @" +{ + // Code size 9 (0x9) + .maxstack 2 + .locals init (int V_0) //x + IL_0000: ldc.i4.2 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldloc.0 + IL_0004: add + IL_0005: ldloc.0 + IL_0006: add + IL_0007: stloc.0 + IL_0008: ret +}"); + } + + [Fact] + public void SkipLocalsInitAttributeOnAssemblyDoesNotPropagateToMethod() + { + var source = @" +[assembly: System.Runtime.CompilerServices.SkipLocalsInitAttribute] + +namespace System.Runtime.CompilerServices +{ + public class SkipLocalsInitAttribute : System.Attribute + { + } +} + +public class C +{ + public void M() + { + int x = 2; + x = x + x + x; + } +} +"; + + var comp = CompileAndVerify(source); + + comp.VerifyIL("C.M", @" +{ + // Code size 9 (0x9) + .maxstack 2 + .locals init (int V_0) //x + IL_0000: ldc.i4.2 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldloc.0 + IL_0004: add + IL_0005: ldloc.0 + IL_0006: add + IL_0007: stloc.0 + IL_0008: ret +}"); + } + + #endregion + [Fact, WorkItem(807, "https://github.com/dotnet/roslyn/issues/807")] public void TestAttributePropagationForAsyncAndIterators_01() { diff --git a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs index da884aa92a19c..3bf48d8579019 100644 --- a/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs +++ b/src/Compilers/Core/Portable/CodeGen/ILBuilder.cs @@ -32,6 +32,7 @@ internal sealed partial class ILBuilder private SyntaxTree _lastSeqPointTree; private readonly SmallDictionary _labelInfos; + private readonly bool _areLocalsZeroed; private int _instructionCountAtLastLabel = -1; // This data is only relevant when builder has been realized. @@ -62,7 +63,7 @@ internal sealed partial class ILBuilder // created, in particular for leader blocks in exception handlers. private bool _pendingBlockCreate; - internal ILBuilder(ITokenDeferral module, LocalSlotManager localSlotManager, OptimizationLevel optimizations) + internal ILBuilder(ITokenDeferral module, LocalSlotManager localSlotManager, OptimizationLevel optimizations, bool areLocalsZeroed) { Debug.Assert(BitConverter.IsLittleEndian); @@ -75,8 +76,11 @@ internal ILBuilder(ITokenDeferral module, LocalSlotManager localSlotManager, Opt _labelInfos = new SmallDictionary(ReferenceEqualityComparer.Instance); _optimizations = optimizations; + _areLocalsZeroed = areLocalsZeroed; } + public bool AreLocalsZeroed => _areLocalsZeroed; + private BasicBlock GetCurrentBlock() { Debug.Assert(!_pendingBlockCreate || (_currentBlock == null)); diff --git a/src/Compilers/Core/Portable/CodeGen/MethodBody.cs b/src/Compilers/Core/Portable/CodeGen/MethodBody.cs index b74db9ba0bf2c..9f3c675c4989f 100644 --- a/src/Compilers/Core/Portable/CodeGen/MethodBody.cs +++ b/src/Compilers/Core/Portable/CodeGen/MethodBody.cs @@ -19,6 +19,7 @@ internal sealed class MethodBody : Cci.IMethodBody private readonly ushort _maxStack; private readonly ImmutableArray _locals; private readonly ImmutableArray _exceptionHandlers; + private readonly bool _areLocalsZeroed; // Debug information emitted to Release & Debug PDBs supporting the debugger, EEs and other tools: private readonly ImmutableArray _sequencePoints; @@ -50,6 +51,7 @@ public MethodBody( SequencePointList sequencePoints, DebugDocumentProvider debugDocumentProvider, ImmutableArray exceptionHandlers, + bool areLocalsZeroed, ImmutableArray localScopes, bool hasDynamicLocalVariables, Cci.IImportScope importScopeOpt, @@ -72,6 +74,7 @@ public MethodBody( _methodId = methodId; _locals = locals; _exceptionHandlers = exceptionHandlers; + _areLocalsZeroed = areLocalsZeroed; _localScopes = localScopes; _hasDynamicLocalVariables = hasDynamicLocalVariables; _importScopeOpt = importScopeOpt; @@ -102,7 +105,7 @@ public MethodBody( ImmutableArray Cci.IMethodBody.ExceptionRegions => _exceptionHandlers; - bool Cci.IMethodBody.LocalsAreZeroed => true; + bool Cci.IMethodBody.AreLocalsZeroed => _areLocalsZeroed; ImmutableArray Cci.IMethodBody.LocalVariables => _locals; diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs index 51c3b864283ec..558515b147649 100644 --- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs +++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs @@ -113,7 +113,7 @@ public EmptyBody(CommonEmbeddedMethod method) ImmutableArray Cci.IMethodBody.ExceptionRegions => ImmutableArray.Empty; - bool Cci.IMethodBody.LocalsAreZeroed => false; + bool Cci.IMethodBody.AreLocalsZeroed => false; ImmutableArray Cci.IMethodBody.LocalVariables => ImmutableArray.Empty; diff --git a/src/Compilers/Core/Portable/PEWriter/Members.cs b/src/Compilers/Core/Portable/PEWriter/Members.cs index 40ee7a314257d..18b2aa143e4a0 100644 --- a/src/Compilers/Core/Portable/PEWriter/Members.cs +++ b/src/Compilers/Core/Portable/PEWriter/Members.cs @@ -326,7 +326,7 @@ ImmutableArray ExceptionRegions /// /// True if the locals are initialized by zeroing the stack upon method entry. /// - bool LocalsAreZeroed { get; } + bool AreLocalsZeroed { get; } /// /// The local variables of the method. diff --git a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs index c2fefa900d5ec..cdc20fdba3b6d 100644 --- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs @@ -70,7 +70,7 @@ internal abstract partial class MetadataWriter internal readonly bool EmitTestCoverageData; // A map of method body before token translation to RVA. Used for deduplication of small bodies. - private readonly Dictionary, int> _smallMethodBodies; + private readonly Dictionary<(ImmutableArray, bool), int> _smallMethodBodies; private const byte TinyFormat = 2; private const int ThrowNullCodeSize = 2; @@ -110,7 +110,7 @@ protected MetadataWriter( this.metadata = metadata; _debugMetadataOpt = debugMetadataOpt; _dynamicAnalysisDataWriterOpt = dynamicAnalysisDataWriterOpt; - _smallMethodBodies = new Dictionary, int>(ByteSequenceComparer.Instance); + _smallMethodBodies = new Dictionary<(ImmutableArray, bool), int>(ByteSequenceBoolTupleComparer.Instance); } private int NumberOfTypeDefsEstimate { get { return _numTypeDefsEstimate; } } @@ -2941,6 +2941,7 @@ private int SerializeMethodBody(MethodBodyStreamEncoder encoder, IMethodBody met int ilLength = methodBody.IL.Length; var exceptionRegions = methodBody.ExceptionRegions; bool isSmallBody = ilLength < 64 && methodBody.MaxStack <= 8 && localSignatureHandleOpt.IsNil && exceptionRegions.Length == 0; + var smallBodyKey = (methodBody.IL, methodBody.AreLocalsZeroed); // Check if an identical method body has already been serialized. // If so, use the RVA of the already serialized one. @@ -2949,7 +2950,7 @@ private int SerializeMethodBody(MethodBodyStreamEncoder encoder, IMethodBody met // Don't do small body method caching during deterministic builds until this issue is fixed // https://github.com/dotnet/roslyn/issues/7595 int bodyOffset; - if (!_deterministic && isSmallBody && _smallMethodBodies.TryGetValue(methodBody.IL, out bodyOffset)) + if (!_deterministic && isSmallBody && _smallMethodBodies.TryGetValue(smallBodyKey, out bodyOffset)) { return bodyOffset; } @@ -2960,13 +2961,13 @@ private int SerializeMethodBody(MethodBodyStreamEncoder encoder, IMethodBody met exceptionRegionCount: exceptionRegions.Length, hasSmallExceptionRegions: MayUseSmallExceptionHeaders(exceptionRegions), localVariablesSignature: localSignatureHandleOpt, - attributes: (methodBody.LocalsAreZeroed ? MethodBodyAttributes.InitLocals : 0)); + attributes: (methodBody.AreLocalsZeroed ? MethodBodyAttributes.InitLocals : 0)); // Don't do small body method caching during deterministic builds until this issue is fixed // https://github.com/dotnet/roslyn/issues/7595 if (isSmallBody && !_deterministic) { - _smallMethodBodies.Add(methodBody.IL, encodedBody.Offset); + _smallMethodBodies.Add(smallBodyKey, encodedBody.Offset); } WriteInstructions(encodedBody.Instructions, methodBody.IL, ref mvidStringHandle, ref mvidStringFixup); @@ -4177,5 +4178,24 @@ protected override void AddItem(T item, int index) _structuralIndex.Add(item, index); } } + + private class ByteSequenceBoolTupleComparer : IEqualityComparer<(ImmutableArray, bool)> + { + internal static readonly ByteSequenceBoolTupleComparer Instance = new ByteSequenceBoolTupleComparer(); + + private ByteSequenceBoolTupleComparer() + { + } + + bool IEqualityComparer<(ImmutableArray, bool)>.Equals((ImmutableArray, bool) x, (ImmutableArray, bool) y) + { + return x.Item2 == y.Item2 && ByteSequenceComparer.Equals(x.Item1, y.Item1); + } + + int IEqualityComparer<(ImmutableArray, bool)>.GetHashCode((ImmutableArray, bool) x) + { + return Hash.Combine(ByteSequenceComparer.GetHashCode(x.Item1), x.Item2.GetHashCode()); + } + } } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs index ea72272358e4c..b4eadf9c540c8 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs @@ -401,6 +401,7 @@ static AttributeDescription() private static readonly byte[][] s_signaturesOfExperimentalAttribute = { s_signature_HasThis_Void }; private static readonly byte[][] s_signaturesOfExcludeFromCodeCoverageAttribute = { s_signature_HasThis_Void }; + private static readonly byte[][] s_signaturesOfSkipLocalsInitAttribute = { s_signature_HasThis_Void }; // early decoded attributes: internal static readonly AttributeDescription OptionalAttribute = new AttributeDescription("System.Runtime.InteropServices", "OptionalAttribute", s_signaturesOfOptionalAttribute); @@ -515,5 +516,6 @@ static AttributeDescription() internal static readonly AttributeDescription DeprecatedAttribute = new AttributeDescription("Windows.Foundation.Metadata", "DeprecatedAttribute", s_signaturesOfDeprecatedAttribute); internal static readonly AttributeDescription ExperimentalAttribute = new AttributeDescription("Windows.Foundation.Metadata", "ExperimentalAttribute", s_signaturesOfExperimentalAttribute); internal static readonly AttributeDescription ExcludeFromCodeCoverageAttribute = new AttributeDescription("System.Diagnostics.CodeAnalysis", "ExcludeFromCodeCoverageAttribute", s_signaturesOfExcludeFromCodeCoverageAttribute); + internal static readonly AttributeDescription SkipLocalsInitAttribute = new AttributeDescription("System.Runtime.CompilerServices", "SkipLocalsInitAttribute", s_signaturesOfSkipLocalsInitAttribute); } } diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/CommonMethodWellKnownAttributeData.cs b/src/Compilers/Core/Portable/Symbols/Attributes/CommonMethodWellKnownAttributeData.cs index ffa8cda9f3853..ce0150c1bdfe0 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/CommonMethodWellKnownAttributeData.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/CommonMethodWellKnownAttributeData.cs @@ -122,6 +122,24 @@ public MethodImplAttributes MethodImplAttributes } #endregion + #region SkipLocalsInitAttribute + private bool _hasSkipLocalsInitAttribute; + public bool HasSkipLocalsInitAttribute + { + get + { + VerifySealed(expected: true); + return _hasSkipLocalsInitAttribute; + } + set + { + VerifySealed(expected: false); + _hasSkipLocalsInitAttribute = value; + SetDataStored(); + } + } + #endregion + #region SpecialNameAttribute private bool _hasSpecialNameAttribute; public bool HasSpecialNameAttribute diff --git a/src/Compilers/Test/Utilities/CSharp/BasicCompilationUtils.cs b/src/Compilers/Test/Utilities/CSharp/BasicCompilationUtils.cs index d1d73544bf23c..a5d7b43084b11 100644 --- a/src/Compilers/Test/Utilities/CSharp/BasicCompilationUtils.cs +++ b/src/Compilers/Test/Utilities/CSharp/BasicCompilationUtils.cs @@ -53,7 +53,7 @@ protected override Compilation GetCompilationForEmit(IEnumerable source, throw new NotImplementedException(); } - internal override string VisualizeRealIL(IModuleSymbol peModule, CodeAnalysis.CodeGen.CompilationTestData.MethodData methodData, IReadOnlyDictionary markers) + internal override string VisualizeRealIL(IModuleSymbol peModule, CodeAnalysis.CodeGen.CompilationTestData.MethodData methodData, IReadOnlyDictionary markers, bool areLocalsZeroed) { throw new NotImplementedException(); } diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index b5abf12e67479..d70597a7df92f 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -944,9 +944,9 @@ internal static string GetDocumentationCommentText(CSharpCompilation compilation #region IL Validation - internal override string VisualizeRealIL(IModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary markers) + internal override string VisualizeRealIL(IModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary markers, bool areLocalsZeroed) { - return VisualizeRealIL((PEModuleSymbol)peModule, methodData, markers); + return VisualizeRealIL((PEModuleSymbol)peModule, methodData, markers, areLocalsZeroed); } /// @@ -959,7 +959,7 @@ internal override string VisualizeRealIL(IModuleSymbol peModule, CompilationTest /// - winmd /// - global methods /// - internal unsafe static string VisualizeRealIL(PEModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary markers) + internal unsafe static string VisualizeRealIL(PEModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary markers, bool areLocalsZeroed) { var typeName = GetContainingTypeMetadataName(methodData.Method); // TODO (tomat): global methods (typeName == null) @@ -1000,7 +1000,7 @@ internal unsafe static string VisualizeRealIL(PEModuleSymbol peModule, Compilati var visualizer = new Visualizer(new MetadataDecoder(peModule, peMethod)); - visualizer.DumpMethod(sb, maxStack, ilBytes, localDefinitions, ehHandlerRegions, markers); + visualizer.DumpMethod(sb, maxStack, ilBytes, localDefinitions, ehHandlerRegions, markers, areLocalsZeroed); return sb.ToString(); } diff --git a/src/Compilers/Test/Utilities/VisualBasic/BasicTestBase.vb b/src/Compilers/Test/Utilities/VisualBasic/BasicTestBase.vb index 9f56f053aeeab..db7316a7057cb 100644 --- a/src/Compilers/Test/Utilities/VisualBasic/BasicTestBase.vb +++ b/src/Compilers/Test/Utilities/VisualBasic/BasicTestBase.vb @@ -458,7 +458,7 @@ Public MustInherit Class BasicTestBaseBase Return attributes.Select(Function(a) a.AttributeClass.Name) End Function - Friend Overrides Function VisualizeRealIL(peModule As IModuleSymbol, methodData As CompilationTestData.MethodData, markers As IReadOnlyDictionary(Of Integer, String)) As String + Friend Overrides Function VisualizeRealIL(peModule As IModuleSymbol, methodData As CompilationTestData.MethodData, markers As IReadOnlyDictionary(Of Integer, String), areLocalsZeroed As Boolean) As String Throw New NotImplementedException() End Function diff --git a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb index 41c4925bffa86..8c030ee6f8901 100644 --- a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb +++ b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb @@ -1507,7 +1507,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic optimizations = OptimizationLevel.Release End If - Dim builder As ILBuilder = New ILBuilder(moduleBuilder, localSlotManager, optimizations) + Dim builder As ILBuilder = New ILBuilder(moduleBuilder, localSlotManager, optimizations, areLocalsZeroed:=True) Try Debug.Assert(Not diagnostics.HasAnyErrors) @@ -1610,6 +1610,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic builder.RealizedSequencePoints, debugDocumentProvider, builder.RealizedExceptionHandlers, + areLocalsZeroed:=True, localScopes, hasDynamicLocalVariables:=False, importScopeOpt:=importScopeOpt, diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs index 3660564e07507..878d59cd4a95c 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/EEMethodSymbol.cs @@ -239,6 +239,11 @@ public override DllImportData GetDllImportData() return null; } + public override bool AreLocalsZeroed + { + get { return true; } + } + internal override IEnumerable GetSecurityInformation() { throw ExceptionUtilities.Unreachable; diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/PlaceholderMethodSymbol.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/PlaceholderMethodSymbol.cs index 10ed2c9243de9..446829f7b5f12 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/PlaceholderMethodSymbol.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/Symbols/PlaceholderMethodSymbol.cs @@ -227,6 +227,11 @@ public override DllImportData GetDllImportData() return null; } + public override bool AreLocalsZeroed + { + get { throw ExceptionUtilities.Unreachable; } + } + internal override ImmutableArray GetAppliedConditionalSymbols() { throw ExceptionUtilities.Unreachable; diff --git a/src/Test/Utilities/Portable/CommonTestBase.cs b/src/Test/Utilities/Portable/CommonTestBase.cs index fef4e56141019..a237c1c03abb4 100644 --- a/src/Test/Utilities/Portable/CommonTestBase.cs +++ b/src/Test/Utilities/Portable/CommonTestBase.cs @@ -472,7 +472,7 @@ private void AddReferencedCompilations(IEnumerable referencedCompil #region IL Verification - internal abstract string VisualizeRealIL(IModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary markers); + internal abstract string VisualizeRealIL(IModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary markers, bool areLocalsZeroed); #endregion diff --git a/src/Test/Utilities/Portable/CompilationVerifier.cs b/src/Test/Utilities/Portable/CompilationVerifier.cs index 0133d53331f3c..d1c5bedbac6ce 100644 --- a/src/Test/Utilities/Portable/CompilationVerifier.cs +++ b/src/Test/Utilities/Portable/CompilationVerifier.cs @@ -28,11 +28,11 @@ public sealed class CompilationVerifier public ImmutableArray EmittedAssemblyData; public ImmutableArray EmittedAssemblyPdb; - private readonly Func, string> _visualizeRealIL; + private readonly Func, bool, string> _visualizeRealIL; internal CompilationVerifier( Compilation compilation, - Func, string> visualizeRealIL = null, + Func, bool, string> visualizeRealIL = null, IEnumerable dependencies = null) { _compilation = compilation; @@ -222,7 +222,7 @@ internal string VisualizeIL(CompilationTestData.MethodData methodData, bool real _lazyModuleSymbol = GetSymbolFromMetadata(targetReference, MetadataImportOptions.All); } - return _lazyModuleSymbol != null ? _visualizeRealIL(_lazyModuleSymbol, methodData, markers) : null; + return _lazyModuleSymbol != null ? _visualizeRealIL(_lazyModuleSymbol, methodData, markers, _testData.Module.GetMethodBody(methodData.Method).AreLocalsZeroed) : null; } public CompilationVerifier VerifyMemberInIL(string methodName, bool expected) diff --git a/src/Test/Utilities/Portable/Metadata/ILBuilderVisualizer.cs b/src/Test/Utilities/Portable/Metadata/ILBuilderVisualizer.cs index 451dc7cccc8cb..535138f31e4b7 100644 --- a/src/Test/Utilities/Portable/Metadata/ILBuilderVisualizer.cs +++ b/src/Test/Utilities/Portable/Metadata/ILBuilderVisualizer.cs @@ -152,7 +152,7 @@ internal static string ILBuilderToString( if (!ilStream.IsDefault) { - visualizer.DumpMethod(sb, builder.MaxStack, ilStream, locals, GetHandlerSpans(builder.RealizedExceptionHandlers), markers); + visualizer.DumpMethod(sb, builder.MaxStack, ilStream, locals, GetHandlerSpans(builder.RealizedExceptionHandlers), markers, builder.AreLocalsZeroed); } else {