blob: 67ffb8c9d726ce836739824e4232c47145b7ee80 [file] [log] [blame]
// Copyright 2018 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 <atomic>
#include <fbl/algorithm.h>
#include <stdint.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <lockdep/common.h>
#include <lockdep/lock_dependency_set.h>
#include <lockdep/lock_traits.h>
#include <type_traits>
namespace lockdep {
// Type that holds the essential information and state for a lock class. This is
// used by ThreadLockState to uniformly operate on the variety of lock classes
// created by each template instantiation of LockClass. Each template
// instantiation of LockClass creates a unique static instance of LockClassState.
class LockClassState {
public:
// Constructs an instance of LockClassState.
LockClassState(const char* const name,
LockDependencySet* dependency_set,
LockFlags flags)
: name_{name},
dependency_set_{dependency_set},
flags_{flags} {}
// Disable copy construction / assignment.
LockClassState(const LockClassState&) = delete;
LockClassState& operator=(const LockClassState&) = delete;
// Returns the LockClassState instance for the given lock class id. The id must
// be a valid lock class id.
static LockClassState* Get(LockClassId id) {
return reinterpret_cast<LockClassState*>(id);
}
// Returns the type name of the lock class for the given lock class id.
static const char* GetName(LockClassId id) { return Get(id)->name_; }
// Returns true if lock class given by |search_id| is in the dependency set of
// the lock class given by |id|, false otherwise.
static bool HasLockClass(LockClassId id, LockClassId search_id) {
return Get(id)->dependency_set_->HasLockClass(search_id);
}
// Adds the lock class given by |add_id| to the dependency set of the lock
// class given by |id|.
static LockResult AddLockClass(LockClassId id, LockClassId add_id) {
return Get(id)->dependency_set_->AddLockClass(add_id);
}
// Returns true if the given lock class is irq-safe, false otherwise.
static bool IsIrqSafe(LockClassId id) {
return !!(Get(id)->flags_ & LockFlagsIrqSafe);
}
// Returns true if the given lock class is nestable, false otherwise.
static bool IsNestable(LockClassId id) {
return !!(Get(id)->flags_ & LockFlagsNestable);
}
// Returns true if reporting is disabled for the given lock class, false
// otherwise.
static bool IsReportingDisabled(LockClassId id) {
return !!(Get(id)->flags_ & LockFlagsReportingDisabled);
}
// Returns true if the validator should abort the program if it detects an
// invalid re-acquire with this lock class.
static bool IsReAcquireFatal(LockClassId id) {
return !!(Get(id)->flags_ & LockFlagsReAcquireFatal);
}
// Returns true if the lock should not be added to the active lock list
// during an acquire.
static bool IsActiveListDisabled(LockClassId id) {
return !!(Get(id)->flags_ & LockFlagsActiveListDisabled);
}
// Returns true if the lock should not be tracked.
static bool IsTrackingDisabled(LockClassId id) {
return !!(Get(id)->flags_ & LockFlagsTrackingDisabled);
}
// Iterator type to traverse the set of LockClassState instances.
class Iterator {
public:
Iterator() = default;
Iterator(const Iterator&) = default;
Iterator& operator=(const Iterator&) = default;
LockClassState& operator*() { return *state_; }
Iterator operator++() {
state_ = state_->next_;
return *this;
}
bool operator!=(const Iterator& other) const {
return state_ != other.state_;
}
Iterator begin() { return {*Head()}; }
Iterator end() { return {nullptr}; }
private:
Iterator(LockClassState* state)
: state_{state} {}
LockClassState* state_{nullptr};
};
// Returns an iterator for the init-time linked list of state instances.
static Iterator Iter() { return {}; }
// Returns the lock class id for this instance. The id is the address of the
// instance.
LockClassId id() const { return reinterpret_cast<LockClassId>(this); }
// Returns the name of this lock class.
const char* name() const { return name_; }
// Return the flags of this lock class.
LockFlags flags() const { return flags_; }
// Returns the dependency set for this lock class.
const LockDependencySet& dependency_set() const { return *dependency_set_; }
LockClassState* connected_set() { return LoopDetector::FindSet(&loop_node_)->ToState(); }
// Runs a loop detection pass on the set of lock classes to find possible
// circular lock dependencies.
static void LoopDetectionPass() {
static LoopDetector detector;
detector.DetectionPass();
}
uint64_t index() const { return loop_node_.index; }
uint64_t least() const { return loop_node_.least; }
// Resets the dependency set and disjoint set of this object. This is
// primarily used to initialize the state between successive tests.
void Reset() {
dependency_set_->clear();
loop_node_.Reset();
}
private:
// The name of the lock class type.
const char* const name_;
// The set of out edges from this node in the lock class dependency graph.
// Out edges represent lock classes that have been held before this class.
LockDependencySet* const dependency_set_;
// Flags specifying which which rules to apply during lock validation.
const LockFlags flags_;
// Linked list pointer to the next state instance. This list is constructed
// by a global initializer and never modified again. The list is used by the
// loop detector and runtime lock inspection commands to access the complete
// list of lock classes.
LockClassState* next_{InitNext(this)};
// Returns a pointer to the head pointer of the state linked list.
static LockClassState** Head() {
static LockClassState* head{nullptr};
return &head;
}
// Updates the linked list to include the given state node and returns the
// previous head. This is used by the global initializer to setup the
// next_ member.
static LockClassState* InitNext(LockClassState* state) {
LockClassState* head = *Head();
*Head() = state;
return head;
}
// Per-lock class state used by the loop detection algorithm.
struct LoopNode {
// The parent of the disjoint sets this node belongs to. Nodes start out
// alone in their own set. Sets are joined by the loop detector when
// found within a cycle.
std::atomic<LoopNode *> parent{this};
// Linked list node for the loop detector's active node stack. The use
// of a linked list of statically allocated nodes avoids dynamic memory
// allocation during graph traversal.
LoopNode* next{nullptr};
// Index values used by the loop detector algorithm.
uint64_t index{0};
uint64_t least{0};
// Returns a pointer to the LockClassState instance that contains this
// LoopNode. This allows the loop detector to mostly operate in terms
// of LoopNode instances, simplifying expressions in the main algorithm.
LockClassState* ToState() {
static_assert(std::is_standard_layout<LockClassState>::value,
"LockClassState must be standard layout!");
uint8_t* byte_pointer = reinterpret_cast<uint8_t*>(this);
uint8_t* state_pointer =
byte_pointer - __offsetof(LockClassState, loop_node_);
return reinterpret_cast<LockClassState*>(state_pointer);
}
// Performs a relaxed, weak compare exchange on the parent pointer of
// this loop node. Due to the loops in FindSet() and UnionSet() this
// may fail due to races, the result is not required and will be
// retried based on other conditions. Relaxed order is used because
// neither caller publishes any other stores, nor depends on any other
// loads.
void CompareExchangeParent(LoopNode** expected, LoopNode* desired) {
parent.compare_exchange_weak(*expected, desired,
std::memory_order_relaxed,
std::memory_order_relaxed);
}
// Removes this node from whatever disjoint set it belongs to and
// returns it to its own separate set.
void Reset() {
parent.store(this, std::memory_order_relaxed);
}
};
// Loop detector node.
LoopNode loop_node_;
// Loop detection using Tarjan's strongly connected components algorithm to
// efficiently identify loops and disjoint set structures to store and
// update the sets of nodes involved in loops.
// NOTE: The loop detector methods, except FindSet and UnionSets, must only
// be called from the loop detector thread.
struct LoopDetector {
// The maximum index of the last loop detection run. Node index values
// are compared with this value to determine whether to revisit the
// node. Using a generation count permits running subsequent passes
// without first clearing the state of every node.
uint64_t generation{0};
// Running counter marking the step at which a node has been visited or
// revisited.
uint64_t index{0};
// The head of the stack of active nodes in a path traversal. The bottom
// of the stack is marked with the sentinel value 1 instead of nullptr
// to simplify determining whether a node is on the stack. Every node on
// the stack has LoopNode::next != nulltpr.
LoopNode* stack{reinterpret_cast<LoopNode*>(1)};
// Performs a single traversal of the lock dependency graph and updates
// the disjoint set structures with any detected loops.
void DetectionPass() {
// The next generation starts at the end of the previous. All nodes
// with indices less than or equal to the generation are revisited.
generation = index;
for (auto& state : LockClassState::Iter()) {
if (state.loop_node_.index <= generation)
Connect(&state.loop_node_);
}
}
// Recursively traverses a node path and updates the disjoint set
// structures when loops are detected.
void Connect(LoopNode* node) {
index += 1;
node->index = index;
node->least = index;
Push(node);
// Evaluate each node along the out edges of the dependency graph.
const auto& out_edges = node->ToState()->dependency_set();
for (LockClassId id : out_edges) {
LoopNode* related_node = &Get(id)->loop_node_;
if (related_node->index <= generation) {
Connect(related_node);
node->least = fbl::min(node->least, related_node->least);
} else if (related_node->next != nullptr) {
node->least = fbl::min(node->least, related_node->index);
}
}
// Update the disjoint set structures. Other nodes above this one on
// the stack are merged into this set.
if (node->index == node->least) {
LoopNode* top = nullptr;
size_t set_size = 0;
while (top != node) {
top = Pop();
UnionSets(node, top);
set_size++;
}
// Report loops with more than two components. Basic inversions
// with only two locks are reported by ThreadLockState::Acquire.
if (set_size > 2) {
LoopNode* root = FindSet(node);
SystemCircularLockDependencyDetected(root->ToState());
}
}
}
// Pushes a node on the active nodes stack.
void Push(LoopNode* node) {
ZX_DEBUG_ASSERT(node->next == nullptr);
node->next = stack;
stack = node;
}
// Pops a node from the active nodes stack.
LoopNode* Pop() {
ZX_DEBUG_ASSERT(stack != reinterpret_cast<LoopNode*>(1));
LoopNode* node = stack;
stack = node->next;
node->next = nullptr;
return node;
}
// Finds the parent node of the disjoint set this node belongs to. This
// approach applies thread-safe path splitting to flatten the set as it
// traverses the path, using the two-try optimization suggested by
// Jayanti and Tarjan.
static LoopNode* FindSet(LoopNode* node) {
while (true) {
// First pass: either terminate or attempt path split.
LoopNode* parent = node->parent.load(std::memory_order_relaxed);
LoopNode* grandparent = parent->parent.load(std::memory_order_relaxed);
if (parent == grandparent)
return parent;
node->CompareExchangeParent(&parent, grandparent);
// Second pass: either terminate, retry if last pass failed, or
// advance and attempt path split.
parent = node->parent.load(std::memory_order_relaxed);
grandparent = parent->parent.load(std::memory_order_relaxed);
if (parent == grandparent)
return parent;
node->CompareExchangeParent(&parent, grandparent);
// Advance regardless of whether split succeeded or failed.
node = parent;
}
}
// Joins the disjoint sets for the given nodes. Performs linking based
// on address order, which approximates the randomized total order
// suggested by Jayanti and Tarjan.
static void UnionSets(LoopNode* a, LoopNode* b) {
while (true) {
LoopNode* root_a = FindSet(a);
LoopNode* root_b = FindSet(b);
a = root_a;
b = root_b;
if (root_a == root_b) {
return; // Nothing to do for nodes in the same set.
} else if (root_a < root_b) {
root_b->CompareExchangeParent(&root_b, root_a);
} else {
root_a->CompareExchangeParent(&root_a, root_b);
}
}
}
};
};
// Runs a loop detection pass to find circular lock dependencies. This must be
// invoked at some point in the future after the lock validator calls the
// system-defined SystemTriggerLoopDetection().
static inline void LoopDetectionPass() {
LockClassState::LoopDetectionPass();
}
} // namespace lockdep