small cleanup for s_lock.h

First seen: 2026-05-04 21:49:44+00:00 · Messages: 8 · Participants: 4

Latest Update

2026-05-06 · opus 4.7

Cleanup of s_lock.h: API Contract Clarification for Spinlocks

Context and Motivation

src/include/storage/s_lock.h is one of the oldest and most platform-sensitive headers in the PostgreSQL tree. It defines the low-level spinlock primitives (S_LOCK, S_UNLOCK, S_INIT_LOCK, SPIN_DELAY, TAS, TAS_SPIN) that underpin LWLocks and virtually every other synchronization primitive in the backend. Because spinlocks must be implemented with platform-specific atomic instructions, the header historically contained large blocks of inline assembly per CPU/compiler combination, with fallback paths to out-of-line assembly files (tas.s) or to a C implementation in s_lock.c.

Over the last several release cycles the set of supported platforms and fallback mechanisms has been pruned aggressively:

The net result is that the surviving code paths always satisfy TAS via an inline macro (typically built on GCC's __sync_lock_test_and_set intrinsic or hand-rolled inline asm), yet the header still advertises — in comments and via an extern int tas(volatile slock_t *lock) declaration — a fallback that no longer exists. This is dead code that also misleads porters.

The Cleanup Nathan Bossart Proposed

The initial patch set had two independent goals:

  1. 0001: Remove the stale extern int tas(...) declaration and the unconditional #define TAS(lock) tas(lock) that followed it, since no implementation is ever linked in.
  2. 0002: Remove HAS_TEST_AND_SET entirely. Nathan's observation was that HAS_TEST_AND_SET is defined in essentially every branch where TAS is defined and nowhere else — so it's a redundant alias for "is TAS defined?". Direct #ifdef TAS checks would be clearer.

Tom Lane pushed back on (2) but engaged constructively with (1), suggesting the declaration be replaced by an explicit #error "must provide a spinlock implementation" guard so that a misconfigured build fails loudly at preprocessing rather than at link time with a missing-symbol error. He also proposed consolidating the HAS_TEST_AND_SET definitions, which are currently scattered across every per-platform block, into a single location at the end of the header:

#if defined(TAS)
#define HAS_TEST_AND_SET
#else
#error "must provide a spinlock implementation"
#endif

The Deeper Issue Tom Surfaced: A Broken API Contract

After a round of exchanges about comment wording, Tom identified what is really the central architectural defect. The header comment currently states:

Usually, S_LOCK() is implemented in terms of even lower-level macros TAS() and TAS_SPIN()

Because every supported platform today happens to follow this layering, the codebase has drifted into treating TAS() as a mandatory porting interface. Tom's point is that this inverts the abstraction: the contract of s_lock.h should be "you must provide S_LOCK()", and TAS() is merely the conventional building block we use to construct it. A hypothetical platform that has a very good native mutex or a pthread_spin_lock-style primitive should be free to supply S_LOCK() directly without faking a TAS().

This reframing has concrete consequences for the cleanup:

Tradeoffs and Design Decisions

Secondary Cleanup Surface

Kirill Reshke observed that tas.s still appears in meson.build, .gitignore, and src/backend/Makefile despite having no remaining producers. These references are vestigial and should be removed as part of the same cleanup for consistency with commit 25f36066dd.

Weight of Opinions

Expected Outcome

The converging design is:

  1. Remove the bogus extern int tas(...) declaration.
  2. Consolidate HAS_TEST_AND_SET handling, then likely remove it entirely since it has no consumers.
  3. Re-anchor the mandatory-interface check on S_LOCK rather than TAS, with #error on absence.
  4. Avoid compiling s_lock.c's s_lock() helper when TAS is undefined.
  5. Rewrite the header comment to reflect that compiler intrinsics are now the mainstream implementation, and to give porters a realistic picture of which macros they actually need to supply.
  6. Scrub remaining tas.s references from meson.build, .gitignore, and the legacy Makefile.