File: src\runtime\src\coreclr\tools\Common\Internal\Runtime\RiscVLoongArch64FpStruct.cs
Web Access
Project: src\runtime\src\coreclr\tools\aot\ILCompiler.ReadyToRun\ILCompiler.ReadyToRun.csproj (ILCompiler.ReadyToRun)
// 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.Collections.Generic;
using System.Diagnostics;
using ILCompiler;
using Internal.TypeSystem;
using static Internal.JitInterface.FpStruct;

namespace Internal.JitInterface
{
    // Bitfields for FpStructInRegistersInfo.flags
    [Flags]
    public enum FpStruct
    {
        // Positions of flags and bitfields
        PosOnlyOne      = 0,
        PosBothFloat    = 1,
        PosFloatInt     = 2,
        PosIntFloat     = 3,
        PosSizeShift1st = 4, // 2 bits
        PosSizeShift2nd = 6, // 2 bits

        UseIntCallConv = 0, // struct is passed according to integer calling convention

        // The flags and bitfields
        OnlyOne          =    1 << PosOnlyOne,      // has only one field, which is floating-point
        BothFloat        =    1 << PosBothFloat,    // has two fields, both are floating-point
        FloatInt         =    1 << PosFloatInt,     // has two fields, 1st is floating and 2nd is integer
        IntFloat         =    1 << PosIntFloat,     // has two fields, 2nd is floating and 1st is integer
        SizeShift1stMask = 0b11 << PosSizeShift1st, // log2(size) of 1st field
        SizeShift2ndMask = 0b11 << PosSizeShift2nd, // log2(size) of 2nd field
        // Note: flags OnlyOne, BothFloat, FloatInt, and IntFloat are mutually exclusive
    }

    // On RISC-V and LoongArch a struct with up to two non-empty fields, at least one of them floating-point,
    // can be passed in registers according to hardware FP calling convention. FpStructInRegistersInfo represents
    // passing information for such parameters.
    public struct FpStructInRegistersInfo
    {
        public FpStruct flags;
        public uint offset1st;
        public uint offset2nd;

        public uint SizeShift1st() { return (uint)((int)flags >> (int)FpStruct.PosSizeShift1st) & 0b11; }
        public uint SizeShift2nd() { return (uint)((int)flags >> (int)FpStruct.PosSizeShift2nd) & 0b11; }

        public uint Size1st() { return 1u << (int)SizeShift1st(); }
        public uint Size2nd() { return 1u << (int)SizeShift2nd(); }
    }

    internal static class RiscVLoongArch64FpStruct
    {
        private const int
            ENREGISTERED_PARAMTYPE_MAXSIZE = 16,
            TARGET_POINTER_SIZE = 8;

        private static void SetFpStructInRegistersInfoField(ref FpStructInRegistersInfo info, int index,
            bool isFloating, uint size, uint offset)
        {
            Debug.Assert(index < 2);
            if (isFloating)
                Debug.Assert(size == sizeof(float) || size == sizeof(double));

            Debug.Assert(size >= 1 && size <= 8);
            Debug.Assert((size & (size - 1)) == 0, "size needs to be a power of 2");
            const int sizeShiftLUT = (0 << (1*2)) | (1 << (2*2)) | (2 << (4*2)) | (3 << (8*2));
            int sizeShift = (sizeShiftLUT >> ((int)size * 2)) & 0b11;

            // Use FloatInt and IntFloat as marker flags for 1st and 2nd field respectively being floating.
            // Fix to real flags (with OnlyOne and BothFloat) after flattening is complete.
            Debug.Assert((int)PosIntFloat == (int)PosFloatInt + 1, "FloatInt and IntFloat need to be adjacent");
            Debug.Assert((int)PosSizeShift2nd == (int)PosSizeShift1st + 2, "SizeShift1st and 2nd need to be adjacent");
            int floatFlag = Convert.ToInt32(isFloating) << ((int)PosFloatInt + index);
            int sizeShiftMask = sizeShift << ((int)PosSizeShift1st + 2 * index);

            info.flags |= (FpStruct)(floatFlag | sizeShiftMask);
            (index == 0 ? ref info.offset1st : ref info.offset2nd) = offset;
        }

