#ifndef liblldb_FuncUnwinders_h
#define liblldb_FuncUnwinders_h

#include <mutex>
#include <vector>

#include "lldb/Core/AddressRange.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/AddressRange.h"

namespace lldb_private {

class UnwindTable;

class FuncUnwinders 
{
public:
    // FuncUnwinders objects are used to track UnwindPlans for a function
    // (named or not - really just an address range)

    // We'll record four different UnwindPlans for each address range:
    //   
    //   1. Unwinding from a call site (a valid exception throw location)
    //      This is often sourced from the eh_frame exception handling info
    //   2. Unwinding from a non-call site (any location in the function)
    //      This is often done by analyzing the function prologue assembly
    //      language instructions
    //   3. A fast unwind method for this function which only retrieves a 
    //      limited set of registers necessary to walk the stack
    //   4. An architectural default unwind plan when none of the above are
    //      available for some reason.

    // Additionally, FuncUnwinds object can be asked where the prologue 
    // instructions are finished for migrating breakpoints past the 
    // stack frame setup instructions when we don't have line table information.

    FuncUnwinders (lldb_private::UnwindTable& unwind_table, AddressRange range);

    ~FuncUnwinders ();

    // current_offset is the byte offset into the function.
    // 0 means no instructions have executed yet.  -1 means the offset is unknown.
    // On architectures where the pc points to the next instruction that will execute, this
    // offset value will have already been decremented by 1 to stay within the bounds of the 
    // correct function body.
    lldb::UnwindPlanSP
    GetUnwindPlanAtCallSite (Target &target, int current_offset);

    lldb::UnwindPlanSP
    GetUnwindPlanAtNonCallSite (Target& target, lldb_private::Thread& thread, int current_offset);

    lldb::UnwindPlanSP
    GetUnwindPlanFastUnwind (Target& target, lldb_private::Thread& thread);

    lldb::UnwindPlanSP
    GetUnwindPlanArchitectureDefault (lldb_private::Thread& thread);

    lldb::UnwindPlanSP
    GetUnwindPlanArchitectureDefaultAtFunctionEntry (lldb_private::Thread& thread);

    Address&
    GetFirstNonPrologueInsn (Target& target);

    const Address&
    GetFunctionStartAddress () const;

    bool
    ContainsAddress (const Address& addr) const
    { 
        return m_range.ContainsFileAddress (addr);
    }

    // A function may have a Language Specific Data Area specified -- a block of data in
    // the object file which is used in the processing of an exception throw / catch.
    // If any of the UnwindPlans have the address of the LSDA region for this function,
    // this will return it.  
    Address
    GetLSDAAddress (Target &target);

    // A function may have a Personality Routine associated with it -- used in the
    // processing of throwing an exception.  If any of the UnwindPlans have the
    // address of the personality routine, this will return it.  Read the target-pointer
    // at this address to get the personality function address.
    Address
    GetPersonalityRoutinePtrAddress (Target &target);



    // The following methods to retrieve specific unwind plans should rarely be used.
    // Instead, clients should ask for the *behavior* they are looking for, using one
    // of the above UnwindPlan retrieval methods.

    lldb::UnwindPlanSP
    GetAssemblyUnwindPlan (Target &target, Thread &thread, int current_offset);

    lldb::UnwindPlanSP
    GetEHFrameUnwindPlan (Target &target, int current_offset);

    lldb::UnwindPlanSP
    GetEHFrameAugmentedUnwindPlan (Target &target, Thread &thread, int current_offset);

    lldb::UnwindPlanSP
    GetCompactUnwindUnwindPlan (Target &target, int current_offset);

    lldb::UnwindPlanSP
    GetArmUnwindUnwindPlan (Target &target, int current_offset);

    lldb::UnwindPlanSP
    GetArchDefaultUnwindPlan (Thread &thread);

    lldb::UnwindPlanSP
    GetArchDefaultAtFuncEntryUnwindPlan (Thread &thread);

private:

    lldb::UnwindAssemblySP
    GetUnwindAssemblyProfiler (Target& target);

    // Do a simplistic comparison for the register restore rule for getting 
    // the caller's pc value on two UnwindPlans -- returns LazyBoolYes if
    // they have the same unwind rule for the pc, LazyBoolNo if they do not
    // have the same unwind rule for the pc, and LazyBoolCalculate if it was
    // unable to determine this for some reason.
    lldb_private::LazyBool 
    CompareUnwindPlansForIdenticalInitialPCLocation (Thread& thread, const lldb::UnwindPlanSP &a, const lldb::UnwindPlanSP &b);

    UnwindTable& m_unwind_table;
    AddressRange m_range;

    std::recursive_mutex m_mutex;

    lldb::UnwindPlanSP              m_unwind_plan_assembly_sp;
    lldb::UnwindPlanSP              m_unwind_plan_eh_frame_sp;
    lldb::UnwindPlanSP              m_unwind_plan_eh_frame_augmented_sp;   // augmented by assembly inspection so it's valid everywhere
    std::vector<lldb::UnwindPlanSP> m_unwind_plan_compact_unwind;
    lldb::UnwindPlanSP              m_unwind_plan_arm_unwind_sp;
    lldb::UnwindPlanSP              m_unwind_plan_fast_sp;
    lldb::UnwindPlanSP              m_unwind_plan_arch_default_sp;
    lldb::UnwindPlanSP              m_unwind_plan_arch_default_at_func_entry_sp;

    // Fetching the UnwindPlans can be expensive - if we've already attempted
    // to get one & failed, don't try again.
    bool m_tried_unwind_plan_assembly:1,
         m_tried_unwind_plan_eh_frame:1,
         m_tried_unwind_plan_eh_frame_augmented:1,
         m_tried_unwind_plan_compact_unwind:1,
         m_tried_unwind_plan_arm_unwind:1,
         m_tried_unwind_fast:1,
         m_tried_unwind_arch_default:1,
         m_tried_unwind_arch_default_at_func_entry:1;

    Address m_first_non_prologue_insn;

    DISALLOW_COPY_AND_ASSIGN (FuncUnwinders);

}; // class FuncUnwinders

} // namespace lldb_private


#endif //liblldb_FuncUnwinders_h
