// Copyright (c) 1994-2006 Sun Microsystems Inc.
// All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// - Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// - Redistribution in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// - Neither the name of Sun Microsystems or the names of contributors may
// be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// The original source code covered by the above license above has been
// modified significantly by Google Inc.
// Copyright 2021 the V8 project authors. All rights reserved.

#ifndef V8_CODEGEN_RISCV_ASSEMBLER_RISCV_H_
#define V8_CODEGEN_RISCV_ASSEMBLER_RISCV_H_

#include <stdio.h>

#include <memory>
#include <set>

#include "src/codegen/assembler.h"
#include "src/codegen/constant-pool.h"
#include "src/codegen/constants-arch.h"
#include "src/codegen/external-reference.h"
#include "src/codegen/flush-instruction-cache.h"
#include "src/codegen/label.h"
#include "src/codegen/riscv/base-assembler-riscv.h"
#include "src/codegen/riscv/base-riscv-i.h"
#include "src/codegen/riscv/extension-riscv-a.h"
#include "src/codegen/riscv/extension-riscv-b.h"
#include "src/codegen/riscv/extension-riscv-c.h"
#include "src/codegen/riscv/extension-riscv-d.h"
#include "src/codegen/riscv/extension-riscv-f.h"
#include "src/codegen/riscv/extension-riscv-m.h"
#include "src/codegen/riscv/extension-riscv-v.h"
#include "src/codegen/riscv/extension-riscv-zfh.h"
#include "src/codegen/riscv/extension-riscv-zicond.h"
#include "src/codegen/riscv/extension-riscv-zicsr.h"
#include "src/codegen/riscv/extension-riscv-zifencei.h"
#include "src/codegen/riscv/extension-riscv-zimop.h"
#include "src/codegen/riscv/register-riscv.h"
#include "src/common/code-memory-access.h"
#include "src/objects/contexts.h"
#include "src/objects/smi.h"

namespace v8 {
namespace internal {

// -----------------------------------------------------------------------------
// Machine instruction Operands.
constexpr int kSmiShift = kSmiTagSize + kSmiShiftSize;
constexpr uintptr_t kSmiShiftMask = (1UL << kSmiShift) - 1;
// Class Operand represents a shifter operand in data processing instructions.
class Operand {
 public:
  // Immediate.
  V8_INLINE explicit Operand(intptr_t immediate,
                             RelocInfo::Mode rmode = RelocInfo::NO_INFO)
      : rm_(no_reg), rmode_(rmode) {
    value_.immediate = immediate;
  }

  V8_INLINE explicit Operand(Tagged<Smi> value)
      : Operand(static_cast<intptr_t>(value.ptr())) {}

  V8_INLINE explicit Operand(const ExternalReference& f)
      : rm_(no_reg), rmode_(RelocInfo::EXTERNAL_REFERENCE) {
    value_.immediate = static_cast<intptr_t>(f.address());
  }

  explicit Operand(Handle<HeapObject> handle,
                   RelocInfo::Mode rmode = RelocInfo::FULL_EMBEDDED_OBJECT);

  static Operand EmbeddedNumber(double number);  // Smi or HeapNumber.

  // Register.
  V8_INLINE explicit Operand(Register rm) : rm_(rm) {}

  // Return true if this is a register operand.
  V8_INLINE bool is_reg() const { return rm_.is_valid(); }

  inline intptr_t immediate_for_heap_number_request() const {
    DCHECK(rmode() == RelocInfo::FULL_EMBEDDED_OBJECT);
    return value_.immediate;
  }

  inline intptr_t immediate() const {
    DCHECK(!is_reg());
    DCHECK(!IsHeapNumberRequest());
    return value_.immediate;
  }

  bool IsImmediate() const { return !rm_.is_valid(); }

  HeapNumberRequest heap_number_request() const {
    DCHECK(IsHeapNumberRequest());
    return value_.heap_number_request;
  }

  bool IsHeapNumberRequest() const {
    DCHECK_IMPLIES(is_heap_number_request_, IsImmediate());
    DCHECK_IMPLIES(is_heap_number_request_,
                   rmode_ == RelocInfo::FULL_EMBEDDED_OBJECT ||
                       rmode_ == RelocInfo::CODE_TARGET);
    return is_heap_number_request_;
  }

  Register rm() const { return rm_; }

  RelocInfo::Mode rmode() const { return rmode_; }

 private:
  Register rm_;
  union Value {
    Value() {}
    HeapNumberRequest heap_number_request;  // if is_heap_number_request_
    intptr_t immediate;                     // otherwise
  } value_;                                 // valid if rm_ == no_reg
  bool is_heap_number_request_ = false;
  RelocInfo::Mode rmode_;