        private static bool HandleInlineArray(int elementTypeIndex, int nElements,
            ref FpStructInRegistersInfo info, ref int typeIndex, ref uint occupiedBytesMap)
        {
            int nFlattenedFieldsPerElement = typeIndex - elementTypeIndex;
            if (nFlattenedFieldsPerElement == 0)
            {
                Debug.Assert(nElements == 1, "HasImpliedRepeatedFields must have returned a false, it can't be an array");
                return true; // ignoring empty struct
            }

            Debug.Assert(nFlattenedFieldsPerElement == 1 || nFlattenedFieldsPerElement == 2);

            if (nElements > 2)
                return false; // array has too many elements

            if (nElements == 2)
            {
                if (typeIndex + nFlattenedFieldsPerElement > 2)
                    return false; // array has too many fields per element

                Debug.Assert(elementTypeIndex == 0);
                Debug.Assert(typeIndex == 1);

                // Duplicate the array element info
                Debug.Assert((int)FpStruct.IntFloat == ((int)FpStruct.FloatInt << 1),
                    "FloatInt and IntFloat need to be adjacent");
                Debug.Assert((int)FpStruct.SizeShift2ndMask == ((int)FpStruct.SizeShift1stMask << 2),
                    "SizeShift1st and 2nd need to be adjacent");
                // Take the 1st field info and shift up to the 2nd field's positions
                int floatFlag = (int)(info.flags & FpStruct.FloatInt) << 1;
                int sizeShiftMask = (int)(info.flags & FpStruct.SizeShift1stMask) << 2;
                info.flags |= (FpStruct)(floatFlag | sizeShiftMask); // merge with 1st field
                info.offset2nd = info.offset1st + info.Size1st(); // bump up the field offset

                Debug.Assert(info.Size1st() == info.Size2nd());
                uint startOffset = info.offset2nd;
                uint endOffset = startOffset + info.Size2nd();

                uint fieldOccupation = (~0u << (int)startOffset) ^ (~0u << (int)endOffset);
                if ((occupiedBytesMap & fieldOccupation) != 0)
                    return false; // duplicated array element overlaps with other fields

                occupiedBytesMap |= fieldOccupation;
            }
            return true;
        }

        private static bool FlattenFields(TypeDesc td, uint offset, ref FpStructInRegistersInfo info, ref int typeIndex)
        {
            IEnumerable<FieldDesc> fields = td.GetFields();
            int nFields = 0;
            int elementTypeIndex = typeIndex;
            FieldDesc lastField = null;
            uint occupiedBytesMap = 0;
            foreach (FieldDesc field in fields)
            {
                if (field.IsStatic)
                    continue;
                nFields++;

                uint startOffset = offset + (uint)field.Offset.AsInt;
                uint endOffset = startOffset + (uint)field.FieldType.GetElementSize().AsInt;

                uint fieldOccupation = (~0u << (int)startOffset) ^ (~0u << (int)endOffset);
                if ((occupiedBytesMap & fieldOccupation) != 0)
                    return false; // fields overlap, treat as union

                occupiedBytesMap |= fieldOccupation;

                lastField = field;

                TypeFlags category = field.FieldType.Category;
                if (category == TypeFlags.ValueType)
                {
                    TypeDesc nested = field.FieldType;
                    if (!FlattenFields(nested, startOffset, ref info, ref typeIndex))
                        return false;
                }
                else if (field.FieldType.GetElementSize().AsInt <= TARGET_POINTER_SIZE)
                {
                    if (typeIndex >= 2)
                        return false; // too many fields

                    bool isFloating = category is TypeFlags.Single or TypeFlags.Double;
                    SetFpStructInRegistersInfoField(ref info, typeIndex++,
                        isFloating, (uint)field.FieldType.GetElementSize().AsInt, startOffset);
                }
                else
                {
                    return false; // field is too big
                }
            }

            if ((td as MetadataType).HasImpliedRepeatedFields())
            {
                Debug.Assert(nFields == 1);
                int nElements = td.GetElementSize().AsInt / lastField.FieldType.GetElementSize().AsInt;

                // Only InlineArrays can have element type of empty struct, fixed-size buffers take only primitives
                if ((typeIndex - elementTypeIndex) == 0 && (td as MetadataType).IsInlineArray)
                {
                    Debug.Assert(nElements > 0, "InlineArray length must be > 0");
                    return false; // struct containing an array of empty structs is passed by integer calling convention
                }

                if (!HandleInlineArray(elementTypeIndex, nElements, ref info, ref typeIndex, ref occupiedBytesMap))
                    return false;
            }
            return true;
        }

