/*
 * 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_COMPILER_DWARF_DEBUG_FRAME_OPCODE_WRITER_H_
#define ART_COMPILER_DWARF_DEBUG_FRAME_OPCODE_WRITER_H_

#include "base/bit_utils.h"
#include "dwarf/dwarf_constants.h"
#include "dwarf/register.h"
#include "dwarf/writer.h"

namespace art {
namespace dwarf {

// Writer for .debug_frame opcodes (DWARF-3).
// See the DWARF specification for the precise meaning of the opcodes.
// 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 Allocator = std::allocator<uint8_t> >
class DebugFrameOpCodeWriter : private Writer<Allocator> {
 public:
  // To save space, DWARF divides most offsets by header-defined factors.
  // They are used in integer divisions, so we make them constants.
  // We usually subtract from stack base pointer, so making the factor
  // negative makes the encoded values positive and thus easier to encode.
  static constexpr int kDataAlignmentFactor = -4;
  static constexpr int kCodeAlignmentFactor = 1;

  // Explicitely advance the program counter to given location.
  void ALWAYS_INLINE AdvancePC(int absolute_pc) {
    DCHECK_GE(absolute_pc, current_pc_);
    if (UNLIKELY(enabled_)) {
      int delta = FactorCodeOffset(absolute_pc - current_pc_);
      if (delta != 0) {
        if (delta <= 0x3F) {
          this->PushUint8(DW_CFA_advance_loc | delta);
        } else if (delta <= UINT8_MAX) {
          this->PushUint8(DW_CFA_advance_loc1);
          this->PushUint8(delta);
        } else if (delta <= UINT16_MAX) {
          this->PushUint8(DW_CFA_advance_loc2);
          this->PushUint16(delta);
        } else {
          this->PushUint8(DW_CFA_advance_loc4);
          this->PushUint32(delta);
        }
      }
      current_pc_ = absolute_pc;
    }
  }

  // Override this method to automatically advance the PC before each opcode.
  virtual void ImplicitlyAdvancePC() { }

  // Common alias in assemblers - spill relative to current stack pointer.
  void ALWAYS_INLINE RelOffset(Reg reg, int offset) {
    Offset(reg, offset - current_cfa_offset_);
  }

  // Common alias in assemblers - increase stack frame size.
  void ALWAYS_INLINE AdjustCFAOffset(int delta) {
    DefCFAOffset(current_cfa_offset_ + delta);
  }

  // Custom alias - spill many registers based on bitmask.
  void ALWAYS_INLINE RelOffsetForMany(Reg reg_base, int offset,
                                      uint32_t reg_mask, int reg_size) {
    DCHECK(reg_size == 4 || reg_size == 8);
    if (UNLIKELY(enabled_)) {
      for (int i = 0; reg_mask != 0u; reg_mask >>= 1, i++) {
        // Skip zero bits and go to the set bit.
        int num_zeros = CTZ(reg_mask);
        i += num_zeros;
        reg_mask >>= num_zeros;
        RelOffset(Reg(reg_base.num() + i), offset);
        offset += reg_size;
      }
    }
  }

  // Custom alias - unspill many registers based on bitmask.
  void ALWAYS_INLINE RestoreMany(Reg reg_base, uint32_t reg_mask) {
    if (UNLIKELY(enabled_)) {
      for (int i = 0; reg_mask != 0u; reg_mask >>= 1, i++) {
        // Skip zero bits and go to the set bit.
        int num_zeros = CTZ(reg_mask);
        i += num_zeros;
        reg_mask >>= num_zeros;
        Restore(Reg(reg_base.num() + i));
      }
    }
  }

  void ALWAYS_INLINE Nop() {
    if (UNLIKELY(enabled_)) {
      this->PushUint8(DW_CFA_nop);
    }
  }

  void ALWAYS_INLINE Offset(Reg reg, int offset) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      int factored_offset = FactorDataOffset(offset);  // May change sign.
      if (factored_offset >= 0) {
        if (0 <= reg.num() && reg.num() <= 0x3F) {
          this->PushUint8(DW_CFA_offset | reg.num());
          this->PushUleb128(factored_offset);
        } else {
          this->PushUint8(DW_CFA_offset_extended);
          this->PushUleb128(reg.num());
          this->PushUleb128(factored_offset);
        }
      } else {
        uses_dwarf3_features_ = true;
        this->PushUint8(DW_CFA_offset_extended_sf);
        this->PushUleb128(reg.num());
        this->PushSleb128(factored_offset);
      }
    }
  }

  void ALWAYS_INLINE Restore(Reg reg) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      if (0 <= reg.num() && reg.num() <= 0x3F) {
        this->PushUint8(DW_CFA_restore | reg.num());
      } else {
        this->PushUint8(DW_CFA_restore_extended);
        this->PushUleb128(reg.num());
      }
    }
  }

  void ALWAYS_INLINE Undefined(Reg reg) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      this->PushUint8(DW_CFA_undefined);
      this->PushUleb128(reg.num());
    }
  }

  void ALWAYS_INLINE SameValue(Reg reg) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      this->PushUint8(DW_CFA_same_value);
      this->PushUleb128(reg.num());
    }
  }

  // The previous value of "reg" is stored in register "new_reg".
  void ALWAYS_INLINE Register(Reg reg, Reg new_reg) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      this->PushUint8(DW_CFA_register);
      this->PushUleb128(reg.num());
      this->PushUleb128(new_reg.num());
    }
  }

  void ALWAYS_INLINE RememberState() {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      this->PushUint8(DW_CFA_remember_state);
    }
  }

  void ALWAYS_INLINE RestoreState() {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      this->PushUint8(DW_CFA_restore_state);
    }
  }

  void ALWAYS_INLINE DefCFA(Reg reg, int offset) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      if (offset >= 0) {
        this->PushUint8(DW_CFA_def_cfa);
        this->PushUleb128(reg.num());
        this->PushUleb128(offset);  // Non-factored.
      } else {
        uses_dwarf3_features_ = true;
        this->PushUint8(DW_CFA_def_cfa_sf);
        this->PushUleb128(reg.num());
        this->PushSleb128(FactorDataOffset(offset));
      }
    }
    current_cfa_offset_ = offset;
  }

  void ALWAYS_INLINE DefCFARegister(Reg reg) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      this->PushUint8(DW_CFA_def_cfa_register);
      this->PushUleb128(reg.num());
    }
  }

  void ALWAYS_INLINE DefCFAOffset(int offset) {
    if (UNLIKELY(enabled_)) {
      if (current_cfa_offset_ != offset) {
        ImplicitlyAdvancePC();
        if (offset >= 0) {
          this->PushUint8(DW_CFA_def_cfa_offset);
          this->PushUleb128(offset);  // Non-factored.
        } else {
          uses_dwarf3_features_ = true;
          this->PushUint8(DW_CFA_def_cfa_offset_sf);
          this->PushSleb128(FactorDataOffset(offset));
        }
      }
    }
    // Uncoditional so that the user can still get and check the value.
    current_cfa_offset_ = offset;
  }

  void ALWAYS_INLINE ValOffset(Reg reg, int offset) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      uses_dwarf3_features_ = true;
      int factored_offset = FactorDataOffset(offset);  // May change sign.
      if (factored_offset >= 0) {
        this->PushUint8(DW_CFA_val_offset);
        this->PushUleb128(reg.num());
        this->PushUleb128(factored_offset);
      } else {
        this->PushUint8(DW_CFA_val_offset_sf);
        this->PushUleb128(reg.num());
        this->PushSleb128(factored_offset);
      }
    }
  }

  void ALWAYS_INLINE DefCFAExpression(void * expr, int expr_size) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      uses_dwarf3_features_ = true;
      this->PushUint8(DW_CFA_def_cfa_expression);
      this->PushUleb128(expr_size);
      this->PushData(expr, expr_size);
    }
  }

  void ALWAYS_INLINE Expression(Reg reg, void * expr, int expr_size) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      uses_dwarf3_features_ = true;
      this->PushUint8(DW_CFA_expression);
      this->PushUleb128(reg.num());
      this->PushUleb128(expr_size);
      this->PushData(expr, expr_size);
    }
  }

  void ALWAYS_INLINE ValExpression(Reg reg, void * expr, int expr_size) {
    if (UNLIKELY(enabled_)) {
      ImplicitlyAdvancePC();
      uses_dwarf3_features_ = true;
      this->PushUint8(DW_CFA_val_expression);
      this->PushUleb128(reg.num());
      this->PushUleb128(expr_size);
      this->PushData(expr, expr_size);
    }
  }

  bool IsEnabled() const { return enabled_; }

  void SetEnabled(bool value) { enabled_ = value; }

  int GetCurrentPC() const { return current_pc_; }

  int GetCurrentCFAOffset() const { return current_cfa_offset_; }

  void SetCurrentCFAOffset(int offset) { current_cfa_offset_ = offset; }

  using Writer<Allocator>::data;

  DebugFrameOpCodeWriter(bool enabled = true,
                         const Allocator& alloc = Allocator())
      : Writer<Allocator>(&opcodes_),
        enabled_(enabled),
        opcodes_(alloc),
        current_cfa_offset_(0),
        current_pc_(0),
        uses_dwarf3_features_(false) {
    if (enabled) {
      // Best guess based on couple of observed outputs.
      opcodes_.reserve(16);
    }
  }

  virtual ~DebugFrameOpCodeWriter() { }

 protected:
  int FactorDataOffset(int offset) const {
    DCHECK_EQ(offset % kDataAlignmentFactor, 0);
    return offset / kDataAlignmentFactor;
  }

  int FactorCodeOffset(int offset) const {
    DCHECK_EQ(offset % kCodeAlignmentFactor, 0);
    return offset / kCodeAlignmentFactor;
  }

  bool enabled_;  // If disabled all writes are no-ops.
  std::vector<uint8_t, Allocator> opcodes_;
  int current_cfa_offset_;
  int current_pc_;
  bool uses_dwarf3_features_;

 private:
  DISALLOW_COPY_AND_ASSIGN(DebugFrameOpCodeWriter);
};

}  // namespace dwarf
}  // namespace art

#endif  // ART_COMPILER_DWARF_DEBUG_FRAME_OPCODE_WRITER_H_