current commit
261004.7 · deps: update V8 to 15.2.124.38
// Copyright 2022 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_H_
#define V8_COMMON_PTR_COMPR_H_

#include "src/base/memory.h"
#include "src/common/globals.h"

namespace v8::internal {

class IsolateGroup;

#ifdef V8_ENABLE_SANDBOX
class Sandbox;
#endif  // V8_ENABLE_SANDBOX

// This is just a collection of common compression scheme related functions.
// Each pointer compression cage then has its own compression scheme, which
// mainly differes in the cage base address they use.
template <typename Cage>
class V8HeapCompressionSchemeImpl {
 public:
  V8_INLINE static constexpr Address GetPtrComprCageBaseAddress(
      Address on_heap_addr);

  V8_INLINE static Address GetPtrComprCageBaseAddress(
      PtrComprCageBase cage_base);

  // Compresses full-pointer representation of a tagged value to on-heap
  // representation.
  // Must only be used for compressing object pointers since this function
  // assumes that we deal with a valid address inside the pointer compression
  // cage.
  V8_INLINE static Tagged_t CompressObject(Address tagged);
  // Compress a potentially invalid pointer.
  V8_INLINE static constexpr Tagged_t CompressAny(Address tagged);

  // Decompresses smi value.
  V8_INLINE static Address DecompressTaggedSigned(Tagged_t raw_value);

  // Decompresses any tagged value, preserving both weak- and smi- tags.
  V8_INLINE static Address DecompressTagged(Tagged_t raw_value);

  // Given a 64bit raw value, found on the stack, calls the callback function
  // with all possible pointers that may be "contained" in compressed form in
  // this value, either as complete compressed pointers or as intermediate
  // (half-computed) results.
  template <typename ProcessPointerCallback>
  V8_INLINE static void ProcessIntermediatePointers(
      Address raw_value, ProcessPointerCallback callback);

  // Process-wide cage base value used for decompression.
  V8_INLINE static void InitBase(Address base);
  V8_CONST V8_INLINE static Address base();
};

// The main pointer compression cage, used for most objects.
class MainCage : public AllStatic {
  friend class V8HeapCompressionSchemeImpl<MainCage>;

  // These non-inlined accessors to base_ field are used in component builds
  // where cross-component access to thread local variables is not allowed.
  static V8_EXPORT_PRIVATE Address base_non_inlined();
  static V8_EXPORT_PRIVATE void set_base_non_inlined(Address base);

#ifdef V8_COMPRESS_POINTERS_IN_SHARED_CAGE
  static V8_EXPORT_PRIVATE uintptr_t base_ V8_CONSTINIT;
#else
  static thread_local uintptr_t base_ V8_CONSTINIT;
#endif  // V8_COMPRESS_POINTERS_IN_SHARED_CAGE
};
using V8HeapCompressionScheme = V8HeapCompressionSchemeImpl<MainCage>;

#ifdef V8_ENABLE_SANDBOX
// Compression scheme used for compressed pointers between trusted objects in
// the trusted heap space, outside of the sandbox.
class TrustedCage : public AllStatic {
  friend class V8HeapCompressionSchemeImpl<TrustedCage>;

  // Just to unify code with other cages in the multi-cage mode.
  static V8_EXPORT_PRIVATE Address base_non_inlined();
  static V8_EXPORT_PRIVATE void set_base_non_inlined(Address base);

#ifdef V8_COMPRESS_POINTERS_IN_SHARED_CAGE
  static V8_EXPORT_PRIVATE uintptr_t base_ V8_CONSTINIT;
#else
  static thread_local uintptr_t base_ V8_CONSTINIT;
#endif  // V8_COMPRESS_POINTERS_IN_SHARED_CAGE
};
using TrustedSpaceCompressionScheme = V8HeapCompressionSchemeImpl<TrustedCage>;
#else
// The trusted cage does not exist in this case, so trusted compression uses
// the main-cage encoding. Keep this as a distinct type (not a `using`-alias)
// so that `is_same_v<..., TrustedSpaceCompressionScheme>` behaves identically
// regardless of the sandbox flag.
struct TrustedSpaceCompressionScheme : V8HeapCompressionScheme {};
#endif  // V8_ENABLE_SANDBOX

// A compression scheme which can be passed if the only objects we ever expect
// to see are Smis (e.g. for {TaggedField<Smi, 0, SmiCompressionScheme>}).
class SmiCompressionScheme : public AllStatic {
 public:
  static Address 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 Tagged_t CompressObject(Address tagged) {
    DCHECK(HAS_SMI_TAG(tagged));
    return static_cast<Tagged_t>(tagged);
  }
};

#ifdef V8_EXTERNAL_CODE_SPACE
// Compression scheme used for fields containing InstructionStream objects
// (namely for the Code::code field).
// Unlike the V8HeapCompressionScheme this one allows the cage to cross 4GB
// boundary at a price of making decompression slightly more complex.
// The former outweighs the latter because it gives us more flexibility in
// allocating the code range closer to .text section in the process address
// space. At the same time decompression of the external code field happens
// relatively rarely during GC.
// The base can be any value such that [base, base + 4GB) contains the whole
// code range.
//
// This scheme works as follows:
//    --|----------{---------|------}--------------|--
//     4GB         |        4GB     |             4GB
//                 +-- code range --+
//                 |
//             cage base
//
// * Cage base value is OS page aligned for simplicity (although it's not
//   strictly necessary).
// * Code range size is smaller than or equal to 4GB.
// * Compression is just a truncation to 32-bits value.
// * Decompression of a pointer:
//   - if "compressed" cage base is <= than compressed value then one just
//     needs to OR the upper 32-bits of the case base to get the decompressed
//     value.
//   - if compressed value is smaller than "compressed" cage base then ORing
//     the upper 32-bits of the cage base is not enough because the resulting
//     value will be off by 4GB, which has to be added to the result.
//   - note that decompression doesn't modify the lower 32-bits of the value.
// * Decompression of Smi values is made a no-op for simplicity given that
//   on the hot paths of decompressing the Code pointers it's already known
//   that the value is not a Smi.
//
class ExternalCodeCompressionScheme {
 public:
  V8_INLINE static Address PrepareCageBaseAddress(Address on_heap_addr);

