Browse Source

Add support for C# 12 inline array expressions

feature/inlinearrays
Siegfried Pammer 2 months ago
parent
commit
79f7a188b0
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 138
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs
  4. 17
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  5. 22
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  6. 2
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
  7. 21
      ICSharpCode.Decompiler/DecompilerSettings.cs
  8. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  9. 149
      ICSharpCode.Decompiler/IL/Instructions.cs
  10. 3
      ICSharpCode.Decompiler/IL/Instructions.tt
  11. 5
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  12. 23
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  13. 234
      ICSharpCode.Decompiler/IL/Transforms/InlineArrayTransform.cs
  14. 5
      ICSharpCode.Decompiler/Semantics/Conversion.cs

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -140,6 +140,7 @@ @@ -140,6 +140,7 @@
<Compile Include="TestCases\ILPretty\MonoFixed.cs" />
<Compile Include="TestCases\Pretty\Comparisons.cs" />
<Compile Include="TestCases\Pretty\GloballyQualifiedTypeInStringInterpolation.cs" />
<Compile Include="TestCases\Pretty\InlineArrayTests.cs" />
<Compile Include="TestCases\Pretty\Issue3406.cs" />
<Compile Include="TestCases\Pretty\Issue3439.cs" />
<Compile Include="TestCases\Pretty\Issue3442.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -755,6 +755,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -755,6 +755,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions);
}
[Test]
public async Task InlineArrayTests([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
}
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
{
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);

138
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs

@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
using System;
using System.Runtime.CompilerServices;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class InlineArrayTests
{
[InlineArray(16)]
public struct Byte16
{
private byte elem;
}
[InlineArray(16)]
public struct Generic16<T>
{
private T elem;
}
public byte Byte0()
{
return GetByte16()[0];
}
public byte GenericByte0()
{
return GetGeneric<byte>()[0];
}
public byte Byte5()
{
return GetByte16()[5];
}
public byte GenericByte5()
{
return GetGeneric<byte>()[5];
}
public byte ByteN()
{
return GetByte16()[GetIndex()];
}
public byte GenericByteN()
{
return GetGeneric<byte>()[GetIndex()];
}
public byte Byte0(Byte16 array, byte value)
{
return array[0] = value;
}
public byte GenericByte0(Generic16<byte> array, byte value)
{
return array[0] = value;
}
public byte Byte5(Byte16 array, byte value)
{
return array[5] = value;
}
public byte GenericByte5(Generic16<byte> array, byte value)
{
return array[5] = value;
}
public byte ByteN(Byte16 array, byte value)
{
return array[GetIndex()] = value;
}
public byte GenericByteN(Generic16<byte> array, byte value)
{
return array[GetIndex()] = value;
}
public byte VariableSplitting(Byte16 array, byte value)
{
return array[GetIndex()] = (array[GetIndex() + 1] = value);
}
public void OverloadResolution()
{
Receiver(GetByte16());
Receiver((object)GetByte16());
Byte16 buffer = GetByte16();
Receiver((Span<byte>)buffer);
Byte16 buffer2 = GetByte16();
Receiver((ReadOnlySpan<byte>)buffer2);
Byte16 buffer3 = GetByte16();
ReceiverSpan((Span<byte>)buffer3);
Byte16 buffer4 = GetByte16();
ReceiverReadOnlySpan((ReadOnlySpan<byte>)buffer4);
}
public Byte16 GetByte16()
{
return default(Byte16);
}
public Generic16<T> GetGeneric<T>()
{
return default(Generic16<T>);
}
public int GetIndex()
{
return 0;
}
public void Receiver(Span<byte> span)
{
}
public void Receiver(ReadOnlySpan<byte> span)
{
}
public void Receiver(Byte16 span)
{
}
public void Receiver(object span)
{
}
public void ReceiverSpan(Span<byte> span)
{
}
public void ReceiverReadOnlySpan(ReadOnlySpan<byte> span)
{
}
}
}

