/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ART_LIBELFFILE_DWARF_DEBUG_LINE_OPCODE_WRITER_H_
#define ART_LIBELFFILE_DWARF_DEBUG_LINE_OPCODE_WRITER_H_

#include <cstdint>

#include "dwarf/dwarf_constants.h"
#include "dwarf/writer.h"

namespace art {
namespace dwarf {

// Writer for the .debug_line opcodes (DWARF-3).
// The writer is very light-weight, however it will do the following for you:
//  * Choose the most compact encoding of a given opcode.
//  * Keep track of current state and convert absolute values to deltas.
//  * Divide by header-defined factors as appropriate.
template<typename Vector = std::vector<uint8_t>>
class DebugLineOpCodeWriter final : private Writer<Vector> {
  static_assert(std::is_same<typename Vector::value_type, uint8_t>::value, "Invalid value type");

 public:
  static constexpr int kOpcodeBase = 13;
  static constexpr bool kDefaultIsStmt = false;
  static constexpr int kLineBase = -5;
  static constexpr int kLineRange = 14;

  void AddRow() {
    this->PushUint8(DW_LNS_copy);
  }

  void AdvancePC(uint64_t absolute_address) {
    DCHECK_NE(current_address_, 0u);  // Use SetAddress for the first advance.
    DCHECK_GE(absolute_address, current_address_);
    if (absolute_address != current_address_) {
      uint64_t delta = FactorCodeOffset(absolute_address - current_address_);
      if (delta <= INT32_MAX) {
        this->PushUint8(DW_LNS_advance_pc);
        this->PushUleb128(static_cast<int>(delta));
        current_address_ = absolute_address;
      } else {
        SetAddress(absolute_address);
      }
    }
  }

  void AdvanceLine(int absolute_line) {
    int delta = absolute_line - current_line_;
    if (delta != 0) {
      this->PushUint8(DW_LNS_advance_line);
      this->PushSleb128(delta);
      current_line_ = absolute_line;
    }
  }

  void SetFile(int file) {
    if (current_file_ != file) {
      this->PushUint8(DW_LNS_set_file);
      this->PushUleb128(file);
      current_file_ = file;
    }
  }

  void SetColumn(int column) {
    this->PushUint8(DW_LNS_set_column);
    this->PushUleb128(column);
  }

  void SetIsStmt(bool is_stmt) {
    if (is_stmt_ != is_stmt) {
      this->PushUint8(DW_LNS_negate_stmt);
      is_stmt_ = is_stmt;
    }
  }

  void SetBasicBlock() {
    this->PushUint8(DW_LNS_set_basic_block);
  }

  void SetPrologueEnd() {
    uses_dwarf3_features_ = true;
    this->PushUint8(DW_LNS_set_prologue_end);
  }

  void SetEpilogueBegin() {
    uses_dwarf3_features_ = true;
    this->PushUint8(DW_LNS_set_epilogue_begin);
  }

  void SetISA(int isa) {
    uses_dwarf3_features_ = true;
    this->PushUint8(DW_LNS_set_isa);
    this->PushUleb128(isa);
  }

  void EndSequence() {
    this->PushUint8(0);
    this->PushUleb128(1);
    this->PushUint8(DW_LNE_end_sequence);
    current_address_ = 0;
    current_file_ = 1;
    current_line_ = 1;
    is_stmt_ = kDefaultIsStmt;
  }

  // Uncoditionally set address using the long encoding.
  // This gives the linker opportunity to relocate the address.
  void SetAddress(uint64_t absolute_address) {
    DCHECK_GE(absolute_address, current_address_);
    FactorCodeOffset(absolute_address);  // Check if it is factorable.
    this->PushUint8(0);
    if (use_64bit_address_) {
      this->PushUleb128(1 + 8);
      this->PushUint8(DW_LNE_set_address);
      patch_locations_.push_back(this->data()->size());
      this->PushUint64(absolute_address);
    } else {
      this->PushUleb128(1 + 4);
      this->PushUint8(DW_LNE_set_address);
      patch_locations_.push_back(this->data()->size());
      this->PushUint32(absolute_address);
    }
    current_address_ = absolute_address;
  }

