Browse Source

Don't inline a pointer-typed stackalloc into an expression

A stackalloc whose result is a pointer is only valid C# as the initializer of a
pointer-typed local. The inliner moved a single-use pointer stackalloc into its
use, producing e.g. 'K.V(stackalloc int[3] { 1, 2, v })' or '*stackalloc ...';
in an expression position the stackalloc is typed as Span<T>, which does not
convert to a pointer, so the output did not compile. Keep such a stackalloc as a
separate local. Moving it into a local store (its declaration) and into the
Span<T>/ReadOnlySpan<T> constructor stay allowed, since those are the positions
where the pointer or span form is exactly what is wanted.

Found while exploring stackalloc-initializer coverage.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3813/head
Siegfried Pammer 2 weeks ago committed by Siegfried Pammer
parent
commit
d4922cfec0
  1. 12
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs
  2. 28
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

12
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs

@ -257,6 +257,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -257,6 +257,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return UsePointer((byte*)ptr);
}
// A pointer-typed stackalloc passed to a call must stay a local: inlining it into the
// argument would retype it as Span<T>, which does not convert to the pointer parameter.
public unsafe void StackAllocPassedToPointerCall(int v)
{
int* ptr = stackalloc int[3] { 1, 2, v };
UseIntPointer(ptr);
}
public unsafe static void UseIntPointer(int* ptr)
{
}
// A buffer that is only partially written through a reinterpreting cast is not an
// initializer: the fourth int is never assigned, so it must stay a sequence of stores
// rather than be reconstructed as 'stackalloc int[4] { 1, v, 3 }' (too few elements).

28
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -249,6 +249,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -249,6 +249,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (r.Type == FindResultType.Found || r.Type == FindResultType.NamedArgument)
{
var loadInst = r.LoadInst;
// A stackalloc whose result is a pointer (rather than a Span<T>) is only valid C# as
// the initializer of a pointer-typed local. Inlining it into any expression position
// retypes it as Span<T>, which breaks every pointer use (dereference, a pointer-typed
// argument, a reinterpreting cast). Moving it into a local store keeps it a local
// initializer, and the Span<T>/ReadOnlySpan<T> constructor is the one position where
// reinterpreting it as a span is exactly the point; everything else must not inline.
if (inlinedExpression.ResultType == StackType.I
&& (inlinedExpression is LocAlloc || inlinedExpression is Block { Kind: BlockKind.StackAllocInitializer })
&& loadInst.Parent is not StLoc
&& !IsStackAllocSpanConstructorArgument(loadInst))
{
return false;
}
if (loadInst.OpCode == OpCode.LdLoca)
{
if (!IsGeneratedTemporaryForAddressOf((LdLoca)loadInst, v, inlinedExpression, options))
@ -291,6 +304,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -291,6 +304,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
/// <summary>
/// Returns true if <paramref name="loadInst"/> is the pointer argument of a
/// <c>new Span&lt;T&gt;(pointer, length)</c> / <c>new ReadOnlySpan&lt;T&gt;(pointer, length)</c>
/// constructor, i.e. the one position where a pointer-typed stackalloc may be inlined
/// (TransformSpanTCtorContainingStackAlloc turns it into a span stackalloc afterwards).
/// </summary>
static bool IsStackAllocSpanConstructorArgument(ILInstruction loadInst)
{
return loadInst.Parent is NewObj newObj
&& newObj.Arguments.Count == 2
&& newObj.Arguments[0] == loadInst
&& (newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.SpanOfT)
|| newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT));
}
/// <summary>
/// Is this a temporary variable generated by the C# compiler for instance method calls on value type values
/// </summary>

Loading…
Cancel
Save