17
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -463,6 +463,23 @@ namespace ICSharpCode.Decompiler.CSharp @@ -463,6 +463,23 @@ namespace ICSharpCode.Decompiler.CSharp
return HandleImplicitConversion(method, argumentList.Arguments[0]);
}
if (settings.InlineArrays
&& method is { DeclaringType.FullName: "<PrivateImplementationDetails>", Name: "InlineArrayAsSpan" or "InlineArrayAsReadOnlySpan" }
&& argumentList.Length == 2)
{
argumentList.CheckNoNamedOrOptionalArguments();
var argument = argumentList.Arguments[0];
var targetType = method.ReturnType;
if (argument.Expression is DirectionExpression { FieldDirection: FieldDirection.In or FieldDirection.Ref, Expression: var lvalueExpr })
{
// `(TargetType)(in arg)` is invalid syntax.
// Also, `f(in arg)` is invalid when there's an implicit conversion involved.
argument = argument.UnwrapChild(lvalueExpr);
}
return new CastExpression(expressionBuilder.ConvertType(targetType), argument.Expression)
.WithRR(new ConversionResolveResult(targetType, argument.ResolveResult, Conversion.InlineArrayConversion));
}
if (settings.LiftNullables && method.Name == "GetValueOrDefault"
&& method.DeclaringType.IsKnownType(KnownTypeCode.NullableOfT)
&& method.DeclaringType.TypeArguments[0].IsKnownType(KnownTypeCode.Boolean)

22
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -3130,6 +3130,28 @@ namespace ICSharpCode.Decompiler.CSharp @@ -3130,6 +3130,28 @@ namespace ICSharpCode.Decompiler.CSharp
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.ResolveResult, ReferenceKind.Ref));
}
protected internal override TranslatedExpression VisitLdElemaInlineArray(LdElemaInlineArray inst, TranslationContext context)
{
TranslatedExpression arrayExpr = TranslateTarget(
inst.Array,
nonVirtualInvocation: true,
memberStatic: false,
memberDeclaringType: inst.Type
);
var inlineArrayElementType = GetInlineArrayElementType(inst.Type);
IndexerExpression indexerExpr = new IndexerExpression(
arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
);
TranslatedExpression expr = indexerExpr.WithILInstruction(inst).WithRR(new ResolveResult(inlineArrayElementType));
return new DirectionExpression(FieldDirection.Ref, expr)
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.ResolveResult, ReferenceKind.Ref));
IType GetInlineArrayElementType(IType arrayType)
{
return arrayType?.GetFields(f => !f.IsStatic).SingleOrDefault()?.Type ?? SpecialType.UnknownType;
}
}
TranslatedExpression TranslateArrayIndex(ILInstruction i)
{
var input = Translate(i);

2
ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

@ -142,7 +142,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -142,7 +142,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
if (c != Conversion.None)
return c;
}
if (resolveResult is InterpolatedStringResolveResult isrr)
if (resolveResult is InterpolatedStringResolveResult)
{
if (toType.IsKnownType(KnownTypeCode.IFormattable) || toType.IsKnownType(KnownTypeCode.FormattableString))
return Conversion.ImplicitInterpolatedStringConversion;

21
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -164,12 +164,13 @@ namespace ICSharpCode.Decompiler @@ -164,12 +164,13 @@ namespace ICSharpCode.Decompiler
{
refReadOnlyParameters = false;
usePrimaryConstructorSyntaxForNonRecordTypes = false;
inlineArrays = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes)
if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes || inlineArrays)
return CSharp.LanguageVersion.CSharp12_0;
if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators)
return CSharp.LanguageVersion.CSharp11_0;
@ -2053,6 +2054,24 @@ namespace ICSharpCode.Decompiler @@ -2053,6 +2054,24 @@ namespace ICSharpCode.Decompiler
}
}
bool inlineArrays = true;
/// <summary>
/// Gets/Sets whether C# 12.0 inline array uses should be transformed.
/// </summary>
[Category("C# 12.0 / VS 2022.8")]
[Description("DecompilerSettings.InlineArrays")]
public bool InlineArrays {
get { return inlineArrays; }
set {
if (inlineArrays != value)
{
inlineArrays = value;
OnPropertyChanged();
}
}
}
bool separateLocalVariableDeclarations = false;
/// <summary>

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -107,6 +107,7 @@ @@ -107,6 +107,7 @@
<Compile Include="DecompilationProgress.cs" />
<Compile Include="Disassembler\IEntityProcessor.cs" />
<Compile Include="Disassembler\SortByNameProcessor.cs" />
<Compile Include="IL\Transforms\InlineArrayTransform.cs" />
<Compile Include="IL\Transforms\RemoveUnconstrainedGenericReferenceTypeCheck.cs" />
<Compile Include="Metadata\MetadataFile.cs" />
<Compile Include="Metadata\ModuleReferenceMetadata.cs" />

