#ifndef V8_SANDBOX_COMPACTIBLE_EXTERNAL_ENTITY_TABLE_H_
#define V8_SANDBOX_COMPACTIBLE_EXTERNAL_ENTITY_TABLE_H_
#include "include/v8config.h"
#include "src/common/globals.h"
#include "src/sandbox/external-entity-table.h"
#ifdef V8_COMPRESS_POINTERS
namespace v8 {
namespace internal {
class Isolate;
class Histogram;
enum class ExternalEntityTableCompactionOutcome {
kSuccess = 0,
kAborted = 2,
};
* An intermediate table class that abstracts garbage collection mechanism
* for pointer tables that support compaction.
*
* Table compaction:
* -----------------
* The table's spaces are to some degree self-compacting: since the freelists
* are sorted in ascending order, segments at the start of the table will
* usually be fairly well utilized, while later segments might become
* completely free, in which case they will be deallocated.
* However, as a single live entry may keep an entire segment alive, the
* following simple algorithm is used to compact a space if that is deemed
* necessary:
* - At the start of the GC marking phase, determine if a space needs to be
* compacted. This decision is mostly based on the absolute and relative
* size of the freelist.
* - If compaction is needed, this algorithm determines by how many segments
* it would like to shrink the space (N). It will then attempt to move all
* live entries out of these segments so that they can be deallocated
* afterwards during sweeping.
* - The algorithm then simply selects the last N segments for evacuation, and
* it "marks" them for evacuation simply by remembering the start of the
* first selected segment. Everything after this threshold value then
* becomes the evacuation area. In this way, it becomes very cheap to test
* if an entry or segment should be evacuated: only a single integer
* comparison against the threshold is required. It also establishes a
* simple compaction invariant: compaction always moves an entry at or above
* the threshold to a new position before the threshold.
* - During marking, whenever a live entry inside the evacuation area is
* found, a new "evacuation entry" is allocated from the freelist (which is
* assumed to have enough free slots) and the address of the handle in the
* object owning the table entry is written into it.
* - During sweeping, these evacuation entries are resolved: the content of
* the old entry is copied into the new entry and the handle in the object
* is updated to point to the new entry.
*
* When compacting, it is expected that the evacuation area contains few live
* entries and that the freelist will be able to serve all evacuation entry
* allocations. In that case, compaction is essentially free (very little
* marking overhead, no memory overhead). However, it can happen that the
* application allocates a large number of table entries during marking, in
* which case we might end up allocating new entries inside the evacuation area
* or even allocate entire new segments for the space that's being compacted.
* If that situation is detected, compaction is aborted during marking.
*
* This algorithm assumes that table entries (except for the null entry) are
* never shared between multiple objects. Otherwise, the following could
* happen: object A initially has handle H1 and is scanned during incremental
* marking. Next, object B with handle H2 is scanned and marked for
* evacuation. Afterwards, object A copies the handle H2 from object B.
* During sweeping, only object B's handle will be updated to point to the
* new entry while object A's handle is now dangling. If shared entries ever
* become necessary, setting pointer handles would have to be guarded by
* write barriers to avoid this scenario.
*/
template <typename Entry, size_t size>
class V8_EXPORT_PRIVATE CompactibleExternalEntityTable
: public ExternalEntityTable<Entry, size> {
using Base = ExternalEntityTable<Entry, size>;
public:
static constexpr bool kSupportsCompaction = true;
struct CompactionResult {
uint32_t start_of_evacuation_area;
bool success;
};
CompactibleExternalEntityTable() = default;
CompactibleExternalEntityTable(const CompactibleExternalEntityTable&) =
delete;
CompactibleExternalEntityTable& operator=(
const CompactibleExternalEntityTable&) = delete;
struct Space : public Base::Space {
public:
Space() : start_of_evacuation_area_(kNotCompactingMarker) {}
void StartCompactingIfNeeded();
private:
friend class CompactibleExternalEntityTable<Entry, size>;
friend class ExternalPointerTable;
friend class CppHeapPointerTable;
inline bool IsCompacting();
inline void StartCompacting(uint32_t start_of_evacuation_area);
inline void StopCompacting();
inline void AbortCompacting(uint32_t start_of_evacuation_area);
inline bool CompactingWasAborted();
inline bool FieldWasInvalidated(Address field_address) const;
inline void ClearInvalidatedFields();
inline void AddInvalidatedField(Address field_address);
static constexpr uint32_t kNotCompactingMarker =
std::numeric_limits<uint32_t>::max();
static constexpr uint32_t kCompactionAbortedMarker = 0xf0000000;
std::atomic<uint32_t> start_of_evacuation_area_;
std::vector<Address> invalidated_fields_;
base::Mutex invalidated_fields_mutex_;
};
inline uint32_t AllocateEntry(Space* space);
CompactionResult FinishCompaction(Space* space, Histogram* counter);
inline void MaybeCreateEvacuationEntry(Space* space, uint32_t index,
Address handle_location);
};
}
}
#endif
#endif