diff --git a/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs b/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs
index 120634338bcb9..96b32b1dec4c7 100644
--- a/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs
+++ b/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs
@@ -64,6 +64,13 @@ private enum IndirectReturnState : byte
private LocalDefinition _returnTemp;
+ ///
+ /// True if there was a anywhere in the method. This will
+ /// affect whether or not we require the locals init flag to be marked, since locals init
+ /// affects .
+ ///
+ private bool _sawStackalloc;
+
public CodeGenerator(
MethodSymbol method,
BoundStatement boundBody,
@@ -188,18 +195,24 @@ internal static bool IsStackLocal(LocalSymbol local, HashSet stackL
private bool IsStackLocal(LocalSymbol local) => IsStackLocal(local, _stackLocals);
- public void Generate()
+ public void Generate(out bool hasStackalloc)
{
this.GenerateImpl();
+ hasStackalloc = _sawStackalloc;
Debug.Assert(_asyncCatchHandlerOffset < 0);
Debug.Assert(_asyncYieldPoints == null);
Debug.Assert(_asyncResumePoints == null);
}
- public void Generate(out int asyncCatchHandlerOffset, out ImmutableArray asyncYieldPoints, out ImmutableArray asyncResumePoints)
+ public void Generate(
+ out int asyncCatchHandlerOffset,
+ out ImmutableArray asyncYieldPoints,
+ out ImmutableArray asyncResumePoints,
+ out bool hasStackAlloc)
{
this.GenerateImpl();
+ hasStackAlloc = _sawStackalloc;
Debug.Assert(_asyncCatchHandlerOffset >= 0);
asyncCatchHandlerOffset = _builder.GetILOffsetFromMarker(_asyncCatchHandlerOffset);
diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
index 87eb97ff0ea28..6bcfd38473ed9 100644
--- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
+++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
@@ -1916,6 +1916,7 @@ private void EmitConvertedStackAllocExpression(BoundConvertedStackAllocExpressio
// we can ignore that if the actual result is unused
if (used)
{
+ _sawStackalloc = true;
_builder.EmitOpCode(ILOpCode.Localloc);
}
diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
index 172279f2e86ac..f58217cfc2b49 100644
--- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
+++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
@@ -1411,6 +1411,7 @@ private static MethodBody GenerateMethodBody(
var optimizations = compilation.Options.OptimizationLevel;
ILBuilder builder = new ILBuilder(moduleBuilder, localSlotManager, optimizations, method.AreLocalsZeroed);
+ bool hasStackalloc;
DiagnosticBag diagnosticsForThisMethod = DiagnosticBag.GetInstance();
try
{
@@ -1447,7 +1448,7 @@ private static MethodBody GenerateMethodBody(
if (isAsyncStateMachine)
{
- codeGen.Generate(out int asyncCatchHandlerOffset, out var asyncYieldPoints, out var asyncResumePoints);
+ codeGen.Generate(out int asyncCatchHandlerOffset, out var asyncYieldPoints, out var asyncResumePoints, out hasStackalloc);
// The exception handler IL offset is used by the debugger to treat exceptions caught by the marked catch block as "user unhandled".
// This is important for async void because async void exceptions generally result in the process being terminated,
@@ -1468,7 +1469,7 @@ private static MethodBody GenerateMethodBody(
}
else
{
- codeGen.Generate();
+ codeGen.Generate(out hasStackalloc);
if ((object)kickoffMethod != null)
{
@@ -1531,6 +1532,7 @@ private static MethodBody GenerateMethodBody(
debugDocumentProvider,
builder.RealizedExceptionHandlers,
builder.AreLocalsZeroed,
+ hasStackalloc,
builder.GetAllScopes(),
builder.HasDynamicLocal,
importScopeOpt,
diff --git a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs
index 60f5f6980e70a..4a52b7688c757 100644
--- a/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs
+++ b/src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_WellKnownAttributes.cs
@@ -6,6 +6,7 @@
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Symbols;
@@ -20,19 +21,67 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public static class WellKnownAttributeTestsUtil
{
- public static bool? HasLocalsInit(this CompilationVerifier verifier, string methodName, bool realIL = false)
+ public static bool? HasLocalsInit(this CompilationVerifier verifier, string qualifiedMethodName, bool realIL = false)
{
- var il = verifier.VisualizeIL(methodName, realIL);
-
- if (il.Contains(".locals init ("))
+ if (realIL)
{
- return true;
+ var peReader = new PEReader(verifier.EmittedAssemblyData);
+ var metadataReader = peReader.GetMetadataReader();
+
+ int lastDotIndex = qualifiedMethodName.LastIndexOf('.');
+ var spanName = qualifiedMethodName.AsSpan();
+ var typeName = spanName.Slice(0, lastDotIndex);
+ var methodName = spanName.Slice(lastDotIndex + 1);
+
+ TypeDefinition typeDef = default;
+
+ foreach (var typeHandle in metadataReader.TypeDefinitions)
+ {
+ var type = metadataReader.GetTypeDefinition(typeHandle);
+ var name = metadataReader.GetString(type.Name);
+
+ if (name.AsSpan().Equals(typeName, StringComparison.Ordinal))
+ {
+ typeDef = type;
+ break;
+ }
+ }
+
+ Assert.NotEqual(default, typeDef);
+
+ MethodDefinition methodDef = default;
+
+ foreach (var methodHandle in typeDef.GetMethods())
+ {
+ var method = metadataReader.GetMethodDefinition(methodHandle);
+ var name = metadataReader.GetString(method.Name);
+
+ if (name.AsSpan().Equals(methodName, StringComparison.Ordinal))
+ {
+ methodDef = method;
+ break;
+ }
+ }
+
+ Assert.NotEqual(default, methodDef);
+
+ var block = peReader.GetMethodBody(methodDef.RelativeVirtualAddress);
+ return block.LocalVariablesInitialized;
}
- if (il.Contains(".locals ("))
+ else
{
- return false;
+ var il = verifier.VisualizeIL(qualifiedMethodName, realIL);
+
+ if (il.Contains(".locals init ("))
+ {
+ return true;
+ }
+ if (il.Contains(".locals ("))
+ {
+ return false;
+ }
+ return null;
}
- return null;
}
}
@@ -8929,6 +8978,20 @@ public static int Main()
#region SkipLocalsInitAttribute
+ private CompilationVerifier CompileAndVerifyWithSkipLocalsInit(string src)
+ {
+ const string skipLocalsInitDef = @"
+namespace System.Runtime.CompilerServices
+{
+ public class SkipLocalsInitAttribute : System.Attribute
+ {
+ }
+}";
+
+ var comp = CreateCompilation(new[] { src, skipLocalsInitDef }, options: TestOptions.UnsafeReleaseDll);
+ return CompileAndVerify(comp, verify: Verification.Fails);
+ }
+
[Fact]
public void SkipLocalsInitRequiresUnsafe()
{
@@ -9040,6 +9103,39 @@ partial class C
Assert.False(comp.HasLocalsInit("C.M"));
}
+ [Fact]
+ public unsafe void StackallocWithSkipLocalsInit()
+ {
+ var src = @"
+public class C
+{
+ [System.Runtime.CompilerServices.SkipLocalsInitAttribute]
+ public unsafe void M1()
+ {
+ int *ptr = stackalloc int[10];
+ System.Console.WriteLine(ptr[0]);
+ }
+
+ public unsafe void M2()
+ {
+ int *ptr = stackalloc int[10];
+ System.Console.WriteLine(ptr[0]);
+ }
+
+ public unsafe void M3()
+ {
+ int* p = stackalloc int[10]; // unused
+ }
+}";
+ var verifier = CompileAndVerifyWithSkipLocalsInit(src);
+ Assert.Null(verifier.HasLocalsInit("C.M1")); // no locals
+ Assert.False(verifier.HasLocalsInit("C.M1", realIL: true));
+ Assert.Null(verifier.HasLocalsInit("C.M2")); // no locals
+ Assert.True(verifier.HasLocalsInit("C.M2", realIL: true));
+ Assert.Null(verifier.HasLocalsInit("C.M3")); // no locals
+ Assert.False(verifier.HasLocalsInit("C.M3", realIL: true));
+ }
+
[Fact]
public void WhenMethodsDifferBySkipLocalsInitAttributeTheyMustHaveDifferentRVA()
{
diff --git a/src/Compilers/Core/Portable/CodeGen/MethodBody.cs b/src/Compilers/Core/Portable/CodeGen/MethodBody.cs
index 9f3c675c4989f..674f49b4c1c28 100644
--- a/src/Compilers/Core/Portable/CodeGen/MethodBody.cs
+++ b/src/Compilers/Core/Portable/CodeGen/MethodBody.cs
@@ -52,6 +52,7 @@ public MethodBody(
DebugDocumentProvider debugDocumentProvider,
ImmutableArray exceptionHandlers,
bool areLocalsZeroed,
+ bool hasStackalloc,
ImmutableArray localScopes,
bool hasDynamicLocalVariables,
Cci.IImportScope importScopeOpt,
@@ -75,6 +76,7 @@ public MethodBody(
_locals = locals;
_exceptionHandlers = exceptionHandlers;
_areLocalsZeroed = areLocalsZeroed;
+ HasStackalloc = hasStackalloc;
_localScopes = localScopes;
_hasDynamicLocalVariables = hasDynamicLocalVariables;
_importScopeOpt = importScopeOpt;
@@ -144,5 +146,10 @@ ImmutableArray Cci.IMethodBody.StateMachineHoistedLocalSlot
public ImmutableArray LambdaDebugInfo => _lambdaDebugInfo;
public ImmutableArray ClosureDebugInfo => _closureDebugInfo;
+
+ ///
+ /// True if there's a stackalloc somewhere in the method.
+ ///
+ public bool HasStackalloc { get; }
}
}
diff --git a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs
index 558515b147649..5d4895bb63299 100644
--- a/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs
+++ b/src/Compilers/Core/Portable/Emit/NoPia/CommonEmbeddedMethod.cs
@@ -113,6 +113,8 @@ public EmptyBody(CommonEmbeddedMethod method)
ImmutableArray Cci.IMethodBody.ExceptionRegions =>
ImmutableArray.Empty;
+ bool Cci.IMethodBody.HasStackalloc => false;
+
bool Cci.IMethodBody.AreLocalsZeroed => false;
ImmutableArray Cci.IMethodBody.LocalVariables =>
diff --git a/src/Compilers/Core/Portable/PEWriter/Members.cs b/src/Compilers/Core/Portable/PEWriter/Members.cs
index 18b2aa143e4a0..442cddb0187fa 100644
--- a/src/Compilers/Core/Portable/PEWriter/Members.cs
+++ b/src/Compilers/Core/Portable/PEWriter/Members.cs
@@ -328,6 +328,11 @@ ImmutableArray ExceptionRegions
///
bool AreLocalsZeroed { get; }
+ ///
+ /// True if there's a stackalloc somewhere in the method.
+ ///
+ bool HasStackalloc { 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 83769fc261fd2..b2c32d04147b0 100644
--- a/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs
+++ b/src/Compilers/Core/Portable/PEWriter/MetadataWriter.cs
@@ -2933,7 +2933,8 @@ private int SerializeMethodBody(MethodBodyStreamEncoder encoder, IMethodBody met
exceptionRegionCount: exceptionRegions.Length,
hasSmallExceptionRegions: MayUseSmallExceptionHeaders(exceptionRegions),
localVariablesSignature: localSignatureHandleOpt,
- attributes: (methodBody.AreLocalsZeroed ? MethodBodyAttributes.InitLocals : 0));
+ attributes: (methodBody.AreLocalsZeroed ? MethodBodyAttributes.InitLocals : 0),
+ hasDynamicStackAllocation: methodBody.HasStackalloc);
// Don't do small body method caching during deterministic builds until this issue is fixed
// https://github.com/dotnet/roslyn/issues/7595
diff --git a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb
index b5ef4a0b67aeb..98507a7b45bd2 100644
--- a/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb
+++ b/src/Compilers/VisualBasic/Portable/Compilation/MethodCompiler.vb
@@ -1611,6 +1611,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
debugDocumentProvider,
builder.RealizedExceptionHandlers,
areLocalsZeroed:=True,
+ hasStackalloc:=False,
localScopes,
hasDynamicLocalVariables:=False,
importScopeOpt:=importScopeOpt,
diff --git a/src/Test/Utilities/Portable/Metadata/ILValidation.cs b/src/Test/Utilities/Portable/Metadata/ILValidation.cs
index 9b0fd37ba38cb..ddcb0754b6e76 100644
--- a/src/Test/Utilities/Portable/Metadata/ILValidation.cs
+++ b/src/Test/Utilities/Portable/Metadata/ILValidation.cs
@@ -300,6 +300,22 @@ public static unsafe string GetMethodIL(this ImmutableArray ilArray)
return result.ToString();
}
+ public static unsafe MethodBodyBlock GetMethodBodyBlock(this ImmutableArray ilArray)
+ {
+ fixed (byte* ilPtr = ilArray.AsSpan())
+ {
+ int offset = 0;
+ // skip padding:
+ while (offset < ilArray.Length && ilArray[offset] == 0)
+ {
+ offset++;
+ }
+
+ var reader = new BlobReader(ilPtr + offset, ilArray.Length - offset);
+ return MethodBodyBlock.Create(reader);
+ }
+ }
+
public static Dictionary GetSequencePointMarkers(string pdbXml, string source = null)
{
var doc = new XmlDocument() { XmlResolver = null };