  friend class Assembler;
  friend class MacroAssembler;
};

// On RISC-V we have only one addressing mode with base_reg + offset.
// Class MemOperand represents a memory operand in load and store instructions.
class V8_EXPORT_PRIVATE MemOperand : public Operand {
 public:
  // Immediate value attached to offset.
  enum OffsetAddend { offset_minus_one = -1, offset_zero = 0 };

  explicit MemOperand(Register rn, int32_t offset = 0);
  explicit MemOperand(Register rn, int32_t unit, int32_t multiplier,
                      OffsetAddend offset_addend = offset_zero);
  int32_t offset() const { return offset_; }

  void set_offset(int32_t offset) { offset_ = offset; }

  bool OffsetIsInt12Encodable() const { return is_int12(offset_); }

 private:
  int32_t offset_;

  friend class Assembler;
};

class V8_EXPORT_PRIVATE Assembler : public AssemblerBase,
                                    public AssemblerRISCVI,
                                    public AssemblerRISCVA,
                                    public AssemblerRISCVB,
                                    public AssemblerRISCVF,
                                    public AssemblerRISCVD,
                                    public AssemblerRISCVM,
                                    public AssemblerRISCVC,
                                    public AssemblerRISCVZifencei,
                                    public AssemblerRISCVZicsr,
                                    public AssemblerRISCVZicond,
                                    public AssemblerRISCVZicfiss,
                                    public AssemblerRISCVZfh,
                                    public AssemblerRISCVV {
 public:
  // Create an assembler. Instructions and relocation information are emitted
  // into a buffer, with the instructions starting from the beginning and the
  // relocation information starting from the end of the buffer. See CodeDesc
  // for a detailed comment on the layout (globals.h).
  //
  // If the provided buffer is nullptr, the assembler allocates and grows its
  // own buffer. Otherwise it takes ownership of the provided buffer.
  explicit Assembler(const AssemblerOptions&,
                     std::unique_ptr<AssemblerBuffer> = {});
  // For compatibility with assemblers that require a zone.
  Assembler(const MaybeAssemblerZone&, const AssemblerOptions& options,
            std::unique_ptr<AssemblerBuffer> buffer = {})
      : Assembler(options, std::move(buffer)) {}

  virtual ~Assembler();

  static RegList DefaultTmpList();
  static DoubleRegList DefaultFPTmpList();

  void AbortedCodeGeneration() override;

  // GetCode emits any pending (non-emitted) code and fills the descriptor desc.
  static constexpr int kNoHandlerTable = 0;
  static constexpr SafepointTableBuilderBase* kNoSafepointTable = nullptr;
  void GetCode(LocalIsolate* isolate, CodeDesc* desc,
               SafepointTableBuilderBase* safepoint_table_builder,
               int handler_table_offset);

  // Convenience wrapper for allocating with an Isolate.
  void GetCode(Isolate* isolate, CodeDesc* desc);
  // Convenience wrapper for code without safepoint or handler tables.
  void GetCode(LocalIsolate* isolate, CodeDesc* desc) {
    GetCode(isolate, desc, kNoSafepointTable, kNoHandlerTable);
  }

  // On RISC-V, we sometimes need to emit branch trampolines between emitting
  // a call instruction (jal/jalr) and recording a safepoint. This means that
  // we have to be careful to make sure the safepoint is recorded at the right
  // position. So we record the pc right after emitting the call instruction
  // and use this for the safepoint.
  int pc_offset_for_safepoint() const { return pc_offset_for_safepoint_; }

  // Unused on this architecture.
  void MaybeEmitOutOfLineConstantPool() {}

  // Clear any internal state to avoid check failures if we drop
  // the assembly code.
  void ClearInternalState() { constpool_.Clear(); }

  // Label operations & relative jumps (PPUM Appendix D).
  //
  // Takes a branch opcode (cc) and a label (L) and generates
  // either a backward branch or a forward branch and links it
  // to the label fixup chain. Usage:
  //
  // Label L;    // unbound label
  // j(cc, &L);  // forward branch to unbound label
  // bind(&L);   // bind label to the current pc
  // j(cc, &L);  // backward branch to bound label
  // bind(&L);   // illegal: a label may be bound only once
  //
  // Note: The same Label can be used for forward and backward branches
  // but it may be bound only once.
  void bind(Label* L);  // Binds an unbound label L to current code position.

  // Determines if Label is bound and near enough so that branch instruction
  // can be used to reach it, instead of jump instruction.
  bool is_near(Label* L);
  bool is_near(Label* L, OffsetSize bits);
  bool is_near_branch(Label* L);

  // Get offset from instr.
  int BranchOffset(Instr instr);
  static int BranchLongOffset(Instr auipc, Instr jalr);
  static int PatchBranchLongOffset(
      Address pc, Instr auipc, Instr instr_I, int32_t offset,
      WritableJitAllocation* jit_allocation = nullptr);

  // Returns the branch offset to the given label from the current code
  // position. Links the label to the current position if it is still unbound.
  int32_t branch_offset_helper(Label* L, OffsetSize bits) override;
  int32_t branch_long_offset(Label* L);

  // During code generation builtin targets in PC-relative call/jump
  // instructions are temporarily encoded as builtin ID until the generated
  // code is moved into the code space.
  static inline Builtin target_builtin_at(Address pc);

  // Read/Modify the code target address in the branch/call instruction at pc.
  // The isolate argument is unused (and may be nullptr) when skipping flushing.
  static Address target_constant_address_at(Address pc);

  static Address target_address_at(Address pc, Address constant_pool);

  static void set_target_address_at(
      Address pc, Address constant_pool, Address target,
      WritableJitAllocation* jit_allocation = nullptr,
      ICacheFlushMode icache_flush_mode = FLUSH_ICACHE_IF_NEEDED);

  // Read/Modify the code target address in the branch/call instruction at pc.
  inline static Tagged_t target_compressed_address_at(Address pc,
                                                      Address constant_pool);
  inline static void set_target_compressed_address_at(
      Address pc, Address constant_pool, Tagged_t target,
      WritableJitAllocation* jit_allocation = nullptr,
      ICacheFlushMode icache_flush_mode = FLUSH_ICACHE_IF_NEEDED);

  inline Handle<Object> code_target_object_handle_at(Address pc,
                                                     Address constant_pool);
  inline Handle<HeapObject> compressed_embedded_object_handle_at(
      Address pc, Address constant_pool);

  inline Handle<HeapObject> embedded_object_handle_at(Address pc);

#ifdef V8_TARGET_ARCH_RISCV64
  inline void set_embedded_object_index_referenced_from(
      Address p, EmbeddedObjectIndex index);
#endif

  static bool IsConstantPoolAt(Instruction* instr);
  static int ConstantPoolSizeAt(Instruction* instr);
  void EmitPoolGuard();

  bool pools_blocked() const { return pools_blocked_nesting_ > 0; }
  void StartBlockPools(int margin);
  void EndBlockPools();

  void FinishCode() { constpool_.Emit(); }

#if defined(V8_TARGET_ARCH_RISCV64)
  static void set_target_value_at(
      Address pc, uint64_t target,
      WritableJitAllocation* jit_allocation = nullptr,
      ICacheFlushMode icache_flush_mode = FLUSH_ICACHE_IF_NEEDED);
#elif defined(V8_TARGET_ARCH_RISCV32)
  static void set_target_value_at(
      Address pc, uint32_t target,
      WritableJitAllocation* jit_allocation = nullptr,
      ICacheFlushMode icache_flush_mode = FLUSH_ICACHE_IF_NEEDED);
#endif

  static inline int32_t target_constant32_at(Address pc);
  static inline void set_target_constant32_at(
      Address pc, uint32_t target, WritableJitAllocation* jit_allocation,
      ICacheFlushMode icache_flush_mode);

  static void JumpLabelToJumpRegister(Address pc);

  // This sets the branch destination (which gets loaded at the call address).
  // This is for calls and branches within generated code.  The serializer
  // has already deserialized the lui/ori instructions etc.
  inline static void deserialization_set_special_target_at(Address location,
                                                           Tagged<Code> code,
                                                           Address target);

  // Get the size of the special target encoded at 'instruction_payload'.
  inline static int deserialization_special_target_size(
      Address instruction_payload);

  // This sets the internal reference at the pc.
  inline static void deserialization_set_target_internal_reference_at(
      Address pc, Address target, WritableJitAllocation& jit_allocation,
      RelocInfo::Mode mode = RelocInfo::INTERNAL_REFERENCE);

  // Read/modify the uint32 constant used at pc.
  static inline uint32_t uint32_constant_at(Address pc, Address constant_pool);
  static inline void set_uint32_constant_at(
      Address pc, Address constant_pool, uint32_t new_constant,
      WritableJitAllocation* jit_allocation,
      ICacheFlushMode icache_flush_mode = FLUSH_ICACHE_IF_NEEDED);

  // Here we are patching the address in the LUI/ADDI instruction pair.
  // These values are used in the serialization process and must be zero for
  // RISC-V platform, as InstructionStream, Embedded Object or
  // External-reference pointers are split across two consecutive instructions
  // and don't exist separately in the code, so the serializer should not step
  // forwards in memory after a target is resolved and written.
  static constexpr int kSpecialTargetSize = 0;

  // Number of consecutive instructions used to store 32bit/64bit constant.
  // This constant was used in RelocInfo::target_address_address() function
  // to tell serializer address of the instruction that follows
  // LUI/ADDI instruction pair.
  static constexpr int kInstructionsFor32BitConstant = 2;
  static constexpr int kInstructionsFor64BitConstant = 8;

  // Difference between address of current opcode and value read from pc
  // register.
  static constexpr int kPcLoadDelta = 4;

  // Bits available for offset field in branches
  static constexpr int kBranchOffsetBits = 13;

  // Bits available for offset field in jump
  static constexpr int kJumpOffsetBits = 21;

  // Bits available for offset field in compresed jump
  static constexpr int kCJalOffsetBits = 12;

  // Bits available for offset field in compressed branch
  static constexpr int kCBranchOffsetBits = 9;

  // Max offset for b instructions with 12-bit offset field (multiple of 2)
  static constexpr int kMaxBranchOffset = (1 << (13 - 1)) - 1;

  // Max offset for jal instruction with 20-bit offset field (multiple of 2)
  static constexpr int kMaxJumpOffset = (1 << (21 - 1)) - 1;

  // The size of the an entry in the trampoline pool.
  static constexpr int kTrampolineSlotsSize = 2 * kInstrSize;

  // The size of the overhead for a trampoline pool. The overhead covers
  // the jump around the entries.
  static constexpr int kTrampolinePoolOverhead = 1 * kInstrSize;

  RegList* GetScratchRegisterList() { return &scratch_register_list_; }
  DoubleRegList* GetScratchDoubleRegisterList() {
    return &scratch_double_register_list_;
  }

  // ---------------------------------------------------------------------------
  // InstructionStream generation.

  // Insert the smallest number of nop instructions
  // possible to align the pc offset to a multiple
  // of m. m must be a power of 2 (>= 4).
  void Align(int m);
  // Insert the smallest number of zero bytes possible to align the pc offset
  // to a mulitple of m. m must be a power of 2 (>= 2).
  void DataAlign(int m);
  // Aligns code to something that's optimal for a jump target for the platform.
  void CodeTargetAlign();
  void SwitchTargetAlign() { CodeTargetAlign(); }
  void BranchTargetAlign() {}
  void LoopHeaderAlign() { CodeTargetAlign(); }

  // Different nop operations are used by the code generator to detect certain
  // states of the generated code.
  enum NopMarkerTypes {
    NON_MARKING_NOP = 0,
    DEBUG_BREAK_NOP,
    // IC markers.
    PROPERTY_ACCESS_INLINED,
    PROPERTY_ACCESS_INLINED_CONTEXT,
    PROPERTY_ACCESS_INLINED_CONTEXT_DONT_DELETE,
    // Helper values.
    LAST_CODE_MARKER,
    FIRST_IC_MARKER = PROPERTY_ACCESS_INLINED,
  };

  void NOP();
  void EBREAK();

  // Assembler Pseudo Instructions (Tables 25.2, 25.3, RISC-V Unprivileged ISA)
  void nop();
#if defined(V8_TARGET_ARCH_RISCV64)
  void RecursiveLiImpl(Register rd, int64_t imm);
  void RecursiveLi(Register rd, int64_t imm);
  static int RecursiveLiCount(int64_t imm);
  static int RecursiveLiImplCount(int64_t imm);
  void RV_li(Register rd, int64_t imm);
  static int RV_li_count(int64_t imm, bool is_get_temp_reg = false);
  // Returns the number of instructions required to load the immediate
  void GeneralLi(Register rd, int64_t imm);
  static int GeneralLiCount(int64_t imm, bool is_get_temp_reg = false);
  // Loads an immediate, always using 8 instructions, regardless of the value,
  // so that it can be modified later.
  void li_constant(Register rd, int64_t imm);
  void li_constant32(Register rd, int32_t imm);
  void li_ptr(Register rd, int64_t imm);
#endif
#if defined(V8_TARGET_ARCH_RISCV32)
  void RV_li(Register rd, int32_t imm);
  static int RV_li_count(int32_t imm, bool is_get_temp_reg = false);

  void li_constant(Register rd, int32_t imm);
  void li_ptr(Register rd, int32_t imm);
#endif

  void break_(uint32_t code, bool break_as_stop = false);
  void stop(uint32_t code = kMaxStopCode);

  // Check the code size generated from label to here.
  int SizeOfCodeGeneratedSince(Label* label) {
    return pc_offset() - label->pos();
  }

  // Check the number of instructions generated from label to here.
  int InstructionsGeneratedSince(Label* label) {
    return SizeOfCodeGeneratedSince(label) / kInstrSize;
  }

  // Blocks the trampoline pool and constant pools emissions. Emits pools if
  // necessary to ensure that {margin} more bytes can be emitted without
  // triggering pool emission.
  class V8_NODISCARD BlockPoolsScope {
   public:
    // We leave space for a number of trampoline pool slots, so we do not
    // have to pass in an explicit margin for all scopes.
    static constexpr int kGap = kTrampolineSlotsSize * 16;

    explicit BlockPoolsScope(Assembler* assem, int margin = 0)
        : assem_(assem), margin_(margin) {
      assem->StartBlockPools(margin);
      start_offset_ = assem->pc_offset();
    }

    ~BlockPoolsScope() {
      int generated = assem_->pc_offset() - start_offset_;
      USE(generated);  // Only used in DCHECK.
      int allowed = margin_;
      if (allowed == 0) allowed = kGap - kTrampolinePoolOverhead;
      DCHECK_GE(generated, 0);
      DCHECK_LE(generated, allowed);
      assem_->EndBlockPools();
    }

   private:
    Assembler* const assem_;
    const int margin_;
    int start_offset_;

    DISALLOW_IMPLICIT_CONSTRUCTORS(BlockPoolsScope);
  };

  // Record a deoptimization reason that can be used by a log or cpu profiler.
  // Use --trace-deopt to enable.
  void RecordDeoptReason(DeoptimizeReason reason, uint32_t node_id,
                         SourcePosition position, int id);
  static int RelocateInternalReference(
      RelocInfo::Mode rmode, Address pc, intptr_t pc_delta,
      WritableJitAllocation* jit_allocation = nullptr);
  static void RelocateRelativeReference(
      RelocInfo::Mode rmode, Address pc, intptr_t pc_delta,
      WritableJitAllocation* jit_allocation = nullptr);

  // Writes a single byte or word of data in the code stream.  Used for
  // inline tables, e.g., jump-tables.
  void db(uint8_t data);
  void dd(uint32_t data);
  void dq(uint64_t data);

#if defined(V8_TARGET_ARCH_RISCV64)
  void dp(uintptr_t data) { dq(data); }
  void dq(Label* label);
#elif defined(V8_TARGET_ARCH_RISCV32)
  void dp(uintptr_t data) { dd(data); }
  void dd(Label* label);
#endif

  Instruction* pc() const { return reinterpret_cast<Instruction*>(pc_); }

  Instruction* InstructionAt(ptrdiff_t offset) const {
    return reinterpret_cast<Instruction*>(buffer_start_ + offset);
  }

  // Check if there is less than kGap bytes available in the buffer.
  // If this is the case, we need to grow the buffer before emitting
  // an instruction or relocation information.
  inline bool overflow() const { return pc_ >= reloc_info_writer.pos() - kGap; }

  // Get the number of bytes available in the buffer.
  inline intptr_t available_space() const {
    return reloc_info_writer.pos() - pc_;
  }

  // Read/patch instructions.
  static Instr instr_at(Address pc) { return *reinterpret_cast<Instr*>(pc); }
  static void instr_at_put(Address pc, Instr instr,
                           WritableJitAllocation* jit_allocation = nullptr);

  Instr instr_at(int pos) {
    return *reinterpret_cast<Instr*>(buffer_start_ + pos);
  }
  void instr_at_put(int pos, Instr instr,
                    WritableJitAllocation* jit_allocation = nullptr);

  void instr_at_put(int pos, ShortInstr instr,
                    WritableJitAllocation* jit_allocation = nullptr);

  // Get the code target object for a pc-relative call or jump.
  inline Handle<Code> relative_code_target_object_handle_at(Address pc) const;

  inline int UnboundLabelsCount() const { return unbound_labels_count_; }

  friend class VectorUnit;
  class VectorUnit {
   public:
    inline VSew sew() const { return sew_; }
    inline int32_t sew_bits() const { return 1 << (sew_ + 3); }

    inline int32_t vlmax() const {
      if ((lmul_ & 0b100) != 0) {
        return (CpuFeatures::vlen() / sew_bits()) >> (lmul_ & 0b11);
      } else {
        return ((CpuFeatures::vlen() << lmul_) / sew_bits());
      }
    }

    explicit VectorUnit(Assembler* assm) : assm_(assm) {}

    // Sets the floating-point rounding mode.
    // Updating the rounding mode can be expensive, and therefore isn't done
    // for every basic block. Instead, we assume that the rounding mode is
    // RNE. Any instruction sequence that changes the rounding mode must
    // change it back to RNE before it finishes.
    void set(FPURoundingMode mode) {
      if (mode_ != mode) {
        if (mode == 0) {
          assm_->fsrm(zero_reg);
        } else {
          assm_->fsrm(mode);
        }
        mode_ = mode;
      }
    }

    void set(int32_t avl, VSew sew, Vlmul lmul, TailAgnosticType tail = ta) {
      DCHECK(is_uint5(avl));
      if (avl != avl_ || sew != sew_ || lmul != lmul_) {
        avl_ = avl;
        sew_ = sew;
        lmul_ = lmul;
        assm_->vsetivli(zero_reg, static_cast<uint8_t>(avl), sew_, lmul_, tail);
      }
    }

    void set(Register vd, Register avl, VSew sew, Vlmul lmul,
             TailAgnosticType tail = ta) {
      assm_->vsetvli(vd, avl, sew, lmul, tail);
      avl_ = -1;
      sew_ = sew;
      lmul_ = lmul;
    }

    bool IsConfiguredForSimd128() const {
      int32_t expected_avl = 128 / sew_bits();
      return (avl_ == expected_avl);
    }

    void SetSimd128(VSew sew, TailAgnosticType tail = ta) {
      Vlmul lmul;
      // Just support riscv zve64x now.
      // ELEN * LMUL >= SEW
      //  --> 2^(elen + 3) * 2^(-n) >= 2^(sew + 3)
      //  --> elen >= sew + n
      // mf2 -> n is 1.
      // mf4 -> n is 2.
      // mf8 -> n is 3.
      switch (CpuFeatures::vlen()) {
        case 128:
          lmul = m1;
          break;
        case 256:
          lmul = (sew + 1) > kRvvELEN ? m1 : mf2;
          break;
        case 512:
          lmul = (sew + 2) > kRvvELEN ? m1 : mf4;
          break;
        default:
          static_assert(kMaxRvvVLEN <= 512, "Unsupported VLEN");
          UNIMPLEMENTED();
      }
      if (sew == E8) {
        set(16, sew, lmul, tail);
      } else if (sew == E16) {
        set(8, sew, lmul, tail);
      } else if (sew == E32) {
        set(4, sew, lmul, tail);
      } else if (sew == E64) {
        set(2, sew, lmul, tail);
      } else {
        UNREACHABLE();
      }
    }

    void SetSimd128Half(VSew sew, TailAgnosticType tail = ta) {
      Vlmul lmul;
      switch (CpuFeatures::vlen()) {
        case 128:
          lmul = (sew + 1) > kRvvELEN ? m1 : mf2;
          break;
        case 256:
          lmul = (sew + 2) > kRvvELEN ? m1 : mf4;
          break;
        case 512:
          lmul = (sew + 3) > kRvvELEN ? m1 : mf8;
          break;
        default:
          static_assert(kMaxRvvVLEN <= 512, "Unsupported VLEN");
          UNIMPLEMENTED();
      }
      if (sew == E8) {
        set(8, sew, lmul, tail);
      } else if (sew == E16) {
        set(4, sew, lmul, tail);
      } else if (sew == E32) {
        set(2, sew, lmul, tail);
      } else if (sew == E64) {
        set(1, sew, lmul, tail);
      } else {
        UNREACHABLE();
      }
    }

    void SetSimd128x2(VSew sew, TailAgnosticType tail = ta) {
      Vlmul lmul;
      switch (CpuFeatures::vlen()) {
        case 128:
          lmul = m2;
          break;
        case 256:
          lmul = m1;
          break;
        case 512:
          lmul = (sew + 1) > kRvvELEN ? m1 : mf2;
          break;
        default:
          static_assert(kMaxRvvVLEN <= 512, "Unsupported VLEN");
          UNIMPLEMENTED();
      }
      if (sew == E8) {
        set(32, sew, lmul, tail);
      } else if (sew == E16) {
        set(16, sew, lmul, tail);
      } else if (sew == E32) {
        set(8, sew, lmul, tail);
      } else if (sew == E64) {
        set(4, sew, lmul, tail);
      } else {
        UNREACHABLE();
      }
    }

    void clear() {
      // If the rounding mode isn't RNE, then we forgot to change it back.
      DCHECK_EQ(RNE, mode_);
      avl_ = -1;
      sew_ = kVsInvalid;
      lmul_ = kVlInvalid;
    }

   private:
    int32_t avl_ = -1;
    VSew sew_ = kVsInvalid;
    Vlmul lmul_ = kVlInvalid;
    Assembler* assm_;
    FPURoundingMode mode_ = RNE;
  };

  VectorUnit VU;

  void ClearVectorUnit() override { VU.clear(); }

 protected:
  // Readable constants for base and offset adjustment helper, these indicate if
  // aside from offset, another value like offset + 4 should fit into int16.
  enum class OffsetAccessType : bool {
    SINGLE_ACCESS = false,
    TWO_ACCESSES = true
  };

  // Determine whether need to adjust base and offset of memroy load/store
  bool NeedAdjustBaseAndOffset(
      const MemOperand& src, OffsetAccessType = OffsetAccessType::SINGLE_ACCESS,
      int second_Access_add_to_offset = 4);

  // Helper function for memory load/store using base register and offset.
  void AdjustBaseAndOffset(
      MemOperand* src, Register scratch,
      OffsetAccessType access_type = OffsetAccessType::SINGLE_ACCESS,
      int second_access_add_to_offset = 4);

  inline static void set_target_internal_reference_encoded_at(Address pc,
                                                              Address target);

  intptr_t buffer_space() const { return reloc_info_writer.pos() - pc_; }

  // Decode branch instruction at pos and return branch target pos.
  int target_at(int pos, bool is_internal);

  // Patch branch instruction at pos to branch to given branch target pos.
  void target_at_put(int pos, int target_pos, bool is_internal);

  // Say if we need to relocate with this mode.
  bool MustUseReg(RelocInfo::Mode rmode);

  // Record reloc info for current pc_.
  void RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data = 0);

