// [AsmJit]
// Machine Code Generation for C++.
//
// [License]
// Zlib - See LICENSE.md file in the package.

#ifndef _ASMJIT_CORE_EMITTER_H
#define _ASMJIT_CORE_EMITTER_H

#include "../core/arch.h"
#include "../core/inst.h"
#include "../core/operand.h"
#include "../core/codeholder.h"

ASMJIT_BEGIN_NAMESPACE

//! \addtogroup asmjit_core
//! \{

// ============================================================================
// [Forward Declarations]
// ============================================================================

class ConstPool;
class FuncFrame;
class FuncArgsAssignment;

// ============================================================================
// [asmjit::BaseEmitter]
// ============================================================================

//! Provides a base foundation to emit code - specialized by `Assembler` and
//! `BaseBuilder`.
class ASMJIT_VIRTAPI BaseEmitter {
public:
  ASMJIT_BASE_CLASS(BaseEmitter)

  //! See `EmitterType`.
  uint8_t _type;
  //! Reserved for future use.
  uint8_t _reserved;
  //! See \ref BaseEmitter::Flags.
  uint16_t _flags;
  //! Emitter options, always in sync with CodeHolder.
  uint32_t _emitterOptions;

  //! CodeHolder the BaseEmitter is attached to.
  CodeHolder* _code;
  //! Attached `ErrorHandler`.
  ErrorHandler* _errorHandler;

  //! Basic information about the code (matches CodeHolder::_codeInfo).
  CodeInfo _codeInfo;
  //! Native GP register signature and signature related information.
  RegInfo _gpRegInfo;
  //! Internal private data used freely by any emitter.
  uint32_t _privateData;

  //! Next instruction options (affects the next instruction).
  uint32_t _instOptions;
  //! Global Instruction options (combined with `_instOptions` by `emit...()`).
  uint32_t _globalInstOptions;
  //! Extra register (op-mask {k} on AVX-512) (affects the next instruction).
  RegOnly _extraReg;
  //! Inline comment of the next instruction (affects the next instruction).
  const char* _inlineComment;

  //! Emitter type.
  enum EmitterType : uint32_t {
    //! Unknown or uninitialized.
    kTypeNone = 0,
    //! Emitter inherits from `BaseAssembler`.
    kTypeAssembler = 1,
    //! Emitter inherits from `BaseBuilder`.
    kTypeBuilder = 2,
    //! Emitter inherits from `BaseCompiler`.
    kTypeCompiler = 3,
    //! Count of emitter types.
    kTypeCount = 4
  };

  //! Emitter flags.
  enum Flags : uint32_t {
    //! The emitter was finalized.
    kFlagFinalized = 0x4000u,
    //! The emitter was destroyed.
    kFlagDestroyed = 0x8000u
  };

  //! Emitter options.
  enum Options : uint32_t {
    //! Logging is enabled, `BaseEmitter::logger()` must return a valid logger.
    //! This option is set automatically by the emitter if the logger is present.
    //! User code should never alter this value.
    //!
    //! Default `false`.
    kOptionLoggingEnabled   = 0x00000001u,

    //! Stricly validate each instruction before it's emitted.
    //!
    //! Default `false`.
    kOptionStrictValidation = 0x00000002u,

    //! Emit instructions that are optimized for size, if possible.
    //!
    //! Default `false`.
    //!
    //! X86 Specific
    //! ------------
    //!
    //! When this option is set it the assembler will try to fix instructions
    //! if possible into operation equivalent instructions that take less bytes
    //! by taking advantage of implicit zero extension. For example instruction
    //! like `mov r64, imm` and `and r64, imm` can be translated to `mov r32, imm`
    //! and `and r32, imm` when the immediate constant is lesser than `2^31`.
    kOptionOptimizedForSize = 0x00000004u,

    //! Emit optimized code-alignment sequences.
    //!
    //! Default `false`.
    //!
    //! X86 Specific
    //! ------------
    //!
    //! Default align sequence used by X86 architecture is one-byte (0x90)
    //! opcode that is often shown by disassemblers as NOP. However there are
    //! more optimized align sequences for 2-11 bytes that may execute faster
    //! on certain CPUs. If this feature is enabled AsmJit will generate
    //! specialized sequences for alignment between 2 to 11 bytes.
    kOptionOptimizedAlign = 0x00000008u,

