The PostgreSQL C Dialect

From PostgreSQL wiki
Jump to navigationJump to search

THIS PAGE IS A WORK IN PROGRESS. DO NOT RELY ON IT FOR DEVELOPMENT UNTIL THIS WARNING IS REMOVED.

This page is an attempt to gather in one place the cross-cutting C conventions and idioms that pervade the PostgreSQL backend — the things behind the common remark that PostgreSQL is written less in C than in a "PostgreSQL C dialect." It is descriptive, not normative, and it is certainly not authoritative: where it disagrees with the source tree or with the official documentation, those win. Corrections, additions, and arguments about where its boundaries should fall are all welcome — please edit freely, or raise points on the talk page or on pgsql‑hackers.

It deliberately does not restate two things that already have canonical homes in the manual: the PostgreSQL Coding Conventions chapter (formatting, error reporting, the error message style guide) and the C‑Language Functions chapter. Those are referenced where relevant rather than copied.

Scope and status

This page covers conventions a reader needs in order to follow backend C code in any subsystem: memory management, error handling, the function‑call interface, the node system, the common container types, the concurrency primitives, the frontend/backend split, and the vocabulary that lives in c.h.

It deliberately stops at the boundary of any single subsystem. Where a convention only matters inside one area — the lock manager's partitioning scheme, the nbtree concurrency protocol, the WAL record format — this page links to that subsystem's own README rather than reproducing it. The working test is: include it if you need it to read code anywhere; link it if it only matters in one place. That boundary is a judgment call, and reasonable contributors will draw it differently; the open cases are collected under #Open scope questions below.

Currency. Everything here is written against the PostgreSQL 18 source tree. The dialect is stable in outline but not frozen in detail, and several pieces below have changed within the span of currently‑supported releases. Those points are flagged inline as Changed in N. If you are reading this well after it was written, treat it as a map and verify against current source; a confidently‑wrong page is worse than none, which is also why the formatting and error‑message conventions are left to the manual, where editing the surrounding code tends to force the documentation to keep pace.

What this page does not cover

  • Formatting and layout. Tab‑stop indentation, BSD brace style, the 80‑column guideline, comment‑block conventions, and so on: see the manual's Formatting section. The rules are enforced mechanically by src/tools/pgindent (a Perl driver over pg_bsd_indent), so there is little point styling new code by hand.
  • Error message wording. Capitalisation, punctuation, what belongs in primary vs. detail vs. hint: see the Error Message Style Guide. The mechanics of raising an error are covered below; the wording rules are not.
  • The SQL‑callable function ABI in full. The tutorial treatment of writing a C function lives in C‑Language Functions; src/backend/utils/fmgr/README is the in‑tree reference. This page summarises only the parts of that interface a reader must recognise to follow backend code.
  • Subsystem internals. Each major area carries its own README (the planner, the executor, transaction management, the lock manager, access methods, logical replication, and more). Those are the right homes for subsystem‑specific idioms; this page points at them rather than absorbing them.

Memory management: contexts and palloc

Backend code almost never calls malloc/free directly. It allocates with palloc() (and palloc0(), repalloc(), pfree()), which draw from the current memory context.

Memory contexts form a tree rooted at TopMemoryContext. Each allocation belongs to some context; resetting or deleting a context frees everything allocated in it and in its child contexts in one operation. This is the central idea, and it inverts the usual C habit: most code does not pair every palloc with a pfree. Instead, a context is created for a phase of work (a query, a tuple, a transaction), and the whole context is reset or deleted when that phase ends. The rationale, stated bluntly in the Developer FAQ, is that it is easier to free everything at the end of a well‑defined scope than to track every individual allocation.

The standard idiom for working in a different context is switch‑do‑restore:

MemoryContext old = MemoryContextSwitchTo(some_context);
/* ... palloc()s here land in some_context ... */
MemoryContextSwitchTo(old);

Contexts you will see named explicitly include TopMemoryContext (process lifetime), CurrentMemoryContext (the active one), ErrorContext (small, reserved, and pre‑allocated so that error handling itself cannot fail for lack of memory), CacheMemoryContext, MessageContext, and the executor's per‑query and per‑tuple contexts (ecxt_per_query_memory, ecxt_per_tuple_memory).

This connects directly to error handling (next section): when an ERROR is thrown, the abort path resets the relevant contexts, which is why leaking memory on an error path is usually a non‑issue. Non‑memory resources are a different matter and are handled by resource owners, also described below.

