// 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 generic unwinder library.
// This corresponds to unwind-generic.h

module gcc.unwind.generic;

import gcc.config;

static if (!GNU_ARM_EABI_Unwinder):

import gcc.builtins;

// This is derived from the C++ ABI for IA-64.  Where we diverge
// for cross-architecture compatibility are noted with "@@@".

extern (C):

// Placed outside @nogc in order to not constrain what the callback does.
// ??? Does this really need to be extern(C) alias?

extern(C) alias _Unwind_Exception_Cleanup_Fn
    = void function(_Unwind_Reason_Code, _Unwind_Exception*);

extern(C) alias _Unwind_Stop_Fn
    = _Unwind_Reason_Code function(int, _Unwind_Action,
                                   _Unwind_Exception_Class,
                                   _Unwind_Exception*,
                                   _Unwind_Context*, void*);

extern(C) alias _Unwind_Trace_Fn
    = _Unwind_Reason_Code function(_Unwind_Context*, void*);

// The personality routine is the function in the C++ (or other language)
// runtime library which serves as an interface between the system unwind
// library and language-specific exception handling semantics.  It is
// specific to the code fragment described by an unwind info block, and
// it is always referenced via the pointer in the unwind info block, and
// hence it has no ABI-specified name.

// Note that this implies that two different C++ implementations can
// use different names, and have different contents in the language
// specific data area.  Moreover, that the language specific data
// area contains no version info because name of the function invoked
// provides more effective versioning by detecting at link time the
// lack of code to handle the different data format.

extern(C) alias _Unwind_Personality_Fn
    = _Unwind_Reason_Code function(int, _Unwind_Action,
                                   _Unwind_Exception_Class,
                                   _Unwind_Exception*,
                                   _Unwind_Context*);

@nogc:

// Level 1: Base ABI

// @@@ The IA-64 ABI uses uint64 throughout.
// Most places this is inefficient for 32-bit and smaller machines.
alias _Unwind_Word = __builtin_unwind_uint;
alias _Unwind_Sword = __builtin_unwind_int;
version (IA64)
{
    version (HPUX)
        alias _Unwind_Ptr = __builtin_machine_uint;
    else
        alias _Unwind_Ptr = __builtin_pointer_uint;
}
else
{
    alias _Unwind_Ptr = __builtin_pointer_uint;
}
alias _Unwind_Internal_Ptr = __builtin_pointer_uint;

// @@@ The IA-64 ABI uses a 64-bit word to identify the producer and
// consumer of an exception.  We'll go along with this for now even on
// 32-bit machines.  We'll need to provide some other option for
// 16-bit machines and for machines with > 8 bits per byte.
alias _Unwind_Exception_Class = ulong;

// The unwind interface uses reason codes in several contexts to
// identify the reasons for failures or other actions.
alias _Unwind_Reason_Code = uint;
enum : _Unwind_Reason_Code
{
    _URC_NO_REASON = 0,
    _URC_FOREIGN_EXCEPTION_CAUGHT = 1,
    _URC_FATAL_PHASE2_ERROR = 2,
    _URC_FATAL_PHASE1_ERROR = 3,
    _URC_NORMAL_STOP = 4,
    _URC_END_OF_STACK = 5,
    _URC_HANDLER_FOUND = 6,
    _URC_INSTALL_CONTEXT = 7,
    _URC_CONTINUE_UNWIND = 8
}

// The unwind interface uses a pointer to an exception header object
// as its representation of an exception being thrown. In general, the
// full representation of an exception object is language- and
// implementation-specific, but it will be prefixed by a header
// understood by the unwind interface.

// @@@ The IA-64 ABI says that this structure must be double-word aligned.
// Taking that literally does not make much sense generically.  Instead we
// provide the maximum alignment required by any type for the machine.
     version (ARM)      private enum __aligned__ = 8;
else version (AArch64)  private enum __aligned__ = 16;
else version (HPPA)     private enum __aligned__ = 8;
else version (HPPA64)   private enum __aligned__ = 16;
else version (MIPS_N32) private enum __aligned__ = 16;
else version (MIPS_N64) private enum __aligned__ = 16;
else version (MIPS32)   private enum __aligned__ = 8;
else version (MIPS64)   private enum __aligned__ = 8;
else version (PPC)      private enum __aligned__ = 16;
else version (PPC64)    private enum __aligned__ = 16;
else version (RISCV32)  private enum __aligned__ = 16;
else version (RISCV64)  private enum __aligned__ = 16;
else version (S390)     private enum __aligned__ = 8;
else version (SPARC)    private enum __aligned__ = 8;
else version (SPARC64)  private enum __aligned__ = 16;
else version (SystemZ)  private enum __aligned__ = 8;
else version (X86)      private enum __aligned__ = 16;
else version (X86_64)   private enum __aligned__ = 16;
else version (LoongArch64) private enum __aligned__ = 16;
else static assert( false, "Platform not supported.");

align(__aligned__) struct _Unwind_Exception
{
    _Unwind_Exception_Class exception_class;
    _Unwind_Exception_Cleanup_Fn exception_cleanup;
    _Unwind_Word private_1;
    _Unwind_Word private_2;
}