    //! Emit jump-prediction hints.
    //!
    //! Default `false`.
    //!
    //! X86 Specific
    //! ------------
    //!
    //! Jump prediction is usually based on the direction of the jump. If the
    //! jump is backward it is usually predicted as taken; and if the jump is
    //! forward it is usually predicted as not-taken. The reason is that loops
    //! generally use backward jumps and conditions usually use forward jumps.
    //! However this behavior can be overridden by using instruction prefixes.
    //! If this option is enabled these hints will be emitted.
    //!
    //! This feature is disabled by default, because the only processor that
    //! used to take into consideration prediction hints was P4. Newer processors
    //! implement heuristics for branch prediction and ignore static hints. This
    //! means that this feature can be used for annotation purposes.
    kOptionPredictedJumps = 0x00000010u
  };

  //! \name Construction & Destruction
  //! \{

  ASMJIT_API explicit BaseEmitter(uint32_t type) noexcept;
  ASMJIT_API virtual ~BaseEmitter() noexcept;

  //! \}

  //! \name Cast
  //! \{

  template<typename T>
  inline T* as() noexcept { return reinterpret_cast<T*>(this); }

  template<typename T>
  inline const T* as() const noexcept { return reinterpret_cast<const T*>(this); }

  //! \}

  //! \name Emitter Type & Flags
  //! \{

  //! Returns the type of this emitter, see `EmitterType`.
  inline uint32_t emitterType() const noexcept { return _type; }
  //! Returns emitter flags , see `Flags`.
  inline uint32_t emitterFlags() const noexcept { return _flags; }

  //! Tests whether the emitter inherits from `BaseAssembler`.
  inline bool isAssembler() const noexcept { return _type == kTypeAssembler; }
  //! Tests whether the emitter inherits from `BaseBuilder`.
  //!
  //! \note Both Builder and Compiler emitters would return `true`.
  inline bool isBuilder() const noexcept { return _type >= kTypeBuilder; }
  //! Tests whether the emitter inherits from `BaseCompiler`.
  inline bool isCompiler() const noexcept { return _type == kTypeCompiler; }

  //! Tests whether the emitter has the given `flag` enabled.
  inline bool hasFlag(uint32_t flag) const noexcept { return (_flags & flag) != 0; }
  //! Tests whether the emitter is finalized.
  inline bool isFinalized() const noexcept { return hasFlag(kFlagFinalized); }
  //! Tests whether the emitter is destroyed (only used during destruction).
  inline bool isDestroyed() const noexcept { return hasFlag(kFlagDestroyed); }

  inline void _addFlags(uint32_t flags) noexcept { _flags = uint16_t(_flags | flags); }
  inline void _clearFlags(uint32_t flags) noexcept { _flags = uint16_t(_flags & ~flags); }

  //! \}

  //! \name Target Information
  //! \{

  //! Returns the CodeHolder this emitter is attached to.
  inline CodeHolder* code() const noexcept { return _code; }
  //! Returns an information about the code, see `CodeInfo`.
  inline const CodeInfo& codeInfo() const noexcept { return _codeInfo; }
  //! Returns an information about the architecture, see `ArchInfo`.
  inline const ArchInfo& archInfo() const noexcept { return _codeInfo.archInfo(); }

  //! Tests whether the target architecture is 32-bit.
  inline bool is32Bit() const noexcept { return archInfo().is32Bit(); }
  //! Tests whether the target architecture is 64-bit.
  inline bool is64Bit() const noexcept { return archInfo().is64Bit(); }

  //! Returns the target architecture type.
  inline uint32_t archId() const noexcept { return archInfo().archId(); }
  //! Returns the target architecture sub-type.
  inline uint32_t archSubId() const noexcept { return archInfo().archSubId(); }
  //! Returns the target architecture's GP register size (4 or 8 bytes).
  inline uint32_t gpSize() const noexcept { return archInfo().gpSize(); }
  //! Returns the number of target GP registers.
  inline uint32_t gpCount() const noexcept { return archInfo().gpCount(); }

  //! \}

  //! \name Initialization & Finalization
  //! \{

  //! Tests whether the BaseEmitter is initialized (i.e. attached to the `CodeHolder`).
  inline bool isInitialized() const noexcept { return _code != nullptr; }