  // Record the current pc for the next safepoint.
  void RecordPcForSafepoint() override {
    set_pc_offset_for_safepoint(pc_offset());
  }

  void set_pc_offset_for_safepoint(int pc_offset) {
    pc_offset_for_safepoint_ = pc_offset;
  }

  RelocInfoStatus RecordEntry64(uint64_t data, RelocInfo::Mode rmode) {
    return constpool_.RecordEntry64(data, rmode);
  }

  void RecordConstPool(int size, const BlockPoolsScope& scope);

  bool is_trampoline_emitted() const { return trampoline_check_ == kMaxInt; }

 private:
  // Avoid overflows for displacements etc.
  static const int kMaximalBufferSize = 512 * MB;

  // The relocation writer's position is at least kGap bytes below the end of
  // the generated instructions. This is so that multi-instruction sequences do
  // not have to check for overflow. The same is true for writes of large
  // relocation info entries.
  static constexpr int kGap = 64;
  static_assert(AssemblerBase::kMinimalBufferSize >= 2 * kGap);

  // Emission of the pools may be blocked in some code sequences. The
  // nesting is zero when the pools aren't blocked.
  int pools_blocked_nesting_ = 0;

  // Relocation information generation.
  // Each relocation is encoded as a variable size value.
  static constexpr int kMaxRelocSize = RelocInfoWriter::kMaxSize;
  RelocInfoWriter reloc_info_writer;

