blob: acea01cf74e4acc64db7e9a8ac3f6799fdb24103 [file] [log] [blame] [view]
# Compile-Time Object Collections Discussion
This document covers active discussion about building compile-time collections
of objects in C++. The following use cases are examples of where compile-time
collections are useful:
* StringRef - A type that supports building a compile-time collection of string
labels with associated unique numeric ids for tracing purposes.
* LockClass - A type that supports building a compile-time collection of state
objects for runtime lock validation purposes.
The following sections discuss common and unique requirements of each use case,
the current challenges with the implementations, and proposed solutions.
## StringRef
StringRef is a type that implements the concept of string references. A string
reference is a mapping from a numeric id to a character string. Using the
mapping makes more economical use of the trace buffer: an (id, string)
pair is emitted once in a tracing session and then subsequent events may refer
to the string by id instead of including the full character sequence inline.
The following is a simple example of `StringRef` in action:
```C++
#include <lib/ktrace.h>
template <typename Op, typename... Args>
inline DoSomething(Op&& op, Args&&... args) {
ktrace_probe(TraceAlways, TraceContext::Thread, "DoSomething"_stringref);
// ...
}
```
Here the string literal operator `_stringref` returns an instance of `StringRef`
that provides the facility to map the string "DoSomething" to a numeric id
used by the tracing function.
### Requirements
* Emit each (id, string) mapping at most once per tracing session, prior to any
trace event that references the id. Ideally the full set of mappings is
emitted at once at the start of a tracing session to avoid the overhead of
emitting mappings in the middle of timing sensitive code however, this is not
a hard requirement.
* A dense id space is desirable so that down stream processing code can use a
linear pre-allocated array or vector to implement the id-to-string lookup,
however this is not a hard requirement.
* Some method to unique duplicate string references, since trace calls must be
supported in template and inline functions and methods.
### Current Implementation
The following is an outline of the current `StringRef` implementation.
```C++
struct StringRef {
static constexpr int kInvalidId = -1;
const char* string{nullptr};
ktl::atomic<int> id{kInvalidId};
StringRef* next{nullptr};
int GetId() {
const int ref_id = id.load(ktl::memory_order_relaxed);
return ref_id == kInvalidId ? Register(this) : ref_id;
}
// Returns the head of the global string ref linked list.
static StringRef* head() { return head_.load(ktl::memory_order_acquire); }
private:
// Registers a string ref in the global linked list.
static int Register(StringRef* string_ref);
static ktl::atomic<int> id_counter_;
static ktl::atomic<StringRef*> head_;
};
// Returns an instance of StringRef that corresponds to the given string
// literal.
template <typename T, T... chars>
inline StringRef* operator""_stringref() {
static const char storage[] = {chars..., '\0'};
static StringRef string_ref{storage};
return &string_ref;
}
```
## LockClass
LockClass is a type that captures information about a lock that is common to all
instances of the lock (e.g. its containing type if it is a struct/class member,
the type of the underlying lock primitive, flags describing its behavior). The
LockClass type is used by the runtime lock validator to determine which ordering
rules apply to each lock and to locate the per-lock-class tracking structure
used to record ordering observations.
The following is a simplified example of LockClass in action:
```C++
struct Foo {
LockClass<Foo, fbl::Mutex> lock;
// ...
};
struct Bar {
LockClass<Bar, fbl::Mutex> lock;
};
```
### Requirements
* Ability to iterate the tracking state for all instantiations of LockClass, for
cycle detection and error reporting purposes.
* Some method to unique duplicate tracking state, since instantiations of
LockClass may be visible from multiple compilation units, depending on how the
containing types (e.g. Foo and Bar) are used.
* A dense id space is desirable so that down stream processing code can simplify
id storage however, this is not a hard requirement.
### Current Implementation
The following is a simplified implementation of LockClass:
```C++
template <typename ContainingType, typename LockType>
class LockClass {
// ...
private:
static LockClassState lock_class_state_;
};
```
Each instantiation of `LockClass` creates a unique instance of `LockClassState`
to track the online lock order observations related to locks of class
(`ContainingType`, `LockType`). The current implementation of `LockClassState`
constructs a linked list of all instances in a global ctor to support the
iteration requirement.
## Compile-Time Array Solution
One way to address the requirements of both types is to build a compile-time
array of de-duplicated static instances, using COMDAT sections and groups. This
completely removes the need to build linked lists of objects at init time or
runtime and supports all of the requirements for each type.
For example:
```C++
// Defined in the linker script mark the beginning and end of the section:
// .data.lock_class_state_table.
extern "C" LockClassState __start_lock_class_state_table[];
extern "C" LockClassState __end_lock_class_state_table[];
template <typename ContainingType, typename LockClass>
class LockClass {
// ...
private:
static LockClassState lock_class_state_ __SECTION(".data.lock_class_state_table");
};
// Defined in the linker script to make the beginning and end of the section:
// .rodata.string_ref_table.
extern "C" StringRef __start_string_ref_table[];
extern "C" StringRef __end_string_ref_table[];
struct StringRef {
const char* const string;
size_t GetId() const {
return static_cast<size_t>(this - __start_string_ref_table);
}
};
template <typename T, T... chars>
inline StringRef* operator""_stringref() {
static const char storage[] = {chars..., '\0'};
static StringRef string_ref __SECTION(".rodata.string_ref_table") {storage};
return &string_ref;
}
```
The challenge with this approach is that GCC does not properly handle the
section attribute on static members of template types or functions. However,
Clang does handle the section attribute correctly for these types.