  ASMJIT_API virtual Error finalize();

  //! \}

  //! \name Emitter Options
  //! \{

  //! Tests whether the `option` is present in emitter options.
  inline bool hasEmitterOption(uint32_t option) const noexcept { return (_emitterOptions & option) != 0; }
  //! Returns the emitter options.
  inline uint32_t emitterOptions() const noexcept { return _emitterOptions; }

  // TODO: Deprecate and remove, CodeHolder::addEmitterOptions() is the way.
  inline void addEmitterOptions(uint32_t options) noexcept {
    _emitterOptions |= options;
    onUpdateGlobalInstOptions();
  }

  inline void clearEmitterOptions(uint32_t options) noexcept {
    _emitterOptions &= ~options;
    onUpdateGlobalInstOptions();
  }

  //! Returns the global instruction options.
  //!
  //! Default instruction options are merged with instruction options before the
  //! instruction is encoded. These options have some bits reserved that are used
  //! for error handling, logging, and strict validation. Other options are globals that
  //! affect each instruction, for example if VEX3 is set globally, it will all
  //! instructions, even those that don't have such option set.
  inline uint32_t globalInstOptions() const noexcept { return _globalInstOptions; }

  //! \}

  //! \name Error Handling
  //! \{

  //! Tests whether the local error handler is attached.
  inline bool hasErrorHandler() const noexcept { return _errorHandler != nullptr; }
  //! Returns the local error handler.
  inline ErrorHandler* errorHandler() const noexcept { return _errorHandler; }
  //! Sets the local error handler.
  inline void setErrorHandler(ErrorHandler* handler) noexcept { _errorHandler = handler; }
  //! Resets the local error handler (does nothing if not attached).
  inline void resetErrorHandler() noexcept { setErrorHandler(nullptr); }

  //! Handles the given error in the following way:
  //!   1. Gets either Emitter's (preferred) or CodeHolder's ErrorHandler.
  //!   2. If exists, calls `ErrorHandler::handleError(error, message, this)`.
  //!   3. Returns the given `err` if ErrorHandler haven't thrown.
  ASMJIT_API Error reportError(Error err, const char* message = nullptr);

  //! \}

  //! \name Instruction Options
  //! \{

  //! Returns options of the next instruction.
  inline uint32_t instOptions() const noexcept { return _instOptions; }
  //! Returns options of the next instruction.
  inline void setInstOptions(uint32_t options) noexcept { _instOptions = options; }
  //! Adds options of the next instruction.
  inline void addInstOptions(uint32_t options) noexcept { _instOptions |= options; }
  //! Resets options of the next instruction.
  inline void resetInstOptions() noexcept { _instOptions = 0; }

  //! Tests whether the extra register operand is valid.
  inline bool hasExtraReg() const noexcept { return _extraReg.isReg(); }
  //! Returns an extra operand that will be used by the next instruction (architecture specific).
  inline const RegOnly& extraReg() const noexcept { return _extraReg; }
  //! Sets an extra operand that will be used by the next instruction (architecture specific).
  inline void setExtraReg(const BaseReg& reg) noexcept { _extraReg.init(reg); }
  //! Sets an extra operand that will be used by the next instruction (architecture specific).
  inline void setExtraReg(const RegOnly& reg) noexcept { _extraReg.init(reg); }
  //! Resets an extra operand that will be used by the next instruction (architecture specific).
  inline void resetExtraReg() noexcept { _extraReg.reset(); }

  //! Returns comment/annotation of the next instruction.
  inline const char* inlineComment() const noexcept { return _inlineComment; }
  //! Sets comment/annotation of the next instruction.
  //!
  //! \note This string is set back to null by `_emit()`, but until that it has
  //! to remain valid as the Emitter is not required to make a copy of it (and
  //! it would be slow to do that for each instruction).
  inline void setInlineComment(const char* s) noexcept { _inlineComment = s; }
  //! Resets the comment/annotation to nullptr.
  inline void resetInlineComment() noexcept { _inlineComment = nullptr; }

  //! \}

  //! \name Sections
  //! \{

  virtual Error section(Section* section) = 0;

  //! \}

  //! \name Labels
  //! \{