  // Keep track of the last call instruction (jal/jalr) position to ensure that
  // we can generate a correct safepoint even in the presence of a branch
  // trampoline between emitting the call and recording the safepoint.
  int pc_offset_for_safepoint_ = -1;

  // InstructionStream emission.
  inline void CheckBuffer();
  void GrowBuffer();
  void emit(Instr x) override;
  void emit(ShortInstr x) override;
  template <typename T>
  inline void EmitHelper(T x, bool disassemble);

  inline void DisassembleInstruction(uint8_t* pc);
  static void DisassembleInstructionHelper(uint8_t* pc);

  // Labels.
  void print(const Label* L);
  void bind_to(Label* L, int pos);
  void next(Label* L, bool is_internal);

  // One trampoline consists of:
  // - space for trampoline slots,
  // - space for labels.
  //
  // Space for trampoline slots is equal to slot_count * 2 * kInstrSize.
  // Space for trampoline slots precedes space for labels. Each label is of one
  // instruction size, so total amount for labels is equal to
  // label_count *  kInstrSize.
  class Trampoline {
   public:
    Trampoline() {
      start_ = 0;
      next_slot_ = 0;
      free_slot_count_ = 0;
      end_ = 0;
    }

    Trampoline(int start, int slot_count) {
      start_ = start;
      next_slot_ = start;
      free_slot_count_ = slot_count;
      end_ = start + slot_count * kTrampolineSlotsSize;
    }

