// Exception handling and frame unwind runtime interface routines.
// Copyright (C) 2011-2024 Free Software Foundation, Inc.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.

// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.

// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
// <http://www.gnu.org/licenses/>.

// extern(C) interface for the GNU/GCC pointer encoding library.
// This corresponds to unwind-pe.h

module gcc.unwind.pe;

import gcc.unwind;
import gcc.builtins;

@nogc:

// Pointer encodings, from dwarf2.h.
enum
{
    DW_EH_PE_absptr   = 0x00,
    DW_EH_PE_omit     = 0xff,

    DW_EH_PE_uleb128  = 0x01,
    DW_EH_PE_udata2   = 0x02,
    DW_EH_PE_udata4   = 0x03,
    DW_EH_PE_udata8   = 0x04,
    DW_EH_PE_sleb128  = 0x09,
    DW_EH_PE_sdata2   = 0x0A,
    DW_EH_PE_sdata4   = 0x0B,
    DW_EH_PE_sdata8   = 0x0C,
    DW_EH_PE_signed   = 0x08,

    DW_EH_PE_pcrel    = 0x10,
    DW_EH_PE_textrel  = 0x20,
    DW_EH_PE_datarel  = 0x30,
    DW_EH_PE_funcrel  = 0x40,
    DW_EH_PE_aligned  = 0x50,

    DW_EH_PE_indirect = 0x80
}

// Given an encoding, return the number of bytes the format occupies.
// This is only defined for fixed-size encodings, and so does not
// include leb128.
uint size_of_encoded_value(ubyte encoding)
{
    if (encoding == DW_EH_PE_omit)
        return 0;

    final switch (encoding & 0x07)
    {
        case DW_EH_PE_absptr:
            return (void*).sizeof;
        case DW_EH_PE_udata2:
            return 2;
        case DW_EH_PE_udata4:
            return 4;
        case DW_EH_PE_udata8:
            return 8;
    }
    assert(0);
}

// Given an encoding and an _Unwind_Context, return the base to which
// the encoding is relative.  This base may then be passed to
// read_encoded_value_with_base for use when the _Unwind_Context is
// not available.
_Unwind_Ptr base_of_encoded_value(ubyte encoding, _Unwind_Context* context)
{
    if (encoding == DW_EH_PE_omit)
        return cast(_Unwind_Ptr) 0;

    final switch (encoding & 0x70)
    {
        case DW_EH_PE_absptr:
        case DW_EH_PE_pcrel:
        case DW_EH_PE_aligned:
            return cast(_Unwind_Ptr) 0;

        case DW_EH_PE_textrel:
            return _Unwind_GetTextRelBase(context);
        case DW_EH_PE_datarel:
            return _Unwind_GetDataRelBase(context);
        case DW_EH_PE_funcrel:
            return _Unwind_GetRegionStart(context);
    }
    assert(0);
}

// Read an unsigned leb128 value from P, P is incremented past the value.
// We assume that a word is large enough to hold any value so encoded;
// if it is smaller than a pointer on some target, pointers should not be
// leb128 encoded on that target.
_uleb128_t read_uleb128(ref const(ubyte)* p)
{
    _uleb128_t result = 0;
    uint shift = 0;

    while (1)
    {
        ubyte b = *p++;
        result |= cast(_uleb128_t)(b & 0x7F) << shift;
        if ((b & 0x80) == 0)
            break;
        shift += 7;
    }

    return result;
}

// Similar, but read a signed leb128 value.
_sleb128_t read_sleb128(ref const(ubyte)* p)
{
    _sleb128_t result = 0;
    uint shift = 0;
    ubyte b = void;

    while (1)
    {
        b = *p++;
        result |= cast(_sleb128_t)(b & 0x7F) << shift;
        shift += 7;
        if ((b & 0x80) == 0)
            break;
    }

    // Sign-extend a negative value.
    if (shift < result.sizeof * 8 && (b & 0x40))
        result |= -(cast(_sleb128_t)1 << shift);

    return result;
}

// Similar, but read an unaligned value of type T.
pragma(inline, true)
private T read_unaligned(T)(ref const(ubyte)* p)
{
    version (X86)         enum hasUnalignedLoads = true;
    else version (X86_64) enum hasUnalignedLoads = true;
    else                  enum hasUnalignedLoads = false;

    static if (hasUnalignedLoads)
    {
        T result = *cast(T*)p;
    }
    else
    {
        import core.stdc.string : memcpy;
        T result = void;
        memcpy(&result, p, T.sizeof);
    }
    p += T.sizeof;
    return result;
}

// Load an encoded value from memory at P.  The function returns the
// encoded value.  P is incremented past the value.  BASE is as given
// by base_of_encoded_value for this encoding in the appropriate context.
_Unwind_Ptr read_encoded_value_with_base(ubyte encoding, _Unwind_Ptr base,
                                         ref const(ubyte)* p)
{
    auto psave = p;
    _Unwind_Internal_Ptr result;

    if (encoding == DW_EH_PE_aligned)
    {
        _Unwind_Internal_Ptr a = cast(_Unwind_Internal_Ptr)p;
        a = cast(_Unwind_Internal_Ptr)((a + (void*).sizeof - 1) & - (void*).sizeof);
        result = *cast(_Unwind_Internal_Ptr*)a;
        p = cast(ubyte*) cast(_Unwind_Internal_Ptr)(a + (void*).sizeof);
    }
    else
    {
        switch (encoding & 0x0f)
        {
            case DW_EH_PE_uleb128:
                result = cast(_Unwind_Internal_Ptr)read_uleb128(p);
                break;

            case DW_EH_PE_sleb128:
                result = cast(_Unwind_Internal_Ptr)read_sleb128(p);
                break;

            case DW_EH_PE_udata2:
                result = cast(_Unwind_Internal_Ptr)read_unaligned!ushort(p);
                break;
            case DW_EH_PE_udata4:
                result = cast(_Unwind_Internal_Ptr)read_unaligned!uint(p);
                break;
            case DW_EH_PE_udata8:
                result = cast(_Unwind_Internal_Ptr)read_unaligned!ulong(p);
                break;

            case DW_EH_PE_sdata2:
                result = cast(_Unwind_Internal_Ptr)read_unaligned!short(p);
                break;
            case DW_EH_PE_sdata4:
                result = cast(_Unwind_Internal_Ptr)read_unaligned!int(p);
                break;
            case DW_EH_PE_sdata8:
                result = cast(_Unwind_Internal_Ptr)read_unaligned!long(p);
                break;

            case DW_EH_PE_absptr:
                result = cast(_Unwind_Internal_Ptr)read_unaligned!(size_t)(p);
                break;

            default:
                __builtin_abort();
        }

        if (result != 0)
        {
            result += ((encoding & 0x70) == DW_EH_PE_pcrel
                       ? cast(_Unwind_Internal_Ptr)psave : base);
            if (encoding & DW_EH_PE_indirect)
                result = *cast(_Unwind_Internal_Ptr*)result;
        }
    }

    return result;
}

// Like read_encoded_value_with_base, but get the base from the context
// rather than providing it directly.
_Unwind_Ptr read_encoded_value(_Unwind_Context* context, ubyte encoding,
                               ref const(ubyte)* p)
{
    auto base = base_of_encoded_value(encoding, context);
    return read_encoded_value_with_base(encoding, base, p);
}
