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:
The following sections discuss common and unique requirements of each use case, the current challenges with the implementations, and proposed solutions.
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:
#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.
The following is an outline of the current StringRef
implementation.
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 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:
struct Foo { LockClass<Foo, fbl::Mutex> lock; // ... }; struct Bar { LockClass<Bar, fbl::Mutex> lock; };
The following is a simplified implementation of LockClass:
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.
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:
// 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.