// The ACTIONS argument to the personality routine is a bitwise OR of one
// or more of the following constants.
alias _Unwind_Action = int;
enum : _Unwind_Action
{
    _UA_SEARCH_PHASE = 1,
    _UA_CLEANUP_PHASE = 2,
    _UA_HANDLER_FRAME = 4,
    _UA_FORCE_UNWIND = 8,
    _UA_END_OF_STACK = 16
}

// This is an opaque type used to refer to a system-specific data
// structure used by the system unwinder. This context is created and
// destroyed by the system, and passed to the personality routine
// during unwinding.
struct _Unwind_Context;

// Raise an exception, passing along the given exception object.
_Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception*);

// Raise an exception for forced unwinding.
_Unwind_Reason_Code _Unwind_ForcedUnwind(_Unwind_Exception*, _Unwind_Stop_Fn, void*);

// Helper to invoke the exception_cleanup routine.
void _Unwind_DeleteException(_Unwind_Exception*);

// Resume propagation of an existing exception.  This is used after
// e.g. executing cleanup code, and not to implement rethrowing.
void _Unwind_Resume(_Unwind_Exception*);

// @@@ Resume propagation of an FORCE_UNWIND exception, or to rethrow
// a normal exception that was handled.
_Unwind_Reason_Code _Unwind_Resume_or_Rethrow(_Unwind_Exception*);

// @@@ Use unwind data to perform a stack backtrace.  The trace callback
// is called for every stack frame in the call chain, but no cleanup
// actions are performed.
_Unwind_Reason_Code _Unwind_Backtrace(_Unwind_Trace_Fn, void*);

// These functions are used for communicating information about the unwind
// context (i.e. the unwind descriptors and the user register state) between
// the unwind library and the personality routine and landing pad.  Only
// selected registers may be manipulated.

_Unwind_Word _Unwind_GetGR(_Unwind_Context*, int);
void _Unwind_SetGR(_Unwind_Context*, int, _Unwind_Word);

_Unwind_Ptr _Unwind_GetIP(_Unwind_Context*);
_Unwind_Ptr _Unwind_GetIPInfo(_Unwind_Context*, int*);
void _Unwind_SetIP(_Unwind_Context*, _Unwind_Ptr);

// @@@ Retrieve the CFA of the given context.
_Unwind_Word _Unwind_GetCFA(_Unwind_Context*);

void* _Unwind_GetLanguageSpecificData(_Unwind_Context*);

_Unwind_Ptr _Unwind_GetRegionStart(_Unwind_Context*);


// @@@ The following alternate entry points are for setjmp/longjmp
// based unwinding.

struct SjLj_Function_Context;
extern void _Unwind_SjLj_Register(SjLj_Function_Context*);
extern void _Unwind_SjLj_Unregister(SjLj_Function_Context*);

_Unwind_Reason_Code _Unwind_SjLj_RaiseException(_Unwind_Exception*);
_Unwind_Reason_Code _Unwind_SjLj_ForcedUnwind(_Unwind_Exception*, _Unwind_Stop_Fn, void*);
void _Unwind_SjLj_Resume(_Unwind_Exception*);
_Unwind_Reason_Code _Unwind_SjLj_Resume_or_Rethrow(_Unwind_Exception*);

// @@@ The following provide access to the base addresses for text
// and data-relative addressing in the LDSA.  In order to stay link
// compatible with the standard ABI for IA-64, we inline these.

version (IA64)
{
    _Unwind_Ptr _Unwind_GetDataRelBase(_Unwind_Context* _C)
    {
        // The GP is stored in R1.
        return _Unwind_GetGR(_C, 1);
    }

    _Unwind_Ptr _Unwind_GetTextRelBase(_Unwind_Context*)
    {
        __builtin_abort();
        return 0;
    }

    // @@@ Retrieve the Backing Store Pointer of the given context.
    _Unwind_Word _Unwind_GetBSP(_Unwind_Context*);
}
else
{
    _Unwind_Ptr _Unwind_GetDataRelBase(_Unwind_Context*);
    _Unwind_Ptr _Unwind_GetTextRelBase(_Unwind_Context*);
}

// @@@ Given an address, return the entry point of the function that
// contains it.
extern void* _Unwind_FindEnclosingFunction(void* pc);


// leb128 type numbers have a potentially unlimited size.
// The target of the following definitions of _sleb128_t and _uleb128_t
// is to have efficient data types large enough to hold the leb128 type
// numbers used in the unwind code.
// Mostly these types will simply be defined to long and unsigned long
// except when a unsigned long data type on the target machine is not
// capable of storing a pointer.

static if (__builtin_clong.sizeof >= (void*).sizeof)
{
    alias _sleb128_t = __builtin_clong;
    alias _uleb128_t = __builtin_culong;
}
else static if (long.sizeof >= (void*).sizeof)
{
    alias _sleb128_t = long;
    alias _uleb128_t = ulong;
}
else
{
    static assert(false, "What type shall we use for _sleb128_t?");
}

version (GNU_SEH_Exceptions)
{
    // We're lazy, exact definition in MinGW/winnt.h
    enum EXCEPTION_DISPOSITION
    {
        ExceptionContinueExecution,
        ExceptionContinueSearch,
        ExceptionNestedException,
        ExceptionCollidedUnwind
    }

    extern(C) EXCEPTION_DISPOSITION _GCC_specific_handler(void*, void*, void*, void*,
                                                          _Unwind_Personality_Fn);
}