  //! Creates a new label.
  virtual Label newLabel() = 0;
  //! Creates a new named label.
  virtual Label newNamedLabel(const char* name, size_t nameSize = SIZE_MAX, uint32_t type = Label::kTypeGlobal, uint32_t parentId = Globals::kInvalidId) = 0;

  //! Returns `Label` by `name`.
  //!
  //! Returns invalid Label in case that the name is invalid or label was not found.
  //!
  //! \note This function doesn't trigger ErrorHandler in case the name is invalid
  //! or no such label exist. You must always check the validity of the `Label` returned.
  ASMJIT_API Label labelByName(const char* name, size_t nameSize = SIZE_MAX, uint32_t parentId = Globals::kInvalidId) noexcept;

  //! Binds the `label` to the current position of the current section.
  //!
  //! \note Attempt to bind the same label multiple times will return an error.
  virtual Error bind(const Label& label) = 0;

  //! Tests whether the label `id` is valid (i.e. registered).
  ASMJIT_API bool isLabelValid(uint32_t labelId) const noexcept;
  //! Tests whether the `label` is valid (i.e. registered).
  inline bool isLabelValid(const Label& label) const noexcept { return isLabelValid(label.id()); }

  //! \}

  //! \name Emit
  //! \{

  // NOTE: These `emit()` helpers are designed to address a code-bloat generated
  // by C++ compilers to call a function having many arguments. Each parameter to
  // `_emit()` requires some code to pass it, which means that if we default to 4
  // operand parameters in `_emit()` and instId the C++ compiler would have to
  // generate a virtual function call having 5 parameters, which is quite a lot.
  // Since by default asm instructions have 2 to 3 operands it's better to
  // introduce helpers that pass those and fill out the remaining operands.

  #define OP const Operand_&
  #define NONE Globals::none

