diff --git a/packages/android-jsi/LICENSE b/packages/android-jsi/LICENSE new file mode 100644 index 000000000..f6b063884 --- /dev/null +++ b/packages/android-jsi/LICENSE @@ -0,0 +1,3 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ diff --git a/packages/android-jsi/README.md b/packages/android-jsi/README.md new file mode 100644 index 000000000..128ce56dc --- /dev/null +++ b/packages/android-jsi/README.md @@ -0,0 +1,53 @@ +# @nativescript/android-jsi + +Standalone `facebook::jsi::Runtime` (`rnv8::V8Runtime`) package for NativeScript Android. + +This package keeps `facebook::jsi` completely decoupled from the core `@nativescript/android` runtime (`libNativeScript.so`) so apps that do not need JSI incur zero binary size overhead, while apps or plugins that use JSI (such as Nitro Modules or custom C++ `HostObject` bridges) can install it on demand. + +## Installation + +```bash +ns plugin add @nativescript/android-jsi +``` + +## Native C++ / CMake Usage + +In your app or plugin `CMakeLists.txt`, include the `@nativescript/android-jsi` subdirectory or link against `nativescript-jsi`: + +```cmake +add_subdirectory( + "${NODE_MODULES_DIR}/@nativescript/android-jsi/platforms/android/cpp" + "${CMAKE_CURRENT_BINARY_DIR}/nativescript-jsi" +) + +target_link_libraries(my-nitro-module nativescript-jsi) +``` + +Then in C++: + +```cpp +#include "JSIRuntime.h" +#include "jsi/jsi.h" + +void installBindings() { + facebook::jsi::Runtime* rt = NativeScriptJSIRuntime(); + if (!rt) return; + + rt->global().setProperty(*rt, "myConstant", facebook::jsi::Value(42)); +} +``` + +Each V8 isolate (the main UI thread and any NativeScript Worker threads) lazily owns its own `std::shared_ptr` keyed by `v8::Isolate::GetCurrent()`, resolving V8 symbols exported by `libNativeScript.so`. + +## JavaScript / TypeScript Usage + +```ts +import { getJSIRuntimePointer, isJSIRuntimeAvailable } from '@nativescript/android-jsi'; + +console.log('JSI available:', isJSIRuntimeAvailable()); +console.log('JSI Runtime*:', '0x' + getJSIRuntimePointer().toString(16)); +``` + +## License + +Apache-2.0 diff --git a/packages/android-jsi/index.d.ts b/packages/android-jsi/index.d.ts new file mode 100644 index 000000000..b26619883 --- /dev/null +++ b/packages/android-jsi/index.d.ts @@ -0,0 +1,15 @@ +/** + * Returns the native `facebook::jsi::Runtime*` pointer address for the active + * V8 isolate on the calling thread, or `0` if unavailable. + */ +export declare function getJSIRuntimePointer(): number; + +/** + * Returns `true` if a `facebook::jsi::Runtime` is available on the calling thread. + */ +export declare function isJSIRuntimeAvailable(): boolean; + +/** + * Disposes the `facebook::jsi::Runtime` instance bound to the calling thread's V8 isolate. + */ +export declare function disposeJSIRuntimeForCurrentIsolate(): void; diff --git a/packages/android-jsi/index.js b/packages/android-jsi/index.js new file mode 100644 index 000000000..69e2b0547 --- /dev/null +++ b/packages/android-jsi/index.js @@ -0,0 +1,32 @@ +function getJSIRuntimePointer() { + if ( + typeof org !== 'undefined' && + org.nativescript && + org.nativescript.jsi && + org.nativescript.jsi.JSIRuntime + ) { + return Number(org.nativescript.jsi.JSIRuntime.getRuntimePointer()); + } + return 0; +} + +function isJSIRuntimeAvailable() { + return getJSIRuntimePointer() !== 0; +} + +function disposeJSIRuntimeForCurrentIsolate() { + if ( + typeof org !== 'undefined' && + org.nativescript && + org.nativescript.jsi && + org.nativescript.jsi.JSIRuntime + ) { + org.nativescript.jsi.JSIRuntime.disposeRuntimeForCurrentIsolate(); + } +} + +module.exports = { + getJSIRuntimePointer, + isJSIRuntimeAvailable, + disposeJSIRuntimeForCurrentIsolate, +}; diff --git a/packages/android-jsi/package.json b/packages/android-jsi/package.json new file mode 100644 index 000000000..ba46526a1 --- /dev/null +++ b/packages/android-jsi/package.json @@ -0,0 +1,36 @@ +{ + "name": "@nativescript/android-jsi", + "version": "0.1.0", + "description": "Standalone Facebook JSI + V8Runtime bridge for NativeScript Android", + "keywords": [ + "NativeScript", + "Android", + "JSI", + "V8", + "Nitro" + ], + "repository": { + "type": "git", + "url": "https://github.com/NativeScript/runtimes", + "directory": "packages/android-jsi" + }, + "author": { + "name": "NativeScript Team", + "email": "oss@nativescript.org" + }, + "main": "index.js", + "types": "index.d.ts", + "nativescript": { + "platforms": { + "android": "8.0.0" + } + }, + "files": [ + "index.js", + "index.d.ts", + "platforms", + "README.md", + "LICENSE" + ], + "license": "Apache-2.0" +} diff --git a/packages/android-jsi/platforms/android/cpp/CMakeLists.txt b/packages/android-jsi/platforms/android/cpp/CMakeLists.txt new file mode 100644 index 000000000..252065ef9 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.22.1) +project(nativescript_jsi LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Allow overriding V8_INCLUDE_DIR for custom V8 headers +if(NOT DEFINED V8_INCLUDE_DIR) + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/include/v8.h") + set(V8_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") + endif() +endif() + +add_library( + nativescript-jsi + SHARED + jsi/jsi.cpp + jsi/jsilib-posix.cpp + v8runtime/V8Runtime.cpp + v8runtime/V8RuntimeFactory.cpp + v8runtime/V8PointerValue.cpp + v8runtime/JSIV8ValueConverter.cpp + v8runtime/HostProxy.cpp + v8runtime/JSIRuntime.cpp +) + +target_include_directories( + nativescript-jsi + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/v8runtime" +) + +if(DEFINED V8_INCLUDE_DIR AND EXISTS "${V8_INCLUDE_DIR}") + target_include_directories( + nativescript-jsi + PRIVATE + "${V8_INCLUDE_DIR}" + ) +endif() + +target_compile_options( + nativescript-jsi + PRIVATE + -O2 + -fexceptions + -frtti + -fvisibility=default +) + +find_library(android-log-lib log) + +# V8 symbols are exported by libNativeScript.so which is already loaded in the +# Android process by the NativeScript runtime. Allow unresolved shared-library +# symbols at link time when NATIVESCRIPT_SO_PATH is not explicitly provided. +if(DEFINED NATIVESCRIPT_SO_PATH AND EXISTS "${NATIVESCRIPT_SO_PATH}") + add_library(NativeScriptRuntime SHARED IMPORTED) + set_target_properties( + NativeScriptRuntime + PROPERTIES IMPORTED_LOCATION "${NATIVESCRIPT_SO_PATH}" + ) + target_link_libraries( + nativescript-jsi + ${android-log-lib} + NativeScriptRuntime + ) +else() + string(REGEX REPLACE "(^| )-Wl,--no-undefined( |$)" " " CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") + target_link_options( + nativescript-jsi + PRIVATE + "-Wl,--unresolved-symbols=ignore-all" + "-Wl,--allow-shlib-undefined" + ) + target_link_libraries( + nativescript-jsi + ${android-log-lib} + ) +endif() diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/DEPS b/packages/android-jsi/platforms/android/cpp/include/cppgc/DEPS new file mode 100644 index 000000000..2ec7ebbd4 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "-include", + "+v8config.h", + "+v8-platform.h", + "+v8-source-location.h", + "+cppgc", + "-src", + "+libplatform/libplatform.h", +] diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/OWNERS b/packages/android-jsi/platforms/android/cpp/include/cppgc/OWNERS new file mode 100644 index 000000000..6ccabf60b --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/OWNERS @@ -0,0 +1,2 @@ +bikineev@chromium.org +omerkatz@chromium.org \ No newline at end of file diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/README.md b/packages/android-jsi/platforms/android/cpp/include/cppgc/README.md new file mode 100644 index 000000000..d825ea5bd --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/README.md @@ -0,0 +1,135 @@ +# Oilpan: C++ Garbage Collection + +Oilpan is an open-source garbage collection library for C++ that can be used stand-alone or in collaboration with V8's JavaScript garbage collector. +Oilpan implements mark-and-sweep garbage collection (GC) with limited compaction (for a subset of objects). + +**Key properties** + +- Trace-based garbage collection; +- Incremental and concurrent marking; +- Incremental and concurrent sweeping; +- Precise on-heap memory layout; +- Conservative on-stack memory layout; +- Allows for collection with and without considering stack; +- Non-incremental and non-concurrent compaction for selected spaces; + +See the [Hello World](https://chromium.googlesource.com/v8/v8/+/main/samples/cppgc/hello-world.cc) example on how to get started using Oilpan to manage C++ code. + +Oilpan follows V8's project organization, see e.g. on how we accept [contributions](https://v8.dev/docs/contribute) and [provide a stable API](https://v8.dev/docs/api). + +## Threading model + +Oilpan features thread-local garbage collection and assumes heaps are not shared among threads. +In other words, objects are accessed and ultimately reclaimed by the garbage collector on the same thread that allocates them. +This allows Oilpan to run garbage collection in parallel with mutators running in other threads. + +References to objects belonging to another thread's heap are modeled using cross-thread roots. +This is even true for on-heap to on-heap references. + +Oilpan heaps may generally not be accessed from different threads unless otherwise noted. + +## Heap partitioning + +Oilpan's heaps are partitioned into spaces. +The space for an object is chosen depending on a number of criteria, e.g.: + +- Objects over 64KiB are allocated in a large object space +- Objects can be assigned to a dedicated custom space. + Custom spaces can also be marked as compactable. +- Other objects are allocated in one of the normal page spaces bucketed depending on their size. + +## Precise and conservative garbage collection + +Oilpan supports two kinds of GCs: + +1. **Conservative GC.** +A GC is called conservative when it is executed while the regular native stack is not empty. +In this case, the native stack might contain references to objects in Oilpan's heap, which should be kept alive. +The GC scans the native stack and treats the pointers discovered via the native stack as part of the root set. +This kind of GC is considered imprecise because values on stack other than references may accidentally appear as references to on-heap object, which means these objects will be kept alive despite being in practice unreachable from the application as an actual reference. + +2. **Precise GC.** +A precise GC is triggered at the end of an event loop, which is controlled by an embedder via a platform. +At this point, it is guaranteed that there are no on-stack references pointing to Oilpan's heap. +This means there is no risk of confusing other value types with references. +Oilpan has precise knowledge of on-heap object layouts, and so it knows exactly where pointers lie in memory. +Oilpan can just start marking from the regular root set and collect all garbage precisely. + +## Atomic, incremental and concurrent garbage collection + +Oilpan has three modes of operation: + +1. **Atomic GC.** +The entire GC cycle, including all its phases (e.g. see [Marking](#Marking-phase) and [Sweeping](#Sweeping-phase)), are executed back to back in a single pause. +This mode of operation is also known as Stop-The-World (STW) garbage collection. +It results in the most jank (due to a single long pause), but is overall the most efficient (e.g. no need for write barriers). + +2. **Incremental GC.** +Garbage collection work is split up into multiple steps which are interleaved with the mutator, i.e. user code chunked into tasks. +Each step is a small chunk of work that is executed either as dedicated tasks between mutator tasks or, as needed, during mutator tasks. +Using incremental GC introduces the need for write barriers that record changes to the object graph so that a consistent state is observed and no objects are accidentally considered dead and reclaimed. +The incremental steps are followed by a smaller atomic pause to finalize garbage collection. +The smaller pause times, due to smaller chunks of work, helps with reducing jank. + +3. **Concurrent GC.** +This is the most common type of GC. +It builds on top of incremental GC and offloads much of the garbage collection work away from the mutator thread and on to background threads. +Using concurrent GC allows the mutator thread to spend less time on GC and more on the actual mutator. + +## Marking phase + +The marking phase consists of the following steps: + +1. Mark all objects in the root set. + +2. Mark all objects transitively reachable from the root set by calling `Trace()` methods defined on each object. + +3. Clear out all weak handles to unreachable objects and run weak callbacks. + +The marking phase can be executed atomically in a stop-the-world manner, in which all 3 steps are executed one after the other. + +Alternatively, it can also be executed incrementally/concurrently. +With incremental/concurrent marking, step 1 is executed in a short pause after which the mutator regains control. +Step 2 is repeatedly executed in an interleaved manner with the mutator. +When the GC is ready to finalize, i.e. step 2 is (almost) finished, another short pause is triggered in which step 2 is finished and step 3 is performed. + +To prevent a user-after-free (UAF) issues it is required for Oilpan to know about all edges in the object graph. +This means that all pointers except on-stack pointers must be wrapped with Oilpan's handles (i.e., Persistent<>, Member<>, WeakMember<>). +Raw pointers to on-heap objects create an edge that Oilpan cannot observe and cause UAF issues +Thus, raw pointers shall not be used to reference on-heap objects (except for raw pointers on native stacks). + +## Sweeping phase + +The sweeping phase consists of the following steps: + +1. Invoke pre-finalizers. +At this point, no destructors have been invoked and no memory has been reclaimed. +Pre-finalizers are allowed to access any other on-heap objects, even those that may get destructed. + +2. Sweeping invokes destructors of the dead (unreachable) objects and reclaims memory to be reused by future allocations. + +Assumptions should not be made about the order and the timing of their execution. +There is no guarantee on the order in which the destructors are invoked. +That's why destructors must not access any other on-heap objects (which might have already been destructed). +If some destructor unavoidably needs to access other on-heap objects, it will have to be converted to a pre-finalizer. +The pre-finalizer is allowed to access other on-heap objects. + +The mutator is resumed before all destructors have ran. +For example, imagine a case where X is a client of Y, and Y holds a list of clients. +If the code relies on X's destructor removing X from the list, there is a risk that Y iterates the list and calls some method of X which may touch other on-heap objects. +This causes a use-after-free. +Care must be taken to make sure that X is explicitly removed from the list before the mutator resumes its execution in a way that doesn't rely on X's destructor (e.g. a pre-finalizer). + +Similar to marking, sweeping can be executed in either an atomic stop-the-world manner or incrementally/concurrently. +With incremental/concurrent sweeping, step 2 is interleaved with mutator. +Incremental/concurrent sweeping can be atomically finalized in case it is needed to trigger another GC cycle. +Even with concurrent sweeping, destructors are guaranteed to run on the thread the object has been allocated on to preserve C++ semantics. + +Notes: + +* Weak processing runs only when the holder object of the WeakMember outlives the pointed object. +If the holder object and the pointed object die at the same time, weak processing doesn't run. +It is wrong to write code assuming that the weak processing always runs. + +* Pre-finalizers are heavy because the thread needs to scan all pre-finalizers at each sweeping phase to determine which pre-finalizers should be invoked (the thread needs to invoke pre-finalizers of dead objects). +Adding pre-finalizers to frequently created objects should be avoided. diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/allocation.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/allocation.h new file mode 100644 index 000000000..450db0047 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/allocation.h @@ -0,0 +1,307 @@ +// Copyright 2020 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 INCLUDE_CPPGC_ALLOCATION_H_ +#define INCLUDE_CPPGC_ALLOCATION_H_ + +#include +#include +#include +#include +#include +#include + +#include "cppgc/custom-space.h" +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/gc-info.h" +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +#if defined(__has_attribute) +#if __has_attribute(assume_aligned) +#define CPPGC_DEFAULT_ALIGNED \ + __attribute__((assume_aligned(api_constants::kDefaultAlignment))) +#define CPPGC_DOUBLE_WORD_ALIGNED \ + __attribute__((assume_aligned(2 * api_constants::kDefaultAlignment))) +#endif // __has_attribute(assume_aligned) +#endif // defined(__has_attribute) + +#if !defined(CPPGC_DEFAULT_ALIGNED) +#define CPPGC_DEFAULT_ALIGNED +#endif + +#if !defined(CPPGC_DOUBLE_WORD_ALIGNED) +#define CPPGC_DOUBLE_WORD_ALIGNED +#endif + +namespace cppgc { + +/** + * AllocationHandle is used to allocate garbage-collected objects. + */ +class AllocationHandle; + +namespace internal { + +using AlignVal = std::align_val_t; + +class MakeGarbageCollectedTraitInternal { + protected: + static inline void MarkObjectAsFullyConstructed(const void* payload) { + // See api_constants for an explanation of the constants. + std::atomic_ref atomic_mutable_bitfield( + *const_cast(reinterpret_cast( + reinterpret_cast(payload) - + api_constants::kFullyConstructedBitFieldOffsetFromPayload))); + // It's safe to split use load+store here (instead of a read-modify-write + // operation), since it's guaranteed that this 16-bit bitfield is only + // modified by a single thread. This is cheaper in terms of code bloat (on + // ARM) and performance. + uint16_t value = atomic_mutable_bitfield.load(std::memory_order_relaxed); + value |= api_constants::kFullyConstructedBitMask; + atomic_mutable_bitfield.store(value, std::memory_order_release); + } + + // Dispatch based on compile-time information. + // + // Default implementation is for a custom space with >`kDefaultAlignment` byte + // alignment. + template + struct AllocationDispatcher final { + static void* Invoke(AllocationHandle& handle, size_t size) { + static_assert(std::is_base_of_v, + "Custom space must inherit from CustomSpaceBase."); + static_assert( + !CustomSpace::kSupportsCompaction, + "Custom spaces that support compaction do not support allocating " + "objects with non-default (i.e. word-sized) alignment."); + return MakeGarbageCollectedTraitInternal::Allocate( + handle, size, static_cast(alignment), + internal::GCInfoTrait::Index(), CustomSpace::kSpaceIndex); + } + }; + + // Fast path for regular allocations for the default space with + // `kDefaultAlignment` byte alignment. + template + struct AllocationDispatcher + final { + static void* Invoke(AllocationHandle& handle, size_t size) { + return MakeGarbageCollectedTraitInternal::Allocate( + handle, size, internal::GCInfoTrait::Index()); + } + }; + + // Default space with >`kDefaultAlignment` byte alignment. + template + struct AllocationDispatcher final { + static void* Invoke(AllocationHandle& handle, size_t size) { + return MakeGarbageCollectedTraitInternal::Allocate( + handle, size, static_cast(alignment), + internal::GCInfoTrait::Index()); + } + }; + + // Custom space with `kDefaultAlignment` byte alignment. + template + struct AllocationDispatcher + final { + static void* Invoke(AllocationHandle& handle, size_t size) { + static_assert(std::is_base_of_v, + "Custom space must inherit from CustomSpaceBase."); + return MakeGarbageCollectedTraitInternal::Allocate( + handle, size, internal::GCInfoTrait::Index(), + CustomSpace::kSpaceIndex); + } + }; + + private: + V8_EXPORT static void* CPPGC_DEFAULT_ALIGNED + Allocate(cppgc::AllocationHandle&, size_t, GCInfoIndex); + V8_EXPORT static void* CPPGC_DOUBLE_WORD_ALIGNED + Allocate(cppgc::AllocationHandle&, size_t, AlignVal, GCInfoIndex); + V8_EXPORT static void* CPPGC_DEFAULT_ALIGNED + Allocate(cppgc::AllocationHandle&, size_t, GCInfoIndex, CustomSpaceIndex); + V8_EXPORT static void* CPPGC_DOUBLE_WORD_ALIGNED + Allocate(cppgc::AllocationHandle&, size_t, AlignVal, GCInfoIndex, + CustomSpaceIndex); + + friend class HeapObjectHeader; +}; + +} // namespace internal + +/** + * Base trait that provides utilities for advancers users that have custom + * allocation needs (e.g., overriding size). It's expected that users override + * MakeGarbageCollectedTrait (see below) and inherit from + * MakeGarbageCollectedTraitBase and make use of the low-level primitives + * offered to allocate and construct an object. + */ +template +class MakeGarbageCollectedTraitBase + : private internal::MakeGarbageCollectedTraitInternal { + private: + static_assert(internal::IsGarbageCollectedType::value, + "T needs to be a garbage collected object"); + static_assert(!IsGarbageCollectedWithMixinTypeV || + sizeof(T) <= + internal::api_constants::kLargeObjectSizeThreshold, + "GarbageCollectedMixin may not be a large object"); + + protected: + /** + * Allocates memory for an object of type T. + * + * \param handle AllocationHandle identifying the heap to allocate the object + * on. + * \param size The size that should be reserved for the object. + * \returns the memory to construct an object of type T on. + */ + V8_INLINE static void* Allocate(AllocationHandle& handle, size_t size) { + static_assert( + std::is_base_of_v, + "U of GarbageCollected must be a base of T. Check " + "GarbageCollected base class inheritance."); + static constexpr size_t kWantedAlignment = + alignof(T) < internal::api_constants::kDefaultAlignment + ? internal::api_constants::kDefaultAlignment + : alignof(T); + static_assert( + kWantedAlignment <= internal::api_constants::kMaxSupportedAlignment, + "Requested alignment larger than alignof(std::max_align_t) bytes. " + "Please file a bug to possibly get this restriction lifted."); + return AllocationDispatcher< + typename internal::GCInfoFolding< + T, typename T::ParentMostGarbageCollectedType>::ResultType, + typename SpaceTrait::Space, kWantedAlignment>::Invoke(handle, size); + } + + /** + * Marks an object as fully constructed, resulting in precise handling by the + * garbage collector. + * + * \param payload The base pointer the object is allocated at. + */ + V8_INLINE static void MarkObjectAsFullyConstructed(const void* payload) { + internal::MakeGarbageCollectedTraitInternal::MarkObjectAsFullyConstructed( + payload); + } +}; + +/** + * Passed to MakeGarbageCollected to specify how many bytes should be appended + * to the allocated object. + * + * Example: + * \code + * class InlinedArray final : public GarbageCollected { + * public: + * explicit InlinedArray(size_t bytes) : size(bytes), byte_array(this + 1) {} + * void Trace(Visitor*) const {} + + * size_t size; + * char* byte_array; + * }; + * + * auto* inlined_array = MakeGarbageCollectedbyte_array[i]); + * } + * \endcode + */ +struct AdditionalBytes { + constexpr explicit AdditionalBytes(size_t bytes) : value(bytes) {} + const size_t value; +}; + +/** + * Default trait class that specifies how to construct an object of type T. + * Advanced users may override how an object is constructed using the utilities + * that are provided through MakeGarbageCollectedTraitBase. + * + * Any trait overriding construction must + * - allocate through `MakeGarbageCollectedTraitBase::Allocate`; + * - mark the object as fully constructed using + * `MakeGarbageCollectedTraitBase::MarkObjectAsFullyConstructed`; + */ +template +class MakeGarbageCollectedTrait : public MakeGarbageCollectedTraitBase { + public: + template + static T* Call(AllocationHandle& handle, Args&&... args) { + void* memory = + MakeGarbageCollectedTraitBase::Allocate(handle, sizeof(T)); + T* object = ::new (memory) T(std::forward(args)...); + MakeGarbageCollectedTraitBase::MarkObjectAsFullyConstructed(object); + return object; + } + + template + static T* Call(AllocationHandle& handle, AdditionalBytes additional_bytes, + Args&&... args) { + void* memory = MakeGarbageCollectedTraitBase::Allocate( + handle, sizeof(T) + additional_bytes.value); + T* object = ::new (memory) T(std::forward(args)...); + MakeGarbageCollectedTraitBase::MarkObjectAsFullyConstructed(object); + return object; + } +}; + +/** + * Allows users to specify a post-construction callback for specific types. The + * callback is invoked on the instance of type T right after it has been + * constructed. This can be useful when the callback requires a + * fully-constructed object to be able to dispatch to virtual methods. + */ +template +struct PostConstructionCallbackTrait { + static void Call(T*) {} +}; + +/** + * Constructs a managed object of type T where T transitively inherits from + * GarbageCollected. + * + * \param args List of arguments with which an instance of T will be + * constructed. + * \returns an instance of type T. + */ +template +V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle, Args&&... args) { + T* object = + MakeGarbageCollectedTrait::Call(handle, std::forward(args)...); + PostConstructionCallbackTrait::Call(object); + return object; +} + +/** + * Constructs a managed object of type T where T transitively inherits from + * GarbageCollected. Created objects will have additional bytes appended to + * it. Allocated memory would suffice for `sizeof(T) + additional_bytes`. + * + * \param additional_bytes Denotes how many bytes to append to T. + * \param args List of arguments with which an instance of T will be + * constructed. + * \returns an instance of type T. + */ +template +V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle, + AdditionalBytes additional_bytes, + Args&&... args) { + T* object = MakeGarbageCollectedTrait::Call(handle, additional_bytes, + std::forward(args)...); + PostConstructionCallbackTrait::Call(object); + return object; +} + +} // namespace cppgc + +#undef CPPGC_DEFAULT_ALIGNED +#undef CPPGC_DOUBLE_WORD_ALIGNED + +#endif // INCLUDE_CPPGC_ALLOCATION_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/common.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/common.h new file mode 100644 index 000000000..961038360 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/common.h @@ -0,0 +1,28 @@ +// Copyright 2020 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 INCLUDE_CPPGC_COMMON_H_ +#define INCLUDE_CPPGC_COMMON_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +/** + * Indicator for the stack state of the embedder. + */ +enum class EmbedderStackState { + /** + * Stack may contain interesting heap pointers. + */ + kMayContainHeapPointers, + /** + * Stack does not contain any interesting heap pointers. + */ + kNoHeapPointers, +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_COMMON_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/cross-thread-persistent.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/cross-thread-persistent.h new file mode 100644 index 000000000..b389eb582 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/cross-thread-persistent.h @@ -0,0 +1,462 @@ +// Copyright 2020 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 INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ +#define INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ + +#include + +#include "cppgc/internal/persistent-node.h" +#include "cppgc/internal/pointer-policies.h" +#include "cppgc/persistent.h" +#include "cppgc/visitor.h" + +namespace cppgc { +namespace internal { + +// Wrapper around PersistentBase that allows accessing poisoned memory when +// using ASAN. This is needed as the GC of the heap that owns the value +// of a CTP, may clear it (heap termination, weakness) while the object +// holding the CTP may be poisoned as itself may be deemed dead. +class CrossThreadPersistentBase : public PersistentBase { + public: + CrossThreadPersistentBase() = default; + explicit CrossThreadPersistentBase(const void* raw) : PersistentBase(raw) {} + + V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const { + return raw_; + } + + V8_CLANG_NO_SANITIZE("address") + PersistentNode* GetNodeFromGC() const { return node_; } + + V8_CLANG_NO_SANITIZE("address") + void ClearFromGC() const { + raw_ = nullptr; + SetNodeSafe(nullptr); + } + + // GetNodeSafe() can be used for a thread-safe IsValid() check in a + // double-checked locking pattern. See ~BasicCrossThreadPersistent. + PersistentNode* GetNodeSafe() const { + return reinterpret_cast*>(&node_)->load( + std::memory_order_acquire); + } + + // The GC writes using SetNodeSafe() while holding the lock. + V8_CLANG_NO_SANITIZE("address") + void SetNodeSafe(PersistentNode* value) const { +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define V8_IS_ASAN 1 +#endif +#endif + +#ifdef V8_IS_ASAN + __atomic_store(&node_, &value, __ATOMIC_RELEASE); +#else // !V8_IS_ASAN + // Non-ASAN builds can use atomics. This also covers MSVC which does not + // have the __atomic_store intrinsic. + reinterpret_cast*>(&node_)->store( + value, std::memory_order_release); +#endif // !V8_IS_ASAN + +#undef V8_IS_ASAN + } +}; + +template +class BasicCrossThreadPersistent final : public CrossThreadPersistentBase, + public LocationPolicy, + private WeaknessPolicy, + private CheckingPolicy { + public: + using typename WeaknessPolicy::IsStrongPersistent; + using PointeeType = T; + + ~BasicCrossThreadPersistent() { + // This implements fast path for destroying empty/sentinel. + // + // Simplified version of `AssignUnsafe()` to allow calling without a + // complete type `T`. Uses double-checked locking with a simple thread-safe + // check for a valid handle based on a node. + if (GetNodeSafe()) { + PersistentRegionLock guard; + const void* old_value = GetValue(); + // The fast path check (GetNodeSafe()) does not acquire the lock. Recheck + // validity while holding the lock to ensure the reference has not been + // cleared. + if (IsValid(old_value)) { + CrossThreadPersistentRegion& region = + this->GetPersistentRegion(old_value); + region.FreeNode(GetNode()); + SetNode(nullptr); + } else { + CPPGC_DCHECK(!GetNode()); + } + } + // No need to call SetValue() as the handle is not used anymore. This can + // leave behind stale sentinel values but will always destroy the underlying + // node. + } + + BasicCrossThreadPersistent(SourceLocation loc = SourceLocation::Current()) + : LocationPolicy(loc) {} + + BasicCrossThreadPersistent(std::nullptr_t, + SourceLocation loc = SourceLocation::Current()) + : LocationPolicy(loc) {} + + BasicCrossThreadPersistent(SentinelPointer s, + SourceLocation loc = SourceLocation::Current()) + : CrossThreadPersistentBase(s), LocationPolicy(loc) {} + + BasicCrossThreadPersistent(T* raw, + SourceLocation loc = SourceLocation::Current()) + : CrossThreadPersistentBase(raw), LocationPolicy(loc) { + if (!IsValid(raw)) return; + PersistentRegionLock guard; + CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); + SetNode(region.AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(raw); + } + + class UnsafeCtorTag { + private: + UnsafeCtorTag() = default; + template + friend class BasicCrossThreadPersistent; + }; + + BasicCrossThreadPersistent(UnsafeCtorTag, T* raw, + SourceLocation loc = SourceLocation::Current()) + : CrossThreadPersistentBase(raw), LocationPolicy(loc) { + if (!IsValid(raw)) return; + CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw); + SetNode(region.AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(raw); + } + + BasicCrossThreadPersistent(T& raw, + SourceLocation loc = SourceLocation::Current()) + : BasicCrossThreadPersistent(&raw, loc) {} + + template >> + BasicCrossThreadPersistent( + internal::BasicMember + member, + SourceLocation loc = SourceLocation::Current()) + : BasicCrossThreadPersistent(member.Get(), loc) {} + + BasicCrossThreadPersistent(const BasicCrossThreadPersistent& other, + SourceLocation loc = SourceLocation::Current()) + : BasicCrossThreadPersistent(loc) { + // Invoke operator=. + *this = other; + } + + // Heterogeneous ctor. + template >> + BasicCrossThreadPersistent(const BasicCrossThreadPersistent< + U, OtherWeaknessPolicy, OtherLocationPolicy, + OtherCheckingPolicy>& other, + SourceLocation loc = SourceLocation::Current()) + : BasicCrossThreadPersistent(loc) { + *this = other; + } + + BasicCrossThreadPersistent( + BasicCrossThreadPersistent&& other, + SourceLocation loc = SourceLocation::Current()) noexcept { + // Invoke operator=. + *this = std::move(other); + } + + BasicCrossThreadPersistent& operator=( + const BasicCrossThreadPersistent& other) { + PersistentRegionLock guard; + AssignSafe(guard, other.Get()); + return *this; + } + + template >> + BasicCrossThreadPersistent& operator=( + const BasicCrossThreadPersistent& other) { + PersistentRegionLock guard; + AssignSafe(guard, other.Get()); + return *this; + } + + BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) { + if (this == &other) return *this; + Clear(); + PersistentRegionLock guard; + PersistentBase::operator=(std::move(other)); + LocationPolicy::operator=(std::move(other)); + if (!IsValid(GetValue())) return *this; + GetNode()->UpdateOwner(this); + other.SetValue(nullptr); + other.SetNode(nullptr); + this->CheckPointer(Get()); + return *this; + } + + /** + * Assigns a raw pointer. + * + * Note: **Not thread-safe.** + */ + BasicCrossThreadPersistent& operator=(T* other) { + AssignUnsafe(other); + return *this; + } + + // Assignment from member. + template >> + BasicCrossThreadPersistent& operator=( + internal::BasicMember + member) { + return operator=(member.Get()); + } + + /** + * Assigns a nullptr. + * + * \returns the handle. + */ + BasicCrossThreadPersistent& operator=(std::nullptr_t) { + Clear(); + return *this; + } + + /** + * Assigns the sentinel pointer. + * + * \returns the handle. + */ + BasicCrossThreadPersistent& operator=(SentinelPointer s) { + PersistentRegionLock guard; + AssignSafe(guard, s); + return *this; + } + + /** + * Returns a pointer to the stored object. + * + * Note: **Not thread-safe.** + * + * \returns a pointer to the stored object. + */ + // CFI cast exemption to allow passing SentinelPointer through T* and support + // heterogeneous assignments between different Member and Persistent handles + // based on their actual types. + V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { + return static_cast(const_cast(GetValue())); + } + + /** + * Clears the stored object. + */ + void Clear() { + PersistentRegionLock guard; + AssignSafe(guard, nullptr); + } + + /** + * Returns a pointer to the stored object and releases it. + * + * Note: **Not thread-safe.** + * + * \returns a pointer to the stored object. + */ + T* Release() { + T* result = Get(); + Clear(); + return result; + } + + /** + * Conversio to boolean. + * + * Note: **Not thread-safe.** + * + * \returns true if an actual object has been stored and false otherwise. + */ + explicit operator bool() const { return Get(); } + + /** + * Conversion to object of type T. + * + * Note: **Not thread-safe.** + * + * \returns the object. + */ + operator T*() const { return Get(); } + + /** + * Dereferences the stored object. + * + * Note: **Not thread-safe.** + */ + T* operator->() const { return Get(); } + T& operator*() const { return *Get(); } + + template + BasicCrossThreadPersistent + To() const { + using OtherBasicCrossThreadPersistent = + BasicCrossThreadPersistent; + PersistentRegionLock guard; + return OtherBasicCrossThreadPersistent( + typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(), + static_cast(Get())); + } + + template ::IsStrongPersistent::value>> + BasicCrossThreadPersistent + Lock() const { + return BasicCrossThreadPersistent< + U, internal::StrongCrossThreadPersistentPolicy>(*this); + } + + private: + static bool IsValid(const void* ptr) { + return ptr && ptr != kSentinelPointer; + } + + static void TraceAsRoot(RootVisitor& root_visitor, const void* ptr) { + root_visitor.Trace(*static_cast(ptr)); + } + + void AssignUnsafe(T* ptr) { + const void* old_value = GetValue(); + if (IsValid(old_value)) { + PersistentRegionLock guard; + old_value = GetValue(); + // The fast path check (IsValid()) does not acquire the lock. Reload + // the value to ensure the reference has not been cleared. + if (IsValid(old_value)) { + CrossThreadPersistentRegion& region = + this->GetPersistentRegion(old_value); + if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { + SetValue(ptr); + this->CheckPointer(ptr); + return; + } + region.FreeNode(GetNode()); + SetNode(nullptr); + } else { + CPPGC_DCHECK(!GetNode()); + } + } + SetValue(ptr); + if (!IsValid(ptr)) return; + PersistentRegionLock guard; + SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(ptr); + } + + void AssignSafe(PersistentRegionLock&, T* ptr) { + PersistentRegionLock::AssertLocked(); + const void* old_value = GetValue(); + if (IsValid(old_value)) { + CrossThreadPersistentRegion& region = + this->GetPersistentRegion(old_value); + if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) { + SetValue(ptr); + this->CheckPointer(ptr); + return; + } + region.FreeNode(GetNode()); + SetNode(nullptr); + } + SetValue(ptr); + if (!IsValid(ptr)) return; + SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(ptr); + } + + void ClearFromGC() const { + if (IsValid(GetValueFromGC())) { + WeaknessPolicy::GetPersistentRegion(GetValueFromGC()) + .FreeNode(GetNodeFromGC()); + CrossThreadPersistentBase::ClearFromGC(); + } + } + + // See Get() for details. + V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") + T* GetFromGC() const { + return static_cast(const_cast(GetValueFromGC())); + } + + friend class internal::RootVisitor; +}; + +template +struct IsWeak< + BasicCrossThreadPersistent> + : std::true_type {}; + +} // namespace internal + +namespace subtle { + +/** + * **DO NOT USE: Has known caveats, see below.** + * + * CrossThreadPersistent allows retaining objects from threads other than the + * thread the owning heap is operating on. + * + * Known caveats: + * - Does not protect the heap owning an object from terminating. + * - Reaching transitively through the graph is unsupported as objects may be + * moved concurrently on the thread owning the object. + */ +template +using CrossThreadPersistent = internal::BasicCrossThreadPersistent< + T, internal::StrongCrossThreadPersistentPolicy>; + +/** + * **DO NOT USE: Has known caveats, see below.** + * + * CrossThreadPersistent allows weakly retaining objects from threads other than + * the thread the owning heap is operating on. + * + * Known caveats: + * - Does not protect the heap owning an object from terminating. + * - Reaching transitively through the graph is unsupported as objects may be + * moved concurrently on the thread owning the object. + */ +template +using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent< + T, internal::WeakCrossThreadPersistentPolicy>; + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/custom-space.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/custom-space.h new file mode 100644 index 000000000..757c4fde1 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/custom-space.h @@ -0,0 +1,97 @@ +// Copyright 2020 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 INCLUDE_CPPGC_CUSTOM_SPACE_H_ +#define INCLUDE_CPPGC_CUSTOM_SPACE_H_ + +#include + +namespace cppgc { + +/** + * Index identifying a custom space. + */ +struct CustomSpaceIndex { + constexpr CustomSpaceIndex(size_t value) : value(value) {} // NOLINT + size_t value; +}; + +/** + * Top-level base class for custom spaces. Users must inherit from CustomSpace + * below. + */ +class CustomSpaceBase { + public: + virtual ~CustomSpaceBase() = default; + virtual CustomSpaceIndex GetCustomSpaceIndex() const = 0; + virtual bool IsCompactable() const = 0; +}; + +/** + * Base class custom spaces should directly inherit from. The class inheriting + * from `CustomSpace` must define `kSpaceIndex` as unique space index. These + * indices need for form a sequence starting at 0. + * + * Example: + * \code + * class CustomSpace1 : public CustomSpace { + * public: + * static constexpr CustomSpaceIndex kSpaceIndex = 0; + * }; + * class CustomSpace2 : public CustomSpace { + * public: + * static constexpr CustomSpaceIndex kSpaceIndex = 1; + * }; + * \endcode + */ +template +class CustomSpace : public CustomSpaceBase { + public: + /** + * Compaction is only supported on spaces that manually manage slots + * recording. + */ + static constexpr bool kSupportsCompaction = false; + + CustomSpaceIndex GetCustomSpaceIndex() const final { + return ConcreteCustomSpace::kSpaceIndex; + } + bool IsCompactable() const final { + return ConcreteCustomSpace::kSupportsCompaction; + } +}; + +/** + * User-overridable trait that allows pinning types to custom spaces. + */ +template +struct SpaceTrait { + using Space = void; +}; + +namespace internal { + +template +struct IsAllocatedOnCompactableSpaceImpl { + static constexpr bool value = CustomSpace::kSupportsCompaction; +}; + +template <> +struct IsAllocatedOnCompactableSpaceImpl { + // Non-custom spaces are by default not compactable. + static constexpr bool value = false; +}; + +template +struct IsAllocatedOnCompactableSpace { + public: + static constexpr bool value = + IsAllocatedOnCompactableSpaceImpl::Space>::value; +}; + +} // namespace internal + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_CUSTOM_SPACE_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/default-platform.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/default-platform.h new file mode 100644 index 000000000..07ce55bbf --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/default-platform.h @@ -0,0 +1,68 @@ +// Copyright 2020 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 INCLUDE_CPPGC_DEFAULT_PLATFORM_H_ +#define INCLUDE_CPPGC_DEFAULT_PLATFORM_H_ + +#include + +#include "cppgc/platform.h" +#include "libplatform/libplatform.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +/** + * Platform provided by cppgc. Uses V8's DefaultPlatform provided by + * libplatform internally. Exception: `GetForegroundTaskRunner()`, see below. + */ +class V8_EXPORT DefaultPlatform : public Platform { + public: + using IdleTaskSupport = v8::platform::IdleTaskSupport; + explicit DefaultPlatform( + int thread_pool_size = 0, + IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled, + std::unique_ptr tracing_controller = {}) + : v8_platform_(v8::platform::NewDefaultPlatform( + thread_pool_size, idle_task_support, + v8::platform::InProcessStackDumping::kDisabled, + std::move(tracing_controller))) {} + + cppgc::PageAllocator* GetPageAllocator() override { + return v8_platform_->GetPageAllocator(); + } + + double MonotonicallyIncreasingTime() override { + return v8_platform_->MonotonicallyIncreasingTime(); + } + + std::shared_ptr GetForegroundTaskRunner( + TaskPriority priority) override { + // V8's default platform creates a new task runner when passed the + // `v8::Isolate` pointer the first time. For non-default platforms this will + // require getting the appropriate task runner. + return v8_platform_->GetForegroundTaskRunner(kNoIsolate, priority); + } + + std::unique_ptr PostJob( + cppgc::TaskPriority priority, + std::unique_ptr job_task) override { + return v8_platform_->PostJob(priority, std::move(job_task)); + } + + TracingController* GetTracingController() override { + return v8_platform_->GetTracingController(); + } + + v8::Platform* GetV8Platform() const { return v8_platform_.get(); } + + protected: + static constexpr v8::Isolate* kNoIsolate = nullptr; + + std::unique_ptr v8_platform_; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_DEFAULT_PLATFORM_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/explicit-management.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/explicit-management.h new file mode 100644 index 000000000..0290328dc --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/explicit-management.h @@ -0,0 +1,100 @@ +// Copyright 2021 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 INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ +#define INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ + +#include + +#include "cppgc/allocation.h" +#include "cppgc/internal/logging.h" +#include "cppgc/type-traits.h" + +namespace cppgc { + +class HeapHandle; + +namespace subtle { + +template +void FreeUnreferencedObject(HeapHandle& heap_handle, T& object); +template +bool Resize(T& object, AdditionalBytes additional_bytes); + +} // namespace subtle + +namespace internal { + +class ExplicitManagementImpl final { + private: + V8_EXPORT static void FreeUnreferencedObject(HeapHandle&, void*); + V8_EXPORT static bool Resize(void*, size_t); + + template + friend void subtle::FreeUnreferencedObject(HeapHandle&, T&); + template + friend bool subtle::Resize(T&, AdditionalBytes); +}; +} // namespace internal + +namespace subtle { + +/** + * Informs the garbage collector that `object` can be immediately reclaimed. The + * destructor may not be invoked immediately but only on next garbage + * collection. + * + * It is up to the embedder to guarantee that no other object holds a reference + * to `object` after calling `FreeUnreferencedObject()`. In case such a + * reference exists, it's use results in a use-after-free. + * + * To aid in using the API, `FreeUnreferencedObject()` may be called from + * destructors on objects that would be reclaimed in the same garbage collection + * cycle. + * + * \param heap_handle The corresponding heap. + * \param object Reference to an object that is of type `GarbageCollected` and + * should be immediately reclaimed. + */ +template +void FreeUnreferencedObject(HeapHandle& heap_handle, T& object) { + static_assert(IsGarbageCollectedTypeV, + "Object must be of type GarbageCollected."); + internal::ExplicitManagementImpl::FreeUnreferencedObject(heap_handle, + &object); +} + +/** + * Tries to resize `object` of type `T` with additional bytes on top of + * sizeof(T). Resizing is only useful with trailing inlined storage, see e.g. + * `MakeGarbageCollected(AllocationHandle&, AdditionalBytes)`. + * + * `Resize()` performs growing or shrinking as needed and may skip the operation + * for internal reasons, see return value. + * + * It is up to the embedder to guarantee that in case of shrinking a larger + * object down, the reclaimed area is not used anymore. Any subsequent use + * results in a use-after-free. + * + * The `object` must be live when calling `Resize()`. + * + * \param object Reference to an object that is of type `GarbageCollected` and + * should be resized. + * \param additional_bytes Bytes in addition to sizeof(T) that the object should + * provide. + * \returns true when the operation was successful and the result can be relied + * on, and false otherwise. + */ +template +bool Resize(T& object, AdditionalBytes additional_bytes) { + static_assert(IsGarbageCollectedTypeV, + "Object must be of type GarbageCollected."); + return internal::ExplicitManagementImpl::Resize( + &object, sizeof(T) + additional_bytes.value); +} + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/garbage-collected.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/garbage-collected.h new file mode 100644 index 000000000..dfd758a35 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/garbage-collected.h @@ -0,0 +1,114 @@ +// Copyright 2020 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 INCLUDE_CPPGC_GARBAGE_COLLECTED_H_ +#define INCLUDE_CPPGC_GARBAGE_COLLECTED_H_ + +#include "cppgc/internal/api-constants.h" +#include "cppgc/platform.h" +#include "cppgc/trace-trait.h" +#include "cppgc/type-traits.h" + +namespace cppgc { + +class Visitor; + +/** + * Base class for managed objects. Only descendent types of `GarbageCollected` + * can be constructed using `MakeGarbageCollected()`. Must be inherited from as + * left-most base class. + * + * Types inheriting from GarbageCollected must provide a method of + * signature `void Trace(cppgc::Visitor*) const` that dispatchs all managed + * pointers to the visitor and delegates to garbage-collected base classes. + * The method must be virtual if the type is not directly a child of + * GarbageCollected and marked as final. + * + * \code + * // Example using final class. + * class FinalType final : public GarbageCollected { + * public: + * void Trace(cppgc::Visitor* visitor) const { + * // Dispatch using visitor->Trace(...); + * } + * }; + * + * // Example using non-final base class. + * class NonFinalBase : public GarbageCollected { + * public: + * virtual void Trace(cppgc::Visitor*) const {} + * }; + * + * class FinalChild final : public NonFinalBase { + * public: + * void Trace(cppgc::Visitor* visitor) const final { + * // Dispatch using visitor->Trace(...); + * NonFinalBase::Trace(visitor); + * } + * }; + * \endcode + */ +template +class GarbageCollected { + public: + using IsGarbageCollectedTypeMarker = void; + using ParentMostGarbageCollectedType = T; + + // Must use MakeGarbageCollected. + void* operator new(size_t) = delete; + void* operator new[](size_t) = delete; + // The garbage collector is taking care of reclaiming the object. Also, + // virtual destructor requires an unambiguous, accessible 'operator delete'. + void operator delete(void*) { +#ifdef V8_ENABLE_CHECKS + internal::Fatal( + "Manually deleting a garbage collected object is not allowed"); +#endif // V8_ENABLE_CHECKS + } + void operator delete[](void*) = delete; + + protected: + GarbageCollected() = default; +}; + +/** + * Base class for managed mixin objects. Such objects cannot be constructed + * directly but must be mixed into the inheritance hierarchy of a + * GarbageCollected object. + * + * Types inheriting from GarbageCollectedMixin must override a virtual method + * of signature `void Trace(cppgc::Visitor*) const` that dispatchs all managed + * pointers to the visitor and delegates to base classes. + * + * \code + * class Mixin : public GarbageCollectedMixin { + * public: + * void Trace(cppgc::Visitor* visitor) const override { + * // Dispatch using visitor->Trace(...); + * } + * }; + * \endcode + */ +class GarbageCollectedMixin { + public: + using IsGarbageCollectedMixinTypeMarker = void; + + // Must use MakeGarbageCollected. + void* operator new(size_t) = delete; + void* operator new[](size_t) = delete; + // The garbage collector is taking care of reclaiming the object. + // Not override the non-array varaint of `delete` to not conflict with the + // operator in GarbageCollected above. + void operator delete[](void*) = delete; + + /** + * This Trace method must be overriden by objects inheriting from + * GarbageCollectedMixin. + */ + virtual void Trace(cppgc::Visitor*) const {} +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_GARBAGE_COLLECTED_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-consistency.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-consistency.h new file mode 100644 index 000000000..23b5d9099 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-consistency.h @@ -0,0 +1,309 @@ +// Copyright 2020 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 INCLUDE_CPPGC_HEAP_CONSISTENCY_H_ +#define INCLUDE_CPPGC_HEAP_CONSISTENCY_H_ + +#include + +#include "cppgc/internal/write-barrier.h" +#include "cppgc/macros.h" +#include "cppgc/member.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +class HeapHandle; + +namespace subtle { + +/** + * **DO NOT USE: Use the appropriate managed types.** + * + * Consistency helpers that aid in maintaining a consistent internal state of + * the garbage collector. + */ +class HeapConsistency final { + public: + using WriteBarrierParams = internal::WriteBarrier::Params; + using WriteBarrierType = internal::WriteBarrier::Type; + + /** + * Gets the required write barrier type for a specific write. + * + * \param slot Slot containing the pointer to the object. The slot itself + * must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + * \param value The pointer to the object. May be an interior pointer to an + * interface of the actual object. + * \param params Parameters that may be used for actual write barrier calls. + * Only filled if return value indicates that a write barrier is needed. The + * contents of the `params` are an implementation detail. + * \returns whether a write barrier is needed and which barrier to invoke. + */ + static V8_INLINE WriteBarrierType GetWriteBarrierType( + const void* slot, const void* value, WriteBarrierParams& params) { + return internal::WriteBarrier::GetWriteBarrierType(slot, value, params); + } + + /** + * Gets the required write barrier type for a specific write. This override is + * only used for all the BasicMember types. + * + * \param slot Slot containing the pointer to the object. The slot itself + * must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + * \param value The pointer to the object held via `BasicMember`. + * \param params Parameters that may be used for actual write barrier calls. + * Only filled if return value indicates that a write barrier is needed. The + * contents of the `params` are an implementation detail. + * \returns whether a write barrier is needed and which barrier to invoke. + */ + template + static V8_INLINE WriteBarrierType GetWriteBarrierType( + const internal::BasicMember& value, + WriteBarrierParams& params) { + return internal::WriteBarrier::GetWriteBarrierType( + value.GetRawSlot(), value.GetRawStorage(), params); + } + + /** + * Gets the required write barrier type for a specific write. + * + * \param slot Slot to some part of an object. The object must not necessarily + have been allocated using `MakeGarbageCollected()` but can also live + off-heap or on stack. + * \param params Parameters that may be used for actual write barrier calls. + * Only filled if return value indicates that a write barrier is needed. The + * contents of the `params` are an implementation detail. + * \param callback Callback returning the corresponding heap handle. The + * callback is only invoked if the heap cannot otherwise be figured out. The + * callback must not allocate. + * \returns whether a write barrier is needed and which barrier to invoke. + */ + template + static V8_INLINE WriteBarrierType + GetWriteBarrierType(const void* slot, WriteBarrierParams& params, + HeapHandleCallback callback) { + return internal::WriteBarrier::GetWriteBarrierType(slot, params, callback); + } + + /** + * Gets the required write barrier type for a specific write. + * This version is meant to be used in conjunction with with a marking write + * barrier barrier which doesn't consider the slot. + * + * \param value The pointer to the object. May be an interior pointer to an + * interface of the actual object. + * \param params Parameters that may be used for actual write barrier calls. + * Only filled if return value indicates that a write barrier is needed. The + * contents of the `params` are an implementation detail. + * \returns whether a write barrier is needed and which barrier to invoke. + */ + static V8_INLINE WriteBarrierType + GetWriteBarrierType(const void* value, WriteBarrierParams& params) { + return internal::WriteBarrier::GetWriteBarrierType(value, params); + } + + /** + * Conservative Dijkstra-style write barrier that processes an object if it + * has not yet been processed. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param object The pointer to the object. May be an interior pointer to + * an interface of the actual object. + */ + static V8_INLINE void DijkstraWriteBarrier(const WriteBarrierParams& params, + const void* object) { + internal::WriteBarrier::DijkstraMarkingBarrier(params, object); + } + + /** + * Conservative Dijkstra-style write barrier that processes a range of + * elements if they have not yet been processed. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param first_element Pointer to the first element that should be processed. + * The slot itself must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + * \param element_size Size of the element in bytes. + * \param number_of_elements Number of elements that should be processed, + * starting with `first_element`. + * \param trace_callback The trace callback that should be invoked for each + * element if necessary. + */ + static V8_INLINE void DijkstraWriteBarrierRange( + const WriteBarrierParams& params, const void* first_element, + size_t element_size, size_t number_of_elements, + TraceCallback trace_callback) { + internal::WriteBarrier::DijkstraMarkingBarrierRange( + params, first_element, element_size, number_of_elements, + trace_callback); + } + + /** + * Steele-style write barrier that re-processes an object if it has already + * been processed. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param object The pointer to the object which must point to an object that + * has been allocated using `MakeGarbageCollected()`. Interior pointers are + * not supported. + */ + static V8_INLINE void SteeleWriteBarrier(const WriteBarrierParams& params, + const void* object) { + internal::WriteBarrier::SteeleMarkingBarrier(params, object); + } + + /** + * Generational barrier for maintaining consistency when running with multiple + * generations. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param slot Slot containing the pointer to the object. The slot itself + * must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + */ + static V8_INLINE void GenerationalBarrier(const WriteBarrierParams& params, + const void* slot) { + internal::WriteBarrier::GenerationalBarrier< + internal::WriteBarrier::GenerationalBarrierType::kPreciseSlot>(params, + slot); + } + + /** + * Generational barrier for maintaining consistency when running with multiple + * generations. This version is used when slot contains uncompressed pointer. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param slot Uncompressed slot containing the direct pointer to the object. + * The slot itself must reside in an object that has been allocated using + * `MakeGarbageCollected()`. + */ + static V8_INLINE void GenerationalBarrierForUncompressedSlot( + const WriteBarrierParams& params, const void* uncompressed_slot) { + internal::WriteBarrier::GenerationalBarrier< + internal::WriteBarrier::GenerationalBarrierType:: + kPreciseUncompressedSlot>(params, uncompressed_slot); + } + + /** + * Generational barrier for source object that may contain outgoing pointers + * to objects in young generation. + * + * \param params The parameters retrieved from `GetWriteBarrierType()`. + * \param inner_pointer Pointer to the source object. + */ + static V8_INLINE void GenerationalBarrierForSourceObject( + const WriteBarrierParams& params, const void* inner_pointer) { + internal::WriteBarrier::GenerationalBarrier< + internal::WriteBarrier::GenerationalBarrierType::kImpreciseSlot>( + params, inner_pointer); + } + + private: + HeapConsistency() = delete; +}; + +/** + * Disallows garbage collection finalizations. Any garbage collection triggers + * result in a crash when in this scope. + * + * Note that the garbage collector already covers paths that can lead to garbage + * collections, so user code does not require checking + * `IsGarbageCollectionAllowed()` before allocations. + */ +class V8_EXPORT V8_NODISCARD DisallowGarbageCollectionScope final { + CPPGC_STACK_ALLOCATED(); + + public: + /** + * \returns whether garbage collections are currently allowed. + */ + static bool IsGarbageCollectionAllowed(HeapHandle& heap_handle); + + /** + * Enters a disallow garbage collection scope. Must be paired with `Leave()`. + * Prefer a scope instance of `DisallowGarbageCollectionScope`. + * + * \param heap_handle The corresponding heap. + */ + static void Enter(HeapHandle& heap_handle); + + /** + * Leaves a disallow garbage collection scope. Must be paired with `Enter()`. + * Prefer a scope instance of `DisallowGarbageCollectionScope`. + * + * \param heap_handle The corresponding heap. + */ + static void Leave(HeapHandle& heap_handle); + + /** + * Constructs a scoped object that automatically enters and leaves a disallow + * garbage collection scope based on its lifetime. + * + * \param heap_handle The corresponding heap. + */ + explicit DisallowGarbageCollectionScope(HeapHandle& heap_handle); + ~DisallowGarbageCollectionScope(); + + DisallowGarbageCollectionScope(const DisallowGarbageCollectionScope&) = + delete; + DisallowGarbageCollectionScope& operator=( + const DisallowGarbageCollectionScope&) = delete; + + private: + HeapHandle& heap_handle_; +}; + +/** + * Avoids invoking garbage collection finalizations. Already running garbage + * collection phase are unaffected by this scope. + * + * Should only be used temporarily as the scope has an impact on memory usage + * and follow up garbage collections. + */ +class V8_EXPORT V8_NODISCARD NoGarbageCollectionScope final { + CPPGC_STACK_ALLOCATED(); + + public: + /** + * Enters a no garbage collection scope. Must be paired with `Leave()`. Prefer + * a scope instance of `NoGarbageCollectionScope`. + * + * \param heap_handle The corresponding heap. + */ + static void Enter(HeapHandle& heap_handle); + + /** + * Leaves a no garbage collection scope. Must be paired with `Enter()`. Prefer + * a scope instance of `NoGarbageCollectionScope`. + * + * \param heap_handle The corresponding heap. + */ + static void Leave(HeapHandle& heap_handle); + + /** + * Constructs a scoped object that automatically enters and leaves a no + * garbage collection scope based on its lifetime. + * + * \param heap_handle The corresponding heap. + */ + explicit NoGarbageCollectionScope(HeapHandle& heap_handle); + ~NoGarbageCollectionScope(); + + NoGarbageCollectionScope(const NoGarbageCollectionScope&) = delete; + NoGarbageCollectionScope& operator=(const NoGarbageCollectionScope&) = delete; + + private: + HeapHandle& heap_handle_; +}; + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_CONSISTENCY_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-handle.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-handle.h new file mode 100644 index 000000000..0d1d21e65 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-handle.h @@ -0,0 +1,48 @@ +// 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 INCLUDE_CPPGC_HEAP_HANDLE_H_ +#define INCLUDE_CPPGC_HEAP_HANDLE_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +namespace internal { +class HeapBase; +class WriteBarrierTypeForCagedHeapPolicy; +class WriteBarrierTypeForNonCagedHeapPolicy; +} // namespace internal + +/** + * Opaque handle used for additional heap APIs. + */ +class HeapHandle { + public: + // Deleted copy ctor to avoid treating the type by value. + HeapHandle(const HeapHandle&) = delete; + HeapHandle& operator=(const HeapHandle&) = delete; + + private: + HeapHandle() = default; + + V8_INLINE bool is_incremental_marking_in_progress() const { + return is_incremental_marking_in_progress_; + } + + V8_INLINE bool is_young_generation_enabled() const { + return is_young_generation_enabled_; + } + + bool is_incremental_marking_in_progress_ = false; + bool is_young_generation_enabled_ = false; + + friend class internal::HeapBase; + friend class internal::WriteBarrierTypeForCagedHeapPolicy; + friend class internal::WriteBarrierTypeForNonCagedHeapPolicy; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_HANDLE_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-state.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-state.h new file mode 100644 index 000000000..28212589f --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-state.h @@ -0,0 +1,82 @@ +// Copyright 2021 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 INCLUDE_CPPGC_HEAP_STATE_H_ +#define INCLUDE_CPPGC_HEAP_STATE_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +class HeapHandle; + +namespace subtle { + +/** + * Helpers to peek into heap-internal state. + */ +class V8_EXPORT HeapState final { + public: + /** + * Returns whether the garbage collector is marking. This API is experimental + * and is expected to be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the garbage collector is currently marking, and false + * otherwise. + */ + static bool IsMarking(const HeapHandle& heap_handle); + + /* + * Returns whether the garbage collector is sweeping. This API is experimental + * and is expected to be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the garbage collector is currently sweeping, and false + * otherwise. + */ + static bool IsSweeping(const HeapHandle& heap_handle); + + /* + * Returns whether the garbage collector is currently sweeping on the thread + * owning this heap. This API allows the caller to determine whether it has + * been called from a destructor of a managed object. This API is experimental + * and may be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the garbage collector is currently sweeping on this + * thread, and false otherwise. + */ + static bool IsSweepingOnOwningThread(const HeapHandle& heap_handle); + + /** + * Returns whether the garbage collector is in the atomic pause, i.e., the + * mutator is stopped from running. This API is experimental and is expected + * to be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the garbage collector is currently in the atomic pause, + * and false otherwise. + */ + static bool IsInAtomicPause(const HeapHandle& heap_handle); + + /** + * Returns whether the last garbage collection was finalized conservatively + * (i.e., with a non-empty stack). This API is experimental and is expected to + * be removed in future. + * + * \param heap_handle The corresponding heap. + * \returns true if the last garbage collection was finalized conservatively, + * and false otherwise. + */ + static bool PreviousGCWasConservative(const HeapHandle& heap_handle); + + private: + HeapState() = delete; +}; + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_STATE_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-statistics.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-statistics.h new file mode 100644 index 000000000..93f4f9b65 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap-statistics.h @@ -0,0 +1,126 @@ +// Copyright 2021 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 INCLUDE_CPPGC_HEAP_STATISTICS_H_ +#define INCLUDE_CPPGC_HEAP_STATISTICS_H_ + +#include +#include +#include +#include + +namespace cppgc { + +/** + * `HeapStatistics` contains memory consumption and utilization statistics for a + * cppgc heap. + */ +struct HeapStatistics final { + /** + * Specifies the detail level of the heap statistics. Brief statistics contain + * only the top-level allocated and used memory statistics for the entire + * heap. Detailed statistics also contain a break down per space and page, as + * well as freelist statistics and object type histograms. Note that used + * memory reported by brief statistics and detailed statistics might differ + * slightly. + */ + enum DetailLevel : uint8_t { + kBrief, + kDetailed, + }; + + /** + * Object statistics for a single type. + */ + struct ObjectStatsEntry { + /** + * Number of allocated bytes. + */ + size_t allocated_bytes; + /** + * Number of allocated objects. + */ + size_t object_count; + }; + + /** + * Page granularity statistics. For each page the statistics record the + * allocated memory size and overall used memory size for the page. + */ + struct PageStatistics { + /** Overall committed amount of memory for the page. */ + size_t committed_size_bytes = 0; + /** Resident amount of memory held by the page. */ + size_t resident_size_bytes = 0; + /** Amount of memory actually used on the page. */ + size_t used_size_bytes = 0; + /** + * Statistics for object allocated on the page. If an object provides a + * name by inheriting from NameProvider, its name will be recorded in the + * statistics. Other objects, without an explicit name, are merged under a + * single type unless the CPPGC_SUPPORTS_OBJECT_NAME build flag is enabled. + */ + std::vector object_statistics; + }; + + /** + * Statistics of the freelist (used only in non-large object spaces). For + * each bucket in the freelist the statistics record the bucket size, the + * number of freelist entries in the bucket, and the overall allocated memory + * consumed by these freelist entries. + */ + struct FreeListStatistics { + /** bucket sizes in the freelist. */ + std::vector bucket_size; + /** number of freelist entries per bucket. */ + std::vector free_count; + /** memory size consumed by freelist entries per size. */ + std::vector free_size; + }; + + /** + * Space granularity statistics. For each space the statistics record the + * space name, the amount of allocated memory and overall used memory for the + * space. The statistics also contain statistics for each of the space's + * pages, its freelist and the objects allocated on the space. + */ + struct SpaceStatistics { + /** The space name */ + std::string name; + /** Overall committed amount of memory for the heap. */ + size_t committed_size_bytes = 0; + /** Resident amount of memory held by the heap. */ + size_t resident_size_bytes = 0; + /** Amount of memory actually used on the space. */ + size_t used_size_bytes = 0; + /** Statistics for each of the pages in the space. */ + std::vector page_stats; + /** Statistics for the freelist of the space. */ + FreeListStatistics free_list_stats; + }; + + /** Overall committed amount of memory for the heap. */ + size_t committed_size_bytes = 0; + /** Resident amount of memory held by the heap. */ + size_t resident_size_bytes = 0; + /** Amount of memory actually used on the heap. */ + size_t used_size_bytes = 0; + /** Memory retained in the page pool, not used directly by the heap. */ + size_t pooled_memory_size_bytes = 0; + /** Detail level of this HeapStatistics. */ + DetailLevel detail_level; + + /** Statistics for each of the spaces in the heap. Filled only when + * `detail_level` is `DetailLevel::kDetailed`. */ + std::vector space_stats; + + /** + * Vector of `cppgc::GarbageCollected` type names. + */ + std::vector type_names; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_STATISTICS_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/heap.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap.h new file mode 100644 index 000000000..2968a0af1 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/heap.h @@ -0,0 +1,219 @@ +// Copyright 2020 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 INCLUDE_CPPGC_HEAP_H_ +#define INCLUDE_CPPGC_HEAP_H_ + +#include +#include +#include +#include + +#include "cppgc/common.h" +#include "cppgc/custom-space.h" +#include "cppgc/platform.h" +#include "v8config.h" // NOLINT(build/include_directory) + +/** + * cppgc - A C++ garbage collection library. + */ +namespace cppgc { + +class AllocationHandle; +class HeapHandle; + +/** + * Implementation details of cppgc. Those details are considered internal and + * may change at any point in time without notice. Users should never rely on + * the contents of this namespace. + */ +namespace internal { +class Heap; +} // namespace internal + +/** + * A marker that captures the current stack start address. + */ +class V8_EXPORT StackStartMarker { + public: + StackStartMarker() : stack_start_(__builtin_frame_address(0)) {} + void* stack_start() const { return stack_start_; } + + private: + void* stack_start_; +}; + +class V8_EXPORT Heap { + public: + /** + * Specifies the stack state the embedder is in. + */ + using StackState = EmbedderStackState; + + /** + * Specifies whether conservative stack scanning is supported. + */ + enum class StackSupport : uint8_t { + /** + * Conservative stack scan is supported. + */ + kSupportsConservativeStackScan, + /** + * Conservative stack scan is not supported. Embedders may use this option + * when using custom infrastructure that is unsupported by the library. + */ + kNoConservativeStackScan, + }; + + /** + * Specifies supported marking types. + */ + enum class MarkingType : uint8_t { + /** + * Atomic stop-the-world marking. This option does not require any write + * barriers but is the most intrusive in terms of jank. + */ + kAtomic, + /** + * Incremental marking interleaves marking with the rest of the application + * workload on the same thread. + */ + kIncremental, + /** + * Incremental and concurrent marking. + */ + kIncrementalAndConcurrent + }; + + /** + * Specifies supported sweeping types. + */ + enum class SweepingType : uint8_t { + /** + * Atomic stop-the-world sweeping. All of sweeping is performed at once. + */ + kAtomic, + /** + * Incremental sweeping interleaves sweeping with the rest of the + * application workload on the same thread. + */ + kIncremental, + /** + * Incremental and concurrent sweeping. Sweeping is split and interleaved + * with the rest of the application. + */ + kIncrementalAndConcurrent + }; + + /** + * Constraints for a Heap setup. + */ + struct ResourceConstraints { + /** + * Allows the heap to grow to some initial size in bytes before triggering + * garbage collections. This is useful when it is known that applications + * need a certain minimum heap to run to avoid repeatedly invoking the + * garbage collector when growing the heap. + */ + size_t initial_heap_size_bytes = 0; + }; + + /** + * Options specifying Heap properties (e.g. custom spaces) when initializing a + * heap through `Heap::Create()`. + */ + struct HeapOptions { + /** + * Creates reasonable defaults for instantiating a Heap. + * + * \returns the HeapOptions that can be passed to `Heap::Create()`. + */ + static HeapOptions Default() { return {}; } + + /** + * Custom spaces added to heap are required to have indices forming a + * numbered sequence starting at 0, i.e., their `kSpaceIndex` must + * correspond to the index they reside in the vector. + */ + std::vector> custom_spaces; + + /** + * Specifies whether conservative stack scan is supported. When conservative + * stack scan is not supported, the collector may try to invoke + * garbage collections using non-nestable task, which are guaranteed to have + * no interesting stack, through the provided Platform. If such tasks are + * not supported by the Platform, the embedder must take care of invoking + * the GC through `ForceGarbageCollectionSlow()`. + */ + StackSupport stack_support = StackSupport::kSupportsConservativeStackScan; + + /** + * Specifies which types of marking are supported by the heap. + */ + MarkingType marking_support = MarkingType::kIncrementalAndConcurrent; + + /** + * Specifies which types of sweeping are supported by the heap. + */ + SweepingType sweeping_support = SweepingType::kIncrementalAndConcurrent; + + /** + * Resource constraints specifying various properties that the internal + * GC scheduler follows. + */ + ResourceConstraints resource_constraints; + + /** + * Optional marker representing the stack start of the thread creating the + * heap. + */ + std::optional stack_start_marker = std::nullopt; + }; + /** + * Creates a new heap that can be used for object allocation. + * + * \param platform implemented and provided by the embedder. + * \param options HeapOptions specifying various properties for the Heap. + * \returns a new Heap instance. + */ + static std::unique_ptr Create( + std::shared_ptr platform, + HeapOptions options = HeapOptions::Default()); + + virtual ~Heap() = default; + + /** + * Forces garbage collection. + * + * \param source String specifying the source (or caller) triggering a + * forced garbage collection. + * \param reason String specifying the reason for the forced garbage + * collection. + * \param stack_state The embedder stack state, see StackState. + */ + void ForceGarbageCollectionSlow( + const char* source, const char* reason, + StackState stack_state = StackState::kMayContainHeapPointers); + + /** + * \returns the opaque handle for allocating objects using + * `MakeGarbageCollected()`. + */ + AllocationHandle& GetAllocationHandle(); + + /** + * \returns the opaque heap handle which may be used to refer to this heap in + * other APIs. Valid as long as the underlying `Heap` is alive. + */ + HeapHandle& GetHeapHandle(); + + private: + Heap() = default; + + friend class internal::Heap; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_HEAP_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/api-constants.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/api-constants.h new file mode 100644 index 000000000..73b955d70 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/api-constants.h @@ -0,0 +1,75 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_ +#define INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_ + +#include +#include + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +// Embedders should not rely on this code! + +// Internal constants to avoid exposing internal types on the API surface. +namespace api_constants { + +constexpr size_t kKB = 1024; +constexpr size_t kMB = kKB * 1024; +constexpr size_t kGB = kMB * 1024; + +// Offset of the uint16_t bitfield from the payload contaning the +// in-construction bit. This is subtracted from the payload pointer to get +// to the right bitfield. +static constexpr size_t kFullyConstructedBitFieldOffsetFromPayload = + 2 * sizeof(uint16_t); +// Mask for in-construction bit. +static constexpr uint16_t kFullyConstructedBitMask = uint16_t{1} << 15; + +static constexpr size_t kPageSizeBits = 17; +static constexpr size_t kPageSize = size_t{1} << kPageSizeBits; + +static constexpr size_t kLargeObjectSizeThreshold = kPageSize / 2; + +#if defined(CPPGC_POINTER_COMPRESSION) +#if defined(CPPGC_ENABLE_LARGER_CAGE) +constexpr unsigned kPointerCompressionShift = 3; +#else // !defined(CPPGC_ENABLE_LARGER_CAGE) +constexpr unsigned kPointerCompressionShift = 1; +#endif // !defined(CPPGC_ENABLE_LARGER_CAGE) +#endif // !defined(CPPGC_POINTER_COMPRESSION) + +#if defined(CPPGC_CAGED_HEAP) +constexpr size_t kCagedHeapDefaultReservationSize = + static_cast(4) * kGB; +#if defined(CPPGC_POINTER_COMPRESSION) +constexpr size_t kCagedHeapMaxReservationSize = + size_t{1} << (31 + kPointerCompressionShift); +#else // !defined(CPPGC_POINTER_COMPRESSION) +constexpr size_t kCagedHeapMaxReservationSize = + kCagedHeapDefaultReservationSize; +#endif // !defined(CPPGC_POINTER_COMPRESSION) +constexpr size_t kCagedHeapReservationAlignment = kCagedHeapMaxReservationSize; +#endif // defined(CPPGC_CAGED_HEAP) + +static constexpr size_t kDefaultAlignment = sizeof(void*); + +// Maximum support alignment for a type as in `alignof(T)`. +static constexpr size_t kMaxSupportedAlignment = 2 * kDefaultAlignment; + +// Granularity of heap allocations. +constexpr size_t kAllocationGranularity = sizeof(void*); + +// Default cacheline size. +constexpr size_t kCachelineSize = 64; + +} // namespace api_constants + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/atomic-entry-flag.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/atomic-entry-flag.h new file mode 100644 index 000000000..5a7d3b8f8 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/atomic-entry-flag.h @@ -0,0 +1,48 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_ +#define INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_ + +#include + +namespace cppgc { +namespace internal { + +// A flag which provides a fast check whether a scope may be entered on the +// current thread, without needing to access thread-local storage or mutex. Can +// have false positives (i.e., spuriously report that it might be entered), so +// it is expected that this will be used in tandem with a precise check that the +// scope is in fact entered on that thread. +// +// Example: +// g_frobnicating_flag.MightBeEntered() && +// ThreadLocalFrobnicator().IsFrobnicating() +// +// Relaxed atomic operations are sufficient, since: +// - all accesses remain atomic +// - each thread must observe its own operations in order +// - no thread ever exits the flag more times than it enters (if used correctly) +// And so if a thread observes zero, it must be because it has observed an equal +// number of exits as entries. +class AtomicEntryFlag final { + public: + void Enter() { entries_.fetch_add(1, std::memory_order_relaxed); } + void Exit() { entries_.fetch_sub(1, std::memory_order_relaxed); } + + // Returns false only if the current thread is not between a call to Enter + // and a call to Exit. Returns true if this thread or another thread may + // currently be in the scope guarded by this flag. + bool MightBeEntered() const { + return entries_.load(std::memory_order_relaxed) != 0; + } + + private: + std::atomic_int entries_{0}; +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/base-page-handle.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/base-page-handle.h new file mode 100644 index 000000000..a5308dc34 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/base-page-handle.h @@ -0,0 +1,43 @@ +// 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 INCLUDE_CPPGC_INTERNAL_BASE_PAGE_HANDLE_H_ +#define INCLUDE_CPPGC_INTERNAL_BASE_PAGE_HANDLE_H_ + +#include "cppgc/heap-handle.h" +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/logging.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +// The class is needed in the header to allow for fast access to HeapHandle in +// the write barrier. +class BasePageHandle { + public: + static V8_INLINE BasePageHandle* FromPayload(void* payload) { + return reinterpret_cast( + reinterpret_cast(payload) & ~(api_constants::kPageSize - 1)); + } + static V8_INLINE const BasePageHandle* FromPayload(const void* payload) { + return FromPayload(const_cast(payload)); + } + + HeapHandle& heap_handle() { return heap_handle_; } + const HeapHandle& heap_handle() const { return heap_handle_; } + + protected: + explicit BasePageHandle(HeapHandle& heap_handle) : heap_handle_(heap_handle) { + CPPGC_DCHECK(reinterpret_cast(this) % api_constants::kPageSize == + 0); + } + + HeapHandle& heap_handle_; +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_BASE_PAGE_HANDLE_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/caged-heap-local-data.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/caged-heap-local-data.h new file mode 100644 index 000000000..ad717d744 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/caged-heap-local-data.h @@ -0,0 +1,117 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_ +#define INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_ + +#include +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/caged-heap.h" +#include "cppgc/internal/logging.h" +#include "cppgc/platform.h" +#include "v8config.h" // NOLINT(build/include_directory) + +#if __cpp_lib_bitopts +#include +#endif // __cpp_lib_bitopts + +#if defined(CPPGC_CAGED_HEAP) + +namespace cppgc { +namespace internal { + +class HeapBase; +class HeapBaseHandle; + +#if defined(CPPGC_YOUNG_GENERATION) + +// AgeTable is the bytemap needed for the fast generation check in the write +// barrier. AgeTable contains entries that correspond to 4096 bytes memory +// regions (cards). Each entry in the table represents generation of the objects +// that reside on the corresponding card (young, old or mixed). +class V8_EXPORT AgeTable final { + static constexpr size_t kRequiredSize = 1 * api_constants::kMB; + static constexpr size_t kAllocationGranularity = + api_constants::kAllocationGranularity; + + public: + // Represents age of the objects living on a single card. + enum class Age : uint8_t { kOld, kYoung, kMixed }; + // When setting age for a range, consider or ignore ages of the adjacent + // cards. + enum class AdjacentCardsPolicy : uint8_t { kConsider, kIgnore }; + + static constexpr size_t kCardSizeInBytes = + api_constants::kCagedHeapDefaultReservationSize / kRequiredSize; + + static constexpr size_t CalculateAgeTableSizeForHeapSize(size_t heap_size) { + return heap_size / kCardSizeInBytes; + } + + void SetAge(uintptr_t cage_offset, Age age) { + table_[card(cage_offset)] = age; + } + + V8_INLINE Age GetAge(uintptr_t cage_offset) const { + return table_[card(cage_offset)]; + } + + void SetAgeForRange(uintptr_t cage_offset_begin, uintptr_t cage_offset_end, + Age age, AdjacentCardsPolicy adjacent_cards_policy); + + Age GetAgeForRange(uintptr_t cage_offset_begin, + uintptr_t cage_offset_end) const; + + void ResetForTesting(); + + private: + V8_INLINE size_t card(uintptr_t offset) const { + constexpr size_t kGranularityBits = +#if __cpp_lib_bitopts + std::countr_zero(static_cast(kCardSizeInBytes)); +#elif V8_HAS_BUILTIN_CTZ + __builtin_ctz(static_cast(kCardSizeInBytes)); +#else //! V8_HAS_BUILTIN_CTZ + // Hardcode and check with assert. + 12; +#endif // !V8_HAS_BUILTIN_CTZ + static_assert((1 << kGranularityBits) == kCardSizeInBytes); + const size_t entry = offset >> kGranularityBits; + CPPGC_DCHECK(CagedHeapBase::GetAgeTableSize() > entry); + return entry; + } + +#if defined(V8_CC_GNU) + // gcc disallows flexible arrays in otherwise empty classes. + Age table_[0]; +#else // !defined(V8_CC_GNU) + Age table_[]; +#endif // !defined(V8_CC_GNU) +}; + +#endif // CPPGC_YOUNG_GENERATION + +struct CagedHeapLocalData final { + V8_INLINE static CagedHeapLocalData& Get() { + return *reinterpret_cast(CagedHeapBase::GetBase()); + } + + static constexpr size_t CalculateLocalDataSizeForHeapSize(size_t heap_size) { + return AgeTable::CalculateAgeTableSizeForHeapSize(heap_size); + } + +#if defined(CPPGC_YOUNG_GENERATION) + AgeTable age_table; +#endif +}; + +} // namespace internal +} // namespace cppgc + +#endif // defined(CPPGC_CAGED_HEAP) + +#endif // INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/caged-heap.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/caged-heap.h new file mode 100644 index 000000000..46fb529f6 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/caged-heap.h @@ -0,0 +1,64 @@ +// 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 INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_H_ +#define INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_H_ + +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/base-page-handle.h" +#include "v8config.h" // NOLINT(build/include_directory) + +#if defined(CPPGC_CAGED_HEAP) + +namespace cppgc { +namespace internal { + +class V8_EXPORT CagedHeapBase { + public: + V8_INLINE static uintptr_t OffsetFromAddress(const void* address) { + return reinterpret_cast(address) & + (api_constants::kCagedHeapReservationAlignment - 1); + } + + V8_INLINE static bool IsWithinCage(const void* address) { + CPPGC_DCHECK(g_heap_base_); + return (reinterpret_cast(address) & + ~(api_constants::kCagedHeapReservationAlignment - 1)) == + g_heap_base_; + } + + V8_INLINE static bool AreWithinCage(const void* addr1, const void* addr2) { +#if defined(CPPGC_POINTER_COMPRESSION) + static constexpr size_t kHeapBaseShift = + 31 + api_constants::kPointerCompressionShift; +#else // !defined(CPPGC_POINTER_COMPRESSION) + static constexpr size_t kHeapBaseShift = sizeof(uint32_t) * CHAR_BIT; +#endif // !defined(CPPGC_POINTER_COMPRESSION) + static_assert((static_cast(1) << kHeapBaseShift) == + api_constants::kCagedHeapMaxReservationSize); + CPPGC_DCHECK(g_heap_base_); + return !(((reinterpret_cast(addr1) ^ g_heap_base_) | + (reinterpret_cast(addr2) ^ g_heap_base_)) >> + kHeapBaseShift); + } + + V8_INLINE static uintptr_t GetBase() { return g_heap_base_; } + V8_INLINE static size_t GetAgeTableSize() { return g_age_table_size_; } + + private: + friend class CagedHeap; + + static uintptr_t g_heap_base_; + static size_t g_age_table_size_; +}; + +} // namespace internal +} // namespace cppgc + +#endif // defined(CPPGC_CAGED_HEAP) + +#endif // INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/compiler-specific.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/compiler-specific.h new file mode 100644 index 000000000..175156ca6 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/compiler-specific.h @@ -0,0 +1,46 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_ +#define INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +#if defined(__has_attribute) +#define CPPGC_HAS_ATTRIBUTE(FEATURE) __has_attribute(FEATURE) +#else +#define CPPGC_HAS_ATTRIBUTE(FEATURE) 0 +#endif + +#if defined(__has_cpp_attribute) +#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) __has_cpp_attribute(FEATURE) +#else +#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) 0 +#endif + +// [[no_unique_address]] comes in C++20 but supported in clang with -std >= +// c++11. +#if defined(V8_CC_MSVC) && CPPGC_HAS_CPP_ATTRIBUTE(msvc::no_unique_address) +// Unfortunately MSVC ignores [[no_unique_address]] (see +// https://devblogs.microsoft.com/cppblog/msvc-cpp20-and-the-std-cpp20-switch/#msvc-extensions-and-abi), +// and clang-cl matches it for ABI compatibility reasons. We need to prefer +// [[msvc::no_unique_address]] when available if we actually want any effect. +#define CPPGC_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#elif CPPGC_HAS_CPP_ATTRIBUTE(no_unique_address) +#define CPPGC_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else +#define CPPGC_NO_UNIQUE_ADDRESS +#endif + +#if CPPGC_HAS_ATTRIBUTE(unused) +#define CPPGC_UNUSED __attribute__((unused)) +#else +#define CPPGC_UNUSED +#endif + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/conditional-stack-allocated.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/conditional-stack-allocated.h new file mode 100644 index 000000000..21c89378f --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/conditional-stack-allocated.h @@ -0,0 +1,41 @@ +// Copyright 2025 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 INCLUDE_CPPGC_INTERNAL_CONDITIONAL_STACK_ALLOCATED_H_ +#define INCLUDE_CPPGC_INTERNAL_CONDITIONAL_STACK_ALLOCATED_H_ + +#include + +#include "cppgc/macros.h" // NOLINT(build/include_directory) +#include "cppgc/type-traits.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +// Base class that is marked as stack allocated if T is either marked as stack +// allocated or a traceable type. +template +class ConditionalStackAllocatedBase; + +template +concept RequiresStackAllocated = + !std::is_void_v && + (cppgc::IsStackAllocatedType || cppgc::internal::IsTraceableV || + cppgc::IsGarbageCollectedOrMixinTypeV); + +template + requires(RequiresStackAllocated) +class ConditionalStackAllocatedBase { + public: + CPPGC_STACK_ALLOCATED(); +}; + +template + requires(!RequiresStackAllocated) +class ConditionalStackAllocatedBase {}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_CONDITIONAL_STACK_ALLOCATED_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/finalizer-trait.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/finalizer-trait.h new file mode 100644 index 000000000..b62da059f --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/finalizer-trait.h @@ -0,0 +1,93 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_ +#define INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_ + +#include + +#include "cppgc/type-traits.h" + +namespace cppgc { +namespace internal { + +using FinalizationCallback = void (*)(void*); + +template +struct HasFinalizeGarbageCollectedObject : std::false_type {}; + +template +struct HasFinalizeGarbageCollectedObject< + T, + std::void_t().FinalizeGarbageCollectedObject())>> + : std::true_type {}; + +// The FinalizerTraitImpl specifies how to finalize objects. +template +struct FinalizerTraitImpl; + +template +struct FinalizerTraitImpl { + private: + // Dispatch to custom FinalizeGarbageCollectedObject(). + struct Custom { + static void Call(void* obj) { + static_cast(obj)->FinalizeGarbageCollectedObject(); + } + }; + + // Dispatch to regular destructor. + struct Destructor { + static void Call(void* obj) { static_cast(obj)->~T(); } + }; + + using FinalizeImpl = + std::conditional_t::value, Custom, + Destructor>; + + public: + static void Finalize(void* obj) { + static_assert(sizeof(T), "T must be fully defined"); + FinalizeImpl::Call(obj); + } +}; + +template +struct FinalizerTraitImpl { + static void Finalize(void* obj) { + static_assert(sizeof(T), "T must be fully defined"); + } +}; + +// The FinalizerTrait is used to determine if a type requires finalization and +// what finalization means. +template +struct FinalizerTrait { + private: + // Object has a finalizer if it has + // - a custom FinalizeGarbageCollectedObject method, or + // - a destructor. + static constexpr bool kNonTrivialFinalizer = + internal::HasFinalizeGarbageCollectedObject::value || + !std::is_trivially_destructible_v>; + + static void Finalize(void* obj) { + internal::FinalizerTraitImpl::Finalize(obj); + } + + public: + static constexpr bool HasFinalizer() { return kNonTrivialFinalizer; } + + // The callback used to finalize an object of type T. + static constexpr FinalizationCallback kCallback = + kNonTrivialFinalizer ? Finalize : nullptr; +}; + +template +constexpr FinalizationCallback FinalizerTrait::kCallback; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/gc-info.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/gc-info.h new file mode 100644 index 000000000..b2fbd1fee --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/gc-info.h @@ -0,0 +1,150 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_GC_INFO_H_ +#define INCLUDE_CPPGC_INTERNAL_GC_INFO_H_ + +#include +#include +#include + +#include "cppgc/internal/finalizer-trait.h" +#include "cppgc/internal/logging.h" +#include "cppgc/internal/name-trait.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +using GCInfoIndex = uint16_t; + +struct V8_EXPORT EnsureGCInfoIndexTrait final { + // Acquires a new GC info object and updates `registered_index` with the index + // that identifies that new info accordingly. + template + V8_INLINE static GCInfoIndex EnsureIndex( + std::atomic& registered_index) { + return EnsureGCInfoIndexTraitDispatch{}(registered_index); + } + + private: + template ::HasFinalizer(), + bool = NameTrait::HasNonHiddenName()> + struct EnsureGCInfoIndexTraitDispatch; + + static GCInfoIndex V8_PRESERVE_MOST + EnsureGCInfoIndex(std::atomic&, TraceCallback, + FinalizationCallback, NameCallback); + static GCInfoIndex V8_PRESERVE_MOST EnsureGCInfoIndex( + std::atomic&, TraceCallback, FinalizationCallback); + static GCInfoIndex V8_PRESERVE_MOST + EnsureGCInfoIndex(std::atomic&, TraceCallback, NameCallback); + static GCInfoIndex V8_PRESERVE_MOST + EnsureGCInfoIndex(std::atomic&, TraceCallback); +}; + +#define DISPATCH(has_finalizer, has_non_hidden_name, function) \ + template \ + struct EnsureGCInfoIndexTrait::EnsureGCInfoIndexTraitDispatch< \ + T, has_finalizer, has_non_hidden_name> { \ + V8_INLINE GCInfoIndex \ + operator()(std::atomic& registered_index) { \ + return function; \ + } \ + }; + +// ------------------------------------------------------- // +// DISPATCH(has_finalizer, has_non_hidden_name, function) // +// ------------------------------------------------------- // +DISPATCH(true, true, // + EnsureGCInfoIndex(registered_index, // + TraceTrait::Trace, // + FinalizerTrait::kCallback, // + NameTrait::GetName)) // +DISPATCH(true, false, // + EnsureGCInfoIndex(registered_index, // + TraceTrait::Trace, // + FinalizerTrait::kCallback)) // +DISPATCH(false, true, // + EnsureGCInfoIndex(registered_index, // + TraceTrait::Trace, // + NameTrait::GetName)) // +DISPATCH(false, false, // + EnsureGCInfoIndex(registered_index, // + TraceTrait::Trace)) // + +#undef DISPATCH + +// Trait determines how the garbage collector treats objects wrt. to traversing, +// finalization, and naming. +template +struct GCInfoTrait final { + V8_INLINE static GCInfoIndex Index() { + static_assert(sizeof(T), "T must be fully defined"); + static std::atomic + registered_index; // Uses zero initialization. + GCInfoIndex index = registered_index.load(std::memory_order_acquire); + if (V8_UNLIKELY(!index)) { + index = EnsureGCInfoIndexTrait::EnsureIndex(registered_index); + CPPGC_DCHECK(index != 0); + CPPGC_DCHECK(index == registered_index.load(std::memory_order_acquire)); + } + return index; + } + + static constexpr void CheckCallbacksAreDefined() { + // No USE() macro available. + (void)static_cast(TraceTrait::Trace); + (void)static_cast(FinalizerTrait::kCallback); + (void)static_cast(NameTrait::GetName); + } +}; + +// Fold types based on finalizer behavior. Note that finalizer characteristics +// align with trace behavior, i.e., destructors are virtual when trace methods +// are and vice versa. +template +struct GCInfoFolding final { + static constexpr bool kHasVirtualDestructorAtBase = + std::has_virtual_destructor_v; + static constexpr bool kBothTypesAreTriviallyDestructible = + std::is_trivially_destructible_v && + std::is_trivially_destructible_v; + static constexpr bool kHasCustomFinalizerDispatchAtBase = + internal::HasFinalizeGarbageCollectedObject< + ParentMostGarbageCollectedType>::value; +#ifdef CPPGC_SUPPORTS_OBJECT_NAMES + static constexpr bool kWantsDetailedObjectNames = true; +#else // !CPPGC_SUPPORTS_OBJECT_NAMES + static constexpr bool kWantsDetailedObjectNames = false; +#endif // !CPPGC_SUPPORTS_OBJECT_NAMES + + // Always true. Forces the compiler to resolve callbacks which ensures that + // both modes don't break without requiring compiling a separate + // configuration. Only a single GCInfo (for `ResultType` below) will actually + // be instantiated but existence (and well-formedness) of all callbacks is + // checked. + static constexpr bool WantToFold() { + if constexpr ((kHasVirtualDestructorAtBase || + kBothTypesAreTriviallyDestructible || + kHasCustomFinalizerDispatchAtBase) && + !kWantsDetailedObjectNames) { + GCInfoTrait::CheckCallbacksAreDefined(); + GCInfoTrait::CheckCallbacksAreDefined(); + return true; + } + return false; + } + + // Folding would regress name resolution when deriving names from C++ + // class names as it would just folds a name to the base class name. + using ResultType = + std::conditional_t; +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_GC_INFO_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/logging.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/logging.h new file mode 100644 index 000000000..887130a16 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/logging.h @@ -0,0 +1,50 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_LOGGING_H_ +#define INCLUDE_CPPGC_INTERNAL_LOGGING_H_ + +#include "cppgc/source-location.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +void V8_EXPORT DCheckImpl(const char*, + SourceLocation = SourceLocation::Current()); +[[noreturn]] void V8_EXPORT +FatalImpl(const char*, SourceLocation = SourceLocation::Current()); + +// Used to ignore -Wunused-variable. +template +struct EatParams {}; + +#ifdef CPPGC_ENABLE_API_CHECKS +#define CPPGC_DCHECK_MSG(condition, message) \ + do { \ + if (V8_UNLIKELY(!(condition))) { \ + ::cppgc::internal::DCheckImpl(message); \ + } \ + } while (false) +#else // !CPPGC_ENABLE_API_CHECKS +#define CPPGC_DCHECK_MSG(condition, message) \ + (static_cast(::cppgc::internal::EatParams(condition), message)>{})) +#endif // !CPPGC_ENABLE_API_CHECKS + +#define CPPGC_DCHECK(condition) CPPGC_DCHECK_MSG(condition, #condition) + +#define CPPGC_CHECK_MSG(condition, message) \ + do { \ + if (V8_UNLIKELY(!(condition))) { \ + ::cppgc::internal::FatalImpl(message); \ + } \ + } while (false) + +#define CPPGC_CHECK(condition) CPPGC_CHECK_MSG(condition, #condition) + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_LOGGING_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/member-storage.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/member-storage.h new file mode 100644 index 000000000..f862740db --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/member-storage.h @@ -0,0 +1,305 @@ +// 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 INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_ +#define INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_ + +#include +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/caged-heap.h" +#include "cppgc/internal/logging.h" +#include "cppgc/sentinel-pointer.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +enum class WriteBarrierSlotType { + kCompressed, + kUncompressed, +}; + +#if defined(CPPGC_POINTER_COMPRESSION) + +#if defined(__clang__) +// Attribute const allows the compiler to assume that CageBaseGlobal::g_base_ +// doesn't change (e.g. across calls) and thereby avoid redundant loads. +#define CPPGC_CONST __attribute__((const)) +#define CPPGC_REQUIRE_CONSTANT_INIT \ + __attribute__((require_constant_initialization)) +#else // defined(__clang__) +#define CPPGC_CONST +#define CPPGC_REQUIRE_CONSTANT_INIT +#endif // defined(__clang__) + +class V8_EXPORT CageBaseGlobal final { + public: + V8_INLINE CPPGC_CONST static uintptr_t Get() { + CPPGC_DCHECK(IsBaseConsistent()); + return g_base_.base; + } + + V8_INLINE CPPGC_CONST static bool IsSet() { + CPPGC_DCHECK(IsBaseConsistent()); + return (g_base_.base & ~kLowerHalfWordMask) != 0; + } + + private: + // We keep the lower halfword as ones to speed up decompression. + static constexpr uintptr_t kLowerHalfWordMask = + (api_constants::kCagedHeapReservationAlignment - 1); + + static union alignas(api_constants::kCachelineSize) Base { + uintptr_t base; + char cache_line[api_constants::kCachelineSize]; + } g_base_ CPPGC_REQUIRE_CONSTANT_INIT; + + CageBaseGlobal() = delete; + + V8_INLINE static bool IsBaseConsistent() { + return kLowerHalfWordMask == (g_base_.base & kLowerHalfWordMask); + } + + friend class CageBaseGlobalUpdater; +}; + +#undef CPPGC_REQUIRE_CONSTANT_INIT +#undef CPPGC_CONST + +class V8_TRIVIAL_ABI CompressedPointer final { + public: + struct AtomicInitializerTag {}; + + using IntegralType = uint32_t; + static constexpr auto kWriteBarrierSlotType = + WriteBarrierSlotType::kCompressed; + + V8_INLINE CompressedPointer() : value_(0u) {} + V8_INLINE explicit CompressedPointer(const void* value, + AtomicInitializerTag) { + StoreAtomic(value); + } + V8_INLINE explicit CompressedPointer(const void* ptr) + : value_(Compress(ptr)) {} + V8_INLINE explicit CompressedPointer(std::nullptr_t) : value_(0u) {} + V8_INLINE explicit CompressedPointer(SentinelPointer) + : value_(kCompressedSentinel) {} + + V8_INLINE const void* Load() const { return Decompress(value_); } + V8_INLINE const void* LoadAtomic() const { + return Decompress( + reinterpret_cast&>(value_).load( + std::memory_order_relaxed)); + } + + V8_INLINE void Store(const void* ptr) { value_ = Compress(ptr); } + V8_INLINE void StoreAtomic(const void* value) { + reinterpret_cast&>(value_).store( + Compress(value), std::memory_order_relaxed); + } + + V8_INLINE void Clear() { value_ = 0u; } + V8_INLINE bool IsCleared() const { return !value_; } + + V8_INLINE bool IsSentinel() const { return value_ == kCompressedSentinel; } + + V8_INLINE uint32_t GetAsInteger() const { return value_; } + + V8_INLINE friend bool operator==(CompressedPointer a, CompressedPointer b) { + return a.value_ == b.value_; + } + V8_INLINE friend bool operator!=(CompressedPointer a, CompressedPointer b) { + return a.value_ != b.value_; + } + V8_INLINE friend bool operator<(CompressedPointer a, CompressedPointer b) { + return a.value_ < b.value_; + } + V8_INLINE friend bool operator<=(CompressedPointer a, CompressedPointer b) { + return a.value_ <= b.value_; + } + V8_INLINE friend bool operator>(CompressedPointer a, CompressedPointer b) { + return a.value_ > b.value_; + } + V8_INLINE friend bool operator>=(CompressedPointer a, CompressedPointer b) { + return a.value_ >= b.value_; + } + + static V8_INLINE IntegralType Compress(const void* ptr) { + static_assert(SentinelPointer::kSentinelValue == + 1 << api_constants::kPointerCompressionShift, + "The compression scheme relies on the sentinel encoded as 1 " + "<< kPointerCompressionShift"); + static constexpr size_t kGigaCageMask = + ~(api_constants::kCagedHeapReservationAlignment - 1); + static constexpr size_t kPointerCompressionShiftMask = + (1 << api_constants::kPointerCompressionShift) - 1; + + CPPGC_DCHECK(CageBaseGlobal::IsSet()); + const uintptr_t base = CageBaseGlobal::Get(); + CPPGC_DCHECK(!ptr || ptr == kSentinelPointer || + (base & kGigaCageMask) == + (reinterpret_cast(ptr) & kGigaCageMask)); + CPPGC_DCHECK( + (reinterpret_cast(ptr) & kPointerCompressionShiftMask) == 0); + + const auto uptr = reinterpret_cast(ptr); + // Shift the pointer and truncate. + auto compressed = static_cast( + uptr >> api_constants::kPointerCompressionShift); + // Normal compressed pointers must have the MSB set. This is guaranteed by + // the cage alignment. + CPPGC_DCHECK((!compressed || compressed == kCompressedSentinel) || + (compressed & (1 << 31))); + return compressed; + } + + static V8_INLINE void* Decompress(IntegralType ptr) { + CPPGC_DCHECK(CageBaseGlobal::IsSet()); + const uintptr_t base = CageBaseGlobal::Get(); + return Decompress(ptr, base); + } + + static V8_INLINE void* Decompress(IntegralType ptr, uintptr_t base) { + CPPGC_DCHECK(CageBaseGlobal::IsSet()); + CPPGC_DCHECK(base == CageBaseGlobal::Get()); + // Sign-extend compressed pointer to full width. This ensure that normal + // pointers have only 1s in the base part of the address. It's also + // important to shift the unsigned value, as otherwise it would result in + // undefined behavior. + const uint64_t mask = static_cast(static_cast(ptr)) + << api_constants::kPointerCompressionShift; + // Set the base part of the address for normal compressed pointers. Note + // that nullptr and the sentinel value do not have 1s in the base part and + // remain as-is in this operation. + return reinterpret_cast(mask & base); + } + + // For a given memory `address`, this method iterates all possible pointers + // that can be reasonably recovered with the current compression scheme and + // passes them to `callback`. + template + static V8_INLINE void VisitPossiblePointers(const void* address, + Callback callback); + + private: + static constexpr IntegralType kCompressedSentinel = + SentinelPointer::kSentinelValue >> + api_constants::kPointerCompressionShift; + // All constructors initialize `value_`. Do not add a default value here as it + // results in a non-atomic write on some builds, even when the atomic version + // of the constructor is used. + IntegralType value_; +}; + +template +// static +void CompressedPointer::VisitPossiblePointers(const void* address, + Callback callback) { + const uintptr_t base = CageBaseGlobal::Get(); + CPPGC_DCHECK(base); + // We may have random compressed pointers on stack (e.g. due to inlined + // collections). These could be present in both halfwords. + const uint32_t compressed_low = + static_cast(reinterpret_cast(address)); + callback(CompressedPointer::Decompress(compressed_low, base)); + const uint32_t compressed_high = static_cast( + reinterpret_cast(address) >> (sizeof(uint32_t) * CHAR_BIT)); + callback(CompressedPointer::Decompress(compressed_high, base)); + // Iterate possible intermediate values, see `Decompress()`. The intermediate + // value of decompressing is a 64-bit value where 35 bits are the offset. We + // don't assume sign extension is stored and recover that part. + // + // Note that this case conveniently also recovers the full pointer. + static constexpr uintptr_t kBitForIntermediateValue = + (sizeof(uint32_t) * CHAR_BIT) + api_constants::kPointerCompressionShift; + static constexpr uintptr_t kSignExtensionMask = + ~((uintptr_t{1} << kBitForIntermediateValue) - 1); + const uintptr_t intermediate_sign_extended = + reinterpret_cast(address) | kSignExtensionMask; + callback(reinterpret_cast(intermediate_sign_extended & base)); +} + +#endif // defined(CPPGC_POINTER_COMPRESSION) + +class V8_TRIVIAL_ABI RawPointer final { + public: + struct AtomicInitializerTag {}; + + using IntegralType = uintptr_t; + static constexpr auto kWriteBarrierSlotType = + WriteBarrierSlotType::kUncompressed; + + V8_INLINE RawPointer() : ptr_(nullptr) {} + V8_INLINE explicit RawPointer(const void* ptr, AtomicInitializerTag) { + StoreAtomic(ptr); + } + V8_INLINE explicit RawPointer(const void* ptr) : ptr_(ptr) {} + + V8_INLINE const void* Load() const { return ptr_; } + V8_INLINE const void* LoadAtomic() const { + return reinterpret_cast&>(ptr_).load( + std::memory_order_relaxed); + } + + V8_INLINE void Store(const void* ptr) { ptr_ = ptr; } + V8_INLINE void StoreAtomic(const void* ptr) { + reinterpret_cast&>(ptr_).store( + ptr, std::memory_order_relaxed); + } + + V8_INLINE void Clear() { ptr_ = nullptr; } + V8_INLINE bool IsCleared() const { return !ptr_; } + + V8_INLINE bool IsSentinel() const { return ptr_ == kSentinelPointer; } + + V8_INLINE uintptr_t GetAsInteger() const { + return reinterpret_cast(ptr_); + } + + V8_INLINE friend bool operator==(RawPointer a, RawPointer b) { + return a.ptr_ == b.ptr_; + } + V8_INLINE friend bool operator!=(RawPointer a, RawPointer b) { + return a.ptr_ != b.ptr_; + } + V8_INLINE friend bool operator<(RawPointer a, RawPointer b) { + return a.ptr_ < b.ptr_; + } + V8_INLINE friend bool operator<=(RawPointer a, RawPointer b) { + return a.ptr_ <= b.ptr_; + } + V8_INLINE friend bool operator>(RawPointer a, RawPointer b) { + return a.ptr_ > b.ptr_; + } + V8_INLINE friend bool operator>=(RawPointer a, RawPointer b) { + return a.ptr_ >= b.ptr_; + } + + template + static V8_INLINE void VisitPossiblePointers(const void* address, + Callback callback) { + // Pass along the full pointer. + return callback(const_cast(address)); + } + + private: + // All constructors initialize `ptr_`. Do not add a default value here as it + // results in a non-atomic write on some builds, even when the atomic version + // of the constructor is used. + const void* ptr_; +}; + +#if defined(CPPGC_POINTER_COMPRESSION) +using DefaultMemberStorage = CompressedPointer; +#else // !defined(CPPGC_POINTER_COMPRESSION) +using DefaultMemberStorage = RawPointer; +#endif // !defined(CPPGC_POINTER_COMPRESSION) + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/name-trait.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/name-trait.h new file mode 100644 index 000000000..6e63a7282 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/name-trait.h @@ -0,0 +1,141 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_ +#define INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_ + +#include +#include +#include + +#include "cppgc/name-provider.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +#if CPPGC_SUPPORTS_OBJECT_NAMES && defined(__clang__) +#define CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME 1 + +// Provides constexpr c-string storage for a name of fixed |Size| characters. +// Automatically appends terminating 0 byte. +template +struct NameBuffer { + char name[Size + 1]{}; + + static constexpr NameBuffer FromCString(const char* str) { + NameBuffer result; + for (size_t i = 0; i < Size; ++i) result.name[i] = str[i]; + result.name[Size] = 0; + return result; + } +}; + +template +const char* GetTypename() { + static constexpr char kSelfPrefix[] = + "const char *cppgc::internal::GetTypename() [T ="; + static_assert(__builtin_strncmp(__PRETTY_FUNCTION__, kSelfPrefix, + sizeof(kSelfPrefix) - 1) == 0, + "The prefix must match"); + static constexpr const char* kTypenameStart = + __PRETTY_FUNCTION__ + sizeof(kSelfPrefix); + static constexpr size_t kTypenameSize = + __builtin_strlen(__PRETTY_FUNCTION__) - sizeof(kSelfPrefix) - 1; + // NameBuffer is an indirection that is needed to make sure that only a + // substring of __PRETTY_FUNCTION__ gets materialized in the binary. + static constexpr auto buffer = + NameBuffer::FromCString(kTypenameStart); + return buffer.name; +} + +#else +#define CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME 0 +#endif + +struct HeapObjectName { + const char* value; + bool name_was_hidden; +}; + +enum class HeapObjectNameForUnnamedObject : uint8_t { + kUseClassNameIfSupported, + kUseHiddenName, +}; + +class V8_EXPORT NameTraitBase { + protected: + static HeapObjectName GetNameFromTypeSignature(const char*); +}; + +// Trait that specifies how the garbage collector retrieves the name for a +// given object. +template +class NameTrait final : public NameTraitBase { + public: + static constexpr bool HasNonHiddenName() { +#if CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME + return true; +#elif CPPGC_SUPPORTS_OBJECT_NAMES + return true; +#else // !CPPGC_SUPPORTS_OBJECT_NAMES + return std::is_base_of_v; +#endif // !CPPGC_SUPPORTS_OBJECT_NAMES + } + + static HeapObjectName GetName( + const void* obj, HeapObjectNameForUnnamedObject name_retrieval_mode) { + return GetNameFor(static_cast(obj), name_retrieval_mode); + } + + private: + static HeapObjectName GetNameFor(const NameProvider* name_provider, + HeapObjectNameForUnnamedObject) { + // Objects inheriting from `NameProvider` are not considered unnamed as + // users already provided a name for them. + return {name_provider->GetHumanReadableName(), false}; + } + + static HeapObjectName GetNameFor( + const void*, HeapObjectNameForUnnamedObject name_retrieval_mode) { + if (name_retrieval_mode == HeapObjectNameForUnnamedObject::kUseHiddenName) + return {NameProvider::kHiddenName, true}; + +#if CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME + return {GetTypename(), false}; +#elif CPPGC_SUPPORTS_OBJECT_NAMES + +#if defined(V8_CC_GNU) +#define PRETTY_FUNCTION_VALUE __PRETTY_FUNCTION__ +#elif defined(V8_CC_MSVC) +#define PRETTY_FUNCTION_VALUE __FUNCSIG__ +#else +#define PRETTY_FUNCTION_VALUE nullptr +#endif + + static const HeapObjectName leaky_name = + GetNameFromTypeSignature(PRETTY_FUNCTION_VALUE); + return leaky_name; + +#undef PRETTY_FUNCTION_VALUE + +#else // !CPPGC_SUPPORTS_OBJECT_NAMES + // We wanted to use a class name but were unable to provide one due to + // compiler limitations or build configuration. As such, return the hidden + // name with name_was_hidden=false, which will cause this object to be + // visible in the snapshot. + return {NameProvider::kHiddenName, false}; +#endif // !CPPGC_SUPPORTS_OBJECT_NAMES + } +}; + +using NameCallback = HeapObjectName (*)(const void*, + HeapObjectNameForUnnamedObject); + +} // namespace internal +} // namespace cppgc + +#undef CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME + +#endif // INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/persistent-node.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/persistent-node.h new file mode 100644 index 000000000..413ddd918 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/persistent-node.h @@ -0,0 +1,219 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_ +#define INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_ + +#include +#include +#include + +#include "cppgc/internal/logging.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +class CrossThreadPersistentRegion; +class FatalOutOfMemoryHandler; +class HeapBase; +class RootVisitor; + +// PersistentNode represents a variant of two states: +// 1) traceable node with a back pointer to the Persistent object; +// 2) freelist entry. +class PersistentNode final { + public: + PersistentNode() = default; + + PersistentNode(const PersistentNode&) = delete; + PersistentNode& operator=(const PersistentNode&) = delete; + + void InitializeAsUsedNode(void* owner, TraceRootCallback trace) { + CPPGC_DCHECK(trace); + owner_ = owner; + trace_ = trace; + } + + void InitializeAsFreeNode(PersistentNode* next) { + next_ = next; + trace_ = nullptr; + } + + void UpdateOwner(void* owner) { + CPPGC_DCHECK(IsUsed()); + owner_ = owner; + } + + PersistentNode* FreeListNext() const { + CPPGC_DCHECK(!IsUsed()); + return next_; + } + + void Trace(RootVisitor& root_visitor) const { + CPPGC_DCHECK(IsUsed()); + trace_(root_visitor, owner_); + } + + bool IsUsed() const { return trace_; } + + void* owner() const { + CPPGC_DCHECK(IsUsed()); + return owner_; + } + + private: + // PersistentNode acts as a designated union: + // If trace_ != nullptr, owner_ points to the corresponding Persistent handle. + // Otherwise, next_ points to the next freed PersistentNode. + union { + void* owner_ = nullptr; + PersistentNode* next_; + }; + TraceRootCallback trace_ = nullptr; +}; + +class V8_EXPORT PersistentRegionBase { + using PersistentNodeSlots = std::array; + + public: + // Clears Persistent fields to avoid stale pointers after heap teardown. + ~PersistentRegionBase(); + + PersistentRegionBase(const PersistentRegionBase&) = delete; + PersistentRegionBase& operator=(const PersistentRegionBase&) = delete; + + void Iterate(RootVisitor&); + + size_t NodesInUse() const; + + void ClearAllUsedNodes(); + + protected: + explicit PersistentRegionBase(const FatalOutOfMemoryHandler& oom_handler); + + PersistentNode* TryAllocateNodeFromFreeList(void* owner, + TraceRootCallback trace) { + PersistentNode* node = nullptr; + if (V8_LIKELY(free_list_head_)) { + node = free_list_head_; + free_list_head_ = free_list_head_->FreeListNext(); + CPPGC_DCHECK(!node->IsUsed()); + node->InitializeAsUsedNode(owner, trace); + nodes_in_use_++; + } + return node; + } + + void FreeNode(PersistentNode* node) { + CPPGC_DCHECK(node); + CPPGC_DCHECK(node->IsUsed()); + node->InitializeAsFreeNode(free_list_head_); + free_list_head_ = node; + CPPGC_DCHECK(nodes_in_use_ > 0); + nodes_in_use_--; + } + + PersistentNode* RefillFreeListAndAllocateNode(void* owner, + TraceRootCallback trace); + + private: + template + void ClearAllUsedNodes(); + + void RefillFreeList(); + + std::vector> nodes_; + PersistentNode* free_list_head_ = nullptr; + size_t nodes_in_use_ = 0; + const FatalOutOfMemoryHandler& oom_handler_; + + friend class CrossThreadPersistentRegion; +}; + +// Variant of PersistentRegionBase that checks whether the allocation and +// freeing happens only on the thread that created the heap. +class V8_EXPORT PersistentRegion final : public PersistentRegionBase { + public: + V8_INLINE PersistentRegion(const HeapBase& heap, + const FatalOutOfMemoryHandler& oom_handler) + : PersistentRegionBase(oom_handler), heap_(heap) { + CPPGC_DCHECK(IsCreationThread()); + } + // Clears Persistent fields to avoid stale pointers after heap teardown. + ~PersistentRegion() = default; + + PersistentRegion(const PersistentRegion&) = delete; + PersistentRegion& operator=(const PersistentRegion&) = delete; + + V8_INLINE PersistentNode* AllocateNode(void* owner, TraceRootCallback trace) { + CPPGC_DCHECK(IsCreationThread()); + auto* node = TryAllocateNodeFromFreeList(owner, trace); + if (V8_LIKELY(node)) return node; + + // Slow path allocation allows for checking thread correspondence. + CPPGC_CHECK(IsCreationThread()); + return RefillFreeListAndAllocateNode(owner, trace); + } + + V8_INLINE void FreeNode(PersistentNode* node) { + CPPGC_DCHECK(IsCreationThread()); + PersistentRegionBase::FreeNode(node); + } + + private: + bool IsCreationThread(); + + const HeapBase& heap_; +}; + +// CrossThreadPersistent uses PersistentRegionBase but protects it using this +// lock when needed. +class V8_EXPORT PersistentRegionLock final { + public: + PersistentRegionLock(); + ~PersistentRegionLock(); + + static void AssertLocked(); +}; + +// Variant of PersistentRegionBase that checks whether the PersistentRegionLock +// is locked. +class V8_EXPORT CrossThreadPersistentRegion final + : protected PersistentRegionBase { + public: + explicit CrossThreadPersistentRegion(const FatalOutOfMemoryHandler&); + // Clears Persistent fields to avoid stale pointers after heap teardown. + ~CrossThreadPersistentRegion(); + + CrossThreadPersistentRegion(const CrossThreadPersistentRegion&) = delete; + CrossThreadPersistentRegion& operator=(const CrossThreadPersistentRegion&) = + delete; + + V8_INLINE PersistentNode* AllocateNode(void* owner, TraceRootCallback trace) { + PersistentRegionLock::AssertLocked(); + auto* node = TryAllocateNodeFromFreeList(owner, trace); + if (V8_LIKELY(node)) return node; + + return RefillFreeListAndAllocateNode(owner, trace); + } + + V8_INLINE void FreeNode(PersistentNode* node) { + PersistentRegionLock::AssertLocked(); + PersistentRegionBase::FreeNode(node); + } + + void Iterate(RootVisitor&); + + size_t NodesInUse() const; + + void ClearAllUsedNodes(); +}; + +} // namespace internal + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/pointer-policies.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/pointer-policies.h new file mode 100644 index 000000000..432cdb47c --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/pointer-policies.h @@ -0,0 +1,280 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_ +#define INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_ + +#include +#include + +#include "cppgc/internal/member-storage.h" +#include "cppgc/internal/write-barrier.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/source-location.h" +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +class HeapBase; +class PersistentRegion; +class CrossThreadPersistentRegion; + +// Tags to distinguish between strong and weak member types. +class StrongMemberTag; +class WeakMemberTag; +class UntracedMemberTag; + +struct DijkstraWriteBarrierPolicy { + // Since in initializing writes the source object is always white, having no + // barrier doesn't break the tri-color invariant. + V8_INLINE static void InitializingBarrier(const void*, const void*) {} + V8_INLINE static void InitializingBarrier(const void*, RawPointer storage) { + } +#if defined(CPPGC_POINTER_COMPRESSION) + V8_INLINE static void InitializingBarrier(const void*, + CompressedPointer storage) {} +#endif + + template + V8_INLINE static void AssigningBarrier(const void* slot, + const void* value) { +#ifdef CPPGC_SLIM_WRITE_BARRIER + if (V8_UNLIKELY(WriteBarrier::IsEnabled())) + WriteBarrier::CombinedWriteBarrierSlow(slot); +#else // !CPPGC_SLIM_WRITE_BARRIER + WriteBarrier::Params params; + const WriteBarrier::Type type = + WriteBarrier::GetWriteBarrierType(slot, value, params); + WriteBarrier(type, params, slot, value); +#endif // !CPPGC_SLIM_WRITE_BARRIER + } + + template + V8_INLINE static void AssigningBarrier(const void* slot, RawPointer storage) { + static_assert( + SlotType == WriteBarrierSlotType::kUncompressed, + "Assigning storages of Member and UncompressedMember is not supported"); +#ifdef CPPGC_SLIM_WRITE_BARRIER + if (V8_UNLIKELY(WriteBarrier::IsEnabled())) + WriteBarrier::CombinedWriteBarrierSlow(slot); +#else // !CPPGC_SLIM_WRITE_BARRIER + WriteBarrier::Params params; + const WriteBarrier::Type type = + WriteBarrier::GetWriteBarrierType(slot, storage, params); + WriteBarrier(type, params, slot, storage.Load()); +#endif // !CPPGC_SLIM_WRITE_BARRIER + } + +#if defined(CPPGC_POINTER_COMPRESSION) + template + V8_INLINE static void AssigningBarrier(const void* slot, + CompressedPointer storage) { + static_assert( + SlotType == WriteBarrierSlotType::kCompressed, + "Assigning storages of Member and UncompressedMember is not supported"); +#ifdef CPPGC_SLIM_WRITE_BARRIER + if (V8_UNLIKELY(WriteBarrier::IsEnabled())) + WriteBarrier::CombinedWriteBarrierSlow(slot); +#else // !CPPGC_SLIM_WRITE_BARRIER + WriteBarrier::Params params; + const WriteBarrier::Type type = + WriteBarrier::GetWriteBarrierType(slot, storage, params); + WriteBarrier(type, params, slot, storage.Load()); +#endif // !CPPGC_SLIM_WRITE_BARRIER + } +#endif // defined(CPPGC_POINTER_COMPRESSION) + + private: + V8_INLINE static void WriteBarrier(WriteBarrier::Type type, + const WriteBarrier::Params& params, + const void* slot, const void* value) { + switch (type) { + case WriteBarrier::Type::kGenerational: + WriteBarrier::GenerationalBarrier< + WriteBarrier::GenerationalBarrierType::kPreciseSlot>(params, slot); + break; + case WriteBarrier::Type::kMarking: + WriteBarrier::DijkstraMarkingBarrier(params, value); + break; + case WriteBarrier::Type::kNone: + break; + } + } +}; + +struct NoWriteBarrierPolicy { + V8_INLINE static void InitializingBarrier(const void*, const void*) {} + V8_INLINE static void InitializingBarrier(const void*, RawPointer storage) {} +#if defined(CPPGC_POINTER_COMPRESSION) + V8_INLINE static void InitializingBarrier(const void*, + CompressedPointer storage) {} +#endif + template + V8_INLINE static void AssigningBarrier(const void*, const void*) {} + template + V8_INLINE static void AssigningBarrier(const void*, MemberStorage) {} +}; + +class V8_EXPORT SameThreadEnabledCheckingPolicyBase { + protected: + void CheckPointerImpl(const void* ptr, bool points_to_payload, + bool check_off_heap_assignments); + + const HeapBase* heap_ = nullptr; +}; + +template +class V8_EXPORT SameThreadEnabledCheckingPolicy + : private SameThreadEnabledCheckingPolicyBase { + protected: + template + V8_INLINE void CheckPointer(RawPointer raw_pointer) { + if (raw_pointer.IsCleared() || raw_pointer.IsSentinel()) { + return; + } + CheckPointersImplTrampoline::Call( + this, static_cast(raw_pointer.Load())); + } +#if defined(CPPGC_POINTER_COMPRESSION) + template + V8_INLINE void CheckPointer(CompressedPointer compressed_pointer) { + if (compressed_pointer.IsCleared() || compressed_pointer.IsSentinel()) { + return; + } + CheckPointersImplTrampoline::Call( + this, static_cast(compressed_pointer.Load())); + } +#endif + template + void CheckPointer(const T* ptr) { + if (!ptr || (kSentinelPointer == ptr)) { + return; + } + CheckPointersImplTrampoline::Call(this, ptr); + } + + private: + template > + struct CheckPointersImplTrampoline { + static void Call(SameThreadEnabledCheckingPolicy* policy, const T* ptr) { + policy->CheckPointerImpl(ptr, false, kCheckOffHeapAssignments); + } + }; + + template + struct CheckPointersImplTrampoline { + static void Call(SameThreadEnabledCheckingPolicy* policy, const T* ptr) { + policy->CheckPointerImpl(ptr, IsGarbageCollectedTypeV, + kCheckOffHeapAssignments); + } + }; +}; + +class DisabledCheckingPolicy { + protected: + template + V8_INLINE void CheckPointer(T*) {} + template + V8_INLINE void CheckPointer(RawPointer) {} +#if defined(CPPGC_POINTER_COMPRESSION) + template + V8_INLINE void CheckPointer(CompressedPointer) {} +#endif +}; + +#ifdef CPPGC_ENABLE_SLOW_API_CHECKS +// Off heap members are not connected to object graph and thus cannot ressurect +// dead objects. +using DefaultMemberCheckingPolicy = + SameThreadEnabledCheckingPolicy; +using DefaultPersistentCheckingPolicy = + SameThreadEnabledCheckingPolicy; +#else // !CPPGC_ENABLE_SLOW_API_CHECKS +using DefaultMemberCheckingPolicy = DisabledCheckingPolicy; +using DefaultPersistentCheckingPolicy = DisabledCheckingPolicy; +#endif // !CPPGC_ENABLE_SLOW_API_CHECKS +// For CT(W)P neither marking information (for value), nor objectstart bitmap +// (for slot) are guaranteed to be present because there's no synchronization +// between heaps after marking. +using DefaultCrossThreadPersistentCheckingPolicy = DisabledCheckingPolicy; + +class KeepLocationPolicy { + public: + constexpr SourceLocation Location() const { return location_; } + + protected: + constexpr KeepLocationPolicy() = default; + constexpr explicit KeepLocationPolicy(SourceLocation location) + : location_(location) {} + + // KeepLocationPolicy must not copy underlying source locations. + KeepLocationPolicy(const KeepLocationPolicy&) = delete; + KeepLocationPolicy& operator=(const KeepLocationPolicy&) = delete; + + // Location of the original moved from object should be preserved. + KeepLocationPolicy(KeepLocationPolicy&&) = default; + KeepLocationPolicy& operator=(KeepLocationPolicy&&) = default; + + private: + SourceLocation location_; +}; + +class IgnoreLocationPolicy { + public: + constexpr SourceLocation Location() const { return {}; } + + protected: + constexpr IgnoreLocationPolicy() = default; + constexpr explicit IgnoreLocationPolicy(SourceLocation) {} +}; + +#if CPPGC_SUPPORTS_OBJECT_NAMES +using DefaultLocationPolicy = KeepLocationPolicy; +#else +using DefaultLocationPolicy = IgnoreLocationPolicy; +#endif + +struct StrongPersistentPolicy { + using IsStrongPersistent = std::true_type; + static V8_EXPORT PersistentRegion& GetPersistentRegion(const void* object); +}; + +struct WeakPersistentPolicy { + using IsStrongPersistent = std::false_type; + static V8_EXPORT PersistentRegion& GetPersistentRegion(const void* object); +}; + +struct StrongCrossThreadPersistentPolicy { + using IsStrongPersistent = std::true_type; + static V8_EXPORT CrossThreadPersistentRegion& GetPersistentRegion( + const void* object); +}; + +struct WeakCrossThreadPersistentPolicy { + using IsStrongPersistent = std::false_type; + static V8_EXPORT CrossThreadPersistentRegion& GetPersistentRegion( + const void* object); +}; + +// Forward declarations setting up the default policies. +template +class BasicCrossThreadPersistent; +template +class BasicPersistent; +template +class BasicMember; + +} // namespace internal + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/write-barrier.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/write-barrier.h new file mode 100644 index 000000000..566724d30 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/internal/write-barrier.h @@ -0,0 +1,487 @@ +// Copyright 2020 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 INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_ +#define INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_ + +#include +#include + +#include "cppgc/heap-handle.h" +#include "cppgc/heap-state.h" +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/atomic-entry-flag.h" +#include "cppgc/internal/base-page-handle.h" +#include "cppgc/internal/member-storage.h" +#include "cppgc/platform.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +#if defined(CPPGC_CAGED_HEAP) +#include "cppgc/internal/caged-heap-local-data.h" +#include "cppgc/internal/caged-heap.h" +#endif + +namespace cppgc { + +class HeapHandle; + +namespace internal { + +#if defined(CPPGC_CAGED_HEAP) +class WriteBarrierTypeForCagedHeapPolicy; +#else // !CPPGC_CAGED_HEAP +class WriteBarrierTypeForNonCagedHeapPolicy; +#endif // !CPPGC_CAGED_HEAP + +class V8_EXPORT WriteBarrier final { + public: + enum class Type : uint8_t { + kNone, + kMarking, + kGenerational, + }; + + enum class GenerationalBarrierType : uint8_t { + kPreciseSlot, + kPreciseUncompressedSlot, + kImpreciseSlot, + }; + + struct Params { + HeapHandle* heap = nullptr; +#if V8_ENABLE_CHECKS + Type type = Type::kNone; +#endif // !V8_ENABLE_CHECKS +#if defined(CPPGC_CAGED_HEAP) + uintptr_t slot_offset = 0; + uintptr_t value_offset = 0; +#endif // CPPGC_CAGED_HEAP + }; + + enum class ValueMode { + kValuePresent, + kNoValuePresent, + }; + + // Returns the required write barrier for a given `slot` and `value`. + static V8_INLINE Type GetWriteBarrierType(const void* slot, const void* value, + Params& params); + // Returns the required write barrier for a given `slot` and `value`. + template + static V8_INLINE Type GetWriteBarrierType(const void* slot, MemberStorage, + Params& params); + // Returns the required write barrier for a given `slot`. + template + static V8_INLINE Type GetWriteBarrierType(const void* slot, Params& params, + HeapHandleCallback callback); + // Returns the required write barrier for a given `value`. + static V8_INLINE Type GetWriteBarrierType(const void* value, Params& params); + +#ifdef CPPGC_SLIM_WRITE_BARRIER + // A write barrier that combines `GenerationalBarrier()` and + // `DijkstraMarkingBarrier()`. We only pass a single parameter here to clobber + // as few registers as possible. + template + static V8_NOINLINE void V8_PRESERVE_MOST + CombinedWriteBarrierSlow(const void* slot); +#endif // CPPGC_SLIM_WRITE_BARRIER + + static V8_INLINE void DijkstraMarkingBarrier(const Params& params, + const void* object); + static V8_INLINE void DijkstraMarkingBarrierRange( + const Params& params, const void* first_element, size_t element_size, + size_t number_of_elements, TraceCallback trace_callback); + static V8_INLINE void SteeleMarkingBarrier(const Params& params, + const void* object); +#if defined(CPPGC_YOUNG_GENERATION) + template + static V8_INLINE void GenerationalBarrier(const Params& params, + const void* slot); +#else // !CPPGC_YOUNG_GENERATION + template + static V8_INLINE void GenerationalBarrier(const Params& params, + const void* slot){} +#endif // CPPGC_YOUNG_GENERATION + +#if V8_ENABLE_CHECKS + static void CheckParams(Type expected_type, const Params& params); +#else // !V8_ENABLE_CHECKS + static void CheckParams(Type expected_type, const Params& params) {} +#endif // !V8_ENABLE_CHECKS + + // The FlagUpdater class allows cppgc internal to update + // |write_barrier_enabled_|. + class FlagUpdater; + static bool IsEnabled() { return write_barrier_enabled_.MightBeEntered(); } + + private: + WriteBarrier() = delete; + +#if defined(CPPGC_CAGED_HEAP) + using WriteBarrierTypePolicy = WriteBarrierTypeForCagedHeapPolicy; +#else // !CPPGC_CAGED_HEAP + using WriteBarrierTypePolicy = WriteBarrierTypeForNonCagedHeapPolicy; +#endif // !CPPGC_CAGED_HEAP + + static void DijkstraMarkingBarrierSlow(const void* value); + static void DijkstraMarkingBarrierSlowWithSentinelCheck(const void* value); + static void DijkstraMarkingBarrierRangeSlow(HeapHandle& heap_handle, + const void* first_element, + size_t element_size, + size_t number_of_elements, + TraceCallback trace_callback); + static void SteeleMarkingBarrierSlow(const void* value); + static void SteeleMarkingBarrierSlowWithSentinelCheck(const void* value); + +#if defined(CPPGC_YOUNG_GENERATION) + static CagedHeapLocalData& GetLocalData(HeapHandle&); + static void GenerationalBarrierSlow(const CagedHeapLocalData& local_data, + const AgeTable& age_table, + const void* slot, uintptr_t value_offset, + HeapHandle* heap_handle); + static void GenerationalBarrierForUncompressedSlotSlow( + const CagedHeapLocalData& local_data, const AgeTable& age_table, + const void* slot, uintptr_t value_offset, HeapHandle* heap_handle); + static void GenerationalBarrierForSourceObjectSlow( + const CagedHeapLocalData& local_data, const void* object, + HeapHandle* heap_handle); +#endif // CPPGC_YOUNG_GENERATION + + static AtomicEntryFlag write_barrier_enabled_; +}; + +template +V8_INLINE WriteBarrier::Type SetAndReturnType(WriteBarrier::Params& params) { + if constexpr (type == WriteBarrier::Type::kNone) + return WriteBarrier::Type::kNone; +#if V8_ENABLE_CHECKS + params.type = type; +#endif // !V8_ENABLE_CHECKS + return type; +} + +#if defined(CPPGC_CAGED_HEAP) +class V8_EXPORT WriteBarrierTypeForCagedHeapPolicy final { + public: + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return ValueModeDispatch::Get(slot, value, params, callback); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, MemberStorage value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return ValueModeDispatch::Get(slot, value, params, callback); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return GetNoSlot(value, params, callback); + } + + private: + WriteBarrierTypeForCagedHeapPolicy() = delete; + + template + static V8_INLINE WriteBarrier::Type GetNoSlot(const void* value, + WriteBarrier::Params& params, + HeapHandleCallback) { + const bool within_cage = CagedHeapBase::IsWithinCage(value); + if (!within_cage) return WriteBarrier::Type::kNone; + + // We know that |value| points either within the normal page or to the + // beginning of large-page, so extract the page header by bitmasking. + BasePageHandle* page = + BasePageHandle::FromPayload(const_cast(value)); + + HeapHandle& heap_handle = page->heap_handle(); + if (V8_UNLIKELY(heap_handle.is_incremental_marking_in_progress())) { + return SetAndReturnType(params); + } + + return SetAndReturnType(params); + } + + template + struct ValueModeDispatch; +}; + +template <> +struct WriteBarrierTypeForCagedHeapPolicy::ValueModeDispatch< + WriteBarrier::ValueMode::kValuePresent> { + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, + MemberStorage storage, + WriteBarrier::Params& params, + HeapHandleCallback) { + if (V8_LIKELY(!WriteBarrier::IsEnabled())) + return SetAndReturnType(params); + + return BarrierEnabledGet(slot, storage.Load(), params); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value, + WriteBarrier::Params& params, + HeapHandleCallback) { + if (V8_LIKELY(!WriteBarrier::IsEnabled())) + return SetAndReturnType(params); + + return BarrierEnabledGet(slot, value, params); + } + + private: + static V8_INLINE WriteBarrier::Type BarrierEnabledGet( + const void* slot, const void* value, WriteBarrier::Params& params) { + const bool within_cage = CagedHeapBase::AreWithinCage(slot, value); + if (!within_cage) return WriteBarrier::Type::kNone; + + // We know that |value| points either within the normal page or to the + // beginning of large-page, so extract the page header by bitmasking. + BasePageHandle* page = + BasePageHandle::FromPayload(const_cast(value)); + + HeapHandle& heap_handle = page->heap_handle(); + if (V8_LIKELY(!heap_handle.is_incremental_marking_in_progress())) { +#if defined(CPPGC_YOUNG_GENERATION) + if (!heap_handle.is_young_generation_enabled()) + return WriteBarrier::Type::kNone; + params.heap = &heap_handle; + params.slot_offset = CagedHeapBase::OffsetFromAddress(slot); + params.value_offset = CagedHeapBase::OffsetFromAddress(value); + return SetAndReturnType(params); +#else // !CPPGC_YOUNG_GENERATION + return SetAndReturnType(params); +#endif // !CPPGC_YOUNG_GENERATION + } + + // Use marking barrier. + params.heap = &heap_handle; + return SetAndReturnType(params); + } +}; + +template <> +struct WriteBarrierTypeForCagedHeapPolicy::ValueModeDispatch< + WriteBarrier::ValueMode::kNoValuePresent> { + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, const void*, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + if (V8_LIKELY(!WriteBarrier::IsEnabled())) + return SetAndReturnType(params); + + HeapHandle& handle = callback(); +#if defined(CPPGC_YOUNG_GENERATION) + if (V8_LIKELY(!handle.is_incremental_marking_in_progress())) { + if (!handle.is_young_generation_enabled()) { + return WriteBarrier::Type::kNone; + } + params.heap = &handle; + // Check if slot is on stack. + if (V8_UNLIKELY(!CagedHeapBase::IsWithinCage(slot))) { + return SetAndReturnType(params); + } + params.slot_offset = CagedHeapBase::OffsetFromAddress(slot); + return SetAndReturnType(params); + } +#else // !defined(CPPGC_YOUNG_GENERATION) + if (V8_UNLIKELY(!handle.is_incremental_marking_in_progress())) { + return SetAndReturnType(params); + } +#endif // !defined(CPPGC_YOUNG_GENERATION) + params.heap = &handle; + return SetAndReturnType(params); + } +}; + +#endif // CPPGC_CAGED_HEAP + +class V8_EXPORT WriteBarrierTypeForNonCagedHeapPolicy final { + public: + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return ValueModeDispatch::Get(slot, value, params, callback); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* slot, RawPointer value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + return ValueModeDispatch::Get(slot, value.Load(), params, + callback); + } + + template + static V8_INLINE WriteBarrier::Type Get(const void* value, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + // The slot will never be used in `Get()` below. + return Get(nullptr, value, params, + callback); + } + + private: + template + struct ValueModeDispatch; + + WriteBarrierTypeForNonCagedHeapPolicy() = delete; +}; + +template <> +struct WriteBarrierTypeForNonCagedHeapPolicy::ValueModeDispatch< + WriteBarrier::ValueMode::kValuePresent> { + template + static V8_INLINE WriteBarrier::Type Get(const void*, const void* object, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + // The following check covers nullptr as well as sentinel pointer. + if (object <= static_cast(kSentinelPointer)) { + return SetAndReturnType(params); + } + if (V8_LIKELY(!WriteBarrier::IsEnabled())) { + return SetAndReturnType(params); + } + // We know that |object| is within the normal page or in the beginning of a + // large page, so extract the page header by bitmasking. + BasePageHandle* page = + BasePageHandle::FromPayload(const_cast(object)); + + HeapHandle& heap_handle = page->heap_handle(); + if (V8_LIKELY(heap_handle.is_incremental_marking_in_progress())) { + return SetAndReturnType(params); + } + return SetAndReturnType(params); + } +}; + +template <> +struct WriteBarrierTypeForNonCagedHeapPolicy::ValueModeDispatch< + WriteBarrier::ValueMode::kNoValuePresent> { + template + static V8_INLINE WriteBarrier::Type Get(const void*, const void*, + WriteBarrier::Params& params, + HeapHandleCallback callback) { + if (V8_UNLIKELY(WriteBarrier::IsEnabled())) { + HeapHandle& handle = callback(); + if (V8_LIKELY(handle.is_incremental_marking_in_progress())) { + params.heap = &handle; + return SetAndReturnType(params); + } + } + return WriteBarrier::Type::kNone; + } +}; + +// static +WriteBarrier::Type WriteBarrier::GetWriteBarrierType( + const void* slot, const void* value, WriteBarrier::Params& params) { + return WriteBarrierTypePolicy::Get(slot, value, + params, []() {}); +} + +// static +template +WriteBarrier::Type WriteBarrier::GetWriteBarrierType( + const void* slot, MemberStorage value, WriteBarrier::Params& params) { + return WriteBarrierTypePolicy::Get(slot, value, + params, []() {}); +} + +// static +template +WriteBarrier::Type WriteBarrier::GetWriteBarrierType( + const void* slot, WriteBarrier::Params& params, + HeapHandleCallback callback) { + return WriteBarrierTypePolicy::Get( + slot, nullptr, params, callback); +} + +// static +WriteBarrier::Type WriteBarrier::GetWriteBarrierType( + const void* value, WriteBarrier::Params& params) { + return WriteBarrierTypePolicy::Get(value, params, + []() {}); +} + +// static +void WriteBarrier::DijkstraMarkingBarrier(const Params& params, + const void* object) { + CheckParams(Type::kMarking, params); +#if defined(CPPGC_CAGED_HEAP) + // Caged heap already filters out sentinels. + DijkstraMarkingBarrierSlow(object); +#else // !CPPGC_CAGED_HEAP + DijkstraMarkingBarrierSlowWithSentinelCheck(object); +#endif // !CPPGC_CAGED_HEAP +} + +// static +void WriteBarrier::DijkstraMarkingBarrierRange(const Params& params, + const void* first_element, + size_t element_size, + size_t number_of_elements, + TraceCallback trace_callback) { + CheckParams(Type::kMarking, params); + DijkstraMarkingBarrierRangeSlow(*params.heap, first_element, element_size, + number_of_elements, trace_callback); +} + +// static +void WriteBarrier::SteeleMarkingBarrier(const Params& params, + const void* object) { + CheckParams(Type::kMarking, params); +#if defined(CPPGC_CAGED_HEAP) + // Caged heap already filters out sentinels. + SteeleMarkingBarrierSlow(object); +#else // !CPPGC_CAGED_HEAP + SteeleMarkingBarrierSlowWithSentinelCheck(object); +#endif // !CPPGC_CAGED_HEAP +} + +#if defined(CPPGC_YOUNG_GENERATION) + +// static +template +void WriteBarrier::GenerationalBarrier(const Params& params, const void* slot) { + CheckParams(Type::kGenerational, params); + + const CagedHeapLocalData& local_data = CagedHeapLocalData::Get(); + const AgeTable& age_table = local_data.age_table; + + // Bail out if the slot (precise or imprecise) is in young generation. + if (V8_LIKELY(age_table.GetAge(params.slot_offset) == AgeTable::Age::kYoung)) + return; + + // Dispatch between different types of barriers. + // TODO(chromium:1029379): Consider reload local_data in the slow path to + // reduce register pressure. + if constexpr (type == GenerationalBarrierType::kPreciseSlot) { + GenerationalBarrierSlow(local_data, age_table, slot, params.value_offset, + params.heap); + } else if constexpr (type == + GenerationalBarrierType::kPreciseUncompressedSlot) { + GenerationalBarrierForUncompressedSlotSlow( + local_data, age_table, slot, params.value_offset, params.heap); + } else { + GenerationalBarrierForSourceObjectSlow(local_data, slot, params.heap); + } +} + +#endif // !CPPGC_YOUNG_GENERATION + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/liveness-broker.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/liveness-broker.h new file mode 100644 index 000000000..2c94f1c0f --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/liveness-broker.h @@ -0,0 +1,78 @@ +// Copyright 2020 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 INCLUDE_CPPGC_LIVENESS_BROKER_H_ +#define INCLUDE_CPPGC_LIVENESS_BROKER_H_ + +#include "cppgc/heap.h" +#include "cppgc/member.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/trace-trait.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +namespace internal { +class LivenessBrokerFactory; +} // namespace internal + +/** + * The broker is passed to weak callbacks to allow (temporarily) querying + * the liveness state of an object. References to non-live objects must be + * cleared when `IsHeapObjectAlive()` returns false. + * + * \code + * class GCedWithCustomWeakCallback final + * : public GarbageCollected { + * public: + * UntracedMember bar; + * + * void CustomWeakCallbackMethod(const LivenessBroker& broker) { + * if (!broker.IsHeapObjectAlive(bar)) + * bar = nullptr; + * } + * + * void Trace(cppgc::Visitor* visitor) const { + * visitor->RegisterWeakCallbackMethod< + * GCedWithCustomWeakCallback, + * &GCedWithCustomWeakCallback::CustomWeakCallbackMethod>(this); + * } + * }; + * \endcode + */ +class V8_EXPORT LivenessBroker final { + public: + template + bool IsHeapObjectAlive(const T* object) const { + // - nullptr objects are considered alive to allow weakness to be used from + // stack while running into a conservative GC. Treating nullptr as dead + // would mean that e.g. custom collections could not be strongified on + // stack. + // - Sentinel pointers are also preserved in weakness and not cleared. + return !object || object == kSentinelPointer || + IsHeapObjectAliveImpl( + TraceTrait::GetTraceDescriptor(object).base_object_payload); + } + + template + bool IsHeapObjectAlive(const WeakMember& weak_member) const { + return IsHeapObjectAlive(weak_member.Get()); + } + + template + bool IsHeapObjectAlive(const UntracedMember& untraced_member) const { + return IsHeapObjectAlive(untraced_member.Get()); + } + + private: + LivenessBroker() = default; + + bool IsHeapObjectAliveImpl(const void*) const; + + friend class internal::LivenessBrokerFactory; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_LIVENESS_BROKER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/macros.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/macros.h new file mode 100644 index 000000000..10a6f8eba --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/macros.h @@ -0,0 +1,56 @@ +// Copyright 2020 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 INCLUDE_CPPGC_MACROS_H_ +#define INCLUDE_CPPGC_MACROS_H_ + +#include + +#include "cppgc/internal/compiler-specific.h" + +namespace cppgc { + +#define CPPGC_DISALLOW_NEW() \ + public: \ + using IsDisallowNewMarker CPPGC_UNUSED = int; \ + void* operator new(size_t, void* location) { return location; } \ + void* operator new(size_t) = delete; \ + static_assert(true, "Force semicolon.") + +// Use CPPGC_STACK_ALLOCATED if the object is only stack allocated. +// Add the CPPGC_STACK_ALLOCATED_IGNORE annotation on a case-by-case basis when +// enforcement of CPPGC_STACK_ALLOCATED should be suppressed. +#if defined(__clang__) + +#define CPPGC_STACK_ALLOCATED() \ + public: \ + using IsStackAllocatedTypeMarker CPPGC_UNUSED = int; \ + \ + private: \ + void* operator new(size_t) = delete; \ + void* operator new(size_t, void*) = delete; \ + static_assert(true, "Force semicolon.") + +#define CPPGC_STACK_ALLOCATED_IGNORE(bug_or_reason) \ + __attribute__((annotate("stack_allocated_ignore"))) + +#define CPPGC_PLUGIN_IGNORE(bug_or_reason) \ + __attribute__((annotate("blink_gc_plugin_ignore"), \ + annotate("stack_allocated_ignore"))) + +#else // !defined(__clang__) + +#define CPPGC_STACK_ALLOCATED() static_assert(true, "Force semicolon.") +#define CPPGC_STACK_ALLOCATED_IGNORE(bug_or_reason) +#define CPPGC_PLUGIN_IGNORE(bug_or_reason) + +#endif // !defined(__clang__) + +template +concept IsStackAllocatedType = + requires { typename T::IsStackAllocatedTypeMarker; }; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_MACROS_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/member.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/member.h new file mode 100644 index 000000000..c028bcd06 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/member.h @@ -0,0 +1,669 @@ +// Copyright 2020 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 INCLUDE_CPPGC_MEMBER_H_ +#define INCLUDE_CPPGC_MEMBER_H_ + +#include +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/internal/member-storage.h" +#include "cppgc/internal/pointer-policies.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +namespace subtle { +class HeapConsistency; +template +class TaggedUncompressedMember; +} // namespace subtle + +class Visitor; + +namespace internal { + +// MemberBase always refers to the object as const object and defers to +// BasicMember on casting to the right type as needed. +template +class V8_TRIVIAL_ABI MemberBase { + public: + using RawStorage = StorageType; + + protected: + struct AtomicInitializerTag {}; + + V8_INLINE MemberBase() = default; + V8_INLINE explicit MemberBase(const void* value) : raw_(value) {} + V8_INLINE MemberBase(const void* value, AtomicInitializerTag) + : raw_(value, typename RawStorage::AtomicInitializerTag{}) {} + + V8_INLINE explicit MemberBase(RawStorage raw) : raw_(raw) {} + V8_INLINE explicit MemberBase(std::nullptr_t) : raw_(nullptr) {} + V8_INLINE explicit MemberBase(SentinelPointer s) : raw_(s) {} + + V8_INLINE const void** GetRawSlot() const { + return reinterpret_cast(const_cast(this)); + } + V8_INLINE const void* GetRaw() const { return raw_.Load(); } + V8_INLINE void SetRaw(void* value) { raw_.Store(value); } + + V8_INLINE const void* GetRawAtomic() const { return raw_.LoadAtomic(); } + V8_INLINE void SetRawAtomic(const void* value) { raw_.StoreAtomic(value); } + + V8_INLINE RawStorage GetRawStorage() const { return raw_; } + V8_INLINE void SetRawStorageAtomic(RawStorage other) { + reinterpret_cast&>(raw_).store( + other, std::memory_order_relaxed); + } + + V8_INLINE bool IsCleared() const { return raw_.IsCleared(); } + + V8_INLINE void ClearFromGC() const { raw_.Clear(); } + + private: + friend class MemberDebugHelper; + + mutable RawStorage raw_; +}; + +// The basic class from which all Member classes are 'generated'. +template +class V8_TRIVIAL_ABI BasicMember final : private MemberBase, + private CheckingPolicy { + using Base = MemberBase; + + public: + using PointeeType = T; + using RawStorage = typename Base::RawStorage; + + V8_INLINE constexpr BasicMember() = default; + V8_INLINE constexpr BasicMember(std::nullptr_t) {} // NOLINT + V8_INLINE BasicMember(SentinelPointer s) : Base(s) {} // NOLINT + V8_INLINE BasicMember(T* raw) : Base(raw) { // NOLINT + InitializingWriteBarrier(raw); + CheckPointer(raw); + } + V8_INLINE BasicMember(T& raw) // NOLINT + : BasicMember(&raw) {} + + // Atomic ctor. Using the AtomicInitializerTag forces BasicMember to + // initialize using atomic assignments. This is required for preventing + // data races with concurrent marking. + using AtomicInitializerTag = typename Base::AtomicInitializerTag; + V8_INLINE BasicMember(std::nullptr_t, AtomicInitializerTag atomic) + : Base(nullptr, atomic) {} + V8_INLINE BasicMember(SentinelPointer s, AtomicInitializerTag atomic) + : Base(s, atomic) {} + V8_INLINE BasicMember(T* raw, AtomicInitializerTag atomic) + : Base(raw, atomic) { + InitializingWriteBarrier(raw); + CheckPointer(raw); + } + V8_INLINE BasicMember(T& raw, AtomicInitializerTag atomic) + : BasicMember(&raw, atomic) {} + + // Copy ctor. + V8_INLINE BasicMember(const BasicMember& other) + : BasicMember(other.GetRawStorage()) {} + + // Heterogeneous copy constructors. When the source pointer have a different + // type, perform a compress-decompress round, because the source pointer may + // need to be adjusted. + template >* = nullptr> + V8_INLINE BasicMember( // NOLINT + const BasicMember& other) + : BasicMember(other.GetRawStorage()) {} + + template >* = nullptr> + V8_INLINE BasicMember( // NOLINT + const BasicMember& other) + : BasicMember(other.Get()) {} + + // Move ctor. + V8_INLINE BasicMember(BasicMember&& other) noexcept + : BasicMember(other.GetRawStorage()) { + other.Clear(); + } + + // Heterogeneous move constructors. When the source pointer have a different + // type, perform a compress-decompress round, because the source pointer may + // need to be adjusted. + template >* = nullptr> + V8_INLINE BasicMember( + BasicMember&& other) noexcept + : BasicMember(other.GetRawStorage()) { + other.Clear(); + } + + template >* = nullptr> + V8_INLINE BasicMember( + BasicMember&& other) noexcept + : BasicMember(other.Get()) { + other.Clear(); + } + + // Construction from Persistent. + template >> + V8_INLINE BasicMember(const BasicPersistent& p) + : BasicMember(p.Get()) {} + + // Copy assignment. + V8_INLINE BasicMember& operator=(const BasicMember& other) { + return operator=(other.GetRawStorage()); + } + + // Heterogeneous copy assignment. When the source pointer have a different + // type, perform a compress-decompress round, because the source pointer may + // need to be adjusted. + template + V8_INLINE BasicMember& operator=( + const BasicMember& other) { + if constexpr (IsDecayedSameV) { + return operator=(other.GetRawStorage()); + } else { + static_assert(IsStrictlyBaseOfV); + return operator=(other.Get()); + } + } + + // Move assignment. + V8_INLINE BasicMember& operator=(BasicMember&& other) noexcept { + operator=(other.GetRawStorage()); + other.Clear(); + return *this; + } + + // Heterogeneous move assignment. When the source pointer have a different + // type, perform a compress-decompress round, because the source pointer may + // need to be adjusted. + template + V8_INLINE BasicMember& operator=( + BasicMember&& other) noexcept { + if constexpr (IsDecayedSameV) { + operator=(other.GetRawStorage()); + } else { + static_assert(IsStrictlyBaseOfV); + operator=(other.Get()); + } + other.Clear(); + return *this; + } + + // Assignment from Persistent. + template >> + V8_INLINE BasicMember& operator=( + const BasicPersistent& + other) { + return operator=(other.Get()); + } + + V8_INLINE BasicMember& operator=(T* other) { + Base::SetRawAtomic(other); + AssigningWriteBarrier(other); + CheckPointer(other); + return *this; + } + + V8_INLINE BasicMember& operator=(std::nullptr_t) { + Clear(); + return *this; + } + V8_INLINE BasicMember& operator=(SentinelPointer s) { + Base::SetRawAtomic(s); + return *this; + } + + template + V8_INLINE void Swap(BasicMember& other) { + auto tmp = GetRawStorage(); + *this = other; + other = tmp; + } + + V8_INLINE explicit operator bool() const { return !Base::IsCleared(); } + V8_INLINE operator T*() const { return Get(); } + V8_INLINE T* operator->() const { return Get(); } + V8_INLINE T& operator*() const { return *Get(); } + + // CFI cast exemption to allow passing SentinelPointer through T* and support + // heterogeneous assignments between different Member and Persistent handles + // based on their actual types. + V8_INLINE V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { + // Executed by the mutator, hence non atomic load. + // + // The const_cast below removes the constness from MemberBase storage. The + // following static_cast re-adds any constness if specified through the + // user-visible template parameter T. + return static_cast(const_cast(Base::GetRaw())); + } + + V8_INLINE void Clear() { Base::SetRawStorageAtomic(RawStorage{}); } + + V8_INLINE T* Release() { + T* result = Get(); + Clear(); + return result; + } + + V8_INLINE const T** GetSlotForTesting() const { + return reinterpret_cast(Base::GetRawSlot()); + } + + V8_INLINE RawStorage GetRawStorage() const { return Base::GetRawStorage(); } + + private: + V8_INLINE explicit BasicMember(RawStorage raw) : Base(raw) { + InitializingWriteBarrier(); + CheckPointer(); + } + + V8_INLINE BasicMember& operator=(RawStorage other) { + Base::SetRawStorageAtomic(other); + AssigningWriteBarrier(); + CheckPointer(); + return *this; + } + + V8_INLINE const void* GetRawAtomic() const { return Base::GetRawAtomic(); } + + V8_INLINE const T* GetAtomic() const { + return static_cast(GetRawAtomic()); + } + + V8_INLINE void InitializingWriteBarrier(T* value) const { + WriteBarrierPolicy::InitializingBarrier(Base::GetRawSlot(), value); + } + V8_INLINE void InitializingWriteBarrier() const { + WriteBarrierPolicy::InitializingBarrier(Base::GetRawSlot(), + Base::GetRawStorage()); + } + V8_INLINE void AssigningWriteBarrier(T* value) const { + WriteBarrierPolicy::template AssigningBarrier< + StorageType::kWriteBarrierSlotType>(Base::GetRawSlot(), value); + } + V8_INLINE void AssigningWriteBarrier() const { + WriteBarrierPolicy::template AssigningBarrier< + StorageType::kWriteBarrierSlotType>(Base::GetRawSlot(), + Base::GetRawStorage()); + } + V8_INLINE void CheckPointer(T* value) { + CheckingPolicy::template CheckPointer(value); + } + V8_INLINE void CheckPointer() { + CheckingPolicy::template CheckPointer(Base::GetRawStorage()); + } + + V8_INLINE void ClearFromGC() const { Base::ClearFromGC(); } + + V8_INLINE T* GetFromGC() const { return Get(); } + + friend class cppgc::subtle::HeapConsistency; + template + friend class cppgc::subtle::TaggedUncompressedMember; + friend class cppgc::Visitor; + template + friend struct cppgc::TraceTrait; + template + friend class BasicMember; +}; + +// Member equality operators. +template +V8_INLINE bool operator==( + const BasicMember& member1, + const BasicMember& member2) { + if constexpr (IsDecayedSameV) { + // Check compressed pointers if types are the same. + return member1.GetRawStorage() == member2.GetRawStorage(); + } else { + static_assert(IsStrictlyBaseOfV || IsStrictlyBaseOfV); + // Otherwise, check decompressed pointers. + return member1.Get() == member2.Get(); + } +} + +template +V8_INLINE bool operator!=( + const BasicMember& member1, + const BasicMember& member2) { + return !(member1 == member2); +} + +// Equality with raw pointers. +template +V8_INLINE bool operator==( + const BasicMember& member, + U* raw) { + // Never allow comparison with erased pointers. + static_assert(!IsDecayedSameV); + + if constexpr (IsDecayedSameV) { + // Check compressed pointers if types are the same. + return member.GetRawStorage() == StorageType(raw); + } else if constexpr (IsStrictlyBaseOfV) { + // Cast the raw pointer to T, which may adjust the pointer. + return member.GetRawStorage() == StorageType(static_cast(raw)); + } else { + // Otherwise, decompressed the member. + return member.Get() == raw; + } +} + +template +V8_INLINE bool operator!=( + const BasicMember& member, + U* raw) { + return !(member == raw); +} + +template +V8_INLINE bool operator==( + T* raw, const BasicMember& member) { + return member == raw; +} + +template +V8_INLINE bool operator!=( + T* raw, const BasicMember& member) { + return !(raw == member); +} + +// Equality with sentinel. +template +V8_INLINE bool operator==( + const BasicMember& member, + SentinelPointer) { + return member.GetRawStorage().IsSentinel(); +} + +template +V8_INLINE bool operator!=( + const BasicMember& member, + SentinelPointer s) { + return !(member == s); +} + +template +V8_INLINE bool operator==( + SentinelPointer s, const BasicMember& member) { + return member == s; +} + +template +V8_INLINE bool operator!=( + SentinelPointer s, const BasicMember& member) { + return !(s == member); +} + +// Equality with nullptr. +template +V8_INLINE bool operator==( + const BasicMember& member, + std::nullptr_t) { + return !static_cast(member); +} + +template +V8_INLINE bool operator!=( + const BasicMember& member, + std::nullptr_t n) { + return !(member == n); +} + +template +V8_INLINE bool operator==( + std::nullptr_t n, const BasicMember& member) { + return member == n; +} + +template +V8_INLINE bool operator!=( + std::nullptr_t n, const BasicMember& member) { + return !(n == member); +} + +// Relational operators. +template +V8_INLINE bool operator<( + const BasicMember& member1, + const BasicMember& member2) { + static_assert( + IsDecayedSameV, + "Comparison works only for same pointer type modulo cv-qualifiers"); + return member1.GetRawStorage() < member2.GetRawStorage(); +} + +template +V8_INLINE bool operator<=( + const BasicMember& member1, + const BasicMember& member2) { + static_assert( + IsDecayedSameV, + "Comparison works only for same pointer type modulo cv-qualifiers"); + return member1.GetRawStorage() <= member2.GetRawStorage(); +} + +template +V8_INLINE bool operator>( + const BasicMember& member1, + const BasicMember& member2) { + static_assert( + IsDecayedSameV, + "Comparison works only for same pointer type modulo cv-qualifiers"); + return member1.GetRawStorage() > member2.GetRawStorage(); +} + +template +V8_INLINE bool operator>=( + const BasicMember& member1, + const BasicMember& member2) { + static_assert( + IsDecayedSameV, + "Comparison works only for same pointer type modulo cv-qualifiers"); + return member1.GetRawStorage() >= member2.GetRawStorage(); +} + +template +struct IsWeak> : std::true_type {}; + +} // namespace internal + +/** + * Members are used in classes to contain strong pointers to other garbage + * collected objects. All Member fields of a class must be traced in the class' + * trace method. + */ +template +using Member = internal::BasicMember< + T, internal::StrongMemberTag, internal::DijkstraWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::DefaultMemberStorage>; + +/** + * WeakMember is similar to Member in that it is used to point to other garbage + * collected objects. However instead of creating a strong pointer to the + * object, the WeakMember creates a weak pointer, which does not keep the + * pointee alive. Hence if all pointers to to a heap allocated object are weak + * the object will be garbage collected. At the time of GC the weak pointers + * will automatically be set to null. + */ +template +using WeakMember = internal::BasicMember< + T, internal::WeakMemberTag, internal::DijkstraWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::DefaultMemberStorage>; + +/** + * UntracedMember is a pointer to an on-heap object that is not traced for some + * reason. Do not use this unless you know what you are doing. Keeping raw + * pointers to on-heap objects is prohibited unless used from stack. Pointee + * must be kept alive through other means. + */ +template +using UntracedMember = internal::BasicMember< + T, internal::UntracedMemberTag, internal::NoWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::DefaultMemberStorage>; + +namespace subtle { + +/** + * UncompressedMember. Use with care in hot paths that would otherwise cause + * many decompression cycles. + */ +template +using UncompressedMember = internal::BasicMember< + T, internal::StrongMemberTag, internal::DijkstraWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::RawPointer>; + +#if defined(CPPGC_POINTER_COMPRESSION) +/** + * CompressedMember. Default implementation of cppgc::Member on builds with + * pointer compression. + */ +template +using CompressedMember = internal::BasicMember< + T, internal::StrongMemberTag, internal::DijkstraWriteBarrierPolicy, + internal::DefaultMemberCheckingPolicy, internal::CompressedPointer>; +#endif // defined(CPPGC_POINTER_COMPRESSION) + +} // namespace subtle + +namespace internal { + +struct Dummy; + +static constexpr size_t kSizeOfMember = sizeof(Member); +static constexpr size_t kSizeOfUncompressedMember = + sizeof(subtle::UncompressedMember); +#if defined(CPPGC_POINTER_COMPRESSION) +static constexpr size_t kSizeofCompressedMember = + sizeof(subtle::CompressedMember); +#endif // defined(CPPGC_POINTER_COMPRESSION) + +} // namespace internal + +} // namespace cppgc + +// Mark `BasicMember` and `T*` as having a common reference type of `T*` (the +// type to which both can be converted or bound). This makes them satisfy +// `std::equality_comparable`, which allows usage like the following: +// ``` +// HeapVector> v; +// T* e; +// auto it = std::ranges::find(v, e); +// ``` +// Without this, the `find()` call above would fail to compile with an error +// about being unable to invoke `std::ranges::equal_to()`. +template typename TQ, template typename UQ> +struct std::basic_common_reference< + cppgc::internal::BasicMember, + T*, TQ, UQ> { + using type = T*; +}; + +template typename TQ, template typename UQ> +struct std::basic_common_reference< + T*, + cppgc::internal::BasicMember, + TQ, UQ> { + using type = T*; +}; + +#endif // INCLUDE_CPPGC_MEMBER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/name-provider.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/name-provider.h new file mode 100644 index 000000000..849176fde --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/name-provider.h @@ -0,0 +1,75 @@ +// Copyright 2020 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 INCLUDE_CPPGC_NAME_PROVIDER_H_ +#define INCLUDE_CPPGC_NAME_PROVIDER_H_ + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +/** + * NameProvider allows for providing a human-readable name for garbage-collected + * objects. + * + * There's two cases of names to distinguish: + * a. Explicitly specified names via using NameProvider. Such names are always + * preserved in the system. + * b. Internal names that Oilpan infers from a C++ type on the class hierarchy + * of the object. This is not necessarily the type of the actually + * instantiated object. + * + * Depending on the build configuration, Oilpan may hide names, i.e., represent + * them with kHiddenName, of case b. to avoid exposing internal details. + */ +class V8_EXPORT NameProvider { + public: + /** + * Name that is used when hiding internals. + */ + static constexpr const char kHiddenName[] = "InternalNode"; + + /** + * Name that is used in case compiler support is missing for composing a name + * from C++ types. + */ + static constexpr const char kNoNameDeducible[] = ""; + + /** + * Indicating whether the build supports extracting C++ names as object names. + * + * @returns true if C++ names should be hidden and represented by kHiddenName. + */ + static constexpr bool SupportsCppClassNamesAsObjectNames() { +#if CPPGC_SUPPORTS_OBJECT_NAMES + return true; +#else // !CPPGC_SUPPORTS_OBJECT_NAMES + return false; +#endif // !CPPGC_SUPPORTS_OBJECT_NAMES + } + + virtual ~NameProvider() = default; + + /** + * Specifies a name for the garbage-collected object. Such names will never + * be hidden, as they are explicitly specified by the user of this API. + * + * Implementations of this function must not allocate garbage-collected + * objects or otherwise modify the cppgc heap. + * + * V8 may call this function while generating a heap snapshot or at other + * times. If V8 is currently generating a heap snapshot (according to + * HeapProfiler::IsTakingSnapshot), then the returned string must stay alive + * until the snapshot generation has completed. Otherwise, the returned string + * must stay alive forever. If you need a place to store a temporary string + * during snapshot generation, use HeapProfiler::CopyNameForHeapSnapshot. + * + * @returns a human readable name for the object. + */ + virtual const char* GetHumanReadableName() const = 0; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_NAME_PROVIDER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/object-size-trait.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/object-size-trait.h new file mode 100644 index 000000000..35795596d --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/object-size-trait.h @@ -0,0 +1,58 @@ +// Copyright 2021 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 INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_ +#define INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_ + +#include + +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +namespace internal { + +struct V8_EXPORT BaseObjectSizeTrait { + protected: + static size_t GetObjectSizeForGarbageCollected(const void*); + static size_t GetObjectSizeForGarbageCollectedMixin(const void*); +}; + +} // namespace internal + +namespace subtle { + +/** + * Trait specifying how to get the size of an object that was allocated using + * `MakeGarbageCollected()`. Also supports querying the size with an inner + * pointer to a mixin. + */ +template > +struct ObjectSizeTrait; + +template +struct ObjectSizeTrait : cppgc::internal::BaseObjectSizeTrait { + static_assert(sizeof(T), "T must be fully defined"); + static_assert(IsGarbageCollectedTypeV, + "T must be of type GarbageCollected or GarbageCollectedMixin"); + + static size_t GetSize(const T& object) { + return GetObjectSizeForGarbageCollected(&object); + } +}; + +template +struct ObjectSizeTrait : cppgc::internal::BaseObjectSizeTrait { + static_assert(sizeof(T), "T must be fully defined"); + + static size_t GetSize(const T& object) { + return GetObjectSizeForGarbageCollectedMixin(&object); + } +}; + +} // namespace subtle +} // namespace cppgc + +#endif // INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/persistent.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/persistent.h new file mode 100644 index 000000000..a86a063ed --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/persistent.h @@ -0,0 +1,376 @@ +// Copyright 2020 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 INCLUDE_CPPGC_PERSISTENT_H_ +#define INCLUDE_CPPGC_PERSISTENT_H_ + +#include + +#include "cppgc/internal/persistent-node.h" +#include "cppgc/internal/pointer-policies.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/source-location.h" +#include "cppgc/type-traits.h" +#include "cppgc/visitor.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { + +// PersistentBase always refers to the object as const object and defers to +// BasicPersistent on casting to the right type as needed. +class PersistentBase { + protected: + PersistentBase() = default; + explicit PersistentBase(const void* raw) : raw_(raw) {} + + const void* GetValue() const { return raw_; } + void SetValue(const void* value) { raw_ = value; } + + PersistentNode* GetNode() const { return node_; } + void SetNode(PersistentNode* node) { node_ = node; } + + // Performs a shallow clear which assumes that internal persistent nodes are + // destroyed elsewhere. + void ClearFromGC() const { + raw_ = nullptr; + node_ = nullptr; + } + + protected: + mutable const void* raw_ = nullptr; + mutable PersistentNode* node_ = nullptr; + + friend class PersistentRegionBase; +}; + +// The basic class from which all Persistent classes are generated. +template +class BasicPersistent final : public PersistentBase, + public LocationPolicy, + private WeaknessPolicy, + private CheckingPolicy { + public: + using typename WeaknessPolicy::IsStrongPersistent; + using PointeeType = T; + + // Null-state/sentinel constructors. + BasicPersistent( // NOLINT + SourceLocation loc = SourceLocation::Current()) + : LocationPolicy(loc) {} + + BasicPersistent(std::nullptr_t, // NOLINT + SourceLocation loc = SourceLocation::Current()) + : LocationPolicy(loc) {} + + BasicPersistent( // NOLINT + SentinelPointer s, SourceLocation loc = SourceLocation::Current()) + : PersistentBase(s), LocationPolicy(loc) {} + + // Raw value constructors. + BasicPersistent(T* raw, // NOLINT + SourceLocation loc = SourceLocation::Current()) + : PersistentBase(raw), LocationPolicy(loc) { + if (!IsValid()) return; + SetNode(WeaknessPolicy::GetPersistentRegion(GetValue()) + .AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(Get()); + } + + BasicPersistent(T& raw, // NOLINT + SourceLocation loc = SourceLocation::Current()) + : BasicPersistent(&raw, loc) {} + + // Copy ctor. + BasicPersistent(const BasicPersistent& other, + SourceLocation loc = SourceLocation::Current()) + : BasicPersistent(other.Get(), loc) {} + + // Heterogeneous ctor. + template >> + // NOLINTNEXTLINE + BasicPersistent( + const BasicPersistent& other, + SourceLocation loc = SourceLocation::Current()) + : BasicPersistent(other.Get(), loc) {} + + // Move ctor. The heterogeneous move ctor is not supported since e.g. + // persistent can't reuse persistent node from weak persistent. + BasicPersistent(BasicPersistent&& other, + SourceLocation loc = SourceLocation::Current()) noexcept + : PersistentBase(std::move(other)), LocationPolicy(std::move(other)) { + if (!IsValid()) return; + GetNode()->UpdateOwner(this); + other.SetValue(nullptr); + other.SetNode(nullptr); + this->CheckPointer(Get()); + } + + // Constructor from member. + template >> + // NOLINTNEXTLINE + BasicPersistent(const internal::BasicMember< + U, MemberBarrierPolicy, MemberWeaknessTag, + MemberCheckingPolicy, MemberStorageType>& member, + SourceLocation loc = SourceLocation::Current()) + : BasicPersistent(member.Get(), loc) {} + + ~BasicPersistent() { Clear(); } + + // Copy assignment. + BasicPersistent& operator=(const BasicPersistent& other) { + return operator=(other.Get()); + } + + template >> + BasicPersistent& operator=( + const BasicPersistent& other) { + return operator=(other.Get()); + } + + // Move assignment. + BasicPersistent& operator=(BasicPersistent&& other) noexcept { + if (this == &other) return *this; + Clear(); + PersistentBase::operator=(std::move(other)); + LocationPolicy::operator=(std::move(other)); + if (!IsValid()) return *this; + GetNode()->UpdateOwner(this); + other.SetValue(nullptr); + other.SetNode(nullptr); + this->CheckPointer(Get()); + return *this; + } + + // Assignment from member. + template >> + BasicPersistent& operator=( + const internal::BasicMember& + member) { + return operator=(member.Get()); + } + + BasicPersistent& operator=(T* other) { + Assign(other); + return *this; + } + + BasicPersistent& operator=(std::nullptr_t) { + Clear(); + return *this; + } + + BasicPersistent& operator=(SentinelPointer s) { + Assign(s); + return *this; + } + + explicit operator bool() const { return Get(); } + // Historically we allow implicit conversions to T*. + // NOLINTNEXTLINE + operator T*() const { return Get(); } + T* operator->() const { return Get(); } + T& operator*() const { return *Get(); } + + // CFI cast exemption to allow passing SentinelPointer through T* and support + // heterogeneous assignments between different Member and Persistent handles + // based on their actual types. + V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const { + // The const_cast below removes the constness from PersistentBase storage. + // The following static_cast re-adds any constness if specified through the + // user-visible template parameter T. + return static_cast(const_cast(GetValue())); + } + + void Clear() { + // Simplified version of `Assign()` to allow calling without a complete type + // `T`. + if (IsValid()) { + WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); + SetNode(nullptr); + } + SetValue(nullptr); + } + + T* Release() { + T* result = Get(); + Clear(); + return result; + } + + template + BasicPersistent + To() const { + return BasicPersistent(static_cast(Get())); + } + + private: + static void TraceAsRoot(RootVisitor& root_visitor, const void* ptr) { + root_visitor.Trace(*static_cast(ptr)); + } + + bool IsValid() const { + // Ideally, handling kSentinelPointer would be done by the embedder. On the + // other hand, having Persistent aware of it is beneficial since no node + // gets wasted. + return GetValue() != nullptr && GetValue() != kSentinelPointer; + } + + void Assign(T* ptr) { + if (IsValid()) { + if (ptr && ptr != kSentinelPointer) { + // Simply assign the pointer reusing the existing node. + SetValue(ptr); + this->CheckPointer(ptr); + return; + } + WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); + SetNode(nullptr); + } + SetValue(ptr); + if (!IsValid()) return; + SetNode(WeaknessPolicy::GetPersistentRegion(GetValue()) + .AllocateNode(this, &TraceAsRoot)); + this->CheckPointer(Get()); + } + + void ClearFromGC() const { + if (IsValid()) { + WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode()); + PersistentBase::ClearFromGC(); + } + } + + // Set Get() for details. + V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") + T* GetFromGC() const { + return static_cast(const_cast(GetValue())); + } + + friend class internal::RootVisitor; +}; + +template +bool operator==(const BasicPersistent& p1, + const BasicPersistent& p2) { + return p1.Get() == p2.Get(); +} + +template +bool operator!=(const BasicPersistent& p1, + const BasicPersistent& p2) { + return !(p1 == p2); +} + +template +bool operator==( + const BasicPersistent& + p, + const BasicMember& m) { + return p.Get() == m.Get(); +} + +template +bool operator!=( + const BasicPersistent& + p, + const BasicMember& m) { + return !(p == m); +} + +template +bool operator==( + const BasicMember& m, + const BasicPersistent& + p) { + return m.Get() == p.Get(); +} + +template +bool operator!=( + const BasicMember& m, + const BasicPersistent& + p) { + return !(m == p); +} + +template +struct IsWeak> : std::true_type {}; +} // namespace internal + +/** + * Persistent is a way to create a strong pointer from an off-heap object to + * another on-heap object. As long as the Persistent handle is alive the GC will + * keep the object pointed to alive. The Persistent handle is always a GC root + * from the point of view of the GC. Persistent must be constructed and + * destructed in the same thread. + */ +template +using Persistent = + internal::BasicPersistent; + +/** + * WeakPersistent is a way to create a weak pointer from an off-heap object to + * an on-heap object. The pointer is automatically cleared when the pointee gets + * collected. WeakPersistent must be constructed and destructed in the same + * thread. + */ +template +using WeakPersistent = + internal::BasicPersistent; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_PERSISTENT_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/platform.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/platform.h new file mode 100644 index 000000000..8e19272f5 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/platform.h @@ -0,0 +1,174 @@ +// Copyright 2020 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 INCLUDE_CPPGC_PLATFORM_H_ +#define INCLUDE_CPPGC_PLATFORM_H_ + +#include + +#include "cppgc/source-location.h" +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +// TODO(v8:10346): Create separate includes for concepts that are not +// V8-specific. +using IdleTask = v8::IdleTask; +using JobHandle = v8::JobHandle; +using JobDelegate = v8::JobDelegate; +using JobTask = v8::JobTask; +using PageAllocator = v8::PageAllocator; +using Task = v8::Task; +using TaskPriority = v8::TaskPriority; +using TaskRunner = v8::TaskRunner; +using TracingController = v8::TracingController; + +/** + * Platform interface used by Heap. Contains allocators and executors. + */ +class V8_EXPORT Platform { + public: + virtual ~Platform() = default; + + /** + * \returns the allocator used by cppgc to allocate its heap and various + * support structures. Returning nullptr results in using the `PageAllocator` + * provided by `cppgc::InitializeProcess()` instead. + */ + virtual PageAllocator* GetPageAllocator() = 0; + + /** + * Monotonically increasing time in seconds from an arbitrary fixed point in + * the past. This function is expected to return at least + * millisecond-precision values. For this reason, + * it is recommended that the fixed point be no further in the past than + * the epoch. + **/ + virtual double MonotonicallyIncreasingTime() = 0; + + /** + * Foreground task runner that should be used by a Heap. + */ + virtual std::shared_ptr GetForegroundTaskRunner() { + return GetForegroundTaskRunner(TaskPriority::kUserBlocking); + } + + /** + * Returns a TaskRunner with a specific |priority| which can be used to post a + * task on the foreground thread. + */ + virtual std::shared_ptr GetForegroundTaskRunner( + TaskPriority priority) { + return nullptr; + } + + /** + * Posts `job_task` to run in parallel. Returns a `JobHandle` associated with + * the `Job`, which can be joined or canceled. + * This avoids degenerate cases: + * - Calling `CallOnWorkerThread()` for each work item, causing significant + * overhead. + * - Fixed number of `CallOnWorkerThread()` calls that split the work and + * might run for a long time. This is problematic when many components post + * "num cores" tasks and all expect to use all the cores. In these cases, + * the scheduler lacks context to be fair to multiple same-priority requests + * and/or ability to request lower priority work to yield when high priority + * work comes in. + * A canonical implementation of `job_task` looks like: + * \code + * class MyJobTask : public JobTask { + * public: + * MyJobTask(...) : worker_queue_(...) {} + * // JobTask implementation. + * void Run(JobDelegate* delegate) override { + * while (!delegate->ShouldYield()) { + * // Smallest unit of work. + * auto work_item = worker_queue_.TakeWorkItem(); // Thread safe. + * if (!work_item) return; + * ProcessWork(work_item); + * } + * } + * + * size_t GetMaxConcurrency() const override { + * return worker_queue_.GetSize(); // Thread safe. + * } + * }; + * + * // ... + * auto handle = PostJob(TaskPriority::kUserVisible, + * std::make_unique(...)); + * handle->Join(); + * \endcode + * + * `PostJob()` and methods of the returned JobHandle/JobDelegate, must never + * be called while holding a lock that could be acquired by `JobTask::Run()` + * or `JobTask::GetMaxConcurrency()` -- that could result in a deadlock. This + * is because (1) `JobTask::GetMaxConcurrency()` may be invoked while holding + * internal lock (A), hence `JobTask::GetMaxConcurrency()` can only use a lock + * (B) if that lock is *never* held while calling back into `JobHandle` from + * any thread (A=>B/B=>A deadlock) and (2) `JobTask::Run()` or + * `JobTask::GetMaxConcurrency()` may be invoked synchronously from + * `JobHandle` (B=>JobHandle::foo=>B deadlock). + * + * A sufficient `PostJob()` implementation that uses the default Job provided + * in libplatform looks like: + * \code + * std::unique_ptr PostJob( + * TaskPriority priority, std::unique_ptr job_task) override { + * return std::make_unique( + * std::make_shared( + * this, std::move(job_task), kNumThreads)); + * } + * \endcode + */ + virtual std::unique_ptr PostJob( + TaskPriority priority, std::unique_ptr job_task) { + return nullptr; + } + + /** + * Returns an instance of a `TracingController`. This must be non-nullptr. The + * default implementation returns an empty `TracingController` that consumes + * trace data without effect. + */ + virtual TracingController* GetTracingController(); +}; + +V8_EXPORT bool IsInitialized(); + +/** + * Process-global initialization of the garbage collector. Must be called before + * creating a Heap. + * + * Can be called multiple times when paired with `ShutdownProcess()`. + * + * \param page_allocator The allocator used for maintaining meta data. Must stay + * always alive and not change between multiple calls to InitializeProcess. If + * no allocator is provided, a default internal version will be used. + * \param desired_heap_size Desired amount of virtual address space to reserve + * for the heap, in bytes. Actual size will be clamped to minimum and maximum + * values based on compile-time settings and may be rounded up. If this + * parameter is zero, a default value will be used. + */ +V8_EXPORT void InitializeProcess(PageAllocator* page_allocator = nullptr, + size_t desired_heap_size = 0); + +/** + * Must be called after destroying the last used heap. Some process-global + * metadata may not be returned and reused upon a subsequent + * `InitializeProcess()` call. + */ +V8_EXPORT void ShutdownProcess(); + +namespace internal { + +V8_EXPORT void Fatal(const std::string& reason = std::string(), + SourceLocation = SourceLocation::Current()); + +} // namespace internal + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_PLATFORM_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/prefinalizer.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/prefinalizer.h new file mode 100644 index 000000000..51f2eac8e --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/prefinalizer.h @@ -0,0 +1,75 @@ +// Copyright 2020 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 INCLUDE_CPPGC_PREFINALIZER_H_ +#define INCLUDE_CPPGC_PREFINALIZER_H_ + +#include "cppgc/internal/compiler-specific.h" +#include "cppgc/liveness-broker.h" + +namespace cppgc { + +namespace internal { + +class V8_EXPORT PrefinalizerRegistration final { + public: + using Callback = bool (*)(const cppgc::LivenessBroker&, void*); + + PrefinalizerRegistration(void*, Callback); + + void* operator new(size_t, void* location) = delete; + void* operator new(size_t) = delete; +}; + +} // namespace internal + +/** + * Macro must be used in the private section of `Class` and registers a + * prefinalization callback `void Class::PreFinalizer()`. The callback is + * invoked on garbage collection after the collector has found an object to be + * dead. + * + * Callback properties: + * - The callback is invoked before a possible destructor for the corresponding + * object. + * - The callback may access the whole object graph, irrespective of whether + * objects are considered dead or alive. + * - The callback is invoked on the same thread as the object was created on. + * + * Example: + * \code + * class WithPrefinalizer : public GarbageCollected { + * CPPGC_USING_PRE_FINALIZER(WithPrefinalizer, Dispose); + * + * public: + * void Trace(Visitor*) const {} + * void Dispose() { prefinalizer_called = true; } + * ~WithPrefinalizer() { + * // prefinalizer_called == true + * } + * private: + * bool prefinalizer_called = false; + * }; + * \endcode + */ +#define CPPGC_USING_PRE_FINALIZER(Class, PreFinalizer) \ + public: \ + static bool InvokePreFinalizer(const cppgc::LivenessBroker& liveness_broker, \ + void* object) { \ + static_assert(cppgc::IsGarbageCollectedOrMixinTypeV, \ + "Only garbage collected objects can have prefinalizers"); \ + Class* self = static_cast(object); \ + if (liveness_broker.IsHeapObjectAlive(self)) return false; \ + self->PreFinalizer(); \ + return true; \ + } \ + \ + private: \ + CPPGC_NO_UNIQUE_ADDRESS cppgc::internal::PrefinalizerRegistration \ + prefinalizer_dummy_{this, Class::InvokePreFinalizer}; \ + static_assert(true, "Force semicolon.") + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_PREFINALIZER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/process-heap-statistics.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/process-heap-statistics.h new file mode 100644 index 000000000..774cc92f4 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/process-heap-statistics.h @@ -0,0 +1,36 @@ +// Copyright 2020 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 INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_ +#define INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_ + +#include +#include + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { +namespace internal { +class ProcessHeapStatisticsUpdater; +} // namespace internal + +class V8_EXPORT ProcessHeapStatistics final { + public: + static size_t TotalAllocatedObjectSize() { + return total_allocated_object_size_.load(std::memory_order_relaxed); + } + static size_t TotalAllocatedSpace() { + return total_allocated_space_.load(std::memory_order_relaxed); + } + + private: + static std::atomic_size_t total_allocated_space_; + static std::atomic_size_t total_allocated_object_size_; + + friend class internal::ProcessHeapStatisticsUpdater; +}; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/sentinel-pointer.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/sentinel-pointer.h new file mode 100644 index 000000000..bee96c776 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/sentinel-pointer.h @@ -0,0 +1,39 @@ +// Copyright 2021 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 INCLUDE_CPPGC_SENTINEL_POINTER_H_ +#define INCLUDE_CPPGC_SENTINEL_POINTER_H_ + +#include + +#include "cppgc/internal/api-constants.h" + +namespace cppgc { +namespace internal { + +// Special tag type used to denote some sentinel member. The semantics of the +// sentinel is defined by the embedder. +struct SentinelPointer { +#if defined(CPPGC_POINTER_COMPRESSION) + static constexpr intptr_t kSentinelValue = + 1 << api_constants::kPointerCompressionShift; +#else // !defined(CPPGC_POINTER_COMPRESSION) + static constexpr intptr_t kSentinelValue = 0b10; +#endif // !defined(CPPGC_POINTER_COMPRESSION) + template + operator T*() const { + return reinterpret_cast(kSentinelValue); + } + // Hidden friends. + friend bool operator==(SentinelPointer, SentinelPointer) { return true; } + friend bool operator!=(SentinelPointer, SentinelPointer) { return false; } +}; + +} // namespace internal + +constexpr internal::SentinelPointer kSentinelPointer; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_SENTINEL_POINTER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/source-location.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/source-location.h new file mode 100644 index 000000000..0dc28aedd --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/source-location.h @@ -0,0 +1,16 @@ +// Copyright 2020 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 INCLUDE_CPPGC_SOURCE_LOCATION_H_ +#define INCLUDE_CPPGC_SOURCE_LOCATION_H_ + +#include "v8-source-location.h" + +namespace cppgc { + +using SourceLocation = v8::SourceLocation; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_SOURCE_LOCATION_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/tagged-member.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/tagged-member.h new file mode 100644 index 000000000..b100a54a5 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/tagged-member.h @@ -0,0 +1,114 @@ +// Copyright 2025 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 INCLUDE_CPPGC_TAGGED_MEMBER_H_ +#define INCLUDE_CPPGC_TAGGED_MEMBER_H_ + +#include +#include +#include +#include + +#include "cppgc/internal/api-constants.h" +#include "cppgc/macros.h" +#include "cppgc/member.h" +#include "cppgc/visitor.h" + +namespace cppgc::subtle { + +// The class allows to store a Member along with a single bit tag. It uses +// distinct tag types, Tag1 and Tag2, to represent the two states of the tag. +// The tag is stored in the least significant bit of the pointer. +// +// Example usage: +// struct ParentTag {}; +// struct ShadowHostTag {}; +// +// /* Constructs a member with the pointer to parent tag: */ +// TaggedUncompressedMember +// m(ParentTag{}, parent); +template +class TaggedUncompressedMember final { + CPPGC_DISALLOW_NEW(); + static constexpr uintptr_t kTagBit = 0b1; + static_assert(kTagBit < internal::api_constants::kAllocationGranularity, + "The tag must live in the alignment bits of the pointer."); + + public: + TaggedUncompressedMember(Tag1, Pointee* ptr) : ptr_(ptr) {} + TaggedUncompressedMember(Tag2, Pointee* ptr) + : ptr_(reinterpret_cast(reinterpret_cast(ptr) | + kTagBit)) {} + + template + Pointee* GetAs() const { + auto* raw = ptr_.Get(); + if constexpr (std::same_as) { + CPPGC_DCHECK(Is()); + return raw; + } else { + static_assert(std::same_as); + CPPGC_DCHECK(Is()); + return GetUntagged(); + } + } + + template + Pointee* TryGetAs() const { + auto* raw = ptr_.Get(); + if constexpr (std::same_as) { + return (reinterpret_cast(raw) & kTagBit) ? nullptr : raw; + } else { + static_assert(std::same_as); + return (reinterpret_cast(raw) & kTagBit) + ? reinterpret_cast(reinterpret_cast(raw) & + ~kTagBit) + : nullptr; + } + } + + Pointee* GetUntagged() const { + return reinterpret_cast(reinterpret_cast(ptr_.Get()) & + ~kTagBit); + } + + template + void SetAs(Pointee* pointee) { + if constexpr (std::same_as) { + ptr_ = pointee; + } else { + static_assert(std::same_as); + ptr_ = reinterpret_cast(reinterpret_cast(pointee) | + kTagBit); + } + } + + template + bool Is() const { + const bool tag_set = reinterpret_cast(ptr_.Get()) & kTagBit; + if constexpr (std::same_as) { + return !tag_set; + } else { + static_assert(std::same_as); + return tag_set; + } + } + + void Trace(Visitor* v) const { + // Construct an untagged pointer and pass it to Visitor::Trace(). The plugin + // would warn that ptr_ is untraced, which is why CPPGC_PLUGIN_IGNORE is + // used. + auto* untagged = reinterpret_cast( + reinterpret_cast(ptr_.GetRawAtomic()) & ~kTagBit); + UncompressedMember temp(untagged); + v->Trace(temp); + } + + private: + CPPGC_PLUGIN_IGNORE("See Trace()") UncompressedMember ptr_; +}; + +} // namespace cppgc::subtle + +#endif // INCLUDE_CPPGC_TAGGED_MEMBER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/testing.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/testing.h new file mode 100644 index 000000000..bddd1fc16 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/testing.h @@ -0,0 +1,106 @@ +// Copyright 2021 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 INCLUDE_CPPGC_TESTING_H_ +#define INCLUDE_CPPGC_TESTING_H_ + +#include "cppgc/common.h" +#include "cppgc/macros.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +class HeapHandle; + +/** + * Namespace contains testing helpers. + */ +namespace testing { + +/** + * Overrides the state of the stack with the provided value. Parameters passed + * to explicit garbage collection calls still take precedence. Must not be + * nested. + * + * This scope is useful to make the garbage collector consider the stack when + * tasks that invoke garbage collection (through the provided platform) contain + * interesting pointers on its stack. + */ +class V8_EXPORT V8_NODISCARD OverrideEmbedderStackStateScope final { + CPPGC_STACK_ALLOCATED(); + + public: + /** + * Constructs a scoped object that automatically enters and leaves the scope. + * + * \param heap_handle The corresponding heap. + */ + explicit OverrideEmbedderStackStateScope(HeapHandle& heap_handle, + EmbedderStackState state); + ~OverrideEmbedderStackStateScope(); + + OverrideEmbedderStackStateScope(const OverrideEmbedderStackStateScope&) = + delete; + OverrideEmbedderStackStateScope& operator=( + const OverrideEmbedderStackStateScope&) = delete; + + private: + HeapHandle& heap_handle_; +}; + +/** + * Testing interface for managed heaps that allows for controlling garbage + * collection timings. Embedders should use this class when testing the + * interaction of their code with incremental/concurrent garbage collection. + */ +class V8_EXPORT StandaloneTestingHeap final { + public: + explicit StandaloneTestingHeap(HeapHandle&); + + /** + * Start an incremental garbage collection. + */ + void StartGarbageCollection(); + + /** + * Perform an incremental step. This will also schedule concurrent steps if + * needed. + * + * \param stack_state The state of the stack during the step. + */ + bool PerformMarkingStep(EmbedderStackState stack_state); + + /** + * Finalize the current garbage collection cycle atomically. + * Assumes that garbage collection is in progress. + * + * \param stack_state The state of the stack for finalizing the garbage + * collection cycle. + */ + void FinalizeGarbageCollection(EmbedderStackState stack_state); + + /** + * Toggle main thread marking on/off. Allows to stress concurrent marking + * (e.g. to better detect data races). + * + * \param should_mark Denotes whether the main thread should contribute to + * marking. Defaults to true. + */ + void ToggleMainThreadMarking(bool should_mark); + + /** + * Force enable compaction for the next garbage collection cycle. + */ + void ForceCompactionForNextGarbageCollection(); + + private: + HeapHandle& heap_handle_; +}; + +V8_EXPORT bool IsHeapObjectOld(void*); + +} // namespace testing +} // namespace cppgc + +#endif // INCLUDE_CPPGC_TESTING_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/trace-trait.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/trace-trait.h new file mode 100644 index 000000000..9b768a152 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/trace-trait.h @@ -0,0 +1,127 @@ +// Copyright 2020 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 INCLUDE_CPPGC_TRACE_TRAIT_H_ +#define INCLUDE_CPPGC_TRACE_TRAIT_H_ + +#include + +#include "cppgc/type-traits.h" +#include "v8config.h" // NOLINT(build/include_directory) + +namespace cppgc { + +class Visitor; + +namespace internal { + +class RootVisitor; + +using TraceRootCallback = void (*)(RootVisitor&, const void* object); + +// Implementation of the default TraceTrait handling GarbageCollected and +// GarbageCollectedMixin. +template >> +struct TraceTraitImpl; + +} // namespace internal + +/** + * Callback for invoking tracing on a given object. + * + * \param visitor The visitor to dispatch to. + * \param object The object to invoke tracing on. + */ +using TraceCallback = void (*)(Visitor* visitor, const void* object); + +/** + * Describes how to trace an object, i.e., how to visit all Oilpan-relevant + * fields of an object. + */ +struct TraceDescriptor { + /** + * Adjusted base pointer, i.e., the pointer to the class inheriting directly + * from GarbageCollected, of the object that is being traced. + */ + const void* base_object_payload; + /** + * Callback for tracing the object. + */ + TraceCallback callback; +}; + +/** + * Callback for getting a TraceDescriptor for a given address. + * + * \param address Possibly inner address of an object. + * \returns a TraceDescriptor for the provided address. + */ +using TraceDescriptorCallback = TraceDescriptor (*)(const void* address); + +namespace internal { + +struct V8_EXPORT TraceTraitFromInnerAddressImpl { + static TraceDescriptor GetTraceDescriptor(const void* address); +}; + +/** + * Trait specifying how the garbage collector processes an object of type T. + * + * Advanced users may override handling by creating a specialization for their + * type. + */ +template +struct TraceTraitBase { + static_assert(internal::IsTraceableV, "T must have a Trace() method"); + + /** + * Accessor for retrieving a TraceDescriptor to process an object of type T. + * + * \param self The object to be processed. + * \returns a TraceDescriptor to process the object. + */ + static TraceDescriptor GetTraceDescriptor(const void* self) { + return internal::TraceTraitImpl::GetTraceDescriptor( + static_cast(self)); + } + + /** + * Function invoking the tracing for an object of type T. + * + * \param visitor The visitor to dispatch to. + * \param self The object to invoke tracing on. + */ + static void Trace(Visitor* visitor, const void* self) { + static_cast(self)->Trace(visitor); + } +}; + +} // namespace internal + +template +struct TraceTrait : public internal::TraceTraitBase {}; + +namespace internal { + +template +struct TraceTraitImpl { + static_assert(IsGarbageCollectedTypeV, + "T must be of type GarbageCollected or GarbageCollectedMixin"); + static TraceDescriptor GetTraceDescriptor(const void* self) { + return {self, TraceTrait::Trace}; + } +}; + +template +struct TraceTraitImpl { + static TraceDescriptor GetTraceDescriptor(const void* self) { + return internal::TraceTraitFromInnerAddressImpl::GetTraceDescriptor(self); + } +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_TRACE_TRAIT_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/type-traits.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/type-traits.h new file mode 100644 index 000000000..1685c31df --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/type-traits.h @@ -0,0 +1,272 @@ +// Copyright 2020 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 INCLUDE_CPPGC_TYPE_TRAITS_H_ +#define INCLUDE_CPPGC_TYPE_TRAITS_H_ + +// This file should stay with minimal dependencies to allow embedder to check +// against Oilpan types without including any other parts. +#include +#include +#include + +namespace cppgc { + +class Visitor; + +namespace internal { +template +class BasicMember; +struct DijkstraWriteBarrierPolicy; +struct NoWriteBarrierPolicy; +class StrongMemberTag; +class UntracedMemberTag; +class WeakMemberTag; + +// Not supposed to be specialized by the user. +template +struct IsWeak : std::false_type {}; + +// IsTraceMethodConst is used to verify that all Trace methods are marked as +// const. It is equivalent to IsTraceable but for a non-const object. +template +struct IsTraceMethodConst : std::false_type {}; + +template +struct IsTraceMethodConst().Trace( + std::declval()))>> : std::true_type { +}; + +template +struct IsTraceable : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsTraceable< + T, std::void_t().Trace(std::declval()))>> + : std::true_type { + // All Trace methods should be marked as const. If an object of type + // 'T' is traceable then any object of type 'const T' should also + // be traceable. + static_assert(IsTraceMethodConst(), + "Trace methods should be marked as const."); +}; + +template +constexpr bool IsTraceableV = IsTraceable::value; + +template +struct HasGarbageCollectedMixinTypeMarker : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct HasGarbageCollectedMixinTypeMarker< + T, std::void_t< + typename std::remove_const_t::IsGarbageCollectedMixinTypeMarker>> + : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct HasGarbageCollectedTypeMarker : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct HasGarbageCollectedTypeMarker< + T, + std::void_t::IsGarbageCollectedTypeMarker>> + : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template ::value, + bool = HasGarbageCollectedMixinTypeMarker::value> +struct IsGarbageCollectedMixinType : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedMixinType : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template ::value> +struct IsGarbageCollectedType : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedType : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedOrMixinType + : std::integral_constant::value || + IsGarbageCollectedMixinType::value> { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template ::value && + HasGarbageCollectedMixinTypeMarker::value)> +struct IsGarbageCollectedWithMixinType : std::false_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsGarbageCollectedWithMixinType : std::true_type { + static_assert(sizeof(T), "T must be fully defined"); +}; + +template +struct IsSubclassOfBasicMemberTemplate { + private: + template + static std::true_type SubclassCheck( + const BasicMember*); + static std::false_type SubclassCheck(...); + + public: + static constexpr bool value = decltype(SubclassCheck( + std::declval*>()))::value; +}; + +template ::value> +struct IsMemberType : std::false_type {}; + +template +struct IsMemberType : std::true_type {}; + +template ::value> +struct IsWeakMemberType : std::false_type {}; + +template +struct IsWeakMemberType : std::true_type {}; + +template ::value> +struct IsUntracedMemberType : std::false_type {}; + +template +struct IsUntracedMemberType : std::true_type {}; + +template +struct IsComplete { + private: + template + static std::true_type IsSizeOfKnown(U*); + static std::false_type IsSizeOfKnown(...); + + public: + static constexpr bool value = + decltype(IsSizeOfKnown(std::declval()))::value; +}; + +template +constexpr bool IsDecayedSameV = + std::is_same_v, std::decay_t>; + +template +constexpr bool IsStrictlyBaseOfV = + std::is_base_of_v, std::decay_t> && + !IsDecayedSameV; + +template +constexpr bool IsAnyMemberTypeV = false; + +template +constexpr bool IsAnyMemberTypeV> = true; + +} // namespace internal + +/** + * Value is true for types that inherit from `GarbageCollectedMixin` but not + * `GarbageCollected` (i.e., they are free mixins), and false otherwise. + */ +template +constexpr bool IsGarbageCollectedMixinTypeV = + internal::IsGarbageCollectedMixinType::value; + +/** + * Value is true for types that inherit from `GarbageCollected`, and false + * otherwise. + */ +template +constexpr bool IsGarbageCollectedTypeV = + internal::IsGarbageCollectedType::value; + +/** + * Value is true for types that inherit from either `GarbageCollected` or + * `GarbageCollectedMixin`, and false otherwise. + */ +template +constexpr bool IsGarbageCollectedOrMixinTypeV = + internal::IsGarbageCollectedOrMixinType::value; + +/** + * Value is true for types that inherit from `GarbageCollected` and + * `GarbageCollectedMixin`, and false otherwise. + */ +template +constexpr bool IsGarbageCollectedWithMixinTypeV = + internal::IsGarbageCollectedWithMixinType::value; + +/** + * Value is true for types of type `Member`, and false otherwise. + */ +template +constexpr bool IsMemberTypeV = internal::IsMemberType::value; + +/** + * Value is true for types of type `UntracedMember`, and false otherwise. + */ +template +constexpr bool IsUntracedMemberTypeV = internal::IsUntracedMemberType::value; + +/** + * Value is true for types of type `WeakMember`, and false otherwise. + */ +template +constexpr bool IsWeakMemberTypeV = internal::IsWeakMemberType::value; + +/** + * Value is true for types that are considered weak references, and false + * otherwise. + */ +template +constexpr bool IsWeakV = internal::IsWeak::value; + +/** + * Value is true for types that are complete, and false otherwise. + */ +template +constexpr bool IsCompleteV = internal::IsComplete::value; + +/** + * Value is true for member types `Member` and `WeakMember`. + */ +template +constexpr bool IsMemberOrWeakMemberTypeV = + IsMemberTypeV || IsWeakMemberTypeV; + +/** + * Value is true for any member type. + */ +template +constexpr bool IsAnyMemberTypeV = internal::IsAnyMemberTypeV>; + +} // namespace cppgc + +#endif // INCLUDE_CPPGC_TYPE_TRAITS_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/cppgc/visitor.h b/packages/android-jsi/platforms/android/cpp/include/cppgc/visitor.h new file mode 100644 index 000000000..fcdf53753 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/cppgc/visitor.h @@ -0,0 +1,537 @@ +// Copyright 2020 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 INCLUDE_CPPGC_VISITOR_H_ +#define INCLUDE_CPPGC_VISITOR_H_ + +#include + +#include "cppgc/custom-space.h" +#include "cppgc/garbage-collected.h" +#include "cppgc/internal/logging.h" +#include "cppgc/internal/member-storage.h" +#include "cppgc/internal/pointer-policies.h" +#include "cppgc/liveness-broker.h" +#include "cppgc/macros.h" +#include "cppgc/member.h" +#include "cppgc/sentinel-pointer.h" +#include "cppgc/source-location.h" +#include "cppgc/trace-trait.h" +#include "cppgc/type-traits.h" + +namespace cppgc { + +namespace internal { +template +class BasicCrossThreadPersistent; +template +class BasicPersistent; +class ConservativeTracingVisitor; +class VisitorBase; +class VisitorFactory; +} // namespace internal + +using WeakCallback = void (*)(const LivenessBroker&, const void*); + +/** + * An ephemeron pair is used to conditionally retain an object. + * The `value` will be kept alive only if the `key` is alive. + */ +template +struct EphemeronPair { + CPPGC_DISALLOW_NEW(); + + EphemeronPair(K* k, V* v) : key(k), value(v) {} + WeakMember key; + Member value; + + void ClearKeyAndValueIfKeyIsDead(const LivenessBroker& broker) { + if (!broker.IsHeapObjectAlive(key)) { + key = nullptr; + value = nullptr; + } + } + + void Trace(Visitor* visitor) const; +}; + +/** + * Visitor passed to trace methods. All managed pointers must have called the + * Visitor's trace method on them. + * + * \code + * class Foo final : public GarbageCollected { + * public: + * void Trace(Visitor* visitor) const { + * visitor->Trace(foo_); + * visitor->Trace(weak_foo_); + * } + * private: + * Member foo_; + * WeakMember weak_foo_; + * }; + * \endcode + */ +class V8_EXPORT Visitor { + public: + class Key { + private: + Key() = default; + friend class internal::VisitorFactory; + }; + + explicit Visitor(Key) {} + + virtual ~Visitor() = default; + + /** + * Trace method for Member. + * + * \param member Member reference retaining an object. + */ + template + void Trace(const Member& member) { + const T* value = member.GetAtomic(); + CPPGC_DCHECK(value != kSentinelPointer); + TraceImpl(value); + } + + /** + * Trace method for WeakMember. + * + * \param weak_member WeakMember reference weakly retaining an object. + */ + template + void Trace(const WeakMember& weak_member) { + static_assert(sizeof(T), "Pointee type must be fully defined."); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "T must be GarbageCollected or GarbageCollectedMixin type"); + static_assert(!internal::IsAllocatedOnCompactableSpace::value, + "Weak references to compactable objects are not allowed"); + + const T* value = weak_member.GetAtomic(); + + // Bailout assumes that WeakMember emits write barrier. + if (!value) { + return; + } + + CPPGC_DCHECK(value != kSentinelPointer); + VisitWeak(value, TraceTrait::GetTraceDescriptor(value), + &HandleWeak>, &weak_member); + } + +#if defined(CPPGC_POINTER_COMPRESSION) + /** + * Trace method for UncompressedMember. + * + * \param member UncompressedMember reference retaining an object. + */ + template + void Trace(const subtle::UncompressedMember& member) { + const T* value = member.GetAtomic(); + CPPGC_DCHECK(value != kSentinelPointer); + TraceImpl(value); + } +#endif // defined(CPPGC_POINTER_COMPRESSION) + + template + void TraceMultiple(const subtle::UncompressedMember* start, size_t len) { + static_assert(sizeof(T), "Pointee type must be fully defined."); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "T must be GarbageCollected or GarbageCollectedMixin type"); + VisitMultipleUncompressedMember(start, len, + &TraceTrait::GetTraceDescriptor); + } + + template , subtle::UncompressedMember>>* = nullptr> + void TraceMultiple(const Member* start, size_t len) { + static_assert(sizeof(T), "Pointee type must be fully defined."); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "T must be GarbageCollected or GarbageCollectedMixin type"); +#if defined(CPPGC_POINTER_COMPRESSION) + static_assert(std::is_same_v, subtle::CompressedMember>, + "Member and CompressedMember must be the same."); + VisitMultipleCompressedMember(start, len, + &TraceTrait::GetTraceDescriptor); +#endif // defined(CPPGC_POINTER_COMPRESSION) + } + + /** + * Trace method for inlined objects that are not allocated themselves but + * otherwise follow managed heap layout and have a Trace() method. + * + * \param object reference of the inlined object. + */ + template + void Trace(const T& object) { + static_assert(!IsGarbageCollectedOrMixinTypeV); +#if V8_ENABLE_CHECKS + // This object is embedded in potentially multiple nested objects. The + // outermost object must not be in construction as such objects are (a) not + // processed immediately, and (b) only processed conservatively if not + // otherwise possible. + CheckObjectNotInConstruction(&object); +#endif // V8_ENABLE_CHECKS + TraceTrait::Trace(this, &object); + } + + template + void TraceMultiple(const T* start, size_t len) { +#if V8_ENABLE_CHECKS + // This object is embedded in potentially multiple nested objects. The + // outermost object must not be in construction as such objects are (a) not + // processed immediately, and (b) only processed conservatively if not + // otherwise possible. + CheckObjectNotInConstruction(start); +#endif // V8_ENABLE_CHECKS + for (size_t i = 0; i < len; ++i) { + const T* object = &start[i]; + if constexpr (std::is_polymorphic_v) { + // The object's vtable may be uninitialized in which case the object is + // not traced. + if (*reinterpret_cast(object) == 0) continue; + } + TraceTrait::Trace(this, object); + } + } + + /** + * Registers a weak callback method on the object of type T. See + * LivenessBroker for an usage example. + * + * \param object of type T specifying a weak callback method. + */ + template + void RegisterWeakCallbackMethod(const T* object) { + RegisterWeakCallback(&WeakCallbackMethodDelegate, object); + } + + /** + * Trace method for EphemeronPair. + * + * \param ephemeron_pair EphemeronPair reference weakly retaining a key object + * and strongly retaining a value object in case the key object is alive. + */ + template + void Trace(const EphemeronPair& ephemeron_pair) { + TraceEphemeron(ephemeron_pair.key, &ephemeron_pair.value); + RegisterWeakCallbackMethod< + EphemeronPair, &EphemeronPair::ClearKeyAndValueIfKeyIsDead>( + &ephemeron_pair); + } + + /** + * Trace method for a single ephemeron. Used for tracing a raw ephemeron in + * which the `key` and `value` are kept separately. + * + * \param weak_member_key WeakMember reference weakly retaining a key object. + * \param member_value Member reference with ephemeron semantics. + */ + template + void TraceEphemeron(const WeakMember& weak_member_key, + const Member* member_value) { + const KeyType* key = weak_member_key.GetAtomic(); + if (!key) return; + + // `value` must always be non-null. + CPPGC_DCHECK(member_value); + const ValueType* value = member_value->GetAtomic(); + if (!value) return; + + // KeyType and ValueType may refer to GarbageCollectedMixin. + TraceDescriptor value_desc = + TraceTrait::GetTraceDescriptor(value); + CPPGC_DCHECK(value_desc.base_object_payload); + const void* key_base_object_payload = + TraceTrait::GetTraceDescriptor(key).base_object_payload; + CPPGC_DCHECK(key_base_object_payload); + + VisitEphemeron(key_base_object_payload, value, value_desc); + } + + /** + * Trace method for a single ephemeron. Used for tracing a raw ephemeron in + * which the `key` and `value` are kept separately. Note that this overload + * is for non-GarbageCollected `value`s that can be traced though. + * + * \param key `WeakMember` reference weakly retaining a key object. + * \param value Reference weakly retaining a value object. Note that + * `ValueType` here should not be `Member`. It is expected that + * `TraceTrait::GetTraceDescriptor(value)` returns a + * `TraceDescriptor` with a null base pointer but a valid trace method. + */ + template + void TraceEphemeron(const WeakMember& weak_member_key, + const ValueType* value) { + static_assert(!IsGarbageCollectedOrMixinTypeV, + "garbage-collected types must use WeakMember and Member"); + const KeyType* key = weak_member_key.GetAtomic(); + if (!key) return; + + // `value` must always be non-null. + CPPGC_DCHECK(value); + TraceDescriptor value_desc = + TraceTrait::GetTraceDescriptor(value); + // `value_desc.base_object_payload` must be null as this override is only + // taken for non-garbage-collected values. + CPPGC_DCHECK(!value_desc.base_object_payload); + + // KeyType might be a GarbageCollectedMixin. + const void* key_base_object_payload = + TraceTrait::GetTraceDescriptor(key).base_object_payload; + CPPGC_DCHECK(key_base_object_payload); + + VisitEphemeron(key_base_object_payload, value, value_desc); + } + + /** + * Trace method that strongifies a WeakMember. + * + * \param weak_member WeakMember reference retaining an object. + */ + template + void TraceStrongly(const WeakMember& weak_member) { + const T* value = weak_member.GetAtomic(); + CPPGC_DCHECK(value != kSentinelPointer); + TraceImpl(value); + } + + /** + * Trace method for retaining containers strongly. + * + * \param object reference to the container. + */ + template + void TraceStrongContainer(const T* object) { + TraceImpl(object); + } + + /** + * Trace method for retaining containers weakly. Note that weak containers + * should emit write barriers. + * + * \param object reference to the container. + * \param callback to be invoked. + * \param callback_data custom data that is passed to the callback. + */ + template + void TraceWeakContainer(const T* object, WeakCallback callback, + const void* callback_data) { + if (!object) return; + VisitWeakContainer(object, TraceTrait::GetTraceDescriptor(object), + TraceTrait::GetWeakTraceDescriptor(object), callback, + callback_data); + } + + /** + * Registers a slot containing a reference to an object allocated on a + * compactable space. Such references maybe be arbitrarily moved by the GC. + * + * \param slot location of reference to object that might be moved by the GC. + * The slot must contain an uncompressed pointer. + */ + template + void RegisterMovableReference(const T** slot) { + static_assert(internal::IsAllocatedOnCompactableSpace::value, + "Only references to objects allocated on compactable spaces " + "should be registered as movable slots."); + static_assert(!IsGarbageCollectedMixinTypeV, + "Mixin types do not support compaction."); + HandleMovableReference(reinterpret_cast(slot)); + } + + /** + * Registers a weak callback that is invoked during garbage collection. + * + * \param callback to be invoked. + * \param data custom data that is passed to the callback. + */ + virtual void RegisterWeakCallback(WeakCallback callback, const void* data) {} + + /** + * Defers tracing an object from a concurrent thread to the mutator thread. + * Should be called by Trace methods of types that are not safe to trace + * concurrently. + * + * \param parameter tells the trace callback which object was deferred. + * \param callback to be invoked for tracing on the mutator thread. + * \param deferred_size size of deferred object. + * + * \returns false if the object does not need to be deferred (i.e. currently + * traced on the mutator thread) and true otherwise (i.e. currently traced on + * a concurrent thread). + */ + virtual V8_WARN_UNUSED_RESULT bool DeferTraceToMutatorThreadIfConcurrent( + const void* parameter, TraceCallback callback, size_t deferred_size) { + // By default tracing is not deferred. + return false; + } + + /** + * Checks whether the visitor is running concurrently to the mutator or not. + */ + virtual bool IsConcurrent() const { return false; } + + protected: + virtual void Visit(const void* self, TraceDescriptor) {} + virtual void VisitWeak(const void* self, TraceDescriptor, WeakCallback, + const void* weak_member) {} + virtual void VisitEphemeron(const void* key, const void* value, + TraceDescriptor value_desc) {} + virtual void VisitWeakContainer(const void* self, TraceDescriptor strong_desc, + TraceDescriptor weak_desc, + WeakCallback callback, const void* data) {} + virtual void HandleMovableReference(const void**) {} + + virtual void VisitMultipleUncompressedMember( + const void* start, size_t len, + TraceDescriptorCallback get_trace_descriptor) { + // Default implementation merely delegates to Visit(). + const char* it = static_cast(start); + const char* end = it + len * internal::kSizeOfUncompressedMember; + for (; it < end; it += internal::kSizeOfUncompressedMember) { + const auto* current = reinterpret_cast(it); + const void* object = current->LoadAtomic(); + if (!object) continue; + + Visit(object, get_trace_descriptor(object)); + } + } + +#if defined(CPPGC_POINTER_COMPRESSION) + virtual void VisitMultipleCompressedMember( + const void* start, size_t len, + TraceDescriptorCallback get_trace_descriptor) { + // Default implementation merely delegates to Visit(). + const char* it = static_cast(start); + const char* end = it + len * internal::kSizeofCompressedMember; + for (; it < end; it += internal::kSizeofCompressedMember) { + const auto* current = + reinterpret_cast(it); + const void* object = current->LoadAtomic(); + if (!object) continue; + + Visit(object, get_trace_descriptor(object)); + } + } +#endif // defined(CPPGC_POINTER_COMPRESSION) + + private: + template + static void WeakCallbackMethodDelegate(const LivenessBroker& info, + const void* self) { + // Callback is registered through a potential const Trace method but needs + // to be able to modify fields. See HandleWeak. + (const_cast(static_cast(self))->*method)(info); + } + + template + static void HandleWeak(const LivenessBroker& info, const void* object) { + const PointerType* weak = static_cast(object); + if (!info.IsHeapObjectAlive(weak->GetFromGC())) { + weak->ClearFromGC(); + } + } + + template + void TraceImpl(const T* t) { + static_assert(sizeof(T), "Pointee type must be fully defined."); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "T must be GarbageCollected or GarbageCollectedMixin type"); + if (!t) { + return; + } + Visit(t, TraceTrait::GetTraceDescriptor(t)); + } + +#if V8_ENABLE_CHECKS + void CheckObjectNotInConstruction(const void* address); +#endif // V8_ENABLE_CHECKS + + template + friend class internal::BasicCrossThreadPersistent; + template + friend class internal::BasicPersistent; + friend class internal::ConservativeTracingVisitor; + friend class internal::VisitorBase; +}; + +template +void EphemeronPair::Trace(Visitor* visitor) const { + visitor->TraceEphemeron(key, value); +} + +namespace internal { + +class V8_EXPORT RootVisitor { + public: + explicit RootVisitor(Visitor::Key) {} + + virtual ~RootVisitor() = default; + + template * = nullptr> + void Trace(const AnyStrongPersistentType& p) { + using PointeeType = typename AnyStrongPersistentType::PointeeType; + const void* object = Extract(p); + if (!object) { + return; + } + VisitRoot(object, TraceTrait::GetTraceDescriptor(object), + p.Location()); + } + + template * = nullptr> + void Trace(const AnyWeakPersistentType& p) { + using PointeeType = typename AnyWeakPersistentType::PointeeType; + static_assert(!internal::IsAllocatedOnCompactableSpace::value, + "Weak references to compactable objects are not allowed"); + const void* object = Extract(p); + if (!object) { + return; + } + VisitWeakRoot(object, TraceTrait::GetTraceDescriptor(object), + &HandleWeak, &p, p.Location()); + } + + protected: + virtual void VisitRoot(const void*, TraceDescriptor, SourceLocation) {} + virtual void VisitWeakRoot(const void* self, TraceDescriptor, WeakCallback, + const void* weak_root, SourceLocation) {} + + private: + template + static const void* Extract(AnyPersistentType& p) { + using PointeeType = typename AnyPersistentType::PointeeType; + static_assert(sizeof(PointeeType), + "Persistent's pointee type must be fully defined"); + static_assert(internal::IsGarbageCollectedOrMixinType::value, + "Persistent's pointee type must be GarbageCollected or " + "GarbageCollectedMixin"); + return p.GetFromGC(); + } + + template + static void HandleWeak(const LivenessBroker& info, const void* object) { + const PointerType* weak = static_cast(object); + if (!info.IsHeapObjectAlive(weak->GetFromGC())) { + weak->ClearFromGC(); + } + } +}; + +} // namespace internal +} // namespace cppgc + +#endif // INCLUDE_CPPGC_VISITOR_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/libplatform/DEPS b/packages/android-jsi/platforms/android/cpp/include/libplatform/DEPS new file mode 100644 index 000000000..d8bcf9988 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/libplatform/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "+libplatform/libplatform-export.h", +] + +specific_include_rules = { + "libplatform\.h": [ + "+libplatform/v8-tracing.h", + ], +} diff --git a/packages/android-jsi/platforms/android/cpp/include/libplatform/libplatform-export.h b/packages/android-jsi/platforms/android/cpp/include/libplatform/libplatform-export.h new file mode 100644 index 000000000..285db86ab --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/libplatform/libplatform-export.h @@ -0,0 +1,29 @@ +// Copyright 2016 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_LIBPLATFORM_LIBPLATFORM_EXPORT_H_ +#define V8_LIBPLATFORM_LIBPLATFORM_EXPORT_H_ + +#if defined(_WIN32) + +#ifdef BUILDING_V8_PLATFORM_SHARED +#define V8_PLATFORM_EXPORT __declspec(dllexport) +#elif USING_V8_PLATFORM_SHARED +#define V8_PLATFORM_EXPORT __declspec(dllimport) +#else +#define V8_PLATFORM_EXPORT +#endif // BUILDING_V8_PLATFORM_SHARED + +#else // defined(_WIN32) + +// Setup for Linux shared library export. +#if defined(BUILDING_V8_PLATFORM_SHARED) || USING_V8_PLATFORM_SHARED +#define V8_PLATFORM_EXPORT __attribute__((visibility("default"))) +#else +#define V8_PLATFORM_EXPORT +#endif // defined(BUILDING_V8_PLATFORM_SHARED) || ... + +#endif // defined(_WIN32) + +#endif // V8_LIBPLATFORM_LIBPLATFORM_EXPORT_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/libplatform/libplatform.h b/packages/android-jsi/platforms/android/cpp/include/libplatform/libplatform.h new file mode 100644 index 000000000..6a34f4324 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/libplatform/libplatform.h @@ -0,0 +1,112 @@ +// Copyright 2014 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_LIBPLATFORM_LIBPLATFORM_H_ +#define V8_LIBPLATFORM_LIBPLATFORM_H_ + +#include + +#include "libplatform/libplatform-export.h" +#include "libplatform/v8-tracing.h" +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { +namespace platform { + +enum class IdleTaskSupport { kDisabled, kEnabled }; +enum class InProcessStackDumping { kDisabled, kEnabled }; + +enum class MessageLoopBehavior : bool { + kDoNotWait = false, + kWaitForWork = true +}; + +enum class PriorityMode : bool { kDontApply, kApply }; + +/** + * Returns a new instance of the default v8::Platform implementation. + * + * The caller will take ownership of the returned pointer. |thread_pool_size| + * is the number of worker threads to allocate for background jobs. If a value + * of zero is passed, a suitable default based on the current number of + * processors online will be chosen. + * If |idle_task_support| is enabled then the platform will accept idle + * tasks (IdleTasksEnabled will return true) and will rely on the embedder + * calling v8::platform::RunIdleTasks to process the idle tasks. + * If |tracing_controller| is nullptr, the default platform will create a + * v8::platform::TracingController instance and use it. + * If |priority_mode| is PriorityMode::kApply, the default platform will use + * multiple task queues executed by threads different system-level priorities + * (where available) to schedule tasks. + */ +V8_PLATFORM_EXPORT std::unique_ptr NewDefaultPlatform( + int thread_pool_size = 0, + IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled, + InProcessStackDumping in_process_stack_dumping = + InProcessStackDumping::kDisabled, + std::unique_ptr tracing_controller = {}, + PriorityMode priority_mode = PriorityMode::kDontApply); + +/** + * The same as NewDefaultPlatform but disables the worker thread pool. + * It must be used with the --single-threaded V8 flag. + */ +V8_PLATFORM_EXPORT std::unique_ptr +NewSingleThreadedDefaultPlatform( + IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled, + InProcessStackDumping in_process_stack_dumping = + InProcessStackDumping::kDisabled, + std::unique_ptr tracing_controller = {}); + +/** + * Returns a new instance of the default v8::JobHandle implementation. + * + * The job will be executed by spawning up to |num_worker_threads| many worker + * threads on the provided |platform| with the given |priority|. + */ +V8_PLATFORM_EXPORT std::unique_ptr NewDefaultJobHandle( + v8::Platform* platform, v8::TaskPriority priority, + std::unique_ptr job_task, size_t num_worker_threads); + +/** + * Pumps the message loop for the given isolate. + * + * The caller has to make sure that this is called from the right thread. + * Returns true if a task was executed, and false otherwise. If the call to + * PumpMessageLoop is nested within another call to PumpMessageLoop, only + * nestable tasks may run. Otherwise, any task may run. Unless requested through + * the |behavior| parameter, this call does not block if no task is pending. The + * |platform| has to be created using |NewDefaultPlatform|. + */ +V8_PLATFORM_EXPORT bool PumpMessageLoop( + v8::Platform* platform, v8::Isolate* isolate, + MessageLoopBehavior behavior = MessageLoopBehavior::kDoNotWait); + +/** + * Runs pending idle tasks for at most |idle_time_in_seconds| seconds. + * + * The caller has to make sure that this is called from the right thread. + * This call does not block if no task is pending. The |platform| has to be + * created using |NewDefaultPlatform|. + */ +V8_PLATFORM_EXPORT void RunIdleTasks(v8::Platform* platform, + v8::Isolate* isolate, + double idle_time_in_seconds); + +/** + * Notifies the given platform about the Isolate getting deleted soon. Has to be + * called for all Isolates which are deleted - unless we're shutting down the + * platform. + * + * The |platform| has to be created using |NewDefaultPlatform|. + * + */ +V8_PLATFORM_EXPORT void NotifyIsolateShutdown(v8::Platform* platform, + Isolate* isolate); + +} // namespace platform +} // namespace v8 + +#endif // V8_LIBPLATFORM_LIBPLATFORM_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/libplatform/v8-tracing.h b/packages/android-jsi/platforms/android/cpp/include/libplatform/v8-tracing.h new file mode 100644 index 000000000..9d05949c3 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/libplatform/v8-tracing.h @@ -0,0 +1,343 @@ +// Copyright 2016 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_LIBPLATFORM_V8_TRACING_H_ +#define V8_LIBPLATFORM_V8_TRACING_H_ + +#include +#include +#include +#include + +#include "libplatform/libplatform-export.h" +#include "v8-platform.h" // NOLINT(build/include_directory) + +namespace perfetto { +#if defined(V8_USE_PERFETTO_JSON_EXPORT) +namespace trace_processor { +class TraceProcessorStorage; +} +#endif // defined(V8_USE_PERFETTO_JSON_EXPORT) +class TracingSession; +} + +namespace v8 { + +namespace base { +class Mutex; +} // namespace base + +namespace platform { +namespace tracing { + +class TraceEventListener; + +const int kTraceMaxNumArgs = 2; + +class V8_PLATFORM_EXPORT TraceObject { + public: + union ArgValue { + uint64_t as_uint; + int64_t as_int; + double as_double; + const void* as_pointer; + const char* as_string; + }; + + TraceObject() = default; + ~TraceObject(); + void Initialize( + char phase, const uint8_t* category_enabled_flag, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags, int64_t timestamp, int64_t cpu_timestamp); + void UpdateDuration(int64_t timestamp, int64_t cpu_timestamp); + void InitializeForTesting( + char phase, const uint8_t* category_enabled_flag, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags, int pid, int tid, int64_t ts, int64_t tts, + uint64_t duration, uint64_t cpu_duration); + + int pid() const { return pid_; } + int tid() const { return tid_; } + char phase() const { return phase_; } + const uint8_t* category_enabled_flag() const { + return category_enabled_flag_; + } + const char* name() const { return name_; } + const char* scope() const { return scope_; } + uint64_t id() const { return id_; } + uint64_t bind_id() const { return bind_id_; } + int num_args() const { return num_args_; } + const char** arg_names() { return arg_names_; } + uint8_t* arg_types() { return arg_types_; } + ArgValue* arg_values() { return arg_values_; } + std::unique_ptr* arg_convertables() { + return arg_convertables_; + } + unsigned int flags() const { return flags_; } + int64_t ts() { return ts_; } + int64_t tts() { return tts_; } + uint64_t duration() { return duration_; } + uint64_t cpu_duration() { return cpu_duration_; } + + private: + int pid_; + int tid_; + char phase_; + const char* name_; + const char* scope_; + const uint8_t* category_enabled_flag_; + uint64_t id_; + uint64_t bind_id_; + int num_args_ = 0; + const char* arg_names_[kTraceMaxNumArgs]; + uint8_t arg_types_[kTraceMaxNumArgs]; + ArgValue arg_values_[kTraceMaxNumArgs]; + std::unique_ptr + arg_convertables_[kTraceMaxNumArgs]; + char* parameter_copy_storage_ = nullptr; + unsigned int flags_; + int64_t ts_; + int64_t tts_; + uint64_t duration_; + uint64_t cpu_duration_; + + // Disallow copy and assign + TraceObject(const TraceObject&) = delete; + void operator=(const TraceObject&) = delete; +}; + +class V8_PLATFORM_EXPORT TraceWriter { + public: + TraceWriter() = default; + virtual ~TraceWriter() = default; + virtual void AppendTraceEvent(TraceObject* trace_event) = 0; + virtual void Flush() = 0; + + static TraceWriter* CreateJSONTraceWriter(std::ostream& stream); + static TraceWriter* CreateJSONTraceWriter(std::ostream& stream, + const std::string& tag); + + static TraceWriter* CreateSystemInstrumentationTraceWriter(); + + private: + // Disallow copy and assign + TraceWriter(const TraceWriter&) = delete; + void operator=(const TraceWriter&) = delete; +}; + +class V8_PLATFORM_EXPORT TraceBufferChunk { + public: + explicit TraceBufferChunk(uint32_t seq); + + void Reset(uint32_t new_seq); + bool IsFull() const { return next_free_ == kChunkSize; } + TraceObject* AddTraceEvent(size_t* event_index); + TraceObject* GetEventAt(size_t index) { return &chunk_[index]; } + + uint32_t seq() const { return seq_; } + size_t size() const { return next_free_; } + + static const size_t kChunkSize = 64; + + private: + size_t next_free_ = 0; + TraceObject chunk_[kChunkSize]; + uint32_t seq_; + + // Disallow copy and assign + TraceBufferChunk(const TraceBufferChunk&) = delete; + void operator=(const TraceBufferChunk&) = delete; +}; + +class V8_PLATFORM_EXPORT TraceBuffer { + public: + TraceBuffer() = default; + virtual ~TraceBuffer() = default; + + virtual TraceObject* AddTraceEvent(uint64_t* handle) = 0; + virtual TraceObject* GetEventByHandle(uint64_t handle) = 0; + virtual bool Flush() = 0; + + static const size_t kRingBufferChunks = 1024; + + static TraceBuffer* CreateTraceBufferRingBuffer(size_t max_chunks, + TraceWriter* trace_writer); + + private: + // Disallow copy and assign + TraceBuffer(const TraceBuffer&) = delete; + void operator=(const TraceBuffer&) = delete; +}; + +// Options determines how the trace buffer stores data. +enum TraceRecordMode { + // Record until the trace buffer is full. + RECORD_UNTIL_FULL, + + // Record until the user ends the trace. The trace buffer is a fixed size + // and we use it as a ring buffer during recording. + RECORD_CONTINUOUSLY, + + // Record until the trace buffer is full, but with a huge buffer size. + RECORD_AS_MUCH_AS_POSSIBLE, + + // Echo to console. Events are discarded. + ECHO_TO_CONSOLE, +}; + +class V8_PLATFORM_EXPORT TraceConfig { + public: + typedef std::vector StringList; + + static TraceConfig* CreateDefaultTraceConfig(); + + TraceConfig() : enable_systrace_(false), enable_argument_filter_(false) {} + TraceRecordMode GetTraceRecordMode() const { return record_mode_; } + const StringList& GetEnabledCategories() const { + return included_categories_; + } + bool IsSystraceEnabled() const { return enable_systrace_; } + bool IsArgumentFilterEnabled() const { return enable_argument_filter_; } + + void SetTraceRecordMode(TraceRecordMode mode) { record_mode_ = mode; } + void EnableSystrace() { enable_systrace_ = true; } + void EnableArgumentFilter() { enable_argument_filter_ = true; } + + void AddIncludedCategory(const char* included_category); + + bool IsCategoryGroupEnabled(const char* category_group) const; + + private: + TraceRecordMode record_mode_; + bool enable_systrace_ : 1; + bool enable_argument_filter_ : 1; + StringList included_categories_; + + // Disallow copy and assign + TraceConfig(const TraceConfig&) = delete; + void operator=(const TraceConfig&) = delete; +}; + +#if defined(_MSC_VER) +#define V8_PLATFORM_NON_EXPORTED_BASE(code) \ + __pragma(warning(suppress : 4275)) code +#else +#define V8_PLATFORM_NON_EXPORTED_BASE(code) code +#endif // defined(_MSC_VER) + +/** + * V8 Tracing controller default implementation. + * + * Will become obsolete in Perfetto build + * (v8_use_perfetto_json_export = true). + */ +class V8_PLATFORM_EXPORT TracingController + : public V8_PLATFORM_NON_EXPORTED_BASE(v8::TracingController) { + public: + TracingController(); + ~TracingController() override; + +#if defined(V8_USE_PERFETTO) + // Must be called before StartTracing() if V8_USE_PERFETTO is true. Provides + // the output stream for the JSON trace data. + void InitializeForPerfetto(std::ostream* output_stream); + // Provide an optional listener for testing that will receive trace events. + // Must be called before StartTracing(). + void SetTraceEventListenerForTesting(TraceEventListener* listener); +#else // defined(V8_USE_PERFETTO) + // The pointer returned from GetCategoryGroupEnabled() points to a value with + // zero or more of the following bits. Used in this class only. The + // TRACE_EVENT macros should only use the value as a bool. These values must + // be in sync with macro values in TraceEvent.h in Blink. + enum CategoryGroupEnabledFlags { + // Category group enabled for the recording mode. + ENABLED_FOR_RECORDING = 1 << 0, + // Category group enabled by SetEventCallbackEnabled(). + ENABLED_FOR_EVENT_CALLBACK = 1 << 2, + // Category group enabled to export events to ETW. + ENABLED_FOR_ETW_EXPORT = 1 << 3 + }; + + // Takes ownership of |trace_buffer|. + void Initialize(TraceBuffer* trace_buffer); + + // v8::TracingController implementation. + const uint8_t* GetCategoryGroupEnabled(const char* category_group) override; + uint64_t AddTraceEvent( + char phase, const uint8_t* category_enabled_flag, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags) override; + uint64_t AddTraceEventWithTimestamp( + char phase, const uint8_t* category_enabled_flag, const char* name, + const char* scope, uint64_t id, uint64_t bind_id, int32_t num_args, + const char** arg_names, const uint8_t* arg_types, + const uint64_t* arg_values, + std::unique_ptr* arg_convertables, + unsigned int flags, int64_t timestamp) override; + void UpdateTraceEventDuration(const uint8_t* category_enabled_flag, + const char* name, uint64_t handle) override; + + static const char* GetCategoryGroupName(const uint8_t* category_enabled_flag); + + void AddTraceStateObserver( + v8::TracingController::TraceStateObserver* observer) override; + void RemoveTraceStateObserver( + v8::TracingController::TraceStateObserver* observer) override; +#endif // !defined(V8_USE_PERFETTO) + + void StartTracing(TraceConfig* trace_config); + void StopTracing(); + + protected: +#if !defined(V8_USE_PERFETTO) + virtual int64_t CurrentTimestampMicroseconds(); + virtual int64_t CurrentCpuTimestampMicroseconds(); +#endif // !defined(V8_USE_PERFETTO) + + private: +#if !defined(V8_USE_PERFETTO) + void UpdateCategoryGroupEnabledFlag(size_t category_index); + void UpdateCategoryGroupEnabledFlags(); +#endif // !defined(V8_USE_PERFETTO) + + std::unique_ptr mutex_; + std::unique_ptr trace_config_; + std::atomic_bool recording_{false}; + +#if defined(V8_USE_PERFETTO_JSON_EXPORT) + std::unique_ptr + trace_processor_; +#endif + +#if defined(V8_USE_PERFETTO) + std::ostream* output_stream_ = nullptr; + TraceEventListener* listener_for_testing_ = nullptr; + std::unique_ptr tracing_session_; +#else // !defined(V8_USE_PERFETTO) + std::unordered_set observers_; + std::unique_ptr trace_buffer_; +#endif // !defined(V8_USE_PERFETTO) + + // Disallow copy and assign + TracingController(const TracingController&) = delete; + void operator=(const TracingController&) = delete; +}; + +#undef V8_PLATFORM_NON_EXPORTED_BASE + +} // namespace tracing +} // namespace platform +} // namespace v8 + +#endif // V8_LIBPLATFORM_V8_TRACING_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-array-buffer.h b/packages/android-jsi/platforms/android/cpp/include/v8-array-buffer.h new file mode 100644 index 000000000..4b55c4453 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-array-buffer.h @@ -0,0 +1,625 @@ +// Copyright 2021 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 INCLUDE_V8_ARRAY_BUFFER_H_ +#define INCLUDE_V8_ARRAY_BUFFER_H_ + +#include + +#include + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-memory-span.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class SharedArrayBuffer; + +#if defined(V8_COMPRESS_POINTERS) && \ + !defined(V8_COMPRESS_POINTERS_IN_SHARED_CAGE) +class IsolateGroup; +#endif + +#ifndef V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT +// Defined using gn arg `v8_array_buffer_internal_field_count`. +#define V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT 2 +#endif + +enum class ArrayBufferCreationMode { kInternalized, kExternalized }; +enum class BackingStoreInitializationMode { kZeroInitialized, kUninitialized }; +enum class BackingStoreOnFailureMode { kReturnNull, kOutOfMemory }; + +/** + * A wrapper around the backing store (i.e. the raw memory) of an array buffer. + * See a document linked in http://crbug.com/v8/9908 for more information. + * + * The allocation and destruction of backing stores is generally managed by + * V8. Clients should always use standard C++ memory ownership types (i.e. + * std::unique_ptr and std::shared_ptr) to manage lifetimes of backing stores + * properly, since V8 internal objects may alias backing stores. + * + * This object does not keep the underlying |ArrayBuffer::Allocator| alive by + * default. Use Isolate::CreateParams::array_buffer_allocator_shared when + * creating the Isolate to make it hold a reference to the allocator itself. + */ +class V8_EXPORT BackingStore : public v8::internal::BackingStoreBase { + public: + ~BackingStore(); + + /** + * Return a pointer to the beginning of the memory block for this backing + * store. The pointer is only valid as long as this backing store object + * lives. + */ + void* Data() const; + + /** + * The length (in bytes) of this backing store. + */ + size_t ByteLength() const; + + /** + * The maximum length (in bytes) that this backing store may grow to. + * + * If this backing store was created for a resizable ArrayBuffer or a growable + * SharedArrayBuffer, it is >= ByteLength(). Otherwise it is == + * ByteLength(). + */ + size_t MaxByteLength() const; + + /** + * Indicates whether the backing store was created for an ArrayBuffer or + * a SharedArrayBuffer. + */ + bool IsShared() const; + + /** + * Indicates whether the backing store is immutable. + */ + bool IsImmutable() const; + + /** + * Indicates whether the backing store was created for a resizable ArrayBuffer + * or a growable SharedArrayBuffer, and thus may be resized by user JavaScript + * code. + */ + bool IsResizableByUserJavaScript() const; + + /** + * Prevent implicit instantiation of operator delete with size_t argument. + * The size_t argument would be incorrect because ptr points to the + * internal BackingStore object. + */ + void operator delete(void* ptr) { ::operator delete(ptr); } + + /** + * This callback is used only if the memory block for a BackingStore cannot be + * allocated with an ArrayBuffer::Allocator. In such cases the destructor of + * the BackingStore invokes the callback to free the memory block. + */ + using DeleterCallback = void (*)(void* data, size_t length, + void* deleter_data); + + /** + * If the memory block of a BackingStore is static or is managed manually, + * then this empty deleter along with nullptr deleter_data can be passed to + * ArrayBuffer::NewBackingStore to indicate that. + * + * The manually managed case should be used with caution and only when it + * is guaranteed that the memory block freeing happens after detaching its + * ArrayBuffer. + */ + static void EmptyDeleter(void* data, size_t length, void* deleter_data); + + private: + /** + * See [Shared]ArrayBuffer::GetBackingStore and + * [Shared]ArrayBuffer::NewBackingStore. + */ + BackingStore(); +}; + +#if !defined(V8_IMMINENT_DEPRECATION_WARNINGS) +// Use v8::BackingStore::DeleterCallback instead. +using BackingStoreDeleterCallback = void (*)(void* data, size_t length, + void* deleter_data); + +#endif + +/** + * An instance of the built-in ArrayBuffer constructor (ES6 draft 15.13.5). + */ +class V8_EXPORT ArrayBuffer : public Object { + public: + /** + * A thread-safe allocator that V8 uses to allocate |ArrayBuffer|'s memory. + * The allocator is a global V8 setting. It has to be set via + * Isolate::CreateParams. + * + * Memory allocated through this allocator by V8 is accounted for as external + * memory by V8. Note that V8 keeps track of the memory for all internalized + * |ArrayBuffer|s. Responsibility for tracking external memory (using + * Isolate::AdjustAmountOfExternalAllocatedMemory) is handed over to the + * embedder upon externalization and taken over upon internalization (creating + * an internalized buffer from an existing buffer). + * + * Note that it is unsafe to call back into V8 from any of the allocator + * functions. + */ + class V8_EXPORT Allocator { + public: + virtual ~Allocator() = default; + + /** + * Allocate |length| bytes. Return nullptr if allocation is not successful. + * Memory should be initialized to zeroes. + */ + virtual void* Allocate(size_t length) = 0; + + /** + * Allocate |length| bytes. Return nullptr if allocation is not successful. + * Memory does not have to be initialized. + */ + virtual void* AllocateUninitialized(size_t length) = 0; + + /** + * Free the memory block of size |length|, pointed to by |data|. + * That memory is guaranteed to be previously allocated by |Allocate|. + */ + virtual void Free(void* data, size_t length) = 0; + + /** + * Returns a size_t that determines the largest ArrayBuffer that can be + * allocated. Override if your Allocator is more restrictive than the + * default. Will only be called once, and the value returned will be + * cached. + * Should not return a value that is larger than kMaxByteLength. + */ + virtual size_t MaxAllocationSize() const { return kMaxByteLength; } + + /** + * ArrayBuffer allocation mode. kNormal is a malloc/free style allocation, + * while kReservation is for larger allocations with the ability to set + * access permissions. + */ + enum class AllocationMode { kNormal, kReservation }; + + /** + * Returns page allocator used by this Allocator instance. + * + * When the sandbox used by Allocator it is expected that this returns + * sandbox's page allocator. + * Otherwise, it should return system page allocator. + */ + virtual PageAllocator* GetPageAllocator() { return nullptr; } + +#if defined(V8_COMPRESS_POINTERS) && \ + !defined(V8_COMPRESS_POINTERS_IN_SHARED_CAGE) + /** + * Convenience allocator. + * + * When the sandbox is enabled, this allocator will allocate its backing + * memory inside the sandbox that belongs to the passed isolate group. + * Otherwise, it will rely on malloc/free. + * + * Caller takes ownership, i.e. the returned object needs to be freed using + * |delete allocator| once it is no longer in use. + */ + static Allocator* NewDefaultAllocator(const IsolateGroup& group); +#endif // defined(V8_COMPRESS_POINTERS) && + // !defined(V8_COMPRESS_POINTERS_IN_SHARED_CAGE) + + /** + * Convenience allocator. + * + * When the sandbox is enabled, this allocator will allocate its backing + * memory inside the default global sandbox. Otherwise, it will rely on + * malloc/free. + * + * Caller takes ownership, i.e. the returned object needs to be freed using + * |delete allocator| once it is no longer in use. + */ + static Allocator* NewDefaultAllocator(); + }; + + /** + * Data length in bytes. + */ + size_t ByteLength() const; + + /** + * Maximum length in bytes. + */ + size_t MaxByteLength() const; + + /** + * Attempt to create a new ArrayBuffer. Allocate |byte_length| bytes. + * Allocated memory will be owned by a created ArrayBuffer and + * will be deallocated when it is garbage-collected, + * unless the object is externalized. If allocation fails, the Maybe + * returned will be empty. + */ + static MaybeLocal MaybeNew( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized); + + /** + * Create a new ArrayBuffer. Allocate |byte_length| bytes, which are either + * zero-initialized or uninitialized. Allocated memory will be owned by a + * created ArrayBuffer and will be deallocated when it is garbage-collected, + * unless the object is externalized. + */ + static Local New( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized); + + /** + * Create a new ArrayBuffer with an existing backing store. + * The created array keeps a reference to the backing store until the array + * is garbage collected. Note that the IsExternal bit does not affect this + * reference from the array to the backing store. + * + * In future IsExternal bit will be removed. Until then the bit is set as + * follows. If the backing store does not own the underlying buffer, then + * the array is created in externalized state. Otherwise, the array is created + * in internalized state. In the latter case the array can be transitioned + * to the externalized state using Externalize(backing_store). + */ + static Local New(Isolate* isolate, + std::shared_ptr backing_store); + + /** + * Returns a new standalone BackingStore that is allocated using the array + * buffer allocator of the isolate. The allocation can either be zero + * initialized, or uninitialized. The result can be later passed to + * ArrayBuffer::New. + * + * If the allocator returns nullptr, then the function may cause GCs in the + * given isolate and re-try the allocation. + * + * If GCs do not help and on_failure is kOutOfMemory, then the + * function will crash with an out-of-memory error. + * + * Otherwise if GCs do not help (or the allocation is too large for GCs to + * help) and on_failure is kReturnNull, then a null result is returned. + */ + static std::unique_ptr NewBackingStore( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized, + BackingStoreOnFailureMode on_failure = + BackingStoreOnFailureMode::kOutOfMemory); + + /** + * Returns a new standalone BackingStore that takes over the ownership of + * the given buffer. The destructor of the BackingStore invokes the given + * deleter callback. + * + * The result can be later passed to ArrayBuffer::New. The raw pointer + * to the buffer must not be passed again to any V8 API function. + */ + static std::unique_ptr NewBackingStore( + void* data, size_t byte_length, v8::BackingStore::DeleterCallback deleter, + void* deleter_data); + + /** + * Returns a new resizable standalone BackingStore that is allocated using the + * array buffer allocator of the isolate. The result can be later passed to + * ArrayBuffer::New. + * + * |byte_length| must be <= |max_byte_length|. + * + * This function is usable without an isolate. Unlike |NewBackingStore| calls + * with an isolate, GCs cannot be triggered, and there are no + * retries. Allocation failure will cause the function to crash with an + * out-of-memory error. + */ + static std::unique_ptr NewResizableBackingStore( + size_t byte_length, size_t max_byte_length); + + /** + * Returns true if this ArrayBuffer may be detached. + */ + bool IsDetachable() const; + + /** + * Returns true if this ArrayBuffer has been detached. + */ + bool WasDetached() const; + + /** + * Returns true if this ArrayBuffer is immutable. + */ + bool IsImmutable() const; + + /** + * Detaches this ArrayBuffer and all its views (typed arrays). + * Detaching sets the byte length of the buffer and all typed arrays to zero, + * preventing JavaScript from ever accessing underlying backing store. + * ArrayBuffer should have been externalized and must be detachable. + */ + V8_DEPRECATED( + "Use the version which takes a key parameter (passing a null handle is " + "ok).") + void Detach(); + + /** + * Detaches this ArrayBuffer and all its views (typed arrays). + * Detaching sets the byte length of the buffer and all typed arrays to zero, + * preventing JavaScript from ever accessing underlying backing store. + * ArrayBuffer should have been externalized and must be detachable. Returns + * Nothing if the key didn't pass the [[ArrayBufferDetachKey]] check, + * Just(true) otherwise. + */ + V8_WARN_UNUSED_RESULT Maybe Detach(v8::Local key); + + /** + * Sets the ArrayBufferDetachKey. + */ + void SetDetachKey(v8::Local key); + + /** + * Get a shared pointer to the backing store of this array buffer. This + * pointer coordinates the lifetime management of the internal storage + * with any live ArrayBuffers on the heap, even across isolates. The embedder + * should not attempt to manage lifetime of the storage through other means. + * + * The returned shared pointer will not be empty, even if the ArrayBuffer has + * been detached. Use |WasDetached| to tell if it has been detached instead. + */ + std::shared_ptr GetBackingStore(); + + /** + * More efficient shortcut for + * GetBackingStore()->IsResizableByUserJavaScript(). + */ + bool IsResizableByUserJavaScript() const; + + /** + * More efficient shortcut for GetBackingStore()->Data(). The returned pointer + * is valid as long as the ArrayBuffer is alive. + */ + void* Data() const; + + V8_INLINE static ArrayBuffer* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + static constexpr int kInternalFieldCount = + V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT; + static constexpr int kEmbedderFieldCount = kInternalFieldCount; + +#if V8_ENABLE_SANDBOX + static constexpr size_t kMaxByteLength = + internal::kMaxSafeBufferSizeForSandbox; +#elif V8_HOST_ARCH_32_BIT + static constexpr size_t kMaxByteLength = std::numeric_limits::max(); +#else + // The maximum safe integer (2^53 - 1). + static constexpr size_t kMaxByteLength = + static_cast((uint64_t{1} << 53) - 1); +#endif + + private: + ArrayBuffer(); + static void CheckCast(Value* obj); + friend class TypedArray; +}; + +#ifndef V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT +// Defined using gn arg `v8_array_buffer_view_internal_field_count`. +#define V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT 2 +#endif + +/** + * A base class for an instance of one of "views" over ArrayBuffer, + * including TypedArrays and DataView (ES6 draft 15.13). + */ +class V8_EXPORT ArrayBufferView : public Object { + public: + /** + * Returns underlying ArrayBuffer. + */ + Local Buffer(); + /** + * Byte offset in |Buffer|. + */ + size_t ByteOffset(); + /** + * Size of a view in bytes. + */ + size_t ByteLength(); + + /** + * Copy the contents of the ArrayBufferView's buffer to an embedder defined + * memory without additional overhead that calling ArrayBufferView::Buffer + * might incur. + * + * Will write at most min(|byte_length|, ByteLength) bytes starting at + * ByteOffset of the underlying buffer to the memory starting at |dest|. + * Returns the number of bytes actually written. + */ + size_t CopyContents(void* dest, size_t byte_length); + + /** + * Returns the contents of the ArrayBufferView's buffer as a MemorySpan. If + * the contents are on the V8 heap, they get copied into `storage`. Otherwise + * a view into the off-heap backing store is returned. The provided storage + * should be at least as large as the maximum on-heap size of a TypedArray, + * was defined in gn with `typed_array_max_size_in_heap`. The default value is + * 64 bytes. + */ + v8::MemorySpan GetContents(v8::MemorySpan storage); + + /** + * Returns true if ArrayBufferView's backing ArrayBuffer has already been + * allocated. + */ + bool HasBuffer() const; + + V8_INLINE static ArrayBufferView* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + static constexpr int kInternalFieldCount = + V8_ARRAY_BUFFER_VIEW_INTERNAL_FIELD_COUNT; + static const int kEmbedderFieldCount = kInternalFieldCount; + + private: + ArrayBufferView(); + static void CheckCast(Value* obj); +}; + +/** + * An instance of DataView constructor (ES6 draft 15.13.7). + */ +class V8_EXPORT DataView : public ArrayBufferView { + public: + static Local New(Local array_buffer, + size_t byte_offset, size_t length); + static Local New(Local shared_array_buffer, + size_t byte_offset, size_t length); + V8_INLINE static DataView* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + private: + DataView(); + static void CheckCast(Value* obj); +}; + +/** + * An instance of the built-in SharedArrayBuffer constructor. + */ +class V8_EXPORT SharedArrayBuffer : public Object { + public: + /** + * Data length in bytes. + */ + size_t ByteLength() const; + + /** + * Maximum length in bytes. + */ + size_t MaxByteLength() const; + + /** + * Create a new SharedArrayBuffer. Allocate |byte_length| bytes, which are + * either zero-initialized or uninitialized. Allocated memory will be owned by + * a created SharedArrayBuffer and will be deallocated when it is + * garbage-collected, unless the object is externalized. + */ + static Local New( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized); + + /** + * Create a new SharedArrayBuffer. Allocate |byte_length| bytes, which are + * either zero-initialized or uninitialized. Allocated memory will be owned by + * a created SharedArrayBuffer and will be deallocated when it is + * garbage-collected, unless the object is externalized. If allocation + * fails, the Maybe returned will be empty. + */ + static MaybeLocal MaybeNew( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized); + + /** + * Create a new SharedArrayBuffer with an existing backing store. + * The created array keeps a reference to the backing store until the array + * is garbage collected. Note that the IsExternal bit does not affect this + * reference from the array to the backing store. + * + * In future IsExternal bit will be removed. Until then the bit is set as + * follows. If the backing store does not own the underlying buffer, then + * the array is created in externalized state. Otherwise, the array is created + * in internalized state. In the latter case the array can be transitioned + * to the externalized state using Externalize(backing_store). + */ + static Local New( + Isolate* isolate, std::shared_ptr backing_store); + + /** + * Returns a new standalone BackingStore that is allocated using the array + * buffer allocator of the isolate. The allocation can either be zero + * initialized, or uninitialized. The result can be later passed to + * SharedArrayBuffer::New. + * + * If the allocator returns nullptr, then the function may cause GCs in the + * given isolate and re-try the allocation. + * + * If on_failure is kOutOfMemory and GCs do not help, then the function will + * crash with an out-of-memory error. + * + * Otherwise, if on_failure is kReturnNull and GCs do not help (or the + * byte_length is so large that the allocation cannot succeed), then a null + * result is returned. + */ + static std::unique_ptr NewBackingStore( + Isolate* isolate, size_t byte_length, + BackingStoreInitializationMode initialization_mode = + BackingStoreInitializationMode::kZeroInitialized, + BackingStoreOnFailureMode on_failure = + BackingStoreOnFailureMode::kOutOfMemory); + + /** + * Returns a new standalone BackingStore that takes over the ownership of + * the given buffer. The destructor of the BackingStore invokes the given + * deleter callback. + * + * The result can be later passed to SharedArrayBuffer::New. The raw pointer + * to the buffer must not be passed again to any V8 functions. + */ + static std::unique_ptr NewBackingStore( + void* data, size_t byte_length, v8::BackingStore::DeleterCallback deleter, + void* deleter_data); + + /** + * Get a shared pointer to the backing store of this array buffer. This + * pointer coordinates the lifetime management of the internal storage + * with any live ArrayBuffers on the heap, even across isolates. The embedder + * should not attempt to manage lifetime of the storage through other means. + */ + std::shared_ptr GetBackingStore(); + + /** + * More efficient shortcut for GetBackingStore()->Data(). The returned pointer + * is valid as long as the ArrayBuffer is alive. + */ + void* Data() const; + + V8_INLINE static SharedArrayBuffer* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + static constexpr int kInternalFieldCount = + V8_ARRAY_BUFFER_INTERNAL_FIELD_COUNT; + + private: + SharedArrayBuffer(); + static void CheckCast(Value* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_ARRAY_BUFFER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-callbacks.h b/packages/android-jsi/platforms/android/cpp/include/v8-callbacks.h new file mode 100644 index 000000000..e5eba5a20 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-callbacks.h @@ -0,0 +1,533 @@ +// Copyright 2021 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 INCLUDE_V8_ISOLATE_CALLBACKS_H_ +#define INCLUDE_V8_ISOLATE_CALLBACKS_H_ + +#include + +#include +#include + +#include "cppgc/common.h" +#include "v8-data.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-promise.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +#if defined(V8_OS_WIN) +struct _EXCEPTION_POINTERS; +#endif + +namespace v8 { + +template +class FunctionCallbackInfo; +class Isolate; +class Message; +class Module; +class Object; +class Promise; +class ScriptOrModule; +class String; +class UnboundScript; +class Value; + +/** + * A JIT code event is issued each time code is added, moved or removed. + * + * \note removal events are not currently issued. + */ +struct JitCodeEvent { + enum EventType { + CODE_ADDED, + CODE_MOVED, + CODE_REMOVED, + CODE_ADD_LINE_POS_INFO, + CODE_START_LINE_INFO_RECORDING, + CODE_END_LINE_INFO_RECORDING + }; + // Definition of the code position type. The "POSITION" type means the place + // in the source code which are of interest when making stack traces to + // pin-point the source location of a stack frame as close as possible. + // The "STATEMENT_POSITION" means the place at the beginning of each + // statement, and is used to indicate possible break locations. + enum PositionType { POSITION, STATEMENT_POSITION }; + + // There are three different kinds of CodeType, one for JIT code generated + // by the optimizing compiler, one for byte code generated for the + // interpreter, and one for code generated from Wasm. For JIT_CODE and + // WASM_CODE, |code_start| points to the beginning of jitted assembly code, + // while for BYTE_CODE events, |code_start| points to the first bytecode of + // the interpreted function. + enum CodeType { BYTE_CODE, JIT_CODE, WASM_CODE }; + + // Type of event. + EventType type; + CodeType code_type; + // Start of the instructions. + void* code_start; + // Size of the instructions. + size_t code_len; + // Script info for CODE_ADDED event. + Local script; + // User-defined data for *_LINE_INFO_* event. It's used to hold the source + // code line information which is returned from the + // CODE_START_LINE_INFO_RECORDING event. And it's passed to subsequent + // CODE_ADD_LINE_POS_INFO and CODE_END_LINE_INFO_RECORDING events. + void* user_data; + + struct name_t { + // Name of the object associated with the code, note that the string is not + // zero-terminated. + const char* str; + // Number of chars in str. + size_t len; + }; + + struct line_info_t { + // PC offset + size_t offset; + // Code position + size_t pos; + // The position type. + PositionType position_type; + }; + + struct wasm_source_info_t { + // Source file name. + const char* filename; + // Length of filename. + size_t filename_size; + // Line number table, which maps offsets of JITted code to line numbers of + // source file. + const line_info_t* line_number_table; + // Number of entries in the line number table. + size_t line_number_table_size; + }; + + wasm_source_info_t* wasm_source_info = nullptr; + + union { + // Only valid for CODE_ADDED. + struct name_t name; + + // Only valid for CODE_ADD_LINE_POS_INFO + struct line_info_t line_info; + + // New location of instructions. Only valid for CODE_MOVED. + void* new_code_start; + }; + + Isolate* isolate; +}; + +/** + * Option flags passed to the SetJitCodeEventHandler function. + */ +enum JitCodeEventOptions { + kJitCodeEventDefault = 0, + // Generate callbacks for already existent code. + kJitCodeEventEnumExisting = 1, + + kLastJitCodeEventOption = kJitCodeEventEnumExisting +}; + +/** + * Callback function passed to SetJitCodeEventHandler. + * + * \param event code add, move or removal event. + */ +using JitCodeEventHandler = void (*)(const JitCodeEvent* event); + +// --- Garbage Collection Callbacks --- + +/** + * Applications can register callback functions which will be called before and + * after certain garbage collection operations. Allocations are not allowed in + * the callback functions, you therefore cannot manipulate objects (set or + * delete properties for example) since it is possible such operations will + * result in the allocation of objects. + * TODO(v8:12612): Deprecate kGCTypeMinorMarkSweep after updating blink. + */ +enum GCType { + kGCTypeScavenge = 1 << 0, + kGCTypeMinorMarkSweep = 1 << 1, + kGCTypeMarkSweepCompact = 1 << 2, + kGCTypeIncrementalMarking = 1 << 3, + kGCTypeProcessWeakCallbacks = 1 << 4, + kGCTypeAll = kGCTypeScavenge | kGCTypeMinorMarkSweep | + kGCTypeMarkSweepCompact | kGCTypeIncrementalMarking | + kGCTypeProcessWeakCallbacks +}; + +/** + * GCCallbackFlags is used to notify additional information about the GC + * callback. + * - kGCCallbackFlagConstructRetainedObjectInfos: The GC callback is for + * constructing retained object infos. + * - kGCCallbackFlagForced: The GC callback is for a forced GC for testing. + * - kGCCallbackFlagSynchronousPhantomCallbackProcessing: The GC callback + * is called synchronously without getting posted to an idle task. + * - kGCCallbackFlagCollectAllAvailableGarbage: The GC callback is called + * in a phase where V8 is trying to collect all available garbage + * (e.g., handling a low memory notification). + * - kGCCallbackScheduleIdleGarbageCollection: The GC callback is called to + * trigger an idle garbage collection. + */ +enum GCCallbackFlags { + kNoGCCallbackFlags = 0, + kGCCallbackFlagConstructRetainedObjectInfos = 1 << 1, + kGCCallbackFlagForced = 1 << 2, + kGCCallbackFlagSynchronousPhantomCallbackProcessing = 1 << 3, + kGCCallbackFlagCollectAllAvailableGarbage = 1 << 4, + kGCCallbackFlagCollectAllExternalMemory = 1 << 5, + kGCCallbackScheduleIdleGarbageCollection = 1 << 6, + kGCCallbackFlagLastResort = 1 << 7, +}; + +using GCCallback = void (*)(GCType type, GCCallbackFlags flags); + +using InterruptCallback = void (*)(Isolate* isolate, void* data); + +using PrintCurrentStackTraceFilterCallback = + bool (*)(Isolate* isolate, Local script_name); + +/** + * This callback is invoked when the heap size is close to the heap limit and + * V8 is likely to abort with out-of-memory error. + * The callback can extend the heap limit by returning a value that is greater + * than the current_heap_limit. The initial heap limit is the limit that was + * set after heap setup. + */ +using NearHeapLimitCallback = size_t (*)(void* data, size_t current_heap_limit, + size_t initial_heap_limit); + +/** + * Callback function passed to SetUnhandledExceptionCallback. + */ +#if defined(V8_OS_WIN) +using UnhandledExceptionCallback = + int (*)(_EXCEPTION_POINTERS* exception_pointers); +#endif + +// --- Counters Callbacks --- + +using CounterLookupCallback = int* (*)(const char* name); + +using CreateHistogramCallback = void* (*)(const char* name, int min, int max, + size_t buckets); + +using AddHistogramSampleCallback = void (*)(void* histogram, int sample); + +// --- Exceptions --- + +using FatalErrorCallback = void (*)(const char* location, const char* message); + +struct OOMDetails { + bool is_heap_oom = false; + const char* detail = nullptr; +}; + +using OOMErrorCallback = void (*)(const char* location, + const OOMDetails& details); + +using OOMErrorCallbackWithData = void (*)(const char* location, + const OOMDetails& details, + void* data); + +using MessageCallback = void (*)(Local message, Local data); + +// --- Tracing --- + +enum LogEventStatus : int { kStart = 0, kEnd = 1, kLog = 2 }; +using LogEventCallback = void (*)(const char* name, + int /* LogEventStatus */ status); + +// --- Crashkeys Callback --- +enum class CrashKeyId { + kIsolateAddress, + kReadonlySpaceFirstPageAddress, + kMapSpaceFirstPageAddress V8_ENUM_DEPRECATE_SOON("Map space got removed"), + kOldSpaceFirstPageAddress, + kCodeRangeBaseAddress, + kCodeSpaceFirstPageAddress, + kDumpType, + kSnapshotChecksumCalculated, + kSnapshotChecksumExpected, +}; + +using AddCrashKeyCallback = void (*)(CrashKeyId id, const std::string& value); + +// --- CrashKeyString Callbacks --- +using CrashKey = void*; +enum class CrashKeySize { Size32, Size64, Size256, Size1024 }; + +using AllocateCrashKeyStringCallback = + std::function; +using SetCrashKeyStringCallback = + std::function; + +// --- Enter/Leave Script Callback --- +using BeforeCallEnteredCallback = void (*)(Isolate*); +using CallCompletedCallback = void (*)(Isolate*); + +// --- Modify Code Generation From Strings Callback --- +struct ModifyCodeGenerationFromStringsResult { + // If true, proceed with the codegen algorithm. Otherwise, block it. + bool codegen_allowed = false; + // Overwrite the original source with this string, if present. + // Use the original source if empty. + // This field is considered only if codegen_allowed is true. + MaybeLocal modified_source; +}; + +/** + * Callback to check if codegen is allowed from a source object, and convert + * the source to string if necessary. See: ModifyCodeGenerationFromStrings. + */ +using ModifyCodeGenerationFromStringsCallback = + ModifyCodeGenerationFromStringsResult (*)(Local context, + Local source); +using ModifyCodeGenerationFromStringsCallback2 = + ModifyCodeGenerationFromStringsResult (*)(Local context, + Local source, + bool is_code_like); + +// --- Failed Access Check Callback --- + +/** + * Access type specification. + */ +enum AccessType { + ACCESS_GET, + ACCESS_SET, + ACCESS_HAS, + ACCESS_DELETE, + ACCESS_KEYS +}; + +using FailedAccessCheckCallback = void (*)(Local target, + AccessType type, Local data); + +// --- WebAssembly compilation callbacks --- +using ExtensionCallback = bool (*)(const FunctionCallbackInfo&); + +using AllowWasmCodeGenerationCallback = bool (*)(Local context, + Local source); + +// --- Callback for APIs defined on v8-supported objects, but implemented +// by the embedder. Example: WebAssembly.{compile|instantiate}Streaming --- +using ApiImplementationCallback = void (*)(const FunctionCallbackInfo&); + +// --- Callback for WebAssembly.compileStreaming --- +using WasmStreamingCallback = void (*)(const FunctionCallbackInfo&); + +enum class WasmAsyncSuccess { kSuccess, kFail }; + +// --- Callback called when async WebAssembly operations finish --- +using WasmAsyncResolvePromiseCallback = void (*)( + Isolate* isolate, Local context, Local resolver, + Local result, WasmAsyncSuccess success); + +// --- Callback for loading source map file for Wasm profiling support +using WasmLoadSourceMapCallback = Local (*)(Isolate* isolate, + const char* name); + +// --- Callback for checking if WebAssembly Custom Descriptors are enabled --- +using WasmCustomDescriptorsEnabledCallback = bool (*)(Local context); + +// --- Callback for checking if the SharedArrayBuffer constructor is enabled --- +using SharedArrayBufferConstructorEnabledCallback = + bool (*)(Local context); + +/** + * Import phases in import requests. + */ +enum class ModuleImportPhase { + kSource, + kDefer, + kEvaluation, +}; + +/** + * HostImportModuleDynamicallyCallback is called when we + * require the embedder to load a module. This is used as part of the dynamic + * import syntax. + * + * The referrer contains metadata about the script/module that calls + * import. + * + * The specifier is the name of the module that should be imported. + * + * The import_attributes are import attributes for this request in the form: + * [key1, value1, key2, value2, ...] where the keys and values are of type + * v8::String. Note, unlike the FixedArray passed to ResolveModuleCallback and + * returned from ModuleRequest::GetImportAttributes(), this array does not + * contain the source Locations of the attributes. + * + * The embedder must compile, instantiate, evaluate the Module, and + * obtain its namespace object. + * + * The Promise returned from this function is forwarded to userland + * JavaScript. The embedder must resolve this promise with the module + * namespace object. In case of an exception, the embedder must reject + * this promise with the exception. If the promise creation itself + * fails (e.g. due to stack overflow), the embedder must propagate + * that exception by returning an empty MaybeLocal. + */ +using HostImportModuleDynamicallyCallback = MaybeLocal (*)( + Local context, Local host_defined_options, + Local resource_name, Local specifier, + Local import_attributes); + +/** + * HostImportModuleWithPhaseDynamicallyCallback is called when we + * require the embedder to load a module with a specific phase. This is used + * as part of the dynamic import syntax. + * + * The referrer contains metadata about the script/module that calls + * import. + * + * The specifier is the name of the module that should be imported. + * + * The phase is the phase of the import requested. + * + * The import_attributes are import attributes for this request in the form: + * [key1, value1, key2, value2, ...] where the keys and values are of type + * v8::String. Note, unlike the FixedArray passed to ResolveModuleCallback and + * returned from ModuleRequest::GetImportAttributes(), this array does not + * contain the source Locations of the attributes. + * + * The Promise returned from this function is forwarded to userland + * JavaScript. The embedder must resolve this promise according to the phase + * requested: + * - For ModuleImportPhase::kSource, the promise must be resolved with a + * compiled ModuleSource object, or rejected with a SyntaxError if the + * module does not support source representation. + * - For ModuleImportPhase::kEvaluation, the promise must be resolved with a + * ModuleNamespace object of a module that has been compiled, instantiated, + * and evaluated. + * + * In case of an exception, the embedder must reject this promise with the + * exception. If the promise creation itself fails (e.g. due to stack + * overflow), the embedder must propagate that exception by returning an empty + * MaybeLocal. + * + * This callback is still experimental and is only invoked for source phase + * imports. + */ +using HostImportModuleWithPhaseDynamicallyCallback = MaybeLocal (*)( + Local context, Local host_defined_options, + Local resource_name, Local specifier, + ModuleImportPhase phase, Local import_attributes); + +/** + * Callback for requesting a compile hint for a function from the embedder. The + * first parameter is the position of the function in source code and the second + * parameter is embedder data to be passed back. + */ +using CompileHintCallback = bool (*)(int, void*); + +/** + * HostInitializeImportMetaObjectCallback is called the first time import.meta + * is accessed for a module. Subsequent access will reuse the same value. + * + * The method combines two implementation-defined abstract operations into one: + * HostGetImportMetaProperties and HostFinalizeImportMeta. + * + * The embedder should use v8::Object::CreateDataProperty to add properties on + * the meta object. + */ +using HostInitializeImportMetaObjectCallback = void (*)(Local context, + Local module, + Local meta); + +/** + * HostCreateShadowRealmContextCallback is called each time a ShadowRealm is + * being constructed in the initiator_context. + * + * The method combines Context creation and implementation defined abstract + * operation HostInitializeShadowRealm into one. + * + * The embedder should use v8::Context::New or v8::Context:NewFromSnapshot to + * create a new context. If the creation fails, the embedder must propagate + * that exception by returning an empty MaybeLocal. + */ +using HostCreateShadowRealmContextCallback = + MaybeLocal (*)(Local initiator_context); + +/** + * IsJSApiWrapperNativeErrorCallback is called on an JSApiWrapper object to + * determine if Error.isError should return true or false. For instance, in an + * HTML embedder, DOMExceptions return true when passed to Error.isError. + */ +using IsJSApiWrapperNativeErrorCallback = bool (*)(Isolate* isolate, + Local obj); + +/** + * PrepareStackTraceCallback is called when the stack property of an error is + * first accessed. The return value will be used as the stack value. If this + * callback is registed, the |Error.prepareStackTrace| API will be disabled. + * |sites| is an array of call sites, specified in + * https://v8.dev/docs/stack-trace-api + */ +using PrepareStackTraceCallback = MaybeLocal (*)(Local context, + Local error, + Local sites); + +#if defined(V8_OS_WIN) +/** + * Callback to selectively enable ETW tracing based on the document URL. + * Implemented by the embedder, it should never call back into V8. + * + * Windows allows passing additional data to the ETW EnableCallback: + * https://learn.microsoft.com/en-us/windows/win32/api/evntprov/nc-evntprov-penablecallback + * + * This data can be configured in a WPR (Windows Performance Recorder) + * profile, adding a CustomFilter to an EventProvider like the following: + * + * + * + * + * + * Where: + * - Name="57277741-3638-4A4B-BDBA-0AC6E45DA56C" is the GUID of the V8 + * ETW provider, (see src/libplatform/etw/etw-provider-win.h), + * - Type="0x80000000" is EVENT_FILTER_TYPE_SCHEMATIZED, + * - Value="AQABAAAAAA..." is a base64-encoded byte array that is + * base64-decoded by Windows and passed to the ETW enable callback in + * the 'PEVENT_FILTER_DESCRIPTOR FilterData' argument; see: + * https://learn.microsoft.com/en-us/windows/win32/api/evntprov/ns-evntprov-event_filter_descriptor. + * + * This array contains a struct EVENT_FILTER_HEADER followed by a + * variable length payload, and as payload we pass a string in JSON format, + * with a list of regular expressions that should match the document URL + * in order to enable ETW tracing: + * { + * "version": "2.0", + * "filtered_urls": [ + * "https:\/\/.*\.chromium\.org\/.*", "https://v8.dev/";, "..." + * ], + * "trace_interpreter_frames": true + * } + */ + +using FilterETWSessionByURLCallback = + bool (*)(Local context, const std::string& etw_filter_payload); + +struct FilterETWSessionByURLResult { + // If true, enable ETW tracing for the current isolate. + bool enable_etw_tracing; + + // If true, also enables ETW tracing for interpreter stack frames. + bool trace_interpreter_frames; +}; +using FilterETWSessionByURL2Callback = FilterETWSessionByURLResult (*)( + Local context, const std::string& etw_filter_payload); +#endif // V8_OS_WIN + +} // namespace v8 + +#endif // INCLUDE_V8_ISOLATE_CALLBACKS_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-container.h b/packages/android-jsi/platforms/android/cpp/include/v8-container.h new file mode 100644 index 000000000..380999e5e --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-container.h @@ -0,0 +1,183 @@ +// Copyright 2021 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 INCLUDE_V8_CONTAINER_H_ +#define INCLUDE_V8_CONTAINER_H_ + +#include +#include + +#include + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; +class Isolate; + +/** + * An instance of the built-in array constructor (ECMA-262, 15.4.2). + */ +class V8_EXPORT Array : public Object { + public: + uint32_t Length() const; + + /** + * Creates a JavaScript array with the given length. If the length + * is negative the returned array will have length 0. + */ + static Local New(Isolate* isolate, int length = 0); + + /** + * Creates a JavaScript array out of a Local array in C++ + * with a known length. + */ + static Local New(Isolate* isolate, Local* elements, + size_t length); + V8_INLINE static Array* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + /** + * Creates a JavaScript array from a provided callback. + * + * \param context The v8::Context to create the array in. + * \param length The length of the array to be created. + * \param next_value_callback The callback that is invoked to retrieve + * elements for the array. The embedder can signal that the array + * initialization should be aborted by throwing an exception and returning + * an empty MaybeLocal. + * \returns The v8::Array if all elements were constructed successfully and an + * empty MaybeLocal otherwise. + */ + static MaybeLocal New( + Local context, size_t length, + std::function()> next_value_callback); + + enum class CallbackResult { + kException, + kBreak, + kContinue, + }; + using IterationCallback = CallbackResult (*)(uint32_t index, + Local element, + void* data); + + /** + * Calls {callback} for every element of this array, passing {callback_data} + * as its {data} parameter. + * This function will typically be faster than calling {Get()} repeatedly. + * As a consequence of being optimized for low overhead, the provided + * callback must adhere to the following restrictions: + * - It must not allocate any V8 objects and continue iterating; it may + * allocate (e.g. an error message/object) and then immediately terminate + * the iteration. + * - It must not modify the array being iterated. + * - It must not call back into V8 (unless it can guarantee that such a + * call does not violate the above restrictions, which is difficult). + * - The {Local element} must not "escape", i.e. must not be assigned + * to any other {Local}. Creating a {Global} from it, or updating a + * v8::TypecheckWitness with it, is safe. + * These restrictions may be lifted in the future if use cases arise that + * justify a slower but more robust implementation. + * + * Returns {Nothing} on exception; use a {TryCatch} to catch and handle this + * exception. + * When the {callback} returns {kException}, iteration is terminated + * immediately, returning {Nothing}. By returning {kBreak}, the callback + * can request non-exceptional early termination of the iteration. + */ + Maybe Iterate(Local context, IterationCallback callback, + void* callback_data); + + private: + Array(); + static void CheckCast(Value* obj); +}; + +/** + * An instance of the built-in Map constructor (ECMA-262, 6th Edition, 23.1.1). + */ +class V8_EXPORT Map : public Object { + public: + size_t Size() const; + void Clear(); + V8_WARN_UNUSED_RESULT MaybeLocal Get(Local context, + Local key); + V8_WARN_UNUSED_RESULT MaybeLocal Set(Local context, + Local key, + Local value); + V8_WARN_UNUSED_RESULT Maybe Has(Local context, + Local key); + V8_WARN_UNUSED_RESULT Maybe Delete(Local context, + Local key); + + /** + * Returns an array of length Size() * 2, where index N is the Nth key and + * index N + 1 is the Nth value. + */ + Local AsArray() const; + + /** + * Creates a new empty Map. + */ + static Local New(Isolate* isolate); + + V8_INLINE static Map* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + private: + Map(); + static void CheckCast(Value* obj); +}; + +/** + * An instance of the built-in Set constructor (ECMA-262, 6th Edition, 23.2.1). + */ +class V8_EXPORT Set : public Object { + public: + size_t Size() const; + void Clear(); + V8_WARN_UNUSED_RESULT MaybeLocal Add(Local context, + Local key); + V8_WARN_UNUSED_RESULT Maybe Has(Local context, + Local key); + V8_WARN_UNUSED_RESULT Maybe Delete(Local context, + Local key); + + /** + * Returns an array of the keys in this Set. + */ + Local AsArray() const; + + /** + * Creates a new empty Set. + */ + static Local New(Isolate* isolate); + + V8_INLINE static Set* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + private: + Set(); + static void CheckCast(Value* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_CONTAINER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-context.h b/packages/android-jsi/platforms/android/cpp/include/v8-context.h new file mode 100644 index 000000000..21137f628 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-context.h @@ -0,0 +1,580 @@ +// Copyright 2021 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 INCLUDE_V8_CONTEXT_H_ +#define INCLUDE_V8_CONTEXT_H_ + +#include + +#include + +#include "v8-data.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-maybe.h" // NOLINT(build/include_directory) +#include "v8-snapshot.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Function; +class MicrotaskQueue; +class Object; +class ObjectTemplate; +class Value; +class String; + +/** + * A container for extension names. + */ +class V8_EXPORT ExtensionConfiguration { + public: + ExtensionConfiguration() : name_count_(0), names_(nullptr) {} + ExtensionConfiguration(int name_count, const char* names[]) + : name_count_(name_count), names_(names) {} + + const char** begin() const { return &names_[0]; } + const char** end() const { return &names_[name_count_]; } + + private: + const int name_count_; + const char** names_; +}; + +/** + * A sandboxed execution context with its own set of built-in objects + * and functions. + */ +class V8_EXPORT Context : public Data { + public: + /** + * Returns the global proxy object. + * + * Global proxy object is a thin wrapper whose prototype points to actual + * context's global object with the properties like Object, etc. This is done + * that way for security reasons (for more details see + * https://wiki.mozilla.org/Gecko:SplitWindow). + * + * Please note that changes to global proxy object prototype most probably + * would break VM---v8 expects only global object as a prototype of global + * proxy object. + */ + Local Global(); + + /** + * Detaches the global object from its context before + * the global object can be reused to create a new context. + */ + void DetachGlobal(); + + /** + * Creates a new context and returns a handle to the newly allocated + * context. + * + * \param isolate The isolate in which to create the context. + * + * \param extensions An optional extension configuration containing + * the extensions to be installed in the newly created context. + * + * \param global_template An optional object template from which the + * global object for the newly created context will be created. + * + * \param global_object An optional global object to be reused for + * the newly created context. This global object must have been + * created by a previous call to Context::New with the same global + * template. The state of the global object will be completely reset + * and only object identify will remain. + * + * \param internal_fields_deserializer An optional callback used + * to deserialize fields set by + * v8::Object::SetAlignedPointerInInternalField() in wrapper objects + * from the default context snapshot. It should match the + * SerializeInternalFieldsCallback() used by + * v8::SnapshotCreator::SetDefaultContext() when the default context + * snapshot is created. It does not need to be configured if the default + * context snapshot contains no wrapper objects with pointer internal + * fields, or if no custom startup snapshot is configured + * in the v8::CreateParams used to create the isolate. + * + * \param microtask_queue An optional microtask queue used to manage + * the microtasks created in this context. If not set the per-isolate + * default microtask queue would be used. + * + * \param context_data_deserializer An optional callback used + * to deserialize embedder data set by + * v8::Context::SetAlignedPointerInEmbedderData() in the default + * context from the default context snapshot. It does not need to be + * configured if the default context snapshot contains no pointer embedder + * data, or if no custom startup snapshot is configured in the + * v8::CreateParams used to create the isolate. + * + * \param api_wrapper_deserializer An optional callback used to deserialize + * API wrapper objects that was initially set with v8::Object::Wrap() and then + * serialized using SerializeAPIWrapperCallback. + */ + static Local New( + Isolate* isolate, ExtensionConfiguration* extensions = nullptr, + MaybeLocal global_template = MaybeLocal(), + MaybeLocal global_object = MaybeLocal(), + DeserializeInternalFieldsCallback internal_fields_deserializer = + DeserializeInternalFieldsCallback(), + MicrotaskQueue* microtask_queue = nullptr, + DeserializeContextDataCallback context_data_deserializer = + DeserializeContextDataCallback(), + DeserializeAPIWrapperCallback api_wrapper_deserializer = + DeserializeAPIWrapperCallback()); + + /** + * Create a new context from a (non-default) context snapshot. There + * is no way to provide a global object template since we do not create + * a new global object from template, but we can reuse a global object. + * + * \param isolate See v8::Context::New(). + * + * \param context_snapshot_index The index of the context snapshot to + * deserialize from. Use v8::Context::New() for the default snapshot. + * + * \param internal_fields_deserializer An optional callback used + * to deserialize fields set by + * v8::Object::SetAlignedPointerInInternalField() in wrapper objects + * from the default context snapshot. It does not need to be + * configured if there are no wrapper objects with no internal + * pointer fields in the default context snapshot or if no startup + * snapshot is configured when the isolate is created. + * + * \param extensions See v8::Context::New(). + * + * \param global_object See v8::Context::New(). + * + * \param internal_fields_deserializer Similar to + * internal_fields_deserializer in v8::Context::New() but applies to + * the context specified by the context_snapshot_index. + * + * \param microtask_queue See v8::Context::New(). + * + * \param context_data_deserializer Similar to + * context_data_deserializer in v8::Context::New() but applies to + * the context specified by the context_snapshot_index. + * + *\param api_wrapper_deserializer Similar to api_wrapper_deserializer in + * v8::Context::New() but applies to the context specified by the + * context_snapshot_index. + */ + static MaybeLocal FromSnapshot( + Isolate* isolate, size_t context_snapshot_index, + DeserializeInternalFieldsCallback internal_fields_deserializer = + DeserializeInternalFieldsCallback(), + ExtensionConfiguration* extensions = nullptr, + MaybeLocal global_object = MaybeLocal(), + MicrotaskQueue* microtask_queue = nullptr, + DeserializeContextDataCallback context_data_deserializer = + DeserializeContextDataCallback(), + DeserializeAPIWrapperCallback api_wrapper_deserializer = + DeserializeAPIWrapperCallback()); + + /** + * Returns an global object that isn't backed by an actual context. + * + * The global template needs to have access checks with handlers installed. + * If an existing global object is passed in, the global object is detached + * from its context. + * + * Note that this is different from a detached context where all accesses to + * the global proxy will fail. Instead, the access check handlers are invoked. + * + * It is also not possible to detach an object returned by this method. + * Instead, the access check handlers need to return nothing to achieve the + * same effect. + * + * It is possible, however, to create a new context from the global object + * returned by this method. + */ + static MaybeLocal NewRemoteContext( + Isolate* isolate, Local global_template, + MaybeLocal global_object = MaybeLocal()); + + /** + * Sets the security token for the context. To access an object in + * another context, the security tokens must match. + */ + void SetSecurityToken(Local token); + + /** Restores the security token to the default value. */ + void UseDefaultSecurityToken(); + + /** Returns the security token of this context.*/ + Local GetSecurityToken(); + + /** + * Enter this context. After entering a context, all code compiled + * and run is compiled and run in this context. If another context + * is already entered, this old context is saved so it can be + * restored when the new context is exited. + */ + void Enter(); + + /** + * Exit this context. Exiting the current context restores the + * context that was in place when entering the current context. + */ + void Exit(); + + /** + * Delegate to help with Deep freezing embedder-specific objects (such as + * JSApiObjects) that can not be frozen natively. + */ + class DeepFreezeDelegate { + public: + /** + * Performs embedder-specific operations to freeze the provided embedder + * object. The provided object *will* be frozen by DeepFreeze after this + * function returns, so only embedder-specific objects need to be frozen. + * This function *may not* create new JS objects or perform JS allocations. + * Any v8 objects reachable from the provided embedder object that should + * also be considered for freezing should be added to the children_out + * parameter. Returns true if the operation completed successfully. + */ + virtual bool FreezeEmbedderObjectAndGetChildren( + Local obj, LocalVector& children_out) = 0; + }; + + /** + * Attempts to recursively freeze all objects reachable from this context. + * Some objects (generators, iterators, non-const closures) can not be frozen + * and will cause this method to throw an error. An optional delegate can be + * provided to help freeze embedder-specific objects. + * + * Freezing occurs in two steps: + * 1. "Marking" where we iterate through all objects reachable by this + * context, accumulating a list of objects that need to be frozen and + * looking for objects that can't be frozen. This step is separated because + * it is more efficient when we can assume there is no garbage collection. + * 2. "Freezing" where we go through the list of objects and freezing them. + * This effectively requires copying them so it may trigger garbage + * collection. + */ + Maybe DeepFreeze(DeepFreezeDelegate* delegate = nullptr); + + /** Returns the microtask queue associated with a current context. */ + MicrotaskQueue* GetMicrotaskQueue(); + + /** Sets the microtask queue associated with the current context. */ + void SetMicrotaskQueue(MicrotaskQueue* queue); + + /** + * The field at kDebugIdIndex used to be reserved for the inspector. + * It now serves no purpose. + */ + enum EmbedderDataFields { kDebugIdIndex = 0 }; + + /** + * Return the number of fields allocated for embedder data. + */ + uint32_t GetNumberOfEmbedderDataFields(); + + /** + * Gets the embedder data with the given index, which must have been set by a + * previous call to SetEmbedderData with the same index. + */ + V8_INLINE Local GetEmbedderDataV2(int index); + + /** + * Sets the embedder data with the given index, growing the data as + * needed. Note that index 0 currently has a special meaning for Chrome's + * debugger. + */ + void SetEmbedderDataV2(int index, Local value); + + /** + * Gets the embedder data with the given index, which must have been set by a + * previous call to SetEmbedderData with the same index. + */ + V8_DEPRECATE_SOON("Use GetEmbedderDataV2 instead") + V8_INLINE Local GetEmbedderData(int index); + + /** + * Gets the binding object used by V8 extras. Extra natives get a reference + * to this object and can use it to "export" functionality by adding + * properties. Extra natives can also "import" functionality by accessing + * properties added by the embedder using the V8 API. + */ + Local GetExtrasBindingObject(); + + /** + * Sets the embedder data with the given index, growing the data as + * needed. Note that index 0 currently has a special meaning for Chrome's + * debugger. + */ + V8_DEPRECATE_SOON("Use SetEmbedderDataV2 instead") + void SetEmbedderData(int index, Local value); + + /** + * Gets a 2-byte-aligned native pointer from the embedder data with the given + * index, which must have been set by a previous call to + * SetAlignedPointerInEmbedderData with the same index. Note that index 0 + * currently has a special meaning for Chrome's debugger. + */ + V8_INLINE void* GetAlignedPointerFromEmbedderData(Isolate* isolate, int index, + EmbedderDataTypeTag tag); + V8_INLINE void* GetAlignedPointerFromEmbedderData(int index, + EmbedderDataTypeTag tag); + + V8_DEPRECATED( + "Use GetAlignedPointerFromEmbedderData with EmbedderDataTypeTag " + "parameter instead.") + V8_INLINE void* GetAlignedPointerFromEmbedderData(Isolate* isolate, + int index) { + return GetAlignedPointerFromEmbedderData(isolate, index, + kEmbedderDataTypeTagDefault); + } + + V8_DEPRECATED( + "Use GetAlignedPointerFromEmbedderData with EmbedderDataTypeTag " + "parameter instead.") + V8_INLINE void* GetAlignedPointerFromEmbedderData(int index) { + return GetAlignedPointerFromEmbedderData(index, + kEmbedderDataTypeTagDefault); + } + + void SetAlignedPointerInEmbedderData(int index, void* value, + EmbedderDataTypeTag tag); + + /** + * Sets a 2-byte-aligned native pointer in the embedder data with the given + * index, growing the data as needed. Note that index 0 currently has a + * special meaning for Chrome's debugger. + */ + V8_DEPRECATED( + "Use SetAlignedPointerInEmbedderData with EmbedderDataTypeTag parameter " + "instead.") + void SetAlignedPointerInEmbedderData(int index, void* value) { + SetAlignedPointerInEmbedderData(index, value, kEmbedderDataTypeTagDefault); + } + + /** + * Control whether code generation from strings is allowed. Calling + * this method with false will disable 'eval' and the 'Function' + * constructor for code running in this context. If 'eval' or the + * 'Function' constructor are used an exception will be thrown. + * + * If code generation from strings is not allowed the + * V8::ModifyCodeGenerationFromStringsCallback callback will be invoked if + * set before blocking the call to 'eval' or the 'Function' + * constructor. If that callback returns true, the call will be + * allowed, otherwise an exception will be thrown. If no callback is + * set an exception will be thrown. + */ + void AllowCodeGenerationFromStrings(bool allow); + + /** + * Returns true if code generation from strings is allowed for the context. + * For more details see AllowCodeGenerationFromStrings(bool) documentation. + */ + bool IsCodeGenerationFromStringsAllowed() const; + + /** + * Sets the error description for the exception that is thrown when + * code generation from strings is not allowed and 'eval' or the 'Function' + * constructor are called. + */ + void SetErrorMessageForCodeGenerationFromStrings(Local message); + + /** + * Sets the error description for the exception that is thrown when + * wasm code generation is not allowed. + */ + void SetErrorMessageForWasmCodeGeneration(Local message); + + /** + * Return data that was previously attached to the context snapshot via + * SnapshotCreator, and removes the reference to it. + * Repeated call with the same index returns an empty MaybeLocal. + */ + template + V8_INLINE MaybeLocal GetDataFromSnapshotOnce(size_t index); + + /** + * If callback is set, abort any attempt to execute JavaScript in this + * context, call the specified callback, and throw an exception. + * To unset abort, pass nullptr as callback. + */ + using AbortScriptExecutionCallback = void (*)(Isolate* isolate, + Local context); + void SetAbortScriptExecution(AbortScriptExecutionCallback callback); + + /** + * Set or clear hooks to be invoked for promise lifecycle operations. + * To clear a hook, set it to an empty v8::Function. Each function will + * receive the observed promise as the first argument. If a chaining + * operation is used on a promise, the init will additionally receive + * the parent promise as the second argument. + */ + void SetPromiseHooks(Local init_hook, Local before_hook, + Local after_hook, + Local resolve_hook); + + bool HasTemplateLiteralObject(Local object); + /** + * Stack-allocated class which sets the execution context for all + * operations executed within a local scope. + */ + class V8_NODISCARD Scope { + public: + explicit V8_INLINE Scope(Local context) : context_(context) { + context_->Enter(); + } + V8_INLINE ~Scope() { context_->Exit(); } + + private: + Local context_; + }; + + /** + * Stack-allocated class to support the backup incumbent settings object + * stack. + * https://html.spec.whatwg.org/multipage/webappapis.html#backup-incumbent-settings-object-stack + */ + class V8_EXPORT V8_NODISCARD BackupIncumbentScope final { + public: + /** + * |backup_incumbent_context| is pushed onto the backup incumbent settings + * object stack. + */ + explicit BackupIncumbentScope(Local backup_incumbent_context); + ~BackupIncumbentScope(); + + private: + friend class internal::Isolate; + + uintptr_t JSStackComparableAddressPrivate() const { + return js_stack_comparable_address_; + } + + Local backup_incumbent_context_; + uintptr_t js_stack_comparable_address_ = 0; + const BackupIncumbentScope* prev_ = nullptr; + }; + + V8_INLINE static Context* Cast(Data* data); + + private: + friend class Value; + friend class Script; + friend class Object; + friend class Function; + + static void CheckCast(Data* obj); + + internal::ValueHelper::InternalRepresentationType GetDataFromSnapshotOnce( + size_t index); + Local SlowGetEmbedderData(int index); + Local SlowGetEmbedderDataV2(int index); + void* SlowGetAlignedPointerFromEmbedderData(int index, + EmbedderDataTypeTag tag); +}; + +// --- Implementation --- + +Local Context::GetEmbedderData(int index) { +#ifndef V8_ENABLE_CHECKS + using A = internal::Address; + using I = internal::Internals; + A ctx = internal::ValueHelper::ValueAsAddress(this); + A embedder_data = + I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset); + int value_offset = + I::kEmbedderDataArrayHeaderSize + (I::kEmbedderDataSlotSize * index); + A value = I::ReadRawField(embedder_data, value_offset); +#ifdef V8_COMPRESS_POINTERS + // We read the full pointer value and then decompress it in order to avoid + // dealing with potential endianness issues. + value = I::DecompressTaggedField(embedder_data, static_cast(value)); +#endif + + auto* isolate = I::GetCurrentIsolate(); + return Local::New(isolate, value); +#else + return SlowGetEmbedderData(index); +#endif +} + +V8_INLINE Local Context::GetEmbedderDataV2(int index) { +#ifndef V8_ENABLE_CHECKS + using A = internal::Address; + using I = internal::Internals; + A ctx = internal::ValueHelper::ValueAsAddress(this); + A embedder_data = + I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset); + int value_offset = + I::kEmbedderDataArrayHeaderSize + (I::kEmbedderDataSlotSize * index); + A value = I::ReadRawField(embedder_data, value_offset); +#ifdef V8_COMPRESS_POINTERS + // We read the full pointer value and then decompress it in order to avoid + // dealing with potential endianness issues. + value = I::DecompressTaggedField(embedder_data, static_cast(value)); +#endif + + auto* isolate = I::GetCurrentIsolate(); + return Local::New(isolate, value); +#else + return SlowGetEmbedderDataV2(index); +#endif +} + +void* Context::GetAlignedPointerFromEmbedderData(Isolate* isolate, int index, + EmbedderDataTypeTag tag) { +#if !defined(V8_ENABLE_CHECKS) + using A = internal::Address; + using I = internal::Internals; + A ctx = internal::ValueHelper::ValueAsAddress(this); + A embedder_data = + I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset); + int value_offset = I::kEmbedderDataArrayHeaderSize + + (I::kEmbedderDataSlotSize * index) + + I::kEmbedderDataSlotExternalPointerOffset; + return reinterpret_cast(I::ReadExternalPointerField( + isolate, embedder_data, value_offset, ToExternalPointerTag(tag))); +#else + return SlowGetAlignedPointerFromEmbedderData(index, tag); +#endif +} + +void* Context::GetAlignedPointerFromEmbedderData(int index, + EmbedderDataTypeTag tag) { +#if !defined(V8_ENABLE_CHECKS) + using A = internal::Address; + using I = internal::Internals; + A ctx = internal::ValueHelper::ValueAsAddress(this); + A embedder_data = + I::ReadTaggedPointerField(ctx, I::kNativeContextEmbedderDataOffset); + int value_offset = I::kEmbedderDataArrayHeaderSize + + (I::kEmbedderDataSlotSize * index) + + I::kEmbedderDataSlotExternalPointerOffset; + Isolate* isolate = I::GetCurrentIsolateForSandbox(); + return reinterpret_cast(I::ReadExternalPointerField( + isolate, embedder_data, value_offset, ToExternalPointerTag(tag))); +#else + return SlowGetAlignedPointerFromEmbedderData(index, tag); +#endif +} + +template +MaybeLocal Context::GetDataFromSnapshotOnce(size_t index) { + if (auto repr = GetDataFromSnapshotOnce(index); + repr != internal::ValueHelper::kEmpty) { + internal::PerformCastCheck(internal::ValueHelper::ReprAsValue(repr)); + return Local::FromRepr(repr); + } + return {}; +} + +Context* Context::Cast(v8::Data* data) { +#ifdef V8_ENABLE_CHECKS + CheckCast(data); +#endif + return static_cast(data); +} + +} // namespace v8 + +#endif // INCLUDE_V8_CONTEXT_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-cpp-heap-external.h b/packages/android-jsi/platforms/android/cpp/include/v8-cpp-heap-external.h new file mode 100644 index 000000000..92a89c455 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-cpp-heap-external.h @@ -0,0 +1,56 @@ +// Copyright 2025 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 INCLUDE_V8_HEAP_EXTERNAL_H_ +#define INCLUDE_V8_HEAP_EXTERNAL_H_ + +#include "cppgc/type-traits.h" // NOLINT(build/include_directory) +#include "v8-sandbox.h" // NOLINT(build/include_directory) +#include "v8-value.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Isolate; + +/** + * A JavaScript value that wraps a `cppgc::GarbageCollected` object allocated + * on the managed C++ heap (CppHeap). This type of value is mainly used to + * associate C++ data structures which aren't exposed to JavaScript with + * JavaScript objects. + */ +class V8_EXPORT CppHeapExternal : public Data { + public: + template + static Local New(Isolate* isolate, T* value, + CppHeapPointerTag tag) { + static_assert(cppgc::IsGarbageCollectedTypeV, + "Object must be of type GarbageCollected."); + return NewImpl(isolate, value, tag); + } + + V8_INLINE static CppHeapExternal* Cast(Data* data) { +#ifdef V8_ENABLE_CHECKS + CheckCast(data); +#endif + return static_cast(data); + } + + template + T* Value(Isolate* isolate, CppHeapPointerTagRange tag_range) const { + static_assert(cppgc::IsGarbageCollectedTypeV, + "Object must be of type GarbageCollected."); + return static_cast(ValueImpl(isolate, tag_range)); + } + + private: + static void CheckCast(v8::Data* obj); + static Local NewImpl(Isolate* isolate, void* value, + CppHeapPointerTag tag); + void* ValueImpl(Isolate*, CppHeapPointerTagRange tag_range) const; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_HEAP_EXTERNAL_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-cppgc.h b/packages/android-jsi/platforms/android/cpp/include/v8-cppgc.h new file mode 100644 index 000000000..9dc6ee5e6 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-cppgc.h @@ -0,0 +1,199 @@ +// Copyright 2020 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 INCLUDE_V8_CPPGC_H_ +#define INCLUDE_V8_CPPGC_H_ + +#include +#include +#include + +#include "cppgc/common.h" +#include "cppgc/custom-space.h" +#include "cppgc/heap-statistics.h" +#include "cppgc/visitor.h" +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8-traced-handle.h" // NOLINT(build/include_directory) + +namespace cppgc { +class AllocationHandle; +class HeapHandle; +} // namespace cppgc + +namespace v8 { + +class Object; + +namespace internal { +class CppHeap; +} // namespace internal + +class CustomSpaceStatisticsReceiver; + +struct V8_EXPORT CppHeapCreateParams { + explicit CppHeapCreateParams( + std::vector> custom_spaces) + : custom_spaces(std::move(custom_spaces)) {} + + CppHeapCreateParams(const CppHeapCreateParams&) = delete; + CppHeapCreateParams& operator=(const CppHeapCreateParams&) = delete; + + std::vector> custom_spaces; + /** + * Specifies which kind of marking are supported by the heap. The type may be + * further reduced via runtime flags when attaching the heap to an Isolate. + */ + cppgc::Heap::MarkingType marking_support = + cppgc::Heap::MarkingType::kIncrementalAndConcurrent; + /** + * Specifies which kind of sweeping is supported by the heap. The type may be + * further reduced via runtime flags when attaching the heap to an Isolate. + */ + cppgc::Heap::SweepingType sweeping_support = + cppgc::Heap::SweepingType::kIncrementalAndConcurrent; + /** + * Optional marker representing the stack start of the thread creating the + * heap. + */ + std::optional stack_start_marker = std::nullopt; +}; + +/** + * A heap for allocating managed C++ objects. + * + * Similar to v8::Isolate, the heap may only be accessed from one thread at a + * time. The heap may be used from different threads using the + * v8::Locker/v8::Unlocker APIs which is different from generic Oilpan. + */ +class V8_EXPORT CppHeap { + public: + static std::unique_ptr Create(v8::Platform* platform, + const CppHeapCreateParams& params); + + virtual ~CppHeap() = default; + + /** + * \returns the opaque handle for allocating objects using + * `MakeGarbageCollected()`. + */ + cppgc::AllocationHandle& GetAllocationHandle(); + + /** + * \returns the opaque heap handle which may be used to refer to this heap in + * other APIs. Valid as long as the underlying `CppHeap` is alive. + */ + cppgc::HeapHandle& GetHeapHandle(); + + /** + * Terminate clears all roots and performs multiple garbage collections to + * reclaim potentially newly created objects in destructors. + * + * After this call, object allocation is prohibited. + */ + V8_DEPRECATED("Terminate gets automatically called in the CppHeap destructor") + void Terminate(); + + /** + * \param detail_level specifies whether should return detailed + * statistics or only brief summary statistics. + * \returns current CppHeap statistics regarding memory consumption + * and utilization. + */ + cppgc::HeapStatistics CollectStatistics( + cppgc::HeapStatistics::DetailLevel detail_level); + + /** + * Collects statistics for the given spaces and reports them to the receiver. + * + * \param custom_spaces a collection of custom space indices. + * \param receiver an object that gets the results. + */ + void CollectCustomSpaceStatisticsAtLastGC( + std::vector custom_spaces, + std::unique_ptr receiver); + + /** + * Enables a detached mode that allows testing garbage collection using + * `cppgc::testing` APIs. Once used, the heap cannot be attached to an + * `Isolate` anymore. + */ + void EnableDetachedGarbageCollectionsForTesting(); + + /** + * Performs a stop-the-world garbage collection for testing purposes. + * + * \param stack_state The stack state to assume for the garbage collection. + */ + void CollectGarbageForTesting(cppgc::EmbedderStackState stack_state); + + /** + * Performs a stop-the-world minor garbage collection for testing purposes. + * + * \param stack_state The stack state to assume for the garbage collection. + */ + void CollectGarbageInYoungGenerationForTesting( + cppgc::EmbedderStackState stack_state); + + private: + CppHeap() = default; + + friend class internal::CppHeap; +}; + +class JSVisitor : public cppgc::Visitor { + public: + explicit JSVisitor(cppgc::Visitor::Key key) : cppgc::Visitor(key) {} + ~JSVisitor() override = default; + + void Trace(const TracedReferenceBase& ref) { + if (ref.IsEmptyThreadSafe()) return; + Visit(ref); + } + + protected: + using cppgc::Visitor::Visit; + + virtual void Visit(const TracedReferenceBase& ref) {} +}; + +/** + * Provided as input to `CppHeap::CollectCustomSpaceStatisticsAtLastGC()`. + * + * Its method is invoked with the results of the statistic collection. + */ +class CustomSpaceStatisticsReceiver { + public: + virtual ~CustomSpaceStatisticsReceiver() = default; + /** + * Reports the size of a space at the last GC. It is called for each space + * that was requested in `CollectCustomSpaceStatisticsAtLastGC()`. + * + * \param space_index The index of the space. + * \param bytes The total size of live objects in the space at the last GC. + * It is zero if there was no GC yet. + */ + virtual void AllocatedBytes(cppgc::CustomSpaceIndex space_index, + size_t bytes) = 0; +}; + +} // namespace v8 + +namespace cppgc { + +template +struct TraceTrait> { + static cppgc::TraceDescriptor GetTraceDescriptor(const void* self) { + return {nullptr, Trace}; + } + + static void Trace(Visitor* visitor, const void* self) { + static_cast(visitor)->Trace( + *static_cast*>(self)); + } +}; + +} // namespace cppgc + +#endif // INCLUDE_V8_CPPGC_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-data.h b/packages/android-jsi/platforms/android/cpp/include/v8-data.h new file mode 100644 index 000000000..7be431b31 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-data.h @@ -0,0 +1,96 @@ +// Copyright 2021 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 INCLUDE_V8_DATA_H_ +#define INCLUDE_V8_DATA_H_ + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; + +/** + * The superclass of objects that can reside on V8's heap. + */ +class V8_EXPORT Data { + public: + /** + * Returns true if this data is a |v8::Value|. + */ + bool IsValue() const; + + /** + * Returns true if this data is a |v8::Module|. + */ + bool IsModule() const; + + /** + * Returns true if this data is a |v8::ModuleRequest|. + */ + bool IsModuleRequest() const; + + /** + * Returns tru if this data is a |v8::FixedArray| + */ + bool IsFixedArray() const; + + /** + * Returns true if this data is a |v8::Private|. + */ + bool IsPrivate() const; + + /** + * Returns true if this data is a |v8::ObjectTemplate|. + */ + bool IsObjectTemplate() const; + + /** + * Returns true if this data is a |v8::FunctionTemplate|. + */ + bool IsFunctionTemplate() const; + + /** + * Returns true if this data is a |v8::DictionaryTemplate|. + */ + bool IsDictionaryTemplate() const; + + /** + * Returns true if this data is a |v8::Context|. + */ + bool IsContext() const; + + /** + * Returns true if this value is a `CppHeapExternal` object. + */ + bool IsCppHeapExternal() const; + + private: + Data() = delete; +}; + +/** + * A fixed-sized array with elements of type Data. + */ +class V8_EXPORT FixedArray : public Data { + public: + int Length() const; + + Local Get(int i) const; + + V8_INLINE static FixedArray* Cast(Data* data) { +#ifdef V8_ENABLE_CHECKS + CheckCast(data); +#endif + return reinterpret_cast(data); + } + + private: + static void CheckCast(Data* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_DATA_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-date.h b/packages/android-jsi/platforms/android/cpp/include/v8-date.h new file mode 100644 index 000000000..5c3cbd91c --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-date.h @@ -0,0 +1,57 @@ +// Copyright 2021 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 INCLUDE_V8_DATE_H_ +#define INCLUDE_V8_DATE_H_ + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; + +/** + * An instance of the built-in Date constructor (ECMA-262, 15.9). + */ +class V8_EXPORT Date : public Object { + public: + static V8_WARN_UNUSED_RESULT MaybeLocal New(Local context, + double time); + + static V8_WARN_UNUSED_RESULT MaybeLocal Parse( + Local context, + Local date_string); + + /** + * A specialization of Value::NumberValue that is more efficient + * because we know the structure of this object. + */ + double ValueOf() const; + + /** + * Generates ISO string representation. + */ + v8::Local ToISOString() const; + + /** + * Generates UTC string representation. + */ + v8::Local ToUTCString() const; + + V8_INLINE static Date* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + private: + static void CheckCast(Value* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_DATE_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-debug.h b/packages/android-jsi/platforms/android/cpp/include/v8-debug.h new file mode 100644 index 000000000..56b98a95b --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-debug.h @@ -0,0 +1,231 @@ +// Copyright 2021 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 INCLUDE_V8_DEBUG_H_ +#define INCLUDE_V8_DEBUG_H_ + +#include + +#include "v8-script.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Isolate; +class String; + +/** + * A single JavaScript stack frame. + */ +class V8_EXPORT StackFrame { + public: + /** + * Returns the source location, 0-based, for the associated function call. + */ + Location GetLocation() const; + + /** + * Returns the number, 1-based, of the line for the associate function call. + * This method will return Message::kNoLineNumberInfo if it is unable to + * retrieve the line number, or if kLineNumber was not passed as an option + * when capturing the StackTrace. + */ + int GetLineNumber() const { return GetLocation().GetLineNumber() + 1; } + + /** + * Returns the 1-based column offset on the line for the associated function + * call. + * This method will return Message::kNoColumnInfo if it is unable to retrieve + * the column number, or if kColumnOffset was not passed as an option when + * capturing the StackTrace. + */ + int GetColumn() const { return GetLocation().GetColumnNumber() + 1; } + + /** + * Returns zero based source position (character offset) for the associated + * function. + */ + int GetSourcePosition() const; + + /** + * Returns the id of the script for the function for this StackFrame. + * This method will return Message::kNoScriptIdInfo if it is unable to + * retrieve the script id, or if kScriptId was not passed as an option when + * capturing the StackTrace. + */ + int GetScriptId() const; + + /** + * Returns the name of the resource that contains the script for the + * function for this StackFrame. + */ + Local GetScriptName() const; + + /** + * Returns the name of the resource that contains the script for the + * function for this StackFrame or sourceURL value if the script name + * is undefined and its source ends with //# sourceURL=... string or + * deprecated //@ sourceURL=... string. + */ + Local GetScriptNameOrSourceURL() const; + + /** + * Returns the source of the script for the function for this StackFrame. + */ + Local GetScriptSource() const; + + /** + * Returns the source mapping URL (if one is present) of the script for + * the function for this StackFrame. + */ + Local GetScriptSourceMappingURL() const; + + /** + * Returns the name of the function associated with this stack frame. + */ + Local GetFunctionName() const; + + /** + * Returns whether or not the associated function is compiled via a call to + * eval(). + */ + bool IsEval() const; + + /** + * Returns whether or not the associated function is called as a + * constructor via "new". + */ + bool IsConstructor() const; + + /** + * Returns whether or not the associated functions is defined in wasm. + */ + bool IsWasm() const; + + /** + * Returns whether or not the associated function is defined by the user. + */ + bool IsUserJavaScript() const; +}; + +/** + * Representation of a JavaScript stack trace. The information collected is a + * snapshot of the execution stack and the information remains valid after + * execution continues. + */ +class V8_EXPORT StackTrace { + public: + /** + * Flags that determine what information is placed captured for each + * StackFrame when grabbing the current stack trace. + * Note: these options are deprecated and we always collect all available + * information (kDetailed). + */ + enum StackTraceOptions { + kLineNumber = 1, + kColumnOffset = 1 << 1 | kLineNumber, + kScriptName = 1 << 2, + kFunctionName = 1 << 3, + kIsEval = 1 << 4, + kIsConstructor = 1 << 5, + kScriptNameOrSourceURL = 1 << 6, + kScriptId = 1 << 7, + kExposeFramesAcrossSecurityOrigins = 1 << 8, + kOverview = kLineNumber | kColumnOffset | kScriptName | kFunctionName, + kDetailed = kOverview | kIsEval | kIsConstructor | kScriptNameOrSourceURL + }; + + struct ScriptIdAndContext { + int id; + v8::Local context; + }; + + struct ScriptData { + int id; + v8::Local function; + v8::Local context; + }; + + /** + * Returns the (unique) ID of this stack trace. + */ + int GetID() const; + + /** + * Returns a StackFrame at a particular index. + */ + Local GetFrame(Isolate* isolate, uint32_t index) const; + + /** + * Returns the number of StackFrames. + */ + int GetFrameCount() const; + + /** + * Grab a snapshot of the current JavaScript execution stack. + * + * \param frame_limit The maximum number of stack frames we want to capture. + * \param options Enumerates the set of things we will capture for each + * StackFrame. + */ + static Local CurrentStackTrace( + Isolate* isolate, int frame_limit, StackTraceOptions options = kDetailed); + + /** + * Returns the first valid script name or source URL starting at the top of + * the JS stack. The returned string is either an empty handle if no script + * name/url was found or a non-zero-length string. + * + * This method is equivalent to calling StackTrace::CurrentStackTrace and + * walking the resulting frames from the beginning until a non-empty script + * name/url is found. The difference is that this method won't allocate + * a stack trace. + */ + static Local CurrentScriptNameOrSourceURL(Isolate* isolate); + + /** + * Returns the first valid script id at the top of the JS stack. The returned + * value is Message::kNoScriptIdInfo if no id was found. + * + * This method is equivalent to calling StackTrace::CurrentStackTrace and + * walking the resulting frames from the beginning until a non-empty id is + * found. The difference is that this method won't allocate a stack trace. + */ + static int CurrentScriptId(Isolate* isolate); + + /** + * Writes up to the first `frame_data.size()` valid script ids and function + * contexts at the top of the JS stack into the given span. Returns a span + * sized to the number of frames worth of data written. It's similar to the + * CurrentStackTrace method but doesn't allocate a stack trace. Further, it + * skips frames that don't have valid script ids or function contexts. The + * final difference is that the script id written for evals or regexp is that + * of the script that ran eval() or regexp, not the current context. + * + */ + V8_DEPRECATE_SOON("Use CurrentScriptData instead") + static v8::MemorySpan + CurrentScriptIdsAndContexts(Isolate* isolate, + v8::MemorySpan frame_data); + + /** + * Writes up to the first `frame_data.size()` valid script ids, functions, and + * contexts at the top of the JS stack into the given span. Returns a span + * sized to the number of frames worth of data written. It's similar to the + * CurrentStackTrace method but doesn't allocate a stack trace. Further, it + * skips non-js frames and frames that don't have valid script ids or function + * contexts. The final difference is that the script id written for evals or + * regexp is that of the script that ran eval() or regexp, not the current + * context. + * + * WARNING: This is an unfinished experimental feature. Semantics and + * implementation may change frequently. + */ + static v8::MemorySpan CurrentScriptData( + Isolate* isolate, v8::MemorySpan frame_data); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_DEBUG_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-embedder-heap.h b/packages/android-jsi/platforms/android/cpp/include/v8-embedder-heap.h new file mode 100644 index 000000000..929b3c396 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-embedder-heap.h @@ -0,0 +1,54 @@ +// Copyright 2021 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 INCLUDE_V8_EMBEDDER_HEAP_H_ +#define INCLUDE_V8_EMBEDDER_HEAP_H_ + +#include "v8-traced-handle.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { +namespace internal { +class TracedHandles; +} // namespace internal + +class Isolate; +class Value; + +/** + * Handler for embedder roots on non-unified heap garbage collections. + */ +class V8_EXPORT EmbedderRootsHandler { + public: + virtual ~EmbedderRootsHandler() = default; + + EmbedderRootsHandler() = default; + + /** + * Used in combination with |IsRoot|. Called by V8 when an + * object that is backed by a handle is reclaimed by a non-tracing garbage + * collection. It is up to the embedder to reset the original handle. + * + * Note that the |handle| is different from the handle that the embedder holds + * for retaining the object. It is up to the embedder to find the original + * handle via the object or class id. + */ + virtual void ResetRoot(const v8::TracedReference& handle) = 0; + + /** + * Similar to |ResetRoot()|, but opportunistic. The function is called in + * parallel for different handles and as such must be thread-safe. In case, + * |false| is returned, |ResetRoot()| will be recalled for the same handle. + */ + virtual bool TryResetRoot(const v8::TracedReference& handle) { + return false; + } + + private: + friend class internal::TracedHandles; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_EMBEDDER_HEAP_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-embedder-state-scope.h b/packages/android-jsi/platforms/android/cpp/include/v8-embedder-state-scope.h new file mode 100644 index 000000000..ec8da4573 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-embedder-state-scope.h @@ -0,0 +1,52 @@ +// Copyright 2021 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 INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_ +#define INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_ + +#include + +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; + +namespace internal { +class EmbedderState; +} // namespace internal + +// A StateTag represents a possible state of the embedder. +enum class EmbedderStateTag : uint8_t { + // reserved + EMPTY = 0, + OTHER = 1, + // embedder can define any state after +}; + +// A stack-allocated class that manages an embedder state on the isolate. +// After an EmbedderState scope has been created, a new embedder state will be +// pushed on the isolate stack. +class V8_EXPORT EmbedderStateScope { + public: + EmbedderStateScope(Isolate* isolate, Local context, + EmbedderStateTag tag); + + ~EmbedderStateScope(); + + private: + // Declaring operator new and delete as deleted is not spec compliant. + // Therefore declare them private instead to disable dynamic alloc + void* operator new(size_t size); + void* operator new[](size_t size); + void operator delete(void*, size_t); + void operator delete[](void*, size_t); + + std::unique_ptr embedder_state_; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_EMBEDDER_STATE_SCOPE_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-exception.h b/packages/android-jsi/platforms/android/cpp/include/v8-exception.h new file mode 100644 index 000000000..f240d9a60 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-exception.h @@ -0,0 +1,298 @@ +// Copyright 2021 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 INCLUDE_V8_EXCEPTION_H_ +#define INCLUDE_V8_EXCEPTION_H_ + +#include + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; +class Isolate; +class Message; +class StackTrace; +class String; +class Value; + +namespace internal { +class Isolate; +class ThreadLocalTop; +} // namespace internal + +/** + * Create new error objects by calling the corresponding error object + * constructor with the message. + */ +class V8_EXPORT Exception { + public: + static Local RangeError(Local message, + Local options = {}); + static Local ReferenceError(Local message, + Local options = {}); + static Local SyntaxError(Local message, + Local options = {}); + static Local TypeError(Local message, + Local options = {}); + static Local WasmCompileError(Local message, + Local options = {}); + static Local WasmLinkError(Local message, + Local options = {}); + static Local WasmRuntimeError(Local message, + Local options = {}); + static Local WasmSuspendError(Local message, + Local options = {}); + static Local Error(Local message, Local options = {}); + + /** + * Creates an error message for the given exception. + * Will try to reconstruct the original stack trace from the exception value, + * or capture the current stack trace if not available. + */ + static Local CreateMessage(Isolate* isolate, Local exception); + + /** + * Returns the original stack trace that was captured at the creation time + * of a given exception, or an empty handle if not available. + */ + static Local GetStackTrace(Local exception); + + /** + * Captures the current stack trace and attaches it to the given object in the + * form of `stack` property. + */ + static Maybe CaptureStackTrace(Local context, + Local object); +}; + +/** + * This is a part of experimental Api and might be changed without further + * notice. + * Do not use it. + */ +enum class ExceptionContext : uint32_t { + kUnknown, + kConstructor, + kOperation, + kAttributeGet, + kAttributeSet, + kIndexedQuery, + kIndexedGetter, + kIndexedDescriptor, + kIndexedSetter, + kIndexedDefiner, + kIndexedDeleter, + kNamedQuery, + kNamedGetter, + kNamedDescriptor, + kNamedSetter, + kNamedDefiner, + kNamedDeleter, + kNamedEnumerator +}; + +/** + * This is a part of experimental Api and might be changed without further + * notice. + * Do not use it. + */ +class ExceptionPropagationMessage { + public: + ExceptionPropagationMessage(v8::Isolate* isolate, Local exception, + Local interface_name, + Local property_name, + ExceptionContext exception_context) + : isolate_(isolate), + exception_(exception), + interface_name_(interface_name), + property_name_(property_name), + exception_context_(exception_context) {} + + V8_INLINE Isolate* GetIsolate() const { return isolate_; } + V8_INLINE Local GetException() const { return exception_; } + V8_INLINE Local GetInterfaceName() const { return interface_name_; } + V8_INLINE Local GetPropertyName() const { return property_name_; } + V8_INLINE ExceptionContext GetExceptionContext() const { + return exception_context_; + } + + private: + Isolate* isolate_; + Local exception_; + Local interface_name_; + Local property_name_; + ExceptionContext exception_context_; +}; + +using ExceptionPropagationCallback = + void (*)(ExceptionPropagationMessage message); + +/** + * An external exception handler. + */ +class V8_EXPORT TryCatch { + public: + /** + * Creates a new try/catch block and registers it with v8. Note that + * all TryCatch blocks should be stack allocated because the memory + * location itself is compared against JavaScript try/catch blocks. + */ + explicit TryCatch(Isolate* isolate); + + /** + * Unregisters and deletes this try/catch block. + */ + ~TryCatch(); + + /** + * Returns true if an exception has been caught by this try/catch block. + */ + bool HasCaught() const; + + /** + * For certain types of exceptions, it makes no sense to continue execution. + * + * If CanContinue returns false, the correct action is to perform any C++ + * cleanup needed and then return. If CanContinue returns false and + * HasTerminated returns true, it is possible to call + * CancelTerminateExecution in order to continue calling into the engine. + */ + bool CanContinue() const; + + /** + * Returns true if an exception has been caught due to script execution + * being terminated. + * + * There is no JavaScript representation of an execution termination + * exception. Such exceptions are thrown when the TerminateExecution + * methods are called to terminate a long-running script. + * + * If such an exception has been thrown, HasTerminated will return true, + * indicating that it is possible to call CancelTerminateExecution in order + * to continue calling into the engine. + */ + bool HasTerminated() const; + + /** + * Throws the exception caught by this TryCatch in a way that avoids + * it being caught again by this same TryCatch. As with ThrowException + * it is illegal to execute any JavaScript operations after calling + * ReThrow; the caller must return immediately to where the exception + * is caught. + */ + Local ReThrow(); + + /** + * Returns the exception caught by this try/catch block. If no exception has + * been caught an empty handle is returned. + */ + Local Exception() const; + + /** + * Returns the .stack property of an object. If no .stack + * property is present an empty handle is returned. + */ + V8_WARN_UNUSED_RESULT static MaybeLocal StackTrace( + Local context, Local exception); + + /** + * Returns the .stack property of the thrown object. If no .stack property is + * present or if this try/catch block has not caught an exception, an empty + * handle is returned. + */ + V8_WARN_UNUSED_RESULT MaybeLocal StackTrace( + Local context) const; + + /** + * Returns the message associated with this exception. If there is + * no message associated an empty handle is returned. + */ + Local Message() const; + + /** + * Clears any exceptions that may have been caught by this try/catch block. + * After this method has been called, HasCaught() will return false. Cancels + * the scheduled exception if it is caught and ReThrow() is not called before. + * + * It is not necessary to clear a try/catch block before using it again; if + * another exception is thrown the previously caught exception will just be + * overwritten. However, it is often a good idea since it makes it easier + * to determine which operation threw a given exception. + */ + void Reset(); + + /** + * Set verbosity of the external exception handler. + * + * By default, exceptions that are caught by an external exception + * handler are not reported. Call SetVerbose with true on an + * external exception handler to have exceptions caught by the + * handler reported as if they were not caught. + */ + void SetVerbose(bool value); + + /** + * Returns true if verbosity is enabled. + */ + bool IsVerbose() const; + + /** + * Set whether or not this TryCatch should capture a Message object + * which holds source information about where the exception + * occurred. True by default. + */ + void SetCaptureMessage(bool value); + + TryCatch(const TryCatch&) = delete; + void operator=(const TryCatch&) = delete; + + private: + // Declaring operator new and delete as deleted is not spec compliant. + // Therefore declare them private instead to disable dynamic alloc + void* operator new(size_t size); + void* operator new[](size_t size); + void operator delete(void*, size_t); + void operator delete[](void*, size_t); + + /** + * There are cases when the raw address of C++ TryCatch object cannot be + * used for comparisons with addresses into the JS stack. The cases are: + * 1) ARM, ARM64 and MIPS simulators which have separate JS stack. + * 2) Address sanitizer allocates local C++ object in the heap when + * UseAfterReturn mode is enabled. + * This method returns address that can be used for comparisons with + * addresses into the JS stack. When neither simulator nor ASAN's + * UseAfterReturn is enabled, then the address returned will be the address + * of the C++ try catch handler itself. + */ + internal::Address JSStackComparableAddressPrivate() { + return js_stack_comparable_address_; + } + + void ResetInternal(); + + // Helper methods for internal::Isolate. + bool capture_message() const; + void set_can_continue(bool value); + bool rethrow() const; + void set_rethrow(bool value); + + internal::Isolate* i_isolate_; + TryCatch* next_; + void* exception_; + void* message_obj_; + internal::Address js_stack_comparable_address_; + uint8_t flags_; + + friend class internal::Isolate; + friend class internal::ThreadLocalTop; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_EXCEPTION_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-extension.h b/packages/android-jsi/platforms/android/cpp/include/v8-extension.h new file mode 100644 index 000000000..d0056cb28 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-extension.h @@ -0,0 +1,60 @@ +// Copyright 2021 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 INCLUDE_V8_EXTENSION_H_ +#define INCLUDE_V8_EXTENSION_H_ + +#include + +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-primitive.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class FunctionTemplate; + +// --- Extensions --- + +/** + * Ignore + */ +class V8_EXPORT Extension { + public: + // Note that the strings passed into this constructor must live as long + // as the Extension itself. + Extension(const char* name, const char* source = nullptr, int dep_count = 0, + const char** deps = nullptr, int source_length = -1); + virtual ~Extension() { delete source_; } + virtual Local GetNativeFunctionTemplate( + Isolate* isolate, Local name) { + return Local(); + } + + const char* name() const { return name_; } + const String::ExternalOneByteStringResource* source() const { + return source_; + } + int dependency_count() const { return dep_count_; } + const char** dependencies() const { return deps_; } + void set_auto_enable(bool value) { auto_enable_ = value; } + bool auto_enable() { return auto_enable_; } + + // Disallow copying and assigning. + Extension(const Extension&) = delete; + void operator=(const Extension&) = delete; + + private: + const char* name_; + String::ExternalOneByteStringResource* source_; + int dep_count_; + const char** deps_; + bool auto_enable_; +}; + +void V8_EXPORT RegisterExtension(std::unique_ptr); + +} // namespace v8 + +#endif // INCLUDE_V8_EXTENSION_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-external-memory-accounter.h b/packages/android-jsi/platforms/android/cpp/include/v8-external-memory-accounter.h new file mode 100644 index 000000000..70df8f4a8 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-external-memory-accounter.h @@ -0,0 +1,60 @@ +// Copyright 2024 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 INCLUDE_EXTERNAL_MEMORY_ACCOUNTER_H_ +#define INCLUDE_EXTERNAL_MEMORY_ACCOUNTER_H_ + +#include + +#include "v8-isolate.h" + +namespace v8 { + +/** + * This class is used to give V8 an indication of the amount of externally + * allocated memory that is kept alive by JavaScript objects. V8 uses this to + * decide when to perform garbage collections. Registering externally allocated + * memory will trigger garbage collections more often than it would otherwise in + * an attempt to garbage collect the JavaScript objects that keep the externally + * allocated memory alive. Instances of ExternalMemoryAccounter check that the + * reported external memory is back to 0 on destruction. + */ +class V8_EXPORT ExternalMemoryAccounter { + public: + /** + * Returns the amount of external memory registered for `isolate`. + */ + static int64_t GetTotalAmountOfExternalAllocatedMemoryForTesting( + const Isolate* isolate); + + ExternalMemoryAccounter() = default; + ~ExternalMemoryAccounter(); + ExternalMemoryAccounter(ExternalMemoryAccounter&&); + ExternalMemoryAccounter& operator=(ExternalMemoryAccounter&&); + ExternalMemoryAccounter(const ExternalMemoryAccounter&) = delete; + ExternalMemoryAccounter& operator=(const ExternalMemoryAccounter&) = delete; + + /** + * Reports an increase of `size` bytes of external memory. + */ + void Increase(Isolate* isolate, size_t size); + /** + * Reports an update of `delta` bytes of external memory. + */ + void Update(Isolate* isolate, int64_t delta); + /** + * Reports an decrease of `size` bytes of external memory. + */ + void Decrease(Isolate* isolate, size_t size); + + private: +#ifdef V8_ENABLE_MEMORY_ACCOUNTING_CHECKS + size_t amount_of_external_memory_ = 0; + v8::Isolate* isolate_ = nullptr; +#endif +}; + +} // namespace v8 + +#endif // INCLUDE_EXTERNAL_MEMORY_ACCOUNTER_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-external.h b/packages/android-jsi/platforms/android/cpp/include/v8-external.h new file mode 100644 index 000000000..acc3705ce --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-external.h @@ -0,0 +1,66 @@ +// Copyright 2021 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 INCLUDE_V8_EXTERNAL_H_ +#define INCLUDE_V8_EXTERNAL_H_ + +#include "v8-value.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Isolate; + +/** + * A tag for external pointers. Objects with different C++ types should use + * different values of ExternalPointerTypeTag when using v8::External. The + * allowed range is 0..V8_EXTERNAL_POINTER_TAG_COUNT - 1. If this is not + * sufficient, V8_EXTERNAL_POINTER_TAG_COUNT can be increased. + */ +using ExternalPointerTypeTag = uint16_t; + +constexpr ExternalPointerTypeTag kExternalPointerTypeTagDefault = 0; + +/** + * A JavaScript value that wraps a C++ void*. This type of value is mainly used + * to associate C++ data structures with JavaScript objects. + */ +class V8_EXPORT External : public Value { + public: + /** + * Creates a new External object. + * + * \param isolate The isolate for the external object. + * \param value The C++ pointer value. + * \param tag The type tag of the external pointer. If type tags are not used + * in the embedder, the default value `kExternalPointerTypeTagDefault` can be + * used. + * \return The new External object. + */ + static Local New(Isolate* isolate, void* value, + ExternalPointerTypeTag tag); + V8_INLINE static External* Cast(Data* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + /** + * Returns the value of the external pointer. + * + * \param tag The type tag of the external pointer. If type tags are not used + * in the embedder, the default value `kExternalPointerTypeTagDefault` can be + * used. + * \return The value of the external pointer. + */ + void* Value(ExternalPointerTypeTag tag) const; + + private: + static void CheckCast(v8::Data* obj); +}; + +} // namespace v8 + +#endif // INCLUDE_V8_EXTERNAL_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-fast-api-calls.h b/packages/android-jsi/platforms/android/cpp/include/v8-fast-api-calls.h new file mode 100644 index 000000000..58ec34fa0 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-fast-api-calls.h @@ -0,0 +1,784 @@ +// Copyright 2020 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 INCLUDE_V8_FAST_API_CALLS_H_ +#define INCLUDE_V8_FAST_API_CALLS_H_ + +/** + * This file provides additional API on top of the default one for making + * API calls, which come from embedder C++ functions. The functions are being + * called directly from optimized code, doing all the necessary typechecks + * in the compiler itself, instead of on the embedder side. Hence the "fast" + * in the name. Example usage might look like: + * + * \code + * void FastMethod(int param, bool another_param); + * + * v8::FunctionTemplate::New(isolate, SlowCallback, data, + * signature, length, constructor_behavior + * side_effect_type, + * &v8::CFunction::Make(FastMethod)); + * \endcode + * + * By design, fast calls are limited by the following requirements, which + * the embedder should enforce themselves: + * - they should not allocate on the JS heap; + * - they should not trigger JS execution. + * To enforce them, the embedder could use the existing + * v8::Isolate::DisallowJavascriptExecutionScope and a utility similar to + * Blink's NoAllocationScope: + * https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/heap/thread_state_scopes.h;l=16 + * + * Due to these limitations, it's not directly possible to report errors by + * throwing a JS exception or to otherwise do an allocation. There is an + * alternative way of creating fast calls that supports falling back to the + * slow call and then performing the necessary allocation. When one creates + * the fast method by using CFunction::MakeWithFallbackSupport instead of + * CFunction::Make, the fast callback gets as last parameter an output variable, + * through which it can request falling back to the slow call. So one might + * declare their method like: + * + * \code + * void FastMethodWithFallback(int param, FastApiCallbackOptions& options); + * \endcode + * + * If the callback wants to signal an error condition or to perform an + * allocation, it must set options.fallback to true and do an early return from + * the fast method. Then V8 checks the value of options.fallback and if it's + * true, falls back to executing the SlowCallback, which is capable of reporting + * the error (either by throwing a JS exception or logging to the console) or + * doing the allocation. It's the embedder's responsibility to ensure that the + * fast callback is idempotent up to the point where error and fallback + * conditions are checked, because otherwise executing the slow callback might + * produce visible side-effects twice. + * + * An example for custom embedder type support might employ a way to wrap/ + * unwrap various C++ types in JSObject instances, e.g: + * + * \code + * + * // Helper method with a check for field count. + * template + * inline T* GetInternalField(v8::Local wrapper) { + * assert(offset < wrapper->InternalFieldCount()); + * return reinterpret_cast( + * wrapper->GetAlignedPointerFromInternalField(offset)); + * } + * + * class CustomEmbedderType { + * public: + * // Returns the raw C object from a wrapper JS object. + * static CustomEmbedderType* Unwrap(v8::Local wrapper) { + * return GetInternalField(wrapper); + * } + * static void FastMethod(v8::Local receiver_obj, int param) { + * CustomEmbedderType* receiver = static_cast( + * receiver_obj->GetAlignedPointerFromInternalField( + * kV8EmbedderWrapperObjectIndex)); + * + * // Type checks are already done by the optimized code. + * // Then call some performance-critical method like: + * // receiver->Method(param); + * } + * + * static void SlowMethod( + * const v8::FunctionCallbackInfo& info) { + * v8::Local instance = + * v8::Local::Cast(info.Holder()); + * CustomEmbedderType* receiver = Unwrap(instance); + * // TODO: Do type checks and extract {param}. + * receiver->Method(param); + * } + * }; + * + * // The following setup function can be templatized based on + * // the {embedder_object} argument. + * void SetupCustomEmbedderObject(v8::Isolate* isolate, + * v8::Local context, + * CustomEmbedderType* embedder_object) { + * v8::CFunction c_func = + * MakeV8CFunction(CustomEmbedderType::FastMethod); + * + * Local method_template = + * v8::FunctionTemplate::New( + * isolate, CustomEmbedderType::SlowMethod, v8::Local(), + * v8::Local(), 1, v8::ConstructorBehavior::kAllow, + * v8::SideEffectType::kHasSideEffect, &c_func); + * + * v8::Local object_template = + * v8::ObjectTemplate::New(isolate); + * object_template->SetInternalFieldCount( + * kV8EmbedderWrapperObjectIndex + 1); + * object_template->Set(isolate, "method", method_template); + * + * // Instantiate the wrapper JS object. + * v8::Local object = + * object_template->NewInstance(context).ToLocalChecked(); + * object->SetAlignedPointerInInternalField( + * kV8EmbedderWrapperObjectIndex, + * reinterpret_cast(embedder_object)); + * + * // TODO: Expose {object} where it's necessary. + * } + * \endcode + * + * For instance if {object} is exposed via a global "obj" variable, + * one could write in JS: + * function hot_func() { + * obj.method(42); + * } + * and once {hot_func} gets optimized, CustomEmbedderType::FastMethod + * will be called instead of the slow version, with the following arguments: + * receiver := the {embedder_object} from above + * param := 42 + * + * Currently supported return types: + * - void + * - bool + * - int32_t + * - uint32_t + * - float32_t + * - float64_t + * Currently supported argument types: + * - pointer to an embedder type + * - JavaScript array of primitive types + * - bool + * - int32_t + * - uint32_t + * - int64_t + * - uint64_t + * - float32_t + * - float64_t + * + * The 64-bit integer types currently have the IDL (unsigned) long long + * semantics: https://heycam.github.io/webidl/#abstract-opdef-converttoint + * In the future we'll extend the API to also provide conversions from/to + * BigInt to preserve full precision. + * The floating point types currently have the IDL (unrestricted) semantics, + * which is the only one used by WebGL. We plan to add support also for + * restricted floats/doubles, similarly to the BigInt conversion policies. + * We also differ from the specific NaN bit pattern that WebIDL prescribes + * (https://heycam.github.io/webidl/#es-unrestricted-float) in that Blink + * passes NaN values as-is, i.e. doesn't normalize them. + * + * To be supported types: + * - TypedArrays and ArrayBuffers + * - arrays of embedder types + * + * + * The API offers a limited support for function overloads: + * + * \code + * void FastMethod_2Args(int param, bool another_param); + * void FastMethod_3Args(int param, bool another_param, int third_param); + * + * v8::CFunction fast_method_2args_c_func = + * MakeV8CFunction(FastMethod_2Args); + * v8::CFunction fast_method_3args_c_func = + * MakeV8CFunction(FastMethod_3Args); + * const v8::CFunction fast_method_overloads[] = {fast_method_2args_c_func, + * fast_method_3args_c_func}; + * Local method_template = + * v8::FunctionTemplate::NewWithCFunctionOverloads( + * isolate, SlowCallback, data, signature, length, + * constructor_behavior, side_effect_type, + * {fast_method_overloads, 2}); + * \endcode + * + * In this example a single FunctionTemplate is associated to multiple C++ + * functions. The overload resolution is currently only based on the number of + * arguments passed in a call. For example, if this method_template is + * registered with a wrapper JS object as described above, a call with two + * arguments: + * obj.method(42, true); + * will result in a fast call to FastMethod_2Args, while a call with three or + * more arguments: + * obj.method(42, true, 11); + * will result in a fast call to FastMethod_3Args. Instead a call with less than + * two arguments, like: + * obj.method(42); + * would not result in a fast call but would fall back to executing the + * associated SlowCallback. + */ + +#include +#include + +#include +#include + +#include "v8-external.h" // NOLINT(build/include_directory) +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-typed-array.h" // NOLINT(build/include_directory) +#include "v8-value.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Isolate; + +class CTypeInfo { + public: + enum class Type : uint8_t { + kVoid, + kBool, + kUint8, + kInt32, + kUint32, + kInt64, + kUint64, + kFloat32, + kFloat64, + kPointer, + kV8Value, + kSeqOneByteString, + kApiObject, // This will be deprecated once all users have + // migrated from v8::ApiObject to v8::Local. + kAny, // This is added to enable untyped representation of fast + // call arguments for test purposes. It can represent any of + // the other types stored in the same memory as a union + // (see AnyCType declared below). This allows for + // uniform passing of arguments w.r.t. their location + // (in a register or on the stack), independent of their + // actual type. It's currently used by the arm64 simulator + // and can be added to the other simulators as well when fast + // calls having both GP and FP params need to be supported. + }; + + // kCallbackOptionsType is not part of the Type enum + // because it is only used internally. Use value 255 that is larger + // than any valid Type enum. + static constexpr Type kCallbackOptionsType = Type(255); + + enum class Flags : uint8_t { + kNone = 0, + kAllowSharedBit = 1 << 0, // Must be an ArrayBuffer or TypedArray + kEnforceRangeBit = 1 << 1, // T must be integral + kClampBit = 1 << 2, // T must be integral + kIsRestrictedBit = 1 << 3, // T must be float or double + }; + + explicit constexpr CTypeInfo(Type type, Flags flags = Flags::kNone) + : type_(type), flags_(flags) {} + + typedef uint32_t Identifier; + explicit constexpr CTypeInfo(Identifier identifier) + : type_(static_cast((identifier >> 8) & 255)), + flags_(static_cast(identifier & 255)) {} + constexpr Identifier GetId() const { + return static_cast(type_) << 8 | + static_cast(flags_); + } + + constexpr Type GetType() const { return type_; } + constexpr Flags GetFlags() const { return flags_; } + + static constexpr bool IsIntegralType(Type type) { + return type == Type::kUint8 || type == Type::kInt32 || + type == Type::kUint32 || type == Type::kInt64 || + type == Type::kUint64; + } + + static constexpr bool IsFloatingPointType(Type type) { + return type == Type::kFloat32 || type == Type::kFloat64; + } + + static constexpr bool IsPrimitive(Type type) { + return IsIntegralType(type) || IsFloatingPointType(type) || + type == Type::kBool; + } + + private: + Type type_; + Flags flags_; +}; + +struct FastOneByteString { + const char* data; + uint32_t length; +}; + +class V8_EXPORT CFunctionInfo { + public: + enum class Int64Representation : uint8_t { + kNumber = 0, // Use numbers to represent 64 bit integers. + kBigInt = 1, // Use BigInts to represent 64 bit integers. + }; + + // Construct a struct to hold a CFunction's type information. + // |return_info| describes the function's return type. + // |arg_info| is an array of |arg_count| CTypeInfos describing the + // arguments. Only the last argument may be of the special type + // CTypeInfo::kCallbackOptionsType. + CFunctionInfo(const CTypeInfo& return_info, unsigned int arg_count, + const CTypeInfo* arg_info, + Int64Representation repr = Int64Representation::kNumber); + + const CTypeInfo& ReturnInfo() const { return return_info_; } + + // The argument count, not including the v8::FastApiCallbackOptions + // if present. + unsigned int ArgumentCount() const { + return HasOptions() ? arg_count_ - 1 : arg_count_; + } + + Int64Representation GetInt64Representation() const { return repr_; } + + // |index| must be less than ArgumentCount(). + // Note: if the last argument passed on construction of CFunctionInfo + // has type CTypeInfo::kCallbackOptionsType, it is not included in + // ArgumentCount(). + const CTypeInfo& ArgumentInfo(unsigned int index) const; + + bool HasOptions() const { + // The options arg is always the last one. + return arg_count_ > 0 && arg_info_[arg_count_ - 1].GetType() == + CTypeInfo::kCallbackOptionsType; + } + + private: + const CTypeInfo return_info_; + const Int64Representation repr_; + const unsigned int arg_count_; + const CTypeInfo* arg_info_; +}; + +struct FastApiCallbackOptions; + +// Provided for testing. +union V8_TRIVIAL_ABI AnyCType { + AnyCType() : int64_value(0) {} + +#if defined(V8_ENABLE_LOCAL_OFF_STACK_CHECK) && V8_HAS_ATTRIBUTE_TRIVIAL_ABI + // In this case, Local is not trivially copyable and the implicit + // copy constructor and copy assignment for the union are deleted. + AnyCType(const AnyCType& other) : int64_value(other.int64_value) {} + AnyCType& operator=(const AnyCType& other) { + int64_value = other.int64_value; + return *this; + } +#endif + + bool bool_value; + int32_t int32_value; + uint32_t uint32_value; + int64_t int64_value; + uint64_t uint64_value; + float float_value; + double double_value; + void* pointer_value; + Local object_value; + Local sequence_value; + const FastOneByteString* string_value; + FastApiCallbackOptions* options_value; +}; + +static_assert( + sizeof(AnyCType) == 8, + "The union AnyCType should have size == 64 bits, as this is assumed " + "by EffectControlLinearizer."); + +class V8_EXPORT CFunction { + public: + constexpr CFunction() : address_(nullptr), type_info_(nullptr) {} + + const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); } + + const CTypeInfo& ArgumentInfo(unsigned int index) const { + return type_info_->ArgumentInfo(index); + } + + unsigned int ArgumentCount() const { return type_info_->ArgumentCount(); } + + const void* GetAddress() const { return address_; } + CFunctionInfo::Int64Representation GetInt64Representation() const { + return type_info_->GetInt64Representation(); + } + const CFunctionInfo* GetTypeInfo() const { return type_info_; } + + enum class OverloadResolution { kImpossible, kAtRuntime, kAtCompileTime }; + + template + static CFunction Make(F* func, + CFunctionInfo::Int64Representation int64_rep = + CFunctionInfo::Int64Representation::kNumber) { + CFunction result = ArgUnwrap::Make(func, int64_rep); + result.GetInt64Representation(); + return result; + } + + // Provided for testing purposes. + template + static CFunction Make(R (*func)(Args...), + R_Patch (*patching_func)(Args_Patch...), + CFunctionInfo::Int64Representation int64_rep = + CFunctionInfo::Int64Representation::kNumber) { + CFunction c_func = ArgUnwrap::Make(func, int64_rep); + static_assert( + sizeof...(Args_Patch) == sizeof...(Args), + "The patching function must have the same number of arguments."); + c_func.address_ = reinterpret_cast(patching_func); + return c_func; + } + + CFunction(const void* address, const CFunctionInfo* type_info); + + private: + const void* address_; + const CFunctionInfo* type_info_; + + template + class ArgUnwrap { + static_assert(sizeof(F) != sizeof(F), + "CFunction must be created from a function pointer."); + }; + + template + class ArgUnwrap { + public: + static CFunction Make(R (*func)(Args...), + CFunctionInfo::Int64Representation int64_rep = + CFunctionInfo::Int64Representation::kNumber); + }; +}; + +/** + * A struct which may be passed to a fast call callback, like so: + * \code + * void FastMethodWithOptions(int param, FastApiCallbackOptions& options); + * \endcode + */ +struct FastApiCallbackOptions { + /** + * Creates a new instance of FastApiCallbackOptions for testing purpose. The + * returned instance may be filled with mock data. + */ + static FastApiCallbackOptions CreateForTesting(Isolate* isolate) { + return {}; + } + + v8::Isolate* isolate = nullptr; + + /** + * The `data` passed to the FunctionTemplate constructor, or `undefined`. + */ + v8::Local data; +}; + +namespace internal { + +// Helper to count the number of occurances of `T` in `List` +template +struct count : std::integral_constant {}; +template +struct count + : std::integral_constant::value> {}; +template +struct count : count {}; + +template +class CFunctionInfoImpl : public CFunctionInfo { + static constexpr int kOptionsArgCount = + count(); + static constexpr int kReceiverCount = 1; + + static_assert(kOptionsArgCount == 0 || kOptionsArgCount == 1, + "Only one options parameter is supported."); + + static_assert(sizeof...(ArgBuilders) >= kOptionsArgCount + kReceiverCount, + "The receiver or the options argument is missing."); + + public: + constexpr CFunctionInfoImpl() + : CFunctionInfo(RetBuilder::Build(), sizeof...(ArgBuilders), + arg_info_storage_, Representation), + arg_info_storage_{ArgBuilders::Build()...} { + constexpr CTypeInfo::Type kReturnType = RetBuilder::Build().GetType(); + static_assert(kReturnType == CTypeInfo::Type::kVoid || + kReturnType == CTypeInfo::Type::kBool || + kReturnType == CTypeInfo::Type::kInt32 || + kReturnType == CTypeInfo::Type::kUint32 || + kReturnType == CTypeInfo::Type::kInt64 || + kReturnType == CTypeInfo::Type::kUint64 || + kReturnType == CTypeInfo::Type::kFloat32 || + kReturnType == CTypeInfo::Type::kFloat64 || + kReturnType == CTypeInfo::Type::kPointer || + kReturnType == CTypeInfo::Type::kAny, + "String and api object values are not currently " + "supported return types."); + } + + private: + const CTypeInfo arg_info_storage_[sizeof...(ArgBuilders)]; +}; + +template +struct TypeInfoHelper { + static_assert(sizeof(T) != sizeof(T), "This type is not supported"); +}; + +#define SPECIALIZE_GET_TYPE_INFO_HELPER_FOR(T, Enum) \ + template <> \ + struct TypeInfoHelper { \ + static constexpr CTypeInfo::Flags Flags() { \ + return CTypeInfo::Flags::kNone; \ + } \ + \ + static constexpr CTypeInfo::Type Type() { return CTypeInfo::Type::Enum; } \ + }; + +template +struct CTypeInfoTraits {}; + +#define DEFINE_TYPE_INFO_TRAITS(CType, Enum) \ + template <> \ + struct CTypeInfoTraits { \ + using ctype = CType; \ + }; + +#define PRIMITIVE_C_TYPES(V) \ + V(bool, kBool) \ + V(uint8_t, kUint8) \ + V(int32_t, kInt32) \ + V(uint32_t, kUint32) \ + V(int64_t, kInt64) \ + V(uint64_t, kUint64) \ + V(float, kFloat32) \ + V(double, kFloat64) \ + V(void*, kPointer) + +// Same as above, but includes deprecated types for compatibility. +#define ALL_C_TYPES(V) \ + PRIMITIVE_C_TYPES(V) \ + V(void, kVoid) \ + V(v8::Local, kV8Value) \ + V(v8::Local, kV8Value) \ + V(v8::Local, kV8Value) \ + V(AnyCType, kAny) + +// ApiObject was a temporary solution to wrap the pointer to the v8::Value. +// Please use v8::Local in new code for the arguments and +// v8::Local for the receiver, as ApiObject will be deprecated. + +ALL_C_TYPES(SPECIALIZE_GET_TYPE_INFO_HELPER_FOR) +PRIMITIVE_C_TYPES(DEFINE_TYPE_INFO_TRAITS) + +#undef PRIMITIVE_C_TYPES +#undef ALL_C_TYPES + +#undef TYPED_ARRAY_C_TYPES + +template <> +struct TypeInfoHelper { + static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; } + + static constexpr CTypeInfo::Type Type() { + return CTypeInfo::kCallbackOptionsType; + } +}; + +template <> +struct TypeInfoHelper { + static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; } + + static constexpr CTypeInfo::Type Type() { + return CTypeInfo::Type::kSeqOneByteString; + } +}; + +#define STATIC_ASSERT_IMPLIES(COND, ASSERTION, MSG) \ + static_assert(((COND) == 0) || (ASSERTION), MSG) + +} // namespace internal + +template +class V8_EXPORT CTypeInfoBuilder { + public: + using BaseType = T; + + static constexpr CTypeInfo Build() { + constexpr CTypeInfo::Flags kFlags = + MergeFlags(internal::TypeInfoHelper::Flags(), Flags...); + constexpr CTypeInfo::Type kType = internal::TypeInfoHelper::Type(); + + STATIC_ASSERT_IMPLIES( + uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kEnforceRangeBit), + CTypeInfo::IsIntegralType(kType), + "kEnforceRangeBit is only allowed for integral types."); + STATIC_ASSERT_IMPLIES( + uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kClampBit), + CTypeInfo::IsIntegralType(kType), + "kClampBit is only allowed for integral types."); + STATIC_ASSERT_IMPLIES( + uint8_t(kFlags) & uint8_t(CTypeInfo::Flags::kIsRestrictedBit), + CTypeInfo::IsFloatingPointType(kType), + "kIsRestrictedBit is only allowed for floating point types."); + + // Return the same type with the merged flags. + return CTypeInfo(internal::TypeInfoHelper::Type(), kFlags); + } + + private: + template + static constexpr CTypeInfo::Flags MergeFlags(CTypeInfo::Flags flags, + Rest... rest) { + return CTypeInfo::Flags(uint8_t(flags) | uint8_t(MergeFlags(rest...))); + } + static constexpr CTypeInfo::Flags MergeFlags() { return CTypeInfo::Flags(0); } +}; + +namespace internal { +template +class CFunctionBuilderWithFunction { + public: + explicit constexpr CFunctionBuilderWithFunction(const void* fn) : fn_(fn) {} + + template + constexpr auto Ret() { + return CFunctionBuilderWithFunction< + CTypeInfoBuilder, + ArgBuilders...>(fn_); + } + + template + constexpr auto Arg() { + // Return a copy of the builder with the Nth arg builder merged with + // template parameter pack Flags. + return ArgImpl( + std::make_index_sequence()); + } + + // Provided for testing purposes. + template + auto Patch(Ret (*patching_func)(Args...)) { + static_assert( + sizeof...(Args) == sizeof...(ArgBuilders), + "The patching function must have the same number of arguments."); + fn_ = reinterpret_cast(patching_func); + return *this; + } + + template + auto Build() { + static CFunctionInfoImpl + instance; + return CFunction(fn_, &instance); + } + + private: + template + struct GetArgBuilder; + + // Returns the same ArgBuilder as the one at index N, including its flags. + // Flags in the template parameter pack are ignored. + template + struct GetArgBuilder { + using type = std::tuple_element_t>; + }; + + // Returns an ArgBuilder with the same base type as the one at index N, + // but merges the flags with the flags in the template parameter pack. + template + struct GetArgBuilder { + using type = CTypeInfoBuilder< + typename std::tuple_element_t>::BaseType, + std::tuple_element_t>::Build().GetFlags(), + Flags...>; + }; + + // Return a copy of the CFunctionBuilder, but merges the Flags on + // ArgBuilder index N with the new Flags passed in the template parameter + // pack. + template + constexpr auto ArgImpl(std::index_sequence) { + return CFunctionBuilderWithFunction< + RetBuilder, typename GetArgBuilder::type...>(fn_); + } + + const void* fn_; +}; + +class CFunctionBuilder { + public: + constexpr CFunctionBuilder() {} + + template + constexpr auto Fn(R (*fn)(Args...)) { + return CFunctionBuilderWithFunction, + CTypeInfoBuilder...>( + reinterpret_cast(fn)); + } +}; + +} // namespace internal + +// static +template +CFunction CFunction::ArgUnwrap::Make( + R (*func)(Args...), CFunctionInfo::Int64Representation int64_rep) { + if (int64_rep == CFunctionInfo::Int64Representation::kNumber) { + return internal::CFunctionBuilder().Fn(func).Build(); + } + return internal::CFunctionBuilder() + .Fn(func) + .template Build(); +} + +using CFunctionBuilder = internal::CFunctionBuilder; + +static constexpr CTypeInfo kTypeInfoInt32 = CTypeInfo(CTypeInfo::Type::kInt32); +static constexpr CTypeInfo kTypeInfoFloat64 = + CTypeInfo(CTypeInfo::Type::kFloat64); + +/** + * Copies the contents of this JavaScript array to a C++ buffer with + * a given max_length. A CTypeInfo is passed as an argument, + * instructing different rules for conversion (e.g. restricted float/double). + * The element type T of the destination array must match the C type + * corresponding to the CTypeInfo (specified by CTypeInfoTraits). + * If the array length is larger than max_length or the array is of + * unsupported type, the operation will fail, returning false. Generally, an + * array which contains objects, undefined, null or anything not convertible + * to the requested destination type, is considered unsupported. The operation + * returns true on success. `type_info` will be used for conversions. + */ +template +bool V8_EXPORT V8_WARN_UNUSED_RESULT TryToCopyAndConvertArrayToCppBuffer( + Local src, T* dst, uint32_t max_length); + +template <> +bool V8_EXPORT V8_WARN_UNUSED_RESULT +TryToCopyAndConvertArrayToCppBuffer::Build().GetId(), + int32_t>(Local src, int32_t* dst, + uint32_t max_length); + +template <> +bool V8_EXPORT V8_WARN_UNUSED_RESULT +TryToCopyAndConvertArrayToCppBuffer::Build().GetId(), + uint32_t>(Local src, uint32_t* dst, + uint32_t max_length); + +template <> +bool V8_EXPORT V8_WARN_UNUSED_RESULT +TryToCopyAndConvertArrayToCppBuffer::Build().GetId(), + float>(Local src, float* dst, + uint32_t max_length); + +template <> +bool V8_EXPORT V8_WARN_UNUSED_RESULT +TryToCopyAndConvertArrayToCppBuffer::Build().GetId(), + double>(Local src, double* dst, + uint32_t max_length); + +constexpr v8::ExternalPointerTypeTag kFastAPIPointerTag = + V8_EXTERNAL_POINTER_TAG_COUNT - 1; + +} // namespace v8 + +#endif // INCLUDE_V8_FAST_API_CALLS_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-forward.h b/packages/android-jsi/platforms/android/cpp/include/v8-forward.h new file mode 100644 index 000000000..435fe856d --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-forward.h @@ -0,0 +1,82 @@ +// Copyright 2021 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 INCLUDE_V8_FORWARD_H_ +#define INCLUDE_V8_FORWARD_H_ + +// This header is intended to be used by headers that pass around V8 types, +// either by pointer or using Local. The full definitions can be included +// either via v8.h or the more fine-grained headers. + +#include "v8-local-handle.h" // NOLINT(build/include_directory) + +namespace v8 { + +class AccessorSignature; +class Array; +class ArrayBuffer; +class ArrayBufferView; +class BigInt; +class BigInt64Array; +class BigIntObject; +class BigUint64Array; +class Boolean; +class BooleanObject; +class Context; +class DataView; +class Data; +class Date; +class DictionaryTemplate; +class Extension; +class External; +class FixedArray; +class Float32Array; +class Float64Array; +class Function; +template +class FunctionCallbackInfo; +class FunctionTemplate; +class Int16Array; +class Int32; +class Int32Array; +class Int8Array; +class Integer; +class Isolate; +class Map; +class Module; +class Name; +class Number; +class NumberObject; +class Object; +class ObjectTemplate; +class Platform; +class Primitive; +class Private; +class Promise; +class Proxy; +class RegExp; +class Script; +class Set; +class SharedArrayBuffer; +class Signature; +class String; +class StringObject; +class Symbol; +class SymbolObject; +class Template; +class TryCatch; +class TypedArray; +class Uint16Array; +class Uint32; +class Uint32Array; +class Uint8Array; +class Uint8ClampedArray; +class UnboundModuleScript; +class Value; +class WasmMemoryObject; +class WasmModuleObject; + +} // namespace v8 + +#endif // INCLUDE_V8_FORWARD_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-function-callback.h b/packages/android-jsi/platforms/android/cpp/include/v8-function-callback.h new file mode 100644 index 000000000..80833bcd5 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-function-callback.h @@ -0,0 +1,714 @@ +// Copyright 2021 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 INCLUDE_V8_FUNCTION_CALLBACK_H_ +#define INCLUDE_V8_FUNCTION_CALLBACK_H_ + +#include +#include + +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-primitive.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +template +class BasicTracedReference; +template +class Global; +class Object; +class Value; + +namespace internal { +class FunctionCallbackArguments; +class PropertyCallbackArguments; +class Builtins; +} // namespace internal + +namespace debug { +class ConsoleCallArguments; +} // namespace debug + +namespace api_internal { +V8_EXPORT v8::Local GetFunctionTemplateData( + v8::Isolate* isolate, v8::Local raw_target); +} // namespace api_internal + +template +class ReturnValue { + public: + template + V8_INLINE ReturnValue(const ReturnValue& that) : value_(that.value_) { + static_assert(std::is_base_of_v, "type check"); + } + // Handle-based setters. + template + V8_INLINE void Set(const Global& handle); + template + V8_INLINE void SetNonEmpty(const Global& handle); + template + V8_INLINE void Set(const BasicTracedReference& handle); + template + V8_INLINE void SetNonEmpty(const BasicTracedReference& handle); + template + V8_INLINE void Set(const Local handle); + template + V8_INLINE void SetNonEmpty(const Local handle); + + // Fast primitive number setters. + V8_INLINE void Set(bool value); + V8_INLINE void Set(double i); + V8_INLINE void Set(int16_t i); + V8_INLINE void Set(int32_t i); + V8_INLINE void Set(int64_t i); + V8_INLINE void Set(uint16_t i); + V8_INLINE void Set(uint32_t i); + V8_INLINE void Set(uint64_t i); + + // Fast JS primitive setters. + V8_INLINE void SetNull(); + V8_INLINE void SetUndefined(); + V8_INLINE void SetFalse(); + V8_INLINE void SetEmptyString(); + + // Convenience getter for the Isolate. + V8_INLINE Isolate* GetIsolate() const; + + // Pointer setter: Uncompilable to prevent inadvertent misuse. + template + V8_INLINE void Set(S* whatever); + + // Getter. Creates a new Local<> so it comes with a certain performance + // hit. If the ReturnValue was not yet set, this will return the undefined + // value. + V8_INLINE Local Get() const; + + private: + template + friend class ReturnValue; + template + friend class FunctionCallbackInfo; + template + friend class PropertyCallbackInfo; + template + friend class PersistentValueMapBase; + V8_INLINE void SetInternal(internal::Address value); + // Default value depends on : + // - -> true_value, + // - -> true_value, + // - -> 0, + // - -> undefined_value, + // - -> undefined_value. + V8_INLINE void SetDefaultValue(); + V8_INLINE explicit ReturnValue(internal::Address* slot); + + // See FunctionCallbackInfo. + static constexpr int kIsolateValueIndex = -1; + + internal::Address* value_; +}; + +/** + * The argument information given to function call callbacks. This + * class provides access to information about the context of the call, + * including the receiver, the number and values of arguments, and + * the holder of the function. + */ +template +class FunctionCallbackInfo { + public: + /** The number of available arguments. */ + V8_INLINE int Length() const; + /** + * Accessor for the available arguments. Returns `undefined` if the index + * is out of bounds. + */ + V8_INLINE Local operator[](int i) const; + /** Returns the receiver. This corresponds to the "this" value. */ + V8_INLINE Local This() const; + /** For construct calls, this returns the "new.target" value. */ + V8_INLINE Local NewTarget() const; + /** Indicates whether this is a regular call or a construct call. */ + V8_INLINE bool IsConstructCall() const; + /** The data argument specified when creating the callback. */ + V8_INLINE Local Data() const; + /** The current Isolate. */ + V8_INLINE Isolate* GetIsolate() const; + /** The ReturnValue for the call. */ + V8_INLINE ReturnValue GetReturnValue() const; + + private: + friend class internal::FunctionCallbackArguments; + friend class internal::CustomArguments; + friend class debug::ConsoleCallArguments; + friend void internal::PrintFunctionCallbackInfo(void*); + using I = internal::Internals; + + // Frame block, matches the layout of ApiCallbackExitFrame. + // See ApiCallbackExitFrameConstants. + enum { + // + // Optional frame arguments block (exists only for API_CONSTRUCT_EXIT + // frame). + + // Frame arguments block. + kNewTargetIndex = -1, + + // + // Mandatory part, exists for both API_CALLBACK_EXIT and API_CONSTRUCT_EXIT + // frames. + // + + // Frame arguments block. + kArgcIndex, + + // Regular ExitFrame structure. + kFrameSPIndex, + kFrameTypeIndex, + kFrameConstantPoolIndex, // Optional, see I::kFrameCPSlotCount. + kFrameFPIndex = kFrameConstantPoolIndex + I::kFrameCPSlotCount, + kFramePCIndex, + + // Api arguments block, starts at kFirstArgumentIndex. + kFirstApiArgumentIndex, + kIsolateIndex = kFirstApiArgumentIndex, + kReturnValueIndex, + kContextIndex, + kTargetIndex, + + // JS args block, starts at kFrameFirstImplicitArgsIndex. + kReceiverIndex, + kFirstJSArgumentIndex, + + // Mandatory part includes receiver. + kArgsLength = kReceiverIndex + 1, + // Optional part size (exists only for API_CONSTRUCT_EXIT frame). + kOptionalArgsLength = 1, + + // The length of just Api arguments part. + kApiArgsLength = kReceiverIndex - kFirstApiArgumentIndex, + }; + + static_assert(kArgcIndex == 0); + static_assert(ReturnValue::kIsolateValueIndex == + kIsolateIndex - kReturnValueIndex); + + internal::Address* address_of_first_argument() const { + return &values_[kFirstJSArgumentIndex]; + } + + V8_INLINE FunctionCallbackInfo() = default; + + // FunctionCallbackInfo object provides a view of the stack area where the + // data is stored and thus it's not supposed to be copyable/movable. + FunctionCallbackInfo(const FunctionCallbackInfo&) = delete; + FunctionCallbackInfo& operator=(const FunctionCallbackInfo&) = delete; + FunctionCallbackInfo(FunctionCallbackInfo&&) = delete; + FunctionCallbackInfo& operator=(FunctionCallbackInfo&&) = delete; + + // Declare as mutable to let GC modify the contents of the slots even though + // it's not possible to change values via this class. + // Define the array size as 1 to make it clear that we are going to access + // it out-of-bounds from both sides anyway. + mutable internal::Address values_[1]; +}; + +/** + * The information passed to a property callback about the context + * of the property access. + */ +template +class PropertyCallbackInfo { + public: + /** + * \return The isolate of the property access. + */ + V8_INLINE Isolate* GetIsolate() const; + + /** + * \return The data set in the configuration, i.e., in + * `NamedPropertyHandlerConfiguration` or + * `IndexedPropertyHandlerConfiguration.` + */ + V8_INLINE Local Data() const; + + /** + * \return The object in the prototype chain of the receiver that has the + * interceptor. Suppose you have `x` and its prototype is `y`, and `y` + * has an interceptor. Then `info.This()` is `x` and `info.Holder()` is `y`. + * In case the property is installed on the global object the Holder() + * would return the global proxy. + */ + V8_INLINE Local Holder() const; + // TODO(http://crbug.com/333672197): deprecate and remove. + V8_DEPRECATE_SOON("Use Holder().") + V8_INLINE Local HolderV2() const; + + /** + * \return The return value of the callback. + * Can be changed by calling Set(). + * \code + * info.GetReturnValue().Set(...) + * \endcode + * + */ + V8_INLINE ReturnValue GetReturnValue() const; + + /** + * For [[Set]], [[DefineOwnProperty]] and [[Delete]] operations (i.e. + * for setter/definer/deleter callbacks) indicates whether TypeError + * should be thrown upon operation failure. The callback should throw + * TypeError only if it's necessary to provide more details than a default + * error thrown by V8 contains in this case. + * + * \return True if the intercepted function should throw if an error occurs. + * Usually, `true` corresponds to `'use strict'` execution mode. + * + * \note Always `false` when the operation was initiated by respecive + * `Reflect` call (i.e. `Reflect.set()`, `Reflect.defineProperty()` and + * `Reflect.deleteProperty()`). + */ + V8_INLINE bool ShouldThrowOnError() const; + + private: + template + friend class PropertyCallbackInfo; + friend class MacroAssembler; + friend class internal::PropertyCallbackArguments; + friend class internal::CustomArguments; + friend void internal::PrintPropertyCallbackInfo(void*); + using I = internal::Internals; + + // ShouldThrowOnError() can return true only for setter/definer/deleter + // callbacks which match [[Set]]/[[DefineOwnProperty]]/[[Delete]] + // operations. We detect these operations by return value type - they + // all return boolean value, even though setter/deleter callbacks are + // still using v8::PropertyCallbackInfo. + // TODO(https://crbug.com/348660658): cleanup this, once the callbacks are + // migrated to a new return type. + static constexpr bool HasShouldThrowOnError() { + return std::is_same_v || std::is_same_v; + } + + // Indicates whether this is a named accessor/interceptor callback call + // or an indexed one. + V8_INLINE bool IsNamed() const; + + // Frame block, matches the layout of ApiAccessorExitFrame. + // See ApiAccessorExitFrameConstants. + enum { + // Frame arguments block. + kPropertyKeyIndex, + + // Regular ExitFrame structure. + kFrameSPIndex, + kFrameTypeIndex, + kFrameConstantPoolIndex, // Optional, see I::kFrameCPSlotCount. + kFrameFPIndex = kFrameConstantPoolIndex + I::kFrameCPSlotCount, + kFramePCIndex, + + // Other arguments block, starts at kFirstArgumentIndex. + kFirstApiArgumentIndex, + kIsolateIndex = kFirstApiArgumentIndex, + kReturnValueIndex, + kCallbackInfoIndex, + kHolderIndex, + + // + // Optional part, used only by setter/definer/deleter callbacks. + // + kFirstOptionalArgument, + kShouldThrowOnErrorIndex = kFirstOptionalArgument, + + // Used as value handle storage when called via CallApiSetter builtin. + kValueIndex, + + kFullArgsLength, + kMandatoryArgsLength = kFirstOptionalArgument, + kOptionalArgsLength = kFullArgsLength - kFirstOptionalArgument, + + // Various lengths of just Api arguments part. + kMandatoryApiArgsLength = kMandatoryArgsLength - kFirstApiArgumentIndex, + kFullApiArgsLength = kFullArgsLength - kFirstApiArgumentIndex, + }; + + // PropertyCallbackInfo object provides a view of the stack area where the + // data is stored and thus it's not supposed to be copyable/movable. + PropertyCallbackInfo(const PropertyCallbackInfo&) = delete; + PropertyCallbackInfo& operator=(const PropertyCallbackInfo&) = delete; + PropertyCallbackInfo(PropertyCallbackInfo&&) = delete; + PropertyCallbackInfo& operator=(PropertyCallbackInfo&&) = delete; + + PropertyCallbackInfo() = default; + + // Declare as mutable to let GC modify the contents of the slots even though + // it's not possible to change values via this class. + // Define the array size as 1 to make it clear that we are going to access + // it out-of-bounds anyway. + mutable internal::Address args_[1]; +}; + +using FunctionCallback = void (*)(const FunctionCallbackInfo& info); + +// --- Implementation --- + +template +ReturnValue::ReturnValue(internal::Address* slot) : value_(slot) {} + +template +void ReturnValue::SetInternal(internal::Address value) { +#if V8_STATIC_ROOTS_BOOL + using I = internal::Internals; + // Ensure that the upper 32-bits are not modified. Compiler should be + // able to optimize this to a store of a lower 32-bits of the value. + // This is fine since the callback can return only JavaScript values which + // are either Smis or heap objects allocated in the main cage. + *value_ = I::DecompressTaggedField(*value_, I::CompressTagged(value)); +#else + *value_ = value; +#endif // V8_STATIC_ROOTS_BOOL +} + +template +template +void ReturnValue::Set(const Global& handle) { + static_assert(std::is_base_of_v, "type check"); + if (V8_UNLIKELY(handle.IsEmpty())) { + SetDefaultValue(); + } else { + SetInternal(handle.ptr()); + } +} + +template +template +void ReturnValue::SetNonEmpty(const Global& handle) { + static_assert(std::is_base_of_v, "type check"); +#ifdef V8_ENABLE_CHECKS + internal::VerifyHandleIsNonEmpty(handle.IsEmpty()); +#endif // V8_ENABLE_CHECKS + SetInternal(handle.ptr()); +} + +template +template +void ReturnValue::Set(const BasicTracedReference& handle) { + static_assert(std::is_base_of_v, "type check"); + if (V8_UNLIKELY(handle.IsEmpty())) { + SetDefaultValue(); + } else { + SetInternal(handle.ptr()); + } +} + +template +template +void ReturnValue::SetNonEmpty(const BasicTracedReference& handle) { + static_assert(std::is_base_of_v, "type check"); +#ifdef V8_ENABLE_CHECKS + internal::VerifyHandleIsNonEmpty(handle.IsEmpty()); +#endif // V8_ENABLE_CHECKS + SetInternal(handle.ptr()); +} + +template +template +void ReturnValue::Set(const Local handle) { + static_assert(std::is_base_of_v, "type check"); + if (V8_UNLIKELY(handle.IsEmpty())) { + SetDefaultValue(); + } else { + SetInternal(handle.ptr()); + } +} + +template +template +void ReturnValue::SetNonEmpty(const Local handle) { + static_assert(std::is_base_of_v, "type check"); +#ifdef V8_ENABLE_CHECKS + internal::VerifyHandleIsNonEmpty(handle.IsEmpty()); +#endif // V8_ENABLE_CHECKS + SetInternal(handle.ptr()); +} + +template +void ReturnValue::Set(double i) { + static_assert(std::is_base_of_v, "type check"); + SetNonEmpty(Number::New(GetIsolate(), i)); +} + +template +void ReturnValue::Set(int16_t i) { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; + static_assert(I::IsValidSmi(std::numeric_limits::min())); + static_assert(I::IsValidSmi(std::numeric_limits::max())); + SetInternal(I::IntegralToSmi(i)); +} + +template +void ReturnValue::Set(int32_t i) { + static_assert(std::is_base_of_v, "type check"); + if (const auto result = internal::Internals::TryIntegralToSmi(i)) { + SetInternal(*result); + return; + } + SetNonEmpty(Integer::New(GetIsolate(), i)); +} + +template +void ReturnValue::Set(int64_t i) { + static_assert(std::is_base_of_v, "type check"); + if (const auto result = internal::Internals::TryIntegralToSmi(i)) { + SetInternal(*result); + return; + } + SetNonEmpty(Number::New(GetIsolate(), static_cast(i))); +} + +template +void ReturnValue::Set(uint16_t i) { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; + static_assert(I::IsValidSmi(std::numeric_limits::min())); + static_assert(I::IsValidSmi(std::numeric_limits::max())); + SetInternal(I::IntegralToSmi(i)); +} + +template +void ReturnValue::Set(uint32_t i) { + static_assert(std::is_base_of_v, "type check"); + if (const auto result = internal::Internals::TryIntegralToSmi(i)) { + SetInternal(*result); + return; + } + SetNonEmpty(Integer::NewFromUnsigned(GetIsolate(), i)); +} + +template +void ReturnValue::Set(uint64_t i) { + static_assert(std::is_base_of_v, "type check"); + if (const auto result = internal::Internals::TryIntegralToSmi(i)) { + SetInternal(*result); + return; + } + SetNonEmpty(Number::New(GetIsolate(), static_cast(i))); +} + +template +void ReturnValue::Set(bool value) { + static_assert(std::is_void_v || std::is_base_of_v, + "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(value ? I::StaticReadOnlyRoot::kTrueValue + : I::StaticReadOnlyRoot::kFalseValue); +#else + int root_index; + if (value) { + root_index = I::kTrueValueRootIndex; + } else { + root_index = I::kFalseValueRootIndex; + } + *value_ = I::GetRoot(GetIsolate(), root_index); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +void ReturnValue::SetDefaultValue() { + using I = internal::Internals; + if constexpr (std::is_same_v || std::is_same_v) { + Set(true); + } else if constexpr (std::is_same_v) { + SetInternal(I::IntegralToSmi(0)); + } else { + static_assert(std::is_same_v || std::is_same_v); +#if V8_STATIC_ROOTS_BOOL + SetInternal(I::StaticReadOnlyRoot::kUndefinedValue); +#else + *value_ = I::GetRoot(GetIsolate(), I::kUndefinedValueRootIndex); +#endif // V8_STATIC_ROOTS_BOOL + } +} + +template +void ReturnValue::SetNull() { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(I::StaticReadOnlyRoot::kNullValue); +#else + *value_ = I::GetRoot(GetIsolate(), I::kNullValueRootIndex); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +void ReturnValue::SetUndefined() { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(I::StaticReadOnlyRoot::kUndefinedValue); +#else + *value_ = I::GetRoot(GetIsolate(), I::kUndefinedValueRootIndex); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +void ReturnValue::SetFalse() { + static_assert(std::is_void_v || std::is_base_of_v, + "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(I::StaticReadOnlyRoot::kFalseValue); +#else + *value_ = I::GetRoot(GetIsolate(), I::kFalseValueRootIndex); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +void ReturnValue::SetEmptyString() { + static_assert(std::is_base_of_v, "type check"); + using I = internal::Internals; +#if V8_STATIC_ROOTS_BOOL +#ifdef V8_ENABLE_CHECKS + internal::PerformCastCheck( + internal::ValueHelper::SlotAsValue(value_)); +#endif // V8_ENABLE_CHECKS + SetInternal(I::StaticReadOnlyRoot::kEmptyString); +#else + *value_ = I::GetRoot(GetIsolate(), I::kEmptyStringRootIndex); +#endif // V8_STATIC_ROOTS_BOOL +} + +template +Isolate* ReturnValue::GetIsolate() const { + return *reinterpret_cast(&value_[kIsolateValueIndex]); +} + +template +Local ReturnValue::Get() const { + return Local::New(GetIsolate(), + internal::ValueHelper::SlotAsValue(value_)); +} + +template +template +void ReturnValue::Set(S* whatever) { + static_assert(sizeof(S) < 0, "incompilable to prevent inadvertent misuse"); +} + +template +Local FunctionCallbackInfo::operator[](int i) const { + if (i < 0 || Length() <= i) return Undefined(GetIsolate()); + return Local::FromSlot(&values_[kFirstJSArgumentIndex + i]); +} + +template +Local FunctionCallbackInfo::This() const { + return Local::FromSlot(&values_[kReceiverIndex]); +} + +template +Local FunctionCallbackInfo::NewTarget() const { + if (IsConstructCall()) { + // Can't use &values_[kNewTargetIndex] because of "array index -1 is + // before the beginning of the array" error. + internal::Address* values = &values_[0]; + return Local::FromSlot(values + kNewTargetIndex); + } + return Undefined(GetIsolate()); +} + +template +Local FunctionCallbackInfo::Data() const { + auto target = Local::FromSlot(&values_[kTargetIndex]); + return api_internal::GetFunctionTemplateData(GetIsolate(), target); +} + +template +Isolate* FunctionCallbackInfo::GetIsolate() const { + return reinterpret_cast(values_[kIsolateIndex]); +} + +template +ReturnValue FunctionCallbackInfo::GetReturnValue() const { + return ReturnValue(&values_[kReturnValueIndex]); +} + +template +bool FunctionCallbackInfo::IsConstructCall() const { + return I::SmiValue(values_[kFrameTypeIndex]) == I::kFrameTypeApiConstructExit; +} + +template +int FunctionCallbackInfo::Length() const { + return static_cast(values_[kArgcIndex]); +} + +template +bool PropertyCallbackInfo::IsNamed() const { + return I::SmiValue(args_[kFrameTypeIndex]) == + I::kFrameTypeApiNamedAccessorExit; +} + +template +Isolate* PropertyCallbackInfo::GetIsolate() const { + return *reinterpret_cast(&args_[kIsolateIndex]); +} + +template +Local PropertyCallbackInfo::Data() const { + internal::Address callback_info = args_[kCallbackInfoIndex]; + internal::Address data = + I::ReadTaggedPointerField(callback_info, I::kCallbackInfoDataOffset); + return Local::New(GetIsolate(), data); +} + +template +Local PropertyCallbackInfo::Holder() const { + return Local::FromSlot(&args_[kHolderIndex]); +} +template +Local PropertyCallbackInfo::HolderV2() const { + return Holder(); +} + +template +ReturnValue PropertyCallbackInfo::GetReturnValue() const { + return ReturnValue(&args_[kReturnValueIndex]); +} + +template +bool PropertyCallbackInfo::ShouldThrowOnError() const { + if constexpr (!HasShouldThrowOnError()) return false; + if (args_[kShouldThrowOnErrorIndex] != + I::IntegralToSmi(I::kInferShouldThrowMode)) { + return args_[kShouldThrowOnErrorIndex] != I::IntegralToSmi(I::kDontThrow); + } + return v8::internal::ShouldThrowOnError( + reinterpret_cast(GetIsolate())); +} + +} // namespace v8 + +#endif // INCLUDE_V8_FUNCTION_CALLBACK_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-function.h b/packages/android-jsi/platforms/android/cpp/include/v8-function.h new file mode 100644 index 000000000..18532519f --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-function.h @@ -0,0 +1,148 @@ +// Copyright 2021 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 INCLUDE_V8_FUNCTION_H_ +#define INCLUDE_V8_FUNCTION_H_ + +#include +#include + +#include "v8-function-callback.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) +#include "v8-message.h" // NOLINT(build/include_directory) +#include "v8-object.h" // NOLINT(build/include_directory) +#include "v8-template.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Context; +class Location; +class UnboundScript; + +/** + * A JavaScript function object (ECMA-262, 15.3). + */ +class V8_EXPORT Function : public Object { + public: + /** + * Create a function in the current execution context + * for a given FunctionCallback. + */ + static MaybeLocal New( + Local context, FunctionCallback callback, Local data = {}, + int length = 0, + ConstructorBehavior behavior = ConstructorBehavior::kAllow, + SideEffectType side_effect_type = SideEffectType::kHasSideEffect); + + V8_WARN_UNUSED_RESULT MaybeLocal NewInstance( + Local context, int argc, Local argv[]) const; + + V8_WARN_UNUSED_RESULT MaybeLocal NewInstance( + Local context) const { + return NewInstance(context, 0, nullptr); + } + + /** + * When side effect checks are enabled, passing kHasNoSideEffect allows the + * constructor to be invoked without throwing. Calls made within the + * constructor are still checked. + */ + V8_WARN_UNUSED_RESULT MaybeLocal NewInstanceWithSideEffectType( + Local context, int argc, Local argv[], + SideEffectType side_effect_type = SideEffectType::kHasSideEffect) const; + + V8_WARN_UNUSED_RESULT MaybeLocal Call(v8::Isolate* isolate, + Local context, + Local recv, int argc, + Local argv[]); + V8_WARN_UNUSED_RESULT MaybeLocal Call(Local context, + Local recv, int argc, + Local argv[]); + + void SetName(Local name); + Local GetName() const; + + /** + * Name inferred from variable or property assignment of this function. + * Used to facilitate debugging and profiling of JavaScript code written + * in an OO style, where many functions are anonymous but are assigned + * to object properties. + */ + Local GetInferredName() const; + + /** + * displayName if it is set, otherwise name if it is configured, otherwise + * function name, otherwise inferred name. + */ + Local GetDebugName() const; + + /** + * Returns zero based line number of function body and + * kLineOffsetNotFound if no information available. + */ + int GetScriptLineNumber() const; + /** + * Returns zero based column number of function body and + * kLineOffsetNotFound if no information available. + */ + int GetScriptColumnNumber() const; + + /** + * Returns zero based line and column number of function body, else returns + * {-1, -1}. + */ + Location GetScriptLocation() const; + + /** + * Returns zero based start position (character offset) of function body and + * kLineOffsetNotFound if no information available. + */ + int GetScriptStartPosition() const; + + /** + * Returns scriptId. + */ + int ScriptId() const; + + /** + * Returns the original function if this function is bound, else returns + * v8::Undefined. + */ + Local GetBoundFunction() const; + + /** + * Calls builtin Function.prototype.toString on this function. + * This is different from Value::ToString() that may call a user-defined + * toString() function, and different than Object::ObjectProtoToString() which + * always serializes "[object Function]". + */ + V8_WARN_UNUSED_RESULT MaybeLocal FunctionProtoToString( + Local context); + + /** + * Returns true if the function does nothing. + * The function returns false on error. + * Note that this function is experimental. Embedders should not rely on + * this existing. We may remove this function in the future. + */ + V8_WARN_UNUSED_RESULT bool Experimental_IsNopFunction() const; + + ScriptOrigin GetScriptOrigin() const; + V8_INLINE static Function* Cast(Value* value) { +#ifdef V8_ENABLE_CHECKS + CheckCast(value); +#endif + return static_cast(value); + } + + static const int kLineOffsetNotFound; + + private: + Function(); + static void CheckCast(Value* obj); +}; +} // namespace v8 + +#endif // INCLUDE_V8_FUNCTION_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-handle-base.h b/packages/android-jsi/platforms/android/cpp/include/v8-handle-base.h new file mode 100644 index 000000000..9149f8fb0 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-handle-base.h @@ -0,0 +1,155 @@ +// 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 INCLUDE_V8_HANDLE_BASE_H_ +#define INCLUDE_V8_HANDLE_BASE_H_ + +#include "v8-internal.h" // NOLINT(build/include_directory) + +namespace v8::api_internal { + +template +class StackAllocated { + public: + V8_INLINE StackAllocated() = default; + + protected: + struct no_checking_tag {}; + static constexpr no_checking_tag do_not_check{}; + + V8_INLINE explicit StackAllocated(no_checking_tag) {} + V8_INLINE explicit StackAllocated(const StackAllocated& other, + no_checking_tag) {} + + V8_INLINE void VerifyOnStack() const {} +}; + +template <> +class V8_TRIVIAL_ABI StackAllocated : public StackAllocated { + public: + V8_INLINE StackAllocated() { VerifyOnStack(); } + +#if V8_HAS_ATTRIBUTE_TRIVIAL_ABI + // In this case, StackAllocated becomes not trivially copyable. + V8_INLINE StackAllocated(const StackAllocated& other) { VerifyOnStack(); } + StackAllocated& operator=(const StackAllocated&) = default; +#endif + + protected: + V8_INLINE explicit StackAllocated(no_checking_tag tag) + : StackAllocated(tag) {} + V8_INLINE explicit StackAllocated(const StackAllocated& other, + no_checking_tag tag) + : StackAllocated(other, tag) {} + +#ifdef ENABLE_SLOW_DCHECKS + V8_EXPORT void VerifyOnStack() const; +#else + V8_INLINE V8_EXPORT void VerifyOnStack() const {} +#endif +}; + +/** + * A base class for abstract handles containing indirect pointers. + * These are useful regardless of whether direct local support is enabled. + */ +class IndirectHandleBase { + public: + // Returns true if the handle is empty. + V8_INLINE bool IsEmpty() const { return location_ == nullptr; } + + // Sets the handle to be empty. IsEmpty() will then return true. + V8_INLINE void Clear() { location_ = nullptr; } + + protected: + friend class internal::ValueHelper; + friend class internal::HandleHelper; + + V8_INLINE IndirectHandleBase() = default; + V8_INLINE IndirectHandleBase(const IndirectHandleBase& other) = default; + V8_INLINE IndirectHandleBase& operator=(const IndirectHandleBase& that) = + default; + + V8_INLINE explicit IndirectHandleBase(internal::Address* location) + : location_(location) {} + + // Returns the address of the actual heap object (tagged). + // This method must be called only if the handle is not empty, otherwise it + // will crash. + V8_INLINE internal::Address ptr() const { return *location_; } + + // Returns a reference to the slot (indirect pointer). + V8_INLINE internal::Address* const& slot() const { return location_; } + V8_INLINE internal::Address*& slot() { return location_; } + + // Returns the handler's "value" (direct or indirect pointer, depending on + // whether direct local support is enabled). + template + V8_INLINE T* value() const { + return internal::ValueHelper::SlotAsValue(slot()); + } + +#ifdef V8_ENABLE_DIRECT_HANDLE + V8_INLINE internal::ValueHelper::InternalRepresentationType repr() const { + return location_ ? *location_ : internal::ValueHelper::kEmpty; + } +#else + V8_INLINE internal::ValueHelper::InternalRepresentationType repr() const { + return location_; + } +#endif // V8_ENABLE_DIRECT_HANDLE + + private: + internal::Address* location_ = nullptr; +}; + +#ifdef V8_ENABLE_DIRECT_HANDLE + +/** + * A base class for abstract handles containing direct pointers. + * These are only possible when conservative stack scanning is enabled. + */ +class DirectHandleBase { + public: + // Returns true if the handle is empty. + V8_INLINE bool IsEmpty() const { + return ptr_ == internal::ValueHelper::kEmpty; + } + + // Sets the handle to be empty. IsEmpty() will then return true. + V8_INLINE void Clear() { ptr_ = internal::ValueHelper::kEmpty; } + + protected: + friend class internal::ValueHelper; + friend class internal::HandleHelper; + + V8_INLINE DirectHandleBase() = default; + V8_INLINE DirectHandleBase(const DirectHandleBase& other) = default; + V8_INLINE DirectHandleBase& operator=(const DirectHandleBase& that) = default; + + V8_INLINE explicit DirectHandleBase(internal::Address ptr) : ptr_(ptr) {} + + // Returns the address of the referenced object. + V8_INLINE internal::Address ptr() const { return ptr_; } + + // Returns the handler's "value" (direct pointer, as direct local support + // is guaranteed to be enabled here). + template + V8_INLINE T* value() const { + return reinterpret_cast(ptr_); + } + + V8_INLINE internal::ValueHelper::InternalRepresentationType repr() const { + return ptr_; + } + + private: + internal::Address ptr_ = internal::ValueHelper::kEmpty; +}; + +#endif // V8_ENABLE_DIRECT_HANDLE + +} // namespace v8::api_internal + +#endif // INCLUDE_V8_HANDLE_BASE_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-initialization.h b/packages/android-jsi/platforms/android/cpp/include/v8-initialization.h new file mode 100644 index 000000000..02f3acfba --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-initialization.h @@ -0,0 +1,384 @@ +// Copyright 2021 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 INCLUDE_V8_INITIALIZATION_H_ +#define INCLUDE_V8_INITIALIZATION_H_ + +#include +#include + +#include "v8-callbacks.h" // NOLINT(build/include_directory) +#include "v8-internal.h" // NOLINT(build/include_directory) +#include "v8-isolate.h" // NOLINT(build/include_directory) +#include "v8-platform.h" // NOLINT(build/include_directory) +#include "v8config.h" // NOLINT(build/include_directory) + +// We reserve the V8_* prefix for macros defined in V8 public API and +// assume there are no name conflicts with the embedder's code. + +/** + * The v8 JavaScript engine. + */ +namespace v8 { + +class PageAllocator; +class Platform; +template +class PersistentValueMapBase; + +/** + * EntropySource is used as a callback function when v8 needs a source + * of entropy. + */ +using EntropySource = bool (*)(unsigned char* buffer, size_t length); + +/** + * ReturnAddressLocationResolver is used as a callback function when v8 is + * resolving the location of a return address on the stack. Profilers that + * change the return address on the stack can use this to resolve the stack + * location to wherever the profiler stashed the original return address. + * + * \param return_addr_location A location on stack where a machine + * return address resides. + * \returns Either return_addr_location, or else a pointer to the profiler's + * copy of the original return address. + * + * \note The resolver function must not cause garbage collection. + */ +using ReturnAddressLocationResolver = + uintptr_t (*)(uintptr_t return_addr_location); + +using DcheckErrorCallback = void (*)(const char* file, int line, + const char* message); + +using V8FatalErrorCallback = void (*)(const char* file, int line, + const char* message); + +/** + * Container class for static utility functions. + */ +class V8_EXPORT V8 { + public: + /** + * Hand startup data to V8, in case the embedder has chosen to build + * V8 with external startup data. + * + * Note: + * - By default the startup data is linked into the V8 library, in which + * case this function is not meaningful. + * - If this needs to be called, it needs to be called before V8 + * tries to make use of its built-ins. + * - To avoid unnecessary copies of data, V8 will point directly into the + * given data blob, so pretty please keep it around until V8 exit. + * - Compression of the startup blob might be useful, but needs to + * handled entirely on the embedders' side. + * - The call will abort if the data is invalid. + */ + static void SetSnapshotDataBlob(StartupData* startup_blob); + + /** Set the callback to invoke in case of Dcheck failures. */ + static void SetDcheckErrorHandler(DcheckErrorCallback that); + + /** Set the callback to invoke in the case of CHECK failures or fatal + * errors. This is distinct from Isolate::SetFatalErrorHandler, which + * is invoked in response to API usage failures. + * */ + static void SetFatalErrorHandler(V8FatalErrorCallback that); + + /** + * Sets V8 flags from a string. + */ + static void SetFlagsFromString(const char* str); + static void SetFlagsFromString(const char* str, size_t length); + + /** + * Sets V8 flags from the command line. + */ + static void SetFlagsFromCommandLine(int* argc, char** argv, + bool remove_flags); + + /** Get the version string. */ + static const char* GetVersion(); + + /** + * Initializes V8. This function needs to be called before the first Isolate + * is created. It always returns true. + */ + V8_INLINE static bool Initialize() { +#ifdef V8_TARGET_OS_ANDROID + const bool kV8TargetOsIsAndroid = true; +#else + const bool kV8TargetOsIsAndroid = false; +#endif + +#ifdef V8_ENABLE_CHECKS + const bool kV8EnableChecks = true; +#else + const bool kV8EnableChecks = false; +#endif + + const int kBuildConfiguration = + (internal::PointerCompressionIsEnabled() ? kPointerCompression : 0) | + (internal::SmiValuesAre31Bits() ? k31BitSmis : 0) | + (internal::SandboxIsEnabled() ? kSandbox : 0) | + (kV8TargetOsIsAndroid ? kTargetOsIsAndroid : 0) | + (kV8EnableChecks ? kEnableChecks : 0); + return Initialize(kBuildConfiguration); + } + + /** + * Allows the host application to provide a callback which can be used + * as a source of entropy for random number generators. + */ + static void SetEntropySource(EntropySource source); + + /** + * Allows the host application to provide a callback that allows v8 to + * cooperate with a profiler that rewrites return addresses on stack. + */ + static void SetReturnAddressLocationResolver( + ReturnAddressLocationResolver return_address_resolver); + + /** + * Releases any resources used by v8 and stops any utility threads + * that may be running. Note that disposing v8 is permanent, it + * cannot be reinitialized. + * + * It should generally not be necessary to dispose v8 before exiting + * a process, this should happen automatically. It is only necessary + * to use if the process needs the resources taken up by v8. + */ + static bool Dispose(); + + /** + * Initialize the ICU library bundled with V8. The embedder should only + * invoke this method when using the bundled ICU. Returns true on success. + * + * If V8 was compiled with the ICU data in an external file, the location + * of the data file has to be provided. + */ + static bool InitializeICU(const char* icu_data_file = nullptr); + + /** + * Initialize the ICU library bundled with V8. The embedder should only + * invoke this method when using the bundled ICU. If V8 was compiled with + * the ICU data in an external file and when the default location of that + * file should be used, a path to the executable must be provided. + * Returns true on success. + * + * The default is a file called icudtl.dat side-by-side with the executable. + * + * Optionally, the location of the data file can be provided to override the + * default. + */ + static bool InitializeICUDefaultLocation(const char* exec_path, + const char* icu_data_file = nullptr); + + /** + * Initialize the external startup data. The embedder only needs to + * invoke this method when external startup data was enabled in a build. + * + * If V8 was compiled with the startup data in an external file, then + * V8 needs to be given those external files during startup. There are + * three ways to do this: + * - InitializeExternalStartupData(const char*) + * This will look in the given directory for the file "snapshot_blob.bin". + * - InitializeExternalStartupDataFromFile(const char*) + * As above, but will directly use the given file name. + * - Call SetSnapshotDataBlob. + * This will read the blobs from the given data structure and will + * not perform any file IO. + */ + static void InitializeExternalStartupData(const char* directory_path); + static void InitializeExternalStartupDataFromFile(const char* snapshot_blob); + + /** + * Sets the v8::Platform to use. This should be invoked before V8 is + * initialized. + */ + static void InitializePlatform(Platform* platform); + + /** + * Clears all references to the v8::Platform. This should be invoked after + * V8 was disposed. + */ + static void DisposePlatform(); + +#if defined(V8_ENABLE_SANDBOX) + /** + * The mode the V8 sandbox operates in. + * + * These values are persisted to logs. Entries should not be renumbered and + * numeric values should never be reused. If you add new items here, update + * V8SandboxMode in tools/metrics/histograms/metadata/v8/enums.xml in + * Chromium. + */ + enum class SandboxMode : uint8_t { + /** + * The sandbox is configured securely with a full reservation and an + * inaccessible Smi address range. + */ + kSecure = 0, + /** + * The sandbox is configured insecurely without a known reason. + */ + kInsecure = 1, + /** + * The sandbox is partially reserved, but the Smi address range is + * inaccessible. + */ + kInsecurePartialReservationSmiInaccessible = 2, + /** + * The sandbox is fully reserved, but the Smi address range is accessible. + */ + kInsecureFullReservationSmiAccessible = 3, + /** + * The sandbox is partially reserved and the Smi address range is + * accessible. + */ + kInsecurePartialReservationSmiAccessible = 4, + + kMaxValue = kInsecurePartialReservationSmiAccessible, + }; + + /** + * Returns the current state of the sandbox. + */ + static SandboxMode GetSandboxMode(); + + /** + * Returns true if the sandbox is configured securely. + * + * If V8 cannot create a regular sandbox during initialization, for example + * because not enough virtual address space can be reserved, it will instead + * create a fallback sandbox that still allows it to function normally but + * does not have the same security properties as a regular sandbox. This API + * can be used to determine if such a fallback sandbox is being used, in + * which case it will return false. + */ + static bool IsSandboxConfiguredSecurely(); + + /** + * Provides access to the virtual address subspace backing the sandbox. + * + * This can be used to allocate pages inside the sandbox, for example to + * obtain virtual memory for ArrayBuffer backing stores, which must be + * located inside the sandbox. + * + * It should be assumed that an attacker can corrupt data inside the sandbox, + * and so in particular the contents of pages allocagted in this virtual + * address space, arbitrarily and concurrently. Due to this, it is + * recommended to to only place pure data buffers in them. + */ + static VirtualAddressSpace* GetSandboxAddressSpace(); + + /** + * Returns the size of the sandbox in bytes. + * + * This represents the size of the address space that V8 can directly address + * and in which it allocates its objects. + */ + static size_t GetSandboxSizeInBytes(); + + /** + * Returns the size of the address space reservation backing the sandbox. + * + * This may be larger than the sandbox (i.e. |GetSandboxSizeInBytes()|) due + * to surrounding guard regions, or may be smaller than the sandbox in case a + * fallback sandbox is being used, which will use a smaller virtual address + * space reservation. In the latter case this will also be different from + * |GetSandboxAddressSpace()->size()| as that will cover a larger part of the + * address space than what has actually been reserved. + */ + static size_t GetSandboxReservationSizeInBytes(); +#endif // V8_ENABLE_SANDBOX + + enum class WasmMemoryType { + kMemory32, + kMemory64, + }; + + /** + * Returns the virtual address space reservation size (in bytes) needed + * for one WebAssembly memory instance of the given capacity. + * + * \param type Whether this is a memory32 or memory64 instance. + * \param byte_capacity The maximum size, in bytes, of the WebAssembly + * memory. Values exceeding the engine's maximum allocatable memory + * size for the given type (determined by max_mem32_pages or + * max_mem64_pages) are clamped. + * + * When trap-based bounds checking is enabled by + * EnableWebAssemblyTrapHandler(), the amount of virtual address space + * that V8 needs to reserve for each WebAssembly memory instance can + * be much bigger than the requested size. If the process does + * not have enough virtual memory available, WebAssembly memory allocation + * would fail. During the initialization of V8, embedders can use this method + * to estimate whether the process has enough virtual memory for their + * usage of WebAssembly, and decide whether to enable the trap handler + * via EnableWebAssemblyTrapHandler(), or to skip it and reduce the amount of + * virtual memory required to keep the application running. + */ + static size_t GetWasmMemoryReservationSizeInBytes(WasmMemoryType type, + size_t byte_capacity); + + /** + * Activate trap-based bounds checking for WebAssembly. + * + * \param use_v8_signal_handler Whether V8 should install its own signal + * handler or rely on the embedder's. + */ + static bool EnableWebAssemblyTrapHandler(bool use_v8_signal_handler); + +#if defined(V8_OS_WIN) + /** + * On Win64, by default V8 does not emit unwinding data for jitted code, + * which means the OS cannot walk the stack frames and the system Structured + * Exception Handling (SEH) cannot unwind through V8-generated code: + * https://code.google.com/p/v8/issues/detail?id=3598. + * + * This function allows embedders to register a custom exception handler for + * exceptions in V8-generated code. + */ + static void SetUnhandledExceptionCallback( + UnhandledExceptionCallback callback); +#endif + + /** + * Allows the host application to provide a callback that will be called when + * v8 has encountered a fatal failure to allocate memory and is about to + * terminate. + */ + static void SetFatalMemoryErrorCallback(OOMErrorCallback callback); + + /** + * Get statistics about the shared memory usage. + */ + static void GetSharedMemoryStatistics(SharedMemoryStatistics* statistics); + + private: + V8(); + + enum BuildConfigurationFeatures { + kPointerCompression = 1 << 0, + k31BitSmis = 1 << 1, + kSandbox = 1 << 2, + kTargetOsIsAndroid = 1 << 3, + kEnableChecks = 1 << 4, + }; + + /** + * Checks that the embedder build configuration is compatible with + * the V8 binary and if so initializes V8. + */ + static bool Initialize(int build_config); + + friend class Context; + template + friend class PersistentValueMapBase; +}; + +} // namespace v8 + +#endif // INCLUDE_V8_INITIALIZATION_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-inspector-protocol.h b/packages/android-jsi/platforms/android/cpp/include/v8-inspector-protocol.h new file mode 100644 index 000000000..a5ffb7d69 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-inspector-protocol.h @@ -0,0 +1,13 @@ +// Copyright 2016 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_V8_INSPECTOR_PROTOCOL_H_ +#define V8_V8_INSPECTOR_PROTOCOL_H_ + +#include "inspector/Debugger.h" // NOLINT(build/include_directory) +#include "inspector/Runtime.h" // NOLINT(build/include_directory) +#include "inspector/Schema.h" // NOLINT(build/include_directory) +#include "v8-inspector.h" // NOLINT(build/include_directory) + +#endif // V8_V8_INSPECTOR_PROTOCOL_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-inspector.h b/packages/android-jsi/platforms/android/cpp/include/v8-inspector.h new file mode 100644 index 000000000..cdd94163e --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-inspector.h @@ -0,0 +1,468 @@ +// Copyright 2016 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_V8_INSPECTOR_H_ +#define V8_V8_INSPECTOR_H_ + +#include + +#include +#include + +#include "cppgc/garbage-collected.h" // NOLINT(build/include_directory) +#include "v8-isolate.h" // NOLINT(build/include_directory) +#include "v8-local-handle.h" // NOLINT(build/include_directory) + +namespace v8 { +class Context; +class Name; +class Object; +class StackTrace; +class Value; +} // namespace v8 + +namespace v8_inspector { + +namespace internal { +class V8DebuggerId; +} // namespace internal + +namespace protocol { +namespace Debugger { +namespace API { +class SearchMatch; +} +} // namespace Debugger +namespace Runtime { +namespace API { +class RemoteObject; +class StackTrace; +class StackTraceId; +} // namespace API +} // namespace Runtime +namespace Schema { +namespace API { +class Domain; +} +} // namespace Schema +} // namespace protocol + +class V8_EXPORT StringView { + public: + StringView() : m_is8Bit(true), m_length(0), m_characters8(nullptr) {} + + StringView(const uint8_t* characters, size_t length) + : m_is8Bit(true), m_length(length), m_characters8(characters) {} + + StringView(const uint16_t* characters, size_t length) + : m_is8Bit(false), m_length(length), m_characters16(characters) {} + + bool is8Bit() const { return m_is8Bit; } + size_t length() const { return m_length; } + + // TODO(dgozman): add DCHECK(m_is8Bit) to accessors once platform can be used + // here. + const uint8_t* characters8() const { return m_characters8; } + const uint16_t* characters16() const { return m_characters16; } + + private: + bool m_is8Bit; + size_t m_length; + union { + const uint8_t* m_characters8; + const uint16_t* m_characters16; + }; +}; + +class V8_EXPORT StringBuffer { + public: + virtual ~StringBuffer() = default; + virtual StringView string() const = 0; + // This method copies contents. + static std::unique_ptr create(StringView); +}; + +class V8_EXPORT V8ContextInfo { + public: + V8ContextInfo(v8::Local context, int contextGroupId, + StringView humanReadableName) + : context(context), + contextGroupId(contextGroupId), + humanReadableName(humanReadableName), + hasMemoryOnConsole(false) {} + + v8::Local context; + // Each v8::Context is a part of a group. The group id must be non-zero. + int contextGroupId; + StringView humanReadableName; + StringView origin; + StringView auxData; + bool hasMemoryOnConsole; + + static int executionContextId(v8::Local context); + + // Disallow copying and allocating this one. + enum NotNullTagEnum { NotNullLiteral }; + void* operator new(size_t) = delete; + void* operator new(size_t, NotNullTagEnum, void*) = delete; + void* operator new(size_t, void*) = delete; + V8ContextInfo(const V8ContextInfo&) = delete; + V8ContextInfo& operator=(const V8ContextInfo&) = delete; +}; + +// This debugger id tries to be unique by generating two random +// numbers, which should most likely avoid collisions. +// Debugger id has a 1:1 mapping to context group. It is used to +// attribute stack traces to a particular debugging, when doing any +// cross-debugger operations (e.g. async step in). +// See also Runtime.UniqueDebuggerId in the protocol. +class V8_EXPORT V8DebuggerId { + public: + V8DebuggerId() = default; + V8DebuggerId(const V8DebuggerId&) = default; + V8DebuggerId& operator=(const V8DebuggerId&) = default; + + std::unique_ptr toString() const; + bool isValid() const; + std::pair pair() const; + + private: + friend class internal::V8DebuggerId; + explicit V8DebuggerId(std::pair); + + int64_t m_first = 0; + int64_t m_second = 0; +}; + +struct V8_EXPORT V8StackFrame { + StringView sourceURL; + StringView functionName; + int lineNumber; + int columnNumber; + int scriptId; +}; + +class V8_EXPORT V8StackTrace { + public: + virtual StringView firstNonEmptySourceURL() const = 0; + virtual bool isEmpty() const = 0; + virtual StringView topSourceURL() const = 0; + virtual int topLineNumber() const = 0; + virtual int topColumnNumber() const = 0; + virtual int topScriptId() const = 0; + virtual StringView topFunctionName() const = 0; + + virtual ~V8StackTrace() = default; + virtual std::unique_ptr + buildInspectorObject(int maxAsyncDepth) const = 0; + virtual std::unique_ptr toString() const = 0; + + // Safe to pass between threads, drops async chain. + virtual std::unique_ptr clone() = 0; + + virtual std::vector frames() const = 0; +}; + +class V8_EXPORT V8InspectorSession { + public: + virtual ~V8InspectorSession() = default; + + // Cross-context inspectable values (DOM nodes in different worlds, etc.). + class V8_EXPORT Inspectable { + public: + virtual v8::Local get(v8::Local) = 0; + virtual ~Inspectable() = default; + }; + virtual void addInspectedObject(std::unique_ptr) = 0; + + // Dispatching protocol messages. + static bool canDispatchMethod(StringView method); + virtual void dispatchProtocolMessage(StringView message) = 0; + virtual std::vector state() = 0; + virtual std::vector> + supportedDomains() = 0; + + // Debugger actions. + virtual void schedulePauseOnNextStatement(StringView breakReason, + StringView breakDetails) = 0; + virtual void cancelPauseOnNextStatement() = 0; + virtual void breakProgram(StringView breakReason, + StringView breakDetails) = 0; + virtual void setSkipAllPauses(bool) = 0; + virtual void resume(bool setTerminateOnResume = false) = 0; + virtual void stepOver() = 0; + virtual std::vector> + searchInTextByLines(StringView text, StringView query, bool caseSensitive, + bool isRegex) = 0; + + // Remote objects. + virtual std::unique_ptr wrapObject( + v8::Local, v8::Local, StringView groupName, + bool generatePreview) = 0; + + virtual bool unwrapObject(std::unique_ptr* error, + StringView objectId, v8::Local*, + v8::Local*, + std::unique_ptr* objectGroup) = 0; + virtual void releaseObjectGroup(StringView) = 0; + virtual void triggerPreciseCoverageDeltaUpdate(StringView occasion) = 0; + + struct V8_EXPORT EvaluateResult { + enum class ResultType { + kNotRun, + kSuccess, + kException, + }; + + ResultType type; + v8::Local value; + }; + // Evalaute 'expression' in the provided context. Does the same as + // Runtime#evaluate under-the-hood but exposed on the C++ side. + virtual EvaluateResult evaluate(v8::Local context, + StringView expression, + bool includeCommandLineAPI = false) = 0; + + // Prepare for shutdown (disables debugger pausing, etc.). + virtual void stop() = 0; +}; + +struct V8_EXPORT DeepSerializedValue { + explicit DeepSerializedValue(std::unique_ptr type, + v8::MaybeLocal value = {}) + : type(std::move(type)), value(value) {} + std::unique_ptr type; + v8::MaybeLocal value; +}; + +struct V8_EXPORT DeepSerializationResult { + explicit DeepSerializationResult( + std::unique_ptr serializedValue) + : serializedValue(std::move(serializedValue)), isSuccess(true) {} + explicit DeepSerializationResult(std::unique_ptr errorMessage) + : errorMessage(std::move(errorMessage)), isSuccess(false) {} + + // Use std::variant when available. + std::unique_ptr serializedValue; + std::unique_ptr errorMessage; + bool isSuccess; +}; + +class V8_EXPORT V8InspectorClient { + public: + virtual ~V8InspectorClient() = default; + + virtual void runMessageLoopOnPause(int contextGroupId) {} + virtual void runMessageLoopOnInstrumentationPause(int contextGroupId) { + runMessageLoopOnPause(contextGroupId); + } + virtual void quitMessageLoopOnPause() {} + virtual void runIfWaitingForDebugger(int contextGroupId) {} + + virtual void muteMetrics(int contextGroupId) {} + virtual void unmuteMetrics(int contextGroupId) {} + + virtual void beginUserGesture() {} + virtual void endUserGesture() {} + + virtual std::unique_ptr deepSerialize( + v8::Local v8Value, int maxDepth, + v8::Local additionalParameters) { + return nullptr; + } + virtual std::unique_ptr valueSubtype(v8::Local) { + return nullptr; + } + virtual std::unique_ptr descriptionForValueSubtype( + v8::Local, v8::Local) { + return nullptr; + } + virtual bool isInspectableHeapObject(v8::Local) { return true; } + + virtual v8::Local ensureDefaultContextInGroup( + int contextGroupId) { + return v8::Local(); + } + virtual void beginEnsureAllContextsInGroup(int contextGroupId) {} + virtual void endEnsureAllContextsInGroup(int contextGroupId) {} + + virtual void installAdditionalCommandLineAPI(v8::Local, + v8::Local) {} + // Deprecated. Use version with contextId. + virtual void consoleAPIMessage(int contextGroupId, + v8::Isolate::MessageErrorLevel level, + const StringView& message, + const StringView& url, unsigned lineNumber, + unsigned columnNumber, V8StackTrace*) {} + virtual void consoleAPIMessage(int contextGroupId, int contextId, + v8::Isolate::MessageErrorLevel level, + const StringView& message, + const StringView& url, unsigned lineNumber, + unsigned columnNumber, + V8StackTrace* stackTrace) { + consoleAPIMessage(contextGroupId, level, message, url, lineNumber, + columnNumber, stackTrace); + } + virtual v8::MaybeLocal memoryInfo(v8::Isolate*, + v8::Local) { + return v8::MaybeLocal(); + } + + virtual void consoleTime(v8::Isolate* isolate, v8::Local label) {} + virtual void consoleTimeEnd(v8::Isolate* isolate, + v8::Local label) {} + virtual void consoleTimeStamp(v8::Isolate* isolate, + v8::Local label) {} + virtual void consoleTimeStampWithArgs( + v8::Isolate* isolate, v8::Local label, + const v8::LocalVector& args) {} + virtual void consoleClear(int contextGroupId) {} + virtual double currentTimeMS() { return 0; } + typedef void (*TimerCallback)(void*); + virtual void startRepeatingTimer(double, TimerCallback, void* data) {} + virtual void cancelTimer(void* data) {} + + // TODO(dgozman): this was added to support service worker shadow page. We + // should not connect at all. + virtual bool canExecuteScripts(int contextGroupId) { return true; } + + virtual void maxAsyncCallStackDepthChanged(int depth) {} + + virtual std::unique_ptr resourceNameToUrl( + const StringView& resourceName) { + return nullptr; + } + + // The caller would defer to generating a random 64 bit integer if + // this method returns 0. + virtual int64_t generateUniqueId() { return 0; } + + virtual void dispatchError(v8::Local, v8::Local, + v8::Local) {} +}; + +// These stack trace ids are intended to be passed between debuggers and be +// resolved later. This allows to track cross-debugger calls and step between +// them if a single client connects to multiple debuggers. +struct V8_EXPORT V8StackTraceId { + uintptr_t id; + std::pair debugger_id; + bool should_pause = false; + + V8StackTraceId(); + V8StackTraceId(const V8StackTraceId&) = default; + V8StackTraceId(uintptr_t id, const std::pair debugger_id); + V8StackTraceId(uintptr_t id, const std::pair debugger_id, + bool should_pause); + explicit V8StackTraceId(StringView); + V8StackTraceId& operator=(const V8StackTraceId&) = default; + V8StackTraceId& operator=(V8StackTraceId&&) noexcept = default; + ~V8StackTraceId() = default; + + bool IsInvalid() const; + std::unique_ptr ToString(); +}; + +class V8_EXPORT V8Inspector { + public: + static std::unique_ptr create(v8::Isolate*, V8InspectorClient*); + virtual ~V8Inspector() = default; + + // Contexts instrumentation. + virtual void contextCreated(const V8ContextInfo&) = 0; + virtual void contextDestroyed(v8::Local) = 0; + virtual void resetContextGroup(int contextGroupId) = 0; + virtual v8::MaybeLocal contextById(int contextId) = 0; + virtual V8DebuggerId uniqueDebuggerId(int contextId) = 0; + virtual uint64_t isolateId() = 0; + + // Various instrumentation. + virtual void idleStarted() = 0; + virtual void idleFinished() = 0; + + // Async stack traces instrumentation. + virtual void asyncTaskScheduled(StringView taskName, void* task, + bool recurring) = 0; + virtual void asyncTaskCanceled(void* task) = 0; + virtual void asyncTaskStarted(void* task) = 0; + virtual void asyncTaskFinished(void* task) = 0; + virtual void allAsyncTasksCanceled() = 0; + + virtual V8StackTraceId storeCurrentStackTrace(StringView description) = 0; + virtual void externalAsyncTaskStarted(const V8StackTraceId& parent) = 0; + virtual void externalAsyncTaskFinished(const V8StackTraceId& parent) = 0; + + // Exceptions instrumentation. + virtual unsigned exceptionThrown(v8::Local, StringView message, + v8::Local exception, + StringView detailedMessage, StringView url, + unsigned lineNumber, unsigned columnNumber, + std::unique_ptr, + int scriptId) = 0; + virtual void exceptionRevoked(v8::Local, unsigned exceptionId, + StringView message) = 0; + virtual bool associateExceptionData(v8::Local, + v8::Local exception, + v8::Local key, + v8::Local value) = 0; + + // Connection. + class V8_EXPORT Channel { + public: + virtual ~Channel() = default; + virtual void sendResponse(int callId, + std::unique_ptr message) = 0; + virtual void sendNotification(std::unique_ptr message) = 0; + virtual void flushProtocolNotifications() = 0; + }; + + class V8_EXPORT ManagedChannel + : public cppgc::GarbageCollected { + public: + virtual ~ManagedChannel() = default; + virtual void sendResponse(int callId, + std::unique_ptr message) = 0; + virtual void sendNotification(std::unique_ptr message) = 0; + virtual void flushProtocolNotifications() = 0; + virtual void Trace(cppgc::Visitor* visitor) const {} + }; + + enum ClientTrustLevel { kUntrusted, kFullyTrusted }; + enum SessionPauseState { kWaitingForDebugger, kNotWaitingForDebugger }; + // TODO(chromium:1352175): remove default value once downstream change lands. + // Deprecated: Use `connectShared` instead. + // Channel is owned by the embedder. Ensure to keep it alive as long as the + // returned session is alive. + virtual std::unique_ptr connect( + int contextGroupId, Channel*, StringView state, + ClientTrustLevel client_trust_level, + SessionPauseState = kNotWaitingForDebugger) = 0; + + // Same as `connect` but returns a std::shared_ptr instead. + // Embedders should not deconstruct V8 sessions while the nested run loop + // (V8InspectorClient::runMessageLoopOnPause) is running. To partially ensure + // this, we defer session deconstruction until no "dispatchProtocolMessages" + // remains on the stack. + // Channel is owned by the embedder. Ensure to keep it alive as long as the + // returned session is alive. + virtual std::shared_ptr connectShared( + int contextGroupId, Channel* channel, StringView state, + ClientTrustLevel clientTrustLevel, SessionPauseState pauseState) = 0; + + // Same as `connectShared` but takes a `ManagedChannel` instead. The session + // will take a cppgc::Persistent on the ManagedChannel so the embedder doesn't + // have to worry about the life-time of `channel`. + virtual std::shared_ptr connectShared( + int contextGroupId, ManagedChannel* channel, StringView state, + ClientTrustLevel clientTrustLevel, SessionPauseState pauseState) = 0; + + // API methods. + virtual std::unique_ptr createStackTrace( + v8::Local) = 0; + virtual std::unique_ptr captureStackTrace(bool fullStack) = 0; +}; + +} // namespace v8_inspector + +#endif // V8_V8_INSPECTOR_H_ diff --git a/packages/android-jsi/platforms/android/cpp/include/v8-internal.h b/packages/android-jsi/platforms/android/cpp/include/v8-internal.h new file mode 100644 index 000000000..a4c21eca7 --- /dev/null +++ b/packages/android-jsi/platforms/android/cpp/include/v8-internal.h @@ -0,0 +1,1942 @@ +// 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 INCLUDE_V8_INTERNAL_H_ +#define INCLUDE_V8_INTERNAL_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "v8config.h" // NOLINT(build/include_directory) + +namespace v8 { + +class Array; +class Context; +class Data; +class Isolate; + +namespace internal { + +class Heap; +class LocalHeap; +class Isolate; +class IsolateGroup; +class LocalIsolate; + +typedef uintptr_t Address; +static constexpr Address kNullAddress = 0; + +constexpr int KB = 1024; +constexpr int MB = KB * 1024; +constexpr int GB = MB * 1024; +#ifdef V8_TARGET_ARCH_X64 +constexpr size_t TB = size_t{GB} * 1024; +#endif + +/** + * Configuration of tagging scheme. + */ +const int kApiSystemPointerSize = sizeof(void*); +const int kApiDoubleSize = sizeof(double); +const int kApiInt32Size = sizeof(int32_t); +const int kApiInt64Size = sizeof(int64_t); +const int kApiSizetSize = sizeof(size_t); + +// Tag information for HeapObject. +const int kHeapObjectTag = 1; +const int kWeakHeapObjectTag = 3; +const int kHeapObjectTagSize = 2; +const intptr_t kHeapObjectTagMask = (1 << kHeapObjectTagSize) - 1; +const intptr_t kHeapObjectReferenceTagMask = 1 << (kHeapObjectTagSize - 1); + +// Tag information for fowarding pointers stored in object headers. +// 0b00 at the lowest 2 bits in the header indicates that the map word is a +// forwarding pointer. +const int kForwardingTag = 0; +const int kForwardingTagSize = 2; +const intptr_t kForwardingTagMask = (1 << kForwardingTagSize) - 1; + +// Tag information for Smi. +const int kSmiTag = 0; +const int kSmiTagSize = 1; +const intptr_t kSmiTagMask = (1 << kSmiTagSize) - 1; + +template +struct SmiTagging; + +constexpr intptr_t kIntptrAllBitsSet = intptr_t{-1}; +constexpr uintptr_t kUintptrAllBitsSet = + static_cast(kIntptrAllBitsSet); + +// Smi constants for systems where tagged pointer is a 32-bit value. +template <> +struct SmiTagging<4> { + enum { kSmiShiftSize = 0, kSmiValueSize = 31 }; + + static constexpr intptr_t kSmiMinValue = + static_cast(kUintptrAllBitsSet << (kSmiValueSize - 1)); + static constexpr intptr_t kSmiMaxValue = -(kSmiMinValue + 1); + + V8_INLINE static constexpr int SmiToInt(Address value) { + int shift_bits = kSmiTagSize + kSmiShiftSize; + // Truncate and shift down (requires >> to be sign extending). + return static_cast(static_cast(value)) >> shift_bits; + } + + template && + std::is_signed_v>* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + // Is value in range [kSmiMinValue, kSmiMaxValue]. + // Use unsigned operations in order to avoid undefined behaviour in case of + // signed integer overflow. + return (static_cast(value) - + static_cast(kSmiMinValue)) <= + (static_cast(kSmiMaxValue) - + static_cast(kSmiMinValue)); + } + + template && + std::is_unsigned_v>* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + static_assert(kSmiMaxValue <= std::numeric_limits::max()); + return value <= static_cast(kSmiMaxValue); + } + + // Same as the `intptr_t` version but works with int64_t on 32-bit builds + // without slowing down anything else. + V8_INLINE static constexpr bool IsValidSmi(int64_t value) { + return (static_cast(value) - + static_cast(kSmiMinValue)) <= + (static_cast(kSmiMaxValue) - + static_cast(kSmiMinValue)); + } + + V8_INLINE static constexpr bool IsValidSmi(uint64_t value) { + static_assert(kSmiMaxValue <= std::numeric_limits::max()); + return value <= static_cast(kSmiMaxValue); + } +}; + +// Smi constants for systems where tagged pointer is a 64-bit value. +template <> +struct SmiTagging<8> { + enum { kSmiShiftSize = 31, kSmiValueSize = 32 }; + + static constexpr intptr_t kSmiMinValue = + static_cast(kUintptrAllBitsSet << (kSmiValueSize - 1)); + static constexpr intptr_t kSmiMaxValue = -(kSmiMinValue + 1); + + V8_INLINE static constexpr int SmiToInt(Address value) { + int shift_bits = kSmiTagSize + kSmiShiftSize; + // Shift down and throw away top 32 bits. + return static_cast(static_cast(value) >> shift_bits); + } + + template && + std::is_signed_v>* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + // To be representable as a long smi, the value must be a 32-bit integer. + return std::numeric_limits::min() <= value && + value <= std::numeric_limits::max(); + } + + template && + std::is_unsigned_v>* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + return value <= std::numeric_limits::max(); + } +}; + +#ifdef V8_COMPRESS_POINTERS +// See v8:7703 or src/common/ptr-compr-inl.h for details about pointer +// compression. +constexpr size_t kPtrComprCageReservationSize = size_t{1} << 32; +constexpr size_t kPtrComprCageBaseAlignment = size_t{1} << 32; + +static_assert( + kApiSystemPointerSize == kApiInt64Size, + "Pointer compression can be enabled only for 64-bit architectures"); +const int kApiTaggedSize = kApiInt32Size; +#else +const int kApiTaggedSize = kApiSystemPointerSize; +#endif + +constexpr bool PointerCompressionIsEnabled() { + return kApiTaggedSize != kApiSystemPointerSize; +} + +#ifdef V8_31BIT_SMIS_ON_64BIT_ARCH +using PlatformSmiTagging = SmiTagging; +#else +using PlatformSmiTagging = SmiTagging; +#endif + +// TODO(ishell): Consinder adding kSmiShiftBits = kSmiShiftSize + kSmiTagSize +// since it's used much more often than the inividual constants. +const int kSmiShiftSize = PlatformSmiTagging::kSmiShiftSize; +const int kSmiValueSize = PlatformSmiTagging::kSmiValueSize; +const int kSmiMinValue = static_cast(PlatformSmiTagging::kSmiMinValue); +const int kSmiMaxValue = static_cast(PlatformSmiTagging::kSmiMaxValue); +constexpr bool SmiValuesAre31Bits() { return kSmiValueSize == 31; } +constexpr bool SmiValuesAre32Bits() { return kSmiValueSize == 32; } +constexpr bool Is64() { return kApiSystemPointerSize == sizeof(int64_t); } + +V8_INLINE static constexpr Address IntToSmi(int value) { + return (static_cast
(value) << (kSmiTagSize + kSmiShiftSize)) | + kSmiTag; +} + +/* + * Sandbox related types, constants, and functions. + */ +constexpr bool SandboxIsEnabled() { +#ifdef V8_ENABLE_SANDBOX + return true; +#else + return false; +#endif +} + +// SandboxedPointers are guaranteed to point into the sandbox. This is achieved +// for example by storing them as offset rather than as raw pointers. +using SandboxedPointer_t = Address; + +#ifdef V8_ENABLE_SANDBOX + +// Size of the sandbox, excluding the guard regions surrounding it. +#if defined(V8_TARGET_OS_ANDROID) +// On Android, most 64-bit devices seem to be configured with only 39 bits of +// virtual address space for userspace. As such, limit the sandbox to 128GB (a +// quarter of the total available address space). +constexpr size_t kSandboxSizeLog2 = 37; // 128 GB +#elif defined(V8_TARGET_OS_IOS) +// On iOS, we only get 64 GB of usable virtual address space even with the +// "jumbo" extended virtual addressing entitlement. Limit the sandbox size to +// 16 GB so that the base address + size for the emulated virtual address space +// lies within the 64 GB total virtual address space. +constexpr size_t kSandboxSizeLog2 = 34; // 16 GB +#elif defined(V8_HOST_ARCH_RISCV64) +// Most RISC-V hardware currently uses Sv39 (39-bit VA, 256GB userspace). +// Limit the sandbox to 128GB (a quarter of Sv39 userspace) to avoid exceeding +// the available virtual address space. Uses V8_HOST_ARCH so that simulator +// builds on x64 are not unnecessarily constrained. +constexpr size_t kSandboxSizeLog2 = 37; // 128 GB +#elif defined(V8_TARGET_ARCH_LOONG64) +// Some hardwares like 2k3000 only have 40-bit virtual address space, 39 bits +// userspace and kernel each. +constexpr size_t kSandboxSizeLog2 = 37; // 128 GB +#else +// Everywhere else use a 1TB sandbox. +constexpr size_t kSandboxSizeLog2 = 40; // 1 TB +#endif // V8_TARGET_OS_ANDROID +constexpr size_t kSandboxSize = 1ULL << kSandboxSizeLog2; + +// Required alignment of the sandbox. For simplicity, we require the +// size of the guard regions to be a multiple of this, so that this specifies +// the alignment of the sandbox including and excluding surrounding guard +// regions. The alignment requirement is due to the pointer compression cage +// being located at the start of the sandbox. +constexpr size_t kSandboxAlignment = kPtrComprCageBaseAlignment; + +// Sandboxed pointers are stored inside the heap as offset from the sandbox +// base shifted to the left. This way, it is guaranteed that the offset is +// smaller than the sandbox size after shifting it to the right again. This +// constant specifies the shift amount. +constexpr uint64_t kSandboxedPointerShift = 64 - kSandboxSizeLog2; + +// On OSes where reserving virtual memory is too expensive to reserve the +// entire address space backing the sandbox, notably Windows pre 8.1, we create +// a partially reserved sandbox that doesn't actually reserve most of the +// memory, and so doesn't have the desired security properties as unrelated +// memory allocations could end up inside of it, but which still ensures that +// objects that should be located inside the sandbox are allocated within +// kSandboxSize bytes from the start of the sandbox. The minimum size of the +// region that is actually reserved for such a sandbox is specified by this +// constant and should be big enough to contain the pointer compression cage as +// well as the ArrayBuffer partition. +constexpr size_t kSandboxMinimumReservationSize = 8ULL * GB; + +static_assert(kSandboxMinimumReservationSize > kPtrComprCageReservationSize, + "The minimum reservation size for a sandbox must be larger than " + "the pointer compression cage contained within it."); + +// The maximum buffer size allowed inside the sandbox. This is mostly dependent +// on the size of the guard regions around the sandbox: an attacker must not be +// able to construct a buffer that appears larger than the guard regions and +// thereby "reach out of" the sandbox. +constexpr size_t kMaxSafeBufferSizeForSandbox = 32ULL * GB - 1; + +constexpr size_t kBoundedSizeShift = 29; +static_assert(1ULL << (64 - kBoundedSizeShift) == + kMaxSafeBufferSizeForSandbox + 1, + "The maximum size of a BoundedSize must be synchronized with the " + "kMaxSafeBufferSizeForSandbox"); +constexpr size_t kBoundedSizeMask = (1ULL << (64 - kBoundedSizeShift)) - 1; + +// Size of the guard regions surrounding the sandbox. This assumes a worst-case +// scenario of a 32-bit unsigned index used to access an array of 64-bit values +// with an additional 32GB (bounded size) offset. In particular, accesses to +// TypedArrays are effectively computed as +// `entry_pointer = array->base + array->offset + index * array->element_size`. +// See also https://crbug.com/40070746 for more details. +constexpr size_t kSandboxGuardRegionSize = + 32ULL * GB + (kMaxSafeBufferSizeForSandbox + 1); + +static_assert((kSandboxGuardRegionSize % kSandboxAlignment) == 0, + "The size of the guard regions around the sandbox must be a " + "multiple of its required alignment."); +static_assert(kMaxSafeBufferSizeForSandbox <= kSandboxGuardRegionSize, + "The maximum allowed buffer size must not be larger than the " + "sandbox's guard regions"); + +#if defined(V8_TARGET_OS_ANDROID) +// On Android, we often won't have sufficient virtual address space available. +constexpr size_t kAdditionalTrailingGuardRegionSize = 0; +#else +// Worst-case, we need 8 (max element size) * 32GB (max ArrayBuffer size) + +// 32GB (additional bounded size offset for TypedArray access). +constexpr size_t kAdditionalTrailingGuardRegionSize = + 288ULL * GB - kSandboxGuardRegionSize; +#endif + +constexpr bool kRequiresTypedArrayAccessMasks = + kAdditionalTrailingGuardRegionSize == 0; + +#endif // V8_ENABLE_SANDBOX + +#ifdef V8_COMPRESS_POINTERS + +#ifdef V8_TARGET_OS_ANDROID +// The size of the virtual memory reservation for an external pointer table. +// This determines the maximum number of entries in a table. Using a maximum +// size allows omitting bounds checks on table accesses if the indices are +// guaranteed (e.g. through shifting) to be below the maximum index. This +// value must be a power of two. +constexpr size_t kExternalPointerTableReservationSize = 256 * MB; + +// The external pointer table indices stored in HeapObjects as external +// pointers are shifted to the left by this amount to guarantee that they are +// smaller than the maximum table size even after the C++ compiler multiplies +// them by 8 to be used as indexes into a table of 64 bit pointers. +constexpr uint32_t kExternalPointerIndexShift = 7; +#elif defined(V8_TARGET_OS_IOS) +// iOS restricts large memory allocations, with 128 MB being the maximum size we +// can configure. If we exceed this, SegmentedTable::Initialize will throw a V8 +// out-of-memory error when running the JetStream benchmark +// (https://browserbench.org/JetStream/). +constexpr size_t kExternalPointerTableReservationSize = 128 * MB; +constexpr uint32_t kExternalPointerIndexShift = 8; +#else +constexpr size_t kExternalPointerTableReservationSize = 512 * MB; +constexpr uint32_t kExternalPointerIndexShift = 6; +#endif // V8_TARGET_OS_ANDROID + +// The byte size of an entry in an external pointer table. +constexpr int kExternalPointerTableEntrySize = 8; +constexpr int kExternalPointerTableEntrySizeLog2 = 3; +// The maximum number of entries in an external pointer table. +constexpr size_t kMaxExternalPointers = + kExternalPointerTableReservationSize / kExternalPointerTableEntrySize; +static_assert((1 << (32 - kExternalPointerIndexShift)) == kMaxExternalPointers, + "kExternalPointerTableReservationSize and " + "kExternalPointerIndexShift don't match"); + +#else // !V8_COMPRESS_POINTERS + +// Needed for the V8.SandboxedExternalPointersCount histogram. +constexpr size_t kMaxExternalPointers = 0; + +#endif // V8_COMPRESS_POINTERS + +constexpr uint64_t kExternalPointerMarkBit = 1ULL << 48; +constexpr uint64_t kExternalPointerTagShift = 49; +constexpr uint64_t kExternalPointerTagMask = 0x00fe000000000000ULL; +constexpr uint64_t kExternalPointerShiftedTagMask = + kExternalPointerTagMask >> kExternalPointerTagShift; +static_assert(kExternalPointerShiftedTagMask << kExternalPointerTagShift == + kExternalPointerTagMask); +constexpr uint64_t kExternalPointerTagAndMarkbitMask = 0x00ff000000000000ULL; +constexpr uint64_t kExternalPointerPayloadMask = 0xff00ffffffffffffULL; + +// A ExternalPointerHandle represents a (opaque) reference to an external +// pointer that can be stored inside the sandbox. A ExternalPointerHandle has +// meaning only in combination with an (active) Isolate as it references an +// external pointer stored in the currently active Isolate's +// ExternalPointerTable. Internally, an ExternalPointerHandles is simply an +// index into an ExternalPointerTable that is shifted to the left to guarantee +// that it is smaller than the size of the table. +using ExternalPointerHandle = uint32_t; + +// ExternalPointers point to objects located outside the sandbox. When the V8 +// sandbox is enabled, these are stored on heap as ExternalPointerHandles, +// otherwise they are simply raw pointers. +#ifdef V8_ENABLE_SANDBOX +using ExternalPointer_t = ExternalPointerHandle; +#else +using ExternalPointer_t = Address; +#endif + +constexpr ExternalPointer_t kNullExternalPointer = 0; +constexpr ExternalPointerHandle kNullExternalPointerHandle = 0; + +// See `ExternalPointerHandle` for the main documentation. The difference to +// `ExternalPointerHandle` is that the handle does not represent an arbitrary +// external pointer but always refers to an object managed by `CppHeap`. The +// handles are using in combination with a dedicated table for `CppHeap` +// references. +using CppHeapPointerHandle = uint32_t; + +// The actual pointer to objects located on the `CppHeap`. When pointer +// compression is enabled these pointers are stored as `CppHeapPointerHandle`. +// In non-compressed configurations the pointers are simply stored as raw +// pointers. +#ifdef V8_COMPRESS_POINTERS +using CppHeapPointer_t = CppHeapPointerHandle; +#else +using CppHeapPointer_t = Address; +#endif + +constexpr CppHeapPointer_t kNullCppHeapPointer = 0; +constexpr CppHeapPointerHandle kNullCppHeapPointerHandle = 0; + +constexpr uint64_t kCppHeapPointerMarkBit = 1ULL; +constexpr uint64_t kCppHeapPointerTagShift = 1; +constexpr uint64_t kCppHeapPointerPayloadShift = 16; + +#ifdef V8_COMPRESS_POINTERS +// CppHeapPointers use a dedicated pointer table. These constants control the +// size and layout of the table. See the corresponding constants for the +// external pointer table for further details. +constexpr size_t kCppHeapPointerTableReservationSize = + kExternalPointerTableReservationSize; +constexpr uint32_t kCppHeapPointerIndexShift = kExternalPointerIndexShift; + +constexpr int kCppHeapPointerTableEntrySize = 8; +constexpr int kCppHeapPointerTableEntrySizeLog2 = 3; +constexpr size_t kMaxCppHeapPointers = + kCppHeapPointerTableReservationSize / kCppHeapPointerTableEntrySize; +static_assert((1 << (32 - kCppHeapPointerIndexShift)) == kMaxCppHeapPointers, + "kCppHeapPointerTableReservationSize and " + "kCppHeapPointerIndexShift don't match"); + +#else // !V8_COMPRESS_POINTERS + +// Needed for the V8.SandboxedCppHeapPointersCount histogram. +constexpr size_t kMaxCppHeapPointers = 0; + +#endif // V8_COMPRESS_POINTERS + +// The number of tags reserved for embedder data stored in internal fields. The +// value is picked arbitrarily, and is slightly larger than the number of tags +// currently used in Chrome. +#define V8_EMBEDDER_DATA_TAG_COUNT 15 + +// The number of tags reserved for pointers stored in v8::External. The value is +// picked arbitrarily, and is slightly larger than the number of tags currently +// used in Chrome. +#define V8_EXTERNAL_POINTER_TAG_COUNT 40 + +// Generic tag range struct to represent ranges of type tags. +// +// When referencing external objects via pointer tables, type tags are +// frequently necessary to guarantee type safety for the external objects. When +// support for subtyping is necessary, range-based type checks are used in +// which all subtypes of a given supertype use contiguous tags. This struct can +// then be used to represent such a type range. +// +// As an example, consider the following type hierarchy: +// +// A F +// / \ +// B E +// / \ +// C D +// +// A potential type id assignment for range-based type checks is +// {A: 0, B: 1, C: 2, D: 3, E: 4, F: 5}. With that, the type check for type A +// would check for the range [A, E], while the check for B would check range +// [B, D], and for F it would simply check [F, F]. +// +// In addition, there is an option for performance tweaks: if the size of the +// type range corresponding to a supertype is a power of two and starts at a +// power of two (e.g. [0x100, 0x13f]), then the compiler can often optimize +// the type check to use even fewer instructions (essentially replace a AND + +// SUB with a single AND). +// +// Tag ranges can also to a limited degree be used for union types. For +// example, with the type graph as above, it would be possible to specify a +// Union(D, E, F) as the tag range [D, F]. However, this only works as long as +// the (otherwise independent) types that form the union have adjacent tags. +// +// +// There are broadly speaking two options for performing the type check when +// given the expected type range and the actual tag of the entry. +// +// The first option is to simply have the equivalent of +// +// CHECK(expected_tag_range.Contains(actual_tag)) +// +// This is nice and simple, and friendly to both the branch-predictor and the +// user/developer as it produces clear error messages. However, this approach +// may result in quite a bit of code being generated, for example for calling +// RuntimeAbort from generated code or similar. +// +// The second option is to generate code such as +// +// if (!expected_tag_range.Contains(actual_tag)) return nullptr; +// +// With this, we are also guaranteed to crash safely when the returned pointer +// is used, but this may result in significantly less code being generated, for +// example because the compiler can implement this with a single conditional +// select in combination with the zero register (e.g. on Arm). +// +// The choice of which approach to use therefore depends on the use case, the +// performance and code size constraints, and the importance of debuggability. +template +struct TagRange { + static_assert(std::is_enum_v && + std::is_same_v, uint16_t>, + "Tag parameter must be an enum with base type uint16_t"); + + // Construct the inclusive tag range [first, last]. + constexpr TagRange(Tag first, Tag last) : first(first), last(last) { +#ifdef V8_ENABLE_CHECKS + // This would typically be a DCHECK, but that's not available here. + if (first > last) __builtin_unreachable(); // Invalid tag range. +#endif + } + + // Construct a tag range consisting of a single tag. + // + // A single tag is always implicitly convertible to a tag range. This greatly + // increases readability as most of the time, the exact tag of a field is + // known and so no tag range needs to explicitly be created for it. + constexpr TagRange(Tag tag) // NOLINT(runtime/explicit) + : first(tag), last(tag) {} + + // Construct an empty tag range. + constexpr TagRange() : TagRange(static_cast(0)) {} + + // A tag range is considered empty if it only contains the null tag. + constexpr bool IsEmpty() const { return first == 0 && last == 0; } + + constexpr size_t Size() const { + if (IsEmpty()) { + return 0; + } else { + return last - first + 1; + } + } + + constexpr bool Contains(Tag tag) const { + // Need to perform the math with uint32_t. Otherwise, the uint16_ts would + // be promoted to (signed) int, allowing the compiler to (wrongly) assume + // that an underflow cannot happen as that would be undefined behavior. + return static_cast(tag) - static_cast(first) <= + static_cast(last) - static_cast(first); + } + + constexpr bool Contains(TagRange tag_range) const { + return tag_range.first >= first && tag_range.last <= last; + } + + constexpr bool operator==(const TagRange other) const { + return first == other.first && last == other.last; + } + + constexpr size_t hash_value() const { + static_assert(std::is_same_v, uint16_t>); + return (static_cast(first) << 16) | last; + } + + // Internally we represent tag ranges as closed ranges [first, last]. + Tag first; + Tag last; +}; + +#define SHARED_MANAGED_TAG_LIST(V) V(WasmFutexManagedObjectWaitListTag) + +#define MANAGED_TAG_LIST(V) \ + SHARED_MANAGED_TAG_LIST(V) \ + V(GenericManagedTag) \ + V(WasmWasmStreamingTag) \ + V(WasmFuncDataTag) \ + V(WasmManagedDataTag) \ + V(WasmNativeModuleTag) \ + V(BackingStoreTag) \ + V(CFunctionWithSignatureTag) \ + V(IcuBreakIteratorTag) \ + V(IcuListFormatterTag) \ + V(IcuLocaleTag) \ + V(IcuSimpleDateFormatTag) \ + V(IcuDateIntervalFormatTag) \ + V(IcuRelativeDateTimeFormatterTag) \ + V(IcuLocalizedNumberFormatterTag) \ + V(IcuPluralRulesTag) \ + V(IcuCollatorTag) \ + V(IcuBreakIteratorWithTextTag) \ + V(TemporalDurationTag) \ + V(TemporalInstantTag) \ + V(TemporalPlainDateTag) \ + V(TemporalPlainTimeTag) \ + V(TemporalPlainDateTimeTag) \ + V(TemporalPlainYearMonthTag) \ + V(TemporalPlainMonthDayTag) \ + V(TemporalZonedDateTimeTag) \ + V(DisplayNamesInternalTag) \ + V(D8WorkerTag) \ + V(D8ModuleEmbedderDataTag) + +#define FOREIGN_TAG_LIST(V) \ + V(GenericForeignTag) \ + V(ApiAccessCheckCallbackTag) \ + V(ApiAbortScriptExecutionCallbackTag) \ + V(SyntheticModuleTag) \ + V(MicrotaskCallbackTag) \ + V(MicrotaskCallbackDataTag) \ + V(MessageListenerTag) \ + V(WaiterQueueForeignTag) \ + /* Needs to stay last to form a range for resources. */ \ + MANAGED_TAG_LIST(V) + +// +// External Pointers. +// +// When the sandbox is enabled, external pointers are stored in an external +// pointer table and are referenced from HeapObjects through an index (a +// "handle"). When stored in the table, the pointers are tagged with per-type +// tags to prevent type confusion attacks between different external objects. +// +// When loading an external pointer, a range of allowed tags can be specified. +// This way, type hierarchies can be supported. The main requirement for that +// is that all (transitive) child classes of a given parent class have type ids +// in the same range, and that there are no unrelated types in that range. For +// more details about how to assign type tags to types, see the TagRange class. +// +// The external pointer sandboxing mechanism ensures that every access to an +// external pointer field will result in a valid pointer of the expected type +// even in the presence of an attacker able to corrupt memory inside the +// sandbox. However, if any data related to the external object is stored +// inside the sandbox it may still be corrupted and so must be validated before +// use or moved into the external object. Further, an attacker will always be +// able to substitute different external pointers of the same type for each +// other. Therefore, code using external pointers must be written in a +// "substitution-safe" way, i.e. it must always be possible to substitute +// external pointers of the same type without causing memory corruption outside +// of the sandbox. Generally this is achieved by referencing any group of +// related external objects through a single external pointer. +// +// Currently we use bit 62 for the marking bit which should always be unused as +// it's part of the non-canonical address range. When Arm's top-byte ignore +// (TBI) is enabled, this bit will be part of the ignored byte, and we assume +// that the Embedder is not using this byte (really only this one bit) for any +// other purpose. This bit also does not collide with the memory tagging +// extension (MTE) which would use bits [56, 60). +// +// External pointer tables are also available even when the sandbox is off but +// pointer compression is on. In that case, the mechanism can be used to ease +// alignment requirements as it turns unaligned 64-bit raw pointers into +// aligned 32-bit indices. To "opt-in" to the external pointer table mechanism +// for this purpose, instead of using the ExternalPointer accessors one needs to +// use ExternalPointerHandles directly and use them to access the pointers in an +// ExternalPointerTable. +// +// The tag is currently in practice limited to 15 bits since it needs to fit +// together with a marking bit into the unused parts of a pointer. +enum ExternalPointerTag : uint16_t { + kFirstExternalPointerTag = 0, + kExternalPointerNullTag = 0, + + // When adding new tags, please ensure that the code using these tags is + // "substitution-safe", i.e. still operate safely if external pointers of the + // same type are swapped by an attacker. See comment above for more details. + + // Shared external pointers are owned by the shared Isolate and stored in the + // shared external pointer table associated with that Isolate, where they can + // be accessed from multiple threads at the same time. The objects referenced + // in this way must therefore always be thread-safe. + kFirstSharedExternalPointerTag, + kWaiterQueueNodeTag = kFirstSharedExternalPointerTag, + kExternalStringResourceTag, + kExternalStringResourceDataTag, + kLastSharedExternalPointerTag = kExternalStringResourceDataTag, + + // External pointers using these tags are kept in a per-Isolate external + // pointer table and can only be accessed when this Isolate is active. + kNativeContextMicrotaskQueueTag, + + // Placeholders for embedder data. + kFirstEmbedderDataTag, + kLastEmbedderDataTag = kFirstEmbedderDataTag + V8_EMBEDDER_DATA_TAG_COUNT - 1, + + // Placeholders for pointers store in v8::External. + kFirstExternalTypeTag, + kLastExternalTypeTag = + kFirstExternalTypeTag + V8_EXTERNAL_POINTER_TAG_COUNT - 1, + // This tag is used when a fast-api callback as a parameter of type + // `kPointer`. The V8 fast API is only able to use this generic tag, and is + // therefore not supposed to be used in Chrome. + kFastApiExternalTypeTag = kLastExternalTypeTag, + kFirstMaybeReadOnlyExternalPointerTag, + kFunctionTemplateInfoCallbackTag = kFirstMaybeReadOnlyExternalPointerTag, + kAccessorInfoGetterTag, + kAccessorInfoSetterTag, + + // InterceptorInfo external pointers. + kFirstInterceptorInfoExternalPointerTag, + kApiNamedPropertyQueryCallbackTag = kFirstInterceptorInfoExternalPointerTag, + kApiNamedPropertyGetterCallbackTag, + kApiNamedPropertySetterCallbackTag, + kApiNamedPropertyDescriptorCallbackTag, + kApiNamedPropertyDefinerCallbackTag, + kApiNamedPropertyDeleterCallbackTag, + kApiNamedPropertyEnumeratorCallbackTag, + kApiIndexedPropertyQueryCallbackTag, + kApiIndexedPropertyGetterCallbackTag, + kApiIndexedPropertySetterCallbackTag, + kApiIndexedPropertyDescriptorCallbackTag, + kApiIndexedPropertyDefinerCallbackTag, + kApiIndexedPropertyDeleterCallbackTag, + kApiIndexedPropertyEnumeratorCallbackTag, + kApiIndexedPropertyIndexOfCallbackTag, + kLastInterceptorInfoExternalPointerTag = + kApiIndexedPropertyIndexOfCallbackTag, + + kLastMaybeReadOnlyExternalPointerTag = kLastInterceptorInfoExternalPointerTag, + + kWasmStackMemoryTag, + +#define AS_ENUM(name) k##name, + FOREIGN_TAG_LIST(AS_ENUM) + +#undef AS_ENUM + + // External resources whose lifetime is tied to their entry in the + // external pointer table but which are not referenced via a Managed + kArrayBufferExtensionTag, + kLastManagedResourceTag = kArrayBufferExtensionTag, + + kExternalPointerZappedEntryTag = 0x7d, + kExternalPointerEvacuationEntryTag = 0x7e, + kExternalPointerFreeEntryTag = 0x7f, + // The tags are limited to 7 bits, so the last tag is 0x7f. + kLastExternalPointerTag = 0x7f, +}; + +constexpr const char* ToString(ExternalPointerTag tag) { + switch (tag) { +#define ENUM_CASE(name) \ + case ExternalPointerTag::k##name: \ + return #name; + + FOREIGN_TAG_LIST(ENUM_CASE) + +#undef ENUM_CASE + default: + return "Unknown tag"; + } +} + +using ExternalPointerTagRange = TagRange; + +#define AS_LIST(name) ExternalPointerTag::k##name, + +#define GET_FIRST(LIST) \ + []() { \ + ExternalPointerTag items[] = {LIST(AS_LIST)}; \ + return items[0]; \ + }() + +#define GET_LAST(LIST) \ + []() { \ + ExternalPointerTag items[] = {LIST(AS_LIST)}; \ + return items[(sizeof(items) / sizeof(items[0])) - 1]; \ + }() + +constexpr ExternalPointerTagRange kAnyExternalPointerTagRange( + kFirstExternalPointerTag, kLastExternalPointerTag); + +constexpr ExternalPointerTag kFirstForeignExternalPointerTag = + GET_FIRST(FOREIGN_TAG_LIST); +constexpr ExternalPointerTag kLastForeignExternalPointerTag = + GET_LAST(FOREIGN_TAG_LIST); +constexpr ExternalPointerTagRange kAnyForeignExternalPointerTagRange( + kFirstForeignExternalPointerTag, kLastForeignExternalPointerTag); +constexpr ExternalPointerTagRange kAnyInterceptorInfoExternalPointerTagRange( + kFirstInterceptorInfoExternalPointerTag, + kLastInterceptorInfoExternalPointerTag); + +constexpr ExternalPointerTag kFirstManagedExternalPointerTag = + GET_FIRST(MANAGED_TAG_LIST); +constexpr ExternalPointerTag kLastManagedExternalPointerTag = + GET_LAST(MANAGED_TAG_LIST); +constexpr ExternalPointerTagRange kAnyManagedExternalPointerTagRange( + kFirstManagedExternalPointerTag, kLastManagedExternalPointerTag); + +constexpr ExternalPointerTagRange kAnyMaybeReadOnlyExternalPointerTagRange( + kFirstMaybeReadOnlyExternalPointerTag, + kLastMaybeReadOnlyExternalPointerTag); + +constexpr ExternalPointerTag kFirstManagedResourceTag = + GET_FIRST(MANAGED_TAG_LIST); +// kLastManagedResourceTag defined in the enum. +constexpr ExternalPointerTagRange kAnyManagedResourceExternalPointerTag( + kFirstManagedResourceTag, kLastManagedResourceTag); + +constexpr ExternalPointerTag kFirstSharedManagedExternalPointerTag = + GET_FIRST(SHARED_MANAGED_TAG_LIST); +constexpr ExternalPointerTag kLastSharedManagedExternalPointerTag = + GET_LAST(SHARED_MANAGED_TAG_LIST); +constexpr ExternalPointerTagRange kAnySharedManagedExternalPointerTagRange( + kFirstSharedManagedExternalPointerTag, + kLastSharedManagedExternalPointerTag); + +#undef AS_LIST +#undef GET_FIRST +#undef GET_LAST + +// True if the external pointer must be accessed from the shared isolate's +// external pointer table. +V8_INLINE static constexpr bool IsSharedExternalPointerType( + ExternalPointerTagRange tag_range) { + // This range should only be used together with + // kAnySharedManagedExternalPointerTagRange in this predicate. Therefore + // it is defined in this scope. + constexpr ExternalPointerTagRange kAnySharedExternalPointerTagRange( + kFirstSharedExternalPointerTag, kLastSharedExternalPointerTag); + return kAnySharedExternalPointerTagRange.Contains(tag_range) || + kAnySharedManagedExternalPointerTagRange.Contains(tag_range); +} + +// True if the external pointer may live in a read-only object, in which case +// the table entry will be in the shared read-only segment of the external +// pointer table. +V8_INLINE static constexpr bool IsMaybeReadOnlyExternalPointerType( + ExternalPointerTagRange tag_range) { + return kAnyMaybeReadOnlyExternalPointerTagRange.Contains(tag_range); +} + +// True if the external pointer references an external object whose lifetime is +// tied to the entry in the external pointer table. +// In this case, the entry in the ExternalPointerTable always points to an +// object derived from ExternalPointerTable::ManagedResource. +V8_INLINE static constexpr bool IsManagedExternalPointerType( + ExternalPointerTagRange tag_range) { + return kAnyManagedResourceExternalPointerTag.Contains(tag_range); +} + +// When an external poiner field can contain the null external pointer handle, +// the type checking mechanism needs to also check for null. +// TODO(saelo): this is mostly a temporary workaround to introduce range-based +// type checks. In the future, we should either (a) change the type tagging +// scheme so that null always passes or (b) (more likely) introduce dedicated +// null entries for those tags that need them (similar to other well-known +// empty value constants such as the empty fixed array). +V8_INLINE static constexpr bool ExternalPointerCanBeEmpty( + ExternalPointerTagRange tag_range) { + return tag_range.Contains(kArrayBufferExtensionTag) || + (tag_range.first <= kLastEmbedderDataTag && + kFirstEmbedderDataTag <= tag_range.last) || + kAnyInterceptorInfoExternalPointerTagRange.Contains(tag_range); +} + +// Indirect Pointers. +// +// When the sandbox is enabled, indirect pointers are used to reference +// HeapObjects that live outside of the sandbox (but are still managed by V8's +// garbage collector). When object A references an object B through an indirect +// pointer, object A will contain a IndirectPointerHandle, i.e. a shifted +// 32-bit index, which identifies an entry in a pointer table (either the +// trusted pointer table for TrustedObjects, or the code pointer table if it is +// a Code object). This table entry then contains the actual pointer to object +// B. Further, object B owns this pointer table entry, and it is responsible +// for updating the "self-pointer" in the entry when it is relocated in memory. +// This way, in contrast to "normal" pointers, indirect pointers never need to +// be tracked by the GC (i.e. there is no remembered set for them). +// These pointers do not exist when the sandbox is disabled. + +// An IndirectPointerHandle represents a 32-bit index into a pointer table. +using IndirectPointerHandle = uint32_t; + +// A null handle always references an entry that contains nullptr. +constexpr IndirectPointerHandle kNullIndirectPointerHandle = 0; + +// When the sandbox is enabled, indirect pointers are used to implement: +// - TrustedPointers: an indirect pointer using the trusted pointer table (TPT) +// and referencing a TrustedObject in one of the trusted heap spaces. +// - CodePointers, an indirect pointer using the code pointer table (CPT) and +// referencing a Code object together with its instruction stream. + +// +// Trusted Pointers. +// +// A pointer to a TrustedObject. +// When the sandbox is enabled, these are indirect pointers using the trusted +// pointer table (TPT). They are used to reference trusted objects (located in +// one of V8's trusted heap spaces, outside of the sandbox) from inside the +// sandbox in a memory-safe way. When the sandbox is disabled, these are +// regular tagged pointers. +using TrustedPointerHandle = IndirectPointerHandle; + +// The size of the virtual memory reservation for the trusted pointer table. +// As with the external pointer table, a maximum table size in combination with +// shifted indices allows omitting bounds checks. +constexpr size_t kTrustedPointerTableReservationSize = 64 * MB; + +// The trusted pointer handles are stored shifted to the left by this amount +// to guarantee that they are smaller than the maximum table size. +constexpr uint32_t kTrustedPointerHandleShift = 9; + +// A null handle always references an entry that contains nullptr. +constexpr TrustedPointerHandle kNullTrustedPointerHandle = + kNullIndirectPointerHandle; + +// The byte size of an entry in the trusted pointer table. +constexpr int kTrustedPointerTableEntrySize = 8; +constexpr int kTrustedPointerTableEntrySizeLog2 = 3; +// The maximum number of entries in the trusted pointer table. +constexpr size_t kMaxTrustedPointers = + kTrustedPointerTableReservationSize / kTrustedPointerTableEntrySize; +static_assert((1 << (32 - kTrustedPointerHandleShift)) == kMaxTrustedPointers, + "kTrustedPointerTableReservationSize and " + "kTrustedPointerHandleShift don't match"); + +// +// Code Pointers. +// +// A pointer to a Code object. +// Essentially a specialized version of a trusted pointer that (when the +// sandbox is enabled) uses the code pointer table (CPT) instead of the TPT. +// Each entry in the CPT contains both a pointer to a Code object as well as a +// pointer to the Code's entrypoint. This allows calling/jumping into Code with +// one fewer memory access (compared to the case where the entrypoint pointer +// first needs to be loaded from the Code object). As such, a CodePointerHandle +// can be used both to obtain the referenced Code object and to directly load +// its entrypoint. +// +// When the sandbox is disabled, these are regular tagged pointers. +// +// TODO(498510170): Removing these explicit code pointer handles is work in +// progress. +using CodePointerHandle = IndirectPointerHandle; + +// The size of the virtual memory reservation for the code pointer table. +// As with the other tables, a maximum table size in combination with shifted +// indices allows omitting bounds checks. +constexpr size_t kCodePointerTableReservationSize = 128 * MB; + +// Code pointer handles are shifted by a different amount than indirect pointer +// handles as the tables have a different maximum size. +constexpr uint32_t kCodePointerHandleShift = 8; + +// A null handle always references an entry that contains nullptr. +constexpr CodePointerHandle kNullCodePointerHandle = kNullIndirectPointerHandle; + +// It can sometimes be necessary to distinguish a code pointer handle from a +// trusted pointer handle. A typical example would be a union trusted pointer +// field that can refer to both Code objects and other trusted objects. To +// support these use-cases, we use a simple marking scheme where some of the +// low bits of a code pointer handle are set, while they will be unset on a +// trusted pointer handle. This way, the correct table to resolve the handle +// can be determined even in the absence of a type tag. +constexpr uint32_t kCodePointerHandleMarker = 0x1; +static_assert(kCodePointerHandleShift > 0); +static_assert(kTrustedPointerHandleShift > 0); + +// The byte size of an entry in a code pointer table. +constexpr int kCodePointerTableEntrySize = 8; +constexpr int kCodePointerTableEntrySizeLog2 = 3; +// The maximum number of entries in a code pointer table. +constexpr size_t kMaxCodePointers = + kCodePointerTableReservationSize / kCodePointerTableEntrySize; +static_assert( + (1 << (32 - kCodePointerHandleShift)) == kMaxCodePointers, + "kCodePointerTableReservationSize and kCodePointerHandleShift don't match"); + +constexpr int kCodePointerTableEntryCodeObjectOffset = 0; + +// Constants that can be used to mark places that should be modified once +// certain types of objects are moved out of the sandbox and into trusted space. +constexpr bool kRuntimeGeneratedCodeObjectsLiveInTrustedSpace = true; +constexpr bool kBuiltinCodeObjectsLiveInTrustedSpace = false; +constexpr bool kAllCodeObjectsLiveInTrustedSpace = + kRuntimeGeneratedCodeObjectsLiveInTrustedSpace && + kBuiltinCodeObjectsLiveInTrustedSpace; + +// {obj} must be the raw tagged pointer representation of a HeapObject +// that's guaranteed to never be in ReadOnlySpace. +V8_DEPRECATE_SOON( + "Use GetCurrentIsolate() instead, which is guaranteed to return the same " + "isolate since https://crrev.com/c/6458560.") +V8_EXPORT internal::Isolate* IsolateFromNeverReadOnlySpaceObject(Address obj); + +// Returns if we need to throw when an error occurs. This infers the language +// mode based on the current context and the closure. This returns true if the +// language mode is strict. +V8_EXPORT bool ShouldThrowOnError(internal::Isolate* isolate); + +struct HandleScopeData final { + static constexpr uint32_t kSizeInBytes = + 2 * kApiSystemPointerSize + 2 * kApiInt32Size; + + Address* next; + Address* limit; + int level; + int sealed_level; + + void Initialize() { + next = limit = nullptr; + sealed_level = level = 0; + } +}; + +static_assert(HandleScopeData::kSizeInBytes == sizeof(HandleScopeData)); + +/** + * This class exports constants and functionality from within v8 that + * is necessary to implement inline functions in the v8 api. Don't + * depend on functions and constants defined here. + */ +class Internals { +#ifdef V8_MAP_PACKING + V8_INLINE static constexpr Address UnpackMapWord(Address mapword) { + // TODO(wenyuzhao): Clear header metadata. + return mapword ^ kMapWordXorMask; + } +#endif + + public: + // These values match non-compiler-dependent values defined within + // the implementation of v8. + static const int kHeapObjectMapOffset = 0; + static const int kMapInstanceTypeOffset = 1 * kApiTaggedSize + kApiInt32Size; + static const int kStringResourceOffset = + 1 * kApiTaggedSize + 2 * kApiInt32Size; + + static const int kOddballKindOffset = 4 * kApiTaggedSize + kApiDoubleSize; + static const int kJSObjectHeaderSize = 3 * kApiTaggedSize; +#ifdef V8_COMPRESS_POINTERS + static const int kJSAPIObjectWithEmbedderSlotsHeaderSize = + kJSObjectHeaderSize + kApiInt32Size; +#else // !V8_COMPRESS_POINTERS + static const int kJSAPIObjectWithEmbedderSlotsHeaderSize = + kJSObjectHeaderSize + kApiTaggedSize; +#endif // !V8_COMPRESS_POINTERS + static const int kFixedArrayHeaderSize = 2 * kApiTaggedSize; + static const int kEmbedderDataArrayHeaderSize = 2 * kApiTaggedSize; + static const int kEmbedderDataSlotSize = kApiSystemPointerSize; +#ifdef V8_ENABLE_SANDBOX + static const int kEmbedderDataSlotExternalPointerOffset = kApiTaggedSize; +#else + static const int kEmbedderDataSlotExternalPointerOffset = 0; +#endif + static const int kNativeContextEmbedderDataOffset = 6 * kApiTaggedSize; + static const int kStringRepresentationAndEncodingMask = 0x0f; + static const int kStringEncodingMask = 0x8; + static const int kExternalTwoByteRepresentationTag = 0x02; + static const int kExternalOneByteRepresentationTag = 0x0a; + + // AccessorInfo::data and InterceptorInfo::data field. + static const int kCallbackInfoDataOffset = 1 * kApiTaggedSize; + + static const uint32_t kNumIsolateDataSlots = 4; + static const int kStackGuardSize = 8 * kApiSystemPointerSize; + static const int kNumberOfBooleanFlags = 6; + static const int kErrorMessageParamSize = 1; + static const int kTablesAlignmentPaddingSize = 1; + static const int kRegExpStaticResultOffsetsVectorSize = kApiSystemPointerSize; + static const int kBuiltinTier0EntryTableSize = 7 * kApiSystemPointerSize; + static const int kBuiltinTier0TableSize = 7 * kApiSystemPointerSize; + static const int kLinearAllocationAreaSize = 3 * kApiSystemPointerSize; + static const int kThreadLocalTopSize = 29 * kApiSystemPointerSize; + static const int kHandleScopeDataSize = + 2 * kApiSystemPointerSize + 2 * kApiInt32Size; + + // ExternalPointerTable and TrustedPointerTable layout guarantees. + static const int kExternalEntityTableBasePointerOffset = 0; + static const int kSegmentedTableSegmentPoolSize = 4; + static const int kExternalEntityTableSize = + 4 * kApiSystemPointerSize + + kSegmentedTableSegmentPoolSize * sizeof(uint32_t); + + // IsolateData layout guarantees. + static const int kIsolateCageBaseOffset = 0; + static const int kIsolateStackGuardOffset = + kIsolateCageBaseOffset + kApiSystemPointerSize; + static const int kVariousBooleanFlagsOffset = + kIsolateStackGuardOffset + kStackGuardSize; + static const int kErrorMessageParamOffset = + kVariousBooleanFlagsOffset + kNumberOfBooleanFlags; + static const int kBuiltinTier0EntryTableOffset = + kErrorMessageParamOffset + kErrorMessageParamSize + + kTablesAlignmentPaddingSize + kRegExpStaticResultOffsetsVectorSize; + static const int kBuiltinTier0TableOffset = + kBuiltinTier0EntryTableOffset + kBuiltinTier0EntryTableSize; + static const int kNewAllocationInfoOffset = + kBuiltinTier0TableOffset + kBuiltinTier0TableSize; + static const int kOldAllocationInfoOffset = + kNewAllocationInfoOffset + kLinearAllocationAreaSize; + static const int kLastYoungAllocationOffset = + kOldAllocationInfoOffset + kApiSystemPointerSize; + + static const int kFastCCallAlignmentPaddingSize = + kApiSystemPointerSize == 8 ? 5 * kApiSystemPointerSize + : 1 * kApiSystemPointerSize; + static const int kIsolateFastCCallCallerPcOffset = + kLastYoungAllocationOffset + kLinearAllocationAreaSize + + kFastCCallAlignmentPaddingSize; + static const int kIsolateFastCCallCallerFpOffset = + kIsolateFastCCallCallerPcOffset + kApiSystemPointerSize; + static const int kIsolateFastApiCallTargetOffset = + kIsolateFastCCallCallerFpOffset + kApiSystemPointerSize; + static const int kIsolateLongTaskStatsCounterOffset = + kIsolateFastApiCallTargetOffset + kApiSystemPointerSize; + static const int kIsolateThreadLocalTopOffset = + kIsolateLongTaskStatsCounterOffset + kApiSizetSize; + static const int kIsolateHandleScopeDataOffset = + kIsolateThreadLocalTopOffset + kThreadLocalTopSize; + static const int kIsolateEmbedderDataOffset = + kIsolateHandleScopeDataOffset + kHandleScopeDataSize; +#ifdef V8_COMPRESS_POINTERS + static const int kIsolateExternalPointerTableOffset = + kIsolateEmbedderDataOffset + kNumIsolateDataSlots * kApiSystemPointerSize; + static const int kIsolateSharedExternalPointerTableAddressOffset = + kIsolateExternalPointerTableOffset + kExternalEntityTableSize; + static const int kIsolateCppHeapPointerTableOffset = + kIsolateSharedExternalPointerTableAddressOffset + kApiSystemPointerSize; +#ifdef V8_ENABLE_SANDBOX + static const int kIsolateTrustedCageBaseOffset = + kIsolateCppHeapPointerTableOffset + kExternalEntityTableSize; + static const int kIsolateTrustedPointerTableOffset = + kIsolateTrustedCageBaseOffset + kApiSystemPointerSize; + static const int kIsolateSharedTrustedPointerTableAddressOffset = + kIsolateTrustedPointerTableOffset + kExternalEntityTableSize; + static const int kIsolateTrustedPointerPublishingScopeOffset = + kIsolateSharedTrustedPointerTableAddressOffset + kApiSystemPointerSize; + static const int kIsolateCodePointerTableBaseAddressOffset = + kIsolateTrustedPointerPublishingScopeOffset + kApiSystemPointerSize; + static const int kIsolateJSDispatchTableOffset = + kIsolateCodePointerTableBaseAddressOffset + kApiSystemPointerSize; +#else + static const int kIsolateJSDispatchTableOffset = + kIsolateCppHeapPointerTableOffset + kExternalEntityTableSize; +#endif // V8_ENABLE_SANDBOX +#else + static const int kIsolateJSDispatchTableOffset = + kIsolateEmbedderDataOffset + kNumIsolateDataSlots * kApiSystemPointerSize; +#endif // V8_COMPRESS_POINTERS + static const int kIsolateApiCallbackThunkArgumentOffset = + kIsolateJSDispatchTableOffset + kExternalEntityTableSize; + static const int kIsolateRegexpExecVectorArgumentOffset = + kIsolateApiCallbackThunkArgumentOffset + kApiSystemPointerSize; + static const int kContinuationPreservedEmbedderDataOffset = + kIsolateRegexpExecVectorArgumentOffset + kApiSystemPointerSize; + static const int kCurrentMicrotaskQueueOffset = + kContinuationPreservedEmbedderDataOffset + kApiSystemPointerSize; + static const int kCurrentMicrotaskNativeContextOffset = + kCurrentMicrotaskQueueOffset + kApiSystemPointerSize; + static const int kIsolateRootsOffset = + kCurrentMicrotaskNativeContextOffset + kApiSystemPointerSize; + +#if V8_TARGET_ARCH_PPC64 + static constexpr int kFrameCPSlotCount = 1; +#else + static constexpr int kFrameCPSlotCount = 0; +#endif + +#if V8_TARGET_ARCH_ARM64 + // The padding required to keep SP 16-byte aligned. + static constexpr int kSPAlignmentSlotCount = 1; +#else + static constexpr int kSPAlignmentSlotCount = 0; +#endif + + static const int kFrameTypeApiCallExit = 18; + static const int kFrameTypeApiConstructExit = 19; + static const int kFrameTypeApiNamedAccessorExit = 20; + static const int kFrameTypeApiIndexedAccessorExit = 21; + + // Assert scopes + static const int kDisallowGarbageCollectionAlign = alignof(uint32_t); + static const int kDisallowGarbageCollectionSize = sizeof(uint32_t); + +#if V8_STATIC_ROOTS_BOOL + +// These constants are copied from static-roots.h and guarded by static asserts. +#define EXPORTED_STATIC_ROOTS_PTR_LIST(V) \ + V(UndefinedValue, 0x11) \ + V(NullValue, 0x2d) \ + V(TrueValue, 0x71) \ + V(FalseValue, 0x55) \ + V(EmptyString, 0x49) \ + /* The Hole moves around depending on build flags, so define it */ \ + /* separately inside StaticReadOnlyRoot using build macros */ \ + V(TheHoleValue, kBuildDependentTheHoleValue) + + using Tagged_t = uint32_t; + struct StaticReadOnlyRoot { +#ifdef V8_ENABLE_WEBASSEMBLY + static constexpr Tagged_t kBuildDependentTheHoleValue = 0x2fffd; +#else + static constexpr Tagged_t kBuildDependentTheHoleValue = 0xfffd; +#endif + +#define DEF_ROOT(name, value) static constexpr Tagged_t k##name = value; + EXPORTED_STATIC_ROOTS_PTR_LIST(DEF_ROOT) +#undef DEF_ROOT + + // Use 0 for kStringMapLowerBound since string maps are the first maps. + static constexpr Tagged_t kStringMapLowerBound = 0; + static constexpr Tagged_t kStringMapUpperBound = 0x425; + +#define PLUSONE(...) +1 + static constexpr size_t kNumberOfExportedStaticRoots = + 2 + EXPORTED_STATIC_ROOTS_PTR_LIST(PLUSONE); +#undef PLUSONE + }; + +#endif // V8_STATIC_ROOTS_BOOL + + static const int kUndefinedValueRootIndex = 0; + static const int kTheHoleValueRootIndex = 1; + static const int kNullValueRootIndex = 2; + static const int kTrueValueRootIndex = 3; + static const int kFalseValueRootIndex = 4; + static const int kEmptyStringRootIndex = 5; + + static const int kNodeClassIdOffset = 1 * kApiSystemPointerSize; + static const int kNodeFlagsOffset = 1 * kApiSystemPointerSize + 3; + static const int kNodeStateMask = 0x3; + static const int kNodeStateIsWeakValue = 2; + + static const int kFirstNonstringType = 0x80; + static const int kOddballType = 0x83; + static const int kForeignType = 0xcc; + static const int kJSSpecialApiObjectType = 0x410; + static const int kJSObjectType = 0x421; + static const int kFirstJSApiObjectType = 0x422; + static const int kLastJSApiObjectType = 0x80A; + // Defines a range [kFirstEmbedderJSApiObjectType, kJSApiObjectTypesCount] + // of JSApiObject instance type values that an embedder can use. + static const int kFirstEmbedderJSApiObjectType = 0; + static const int kLastEmbedderJSApiObjectType = + kLastJSApiObjectType - kFirstJSApiObjectType; + + static const int kUndefinedOddballKind = 4; + static const int kNullOddballKind = 3; + + // Constants used by PropertyCallbackInfo to check if we should throw when an + // error occurs. + static const int kDontThrow = 0; + static const int kThrowOnError = 1; + static const int kInferShouldThrowMode = 2; + + // Soft limit for AdjustAmountofExternalAllocatedMemory. Trigger an + // incremental GC once the external memory reaches this limit. + static constexpr size_t kExternalAllocationSoftLimit = 64 * 1024 * 1024; + +#ifdef V8_MAP_PACKING + static const uintptr_t kMapWordMetadataMask = 0xffffULL << 48; + // The lowest two bits of mapwords are always `0b10` + static const uintptr_t kMapWordSignature = 0b10; + // XORing a (non-compressed) map with this mask ensures that the two + // low-order bits are 0b10. The 0 at the end makes this look like a Smi, + // although real Smis have all lower 32 bits unset. We only rely on these + // values passing as Smis in very few places. + static const int kMapWordXorMask = 0b11; +#endif + + V8_EXPORT static void CheckInitializedImpl(v8::Isolate* isolate); + V8_INLINE static void CheckInitialized(v8::Isolate* isolate) { +#ifdef V8_ENABLE_CHECKS + CheckInitializedImpl(isolate); +#endif + } + + V8_INLINE static constexpr bool HasHeapObjectTag(Address value) { + return (value & kHeapObjectTagMask) == static_cast
(kHeapObjectTag); + } + + V8_INLINE static constexpr int SmiValue(Address value) { + return PlatformSmiTagging::SmiToInt(value); + } + + V8_INLINE static constexpr Address AddressToSmi(Address value) { + return (value << (kSmiTagSize + PlatformSmiTagging::kSmiShiftSize)) | + kSmiTag; + } + + V8_INLINE static constexpr Address IntToSmi(int value) { + return AddressToSmi(static_cast
(value)); + } + + template >* = nullptr> + V8_INLINE static constexpr Address IntegralToSmi(T value) { + return AddressToSmi(static_cast
(value)); + } + + template >* = nullptr> + V8_INLINE static constexpr bool IsValidSmi(T value) { + return PlatformSmiTagging::IsValidSmi(value); + } + + template >* = nullptr> + static constexpr std::optional
TryIntegralToSmi(T value) { + if (V8_LIKELY(PlatformSmiTagging::IsValidSmi(value))) { + return {AddressToSmi(static_cast
(value))}; + } + return {}; + } + +#if V8_STATIC_ROOTS_BOOL + V8_INLINE static bool is_identical(Address obj, Tagged_t constant) { + return static_cast(obj) == constant; + } + + V8_INLINE static bool CheckInstanceMapRange(Address obj, Tagged_t first_map, + Tagged_t last_map) { + auto map = ReadRawField(obj, kHeapObjectMapOffset); +#ifdef V8_MAP_PACKING + map = UnpackMapWord(map); +#endif + return map >= first_map && map <= last_map; + } +#endif + + V8_INLINE static int GetInstanceType(Address obj) { + Address map = ReadTaggedPointerField(obj, kHeapObjectMapOffset); +#ifdef V8_MAP_PACKING + map = UnpackMapWord(map); +#endif + return ReadRawField(map, kMapInstanceTypeOffset); + } + + V8_INLINE static Address LoadMap(Address obj) { + if (!HasHeapObjectTag(obj)) return kNullAddress; + Address map = ReadTaggedPointerField(obj, kHeapObjectMapOffset); +#ifdef V8_MAP_PACKING + map = UnpackMapWord(map); +#endif + return map; + } + + V8_INLINE static int GetOddballKind(Address obj) { + return SmiValue(ReadTaggedSignedField(obj, kOddballKindOffset)); + } + + V8_INLINE static bool IsExternalTwoByteString(int instance_type) { + int representation = (instance_type & kStringRepresentationAndEncodingMask); + return representation == kExternalTwoByteRepresentationTag; + } + + V8_INLINE static bool IsExternalOneByteString(int instance_type) { + int representation = (instance_type & kStringRepresentationAndEncodingMask); + return representation == kExternalOneByteRepresentationTag; + } + + V8_INLINE static constexpr bool CanHaveInternalField(int instance_type) { + static_assert(kJSObjectType + 1 == kFirstJSApiObjectType); + static_assert(kJSObjectType < kLastJSApiObjectType); + static_assert(kFirstJSApiObjectType < kLastJSApiObjectType); + // Check for IsJSObject() || IsJSSpecialApiObject() || IsJSApiObject() + return instance_type == kJSSpecialApiObjectType || + // inlined version of base::IsInRange + (static_cast(static_cast(instance_type) - + static_cast(kJSObjectType)) <= + static_cast(kLastJSApiObjectType - kJSObjectType)); + } + + V8_INLINE static uint8_t GetNodeFlag(Address* obj, int shift) { + uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; + return *addr & static_cast(1U << shift); + } + + V8_INLINE static void UpdateNodeFlag(Address* obj, bool value, int shift) { + uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; + uint8_t mask = static_cast(1U << shift); + *addr = static_cast((*addr & ~mask) | (value << shift)); + } + + V8_INLINE static uint8_t GetNodeState(Address* obj) { + uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; + return *addr & kNodeStateMask; + } + + V8_INLINE static void UpdateNodeState(Address* obj, uint8_t value) { + uint8_t* addr = reinterpret_cast(obj) + kNodeFlagsOffset; + *addr = static_cast((*addr & ~kNodeStateMask) | value); + } + + V8_INLINE static void SetEmbedderData(v8::Isolate* isolate, uint32_t slot, + void* data) { + Address addr = reinterpret_cast
(isolate) + + kIsolateEmbedderDataOffset + slot * kApiSystemPointerSize; + *reinterpret_cast(addr) = data; + } + + V8_INLINE static void* GetEmbedderData(const v8::Isolate* isolate, + uint32_t slot) { + Address addr = reinterpret_cast
(isolate) + + kIsolateEmbedderDataOffset + slot * kApiSystemPointerSize; + return *reinterpret_cast(addr); + } + + V8_INLINE static HandleScopeData* GetHandleScopeData(v8::Isolate* isolate) { + Address addr = + reinterpret_cast
(isolate) + kIsolateHandleScopeDataOffset; + return reinterpret_cast(addr); + } + + V8_INLINE static void IncrementLongTasksStatsCounter(v8::Isolate* isolate) { + Address addr = + reinterpret_cast
(isolate) + kIsolateLongTaskStatsCounterOffset; + ++(*reinterpret_cast(addr)); + } + + V8_INLINE static Address* GetRootSlot(v8::Isolate* isolate, int index) { + Address addr = reinterpret_cast
(isolate) + kIsolateRootsOffset + + index * kApiSystemPointerSize; + return reinterpret_cast(addr); + } + + V8_INLINE static Address GetRoot(v8::Isolate* isolate, int index) { +#if V8_STATIC_ROOTS_BOOL + Address base = *reinterpret_cast( + reinterpret_cast(isolate) + kIsolateCageBaseOffset); + switch (index) { +#define DECOMPRESS_ROOT(name, ...) \ + case k##name##RootIndex: \ + return base + StaticReadOnlyRoot::k##name; + EXPORTED_STATIC_ROOTS_PTR_LIST(DECOMPRESS_ROOT) +#undef DECOMPRESS_ROOT +#undef EXPORTED_STATIC_ROOTS_PTR_LIST + default: + break; + } +#endif // V8_STATIC_ROOTS_BOOL + return *GetRootSlot(isolate, index); + } + +#ifdef V8_ENABLE_SANDBOX + V8_INLINE static Address* GetExternalPointerTableBase(v8::Isolate* isolate) { + Address addr = reinterpret_cast
(isolate) + + kIsolateExternalPointerTableOffset + + kExternalEntityTableBasePointerOffset; + return *reinterpret_cast(addr); + } + + V8_INLINE static Address* GetSharedExternalPointerTableBase( + v8::Isolate* isolate) { + Address addr = reinterpret_cast
(isolate) + + kIsolateSharedExternalPointerTableAddressOffset; + addr = *reinterpret_cast(addr); + addr += kExternalEntityTableBasePointerOffset; + return *reinterpret_cast(addr); + } +#endif + + template + V8_INLINE static T ReadRawField(Address heap_object_ptr, int offset) { + Address addr = heap_object_ptr + offset - kHeapObjectTag; +#ifdef V8_COMPRESS_POINTERS + if constexpr (sizeof(T) > kApiTaggedSize) { + // TODO(ishell, v8:8875): When pointer compression is enabled 8-byte size + // fields (external pointers, doubles and BigInt data) are only + // kTaggedSize aligned so we have to use unaligned pointer friendly way of + // accessing them in order to avoid undefined behavior in C++ code. + T r; + memcpy(&r, reinterpret_cast(addr), sizeof(T)); + return r; + } +#endif + return *reinterpret_cast(addr); + } + + V8_INLINE static Address ReadTaggedPointerField(Address heap_object_ptr, + int offset) { +#ifdef V8_COMPRESS_POINTERS + uint32_t value = ReadRawField(heap_object_ptr, offset); + Address base = GetPtrComprCageBaseFromOnHeapAddress(heap_object_ptr); + return base + static_cast
(static_cast(value)); +#else + return ReadRawField
(heap_object_ptr, offset); +#endif + } + + V8_INLINE static Address ReadTaggedSignedField(Address heap_object_ptr, + int offset) { +#ifdef V8_COMPRESS_POINTERS + uint32_t value = ReadRawField(heap_object_ptr, offset); + return static_cast
(static_cast(value)); +#else + return ReadRawField
(heap_object_ptr, offset); +#endif + } + + // Returns v8::Isolate::Current(), but without needing to include the + // v8-isolate.h header. + V8_EXPORT static v8::Isolate* GetCurrentIsolate(); + + V8_INLINE static v8::Isolate* GetCurrentIsolateForSandbox() { +#ifdef V8_ENABLE_SANDBOX + return GetCurrentIsolate(); +#else + // Not used in non-sandbox mode. + return nullptr; +#endif + } + + template + V8_INLINE static Address ReadExternalPointerField(v8::Isolate* isolate, + Address heap_object_ptr, + int offset) { +#ifdef V8_ENABLE_SANDBOX + static_assert(!tag_range.IsEmpty()); + // See src/sandbox/external-pointer-table.h. Logic duplicated here so + // it can be inlined and doesn't require an additional call. + Address* table = IsSharedExternalPointerType(tag_range) + ? GetSharedExternalPointerTableBase(isolate) + : GetExternalPointerTableBase(isolate); + internal::ExternalPointerHandle handle = + ReadRawField(heap_object_ptr, offset); + uint32_t index = handle >> kExternalPointerIndexShift; + std::atomic
* ptr = + reinterpret_cast*>(&table[index]); + Address entry = std::atomic_load_explicit(ptr, std::memory_order_relaxed); + ExternalPointerTag actual_tag = static_cast( + (entry & kExternalPointerTagMask) >> kExternalPointerTagShift); + volatile Address safe_entry; + if (V8_LIKELY(tag_range.Contains(actual_tag))) { + safe_entry = entry & kExternalPointerPayloadMask; + } else { + safe_entry = 0; + } + return safe_entry; +#else + return ReadRawField
(heap_object_ptr, offset); +#endif // V8_ENABLE_SANDBOX + } + + V8_INLINE static Address ReadExternalPointerField( + v8::Isolate* isolate, Address heap_object_ptr, int offset, + ExternalPointerTagRange tag_range) { +#ifdef V8_ENABLE_SANDBOX + // See src/sandbox/external-pointer-table.h. Logic duplicated here so + // it can be inlined and doesn't require an additional call. + Address* table = IsSharedExternalPointerType(tag_range) + ? GetSharedExternalPointerTableBase(isolate) + : GetExternalPointerTableBase(isolate); + internal::ExternalPointerHandle handle = + ReadRawField(heap_object_ptr, offset); + uint32_t index = handle >> kExternalPointerIndexShift; + std::atomic
* ptr = + reinterpret_cast*>(&table[index]); + Address entry = std::atomic_load_explicit(ptr, std::memory_order_relaxed); + ExternalPointerTag actual_tag = static_cast( + (entry & kExternalPointerTagMask) >> kExternalPointerTagShift); + // Avoid DCE of the entry logic using volatile. + volatile Address safe_entry; + if (V8_LIKELY(tag_range.Contains(actual_tag))) { + safe_entry = entry & kExternalPointerPayloadMask; + } else { + safe_entry = 0; + } + return safe_entry; +#else + return ReadRawField
(heap_object_ptr, offset); +#endif // V8_ENABLE_SANDBOX + } + +#ifdef V8_COMPRESS_POINTERS + V8_INLINE static Address GetPtrComprCageBaseFromOnHeapAddress(Address addr) { + return addr & -static_cast(kPtrComprCageBaseAlignment); + } + + V8_INLINE static uint32_t CompressTagged(Address value) { + return static_cast(value); + } + + V8_INLINE static Address DecompressTaggedField(Address heap_object_ptr, + uint32_t value) { + Address base = GetPtrComprCageBaseFromOnHeapAddress(heap_object_ptr); + return base + static_cast
(static_cast(value)); + } + +#endif // V8_COMPRESS_POINTERS +}; + +// Only perform cast check for types derived from v8::Data since +// other types do not implement the Cast method. +template +struct CastCheck { + template + static void Perform(T* data); +}; + +template <> +template +void CastCheck::Perform(T* data) { + T::Cast(data); +} + +template <> +template +void CastCheck::Perform(T* data) {} + +template +V8_INLINE void PerformCastCheck(T* data) { + CastCheck && + !std::is_same_v>>::Perform(data); +} + +// A base class for backing stores, which is needed due to vagaries of +// how static casts work with std::shared_ptr. +class BackingStoreBase {}; + +// The maximum value in enum GarbageCollectionReason, defined in heap.h. +// This is needed for histograms sampling garbage collection reasons. +constexpr int kGarbageCollectionReasonMaxValue = 30; + +// Base class for the address block allocator compatible with standard +// containers, which registers its allocated range as strong roots. +class V8_EXPORT StrongRootAllocatorBase { + public: + Heap* heap() const { return heap_; } + + constexpr bool operator==(const StrongRootAllocatorBase&) const = default; + + protected: + explicit StrongRootAllocatorBase(Heap* heap) : heap_(heap) {} + explicit StrongRootAllocatorBase(LocalHeap* heap); + explicit StrongRootAllocatorBase(Isolate* isolate); + explicit StrongRootAllocatorBase(v8::Isolate* isolate); + explicit StrongRootAllocatorBase(LocalIsolate* isolate); + + // Allocate/deallocate a range of n elements of type internal::Address. + Address* allocate_impl(size_t n); + void deallocate_impl(Address* p, size_t n) noexcept; + + private: + Heap* heap_; +}; + +// The general version of this template behaves just as std::allocator, with +// the exception that the constructor takes the isolate as parameter. Only +// specialized versions, e.g., internal::StrongRootAllocator +// and internal::StrongRootAllocator> register the allocated range +// as strong roots. +template +class StrongRootAllocator : private std::allocator { + public: + using value_type = T; + + template + explicit StrongRootAllocator(HeapOrIsolateT*) {} + template + StrongRootAllocator(const StrongRootAllocator& other) noexcept {} + + using std::allocator::allocate; + using std::allocator::deallocate; +}; + +template +concept HasIteratorConcept = requires { typename Iterator::iterator_concept; }; + +template +concept HasIteratorCategory = + requires { typename Iterator::iterator_category; }; + +// Helper struct that contains an `iterator_concept` type alias only when either +// `Iterator` or `std::iterator_traits` do. +// Default: no alias. +template +struct MaybeDefineIteratorConcept {}; +// Use `Iterator::iterator_concept` if available. +template +struct MaybeDefineIteratorConcept { + using iterator_concept = typename Iterator::iterator_concept; +}; +// Otherwise fall back to `std::iterator_traits` if possible. +template + requires(HasIteratorCategory && !HasIteratorConcept) +struct MaybeDefineIteratorConcept { + using iterator_concept = + typename std::iterator_traits::iterator_concept; +}; + +// A class of iterators that wrap some different iterator type. +// If specified, ElementType is the type of element accessed by the wrapper +// iterator; in this case, the actual reference and pointer types of Iterator +// must be convertible to ElementType& and ElementType*, respectively. +template +class WrappedIterator : public MaybeDefineIteratorConcept { + public: + static_assert( + std::is_void_v || + (std::is_convertible_v::pointer, + std::add_pointer_t> && + std::is_convertible_v::reference, + std::add_lvalue_reference_t>)); + + using difference_type = + typename std::iterator_traits::difference_type; + using value_type = + std::conditional_t, + typename std::iterator_traits::value_type, + ElementType>; + using pointer = + std::conditional_t, + typename std::iterator_traits::pointer, + std::add_pointer_t>; + using reference = + std::conditional_t, + typename std::iterator_traits::reference, + std::add_lvalue_reference_t>; + using iterator_category = + typename std::iterator_traits::iterator_category; + + constexpr WrappedIterator() noexcept = default; + constexpr explicit WrappedIterator(Iterator it) noexcept : it_(it) {} + + template + requires std::is_convertible_v + constexpr WrappedIterator( + const WrappedIterator& other) noexcept + : it_(other.base()) {} + + [[nodiscard]] constexpr reference operator*() const noexcept { return *it_; } + [[nodiscard]] constexpr pointer operator->() const noexcept { + if constexpr (std::is_pointer_v) { + return it_; + } else { + return it_.operator->(); + } + } + + template + [[nodiscard]] constexpr bool operator==( + const WrappedIterator& other) + const noexcept { + return it_ == other.base(); + } + + template + [[nodiscard]] constexpr auto operator<=>( + const WrappedIterator& other) + const noexcept { + if constexpr (std::three_way_comparable_with) { + return it_ <=> other.base(); + } else if constexpr (std::totally_ordered_with) { + if (it_ < other.base()) { + return std::strong_ordering::less; + } + return (it_ > other.base()) ? std::strong_ordering::greater + : std::strong_ordering::equal; + } else { + if (it_ < other.base()) { + return std::partial_ordering::less; + } + if (other.base() < it_) { + return std::partial_ordering::greater; + } + return (it_ == other.base()) ? std::partial_ordering::equivalent + : std::partial_ordering::unordered; + } + } + + constexpr WrappedIterator& operator++() noexcept { + ++it_; + return *this; + } + constexpr WrappedIterator operator++(int) noexcept { + WrappedIterator result(*this); + ++(*this); + return result; + } + + constexpr WrappedIterator& operator--() noexcept { + --it_; + return *this; + } + constexpr WrappedIterator operator--(int) noexcept { + WrappedIterator result(*this); + --(*this); + return result; + } + [[nodiscard]] constexpr WrappedIterator operator+( + difference_type n) const noexcept { + WrappedIterator result(*this); + result += n; + return result; + } + [[nodiscard]] friend constexpr WrappedIterator operator+( + difference_type n, const WrappedIterator& x) noexcept { + return x + n; + } + constexpr WrappedIterator& operator+=(difference_type n) noexcept { + it_ += n; + return *this; + } + [[nodiscard]] constexpr WrappedIterator operator-( + difference_type n) const noexcept { + return *this + -n; + } + constexpr WrappedIterator& operator-=(difference_type n) noexcept { + return *this += -n; + } + template + [[nodiscard]] constexpr auto operator-( + const WrappedIterator& other) + const noexcept { + return it_ - other.base(); + } + [[nodiscard]] constexpr reference operator[]( + difference_type n) const noexcept { + return it_[n]; + } + + [[nodiscard]] constexpr const Iterator& base() const noexcept { return it_; } + + private: + Iterator it_; +}; + +// Helper functions about values contained in handles. +// A value is either an indirect pointer or a direct pointer, depending on +// whether direct local support is enabled. +class ValueHelper final { + public: + // ValueHelper::InternalRepresentationType is an abstract type that + // corresponds to the internal representation of v8::Local and essentially + // to what T* really is (these two are always in sync). This type is used in + // methods like GetDataFromSnapshotOnce that need access to a handle's + // internal representation. In particular, if `x` is a `v8::Local`, then + // `v8::Local::FromRepr(x.repr())` gives exactly the same handle as `x`. +#ifdef V8_ENABLE_DIRECT_HANDLE + static constexpr Address kTaggedNullAddress = 1; + + using InternalRepresentationType = internal::Address; + static constexpr InternalRepresentationType kEmpty = kTaggedNullAddress; +#else + using InternalRepresentationType = internal::Address*; + static constexpr InternalRepresentationType kEmpty = nullptr; +#endif // V8_ENABLE_DIRECT_HANDLE + + template + V8_INLINE static bool IsEmpty(T* value) { + return ValueAsRepr(value) == kEmpty; + } + + // Returns a handle's "value" for all kinds of abstract handles. For Local, + // it is equivalent to `*handle`. The variadic parameters support handle + // types with extra type parameters, like `Persistent`. + template