chromium/v8/src/objects/tagged.h

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

#ifndef V8_OBJECTS_TAGGED_H_
#define V8_OBJECTS_TAGGED_H_

#include <type_traits>

#include "src/common/globals.h"
#include "src/objects/tagged-impl.h"
#include "src/objects/union.h"

namespace v8 {
namespace internal {

class BigInt;
class FieldType;
class HeapObject;
class HeapNumber;
class HeapObjectLayout;
class Object;
class TaggedIndex;
class Smi;

// Tagged<T> represents an uncompressed V8 tagged pointer.
//
// The tagged pointer is a pointer-sized value with a tag in the LSB. The value
// is either:
//
//   * A small integer (Smi), shifted right, with the tag set to 0
//   * A strong pointer to an object on the V8 heap, with the tag set to 01
//   * A weak pointer to an object on the V8 heap, with the tag set to 11
//   * A cleared weak pointer, with the value 11
//
// The exact encoding differs depending on 32- vs 64-bit architectures, and in
// the latter case, whether or not pointer compression is enabled.
//
// On 32-bit architectures, this is:
//             |----- 32 bits -----|
// Pointer:    |______address____w1|
//    Smi:     |____int31_value___0|
//
// On 64-bit architectures with pointer compression:
//             |----- 32 bits -----|----- 32 bits -----|
// Pointer:    |________base_______|______offset_____w1|
//    Smi:     |......garbage......|____int31_value___0|
//
// On 64-bit architectures without pointer compression:
//             |----- 32 bits -----|----- 32 bits -----|
// Pointer:    |________________address______________w1|
//    Smi:     |____int32_value____|00...............00|
//
// where `w` is the "weak" bit.
//
// We specialise Tagged separately for Object, Smi and HeapObject, and then all
// other types T, so that:
//
//                    Tagged<Object> -> StrongTaggedBase
//                       Tagged<Smi> -> StrongTaggedBase
//   Tagged<T> -> Tagged<HeapObject> -> StrongTaggedBase
//
// We also specialize it separately for MaybeWeak types, with a parallel
// hierarchy:
//
//                               Tagged<MaybeWeak<Object>> -> WeakTaggedBase
//                                  Tagged<MaybeWeak<Smi>> -> WeakTaggedBase
//   Tagged<MaybeWeak<T>> -> Tagged<MaybeWeak<HeapObject>> -> WeakTaggedBase
template <typename T>
class Tagged;

// MaybeWeak<T> represents a reference to T that may be either a strong or weak.
//
// MaybeWeak doesn't really exist by itself, but is rather a sentinel type for
// templates on tagged interfaces (like Tagged). For example, where Tagged<T>
// represents a strong reference to T, Tagged<MaybeWeak<T>> represents a
// potentially weak reference to T, and it is the responsibility of the Tagged
// interface to provide some mechanism (likely template specialization) to
// distinguish between the two and provide accessors to the T reference itself
// (which will always be strong).
template <typename T>
class MaybeWeak {};

template <typename T>
struct is_maybe_weak : public std::false_type {};
is_maybe_weak<MaybeWeak<T>>;
is_maybe_weak_v;

// ClearedWeakValue is a sentinel type for cleared weak values.
class ClearedWeakValue {};

// Convert a strong reference to T into a weak reference to T.
template <typename T>
inline Tagged<MaybeWeak<T>> MakeWeak(Tagged<T> value);
template <typename T>
inline Tagged<MaybeWeak<T>> MakeWeak(Tagged<MaybeWeak<T>> value);

// Convert a weak reference to T into a strong reference to T.
template <typename T>
inline Tagged<T> MakeStrong(Tagged<T> value);
template <typename T>
inline Tagged<T> MakeStrong(Tagged<MaybeWeak<T>> value);

// Base class for all Tagged<T> classes.
StrongTaggedBase;
WeakTaggedBase;

// `is_subtype<Derived, Base>::value` is true when Derived is a subtype of Base
// according to our object hierarchy. In particular, Smi is considered a
// subtype of Object.
template <typename Derived, typename Base, typename Enabled = void>
struct is_subtype;
is_subtype_v;

namespace detail {
template <typename Derived, typename Base, typename Enabled = void>
struct is_simple_subtype;
template <typename Derived, typename Base, typename Enabled = void>
struct is_complex_subtype;
}  // namespace detail

// `is_subtype<Derived, Base>` tries is_simple_subtype first, and if that fails,
// is_complex_subtype. This is to prevent instantiating the is_complex_subtype
// template when is_simple_subtype, to avoid trying std::is_base_of. This allows
// subtype checks to pass, for simple subtypes, with forward declarations.
template <typename Derived, typename Base, typename Enabled>
struct is_subtype
    : public std::disjunction<detail::is_simple_subtype<Derived, Base>,
                              detail::is_complex_subtype<Derived, Base>> {};

// Forward declarations for is_simple_subtype hack, remove once those
// specializations are removed.
class FixedArrayBase;
class FixedArray;
class FixedDoubleArray;
class ByteArray;
class NameDictionary;
class NumberDictionary;
class OrderedHashMap;
class OrderedHashSet;
class OrderedNameDictionary;
class ScriptContextTable;
class ArrayList;

detail  // namespace detail

static_assert;
static_assert;
static_assert;

// `is_taggable<T>::value` is true when T is a valid type for Tagged. This means
// de-facto being a subtype of Object.
is_taggable;
is_taggable_v;

// `is_castable<From, To>::value` is true when you can use `::cast` to cast from
// From to To. This means an upcast or downcast, which in practice means
// checking `is_subtype` symmetrically.
is_castable;
is_castable_v;

// TODO(leszeks): Remove this once there are no more conversions between
// Tagged<Foo> and Foo.
static constexpr bool kTaggedCanConvertToRawObjects =;

namespace detail {

// {TaggedOperatorArrowRef} is returned by {Tagged::operator->}. It should never
// be stored anywhere or used in any other code; no one should ever have to
// spell out {TaggedOperatorArrowRef} in code. Its only purpose is to be
// dereferenced immediately by "operator-> chaining". Returning the address of
// the field is valid because this objects lifetime only ends at the end of the
// full statement.
template <typename T>
class TaggedOperatorArrowRef {};

template <typename T>
struct BaseForTagged {};

BaseForTagged<MaybeWeak<T>>;

BaseForTagged<Union<T...>>;

// FieldType is special, since it can be Smi or Map. It could probably even be
// its own specialization, to avoid exposing an operator->.
template <>
struct BaseForTagged<FieldType> {};

}  // namespace detail

// Specialization for Object, where it's unknown whether this is a Smi or a
// HeapObject.
template <>
class Tagged<Object> : public StrongTaggedBase {};

// Specialization for Smi disallowing any implicit creation or access via ->,
// but offering instead a cast from Object and an int32_t value() method.
template <>
class Tagged<Smi> : public StrongTaggedBase {};

// Specialization for TaggedIndex disallowing any implicit creation or access
// via ->, but offering instead a cast from Object and an intptr_t value()
// method.
template <>
class Tagged<TaggedIndex> : public StrongTaggedBase {};

// Specialization for HeapObject, to group together functions shared between all
// HeapObjects
template <>
class Tagged<HeapObject> : public StrongTaggedBase {};

static_assert;

// Specialization for MaybeWeak<Object>, where it's unknown whether this is a
// Smi, a strong HeapObject, or a weak HeapObject
template <>
class Tagged<MaybeWeak<Object>> : public WeakTaggedBase {};

// Specialization for MaybeWeak<HeapObject>, to group together functions shared
// between all HeapObjects
template <>
class Tagged<MaybeWeak<HeapObject>> : public WeakTaggedBase {};

// Generic Tagged<T> for Unions. This doesn't allow direct access to the object,
// aside from casting.
Tagged<Union<Ts...>>;

// Generic Tagged<T> for any T that is a subclass of HeapObject. There are
// separate Tagged<T> specialaizations for T==Smi and T==Object, so we know that
// all other Tagged<T> are definitely pointers and not Smis.
template <typename T>
class Tagged : public detail::BaseForTagged<T>::type {};

// Specialized Tagged<T> for cleared weak values. This is only used, in
// practice, for conversions from Tagged<ClearedWeakValue> to a
// Tagged<MaybeWeak<T>>, where subtyping rules mean that this works for
// aribitrary T.
template <>
class Tagged<ClearedWeakValue> : public WeakTaggedBase {};

// Generic Tagged<T> for any T that is a subclass of HeapObject. There are
// separate Tagged<T> specializations for T==Smi and T==Object, so we know that
// all other Tagged<T> are definitely pointers and not Smis.
Tagged<MaybeWeak<T>>;

MaybeObject;
HeapObjectReference;

template <typename T>
inline Tagged<MaybeWeak<T>> MakeWeak(Tagged<T> value) {}

template <typename T>
inline Tagged<MaybeWeak<T>> MakeWeak(Tagged<MaybeWeak<T>> value) {}

template <typename T>
inline Tagged<T> MakeStrong(Tagged<T> value) {}

template <typename T>
inline Tagged<T> MakeStrong(Tagged<MaybeWeak<T>> value) {}

// Deduction guide to simplify Foo->Tagged<Foo> transition.
// TODO(leszeks): Remove once we're using Tagged everywhere.
static_assert;
template <class T>
Tagged(T object) -> Tagged<T>;

<deduction guide for Tagged>(const HeapObjectLayout *);

template <class T>
Tagged(const T* object) -> Tagged<T>;
template <class T>
Tagged(T* object) -> Tagged<T>;

template <typename T>
struct RemoveTagged {};

RemoveTagged<Tagged<T>>;

}  // namespace internal
}  // namespace v8

namespace std {

// Template specialize std::common_type to always return Object when compared
// against a subtype of Object.
//
// This is an incomplete specialization for objects and common_type, but
// sufficient for existing use-cases. A proper specialization would need to be
// conditionally enabled via `requires`, which is C++20, or with `enable_if`,
// which would require a custom common_type implementation.
common_type<T, i::Object>;

}  // namespace std

#endif  // V8_OBJECTS_TAGGED_H_