diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 1a0d83379..110b3bb36 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -140,6 +140,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 25c5e0191..12020c28b 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -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 configureDecompiler = null)
{
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs
new file mode 100644
index 000000000..79b800c39
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InlineArrayTests.cs
@@ -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
+ {
+ private T elem;
+ }
+
+ public byte Byte0()
+ {
+ return GetByte16()[0];
+ }
+
+ public byte GenericByte0()
+ {
+ return GetGeneric()[0];
+ }
+
+ public byte Byte5()
+ {
+ return GetByte16()[5];
+ }
+
+ public byte GenericByte5()
+ {
+ return GetGeneric()[5];
+ }
+
+ public byte ByteN()
+ {
+ return GetByte16()[GetIndex()];
+ }
+
+ public byte GenericByteN()
+ {
+ return GetGeneric()[GetIndex()];
+ }
+
+ public byte Byte0(Byte16 array, byte value)
+ {
+ return array[0] = value;
+ }
+
+ public byte GenericByte0(Generic16 array, byte value)
+ {
+ return array[0] = value;
+ }
+
+ public byte Byte5(Byte16 array, byte value)
+ {
+ return array[5] = value;
+ }
+
+ public byte GenericByte5(Generic16 array, byte value)
+ {
+ return array[5] = value;
+ }
+
+ public byte ByteN(Byte16 array, byte value)
+ {
+ return array[GetIndex()] = value;
+ }
+
+ public byte GenericByteN(Generic16 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)buffer);
+ Byte16 buffer2 = GetByte16();
+ Receiver((ReadOnlySpan)buffer2);
+ Byte16 buffer3 = GetByte16();
+ ReceiverSpan((Span)buffer3);
+ Byte16 buffer4 = GetByte16();
+ ReceiverReadOnlySpan((ReadOnlySpan)buffer4);
+ }
+
+ public Byte16 GetByte16()
+ {
+ return default(Byte16);
+ }
+
+ public Generic16 GetGeneric()
+ {
+ return default(Generic16);
+ }
+
+ public int GetIndex()
+ {
+ return 0;
+ }
+
+ public void Receiver(Span span)
+ {
+ }
+
+ public void Receiver(ReadOnlySpan span)
+ {
+ }
+
+ public void Receiver(Byte16 span)
+ {
+ }
+
+ public void Receiver(object span)
+ {
+ }
+
+ public void ReceiverSpan(Span span)
+ {
+ }
+
+ public void ReceiverReadOnlySpan(ReadOnlySpan span)
+ {
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
index b8b6680e5..298adf7c4 100644
--- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
@@ -463,6 +463,23 @@ namespace ICSharpCode.Decompiler.CSharp
return HandleImplicitConversion(method, argumentList.Arguments[0]);
}
+ if (settings.InlineArrays
+ && method is { DeclaringType.FullName: "", 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)
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 13ec88dcc..43fd7bb78 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -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);
diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
index 6b19a4762..f190a554f 100644
--- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
+++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
@@ -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;
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 0ccfec2fa..bbf7a0055 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -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
}
}
+ bool inlineArrays = true;
+
+ ///
+ /// Gets/Sets whether C# 12.0 inline array uses should be transformed.
+ ///
+ [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;
///
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 5e02ca705..7e2e19341 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -107,6 +107,7 @@
+
diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs
index 4f3ef843d..4094f9c9e 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -191,6 +191,8 @@ namespace ICSharpCode.Decompiler.IL
LdLen,
/// Load address of array element.
LdElema,
+ /// Load address of inline array element.
+ LdElemaInlineArray,
/// 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
}
}
namespace ICSharpCode.Decompiler.IL
+{
+ /// Load address of inline array element.
+ 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(this, 1);
+ this.Indices.AddRange(indices);
+ }
+ IType type;
+ /// Returns the type operand.
+ 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 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(clone, 1);
+ clone.Indices.AddRange(this.Indices.Select(arg => (ILInstruction)arg.Clone()));
+ return clone;
+ }
+ public override StackType ResultType { get { return StackType.Ref; } }
+ /// Gets whether the 'readonly' prefix was applied to this instruction.
+ 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(ILVisitor visitor)
+ {
+ return visitor.VisitLdElemaInlineArray(this);
+ }
+ public override T AcceptVisitor(ILVisitor 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
{
/// Retrieves a pinnable reference for the input object.
/// The input must be an object reference (O).
@@ -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
{
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
{
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
"sizeof",
"ldlen",
"ldelema",
+ "ldelema.inlinearray",
"get.pinnable.reference",
"string.to.int",
"expression.tree.cast",
@@ -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;
diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt
index 6f42205af..8c2a5bf5e 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -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
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
index 57b475694..90907922b 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
@@ -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))
{
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 2b92f6cc5..af52fd58b 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -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
// 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
}
}
+ 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 == ""
+ && 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)
diff --git a/ICSharpCode.Decompiler/IL/Transforms/InlineArrayTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/InlineArrayTransform.cs
new file mode 100644
index 000000000..8631b09eb
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Transforms/InlineArrayTransform.cs
@@ -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;
+ }
+
+ ///
+ /// Matches call get_Item(addressof System.(ReadOnly)Span[[T]](call InlineArrayAs(ReadOnly)Span(addr, length)), index)
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Matches call InlineArrayElementRef(ReadOnly)(addr, index)
+ ///
+ 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: "", 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;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/Semantics/Conversion.cs b/ICSharpCode.Decompiler/Semantics/Conversion.cs
index a92735b4f..4bcc1d766 100644
--- a/ICSharpCode.Decompiler/Semantics/Conversion.cs
+++ b/ICSharpCode.Decompiler/Semantics/Conversion.cs
@@ -87,6 +87,11 @@ namespace ICSharpCode.Decompiler.Semantics
///
public static readonly Conversion ThrowExpressionConversion = new BuiltinConversion(true, 11);
+ ///
+ /// C# 12 inline array implicitly being converted to or .
+ ///
+ 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)