  void DefineFile(const char* filename,
                  int directory_index,
                  int modification_time,
                  int file_size) {
    int size = 1 +
               strlen(filename) + 1 +
               UnsignedLeb128Size(directory_index) +
               UnsignedLeb128Size(modification_time) +
               UnsignedLeb128Size(file_size);
    this->PushUint8(0);
    this->PushUleb128(size);
    size_t start = data()->size();
    this->PushUint8(DW_LNE_define_file);
    this->PushString(filename);
    this->PushUleb128(directory_index);
    this->PushUleb128(modification_time);
    this->PushUleb128(file_size);
    DCHECK_EQ(start + size, data()->size());
  }

  // Compact address and line opcode.
  void AddRow(uint64_t absolute_address, int absolute_line) {
    DCHECK_GE(absolute_address, current_address_);

    // If the address is definitely too far, use the long encoding.
    uint64_t delta_address = FactorCodeOffset(absolute_address - current_address_);
    if (delta_address > UINT8_MAX) {
      AdvancePC(absolute_address);
      delta_address = 0;
    }

    // If the line is definitely too far, use the long encoding.
    int delta_line = absolute_line - current_line_;
    if (!(kLineBase <= delta_line && delta_line < kLineBase + kLineRange)) {
      AdvanceLine(absolute_line);
      delta_line = 0;
    }

    // Both address and line should be reasonable now.  Use the short encoding.
    int opcode = kOpcodeBase + (delta_line - kLineBase) +
                 (static_cast<int>(delta_address) * kLineRange);
    if (opcode > UINT8_MAX) {
      // If the address is still too far, try to increment it by const amount.
      int const_advance = (0xFF - kOpcodeBase) / kLineRange;
      opcode -= (kLineRange * const_advance);
      if (opcode <= UINT8_MAX) {
        this->PushUint8(DW_LNS_const_add_pc);
      } else {
        // Give up and use long encoding for address.
        AdvancePC(absolute_address);
        // Still use the opcode to do line advance and copy.
        opcode = kOpcodeBase + (delta_line - kLineBase);
      }
    }
    DCHECK(kOpcodeBase <= opcode && opcode <= 0xFF);
    this->PushUint8(opcode);  // Special opcode.
    current_line_ = absolute_line;
    current_address_ = absolute_address;
  }

  int GetCodeFactorBits() const {
    return code_factor_bits_;
  }

  uint64_t CurrentAddress() const {
    return current_address_;
  }

  int CurrentFile() const {
    return current_file_;
  }

  int CurrentLine() const {
    return current_line_;
  }

  const std::vector<uintptr_t>& GetPatchLocations() const {
    return patch_locations_;
  }

  using Writer<Vector>::data;

  DebugLineOpCodeWriter(bool use64bitAddress,
                        int codeFactorBits,
                        const typename Vector::allocator_type& alloc =
                            typename Vector::allocator_type())
      : Writer<Vector>(&opcodes_),
        opcodes_(alloc),
        uses_dwarf3_features_(false),
        use_64bit_address_(use64bitAddress),
        code_factor_bits_(codeFactorBits),
        current_address_(0),
        current_file_(1),
        current_line_(1),
        is_stmt_(kDefaultIsStmt) {
  }

 private:
  uint64_t FactorCodeOffset(uint64_t offset) const {
    DCHECK_GE(code_factor_bits_, 0);
    DCHECK_EQ((offset >> code_factor_bits_) << code_factor_bits_, offset);
    return offset >> code_factor_bits_;
  }

  Vector opcodes_;
  bool uses_dwarf3_features_;
  bool use_64bit_address_;
  int code_factor_bits_;
  uint64_t current_address_;
  int current_file_;
  int current_line_;
  bool is_stmt_;
  std::vector<uintptr_t> patch_locations_;

  DISALLOW_COPY_AND_ASSIGN(DebugLineOpCodeWriter);
};

}  // namespace dwarf
}  // namespace art

#endif  // ART_LIBELFFILE_DWARF_DEBUG_LINE_OPCODE_WRITER_H_
