File: IL\RvaIntrinsicPatternAnalyzer.cs
Web Access
Project: src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

using Internal.IL;
using Internal.TypeSystem;

namespace ILCompiler
{
    /// <summary>
    /// State machine for RVA field tokens consumed by InitializeArray or CreateSpan.
    /// </summary>
    internal struct RvaIntrinsicPatternAnalyzer
    {
        private const int MaxInitializeArrayArguments = 64;

        [InlineArray(MaxInitializeArrayArguments)]
        private struct ConstantBuffer
        {
            private int _element0;
        }

        private enum State : byte
        {
            Constants = 1,
            ArrayAllocation,
            ArrayDup,
            LdToken,
            InitializeArrayLdToken,
            IntrinsicCall,
        }

        private State _state;
        private ConstantBuffer _recentConstants;
        private int _recentConstantCount;
        private int _nextConstantIndex;
        private int _fieldToken;
        private int _initializationSize;

        public readonly bool IsRvaIntrinsicCall => _state is State.IntrinsicCall;

        public void Advance(ILOpcode opcode, in ILReader reader, MethodIL methodIL)
        {
            switch (_state)
            {
                case 0:
                case State.Constants:
                    if (TryReadInt32Constant(opcode, reader, out int value))
                    {
                        AddConstant(value);
                        _state = State.Constants;
                    }
                    else if (TryGetInitializeArraySize(opcode, reader, methodIL, out _initializationSize))
                    {
                        ResetConstants();
                        _state = State.ArrayAllocation;
                    }
                    else if (opcode == ILOpcode.ldtoken)
                    {
                        ResetConstants();
                        _fieldToken = reader.PeekILToken();
                        _state = State.LdToken;
                    }
                    else
                    {
                        Reset();
                    }
                    return;

                case State.ArrayAllocation:
                    if (opcode == ILOpcode.dup)
                    {
                        _state = State.ArrayDup;
                        return;
                    }
                    break;

                case State.ArrayDup:
                    if (opcode == ILOpcode.ldtoken)
                    {
                        _fieldToken = reader.PeekILToken();
                        _state = State.InitializeArrayLdToken;
                        return;
                    }
                    break;

                case State.LdToken:
                    if (TryMatchCreateSpan(opcode, reader, methodIL))
                    {
                        _state = State.IntrinsicCall;
                        return;
                    }
                    break;

                case State.InitializeArrayLdToken:
                    if (TryMatchInitializeArray(opcode, reader, methodIL)
                        || TryMatchCreateSpan(opcode, reader, methodIL))
                    {
                        _state = State.IntrinsicCall;
                        return;
                    }
                    break;

                case State.IntrinsicCall:
                    break;

                default:
                    throw new UnreachableException();
            }

            Reset();
            Advance(opcode, reader, methodIL);
        }

        private void AddConstant(int value)
        {
            _recentConstants[_nextConstantIndex] = value;
            _nextConstantIndex = (_nextConstantIndex + 1) % MaxInitializeArrayArguments;
            _recentConstantCount = Math.Min(_recentConstantCount + 1, MaxInitializeArrayArguments);
        }

        private void Reset()
        {
            _state = default;
            _fieldToken = 0;
            _initializationSize = 0;
            ResetConstants();
        }

        private void ResetConstants()
        {
            _recentConstantCount = 0;
            _nextConstantIndex = 0;
        }

        private readonly int GetConstant(int index)
        {
            int firstConstantIndex = _recentConstantCount == MaxInitializeArrayArguments ? _nextConstantIndex : 0;
            return _recentConstants[(firstConstantIndex + index) % MaxInitializeArrayArguments];
        }

        private bool TryGetInitializeArraySize(ILOpcode opcode, in ILReader reader, MethodIL methodIL, out int initializationSize)
        {
            initializationSize = 0;

            TypeDesc elementType;
            int elementCount = 1;
            if (opcode == ILOpcode.newarr)
            {
                if (_recentConstantCount == 0)
                    return false;

                elementType = (TypeDesc)methodIL.GetObject(reader.PeekILToken());
                elementCount = GetConstant(_recentConstantCount - 1);
            }
            else if (opcode == ILOpcode.newobj)
            {
                MethodDesc constructor = (MethodDesc)methodIL.GetObject(reader.PeekILToken());
                if (!constructor.IsConstructor || constructor.OwningType is not ArrayType arrayType)
                    return false;

                elementType = arrayType.ElementType;
                int rank = arrayType.Rank;
                int argumentCount = constructor.Signature.Length;
                if (argumentCount > _recentConstantCount)
                    return false;

                bool hasLowerBounds;
                if (argumentCount == rank)
                {
                    hasLowerBounds = false;
                }
                else if (argumentCount == 2 * rank)
                {
                    hasLowerBounds = true;
                }
                else
                {
                    return false;
                }

                int firstArgumentIndex = _recentConstantCount - argumentCount;
                for (int dimension = 0; dimension < rank; dimension++)
                {
                    int lowerBoundOffset = hasLowerBounds ? 1 : 0;
                    int lengthIndex = firstArgumentIndex + (dimension * (lowerBoundOffset + 1)) + lowerBoundOffset;
                    if (!TryMultiplyPositive(elementCount, GetConstant(lengthIndex), out elementCount))
                        return false;
                }
            }
            else
            {
                return false;
            }

            if (elementType.IsRuntimeDeterminedSubtype
                || !TryGetInitializeArrayElementSize(elementType, out int elementSize))
            {
                return false;
            }

            return TryMultiplyPositive(elementCount, elementSize, out initializationSize);
        }