    int start() const { return start_; }
    int end() const { return end_; }

    int take_slot() {
      if (free_slot_count_ <= 0) return kInvalidSlotPos;
      int trampoline_slot = next_slot_;
      free_slot_count_--;
      next_slot_ += kTrampolineSlotsSize;
      DEBUG_PRINTF("\ttrampoline slot %d next %d free %d\n", trampoline_slot,
                   next_slot_, free_slot_count_)
      return trampoline_slot;
    }

   private:
    int start_;
    int end_;
    int next_slot_;
    int free_slot_count_;
  };

  static constexpr int kInvalidSlotPos = -1;
  Trampoline trampoline_;

  int unbound_labels_count_ = 0;
  int trampoline_check_;  // The pc offset of next trampoline pool check.

  void CheckTrampolinePool();
  inline void CheckTrampolinePoolQuick(int margin);
  int32_t GetTrampolineEntry(int32_t pos);

  // We keep track of the position of all internal reference uses of labels,
  // so we can distinguish the use site from other kinds of uses. The other
  // uses can be recognized by looking at the generated code at the position,
  // but internal references are just data (like jump table entries), so we
  // need something extra to tell them apart from other kinds of uses.
  std::set<intptr_t> internal_reference_positions_;
  bool is_internal_reference(Label* L) const {
    DCHECK(L->is_linked());
    return internal_reference_positions_.contains(L->pos());
  }

