//===- AddressesMap.h -------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_DWARFLINKER_ADDRESSESMAP_H
#define LLVM_DWARFLINKER_ADDRESSESMAP_H

#include "llvm/ADT/AddressRanges.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/DebugInfo/DWARF/DWARFDie.h"
#include "llvm/DebugInfo/DWARF/DWARFUnit.h"
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h"
#include <cstdint>

namespace llvm {
namespace dwarf_linker {

/// Mapped value in the address map is the offset to apply to the
/// linked address.
using RangesTy = AddressRangesMap;

/// AddressesMap represents information about valid addresses used
/// by debug information. Valid addresses are those which points to
/// live code sections. i.e. relocations for these addresses point
/// into sections which would be/are placed into resulting binary.
class AddressesMap {
public:
  virtual ~AddressesMap() = default;

  /// Checks that there are valid relocations in the .debug_info
  /// section.
  virtual bool hasValidRelocs() = 0;

  /// Checks that the specified DWARF expression operand \p Op references live
  /// code section and returns the relocation adjustment value (to get the
  /// linked address this value might be added to the source expression operand
  /// address). Print debug output if \p Verbose is true.
  /// \returns relocation adjustment value or std::nullopt if there is no
  /// corresponding live address.
  virtual std::optional<int64_t> getExprOpAddressRelocAdjustment(
      DWARFUnit &U, const DWARFExpression::Operation &Op, uint64_t StartOffset,
      uint64_t EndOffset, bool Verbose) = 0;

  /// Checks that the specified subprogram \p DIE references the live code
  /// section and returns the relocation adjustment value (to get the linked
  /// address this value might be added to the source subprogram address).
  /// Allowed kinds of input DIE: DW_TAG_subprogram, DW_TAG_label.
  /// Print debug output if \p Verbose is true.
  /// \returns relocation adjustment value or std::nullopt if there is no
  /// corresponding live address.
  virtual std::optional<int64_t>
  getSubprogramRelocAdjustment(const DWARFDie &DIE, bool Verbose) = 0;

  // Returns the library install name associated to the AddessesMap.
  virtual std::optional<StringRef> getLibraryInstallName() = 0;

  /// Apply the valid relocations to the buffer \p Data, taking into
  /// account that Data is at \p BaseOffset in the .debug_info section.
  ///
  /// \returns true whether any reloc has been applied.
  virtual bool applyValidRelocs(MutableArrayRef<char> Data, uint64_t BaseOffset,
                                bool IsLittleEndian) = 0;

  /// Check if the linker needs to gather and save relocation info.
  virtual bool needToSaveValidRelocs() = 0;

  /// Update and save relocation values to be serialized
  virtual void updateAndSaveValidRelocs(bool IsDWARF5,
                                        uint64_t OriginalUnitOffset,
                                        int64_t LinkedOffset,
                                        uint64_t StartOffset,
                                        uint64_t EndOffset) = 0;

  /// Update the valid relocations that used OriginalUnitOffset as the compile
  /// unit offset, and update their values to reflect OutputUnitOffset.
  virtual void updateRelocationsWithUnitOffset(uint64_t OriginalUnitOffset,
                                               uint64_t OutputUnitOffset) = 0;

  /// Erases all data.
  virtual void clear() = 0;