149
ICSharpCode.Decompiler/IL/Instructions.cs

@ -191,6 +191,8 @@ namespace ICSharpCode.Decompiler.IL @@ -191,6 +191,8 @@ namespace ICSharpCode.Decompiler.IL
LdLen,
/// <summary>Load address of array element.</summary>
LdElema,
/// <summary>Load address of inline array element.</summary>
LdElemaInlineArray,
/// <summary>Retrieves a pinnable reference for the input object.
/// The input must be an object reference (O).
/// If the input is an array/string, evaluates to a reference to the first element/character, or to a null reference if the array is null or empty.
@ -4998,6 +5000,127 @@ namespace ICSharpCode.Decompiler.IL @@ -4998,6 +5000,127 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Load address of inline array element.</summary>
public sealed partial class LdElemaInlineArray : ILInstruction
{
public LdElemaInlineArray(IType type, ILInstruction array, params ILInstruction[] indices) : base(OpCode.LdElemaInlineArray)
{
this.type = type;
this.Array = array;
this.Indices = new InstructionCollection<ILInstruction>(this, 1);
this.Indices.AddRange(indices);
}
IType type;
/// <summary>Returns the type operand.</summary>
public IType Type {
get { return type; }
set { type = value; InvalidateFlags(); }
}
public static readonly SlotInfo ArraySlot = new SlotInfo("Array", canInlineInto: true);
ILInstruction array = null!;
public ILInstruction Array {
get { return this.array; }
set {
ValidateChild(value);
SetChildInstruction(ref this.array, value, 0);
}
}
public static readonly SlotInfo IndicesSlot = new SlotInfo("Indices", canInlineInto: true);
public InstructionCollection<ILInstruction> Indices { get; private set; }
protected sealed override int GetChildCount()
{
return 1 + Indices.Count;
}
protected sealed override ILInstruction GetChild(int index)
{
switch (index)
{
case 0:
return this.array;
default:
return this.Indices[index - 1];
}
}
protected sealed override void SetChild(int index, ILInstruction value)
{
switch (index)
{
case 0:
this.Array = value;
break;
default:
this.Indices[index - 1] = (ILInstruction)value;
break;
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index)
{
case 0:
return ArraySlot;
default:
return IndicesSlot;
}
}
public sealed override ILInstruction Clone()
{
var clone = (LdElemaInlineArray)ShallowClone();
clone.Array = this.array.Clone();
clone.Indices = new InstructionCollection<ILInstruction>(clone, 1);
clone.Indices.AddRange(this.Indices.Select(arg => (ILInstruction)arg.Clone()));
return clone;
}
public override StackType ResultType { get { return StackType.Ref; } }
/// <summary>Gets whether the 'readonly' prefix was applied to this instruction.</summary>
public bool IsReadOnly { get; set; }
protected override InstructionFlags ComputeFlags()
{
return array.Flags | Indices.Aggregate(InstructionFlags.None, (f, arg) => f | arg.Flags) | InstructionFlags.MayThrow;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.MayThrow;
}
}
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
WriteILRange(output, options);
if (IsReadOnly)
output.Write("readonly.");
output.Write(OpCode);
output.Write(' ');
type.WriteTo(output);
output.Write('(');
this.array.WriteTo(output, options);
foreach (var indices in Indices)
{
output.Write(", ");
indices.WriteTo(output, options);
}
output.Write(')');
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitLdElemaInlineArray(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitLdElemaInlineArray(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
return visitor.VisitLdElemaInlineArray(this, context);
}
protected internal override bool PerformMatch(ILInstruction? other, ref Patterns.Match match)
{
var o = other as LdElemaInlineArray;
return o != null && type.Equals(o.type) && this.array.PerformMatch(o.array, ref match) && Patterns.ListMatch.DoMatch(this.Indices, o.Indices, ref match) && IsReadOnly == o.IsReadOnly;
}
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Retrieves a pinnable reference for the input object.
/// The input must be an object reference (O).
@ -7235,6 +7358,10 @@ namespace ICSharpCode.Decompiler.IL @@ -7235,6 +7358,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
protected internal virtual void VisitLdElemaInlineArray(LdElemaInlineArray inst)
{
Default(inst);
}
protected internal virtual void VisitGetPinnableReference(GetPinnableReference inst)
{
Default(inst);
@ -7641,6 +7768,10 @@ namespace ICSharpCode.Decompiler.IL @@ -7641,6 +7768,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
protected internal virtual T VisitLdElemaInlineArray(LdElemaInlineArray inst)
{
return Default(inst);
}
protected internal virtual T VisitGetPinnableReference(GetPinnableReference inst)
{
return Default(inst);
@ -8047,6 +8178,10 @@ namespace ICSharpCode.Decompiler.IL @@ -8047,6 +8178,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
protected internal virtual T VisitLdElemaInlineArray(LdElemaInlineArray inst, C context)
{
return Default(inst, context);
}
protected internal virtual T VisitGetPinnableReference(GetPinnableReference inst, C context)
{
return Default(inst, context);
@ -8223,6 +8358,7 @@ namespace ICSharpCode.Decompiler.IL @@ -8223,6 +8358,7 @@ namespace ICSharpCode.Decompiler.IL
"sizeof",
"ldlen",
"ldelema",
"ldelema.inlinearray",
"get.pinnable.reference",
"string.to.int",
"expression.tree.cast",
@ -8849,6 +8985,19 @@ namespace ICSharpCode.Decompiler.IL @@ -8849,6 +8985,19 @@ namespace ICSharpCode.Decompiler.IL
array = default(ILInstruction);
return false;
}
public bool MatchLdElemaInlineArray([NotNullWhen(true)] out IType? type, [NotNullWhen(true)] out ILInstruction? array)
{
var inst = this as LdElemaInlineArray;
if (inst != null)
{
type = inst.Type;
array = inst.Array;
return true;
}
type = default(IType);
array = default(ILInstruction);
return false;
}
public bool MatchGetPinnableReference([NotNullWhen(true)] out ILInstruction? argument, out IMethod? method)
{
var inst = this as GetPinnableReference;

3
ICSharpCode.Decompiler/IL/Instructions.tt

@ -290,6 +290,9 @@ @@ -290,6 +290,9 @@
CustomClassName("LdElema"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("array"), new ArgumentInfo("indices") { IsCollection = true } }, true),
BoolFlag("WithSystemIndex"),
MayThrowIfNotDelayed, ResultType("Ref"), SupportsReadonlyPrefix),
new OpCode("ldelema.inlinearray", "Load address of inline array element.",
CustomClassName("LdElemaInlineArray"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("array"), new ArgumentInfo("indices") { IsCollection = true } }, true),
MayThrow, ResultType("Ref"), SupportsReadonlyPrefix),
new OpCode("get.pinnable.reference", "Retrieves a pinnable reference for the input object." + Environment.NewLine
+ "The input must be an object reference (O)." + Environment.NewLine
+ "If the input is an array/string, evaluates to a reference to the first element/character, or to a null reference if the array is null or empty." + Environment.NewLine

5
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -277,6 +277,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -277,6 +277,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
protected internal override void VisitCall(Call inst)
{
if (context.Settings.InlineArrays && InlineArrayTransform.RunOnExpression(inst, context))
{
return;
}
if (NullableLiftingTransform.MatchGetValueOrDefault(inst, out var nullableValue, out var fallback)
&& SemanticHelper.IsPure(fallback.Flags))
{

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

@ -333,6 +333,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -333,6 +333,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
throw new InvalidOperationException("invalid expression classification");
}
}
else if (loadInst.Parent is LdElemaInlineArray)
{
return true;
}
else if (IsPassedToReadOnlySpanOfCharCtor(loadInst))
{
// Always inlining is possible here, because it's an 'in' or 'ref readonly' parameter
@ -344,6 +348,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -344,6 +348,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// already inlined.
return true;
}
if (IsPassedToInlineArrayAsSpan(loadInst))
{
// Inlining is not allowed
return false;
}
else if (IsPassedToInParameter(loadInst))
{
if (options.HasFlag(InliningOptions.Aggressive))
@ -377,6 +386,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -377,6 +386,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
private static bool IsPassedToInlineArrayAsSpan(LdLoca loadInst)
{
if (loadInst.Parent is not Call call)
return false;
var method = call.Method;
var declaringType = method.DeclaringType;
return declaringType.ReflectionName == "<PrivateImplementationDetails>"
&& method.Name is "InlineArrayAsReadOnlySpan" or "InlineArrayAsSpan"
&& method.Parameters is [var arg, var length]
&& (method.ReturnType.IsKnownType(KnownTypeCode.SpanOfT) || method.ReturnType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT))
&& arg.Type is ByReferenceType
&& length.Type.IsKnownType(KnownTypeCode.Int32);
}
internal static bool MethodRequiresCopyForReadonlyLValue(IMethod method, IType constrainedTo = null)
{
if (method == null)

234
ICSharpCode.Decompiler/IL/Transforms/InlineArrayTransform.cs

@ -0,0 +1,234 @@ @@ -0,0 +1,234 @@
// Copyright (c) 2025 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Diagnostics.CodeAnalysis;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms
{
static class InlineArrayTransform
{
internal static bool RunOnExpression(Call inst, StatementTransformContext context)
{
if (MatchSpanIndexerWithInlineArrayAsSpan(inst, out var type, out var addr, out var index, out bool isReadOnly))
{
if (isReadOnly)
{
context.Step("call get_Item(addressof System.ReadOnlySpan{T}(call InlineArrayAsReadOnlySpan(addr)), index) -> readonly.ldelema.inlinearray(addr, index)", inst);
}
else
{
context.Step("call get_Item(addressof System.Span{T}(call InlineArrayAsSpan(addr)), index) -> ldelema.inlinearray(addr, index)", inst);
}
inst.ReplaceWith(new LdElemaInlineArray(type, addr, index) { IsReadOnly = isReadOnly }.WithILRange(inst));
return true;
}
if (MatchInlineArrayElementRef(inst, out type, out addr, out index, out isReadOnly))
{
if (isReadOnly)
{
context.Step("call InlineArrayElementRefReadOnly(addr, index) -> readonly.ldelema.inlinearray(addr, index)", inst);
}
else
{
context.Step("call InlineArrayElementRef(addr, index) -> ldelema.inlinearray(addr, index)", inst);
}
inst.ReplaceWith(new LdElemaInlineArray(type, addr, index) { IsReadOnly = isReadOnly }.WithILRange(inst));
return true;
}
if (MatchInlineArrayFirstElementRef(inst, out type, out addr, out isReadOnly))
{
if (isReadOnly)
{
context.Step("call InlineArrayFirstElementRefReadOnly(addr) -> readonly.ldelema.inlinearray(addr, ldc.i4 0)", inst);
}
else
{
context.Step("call InlineArrayFirstElementRef(addr) -> ldelema.inlinearray(addr, ldc.i4 0)", inst);
}
inst.ReplaceWith(new LdElemaInlineArray(type, addr, new LdcI4(0)) { IsReadOnly = isReadOnly }.WithILRange(inst));
return true;
}
return false;
}
/// <summary>
/// Matches call get_Item(addressof System.(ReadOnly)Span[[T]](call InlineArrayAs(ReadOnly)Span(addr, length)), index)
/// </summary>
static bool MatchSpanIndexerWithInlineArrayAsSpan(Call inst, [NotNullWhen(true)] out IType? type, [NotNullWhen(true)] out ILInstruction? addr, [NotNullWhen(true)] out ILInstruction? index, out bool isReadOnly)
{
isReadOnly = false;
type = null;
addr = null;
index = null;
if (MatchSpanGetItem(inst.Method, "ReadOnlySpan"))
{
isReadOnly = true;
if (inst.Arguments is not [AddressOf { Value: Call targetInst, Type: var typeInfo }, var indexInst])
return false;
if (!MatchInlineArrayHelper(targetInst.Method, "InlineArrayAsReadOnlySpan", out var inlineArrayType))
return false;
if (targetInst.Arguments is not [var addrInst, LdcI4])
return false;
type = inlineArrayType;
addr = addrInst;
index = indexInst;
return true;
}
else if (MatchSpanGetItem(inst.Method, "Span"))
{
if (inst.Arguments is not [AddressOf { Value: Call targetInst, Type: var typeInfo }, var indexInst])
return false;
if (!MatchInlineArrayHelper(targetInst.Method, "InlineArrayAsSpan", out var inlineArrayType))
return false;
if (targetInst.Arguments is not [var addrInst, LdcI4])
return false;
type = inlineArrayType;
addr = addrInst;
index = indexInst;
return true;
}
else
{
return false;
}
}
/// <summary>
/// Matches call InlineArrayElementRef(ReadOnly)(addr, index)
/// </summary>
static bool MatchInlineArrayElementRef(Call inst, [NotNullWhen(true)] out IType? type, [NotNullWhen(true)] out ILInstruction? addr, [NotNullWhen(true)] out ILInstruction? index, out bool isReadOnly)
{
type = null;
addr = null;
index = null;
isReadOnly = false;
if (inst.Arguments is not [var addrInst, var indexInst])
return false;
if (MatchInlineArrayHelper(inst.Method, "InlineArrayElementRef", out var inlineArrayType))
{
isReadOnly = false;
type = inlineArrayType;
addr = addrInst;
index = indexInst;
return true;
}
if (MatchInlineArrayHelper(inst.Method, "InlineArrayElementRefReadOnly", out inlineArrayType))
{
isReadOnly = true;
type = inlineArrayType;
addr = addrInst;
index = indexInst;
return true;
}
return false;
}
private static bool MatchInlineArrayFirstElementRef(Call inst, [NotNullWhen(true)] out IType? type, [NotNullWhen(true)] out ILInstruction? addr, out bool isReadOnly)
{
type = null;
addr = null;
isReadOnly = false;
if (inst.Arguments is not [var addrInst])
return false;
if (MatchInlineArrayHelper(inst.Method, "InlineArrayFirstElementRef", out var inlineArrayType))
{
isReadOnly = false;
type = inlineArrayType;
addr = addrInst;
return true;
}
if (MatchInlineArrayHelper(inst.Method, "InlineArrayFirstElementRefReadOnly", out inlineArrayType))
{
isReadOnly = true;
type = inlineArrayType;
addr = addrInst;
return true;
}
return false;
}
static bool MatchSpanGetItem(IMethod method, string typeName)
{
return method is {
IsStatic: false,
Name: "get_Item",
DeclaringType: { Namespace: "System", Name: string name, TypeParameterCount: 1, DeclaringType: null }
} && typeName == name;
}
static bool MatchInlineArrayHelper(IMethod method, string methodName, [NotNullWhen(true)] out IType? inlineArrayType)
{
inlineArrayType = null;
if (method is not {
IsStatic: true, Name: var name,
DeclaringType: { FullName: "<PrivateImplementationDetails>", TypeParameterCount: 0 },
TypeArguments: [var bufferType, _],
Parameters: var parameters
})
{
return false;
}
if (methodName != name)
return false;
if (methodName.Contains("FirstElement"))
{
if (parameters is not [{ Type: ByReferenceType { ElementType: var type } }])
return false;
if (!type.Equals(bufferType))
return false;
}
else
{
if (parameters is not [{ Type: ByReferenceType { ElementType: var type } }, { Type: var lengthOrIndexParameterType }])
return false;
if (!type.Equals(bufferType) || !lengthOrIndexParameterType.IsKnownType(KnownTypeCode.Int32))
return false;
}
inlineArrayType = bufferType;
return true;
}
}
}

5
ICSharpCode.Decompiler/Semantics/Conversion.cs

@ -87,6 +87,11 @@ namespace ICSharpCode.Decompiler.Semantics @@ -87,6 +87,11 @@ namespace ICSharpCode.Decompiler.Semantics
/// </summary>
public static readonly Conversion ThrowExpressionConversion = new BuiltinConversion(true, 11);
/// <summary>
/// C# 12 inline array implicitly being converted to <see cref="System.Span{T}"/> or <see cref="System.ReadOnlySpan{T}"/>.
/// </summary>
public static readonly Conversion InlineArrayConversion = new BuiltinConversion(true, 12);
public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted = false, bool isAmbiguous = false)
{
if (operatorMethod == null)

Loading…
Cancel
Save