  RegList scratch_register_list_;
  DoubleRegList scratch_double_register_list_;
  ConstantPool constpool_;

  void PatchInHeapNumberRequest(Address pc, Handle<HeapNumber> object) override;

  int WriteCodeComments();

  friend class EnsureSpace;
  friend class ConstantPool;
  friend class RelocInfo;
  friend class RegExpMacroAssemblerRISCV;
};

class EnsureSpace {
 public:
  explicit inline EnsureSpace(Assembler* assembler);
};

// This scope utility allows scratch registers to be managed safely. The
// Assembler's {GetScratchRegisterList()}/{GetScratchDoubleRegisterList()}
// are used as pools of general-purpose/double scratch registers.
// These registers can be allocated on demand, and will be returned
// at the end of the scope.
//
// When the scope ends, the Assembler's lists will be restored to their original
// states, even if the lists are modified by some other means. Note that this
// scope can be nested but the destructors need to run in the opposite order as
// the constructors. We do not have assertions for this.
class V8_EXPORT_PRIVATE UseScratchRegisterScope {
 public:
  explicit UseScratchRegisterScope(Assembler* assembler)
      : assembler_(assembler),
        old_available_(*assembler->GetScratchRegisterList()),
        old_available_double_(*assembler->GetScratchDoubleRegisterList()) {}