  /// This function checks whether variable has DWARF expression containing
  /// operation referencing live address(f.e. DW_OP_addr, DW_OP_addrx...).
  /// \returns first is true if the expression has an operation referencing an
  /// address.
  ///          second is the relocation adjustment value if the live address is
  ///          referenced.
  std::pair<bool, std::optional<int64_t>>
  getVariableRelocAdjustment(const DWARFDie &DIE, bool Verbose) {
    assert((DIE.getTag() == dwarf::DW_TAG_variable ||
            DIE.getTag() == dwarf::DW_TAG_constant) &&
           "Wrong type of input die");

    const auto *Abbrev = DIE.getAbbreviationDeclarationPtr();

    // Check if DIE has DW_AT_location attribute.
    DWARFUnit *U = DIE.getDwarfUnit();
    std::optional<uint32_t> LocationIdx =
        Abbrev->findAttributeIndex(dwarf::DW_AT_location);
    if (!LocationIdx)
      return std::make_pair(false, std::nullopt);

    // Get offset to the DW_AT_location attribute.
    uint64_t AttrOffset =
        Abbrev->getAttributeOffsetFromIndex(*LocationIdx, DIE.getOffset(), *U);

    // Get value of the DW_AT_location attribute.
    std::optional<DWARFFormValue> LocationValue =
        Abbrev->getAttributeValueFromOffset(*LocationIdx, AttrOffset, *U);
    if (!LocationValue)
      return std::make_pair(false, std::nullopt);

    // Check that DW_AT_location attribute is of 'exprloc' class.
    // Handling value of location expressions for attributes of 'loclist'
    // class is not implemented yet.
    std::optional<ArrayRef<uint8_t>> Expr = LocationValue->getAsBlock();
    if (!Expr)
      return std::make_pair(false, std::nullopt);

    // Parse 'exprloc' expression.
    DataExtractor Data(toStringRef(*Expr), U->getContext().isLittleEndian(),
                       U->getAddressByteSize());
    DWARFExpression Expression(Data, U->getAddressByteSize(),
                               U->getFormParams().Format);

    bool HasLocationAddress = false;
    uint64_t CurExprOffset = 0;
    for (DWARFExpression::iterator It = Expression.begin();
         It != Expression.end(); ++It) {
      DWARFExpression::iterator NextIt = It;
      ++NextIt;

      const DWARFExpression::Operation &Op = *It;
      switch (Op.getCode()) {
      case dwarf::DW_OP_const2u:
      case dwarf::DW_OP_const4u:
      case dwarf::DW_OP_const8u:
      case dwarf::DW_OP_const2s:
      case dwarf::DW_OP_const4s:
      case dwarf::DW_OP_const8s:
        if (NextIt == Expression.end() || !isTlsAddressCode(NextIt->getCode()))
          break;
        [[fallthrough]];
      case dwarf::DW_OP_addr: {
        HasLocationAddress = true;
        // Check relocation for the address.
        if (std::optional<int64_t> RelocAdjustment =
                getExprOpAddressRelocAdjustment(
                    *U, Op, AttrOffset + CurExprOffset,
                    AttrOffset + Op.getEndOffset(), Verbose))
          return std::make_pair(HasLocationAddress, *RelocAdjustment);
      } break;
      case dwarf::DW_OP_constx:
      case dwarf::DW_OP_addrx: {
        HasLocationAddress = true;
        if (std::optional<uint64_t> AddressOffset =
                DIE.getDwarfUnit()->getIndexedAddressOffset(
                    Op.getRawOperand(0))) {
          // Check relocation for the address.
          if (std::optional<int64_t> RelocAdjustment =
                  getExprOpAddressRelocAdjustment(
                      *U, Op, *AddressOffset,
                      *AddressOffset + DIE.getDwarfUnit()->getAddressByteSize(),
                      Verbose))
            return std::make_pair(HasLocationAddress, *RelocAdjustment);
        }
      } break;
      default: {
        // Nothing to do.
      } break;
      }
      CurExprOffset = Op.getEndOffset();
    }

    return std::make_pair(HasLocationAddress, std::nullopt);
  }

protected:
  inline bool isTlsAddressCode(uint8_t DW_OP_Code) {
    return DW_OP_Code == dwarf::DW_OP_form_tls_address ||
           DW_OP_Code == dwarf::DW_OP_GNU_push_tls_address;
  }
};

} // namespace dwarf_linker
} // end namespace llvm

#endif // LLVM_DWARFLINKER_ADDRESSESMAP_H