Several allocator implementations sit behind the context interface, chosen to match an allocation pattern:

  • aset (aset.c) — the general‑purpose default, created via AllocSetContextCreate with sizing macros such as ALLOCSET_DEFAULT_SIZES.
  • slab (slab.c) — fixed‑size chunks, for workloads that allocate and free many objects of one size.
  • generation (generation.c) — for roughly FIFO allocate‑then‑free patterns (logical decoding is the motivating case).
  • bump (bump.c, added in PostgreSQL 17) — allocate‑only, no per‑chunk free, for build‑once / free‑all‑at‑end workloads such as tuple sorting.

Canonical references: src/backend/utils/mmgr/README; headers src/include/utils/palloc.h and src/include/utils/memutils.h.

Error handling: ereport, elog, and the exception model

Errors are raised with ereport() or elog(). elog(level, "format", ...) is the short form for internal "this should not happen" conditions; ereport() is for everything user‑facing and takes auxiliary calls inside its argument list:

ereport(ERROR,
        (errcode(ERRCODE_DIVISION_BY_ZERO),
         errmsg("division by zero")));

The auxiliary functions include errcode(), errmsg(), errdetail(), errhint(), and a number of others. (Their wording is governed by the style guide, which this page leaves to the manual.)

Severity levels run from the DEBUG family up through LOG, INFO, NOTICE, WARNING to ERROR, FATAL, and PANIC. The crucial behavioural fact is that ERROR and above do not return. ERROR performs a non‑local exit to the nearest enclosing handler (ultimately the transaction abort path); FATAL terminates the backend; PANIC brings down the whole server.

The exception mechanism is built on setjmp/longjmp. Most code never touches it directly — it simply throws and lets the transaction machinery unwind — but when a region needs to catch and re‑raise, the structure is:

PG_TRY();
{
    /* code that might throw */
}
PG_CATCH();
{
    /* cleanup; usually ends by re-raising */
    PG_RE_THROW();
}
PG_END_TRY();

Because ERROR is a non‑local jump, manual memory cleanup on error paths is normally unnecessary (contexts handle it). What you do have to account for are non‑memory resources — buffer pins, lock references, open files, tuple descriptors. These are tracked by resource owners (ResourceOwner) and released during abort. The division of labour is worth internalising: memory contexts reclaim memory; resource owners reclaim everything else.

Changed in 13: PG_FINALLY() was added, giving a try/finally form alongside try/catch.

Changed in 16: a "soft error" path was added so that input functions can report a failure without throwing — errsave() and ereturn() with an explicit Node *escontext, plus callers such as InputFunctionCallSafe(). New input‑function code should be aware of it.

Canonical references: the manual's Reporting Errors Within the Server; header src/include/utils/elog.h.

The function manager (fmgr) and the version-1 calling convention

Every SQL‑callable C function uses the "version 1" convention. The function is declared with a fixed signature and accesses its arguments and result through macros:

PG_FUNCTION_INFO_V1(my_func);

Datum
my_func(PG_FUNCTION_ARGS)
{
    int32 a = PG_GETARG_INT32(0);
    if (PG_ARGISNULL(1))
        PG_RETURN_NULL();
    PG_RETURN_INT32(a + PG_GETARG_INT32(1));
}

PG_FUNCTION_ARGS expands to FunctionCallInfo fcinfo. Arguments come in through PG_GETARG_*(n), results go out through PG_RETURN_*(), nulls are tested with PG_ARGISNULL(n) and produced with PG_RETURN_NULL(). A loadable module must contain PG_MODULE_MAGIC once, and PG_FUNCTION_INFO_V1() for each exported function.

Underlying all of this is Datum: a uniform, pointer‑width value type. Pass‑by‑value types are stored in the Datum directly; pass‑by‑reference types are stored as a pointer. Conversions are explicit and ubiquitous — Int32GetDatum/DatumGetInt32, PointerGetDatum/DatumGetPointer, and so on. By a long‑standing convention noted in the fmgr README, the GETARG/RETURN macros that yield or take a pointer end in _P (for example PG_GETARG_TEXT_P yields a text *).

