#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_STARSCAN_PCSCAN_H_
#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_STARSCAN_PCSCAN_H_
#include <atomic>
#include "base/allocator/partition_allocator/page_allocator.h"
#include "base/allocator/partition_allocator/partition_alloc_base/compiler_specific.h"
#include "base/allocator/partition_allocator/partition_alloc_base/component_export.h"
#include "base/allocator/partition_allocator/partition_alloc_config.h"
#include "base/allocator/partition_allocator/partition_alloc_forward.h"
#include "base/allocator/partition_allocator/partition_direct_map_extent.h"
#include "base/allocator/partition_allocator/partition_page.h"
#include "base/allocator/partition_allocator/starscan/pcscan_scheduling.h"
#include "base/allocator/partition_allocator/tagging.h"
namespace partition_alloc {
class StatsReporter;
namespace internal {
[[noreturn]] PA_NOINLINE PA_NOT_TAIL_CALLED
PA_COMPONENT_EXPORT(PARTITION_ALLOC) void DoubleFreeAttempt();
class PA_COMPONENT_EXPORT(PARTITION_ALLOC) PCScan final {
public:
using Root = PartitionRoot<ThreadSafe>;
using SlotSpan = SlotSpanMetadata<ThreadSafe>;
enum class InvocationMode {
kBlocking,
kNonBlocking,
kForcedBlocking,
kScheduleOnlyForTesting,
};
enum class ClearType : uint8_t {
kLazy,
kEager,
};
struct InitConfig {
enum class WantedWriteProtectionMode : uint8_t {
kDisabled,
kEnabled,
} write_protection = WantedWriteProtectionMode::kDisabled;
enum class SafepointMode : uint8_t {
kDisabled,
kEnabled,
} safepoint = SafepointMode::kDisabled;
};
PCScan(const PCScan&) = delete;
PCScan& operator=(const PCScan&) = delete;
static void Initialize(InitConfig);
static bool IsInitialized();
static void Disable();
static void Reenable();
static bool IsEnabled();
static void RegisterScannableRoot(Root* root);
static void RegisterNonScannableRoot(Root* root);
static void RegisterNewSuperPage(Root* root, uintptr_t super_page_base);
PA_ALWAYS_INLINE static void MoveToQuarantine(void* object,
size_t usable_size,
uintptr_t slot_start,
size_t slot_size);
static void PerformScan(InvocationMode invocation_mode);
static void PerformScanIfNeeded(InvocationMode invocation_mode);
static void PerformDelayedScan(int64_t delay_in_microseconds);
PA_ALWAYS_INLINE static void EnableSafepoints();
PA_ALWAYS_INLINE static void JoinScanIfNeeded();
PA_ALWAYS_INLINE static bool IsInProgress();
static void SetProcessName(const char* name);
static void EnableStackScanning();
static void DisableStackScanning();
static bool IsStackScanningEnabled();
static void EnableImmediateFreeing();
static void NotifyThreadCreated(void* stack_top);
static void NotifyThreadDestroyed();
static void SetClearType(ClearType);
static void UninitForTesting();
static inline PCScanScheduler& scheduler();
static void RegisterStatsReporter(partition_alloc::StatsReporter* reporter);
private:
class PCScanThread;
friend class PCScanTask;
friend class PartitionAllocPCScanTestBase;
friend class PCScanInternal;
enum class State : uint8_t {
kNotRunning,
kScheduled,
kScanning,
kSweepingAndFinishing
};
PA_ALWAYS_INLINE static PCScan& Instance();
PA_ALWAYS_INLINE bool IsJoinable() const;
PA_ALWAYS_INLINE void SetJoinableIfSafepointEnabled(bool);
inline constexpr PCScan();
static void JoinScan();
static void FinishScanForTesting();
static void ReinitForTesting(InitConfig);
size_t epoch() const { return scheduler_.epoch(); }
static PCScan instance_ PA_CONSTINIT;
PCScanScheduler scheduler_{};
std::atomic<State> state_{State::kNotRunning};
std::atomic<bool> is_joinable_{false};
bool is_safepoint_enabled_{false};
ClearType clear_type_{ClearType::kLazy};
};
constexpr PCScan::PCScan() = default;
PA_ALWAYS_INLINE PCScan& PCScan::Instance() {
return instance_;
}
PA_ALWAYS_INLINE bool PCScan::IsInProgress() {
const PCScan& instance = Instance();
return instance.state_.load(std::memory_order_relaxed) != State::kNotRunning;
}
PA_ALWAYS_INLINE bool PCScan::IsJoinable() const {
return is_joinable_.load(std::memory_order_acquire);
}
PA_ALWAYS_INLINE void PCScan::SetJoinableIfSafepointEnabled(bool value) {
if (!is_safepoint_enabled_) {
PA_DCHECK(!is_joinable_.load(std::memory_order_relaxed));
return;
}
is_joinable_.store(value, std::memory_order_release);
}
PA_ALWAYS_INLINE void PCScan::EnableSafepoints() {
PCScan& instance = Instance();
instance.is_safepoint_enabled_ = true;
}
PA_ALWAYS_INLINE void PCScan::JoinScanIfNeeded() {
PCScan& instance = Instance();
if (PA_UNLIKELY(instance.IsJoinable()))
instance.JoinScan();
}
PA_ALWAYS_INLINE void PCScan::MoveToQuarantine(void* object,
size_t usable_size,
uintptr_t slot_start,
size_t slot_size) {
PCScan& instance = Instance();
if (instance.clear_type_ == ClearType::kEager) {
SecureMemset(object, 0, usable_size);
}
auto* state_bitmap = StateBitmapFromAddr(slot_start);
[[maybe_unused]] const bool succeeded =
state_bitmap->Quarantine(slot_start, instance.epoch());
#if PA_CONFIG(STARSCAN_EAGER_DOUBLE_FREE_DETECTION_ENABLED)
if (PA_UNLIKELY(!succeeded))
DoubleFreeAttempt();
#else
#endif
const bool is_limit_reached = instance.scheduler_.AccountFreed(slot_size);
if (PA_UNLIKELY(is_limit_reached)) {
if (instance.IsInProgress())
return;
instance.PerformScan(InvocationMode::kNonBlocking);
}
}
inline PCScanScheduler& PCScan::scheduler() {
PCScan& instance = Instance();
return instance.scheduler_;
}
}
}
#endif