        private static bool IsAligned(uint val, uint alignment) => 0 == (val & (alignment - 1));

        public static FpStructInRegistersInfo GetFpStructInRegistersInfo(TypeDesc td, TargetArchitecture arch)
        {
            Debug.Assert(arch is TargetArchitecture.RiscV64 or TargetArchitecture.LoongArch64);

            if (td.GetElementSize().AsInt > ENREGISTERED_PARAMTYPE_MAXSIZE)
                return new FpStructInRegistersInfo{};

            FpStructInRegistersInfo info = new FpStructInRegistersInfo{};
            int nFields = 0;
            if (!FlattenFields(td, 0, ref info, ref nFields))
                return new FpStructInRegistersInfo{};

            if ((info.flags & (FloatInt | IntFloat)) == 0)
                return new FpStructInRegistersInfo{}; // struct has no floating fields

            Debug.Assert(nFields == 1 || nFields == 2);
            if (nFields == 2 && info.offset1st > info.offset2nd)
            {
                // swap fields to match memory order
                info.flags = (FpStruct)(
                    ((uint)(info.flags & FloatInt) << (PosIntFloat - PosFloatInt)) |
                    ((uint)(info.flags & IntFloat) >> (PosIntFloat - PosFloatInt)) |
                    ((uint)(info.flags & SizeShift1stMask) << (PosSizeShift2nd - PosSizeShift1st)) |
                    ((uint)(info.flags & SizeShift2ndMask) >> (PosSizeShift2nd - PosSizeShift1st))
                );
                (info.offset2nd, info.offset1st) = (info.offset1st, info.offset2nd);
            }
            Debug.Assert((info.flags & (OnlyOne | BothFloat)) == 0);
            Debug.Assert((info.flags & FloatInt) == 0 || info.Size1st() == sizeof(float) || info.Size1st() == sizeof(double));
            Debug.Assert((info.flags & IntFloat) == 0 || info.Size2nd() == sizeof(float) || info.Size2nd() == sizeof(double));

            if ((info.flags & (FloatInt | IntFloat)) == (FloatInt | IntFloat))
            {
                Debug.Assert(nFields == 2);
                info.flags ^= (FloatInt | IntFloat | BothFloat); // replace (FloatInt | IntFloat) with BothFloat
            }
            else if (nFields == 1)
            {
                Debug.Assert((info.flags & FloatInt) != 0);
                Debug.Assert((info.flags & (IntFloat | SizeShift2ndMask)) == 0);
                Debug.Assert(info.offset2nd == 0);
                info.flags ^= (FloatInt | OnlyOne); // replace FloatInt with OnlyOne
            }
            Debug.Assert(nFields == ((info.flags & OnlyOne) != 0 ? 1 : 2));
            FpStruct floatFlags = info.flags & (OnlyOne | BothFloat | FloatInt | IntFloat);
            Debug.Assert(floatFlags != 0);
            Debug.Assert(((uint)floatFlags & ((uint)floatFlags - 1)) == 0,
                "there can be only one of (OnlyOne | BothFloat | FloatInt | IntFloat)");
            if (nFields == 2)
            {
                uint end1st = info.offset1st + info.Size1st();
                uint end2nd = info.offset2nd + info.Size2nd();
                Debug.Assert(end1st <= info.offset2nd || end2nd <= info.offset1st, "fields must not overlap");
            }
            Debug.Assert(info.offset1st + info.Size1st() <= td.GetElementSize().AsInt);
            Debug.Assert(info.offset2nd + info.Size2nd() <= td.GetElementSize().AsInt);

            return info;
        }
    }
}