#include <iostream>
// 1. Define the master list of colors (X Macro)
// This is the single source of truth. Adding a color here
// automatically updates both the enum and the string converter below.
#define COLORS \
FUNC(Red) \
FUNC(Blue) \
FUNC(Green)
// 2. Generate the Enum Class
enum class Color {
#define FUNC(name) name,
COLORS
#undef FUNC
};
// 3. Generate a Helper Function to convert Enum to String using '#' (stringification)
const char* ColorToString(Color color) {
switch (color) {
#define FUNC(name) case Color::name: return #name;
COLORS
#undef FUNC
default: return "Unknown";
}
}
int main() {
// Instantiate color variables
Color r = Color::Red;
Color b = Color::Blue;
Color g = Color::Green;
// Output and verify the mapping works correctly
std::cout << "Color r is: " << ColorToString(r) << " (Enum value: " << static_cast<int>(r) << ")\n";
std::cout << "Color b is: " << ColorToString(b) << " (Enum value: " << static_cast<int>(b) << ")\n";
std::cout << "Color g is: " << ColorToString(g) << " (Enum value: " << static_cast<int>(g) << ")\n";
return 0;
}
Aug 3, 2026
[C++26] Reflection minute
Aug 2, 2026
[cmake] phases and generator expressions
Demystifying Modern CMake: Interface Libraries, Generator Expressions, and Build Phases
Category: C++ & Build Systems
Reading Time: 8 min read
If you've been working with C++ in recent years, you've likely encountered CMake code snippets like this header-only library setup:
add_library(vactor INTERFACE)
add_library(vactor::vactor ALIAS vactor)
target_include_directories(vactor INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
$<INSTALL_INTERFACE:include>
)
While this looks short, it packs in three critical Modern CMake concepts: Interface targets, Generator Expressions, and explicit separation of Build vs. Install environments. Let's break down how this works under the hood.
1. Header-Only Libraries with INTERFACE
In traditional CMake, every target compiled source files into .a, .so, or .lib binaries. But header-only C++ libraries don't generate binaries.
By specifying INTERFACE in add_library(vactor INTERFACE), you tell CMake:
"This library has no binary files to compile directly. It only acts as a container for properties (include directories, compile flags, options) that downstream targets will inherit."
How does CMake know which header file to compile?
It doesn't—and doesn't need to! CMake doesn't pass header files directly to compilers. Instead, it passes directory flags (like-I /path/to/headers) to the compiler. The compiler's C++ preprocessor then finds#include <vactor/vactor.hpp>when parsing code.
2. Why the ALIAS Pattern?
Creating an alias target like add_library(vactor::vactor ALIAS vactor) is a modern best practice:
- Namespaced Uniformity: When installing libraries and importing them via
find_package(), CMake targets are usually namespaced (Package::Target). Using anALIASensures internal subprojects use the exact same target name as external consumers. - Early Error Catching: If you misspell an un-namespaced target in
target_link_libraries(), CMake might assume it's a raw system library name and defer the error to link-time. Namespaced targets with::trigger immediate CMake configure-time errors.
3. Decoding Generator Expressions ($<...>)
The syntax $<KEYWORD:VALUE> is a CMake Generator Expression. Standard variables like ${MY_VAR} are evaluated sequentially when CMake reads the script. Generator expressions, however, are deferred evaluation rules.
In our snippet:
$<BUILD_INTERFACE:...>: Evaluates to the given path ONLY when building within the local source tree or as a subproject viaadd_subdirectory().$<INSTALL_INTERFACE:...>: Evaluates to the given path ONLY when exported and imported viafind_package().
Without this separation, absolute build directory paths from your local machine would leak into installed config files, breaking the build on end-user machines!
4. The Four Phases of CMake
To fully grasp generator expressions, it helps to understand that CMake execution spans four separate execution phases:
- Configure Phase (
cmake -S . -B build)
CMake readsCMakeLists.txtline-by-line, runs system checks, evaluates standard variables (${VAR}) andif()logic, constructing the internal target graph. - Generate Phase (End of
cmake -S . -B build)
CMake evaluates all$<...>generator expressions using the full target graph and writes native build files (Ninja rules, Makefiles, or MSBuild.vcxprojfiles). - Build Phase (
cmake --build build)
The underlying build tool (Ninja, Make, MSVC) executes the compiler (g++/clang++) using the generated command-line flags. - Runtime Phase
The user runs the compiled executable. CMake is no longer active.
Summary: Generator Expression Quick Reference
| Generator Expression | Description / Usage |
|---|---|
$<CONFIG:Debug> |
Evaluates to 1 if current target configuration is Debug, else 0. |
$<COMPILE_LANGUAGE:CXX> |
Evaluates to 1 if current file is compiled with C++ compiler. |
$<TARGET_FILE:target> |
Returns full output path to the target binary file. |
$<CXX_COMPILER_ID:GNU> |
Evaluates to 1 if using GCC compiler. |
By leveraging INTERFACE targets and generator expressions, you ensure your C++ libraries stay portable, clean, and easy to consume across both local and installed environments!
Comprehensive Guide to CMake Generator Expressions ($<...>)
CMake Generator Expressions are evaluated during build system generation (the Generate Phase) [cite: 1.1.4, 1.2.1]. They allow conditional compilation, string transformation, target queries, and cross-platform flag customization [cite: 1.1.2, 1.2.5].
1. Conditional & Logical Expressions
Boolean generator expressions evaluate to 1 (true) or 0 (false) [cite: 1.2.5]. They are most commonly used inside conditional output blocks like $<$<CONDITION>:true_string> [cite: 1.1.4, 1.2.5].
Conditional Output
| Expression | Description |
|---|---|
$<$<CONDITION>:string> |
Evaluates to string if CONDITION is 1, otherwise evaluates to an empty string [cite: 1.1.4, 1.2.5]. |
$<IF:condition,true_val,false_val> |
Evaluates to true_val if condition is 1, or false_val if 0 [cite: 1.1.4]. |
Logical Operators
| Expression | Description |
|---|---|
$<BOOL:string> |
Converts string to 0 or 1 using standard CMake boolean logic (e.g., OFF, FALSE, 0, empty evaluate to 0) [cite: 1.1.1, 1.2.5]. |
$<AND:cond1,cond2,...> |
Evaluates to 1 if all conditions evaluate to 1, otherwise 0 [cite: 1.2.5]. |
$<OR:cond1,cond2,...> |
Evaluates to 1 if at least one condition evaluates to 1, otherwise 0 [cite: 1.2.5]. |
$<NOT:condition> |
Evaluates to 0 if condition is 1, otherwise 1 [cite: 1.2.5]. |
2. Comparisons (Strings, Numbers, Versions)
String & List Comparisons
| Expression | Description |
|---|---|
$<STREQUAL:str1,str2> |
1 if str1 and str2 are equal (case-sensitive), else 0 [cite: 1.2.5]. |
$<IN_LIST:item,list> |
1 if item is present in the semicolon-separated list, else 0 [cite: 1.2.5]. |
Numeric Comparisons
| Expression | Description |
|---|---|
$<EQUAL:val1,val2> |
1 if numbers val1 and val2 are equal, else 0 [cite: 1.2.5]. |
$<LESS:val1,val2> |
1 if val1 is strictly less than val2, else 0. |
$<GREATER:val1,val2> |
1 if val1 is strictly greater than val2, else 0. |
$<LESS_EQUAL:val1,val2> |
1 if val1 is less than or equal to val2, else 0. |
$<GREATER_EQUAL:val1,val2> |
1 if val1 is greater than or equal to val2, else 0. |
Version Comparisons
| Expression | Description |
|---|---|
$<VERSION_EQUAL:v1,v2> |
1 if version v1 equals v2, else 0 [cite: 1.2.5]. |
$<VERSION_LESS:v1,v2> |
1 if version v1 is less than v2, else 0 [cite: 1.2.5]. |
$<VERSION_GREATER:v1,v2> |
1 if version v1 is greater than v2, else 0 [cite: 1.2.5]. |
$<VERSION_LESS_EQUAL:v1,v2> |
1 if v1 is less than or equal to v2, else 0 [cite: 1.2.5]. |
$<VERSION_GREATER_EQUAL:v1,v2> |
1 if v1 is greater than or equal to v2, else 0 [cite: 1.2.5]. |
3. Platform, Compiler & Language Queries
These queries allow writing cross-platform CMake configurations that adjust compiler flags and build settings automatically [cite: 1.2.5].
Compiler & Platform Identification
| Expression | Description |
|---|---|
$<CONFIG:config_name> |
1 if current build configuration matches config_name (e.g., Debug, Release) [cite: 1.2.5]. |
$<PLATFORM_ID:id_list> |
1 if the host/target platform matches any ID in the list (e.g., Linux, Windows, Darwin) [cite: 1.2.5]. |
$<C_COMPILER_ID:id_list> |
1 if the C compiler matches an ID in the list (e.g., GNU, Clang, MSVC) [cite: 1.2.5]. |
$<CXX_COMPILER_ID:id_list> |
1 if the C++ compiler matches an ID in the list [cite: 1.2.5]. |
$<C_COMPILER_VERSION:ver> |
1 if C compiler version matches ver [cite: 1.2.5]. |
$<CXX_COMPILER_VERSION:ver> |
1 if C++ compiler version matches ver [cite: 1.2.5]. |
$<COMPILE_LANGUAGE:lang> |
1 if the source file currently being compiled uses language lang (e.g., C, CXX, CUDA) [cite: 1.2.1, 1.2.3]. |
$<COMPILE_LANG_AND_ID:lang,ids> |
1 if language matches lang AND compiler ID matches ids. |
$<COMPILE_FEATURES:features> |
1 if all specified compile features are available for the target [cite: 1.2.2]. |
4. Target & Artifact Queries
These expressions extract build artifact metadata, output filenames, and locations dynamically across all platforms [cite: 1.1.3].
File Paths & Artifact Names
| Expression | Description |
|---|---|
$<TARGET_FILE:target> |
Full path to the main binary file produced by target (e.g., /usr/lib/libfoo.so or C:/app.exe) [cite: 1.1.3, 1.2.1]. |
$<TARGET_FILE_NAME:target> |
Filename of the target binary file (e.g., app.exe). |
$<TARGET_FILE_DIR:target> |
Directory containing the target binary file [cite: 1.2.1]. |
$<TARGET_LINKER_FILE:target> |
Full path to the file used for linking against target (.lib, .a, .so) [cite: 1.2.1]. |
$<TARGET_LINKER_FILE_NAME:target> |
Filename of the linker file. |
$<TARGET_LINKER_FILE_DIR:target> |
Directory containing the linker file. |
$<TARGET_SONAME_FILE:target> |
Full path to the file with soname (.so.1) [cite: 1.2.1]. |
$<TARGET_PDB_FILE:target> |
Full path to the Visual Studio .pdb debug symbols file. |
Property Queries & Existence
| Expression | Description |
|---|---|
$<TARGET_PROPERTY:target,prop> |
Value of property prop on target [cite: 1.2.1]. |
$<TARGET_PROPERTY:prop> |
Value of property prop on the target being evaluated [cite: 1.2.1]. |
$<TARGET_NAME_IF_EXISTS:target> |
Returns target if target exists, else empty string. |
$<TARGET_EXISTS:target> |
1 if target exists, else 0. |
$<TARGET_GENEX_EVAL:target,expr> |
Evaluates expr in the context of target [cite: 1.2.1]. |
5. String & List Manipulations
String Transformations
| Expression | Description |
|---|---|
$<LOWER_CASE:string> |
Converts string to lowercase [cite: 1.1.1]. |
$<UPPER_CASE:string> |
Converts string to uppercase [cite: 1.1.1]. |
$<MAKE_C_IDENTIFIER:string> |
Converts string into a valid C identifier (replaces non-alphanumeric chars with _). |
List Transformations & Operations
| Expression | Description |
|---|---|
$<JOIN:list,glue> |
Joins elements in list with the delimiter string glue [cite: 1.2.1]. |
$<REMOVE_DUPLICATES:list> |
Removes duplicate entries from list. |
$<FILTER:list,operator,regex> |
Filters list entries using an INCLUDE or EXCLUDE regex operator. |
$<LIST:ACTION,list,...> |
Executes list sub-commands (LENGTH, GET, SUBLIST, FIND, TRANSFORM, etc.) [cite: 1.1.1]. |
6. Path & Interface Expressions
Path Operations
| Expression | Description |
|---|---|
$<PATH:HAS_PARENT_PATH,path> |
1 if path has a parent directory, else 0. |
$<PATH:GET_FILENAME,path> |
Extracts the filename portion from path. |
$<PATH:GET_PARENT_PATH,path> |
Extracts the parent directory path from path. |
$<PATH:NORMAL_PATH,path> |
Returns normalized clean path (resolving . and ..). |
Build & Install Interfaces
| Expression | Description |
|---|---|
$<BUILD_INTERFACE:paths...> |
Included only when building within the source/build tree [cite: 1.1.4]. |
$<INSTALL_INTERFACE:paths...> |
Included only when consumed from an installed package via find_package() [cite: 1.1.1, 1.1.4]. |
7. Escaping & Special Characters
Because CMake uses characters like , and > for parsing generator expressions, escaping expressions are required when passing literal special characters inside generator expressions [cite: 1.1.2, 1.2.5].
| Generator Expression | Literal Evaluated String |
|---|---|
$<ANGLE-R> |
> |
$<COMMA> |
, |
$<SEMICOLON> |
; |
$<LOWER_THAN> |
< |
$<GREATER_THAN> |
> |
8. Summary Example
Combining multiple generator expressions for modern target configuration:
target_compile_options(my_app PRIVATE
# Enable strict warnings for GCC/Clang only in Debug build
$<$<AND:$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>,$<CONFIG:Debug>>:-Wall;-Wextra;-Werror>
# Disable exceptions when compiling C++ code on MSVC
$<$<AND:$<CXX_COMPILER_ID:MSVC>,$<COMPILE_LANGUAGE:CXX>>:/EHa->
)
Complete Reference: Built-in CMake Generator Expressions ($<...>)
CMake Generator Expressions are evaluated during build system generation (the Generate Phase). They allow conditional compilation, string transformation, target queries, and cross-platform flag customization.
1. Conditional & Logical Keys
Evaluates conditions or performs boolean operations (0 or 1).
| Expression | Description |
|---|---|
$<$<CONDITION>:value> |
Conditional output (outputs value if CONDITION is 1). |
$<IF:cond,true_val,false_val> |
Conditional branch selector. |
$<BOOL:string> |
Converts string to boolean 0 or 1. |
$<AND:cond1,cond2,...> |
Logical AND operator. |
$<OR:cond1,cond2,...> |
Logical OR operator. |
$<NOT:cond> |
Logical NOT operator. |
2. Comparison Keys
String & List Comparisons
| Expression | Description |
|---|---|
$<STREQUAL:str1,str2> |
Case-sensitive equality check. |
$<EQUAL:str1,str2> |
Same as STREQUAL (string comparison). |
$<IN_LIST:item,list> |
Checks if item exists inside a CMake list. |
Numeric Comparisons
| Expression | Description |
|---|---|
$<EQUAL:num1,num2> |
Numeric equality. |
$<LESS:num1,num2> |
Numeric less than (<). |
$<GREATER:num1,num2> |
Numeric greater than (>). |
$<LESS_EQUAL:num1,num2> |
Numeric less than or equal (<=). |
$<GREATER_EQUAL:num1,num2> |
Numeric greater than or equal (>=). |
Version Comparisons
| Expression | Description |
|---|---|
$<VERSION_EQUAL:v1,v2> |
Version string equality (=). |
$<VERSION_LESS:v1,v2> |
Version string less than (<). |
$<VERSION_GREATER:v1,v2> |
Version string greater than (>). |
$<VERSION_LESS_EQUAL:v1,v2> |
Version string less than or equal (<=). |
$<VERSION_GREATER_EQUAL:v1,v2> |
Version string greater than or equal (>=). |
3. Platform, Compiler & Query Keys
Build Environment & Platform
| Expression | Description |
|---|---|
$<CONFIG:cfg_list> |
Checks build configuration (e.g., Debug, Release). |
$<PLATFORM_ID:id_list> |
Checks target platform ID (e.g., Linux, Windows, Darwin). |
$<POLICY:policy_id> |
Checks CMake policy status (NEW/OLD). |
Compiler Identification
| Expression | Description |
|---|---|
$<C_COMPILER_ID:id_list> |
Checks C compiler ID (e.g., GNU, Clang, MSVC). |
$<CXX_COMPILER_ID:id_list> |
Checks C++ compiler ID. |
$<CUDA_COMPILER_ID:id_list> |
Checks CUDA compiler ID. |
$<OBJC_COMPILER_ID:id_list> |
Checks Objective-C compiler ID. |
$<OBJCXX_COMPILER_ID:id_list> |
Checks Objective-C++ compiler ID. |
$<Fortran_COMPILER_ID:id_list> |
Checks Fortran compiler ID. |
$<HIP_COMPILER_ID:id_list> |
Checks HIP compiler ID. |
$<C_COMPILER_VERSION:ver> |
Checks C compiler version. |
$<CXX_COMPILER_VERSION:ver> |
Checks C++ compiler version. |
$<CUDA_COMPILER_VERSION:ver> |
Checks CUDA compiler version. |
$<OBJC_COMPILER_VERSION:ver> |
Checks Objective-C compiler version. |
$<OBJCXX_COMPILER_VERSION:ver> |
Checks Objective-C++ compiler version. |
$<Fortran_COMPILER_VERSION:ver> |
Checks Fortran compiler version. |
$<HIP_COMPILER_VERSION:ver> |
Checks HIP compiler version. |
Language & Features
| Expression | Description |
|---|---|
$<COMPILE_LANGUAGE:lang> |
Active source file language (e.g., C, CXX, CUDA). |
$<COMPILE_LANG_AND_ID:lang,compiler_ids> |
Checks language AND compiler ID simultaneously. |
$<COMPILE_FEATURES:features> |
Checks required target compile features. |
$<LINK_LANGUAGE:lang> |
Linker language used for the binary target. |
$<LINK_LANG_AND_ID:lang,compiler_ids> |
Checks link language AND linker/compiler ID. |
4. Target & Artifact Property Keys
Paths & File Locations
| Expression | Description |
|---|---|
$<TARGET_FILE:target> |
Full path to primary binary. |
$<TARGET_FILE_NAME:target> |
Filename of primary binary. |
$<TARGET_FILE_DIR:target> |
Directory containing primary binary. |
$<TARGET_FILE_BASE_NAME:target> |
Base filename without prefix/extension. |
$<TARGET_FILE_PREFIX:target> |
Target output prefix (e.g., lib). |
$<TARGET_FILE_SUFFIX:target> |
Target output suffix (e.g., .so, .exe). |
$<TARGET_LINKER_FILE:target> |
Full path to linker library file (.a, .lib, .so). |
$<TARGET_LINKER_FILE_NAME:target> |
Filename of link library file. |
$<TARGET_LINKER_FILE_DIR:target> |
Directory containing link library file. |
$<TARGET_LINKER_FILE_BASE_NAME:target> |
Base filename of link library. |
$<TARGET_LINKER_FILE_PREFIX:target> |
Linker library prefix. |
$<TARGET_LINKER_FILE_SUFFIX:target> |
Linker library suffix. |
$<TARGET_SONAME_FILE:target> |
Full path to binary file with soname (.so.1). |
$<TARGET_SONAME_FILE_NAME:target> |
Filename of soname file. |
$<TARGET_SONAME_FILE_DIR:target> |
Directory containing soname file. |
$<TARGET_PDB_FILE:target> |
Full path to Visual Studio PDB debug file. |
$<TARGET_PDB_FILE_NAME:target> |
Filename of MSVC PDB file. |
$<TARGET_PDB_FILE_DIR:target> |
Directory containing MSVC PDB file. |
$<TARGET_PDB_FILE_BASE_NAME:target> |
Base name of MSVC PDB file. |
$<TARGET_BUNDLE_DIR:target> |
Directory of macOS Application Bundle. |
$<TARGET_BUNDLE_CONTENT_DIR:target> |
Content directory of macOS Application Bundle (Contents/). |
Target Metadata & Properties
| Expression | Description |
|---|---|
$<TARGET_PROPERTY:target,prop> |
Retrieves property prop from target. |
$<TARGET_PROPERTY:prop> |
Retrieves property prop from current target. |
$<TARGET_EXISTS:target> |
Checks if a target exists (1 or 0). |
$<TARGET_NAME_IF_EXISTS:target> |
Returns target name if exists, else empty string. |
$<TARGET_GENEX_EVAL:target,expr> |
Evaluates expression in context of target. |
$<GENEX_EVAL:expr> |
Evaluates expression recursively. |
$<TARGET_POLICY:policy_id> |
Evaluates policy in target context. |
$<TARGET_OBJECTS:target> |
List of .o/.obj object files from an OBJECT library target. |
$<LINK_ONLY:target> |
Includes link options for target without inheriting compile interface definitions. |
5. String & List Manipulation Keys
String Transformations
| Expression | Description |
|---|---|
$<LOWER_CASE:str> |
Lowercase conversion. |
$<UPPER_CASE:str> |
Uppercase conversion. |
$<MAKE_C_IDENTIFIER:str> |
Converts string into C-style identifier. |
List Transformations
| Expression | Description | |
|---|---|---|
$<JOIN:list,glue> |
Joins list with separator string. | |
$<REMOVE_DUPLICATES:list> |
Removes duplicates from list. | |
| `$<FILTER:list,INCLUDE\ | EXCLUDE,regex>` | Filters list using regular expressions. |
$<LIST:action,list,...> |
Generic list operations (LENGTH, GET, SUBLIST, FIND, TRANSFORM, etc.). |
6. Path & Interface Keys
Path Operations
| Expression | Description |
|---|---|
$<PATH:HAS_PARENT_PATH,path> |
Checks parent directory existence. |
$<PATH:GET_FILENAME,path> |
Extracts filename component. |
$<PATH:GET_EXTENSION,path> |
Extracts extension component. |
$<PATH:GET_STEM,path> |
Extracts filename without extension. |
$<PATH:GET_RELATIVE_PART,path> |
Extracts relative portion. |
$<PATH:GET_PARENT_PATH,path> |
Extracts parent path. |
$<PATH:GET_ROOT_NAME,path> |
Extracts root drive/server name. |
$<PATH:GET_ROOT_DIRECTORY,path> |
Extracts root path directory. |
$<PATH:GET_ROOT_PATH,path> |
Extracts full root path. |
$<PATH:NORMAL_PATH,path> |
Normalizes path syntax. |
$<PATH:RELATIVE_PATH,path,base_dir> |
Computes relative path. |
$<PATH:ABSOLUTE_PATH,path,base_dir> |
Computes absolute path. |
Target Interfaces
| Expression | Description |
|---|---|
$<BUILD_INTERFACE:paths...> |
Used during local build/subproject tree context. |
$<INSTALL_INTERFACE:paths...> |
Used when package is installed and imported via find_package(). |
$<BUILD_LOCAL_INTERFACE:paths...> |
Used only in current build tree (not inherited transitivity). |
7. Escaping & Output Characters
Special keys used to pass reserved syntactic characters inside generator expressions.
| Generator Expression | Description |
|---|---|
$<ANGLE-R> |
Right angle bracket > |
$<COMMA> |
Literal comma , |
$<SEMICOLON> |
Literal semicolon ; |
$<LOWER_THAN> |
Left angle bracket < |
$<GREATER_THAN> |
Right angle bracket > |
Aug 1, 2026
[C++][coroutine] Symmetric Transfer - what problem it solves
The Core Problem: Stack Overflow via Asymmetric Transfer
In early C++ Coroutines (Coroutines TS), resuming a coroutine meant calling .resume() inside `await_suspend()`. When coroutines synchronously complete in a loop or tail-recurse, every .resume() call pushes a new C++ stack frame without popping the old one, leading to stack overflow.
// ASYMMETRIC TRANSFER (Naive Approach)
void await_suspend(std::coroutine_handle<> h) {
// Calling .resume() pushes a new stack frame.
// If done in a deep loop or recursive chain, stack space explodes!
other_coro_.resume();
}
Stack Frame Accumulation:
[ loop_coroutine$resume ]
└─> [ task::awaiter::await_suspend ]
└─> [ child_coroutine$resume ]
└─> [ final_awaiter::await_suspend ]
└─> [ loop_coroutine$resume ] <-- STACK OVERFLOW!
The Solution: Symmetric Transfer
Symmetric transfer allows `await_suspend()` to return a std::coroutine_handle<> instead of void.
Returning a handle suspends the current coroutine frame, pops the current stack frame, and transfers execution directly to the returned handle via a tail call.
Stack usage remains O(1) regardless of how many synchronous suspension/resumes occur.
// SYMMETRIC TRANSFER (Modern C++20)
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) {
// Return the handle to transfer control directly.
// The compiler generates a tail-call: pops current stack frame, then resumes target.
return other_coro_;
}
Key Implementations
A. The Awaiter (`task::operator co_await`)
When `co_await child_task;` executes, transfer control directly to the child's handle:
struct task_awaiter {
std::coroutine_handle<promise_type> child_coro_;
bool await_ready() noexcept { return false; }
// Symmetric Transfer: Returns child handle to resume
std::coroutine_handle<> await_suspend(std::coroutine_handle<> awaiting_coro) noexcept {
// 1. Store caller as continuation in child's promise
child_coro_.promise().continuation = awaiting_coro;
// 2. Return child handle -> tail-call into child_coro_
return child_coro_;
}
void await_resume() noexcept {}
};
B. The Final Suspend (`promise_type::final_suspend`)
When a child coroutine finishes at `co_return`, transfer control back to its continuation (the caller):
struct final_awaiter {
bool await_ready() noexcept { return false; }
// Symmetric Transfer: Returns caller's handle to resume
std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> me) noexcept {
// Returns parent handle -> tail-call back to parent coroutine
return me.promise().continuation;
}
void await_resume() noexcept {}
};
struct promise_type {
std::coroutine_handle<> continuation{std::noop_coroutine()};
final_awaiter final_suspend() noexcept { return {}; }
// ...
};
Summary Matrix
Jul 12, 2026
[2026] recap read
C++
System
Jul 2, 2026
[C++] is inline a function always a good idea?
Reference:
A deep dive into SmallVector::push_back
https://llvm-compile-time-tracker.com/
Shrink-wrapping optimization
Shrink-wrapping is a compiler optimization technique designed to minimize the overhead of a function's prologue and epilogue by moving them so they execute only when absolutely necessary.To understand why this is useful, it helps to look at how a standard function handles memory and registers at the assembly level.
The Problem: Preemptive Over-Allocation
When a function executes, it often needs to save certain CPU registers to the stack (callee-saved registers) and allocate space for local variables. This setup is called the prologue. Before the function returns, it restores those registers and cleans up the stack, which is called the epilogue.By default, standard compilers place the prologue at the very beginning of a function and the epilogue at the very end.
If a function has a hot fast-path (e.g., an early return or a quick validation check) that doesn't actually use those registers or local variables, it still pays the CPU cycle tax of executing the prologue and epilogue.
How Shrink-Wrapping Fixes This
Shrink-wrapping analyzes the control flow graph (CFG) of a function to find the precise basic blocks where the saved registers or stack space are actually required. It then "shrinks" the scope of the prologue and epilogue, wrapping them tightly only around those specific paths.Before Shrink-Wrapping (Standard Layout)
After Shrink-Wrapping
Limitations: The "Rejoin" Problem
How to solve if shrink-wrap not working?
LLVM_ATTRIBUTE_NOINLINE void growAndPushBack(ValueParamT Elt) {
// in case Elt aliases storage that grow() invalidates
// This is very much edge-case considered.
T Tmp = Elt;
// +1 is sufficient, while internally doing
// exponential growth algorithm.
this->grow(this->size() + 1);
std::memcpy(reinterpret_cast<void *>(this->end()), &Tmp, sizeof(T));
// size is not just +1 but applied with
// exponential growth algorithm.
this->set_size(this->size() + 1);
}
void push_back(ValueParamT Elt) {
if (LLVM_UNLIKELY(this->size() >= this->capacity()))
return growAndPushBack(Elt);
std::memcpy(reinterpret_cast<void *>(this->end()), &Elt, sizeof(T));
this->set_size(this->size() + 1);
}
mov eax, [rdi + 8]
cmp eax, [rdi + 12]
jae growAndPushBack # TAILCALL
mov rcx, [rdi]
mov [rcx + rax*4], esi
inc dword ptr [rdi + 8]
retMoving the slow path into a separate COMDAT/section groups section as an out-of-line function further degrades its performance.
// noinline growAndPushBack is load-bearing for both Clang and GCC.
void DecodeMOVDDUPMask(unsigned n, llvm::SmallVectorImpl<int> &v) {
for (unsigned l = 0; l < n; l += 2)
for (unsigned i = 0; i < 2; ++i)
v.push_back(i);
}The noinline attribute is required here. Without it, Clang and GCC may inline the helper function, which defeats the optimization by reintroducing the prologue.Jul 1, 2026
[benchmark] using rdtsc counter
On modern processors, RDTSC does not count actual, variable CPU clock cycles affected by power-saving states (C-states) or Turbo Boost. Instead, it uses a feature called Invariant TSC.
Invariant TSC: The counter increments at a constant, fixed frequency (usually the base/nominal frequency of the processor), regardless of the current operational frequency or power state.
Implication: Because it measures reference time rather than actual executed core clock cycles, if CPU turbos up to 5GHz but its base frequency is 2.5GHz, RDTSC will still increment at the 2.5GHz rate.
Because modern CPUs execute instructions out-of-order, RDTSC can float ahead or behind the block of code we are trying to benchmark.
To get an accurate cycle count for a specific code snippet, we must fence the instruction.
RDTSCP: A serialized variant that waits until all previous instructions have executed before reading the counter (though it doesn't prevent subsequent instructions from moving above it).
LFENCE; RDTSC: The industry-standard way to benchmark. Placing an LFENCE (Load Fence) right before RDTSC forces the CPU to serialize execution, ensuring measure exactly what happens between fences.
The Fence Strategy
lfence (Load Fence): This instruction acts as a serializing barrier for instruction execution. It forces the CPU to wait until all previous instructions in the pipeline have completed execution before it allows any subsequent instructions to begin.The Goal: Putting lfence before rdtsc prevents the CPU from executing rdtsc early (out-of-order). It guarantees that everything you wanted to measure before this benchmark has truly finished before the timer starts.
The Register Mapping
The x86 rdtsc instruction reads the 64-bit Time-Stamp Counter and splits the value across two 32-bit registers:EDX gets the high-order 32 bits.
EAX gets the low-order 32 bits.
The inline assembly output constraints capture this:
"=a"(lo): Tells the compiler to bind the value in EAX (a) to the C variable lo.
"=d"(hi): Tells the compiler to bind the value in EDX (d) to the C variable hi.
uint64_t rdtsc_start() {
uint32_t lo; uint32_t hi;
__asm__ __volatile__(
"lfence\n\t"
"rdtsc"
: "=a"(lo), "=d"(hi)
:
: "memory");
return ((uint64_t)hi << 32) | lo;
}
The Fence Strategy
uint64_t rdtsc_end() {
uint32_t lo; uint32_t hi;
__asm__ __volatile__(
"rdtscp\n\t"
"lfence"
: "=a"(lo), "=d"(hi)
:
: "rcx", "memory");
return ((uint64_t)hi << 32) | lo;
}
Why __volatile__?
Without __volatile__, the compiler's optimization passes might conclude that reading a hardware counter is a pure function or that its order doesn't matter relative to adjacent C statements.The "memory" Clobber
The "memory" token tells the compiler that this assembly block read or wrote to arbitrary locations in RAM. This creates a compiler-level memory fence, forcing the compiler to flush registers back to memory before the block and reload them afterward. This stops the compiler from scheduling code movements across the boundary.[ Pre-benchmarking Code ]
----------------------------------- <- lfence forces completion of above
RDTSC (Start Timer)
===================================
[ Critical Code Block to Measure ] <- Cannot leak upwards (lfence blocks it)
=================================== <- Cannot leak downwards (rdtscp blocks it)
RDTSCP (Stop Timer)
----------------------------------- <- lfence forces completion of rdtscp
[ Post-benchmarking Code ]