// Copyright 2023 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/objects/instruction-stream.h"

#include "src/builtins/builtins-inl.h"
#include "src/codegen/assembler-inl.h"
#include "src/codegen/flush-instruction-cache.h"
#include "src/codegen/reloc-info-inl.h"
#include "src/codegen/reloc-info.h"
#include "src/objects/instruction-stream-inl.h"

namespace v8 {
namespace internal {

void InstructionStream::Relocate(WritableJitAllocation& jit_allocation,
                                 intptr_t delta) {
  Tagged<Code> code;
  if (!TryGetCodeUnchecked(&code, kAcquireLoad)) return;
  // This is called during evacuation and code.instruction_stream() will point
  // to the old object. So pass *this directly to the RelocIterator.
  for (WritableRelocIterator it(jit_allocation, *this, constant_pool(),
                                RelocInfo::kApplyMask);
       !it.done(); it.next()) {
    it.rinfo()->apply(delta);
  }
  FlushInstructionCache(instruction_start(), body_size());
}

// This function performs the relocations but doesn't trigger any write barriers
// yet. We skip the write barriers here with UNSAFE_SKIP_WRITE_BARRIER but the
// caller needs to call RelocateFromDescWriteBarriers afterwards.
InstructionStream::WriteBarrierPromise InstructionStream::RelocateFromDesc(
    WritableJitAllocation& jit_allocation, Heap* heap, const CodeDesc& desc,
    Address constant_pool, const DisallowGarbageCollection& no_gc) {
  WriteBarrierPromise write_barrier_promise;
  Assembler* origin = desc.origin;
  const int mode_mask = RelocInfo::PostCodegenRelocationMask();
  for (WritableRelocIterator it(jit_allocation, *this, constant_pool,
                                mode_mask);
       !it.done(); it.next()) {
    // IMPORTANT:
    // this code needs be stay in sync with RelocateFromDescWriteBarriers below.

    RelocInfo::Mode mode = it.rinfo()->rmode();
    if (RelocInfo::IsEmbeddedObjectMode(mode)) {
      DirectHandle<HeapObject> p = it.rinfo()->target_object_handle(origin);
      it.rinfo()->set_target_object(*this, *p, UNSAFE_SKIP_WRITE_BARRIER,
                                    SKIP_ICACHE_FLUSH);
      write_barrier_promise.RegisterAddress(it.rinfo()->pc());
    } else if (RelocInfo::IsCodeTargetMode(mode)) {
      // Rewrite code handles to direct pointers to the first instruction in the
      // code object.
      DirectHandle<HeapObject> p = it.rinfo()->target_object_handle(origin);
      DCHECK(IsCode(*p));
      Tagged<InstructionStream> target_istream =
          Cast<Code>(*p)->instruction_stream();
      it.rinfo()->set_target_address(*this, target_istream->instruction_start(),
                                     UNSAFE_SKIP_WRITE_BARRIER,
                                     SKIP_ICACHE_FLUSH);
      write_barrier_promise.RegisterAddress(it.rinfo()->pc());
    } else if (RelocInfo::IsNearBuiltinEntry(mode)) {
      // Rewrite builtin IDs to PC-relative offset to the builtin entry point.
      Builtin builtin = it.rinfo()->target_builtin_at(origin);
      Address p = Builtins::EntryOf(builtin, heap->isolate());
      // This won't trigger a write barrier, but setting mode to
      // UPDATE_WRITE_BARRIER to make it clear that we didn't forget about it
      // below.
      it.rinfo()->set_target_address(*this, p, UPDATE_WRITE_BARRIER,
                                     SKIP_ICACHE_FLUSH);
      DCHECK_EQ(p, it.rinfo()->target_address());
    } else if (RelocInfo::IsWasmStubCall(mode)) {
#if V8_ENABLE_WEBASSEMBLY
      // Map wasm stub id to builtin.
      uint32_t stub_call_tag = it.rinfo()->wasm_call_tag();
      DCHECK_LT(stub_call_tag,
                static_cast<uint32_t>(Builtin::kFirstBytecodeHandler));
      Builtin builtin = static_cast<Builtin>(stub_call_tag);
      // Store the builtin address in relocation info.
      Address entry = Builtins::EntryOf(builtin, heap->isolate());
      it.rinfo()->set_wasm_stub_call_address(entry);
#else
      UNREACHABLE();
#endif
    } else {
      intptr_t delta =
          instruction_start() - reinterpret_cast<Address>(desc.buffer);
      it.rinfo()->apply(delta);
    }
  }
  return write_barrier_promise;
}

void InstructionStream::RelocateFromDescWriteBarriers(
    Heap* heap, const CodeDesc& desc, Address constant_pool,
    WriteBarrierPromise& write_barrier_promise,
    const DisallowGarbageCollection& no_gc) {
  const int mode_mask = RelocInfo::PostCodegenRelocationMask();
  for (RelocIterator it(code(kAcquireLoad), mode_mask); !it.done(); it.next()) {
    // IMPORTANT:
    // this code needs be stay in sync with RelocateFromDesc above.

    RelocInfo::Mode mode = it.rinfo()->rmode();
    if (RelocInfo::IsEmbeddedObjectMode(mode)) {
      Tagged<HeapObject> p = it.rinfo()->target_object(heap->isolate());
      WriteBarrier::ForRelocInfo(*this, it.rinfo(), p, UPDATE_WRITE_BARRIER);
      write_barrier_promise.ResolveAddress(it.rinfo()->pc());
    } else if (RelocInfo::IsCodeTargetMode(mode)) {
      Tagged<InstructionStream> target_istream =
          InstructionStream::FromTargetAddress(it.rinfo()->target_address());
      WriteBarrier::ForRelocInfo(*this, it.rinfo(), target_istream,
                                 UPDATE_WRITE_BARRIER);
      write_barrier_promise.ResolveAddress(it.rinfo()->pc());
    }
  }
}

#ifdef DEBUG
void InstructionStream::WriteBarrierPromise::RegisterAddress(Address address) {
  DCHECK(delayed_write_barriers_.insert(address).second);
}

void InstructionStream::WriteBarrierPromise::ResolveAddress(Address address) {
  DCHECK_EQ(delayed_write_barriers_.erase(address), 1);
}
InstructionStream::WriteBarrierPromise::~WriteBarrierPromise() {
  DCHECK(delayed_write_barriers_.empty());
}
#endif

}  // namespace internal
}  // namespace v8
