1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef INCLUDE_CPPGC_ALLOCATION_H_
6#define INCLUDE_CPPGC_ALLOCATION_H_
7
8#include <atomic>
9#include <cstddef>
10#include <cstdint>
11#include <new>
12#include <type_traits>
13#include <utility>
14
15#include "cppgc/custom-space.h"
16#include "cppgc/internal/api-constants.h"
17#include "cppgc/internal/gc-info.h"
18#include "cppgc/type-traits.h"
19#include "v8config.h"  // NOLINT(build/include_directory)
20
21#if defined(__has_attribute)
22#if __has_attribute(assume_aligned)
23#define CPPGC_DEFAULT_ALIGNED \
24  __attribute__((assume_aligned(api_constants::kDefaultAlignment)))
25#define CPPGC_DOUBLE_WORD_ALIGNED \
26  __attribute__((assume_aligned(2 * api_constants::kDefaultAlignment)))
27#endif  // __has_attribute(assume_aligned)
28#endif  // defined(__has_attribute)
29
30#if !defined(CPPGC_DEFAULT_ALIGNED)
31#define CPPGC_DEFAULT_ALIGNED
32#endif
33
34#if !defined(CPPGC_DOUBLE_WORD_ALIGNED)
35#define CPPGC_DOUBLE_WORD_ALIGNED
36#endif
37
38namespace cppgc {
39
40/**
41 * AllocationHandle is used to allocate garbage-collected objects.
42 */
43class AllocationHandle;
44
45namespace internal {
46
47// Similar to C++17 std::align_val_t;
48enum class AlignVal : size_t {};
49
50class V8_EXPORT MakeGarbageCollectedTraitInternal {
51 protected:
52  static inline void MarkObjectAsFullyConstructed(const void* payload) {
53    // See api_constants for an explanation of the constants.
54    std::atomic<uint16_t>* atomic_mutable_bitfield =
55        reinterpret_cast<std::atomic<uint16_t>*>(
56            const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(
57                reinterpret_cast<const uint8_t*>(payload) -
58                api_constants::kFullyConstructedBitFieldOffsetFromPayload)));
59    // It's safe to split use load+store here (instead of a read-modify-write
60    // operation), since it's guaranteed that this 16-bit bitfield is only
61    // modified by a single thread. This is cheaper in terms of code bloat (on
62    // ARM) and performance.
63    uint16_t value = atomic_mutable_bitfield->load(std::memory_order_relaxed);
64    value |= api_constants::kFullyConstructedBitMask;
65    atomic_mutable_bitfield->store(value, std::memory_order_release);
66  }
67
68  // Dispatch based on compile-time information.
69  //
70  // Default implementation is for a custom space with >`kDefaultAlignment` byte
71  // alignment.
72  template <typename GCInfoType, typename CustomSpace, size_t alignment>
73  struct AllocationDispatcher final {
74    static void* Invoke(AllocationHandle& handle, size_t size) {
75      static_assert(std::is_base_of<CustomSpaceBase, CustomSpace>::value,
76                    "Custom space must inherit from CustomSpaceBase.");
77      static_assert(
78          !CustomSpace::kSupportsCompaction,
79          "Custom spaces that support compaction do not support allocating "
80          "objects with non-default (i.e. word-sized) alignment.");
81      return MakeGarbageCollectedTraitInternal::Allocate(
82          handle, size, static_cast<AlignVal>(alignment),
83          internal::GCInfoTrait<GCInfoType>::Index(), CustomSpace::kSpaceIndex);
84    }
85  };
86
87  // Fast path for regular allocations for the default space with
88  // `kDefaultAlignment` byte alignment.
89  template <typename GCInfoType>
90  struct AllocationDispatcher<GCInfoType, void,
91                              api_constants::kDefaultAlignment>
92      final {
93    static void* Invoke(AllocationHandle& handle, size_t size) {
94      return MakeGarbageCollectedTraitInternal::Allocate(
95          handle, size, internal::GCInfoTrait<GCInfoType>::Index());
96    }
97  };
98
99  // Default space with >`kDefaultAlignment` byte alignment.
100  template <typename GCInfoType, size_t alignment>
101  struct AllocationDispatcher<GCInfoType, void, alignment> final {
102    static void* Invoke(AllocationHandle& handle, size_t size) {
103      return MakeGarbageCollectedTraitInternal::Allocate(
104          handle, size, static_cast<AlignVal>(alignment),
105          internal::GCInfoTrait<GCInfoType>::Index());
106    }
107  };
108
109  // Custom space with `kDefaultAlignment` byte alignment.
110  template <typename GCInfoType, typename CustomSpace>
111  struct AllocationDispatcher<GCInfoType, CustomSpace,
112                              api_constants::kDefaultAlignment>
113      final {
114    static void* Invoke(AllocationHandle& handle, size_t size) {
115      static_assert(std::is_base_of<CustomSpaceBase, CustomSpace>::value,
116                    "Custom space must inherit from CustomSpaceBase.");
117      return MakeGarbageCollectedTraitInternal::Allocate(
118          handle, size, internal::GCInfoTrait<GCInfoType>::Index(),
119          CustomSpace::kSpaceIndex);
120    }
121  };
122
123 private:
124  static void* CPPGC_DEFAULT_ALIGNED Allocate(cppgc::AllocationHandle&, size_t,
125                                              GCInfoIndex);
126  static void* CPPGC_DOUBLE_WORD_ALIGNED Allocate(cppgc::AllocationHandle&,
127                                                  size_t, AlignVal,
128                                                  GCInfoIndex);
129  static void* CPPGC_DEFAULT_ALIGNED Allocate(cppgc::AllocationHandle&, size_t,
130                                              GCInfoIndex, CustomSpaceIndex);
131  static void* CPPGC_DOUBLE_WORD_ALIGNED Allocate(cppgc::AllocationHandle&,
132                                                  size_t, AlignVal, GCInfoIndex,
133                                                  CustomSpaceIndex);
134
135  friend class HeapObjectHeader;
136};
137
138}  // namespace internal
139
140/**
141 * Base trait that provides utilities for advancers users that have custom
142 * allocation needs (e.g., overriding size). It's expected that users override
143 * MakeGarbageCollectedTrait (see below) and inherit from
144 * MakeGarbageCollectedTraitBase and make use of the low-level primitives
145 * offered to allocate and construct an object.
146 */
147template <typename T>
148class MakeGarbageCollectedTraitBase
149    : private internal::MakeGarbageCollectedTraitInternal {
150 private:
151  static_assert(internal::IsGarbageCollectedType<T>::value,
152                "T needs to be a garbage collected object");
153  static_assert(!IsGarbageCollectedWithMixinTypeV<T> ||
154                    sizeof(T) <=
155                        internal::api_constants::kLargeObjectSizeThreshold,
156                "GarbageCollectedMixin may not be a large object");
157
158 protected:
159  /**
160   * Allocates memory for an object of type T.
161   *
162   * \param handle AllocationHandle identifying the heap to allocate the object
163   *   on.
164   * \param size The size that should be reserved for the object.
165   * \returns the memory to construct an object of type T on.
166   */
167  V8_INLINE static void* Allocate(AllocationHandle& handle, size_t size) {
168    static_assert(
169        std::is_base_of<typename T::ParentMostGarbageCollectedType, T>::value,
170        "U of GarbageCollected<U> must be a base of T. Check "
171        "GarbageCollected<T> base class inheritance.");
172    static constexpr size_t kWantedAlignment =
173        alignof(T) < internal::api_constants::kDefaultAlignment
174            ? internal::api_constants::kDefaultAlignment
175            : alignof(T);
176    static_assert(
177        kWantedAlignment <= internal::api_constants::kMaxSupportedAlignment,
178        "Requested alignment larger than alignof(std::max_align_t) bytes. "
179        "Please file a bug to possibly get this restriction lifted.");
180    return AllocationDispatcher<
181        typename internal::GCInfoFolding<
182            T, typename T::ParentMostGarbageCollectedType>::ResultType,
183        typename SpaceTrait<T>::Space, kWantedAlignment>::Invoke(handle, size);
184  }
185
186  /**
187   * Marks an object as fully constructed, resulting in precise handling by the
188   * garbage collector.
189   *
190   * \param payload The base pointer the object is allocated at.
191   */
192  V8_INLINE static void MarkObjectAsFullyConstructed(const void* payload) {
193    internal::MakeGarbageCollectedTraitInternal::MarkObjectAsFullyConstructed(
194        payload);
195  }
196};
197
198/**
199 * Passed to MakeGarbageCollected to specify how many bytes should be appended
200 * to the allocated object.
201 *
202 * Example:
203 * \code
204 * class InlinedArray final : public GarbageCollected<InlinedArray> {
205 *  public:
206 *   explicit InlinedArray(size_t bytes) : size(bytes), byte_array(this + 1) {}
207 *   void Trace(Visitor*) const {}
208
209 *   size_t size;
210 *   char* byte_array;
211 * };
212 *
213 * auto* inlined_array = MakeGarbageCollected<InlinedArray(
214 *    GetAllocationHandle(), AdditionalBytes(4), 4);
215 * for (size_t i = 0; i < 4; i++) {
216 *   Process(inlined_array->byte_array[i]);
217 * }
218 * \endcode
219 */
220struct AdditionalBytes {
221  constexpr explicit AdditionalBytes(size_t bytes) : value(bytes) {}
222  const size_t value;
223};
224
225/**
226 * Default trait class that specifies how to construct an object of type T.
227 * Advanced users may override how an object is constructed using the utilities
228 * that are provided through MakeGarbageCollectedTraitBase.
229 *
230 * Any trait overriding construction must
231 * - allocate through `MakeGarbageCollectedTraitBase<T>::Allocate`;
232 * - mark the object as fully constructed using
233 *   `MakeGarbageCollectedTraitBase<T>::MarkObjectAsFullyConstructed`;
234 */
235template <typename T>
236class MakeGarbageCollectedTrait : public MakeGarbageCollectedTraitBase<T> {
237 public:
238  template <typename... Args>
239  static T* Call(AllocationHandle& handle, Args&&... args) {
240    void* memory =
241        MakeGarbageCollectedTraitBase<T>::Allocate(handle, sizeof(T));
242    T* object = ::new (memory) T(std::forward<Args>(args)...);
243    MakeGarbageCollectedTraitBase<T>::MarkObjectAsFullyConstructed(object);
244    return object;
245  }
246
247  template <typename... Args>
248  static T* Call(AllocationHandle& handle, AdditionalBytes additional_bytes,
249                 Args&&... args) {
250    void* memory = MakeGarbageCollectedTraitBase<T>::Allocate(
251        handle, sizeof(T) + additional_bytes.value);
252    T* object = ::new (memory) T(std::forward<Args>(args)...);
253    MakeGarbageCollectedTraitBase<T>::MarkObjectAsFullyConstructed(object);
254    return object;
255  }
256};
257
258/**
259 * Allows users to specify a post-construction callback for specific types. The
260 * callback is invoked on the instance of type T right after it has been
261 * constructed. This can be useful when the callback requires a
262 * fully-constructed object to be able to dispatch to virtual methods.
263 */
264template <typename T, typename = void>
265struct PostConstructionCallbackTrait {
266  static void Call(T*) {}
267};
268
269/**
270 * Constructs a managed object of type T where T transitively inherits from
271 * GarbageCollected.
272 *
273 * \param args List of arguments with which an instance of T will be
274 *   constructed.
275 * \returns an instance of type T.
276 */
277template <typename T, typename... Args>
278V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle, Args&&... args) {
279  T* object =
280      MakeGarbageCollectedTrait<T>::Call(handle, std::forward<Args>(args)...);
281  PostConstructionCallbackTrait<T>::Call(object);
282  return object;
283}
284
285/**
286 * Constructs a managed object of type T where T transitively inherits from
287 * GarbageCollected. Created objects will have additional bytes appended to
288 * it. Allocated memory would suffice for `sizeof(T) + additional_bytes`.
289 *
290 * \param additional_bytes Denotes how many bytes to append to T.
291 * \param args List of arguments with which an instance of T will be
292 *   constructed.
293 * \returns an instance of type T.
294 */
295template <typename T, typename... Args>
296V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle,
297                                  AdditionalBytes additional_bytes,
298                                  Args&&... args) {
299  T* object = MakeGarbageCollectedTrait<T>::Call(handle, additional_bytes,
300                                                 std::forward<Args>(args)...);
301  PostConstructionCallbackTrait<T>::Call(object);
302  return object;
303}
304
305}  // namespace cppgc
306
307#undef CPPGC_DEFAULT_ALIGNED
308#undef CPPGC_DOUBLE_WORD_ALIGNED
309
310#endif  // INCLUDE_CPPGC_ALLOCATION_H_
311