// Copyright 2018 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.

#ifndef V8_COMMON_PTR_COMPR_INL_H_
#define V8_COMMON_PTR_COMPR_INL_H_

#include "src/common/ptr-compr.h"
// Include the non-inl header before the rest of the headers.

#include "src/common/globals.h"
#include "src/execution/isolate.h"
#include "src/objects/tagged.h"
#include "src/utils/utils.h"

#ifdef V8_ENABLE_SANDBOX
#include "src/sandbox/sandbox.h"
#endif  // V8_ENABLE_SANDBOX

namespace v8 {
namespace internal {

#ifdef V8_COMPRESS_POINTERS

// Always return the global/thread local cage base for isolate
PtrComprCageBase::PtrComprCageBase(const Isolate* isolate)
    : address_(V8HeapCompressionScheme::base()) {}
PtrComprCageBase::PtrComprCageBase(const LocalIsolate* isolate)
    : address_(V8HeapCompressionScheme::base()) {}

//
// V8HeapCompressionSchemeImpl
//

constexpr Address kPtrComprCageBaseMask = ~(kPtrComprCageBaseAlignment - 1);

// static
template <typename Cage>
constexpr Address V8HeapCompressionSchemeImpl<Cage>::GetPtrComprCageBaseAddress(
    Address on_heap_addr) {
  return RoundDown<kPtrComprCageBaseAlignment>(on_heap_addr);
}

// static
template <typename Cage>
Address V8HeapCompressionSchemeImpl<Cage>::GetPtrComprCageBaseAddress(
    PtrComprCageBase cage_base) {
  Address base = cage_base.address();
  V8_ASSUME((base & kPtrComprCageBaseMask) == base);
  base = reinterpret_cast<Address>(V8_ASSUME_ALIGNED(
      reinterpret_cast<void*>(base), kPtrComprCageBaseAlignment));
  return base;
}

// static
template <typename Cage>
void V8HeapCompressionSchemeImpl<Cage>::InitBase(Address base) {
  CHECK_EQ(base, GetPtrComprCageBaseAddress(base));
#if defined(USING_V8_SHARED_PRIVATE) && \
    defined(V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES)
  Cage::set_base_non_inlined(base);
#else
  Cage::base_ = base;
#endif
}

// static
template <typename Cage>
Address V8HeapCompressionSchemeImpl<Cage>::base() {
#if defined(USING_V8_SHARED_PRIVATE) && \
    defined(V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES)
  Address base = Cage::base_non_inlined();
#else
  Address base = Cage::base_;
#endif
  // V8_ASSUME_ALIGNED is often not preserved across ptr-to-int casts (i.e. when
  // casting to an Address). To increase our chances we additionally encode the
  // same information in this V8_ASSUME.
  V8_ASSUME((base & kPtrComprCageBaseMask) == base);
  return reinterpret_cast<Address>(V8_ASSUME_ALIGNED(
      reinterpret_cast<void*>(base), kPtrComprCageBaseAlignment));
}

// static
template <typename Cage>
Tagged_t V8HeapCompressionSchemeImpl<Cage>::CompressObject(Address tagged) {
#ifdef V8_COMPRESS_POINTERS
  // Ensure that we do not accidentally compress value from a different cage.
  // Cleared weak reference must be either pure (with empty upper part) or
  // belong to the same cage.
  DCHECK_IMPLIES(
      !HAS_SMI_TAG(tagged) && (tagged != kClearedWeakHeapObjectLower32),
      (tagged & kPtrComprCageBaseMask) == base());
#endif
  return static_cast<Tagged_t>(tagged);
}

// static
template <typename Cage>
constexpr Tagged_t V8HeapCompressionSchemeImpl<Cage>::CompressAny(
    Address tagged) {
  return static_cast<Tagged_t>(tagged);
}

// static
template <typename Cage>
Address V8HeapCompressionSchemeImpl<Cage>::DecompressTaggedSigned(
    Tagged_t raw_value) {
  // For runtime code the upper 32-bits of the Smi value do not matter.
  return static_cast<Address>(raw_value);
}

// static
template <typename Cage>
Address V8HeapCompressionSchemeImpl<Cage>::DecompressTagged(
    Tagged_t raw_value) {
#ifdef V8_COMPRESS_POINTERS
  Address cage_base = base();
#ifdef V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
  DCHECK_WITH_MSG(cage_base != kNullAddress,
                  "V8HeapCompressionSchemeImpl::base is not initialized for "
                  "current thread");
#endif  // V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
#else
  Address cage_base = GetPtrComprCageBaseAddress(on_heap_addr);
#endif  // V8_COMPRESS_POINTERS
  Address result = cage_base + static_cast<Address>(raw_value);
  V8_ASSUME(static_cast<uint32_t>(result) == raw_value);
  return result;
}

// static
template <typename Cage>
template <typename ProcessPointerCallback>
void V8HeapCompressionSchemeImpl<Cage>::ProcessIntermediatePointers(
    Address raw_value, ProcessPointerCallback callback) {
  // If pointer compression is enabled, we may have random compressed pointers
  // on the stack that may be used for subsequent operations.
  // Extract, decompress and trace both halfwords.
  Address decompressed_low =
      V8HeapCompressionSchemeImpl<Cage>::DecompressTagged(
          static_cast<Tagged_t>(raw_value));
  callback(decompressed_low);
  Address decompressed_high =
      V8HeapCompressionSchemeImpl<Cage>::DecompressTagged(
          static_cast<Tagged_t>(raw_value >> (sizeof(Tagged_t) * CHAR_BIT)));
  callback(decompressed_high);
}

#ifdef V8_EXTERNAL_CODE_SPACE

//
// ExternalCodeCompressionScheme
//

constexpr Address kMinExpectedOSPageSizeMask = ~(kMinExpectedOSPageSize - 1);

// static
Address ExternalCodeCompressionScheme::PrepareCageBaseAddress(
    Address on_heap_addr) {
  return RoundDown<kMinExpectedOSPageSize>(on_heap_addr);
}

// static
Address ExternalCodeCompressionScheme::GetPtrComprCageBaseAddress(
    PtrComprCageBase cage_base) {
  Address base = cage_base.address();
  V8_ASSUME((base & kMinExpectedOSPageSizeMask) == base);
  base = reinterpret_cast<Address>(
      V8_ASSUME_ALIGNED(reinterpret_cast<void*>(base), kMinExpectedOSPageSize));
  return base;
}

// static
void ExternalCodeCompressionScheme::InitBase(Address base) {
  CHECK_EQ(base, PrepareCageBaseAddress(base));
#if defined(USING_V8_SHARED_PRIVATE) && \
    defined(V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES)
  set_base_non_inlined(base);
#else
  base_ = base;
#endif
}

// static
V8_CONST Address ExternalCodeCompressionScheme::base() {
#if defined(USING_V8_SHARED_PRIVATE) && \
    defined(V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES)
  Address base = base_non_inlined();
#else
  Address base = base_;
#endif
  // V8_ASSUME_ALIGNED is often not preserved across ptr-to-int casts (i.e. when
  // casting to an Address). To increase our chances we additionally encode the
  // same information in this V8_ASSUME.
  V8_ASSUME((base & kMinExpectedOSPageSizeMask) == base);
  return reinterpret_cast<Address>(
      V8_ASSUME_ALIGNED(reinterpret_cast<void*>(base), kMinExpectedOSPageSize));
}

// static
Tagged_t ExternalCodeCompressionScheme::CompressObject(Address tagged) {
  // Sanity check - the tagged value should belong to this cage.
#ifdef V8_COMPRESS_POINTERS_IN_SHARED_CAGE
  DCHECK_IMPLIES(
      !HAS_SMI_TAG(tagged),
      (base() <= tagged) && (tagged < base() + kPtrComprCageReservationSize));
#endif
  return static_cast<Tagged_t>(tagged);
}

// static
constexpr Tagged_t ExternalCodeCompressionScheme::CompressAny(Address tagged) {
  return static_cast<Tagged_t>(tagged);
}

// static
Address ExternalCodeCompressionScheme::DecompressTagged(Tagged_t raw_value) {
  Address cage_base = base();
#ifdef V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
  DCHECK_WITH_MSG(cage_base != kNullAddress,
                  "ExternalCodeCompressionScheme::base is not initialized for "
                  "current thread");
#endif  // V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
  V8_ASSUME((cage_base & kMinExpectedOSPageSizeMask) == cage_base);

  Address diff = static_cast<Address>(static_cast<uint32_t>(raw_value)) -
                 static_cast<Address>(static_cast<uint32_t>(cage_base));
  // The cage base value was chosen such that it's less or equal than any
  // pointer in the cage, thus if we got a negative diff then it means that
  // the decompressed value is off by 4GB.
  if (static_cast<intptr_t>(diff) < 0) {
    diff += size_t{4} * GB;
  }
  DCHECK(is_uint32(diff));
  Address result = cage_base + diff;
  DCHECK_EQ(static_cast<uint32_t>(result), raw_value);
  return result;
}

// static
template <typename ProcessPointerCallback>
void ExternalCodeCompressionScheme::ProcessIntermediatePointers(
    Address raw_value, ProcessPointerCallback callback) {
  // If pointer compression is enabled, we may have random compressed pointers
  // on the stack that may be used for subsequent operations.
  // Extract, decompress and trace both halfwords.
  Address decompressed_low = ExternalCodeCompressionScheme::DecompressTagged(
      static_cast<Tagged_t>(raw_value));
  callback(decompressed_low);
  Address decompressed_high = ExternalCodeCompressionScheme::DecompressTagged(
      static_cast<Tagged_t>(raw_value >> (sizeof(Tagged_t) * CHAR_BIT)));
  callback(decompressed_high);
}

#endif  // V8_EXTERNAL_CODE_SPACE

//
// Misc functions.
//

V8_INLINE PtrComprCageBase
GetPtrComprCageBaseFromOnHeapAddress(Address address) {
  return PtrComprCageBase(
      V8HeapCompressionScheme::GetPtrComprCageBaseAddress(address));
}

// Load the main pointer compression cage base.
V8_INLINE PtrComprCageBase GetPtrComprCageBase() {
  return PtrComprCageBase(V8HeapCompressionScheme::base());
}

#else

//
// V8HeapCompressionSchemeImpl
//

// static
template <typename Cage>
constexpr Address V8HeapCompressionSchemeImpl<Cage>::GetPtrComprCageBaseAddress(
    Address on_heap_addr) {
  UNREACHABLE();
  return {};
}

// static
template <typename Cage>
Tagged_t V8HeapCompressionSchemeImpl<Cage>::CompressObject(Address tagged) {
  UNREACHABLE();
}

// static
template <typename Cage>
constexpr Tagged_t V8HeapCompressionSchemeImpl<Cage>::CompressAny(
    Address tagged) {
  UNREACHABLE();
  return {};
}

// static
template <typename Cage>
Address V8HeapCompressionSchemeImpl<Cage>::DecompressTaggedSigned(
    Tagged_t raw_value) {
  UNREACHABLE();
}

// static
template <typename Cage>
Address V8HeapCompressionSchemeImpl<Cage>::DecompressTagged(
    Tagged_t raw_value) {
  UNREACHABLE();
}

// static
template <typename Cage>
template <typename ProcessPointerCallback>
void V8HeapCompressionSchemeImpl<Cage>::ProcessIntermediatePointers(
    Address raw_value, ProcessPointerCallback callback) {
  UNREACHABLE();
}

//
// Misc functions.
//

V8_INLINE constexpr PtrComprCageBase GetPtrComprCageBaseFromOnHeapAddress(
    Address address) {
  return PtrComprCageBase();
}

V8_INLINE PtrComprCageBase GetPtrComprCageBase() { return PtrComprCageBase(); }

#endif  // V8_COMPRESS_POINTERS

V8_INLINE PtrComprCageBase GetPtrComprCageBase(Tagged<HeapObject> object) {
  return GetPtrComprCageBaseFromOnHeapAddress(object.ptr());
}

#ifdef V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES

PtrComprCageAccessScope::PtrComprCageAccessScope(Isolate* isolate)
    : cage_base_(V8HeapCompressionScheme::base()),
#ifdef V8_EXTERNAL_CODE_SPACE
      code_cage_base_(ExternalCodeCompressionScheme::base()),
#endif  // V8_EXTERNAL_CODE_SPACE
      saved_current_isolate_group_(IsolateGroup::current())
#ifdef V8_ENABLE_SANDBOX
      ,
      saved_trusted_cage_base_(TrustedSpaceCompressionScheme::base()),
      saved_current_sandbox_(Sandbox::current())
#endif  // V8_ENABLE_SANDBOX
{
  V8HeapCompressionScheme::InitBase(isolate->cage_base());
#ifdef V8_EXTERNAL_CODE_SPACE
  ExternalCodeCompressionScheme::InitBase(isolate->code_cage_base());
#endif  // V8_EXTERNAL_CODE_SPACE
  IsolateGroup::set_current(isolate->isolate_group());
#ifdef V8_ENABLE_SANDBOX
  TrustedSpaceCompressionScheme::InitBase(
      isolate->isolate_group()->GetTrustedPtrComprCageBase());
  Sandbox::set_current(isolate->isolate_group()->sandbox());
#endif  // V8_ENABLE_SANDBOX
}

PtrComprCageAccessScope::~PtrComprCageAccessScope() {
  V8HeapCompressionScheme::InitBase(cage_base_);
#ifdef V8_EXTERNAL_CODE_SPACE
  ExternalCodeCompressionScheme::InitBase(code_cage_base_);
#endif  // V8_EXTERNAL_CODE_SPACE
  IsolateGroup::set_current(saved_current_isolate_group_);
#ifdef V8_ENABLE_SANDBOX
  TrustedSpaceCompressionScheme::InitBase(saved_trusted_cage_base_);
  Sandbox::set_current(saved_current_sandbox_);
#endif  // V8_ENABLE_SANDBOX
}

#endif  // V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES

}  // namespace internal
}  // namespace v8

#endif  // V8_COMMON_PTR_COMPR_INL_H_