        private bool TryMatchInitializeArray(ILOpcode opcode, in ILReader reader, MethodIL methodIL)
        {
            if (!TryGetCallTarget(opcode, reader, methodIL, out MethodDesc method)
                || !IsRuntimeHelpersInitializeArray(method))
            {
                return false;
            }

            return methodIL.GetObject(_fieldToken) is FieldDesc field
                && !field.OwningType.IsRuntimeDeterminedSubtype
                && TryGetRvaFieldSize(field, out int fieldSize)
                && _initializationSize <= fieldSize;
        }

        private bool TryMatchCreateSpan(ILOpcode opcode, in ILReader reader, MethodIL methodIL)
        {
            if (!TryGetCallTarget(opcode, reader, methodIL, out MethodDesc method)
                || !IsRuntimeHelpersCreateSpan(method, out TypeDesc elementType))
            {
                return false;
            }

            if (methodIL.GetObject(_fieldToken) is not FieldDesc field
                || field.OwningType.IsRuntimeDeterminedSubtype
                || !TryGetRvaFieldSize(field, out int fieldSize))
            {
                return false;
            }

            LayoutInt elementSize = elementType.GetElementSize();
            return !elementSize.IsIndeterminate
                && elementSize.AsInt > 0
                && fieldSize / elementSize.AsInt > 0;
        }

        private static bool TryGetCallTarget(ILOpcode opcode, in ILReader reader, MethodIL methodIL, out MethodDesc method)
        {
            if (opcode == ILOpcode.call
                && methodIL.GetObject(reader.PeekILToken()) is MethodDesc targetMethod)
            {
                method = targetMethod;
                return true;
            }

            method = null;
            return false;
        }

        private static bool TryReadInt32Constant(ILOpcode opcode, in ILReader reader, out int value)
        {
            ILReader nestedReader = reader;
            switch (opcode)
            {
                case ILOpcode.ldc_i4_m1:
                    value = -1;
                    return true;
                case >= ILOpcode.ldc_i4_0 and <= ILOpcode.ldc_i4_8:
                    value = opcode - ILOpcode.ldc_i4_0;
                    return true;
                case ILOpcode.ldc_i4_s:
                    value = (sbyte)nestedReader.ReadILByte();
                    return true;
                case ILOpcode.ldc_i4:
                    value = (int)nestedReader.ReadILUInt32();
                    return true;
                default:
                    value = 0;
                    return false;
            }
        }

        private static bool TryGetInitializeArrayElementSize(TypeDesc elementType, out int elementSize)
        {
            if (elementType.IsEnum)
                elementType = elementType.UnderlyingType;

            if ((!elementType.IsPrimitive || elementType.IsVoid)
                && !elementType.IsPointer
                && !elementType.IsFunctionPointer)
            {
                elementSize = 0;
                return false;
            }

            LayoutInt layoutSize = elementType.GetElementSize();
            elementSize = layoutSize.IsIndeterminate ? 0 : layoutSize.AsInt;
            return elementSize > 0;
        }

        private static bool TryGetRvaFieldSize(FieldDesc field, out int fieldSize)
        {
            if (!field.HasRva)
            {
                fieldSize = 0;
                return false;
            }

            LayoutInt layoutSize = field.FieldType.GetElementSize();
            fieldSize = layoutSize.IsIndeterminate ? 0 : layoutSize.AsInt;
            return fieldSize > 0;
        }

        private static bool TryMultiplyPositive(int left, int right, out int product)
        {
            if (left <= 0 || right <= 0 || left > int.MaxValue / right)
            {
                product = 0;
                return false;
            }

            product = left * right;
            return true;
        }

        private static bool IsRuntimeHelpersInitializeArray(MethodDesc method)
        {
            MethodSignature signature = method.Signature;
            return method.Name == "InitializeArray"u8
                && IsRuntimeHelpersIntrinsic(method)
                && method.Instantiation.Length == 0
                && signature.IsStatic
                && signature.Length == 2
                && signature.ReturnType.IsVoid
                && signature[0].IsWellKnownType(WellKnownType.Array)
                && signature[1].IsWellKnownType(WellKnownType.RuntimeFieldHandle);
        }

        private static bool IsRuntimeHelpersCreateSpan(MethodDesc method, out TypeDesc elementType)
        {
            MethodSignature signature = method.Signature;
            if (method.Name == "CreateSpan"u8
                && IsRuntimeHelpersIntrinsic(method)
                && method.Instantiation.Length == 1
                && signature.IsStatic
                && signature.Length == 1
                && signature[0].IsWellKnownType(WellKnownType.RuntimeFieldHandle)
                && (method.Instantiation[0].IsPrimitive || method.Instantiation[0].IsEnum))
            {
                elementType = method.Instantiation[0];
                return true;
            }

            elementType = null;
            return false;
        }

        private static bool IsRuntimeHelpersIntrinsic(MethodDesc method)
        {
            return method.IsIntrinsic
                && method.OwningType is MetadataType owningType
                && owningType.Name == "RuntimeHelpers"u8
                && owningType.Namespace == "System.Runtime.CompilerServices"u8
                && owningType.Module == owningType.Context.SystemModule;
        }
    }
}