File: Compiler\ObjectWriter\Eabi\EabiUnwindConverter.cs
Web Access
Project: src\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.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using System.Text;

using static ILCompiler.ObjectWriter.EabiNative;

namespace ILCompiler.ObjectWriter
{
    internal static class EabiUnwindConverter
    {
        /// <summary>
        /// Convert from the DWARF CFI opcodes produced by JIT into the ARM EHABI
        /// opcodes for exception unwinding.
        /// </summary>
        /// <param name="blobData">DWARF CFI blob from JIT</param>
        /// <returns>
        /// ARM EHABI unwind code, as specified by Exception Handling ABI for the Arm
        /// Architecture, 2023Q3, section 10.3.
        /// </returns>
        public static byte[] ConvertCFIToEabi(byte[] blobData)
        {
            if (blobData == null || blobData.Length == 0)
            {
                return blobData;
            }

            Debug.Assert(blobData.Length % 8 == 0);

            // The maximum sequence length of the ARM EHABI unwinding code is 1024
            // bytes.
            byte[] unwindData = ArrayPool<byte>.Shared.Rent(1024);
            int unwindDataOffset = 0;

            // The DWARF CFI data produced by the JIT describe the method prolog that
            // saves registers, adjusts the stack, and optionally set ups the frame
            // register. In contrast, the ARM EHABI unwind code describes how the epilog
            // would do the unwinding. It lacks the code offsets so it cannot unwind
            // inside either the prolog, or the epilog. The runtime code detects these
            // cases when doing the asynchronous unwinding.
            //
            // In order to convert between the two formats we thus need to reverse
            // the order of operatations. The EHABI unwind codes closely mirror the
            // ARM instructions and they efficiently describe the POP/VPOP operation on
            // multiple registers. In order to get the most compact representation we
            // record the pending opertations at each code offset and only flush the
            // unwind code when necessary.

            // Adjustment to VSP made by VPOP instruction relative to the DWARF CFI
            // which uses an explicit CFI_ADJUST_CFA_OFFSET opcode.
            int popOffset = 0;

            // Mask of pending Rn registers popped at current code offset.
            uint pendingPopMask = 0;

            // Mask of pending Dn vector registers popped at current code offset.
            uint pendingVPopMask = 0;

            // Stack adjustment by add/sub sp, X instructions
            int pendingSpAdjustment = 0;

            int lastCodeOffset = blobData[blobData.Length - 8];

            // Walk the CFI data backwards
            for (int offset = blobData.Length - 8; offset >= 0; offset -= 8)
            {
                byte codeOffset = blobData[offset];
                CFI_OPCODE opcode = (CFI_OPCODE)blobData[offset + 1];
                short dwarfReg = BinaryPrimitives.ReadInt16LittleEndian(blobData.AsSpan(offset + 2));
                int cfiOffset = BinaryPrimitives.ReadInt32LittleEndian(blobData.AsSpan(offset + 4));

                if (lastCodeOffset != codeOffset)
                {
                    Debug.Assert(popOffset == 0);
                    FlushPendingOperation();
                    lastCodeOffset = codeOffset;
                }

                switch (opcode)
                {
                    case CFI_OPCODE.CFI_DEF_CFA_REGISTER:
                        Debug.Assert(dwarfReg != 13); // SP
                        Debug.Assert(dwarfReg < 15);

                        FlushPendingOperation();
                        // Set vsp = r[nnnn]
                        unwindData[unwindDataOffset++] = (byte)(0x90 | dwarfReg);
                        break;

                    case CFI_OPCODE.CFI_REL_OFFSET:
                        Debug.Assert(cfiOffset == popOffset);
                        if (dwarfReg >= 0 && dwarfReg <= 15)
                        {
                            EmitPop((uint)(1u << dwarfReg));
                            popOffset += 4;
                        }
                        else if (dwarfReg >= 256 && dwarfReg <= 287)
                        {
                            dwarfReg -= 256;
                            EmitVPop((uint)(1u << dwarfReg));
                            popOffset += 8;
                        }
                        else
                        {
                            Debug.Fail("Unknown register");
                        }
                        break;

                    case CFI_OPCODE.CFI_ADJUST_CFA_OFFSET:
                        cfiOffset -= popOffset;
                        popOffset = 0;
                        if (cfiOffset != 0)
                        {
                            EmitSpAdjustment(cfiOffset);
                        }
                        break;
                }
            }

            FlushPendingOperation();

            var result = unwindData[..unwindDataOffset];
            ArrayPool<byte>.Shared.Return(unwindData);
            return result;

            void EmitPop(uint popMask)
            {
                if (pendingPopMask == 0)
                    FlushPendingOperation();
                pendingPopMask |= popMask;
            }

            void EmitVPop(uint vpopMask)
            {
                if (pendingVPopMask == 0)
                    FlushPendingOperation();
                pendingVPopMask |= vpopMask;
            }

            void EmitSpAdjustment(int spAdjustment)
            {
                if (pendingSpAdjustment == 0)
                    FlushPendingOperation();
                pendingSpAdjustment += spAdjustment;
            }

            void FlushPendingOperation()
            {
                if (pendingSpAdjustment > 0)
                {
                    Debug.Assert((pendingSpAdjustment & 3) == 0);
                    if (pendingSpAdjustment <= 0x100)
                    {
                        // vsp = vsp + (xxxxxx << 2) + 4.
                        // 00xxxxxx
                        unwindData[unwindDataOffset++] = (byte)((pendingSpAdjustment >> 2) - 1);
                    }
                    else if (pendingSpAdjustment <= 0x200)
                    {
                        // vsp = vsp + (0x3f << 2) + 4.
                        // 00111111
                        unwindData[unwindDataOffset++] = (byte)0x3f;
                        pendingSpAdjustment -= 0x100;
                        // vsp = vsp + (xxxxxx << 2) + 4.
                        // 00xxxxxx
                        unwindData[unwindDataOffset++] = (byte)((pendingSpAdjustment >> 2) - 1);
                    }
                    else
                    {
                        // vsp = vsp + 0x204 + (uleb128 << 2)
                        // 10110010 uleb128
                        unwindData[unwindDataOffset++] = (byte)0xb2;
                        unwindDataOffset += DwarfHelper.WriteULEB128(unwindData.AsSpan(unwindDataOffset), (uint)((pendingSpAdjustment - 0x204) >> 2));
                    }

                    pendingSpAdjustment = 0;
                }
                else if (pendingSpAdjustment < 0)
                {
                    while (pendingSpAdjustment < -0x100)
                    {
                        // vsp = vsp - (0x3f << 2) - 4.
                        // 01111111
                        unwindData[unwindDataOffset++] = 0x7f;
                        pendingSpAdjustment += 0x100;
                    }
                    // vsp = vsp - (xxxxxx << 2) - 4.
                    // 01xxxxxx
                    unwindData[unwindDataOffset++] = (byte)(0x40 | ((-pendingSpAdjustment >> 2) - 1));

                    pendingSpAdjustment = 0;
                }
                else if (pendingPopMask != 0)
                {
                    // Try to use efficient encoding if we have a consecutive run of
                    // r4-rN registers for N <= 11, and either no high registers or r14.
                    if ((pendingPopMask & 0x10) == 0x10 &&
                        ((pendingPopMask & 0xF000) == 0 || (pendingPopMask & 0xF000) == 0x4000))
                    {
                        uint r5AndHigher = (pendingPopMask & 0xFF0) >> 5;
                        int bitRunLength = BitOperations.TrailingZeroCount(~r5AndHigher);
                        // No gaps...
                        if ((r5AndHigher & ((1 << bitRunLength) - 1)) == r5AndHigher)
                        {
                            if ((pendingPopMask & 0xF000) == 0)
                            {
                                // Pop r4-r[4+nnn]
                                // 10100nnn
                                unwindData[unwindDataOffset++] = (byte)(0xA0 | bitRunLength);
                            }
                            else
                            {
                                // Pop r4-r[4+nnn], r14
                                // 10101nnn
                                unwindData[unwindDataOffset++] = (byte)(0xA8 | bitRunLength);
                            }

                            pendingPopMask &= 0xF;
                        }
                    }

                    // Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}
                    // 1000iiii iiiiiiii
                    if ((pendingPopMask & 0xFFF0) != 0)
                    {
                        ushort ins = (ushort)(0x8000u | (pendingPopMask >> 4));
                        unwindData[unwindDataOffset++] = (byte)(ins >> 8);
                        unwindData[unwindDataOffset++] = (byte)(ins & 0xff);
                    }

                    // Pop integer registers under mask {r3, r2, r1, r0}
                    // 10110001 0000iiii
                    if ((pendingPopMask & 0xF) != 0)
                    {
                        ushort ins = (ushort)(0xB100u | (pendingPopMask & 0xf));
                        unwindData[unwindDataOffset++] = (byte)(ins >> 8);
                        unwindData[unwindDataOffset++] = (byte)(ins & 0xff);
                    }

                    pendingPopMask = 0;
                }
                else if (pendingVPopMask != 0)
                {
                    // Find consecutive bit runs

                    // Pop VFP double precision registers D[16+ssss]-D[16+ssss+cccc] saved (as if) by VPUSH
                    // 11001000 sssscccc
                    uint mask = pendingVPopMask >> 16;
                    while (mask > 0)
                    {
                        int leadingZeros = BitOperations.LeadingZeroCount(mask);
                        int bitRunLength = BitOperations.LeadingZeroCount(~(mask << leadingZeros));
                        leadingZeros -= 16; // Using uint but working only with low 16 bits
                        unwindData[unwindDataOffset++] = 0xc8;
                        unwindData[unwindDataOffset++] = (byte)(((16 - leadingZeros - bitRunLength) << 4) | (bitRunLength - 1));
                        mask &= (uint)(1u << (16 - leadingZeros - bitRunLength)) - 1u;
                    }

                    // Pop VFP double precision registers D[ssss]-D[ssss+cccc] saved (as if) by VPUSH
                    // 11001001 sssscccc
                    mask = pendingVPopMask & 0xffff;
                    while (mask > 0)
                    {
                        int leadingZeros = BitOperations.LeadingZeroCount(mask);
                        int bitRunLength = BitOperations.LeadingZeroCount(~(mask << leadingZeros));
                        leadingZeros -= 16; // Using uint but working only with low 16 bits
                        unwindData[unwindDataOffset++] = 0xc9;
                        unwindData[unwindDataOffset++] = (byte)(((16 - leadingZeros - bitRunLength) << 4) | (bitRunLength - 1));
                        mask &= (uint)(1u << (16 - leadingZeros - bitRunLength)) - 1u;
                    }

                    pendingVPopMask = 0;
                }
            }
        }
    }
}