// 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; } } }