Changed in 12: the call‑info struct was renamed from FunctionCallInfoData to FunctionCallInfoBaseData — deliberately, to break code that allocated it the old way — and the former parallel arrays fcinfo->arg[i] / fcinfo->argnull[i] were replaced by a single fcinfo->args[i] array of NullableDatum (each entry carrying .value and .isnull). Extension code written for 11 or earlier that reaches into arg/argnull directly will not compile against 12+. This is one of the more common surprises when porting older C functions forward.

Canonical references: the manual's C‑Language Functions; src/backend/utils/fmgr/README; header src/include/fmgr.h.

The node system

Nearly every structure passed through the parser, rewriter, planner, and executor is a "node": a struct whose first member is a NodeTag. The tag turns a heterogeneous tree of structs into something a small set of generic operations can walk.

The vocabulary:

  • makeNode(Type) — allocate and tag a node.
  • IsA(ptr, Type) — test a node's tag.
  • nodeTag(ptr) — read the tag.
  • castNode(Type, ptr) — a checked downcast (asserts the tag, then casts).

The generic operations defined over any node are copyObject() (deep copy), equal() (deep structural comparison), nodeToString() (serialise), and stringToNode() (deserialise). These are not incidental: they are how a plan tree gets copied into the plan cache, compared, and serialised to ship to parallel workers. Serialisation of plan trees is real, load‑bearing infrastructure, not a debugging convenience.

Changed in 16 — and this is important, because it changes the workflow itself: the copy, equal, out, and read functions (and the NodeTag enum, and query jumbling) are now generated at build time by src/backend/nodes/gen_node_support.pl from annotations on the struct definitions, rather than hand‑maintained in copyfuncs.c / equalfuncs.c / outfuncs.c / readfuncs.c. To control how a field is handled you annotate it with pg_node_attr(...) — for example no_copy, no_equal, equal_ignore, read_write_ignore, copy_as(...), or whole‑node attributes such as custom_copy_equal and nodetag_only. You no longer edit the four support files by hand for an ordinary new field.

This is a place where existing secondary sources are actively misleading: at time of writing the Developer FAQ still describes the old manual procedure of adding support to copyfuncs.c and equalfuncs.c. Following it on a current tree produces work that the build system would have done for you, or conflicts with the generator. Superseding that kind of stale guidance is part of why a consolidated page is worth having — and equally a reminder of how quickly one rots.

Canonical references: src/backend/nodes/README; header src/include/nodes/nodes.h (which lists the full set of node attributes). For inspecting node trees while debugging, pprint() dumps a tree to the server log.

Common container and utility types

List

List is the backend's workhorse sequence, used everywhere in the parser and planner. It comes in a pointer form (List, elements usually Node *, declared void * to reduce casting) and integer forms for int, Oid, and (more recently) TransactionId. The empty list is always the null pointer, written NIL.

Typical operations: lappend(), lcons(), list_concat(), linitial(), lsecond(), llast(), list_nth(), list_length(), and iteration with foreach(cell, list) reading each element via lfirst(cell). Recent releases also provide typed iteration macros (foreach_node, foreach_ptr, foreach_int, foreach_oid) that hand you the element directly.

Changed in 13 — and this is the single most consequential currency point on this page. List was, for most of PostgreSQL's history, a cons‑cell linked list. In 13 it was rewritten as a flat, expansible array while keeping the "List" name and most of the API. The practical consequences for anyone reading or writing backend code:

  • A ListCell *, and the list's backing array, are invalidated by insertion or deletion. You can no longer cache a cell pointer across a mutation, and the classic "delete the current cell during foreach" pattern from cons‑cell days is no longer valid as written.
  • list_delete_cell() and several related functions changed signature in the rewrite.

The broader lesson is the one this whole effort is partly about: a great deal of pre‑13 code and, worse, pre‑13 advice about List is wrong against a modern tree. Old blog posts and old extension code are a hazard precisely here. The header comment in src/include/nodes/pg_list.h tells the story of the rewrites and is worth reading directly.

StringInfo

A resizable string/byte buffer (src/include/lib/stringinfo.h). Created with makeStringInfo() or initStringInfo(); appended to with appendStringInfo() (printf‑style), appendStringInfoString(), appendStringInfoChar(); emptied with resetStringInfo(). It is the standard tool for building up text and is also the basis of the client/server protocol buffers.

Bitmapset

A compact set of small non‑negative integers (src/include/nodes/bitmapset.h): bms_add_member(), bms_is_member(), bms_union(), bms_intersect(), bms_difference(), bms_next_member(). Heavily used in the planner, where sets of relation ids are everywhere.