  // Note that this compression scheme doesn't allow reconstruction of the cage
  // base value from any arbitrary value, thus the cage base has to be passed
  // explicitly to the decompression functions.
  static Address GetPtrComprCageBaseAddress(Address on_heap_addr) = delete;

  V8_INLINE static Address GetPtrComprCageBaseAddress(
      PtrComprCageBase cage_base);

  // Compresses full-pointer representation of a tagged value to on-heap
  // representation.
  // Must only be used for compressing object pointers (incl. SMI) since this
  // function assumes pointers to be inside the pointer compression cage.
  V8_INLINE static Tagged_t CompressObject(Address tagged);
  // Compress anything that does not follow the above requirements (e.g. a maybe
  // object, or a marker bit pattern).
  V8_INLINE static constexpr Tagged_t CompressAny(Address tagged);

  // Decompresses any tagged value, preserving both weak- and smi- tags.
  V8_INLINE static Address DecompressTagged(Tagged_t raw_value);

  // Process-wide cage base value used for decompression.
  V8_INLINE static void InitBase(Address base);
  V8_INLINE static Address base();

  // Given a 64bit raw value, found on the stack, calls the callback function
  // with all possible pointers that may be "contained" in compressed form in
  // this value, either as complete compressed pointers or as intermediate
  // (half-computed) results.
  template <typename ProcessPointerCallback>
  V8_INLINE static void ProcessIntermediatePointers(
      Address raw_value, ProcessPointerCallback callback);

 private:
  // These non-inlined accessors to base_ field are used in component builds
  // where cross-component access to thread local variables is not allowed.
  static V8_EXPORT_PRIVATE Address base_non_inlined();
  static V8_EXPORT_PRIVATE void set_base_non_inlined(Address base);

#ifdef V8_COMPRESS_POINTERS_IN_SHARED_CAGE
  static V8_EXPORT_PRIVATE uintptr_t base_ V8_CONSTINIT;
#else
  static thread_local uintptr_t base_ V8_CONSTINIT;
#endif  // V8_COMPRESS_POINTERS_IN_SHARED_CAGE
};

#endif  // V8_EXTERNAL_CODE_SPACE

// Accessors for fields that may be unaligned due to pointer compression.

template <typename V>
static inline V ReadMaybeUnalignedValue(Address p) {
  // Pointer compression causes types larger than kTaggedSize to be unaligned.
#ifdef V8_COMPRESS_POINTERS
  constexpr bool v8_pointer_compression_unaligned = sizeof(V) > kTaggedSize;
#else
  constexpr bool v8_pointer_compression_unaligned = false;
#endif
  // Bug(v8:8875) Double fields may be unaligned.
  constexpr bool unaligned_double_field =
      std::is_same_v<V, double> && kDoubleSize > kTaggedSize;
  if (unaligned_double_field || v8_pointer_compression_unaligned) {
    return base::ReadUnalignedValue<V>(p);
  } else {
    return base::Memory<V>(p);
  }
}

template <typename V>
static inline void WriteMaybeUnalignedValue(Address p, V value) {
  // Pointer compression causes types larger than kTaggedSize to be unaligned.
#ifdef V8_COMPRESS_POINTERS
  constexpr bool v8_pointer_compression_unaligned = sizeof(V) > kTaggedSize;
#else
  constexpr bool v8_pointer_compression_unaligned = false;
#endif
  // Bug(v8:8875) Double fields may be unaligned.
  constexpr bool unaligned_double_field =
      std::is_same_v<V, double> && kDoubleSize > kTaggedSize;
  if (unaligned_double_field || v8_pointer_compression_unaligned) {
    base::WriteUnalignedValue<V>(p, value);
  } else {
    base::Memory<V>(p) = value;
  }
}

// When multi-cage pointer compression mode is enabled this scope object
// saves current cage's base values and sets them according to given Isolate.
// For all other configurations this scope object is a no-op.
// Note: In most cases you want a full `SetCurrentIsolateScope` which also
// updates TLS to make the isolate the "current" isolate.
#ifdef V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
class V8_NODISCARD PtrComprCageAccessScope final {
 public:
  V8_INLINE explicit PtrComprCageAccessScope(Isolate* isolate);
  V8_INLINE ~PtrComprCageAccessScope();

 private:
  const Address cage_base_;
#ifdef V8_EXTERNAL_CODE_SPACE
  const Address code_cage_base_;
#endif  // V8_EXTERNAL_CODE_SPACE
  IsolateGroup* saved_current_isolate_group_;
#ifdef V8_ENABLE_SANDBOX
  const Address saved_trusted_cage_base_;
  Sandbox* saved_current_sandbox_;
#endif  // V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
};
#else   // V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
class V8_NODISCARD PtrComprCageAccessScope final {
 public:
  V8_INLINE explicit PtrComprCageAccessScope(Isolate* isolate) {}
};
#endif  // V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES

V8_INLINE PtrComprCageBase GetPtrComprCageBase();

}  // namespace v8::internal

#endif  // V8_COMMON_PTR_COMPR_H_