  ~UseScratchRegisterScope() {
    RegList* available = assembler_->GetScratchRegisterList();
    DoubleRegList* available_double =
        assembler_->GetScratchDoubleRegisterList();
    *available = old_available_;
    *available_double = old_available_double_;
  }

  Register Acquire() {
    RegList* available = assembler_->GetScratchRegisterList();
    return available->PopFirst();
  }

  DoubleRegister AcquireDouble() {
    DoubleRegList* available_double =
        assembler_->GetScratchDoubleRegisterList();
    return available_double->PopFirst();
  }

  // Check if we have registers available to acquire.
  bool CanAcquire() const {
    RegList* available = assembler_->GetScratchRegisterList();
    return !available->is_empty();
  }

  void Include(const Register& reg1, const Register& reg2) {
    Include(reg1);
    Include(reg2);
  }
  void Include(const Register& reg) {
    DCHECK_NE(reg, no_reg);
    RegList* available = assembler_->GetScratchRegisterList();
    DCHECK_NOT_NULL(available);
    DCHECK(!available->has(reg));
    available->set(reg);
  }
  void Include(RegList list) {
    RegList* available = assembler_->GetScratchRegisterList();
    DCHECK_NOT_NULL(available);
    *available = *available | list;
  }
  void Exclude(const RegList& list) {
    RegList* available = assembler_->GetScratchRegisterList();
    DCHECK_NOT_NULL(available);
    available->clear(list);
  }
  void Exclude(const Register& reg1, const Register& reg2) {
    Exclude(reg1);
    Exclude(reg2);
  }
  void Exclude(const Register& reg) {
    DCHECK_NE(reg, no_reg);
    RegList list({reg});
    Exclude(list);
  }

  void Include(DoubleRegList list) {
    DoubleRegList* available_double =
        assembler_->GetScratchDoubleRegisterList();
    DCHECK_NOT_NULL(available_double);
    DCHECK_EQ((*available_double & list).bits(), 0x0);
    *available_double = *available_double | list;
  }

  RegList Available() { return *assembler_->GetScratchRegisterList(); }
  void SetAvailable(RegList available) {
    *assembler_->GetScratchRegisterList() = available;
  }
  DoubleRegList AvailableDouble() {
    return *assembler_->GetScratchDoubleRegisterList();
  }
  void SetAvailableDouble(DoubleRegList available_double) {
    *assembler_->GetScratchDoubleRegisterList() = available_double;
  }

 private:
  friend class Assembler;
  friend class MacroAssembler;

  Assembler* assembler_;
  RegList old_available_;
  DoubleRegList old_available_double_;
};

}  // namespace internal
}  // namespace v8

#endif  // V8_CODEGEN_RISCV_ASSEMBLER_RISCV_H_