Hash tables

Two facilities you will encounter:

  • dynahash (src/backend/utils/hash/dynahash.c): the classic hash_create() / hash_search() API (the table type is HTAB). It backs both shared‑memory and backend‑local hash tables.
  • simplehash (src/include/lib/simplehash.h): a header template you instantiate by defining SH_* macros, producing a typed, open‑addressing, cache‑friendly hash table. Used in performance‑sensitive spots.

Tuplestore

A tuplestore (src/include/utils/tuplestore.h, src/backend/utils/sort/tuplestore.c; opaque type Tuplestorestate) holds an ordered sequence of tuples that begins in memory and spills to a temporary file once it exceeds a memory budget — the transition is transparent to the caller. It is one of the most widely encountered facilities in the backend, so it earns a place here even though it is more an executor utility than a plain container.

What distinguishes it from an array of tuples is that it preserves insertion order while also supporting rewind, optional backward scanning, mark/restore, and multiple independent read pointers — so a result set can be re‑read, or several consumers can read the same materialised data at different positions. Its sibling tuplesort (tuplesort.c) also begins in memory and spills, but reorders tuples into sorted output; if you want sorted results you want tuplesort, not tuplestore.

Lifecycle:

  • Create with tuplestore_begin_heap(randomAccess, interXact, maxKBytes). randomAccess enables rewind/backward/mark (and, because it must be able to revisit any row, forces the store to retain everything); interXact lets the backing temp file outlive the current transaction (holdable cursors); maxKBytes is the in‑memory budget before spilling, conventionally work_mem.
  • Where you create it matters, and this is the usual bug. A tuplestore is not a pure memory object: at creation it captures both the current memory context and the current ResourceOwner, and once it spills it holds an open temporary file owned by that resource owner. It must therefore be created in a memory context and under a resource owner that live at least as long as it will be read. In the executor and in set‑returning functions that means the per‑query context (rsinfo->econtext->ecxt_per_query_memory), never a per‑tuple or otherwise short‑lived context. A store created in too short a scope behaves correctly while it stays in memory and fails — freed file reference — only once the data grows large enough to spill, which is exactly the kind of bug that survives light testing.
  • Fill with tuplestore_puttupleslot() (from a TupleTableSlot), tuplestore_puttuple() (a HeapTuple), or tuplestore_putvalues() (a Datum/null array). Internally every tuple is stored as a compact MinimalTuple.
  • Read with tuplestore_gettupleslot(forward, copy, slot) into a slot, returning false at the end. Because storage is minimal‑tuple form, the slot is typically a minimal‑tuple slot; the copy flag governs whether the slot receives its own copy that survives the next fetch.
  • Reposition (only meaningful with randomAccess): tuplestore_rescan() returns to the start, tuplestore_markpos()/tuplestore_restorepos() mark and return, and several consumers share one set through tuplestore_alloc_read_pointer()/tuplestore_select_read_pointer().
  • Reuse and dispose: tuplestore_clear() empties a store for reuse and resets its read pointers (cheaper than end + begin, used when a node rescans); tuplestore_trim() discards tuples before the oldest read pointer to reclaim memory; tuplestore_end() destroys the store and removes any temp file. Something must call tuplestore_end() — in the executor that is tied to node shutdown.

You will meet tuplestores behind the Material and CTE‑scan nodes, in WindowAgg (one partition buffered with several read pointers), in set‑returning functions running in materialize mode, in PL set‑returning functions and RETURN QUERY, in trigger transition tables (OLD TABLE/NEW TABLE), and behind holdable cursors. The interaction with TupleTableSlot noted above is one reason the slot conventions are a candidate for inclusion here; see #Open scope questions.

Intrusive lists

For the cases where List's node‑ness or array reallocation is unwanted, src/include/lib/ilist.h provides intrusive doubly‑ and singly‑linked lists (dlist, slist), where the link fields live inside the elements themselves.

Concurrency primitives

