blob: 4f9d8c12de5073775ca53b78114c30826faeb244 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#pragma once
#include <zircon/assert.h>
#include <atomic>
#include <cstddef>
#include <new>
#include <type_traits>
#include <utility>
namespace lazy_init {
// Enum that specifies what kind of debug init checks to perform for a
// lazy-initialized global variable.
enum class CheckType {
// No checks are performed.
None,
// Initalization checks are performed. If multiple threads will access the
// global variable, initialization must be manually serialized with respect
// to the guard variable.
Basic,
// Initialization checks are performed using atomic operations. Checks are
// guaranteed to be consistent, even when races occur over initialization.
Atomic,
// The default check type as specified by the build. This is the check type
// used when not explicitly specified. It may also be specified explicitly
// to defer to the build configuration when setting other options.
// TODO(eieio): Add the build arg and conditional logic.
Default = None,
};
// Enum that specifies whether to enable a lazy-initialized global variable's
// destructor. Disabling global destructors avoids destructor registration.
// However, destructors can be conditionally enabled on builds that require
// them, such as ASAN.
enum class Destructor {
Disabled,
Enabled,
// The default destructor enablement as specified by the build. This is the
// enablement used when not explicitly specified. It may also be specified
// explicitly to defer to the build configuration when setting other
// options.
// TODO(eieio): Add the build arg and conditional logic.
Default = Disabled,
};
namespace internal {
// Empty type that is trivially constructible/destructible.
struct Empty {};
// Lazy-initialized storage type for trivially destructible value types.
template <typename T, bool = std::is_trivially_destructible_v<T>>
union LazyInitStorage {
constexpr LazyInitStorage() : empty{} {}
// Trivial destructor required so that the overall union is also trivially
// destructible.
~LazyInitStorage() = default;
constexpr T& operator*() { return value; }
constexpr T* operator->() { return &value; }
constexpr T* operator&() { return &value; }
Empty empty;
T value;
};
// Lazy-initialized storage type for non-trivially destructible value types.
template <typename T>
union LazyInitStorage<T, false> {
constexpr LazyInitStorage() : empty{} {}
// Non-trivial destructor required when at least one variant is non-
// trivially destructible, making the overall union also non-trivially
// destructible.
~LazyInitStorage() {}
constexpr T& operator*() { return value; }
constexpr T* operator->() { return &value; }
constexpr T* operator&() { return &value; }
Empty empty;
T value;
};
} // namespace internal
// Wrapper type for global variables that removes automatic constructor and
// destructor generation and provides explicit control over initialization.
// This avoids initialization order hazards between globals, as well as code
// that runs before and after global constructors are invoked.
//
// This is the base type that specifies the default parameters that control
// consistency checks and global destructor generation options.
template <typename T, CheckType = CheckType::Default, Destructor = Destructor::Default>
class LazyInit;
// Specialization that does not provide consistency checks.
template <typename T>
class LazyInit<T, CheckType::None, Destructor::Disabled> {
public:
constexpr LazyInit() = default;
~LazyInit() = default;
LazyInit(const LazyInit&) = delete;
LazyInit& operator=(const LazyInit&) = delete;
LazyInit(LazyInit&&) = delete;
LazyInit& operator=(LazyInit&&) = delete;
// Explicitly constructs the wrapped global. It is up to the caller to
// ensure that initialization is not already performed.
// Returns a reference to the newly constructed global.
template <typename... Args>
std::enable_if_t<std::is_constructible_v<T, Args&&...>, T&> Initialize(Args&&... args) {
static_assert(alignof(LazyInit) >= alignof(T));
new (&storage_) T(std::forward<Args>(args)...);
return *storage_;
}
// Returns a reference to the wrapped global. It is up to the caller to
// ensure that initialization is already performed and that the effects of
// initialization are visible.
T& Get() { return *storage_; }
// Accesses the wrapped global by pointer. It is up to the caller to ensure
// that initialization is already performed and that the effects of
// initialization are visible.
T* operator->() { return &Get(); }
// Returns a pointer to the wrapped global. All specializations return a
// pointer without performing consistency checks. This should be used
// cautiously, preferably only in constant expressions that take the address
// of the wrapped global.
constexpr T* operator&() { return &storage_; }
private:
template <typename, CheckType, Destructor>
friend class LazyInit;
// Explicitly destroys the wrapped global. Called by the specialization of
// LazyInit with the destructor enabled.
void Destruct() { storage_->T::~T(); }
// Lazy-initialized storage for a value of type T.
internal::LazyInitStorage<T> storage_;
};
// Specialization that provides basic consistency checks. It is up to the caller
// to ensure proper synchronization and barriers.
template <typename T>
class LazyInit<T, CheckType::Basic, Destructor::Disabled> {
public:
constexpr LazyInit() = default;
~LazyInit() = default;
LazyInit(const LazyInit&) = delete;
LazyInit& operator=(const LazyInit&) = delete;
LazyInit(LazyInit&&) = delete;
LazyInit& operator=(LazyInit&&) = delete;
// Explicitly constructs the wrapped global. Asserts that initialization is
// not already performed.
// Returns a reference to the newly constructed global.
template <typename... Args>
std::enable_if_t<std::is_constructible_v<T, Args&&...>, T&> Initialize(Args&&... args) {
static_assert(alignof(LazyInit) >= alignof(T));
ZX_ASSERT(!*initialized_);
*initialized_ = true;
new (&storage_) T(std::forward<Args>(args)...);
return *storage_;
}
// Returns a reference to the wrapped global. Asserts that initialization
// is already performed, however, it is up to the caller to ensure that the
// effects of initialization are visible.
T& Get() {
ZX_ASSERT(*initialized_);
return *storage_;
}
// Accesses the wrapped global by pointer. Asserts that initialization is
// already perfromed, however, it is up the caller to ensure that the
// effects of initialization are visible.
T* operator->() { return &Get(); }
// Returns a pointer to the wrapped global. All specializations return a
// pointer without performing consistency checks. This should be used
// cautiously, preferably only in constant expressions that take the address
// of the wrapped global.
constexpr T* operator&() { return &storage_; }
private:
template <typename, CheckType, Destructor>
friend class LazyInit;
// Explicitly destroys the wrapped global. Called by the specialization of
// LazyInit with the destructor enabled.
void Destruct() {
ZX_ASSERT(*initialized_);
storage_->T::~T();
// Prevent the compiler from omitting this write during destruction.
static_cast<volatile bool&>(*initialized_) = false;
}
// Lazy-initialized storage for a value of type T.
internal::LazyInitStorage<T> storage_;
// Guard variable to check for multiple initializations and access before
// initialization.
internal::LazyInitStorage<bool> initialized_;
};
// Specialization that provides atomic consistency checks. Checks are guaranteed
// to be consistent under races over initialization.
template <typename T>
class LazyInit<T, CheckType::Atomic, Destructor::Disabled> {
public:
constexpr LazyInit() = default;
~LazyInit() = default;
LazyInit(const LazyInit&) = delete;
LazyInit& operator=(const LazyInit&) = delete;
LazyInit(LazyInit&&) = delete;
LazyInit& operator=(LazyInit&&) = delete;
// Explicitly constructs the wrapped global. Asserts that initialization is
// not already performed.
// Returns a reference to the newly constructed global.
template <typename... Args>
std::enable_if_t<std::is_constructible_v<T, Args&&...>, T&> Initialize(Args&&... args) {
static_assert(alignof(LazyInit) >= alignof(T));
TransitionState(State::Uninitialized, State::Constructing);
new (&storage_) T(std::forward<Args>(args)...);
state_->store(State::Initialized, std::memory_order_release);
return *storage_;
}
// Returns a reference to the wrapped global. Asserts that initialization
// is already performed. The effects of initialization are guaranteed to be
// visible if the assertion passes.
T& Get() {
AssertState(State::Initialized, state_->load(std::memory_order_relaxed));
return *storage_;
}
// Accesses the wrapped global by pointer. Asserts that initialization is
// already perfromed. The effects of initialization are guaranteed to be
// visible if the assertion passes.
T* operator->() { return &Get(); }
// Returns a pointer to the wrapped global. All specializations return a
// pointer without performing consistency checks. This should be used
// cautiously, preferably only in constant expressions that take the address
// of the wrapped global.
constexpr T* operator&() { return &storage_; }
private:
template <typename, CheckType, Destructor>
friend class LazyInit;
// Enum representing the states of initialization.
enum class State : int {
Uninitialized = 0,
Constructing,
Initialized,
Destructing,
Destroyed,
};
// Asserts that the expected state matches the actual state.
void AssertState(State expected, State actual) {
ZX_ASSERT_MSG(expected == actual, "expected=%d actual=%d", static_cast<int>(expected),
static_cast<int>(actual));
}
// Transitions the guard from the |expected| state to the |target| state.
// Asserts that the expected state matches the actual state.
void TransitionState(State expected, State target) {
State actual = state_->load(std::memory_order_relaxed);
AssertState(expected, actual);
while (!state_->compare_exchange_weak(actual, target, std::memory_order_acquire,
std::memory_order_relaxed)) {
AssertState(expected, actual);
}
}
// Explicitly destroys the wrapped global. Called by the specialization of
// LazyInit with the destructor enabled.
void Destruct() {
TransitionState(State::Initialized, State::Destructing);
storage_->T::~T();
state_->store(State::Destroyed, std::memory_order_release);
}
// Lazy-initialized storage for a value of type T.
internal::LazyInitStorage<T> storage_;
// Guard variable to check for multiple initializations and access before
// initialization.
internal::LazyInitStorage<std::atomic<State>> state_;
};
// Specialization that includes a global destructor. This type is based on the
// equivalent specialization without a global destructor and simply calls the
// base type Destruct() method.
template <typename T, CheckType Check>
class LazyInit<T, Check, Destructor::Enabled> : public LazyInit<T, Check, Destructor::Disabled> {
public:
using LazyInit<T, Check, Destructor::Disabled>::LazyInit;
~LazyInit() { this->Destruct(); }
};
} // namespace lazy_init