  //! Emits an instruction.
  ASMJIT_NOINLINE Error emit(uint32_t instId) { return _emit(instId, NONE, NONE, NONE, NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0) { return _emit(instId, o0, NONE, NONE, NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1) { return _emit(instId, o0, o1, NONE, NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1, OP o2) { return _emit(instId, o0, o1, o2, NONE); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3) { return _emit(instId, o0, o1, o2, o3); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, OP o4) { return _emit(instId, o0, o1, o2, o3, o4, NONE); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, OP o4, OP o5) { return _emit(instId, o0, o1, o2, o3, o4, o5); }

  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, int o0) { return _emit(instId, Imm(o0), NONE, NONE, NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, int o1) { return _emit(instId, o0, Imm(o1), NONE, NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1, int o2) { return _emit(instId, o0, o1, Imm(o2), NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1, OP o2, int o3) { return _emit(instId, o0, o1, o2, Imm(o3)); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, int o4) { return _emit(instId, o0, o1, o2, o3, Imm(o4), NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, OP o4, int o5) { return _emit(instId, o0, o1, o2, o3, o4, Imm(o5)); }

  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, int64_t o0) { return _emit(instId, Imm(o0), NONE, NONE, NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, int64_t o1) { return _emit(instId, o0, Imm(o1), NONE, NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1, int64_t o2) { return _emit(instId, o0, o1, Imm(o2), NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1, OP o2, int64_t o3) { return _emit(instId, o0, o1, o2, Imm(o3)); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, int64_t o4) { return _emit(instId, o0, o1, o2, o3, Imm(o4), NONE); }
  //! \overload
  ASMJIT_NOINLINE Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, OP o4, int64_t o5) { return _emit(instId, o0, o1, o2, o3, o4, Imm(o5)); }

  //! \overload
  inline Error emit(uint32_t instId, unsigned int o0) { return emit(instId, int64_t(o0)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, unsigned int o1) { return emit(instId, o0, int64_t(o1)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, unsigned int o2) { return emit(instId, o0, o1, int64_t(o2)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, OP o2, unsigned int o3) { return emit(instId, o0, o1, o2, int64_t(o3)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, unsigned int o4) { return emit(instId, o0, o1, o2, o3, int64_t(o4)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, OP o4, unsigned int o5) { return emit(instId, o0, o1, o2, o3, o4, int64_t(o5)); }

  //! \overload
  inline Error emit(uint32_t instId, uint64_t o0) { return emit(instId, int64_t(o0)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, uint64_t o1) { return emit(instId, o0, int64_t(o1)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, uint64_t o2) { return emit(instId, o0, o1, int64_t(o2)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, OP o2, uint64_t o3) { return emit(instId, o0, o1, o2, int64_t(o3)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, uint64_t o4) { return emit(instId, o0, o1, o2, o3, int64_t(o4)); }
  //! \overload
  inline Error emit(uint32_t instId, OP o0, OP o1, OP o2, OP o3, OP o4, uint64_t o5) { return emit(instId, o0, o1, o2, o3, o4, int64_t(o5)); }

  #undef NONE
  #undef OP

  inline Error emitOpArray(uint32_t instId, const Operand_* operands, size_t count) { return _emitOpArray(instId, operands, count); }

  inline Error emitInst(const BaseInst& inst, const Operand_* operands, size_t count) {
    setInstOptions(inst.options());
    setExtraReg(inst.extraReg());
    return _emitOpArray(inst.id(), operands, count);
  }

  //! \cond INTERNAL
  //! Emits instruction having max 4 operands.
  virtual Error _emit(uint32_t instId, const Operand_& o0, const Operand_& o1, const Operand_& o2, const Operand_& o3) = 0;
  //! Emits instruction having max 6 operands.
  virtual Error _emit(uint32_t instId, const Operand_& o0, const Operand_& o1, const Operand_& o2, const Operand_& o3, const Operand_& o4, const Operand_& o5) = 0;
  //! Emits instruction having operands stored in array.
  virtual Error _emitOpArray(uint32_t instId, const Operand_* operands, size_t count);
  //! \endcond

  //! \}

  //! \name Emit Utilities
  //! \{

  ASMJIT_API Error emitProlog(const FuncFrame& layout);
  ASMJIT_API Error emitEpilog(const FuncFrame& layout);
  ASMJIT_API Error emitArgsAssignment(const FuncFrame& layout, const FuncArgsAssignment& args);

  //! \}

  //! \name Align
  //! \{

  //! Aligns the current CodeBuffer to the `alignment` specified.
  //!
  //! The sequence that is used to fill the gap between the aligned location
  //! and the current location depends on the align `mode`, see `AlignMode`.
  virtual Error align(uint32_t alignMode, uint32_t alignment) = 0;

  //! \}

  //! \name Embed
  //! \{

  //! Embeds raw data into the CodeBuffer.
  virtual Error embed(const void* data, uint32_t dataSize) = 0;

  //! Embeds an absolute label address as data (4 or 8 bytes).
  virtual Error embedLabel(const Label& label) = 0;

  //! Embeds a delta (distance) between the `label` and `base` calculating it
  //! as `label - base`. This function was designed to make it easier to embed
  //! lookup tables where each index is a relative distance of two labels.
  virtual Error embedLabelDelta(const Label& label, const Label& base, uint32_t dataSize) = 0;

  //! Embeds a constant pool at the current offset by performing the following:
  //!   1. Aligns by using kAlignData to the minimum `pool` alignment.
  //!   2. Binds the ConstPool label so it's bound to an aligned location.
  //!   3. Emits ConstPool content.
  virtual Error embedConstPool(const Label& label, const ConstPool& pool) = 0;

  //! \}

  //! \name Comment
  //! \{

  //! Emits a comment stored in `data` with an optional `size` parameter.
  virtual Error comment(const char* data, size_t size = SIZE_MAX) = 0;

  //! Emits a formatted comment specified by `fmt` and variable number of arguments.
  ASMJIT_API Error commentf(const char* fmt, ...);
  //! Emits a formatted comment specified by `fmt` and `ap`.
  ASMJIT_API Error commentv(const char* fmt, va_list ap);

  //! \}

  //! \name Events
  //! \{

  //! Called after the emitter was attached to `CodeHolder`.
  virtual Error onAttach(CodeHolder* code) noexcept = 0;
  //! Called after the emitter was detached from `CodeHolder`.
  virtual Error onDetach(CodeHolder* code) noexcept = 0;

  //! Called to update `_globalInstOptions` based on `_emitterOptions`.
  //!
  //! This function should only touch one bit `BaseInst::kOptionReserved`, which
  //! is used to handle errors and special-cases in a way that minimizes branching.
  ASMJIT_API void onUpdateGlobalInstOptions() noexcept;

  //! \}
};

//! \}

ASMJIT_END_NAMESPACE

#endif // _ASMJIT_CORE_EMITTER_H