PostgreSQL backends are separate processes sharing a memory segment, so the synchronisation toolkit is its own, not the C library's.

  • Spinlocks (s_lock.h): SpinLockInit(), SpinLockAcquire(), SpinLockRelease(). For critical sections of a few instructions only. They busy‑wait, so you must not hold one across anything that could block, palloc, or throw an error.
  • LWLocks (lwlock.h): LWLockAcquire() / LWLockRelease() in shared or exclusive mode — lightweight reader/writer locks protecting shared‑memory structures. They can block. Note the distinction that trips people up: LWLocks are not tracked by the deadlock detector (that is the heavyweight lock manager's job), so lock‑ordering discipline is on you. They are released automatically on error during abort.
  • Atomics (src/include/port/atomics.h): a portable layer — pg_atomic_uint32/pg_atomic_uint64, with pg_atomic_read_u32(), pg_atomic_compare_exchange_u32(), pg_atomic_fetch_add_u32(), and memory barriers (pg_memory_barrier(), pg_read_barrier(), pg_write_barrier()). Where a platform lacks native support, the layer falls back to a spinlock‑backed emulation.
  • Condition variables (condition_variable.h): ConditionVariableSleep() / ConditionVariableSignal(), for waiting on changes to shared‑memory state.

The heavyweight lock managerLockAcquire(), the lock modes, deadlock detection — is by the test of this page a subsystem, not a dialect‑wide primitive: it is used through a narrow interface and its internals matter in one place. See src/backend/storage/lmgr/README. The contrast with spinlocks/LWLocks/atomics, which appear in code throughout the tree, is a concrete illustration of where the scope line falls.

The frontend/backend split and portability

PostgreSQL's C is compiled into two worlds: the backend (the server) and the frontend (client programs and tools). Several conventions exist to keep one body of code serving both.

  • The first include. Every .c file includes exactly one of postgres.h (backend), postgres_fe.h (frontend), or c.h (shared, lowest common denominator) as its first header, before anything else, including system headers. The Committing checklist gives the reason: it guarantees pg_config_os.h is seen before any system header, which keeps large‑file and similar options consistent across the whole translation unit. System and external‑library headers come after this and before other Postgres headers.
  • The FRONTEND macro. Frontend compiles define FRONTEND. Backend‑only facilities (memory contexts, palloc, backend elog) do not exist there, so headers guard backend‑only inline functions with #ifndef FRONTEND.
  • PGDLLIMPORT. Any backend global variable an extension might reference must be marked PGDLLIMPORT so it is exported on Windows. Omitting it produces code that links on Unix and fails to link on Windows — a frequent and easily‑missed defect.
  • Integer widths. Use PostgreSQL's own width‑explicit types — int16/int32/int64 and the unsigned counterparts, plus Size, Index, and the INT64_FORMAT/UINT64_FORMAT printf strings — rather than bare long/long long, whose widths vary by platform.

The language baseline is C99. A handful of otherwise‑legal C99 features are disallowed in core code (variable‑length arrays, intermingled declarations and code, // comments, universal character names); later‑standard or compiler‑specific features may be used only with a portable fallback. The full statement is in the manual's Miscellaneous Coding Conventions; it is restated briefly here only because it is so much a part of what makes the code look the way it does.

The c.h vocabulary

c.h is the master header that everything includes, directly or transitively. Reading it top to bottom is the closest thing that exists to a glossary of the dialect's base layer. The items you will meet constantly:

  • Assert(condition) — a no‑op unless the tree was built with --enable-cassert. Never put side effects inside one. StaticAssertStmt(), StaticAssertDecl(), and StaticAssertExpr() give compile‑time checks (C11 _Static_assert with a C99 fallback).
  • FLEXIBLE_ARRAY_MEMBER — the portable spelling for a trailing flexible array member, used pervasively (the varlena types, the fmgr args array, and many more).
  • lengthof(array) — element count of a fixed‑size array.
  • Min(), Max(), Abs() — the obvious macros; mind the double evaluation.
  • The compiler‑attribute wrappers: pg_attribute_noreturn(), pg_attribute_printf(), pg_attribute_unused(), pg_noinline, pg_nodiscard, and pg_unreachable() — portable spellings with fallbacks where a compiler lacks the underlying feature.
  • Validity helpers such as PointerIsValid() and OidIsValid().
  • unconstify() and unvolatize() — checked casts that strip a qualifier without silently stripping others.

Changed in 12: bool became the C99 <stdbool.h> type (a one‑byte type), where historically PostgreSQL defined its own. Code and assumptions from before then should be checked.

After c.h, the next header to read is src/include/postgres.h, which adds the backend‑specific core: Datum, the varlena machinery, and NullableDatum.

Signals and latches

Because backends are processes, asynchronous coordination uses signals together with latches, under a strict discipline.

A signal handler does almost nothing. The rule, stated in the manual's Miscellaneous Coding Conventions, is that a handler should record that the signal arrived — typically setting a volatile sig_atomic_t flag — and wake the main code with SetLatch(MyLatch), then return. The real work happens later, at a safe point in the main loop.

static void
handle_sighup(SIGNAL_ARGS)
{
    got_SIGHUP = true;
    SetLatch(MyLatch);
}

The waiting side uses the latch API (src/include/storage/latch.h): WaitLatch(), ResetLatch(), SetLatch(), with MyLatch being the process's own latch; the WaitEventSet API generalises this to wait on several latches, sockets, and timeouts at once.

Cooperative cancellation runs through CHECK_FOR_INTERRUPTS() (in src/include/miscadmin.h): any long‑running loop must call it so that query cancel and backend termination take effect. Regions that must not be interrupted are bracketed with HOLD_INTERRUPTS() / RESUME_INTERRUPTS().

Debugging and assertion aids

Briefly, since these are how the conventions above are exercised in practice:

  • pprint(node) dumps a node tree to the server log; invaluable when working in the parser, rewriter, planner, or executor.
  • --enable-cassert activates Assert and a set of memory‑debugging behaviours (freed‑memory clobbering, context checking, randomised allocations) that turn latent bugs into visible ones.
  • The Doxygen source browser and the Backend Flowchart (linked from the Developer FAQ) help with orientation.

Open scope questions

The boundary of this page is a judgment call, and the following are genuinely unsettled — listed so they can be argued rather than silently decided. The line currently drawn is noted in each case; please disagree on the talk page.

  • Catalog and syscache access (SearchSysCache, GETSTRUCT, ReleaseSysCache, heap_form_tuple). Pervasive across the backend, which argues for inclusion; tightly bound to the catalog cache subsystem, which argues for a link to src/backend/utils/cache/. Currently: excluded, link only.
  • Executor tuple slots (TupleTableSlot, slot_getattr, the ExecStore* family). Everywhere in the executor, but arguably an executor‑subsystem idiom rather than a tree‑wide one. Currently: excluded.
  • SPI (SPI_connect, SPI_execute). The in‑backend SQL interface, used by procedural languages and some core code. Dialect, or subsystem? Currently: excluded.
  • TOAST / varlena detoasting (PG_DETOAST_DATUM, VARDATA, VARSIZE). Arguably part of the Datum story and therefore dialect; arguably a storage‑subsystem concern. Currently: a passing mention under c.h/postgres.h.
  • GUC definition (DefineCustomIntVariable and friends). Extensions need it; core uses a different path. Include for the extension author's benefit, or treat as extension‑facing API documented elsewhere? Currently: excluded.
  • Naming conventions (snake‑case functions, the recurring Coll/_internal/_P suffixes). Partly covered in the Developer FAQ. Restate here for completeness, or link? Currently: only the _P convention is mentioned, in passing.
  • Depth of the atomics/barrier treatment. How much of the memory‑ordering model to spell out before it stops being "dialect" and starts being a topic of its own.

References

Canonical documentation (the conventions this page does not repeat):

Project wiki:

  • Committing checklist — "frequently cited, but not well documented, project policies"; the nearest existing capture of dialect folklore.
  • Developer FAQ — orientation, debugging, naming notes (parts predate the node‑support generator; see #The node system).

Key headers:

  • src/include/c.h, src/include/postgres.h — the base vocabulary and the backend core.
  • src/include/fmgr.h — the function‑call interface.
  • src/include/utils/palloc.h, src/include/utils/memutils.h — memory contexts.
  • src/include/utils/elog.h — error handling.
  • src/include/nodes/nodes.h, src/include/nodes/pg_list.h, src/include/nodes/bitmapset.h — the node system and core containers.
  • src/include/lib/stringinfo.h, src/include/lib/simplehash.h, src/include/lib/ilist.h — utility data structures.
  • src/include/storage/lwlock.h, src/include/port/atomics.h, src/include/storage/latch.h, src/include/miscadmin.h — concurrency, waiting, interrupts.

Key subsystem READMEs (linked, not absorbed):

  • src/backend/utils/mmgr/README — memory management.
  • src/backend/utils/fmgr/README — the function manager.
  • src/backend/nodes/README — the node system.
  • src/backend/optimizer/README — the planner.
  • src/backend/access/transam/README — transactions and WAL.
  • src/backend/storage/lmgr/README — the lock manager.