編譯時間物件集合討論

本文件將探討如何主動討論在 C++ 中建構物件的編譯時間集合。下列使用案例是編譯時間集合實用的用途:

  • StringRef - 一種類型,支援建構具有相關不重複數字 ID 的字串標籤編譯時間集合,以供追蹤之用。
  • LockClass - 支援建構狀態物件的編譯時間集合,以便驗證執行階段鎖定。

以下各節將討論各種用途的常見需求、實作方式目前的挑戰,以及建議的解決方案。

字串參照

StringRef 是一種實作字串參照概念的型別。字串參照是從數字 ID 對應至字元字串。使用對應會提高追蹤緩衝區的成本效益:一個 (ID、字串) 組合會在追蹤工作階段中發出一次,然後後續事件可能會依 ID 參照字串,而非加入內嵌完整的字元序列。

以下是 StringRef 的簡易實際範例:

#include <lib/ktrace.h>

template <typename Op, typename... Args>
inline DoSomething(Op&& op, Args&&... args) {
    ktrace_probe(TraceAlways, TraceContext::Thread, "DoSomething"_stringref);
    // ...
}

這裡的字串文字運算子 _stringref 會傳回 StringRef 的執行個體,提供設施將「DoSomething」字串對應至追蹤函式使用的數字 ID。

相關規定

  • 在參照此 ID 的任何追蹤事件之前,在每個追蹤工作階段最多發出一次 (ID、字串) 對應。理想上,系統會在追蹤工作階段開始時一次發出完整的對應項目,以免因時間緊迫的程式碼而產生對應作業負擔,但這並非硬性規定。
  • 建議您使用密集的 ID 空間,因此下游處理程式碼可使用線性預先分配的陣列或向量來實作 ID 對字串查詢,但這並非硬性規定。
  • 不重複字串參照的一些方法,因為範本、內嵌函式和方法必須支援追蹤呼叫。

目前導入作業

以下是目前 StringRef 實作的大綱。

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 是一種類型,可擷取鎖定所有執行個體通用的鎖定相關資訊 (例如,內含的類型是結構體/類別成員、基礎鎖定基元的類型、說明其行為的旗標)。執行階段鎖定驗證工具會使用 LockClass 類型,判斷要套用至每個鎖定的排序規則,並找出用於記錄排序觀察的每個鎖定類別追蹤結構。

以下是 LockClass 的實際運作範例:

struct Foo {
    LockClass<Foo, fbl::Mutex> lock;
    // ...
};

struct Bar {
    LockClass<Bar, fbl::Mutex> lock;
};

相關規定

  • 可疊代 LockClass 所有例項化的追蹤狀態,以用於週期偵測和錯誤回報。
  • 建立不重複追蹤狀態的一些方法,因為視包含類型 (例如 Foo 和 Bar) 的使用方式而定,LockClass 的例項化可能是來自多個編譯單位。
  • 建議您使用密集的 ID 空間,以便下用串流處理程式碼簡化 ID 儲存空間,但這並非硬性規定。

目前導入作業

以下是 LockClass 的簡化實作:

template <typename ContainingType, typename LockType>
class LockClass {
    // ...
private:
    static LockClassState lock_class_state_;
};

每次建立 LockClass 執行個體時,系統都會建立一個不重複的 LockClassState 執行個體,用於追蹤與類別鎖定 (ContainingTypeLockType) 相關的線上鎖定訂單觀察結果。目前的 LockClassState 實作做法是在全域導數中建構所有執行個體的連結清單,以支援疊代要求。

編譯時間陣列解決方案

滿足這兩種類型需求的其中一種方式,是使用 COMDAT 區段和群組,建構已去除重複靜態執行個體的編譯時間陣列。這完全省去在初始化時間或執行階段建構已連結物件清單的麻煩,而且支援每種類型的所有要求。

例如:

// 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;
}

這種做法的困難在於 GCC 無法正確處理範本類型或函式靜態成員上的區段屬性。不過,Clang 會正確處理這些類型的區段屬性。