From 315c5fb7eb3c7da8a158062190884d52a489747a Mon Sep 17 00:00:00 2001
From: Alexandre Felipe <o.alexandre.felipe@gmail.com>
Date: Thu, 8 Oct 2026 18:46:31 +0100
Subject: [PATCH-v1 1/2] half compare exchange.

Unfortunately compare exchange doesn't accept a mask, when
a 64-bit value is used to store bit field structures in CAS loops,
sometimes the compare-exchange operation will fail due to changes
in irrelevant bits.

e.g. one thread running

Several threads running

while(!pg_atomic_compare_exchange_u64(ptr, &exp, new))
  new = exp | (v & 0x00000000ffffffff};
}

and others running

while(!pg_atomic_compare_exchange_u64(ptr, &exp, new))
  new = exp | (v2 & 0xffffffff00000000);
}

all threads will conflict with every other, the first operation is linearisable
with the second. This commits introduce pg_atomic_compare_exchange_u64_{lo,hi}
that allow acting acting on 32-bit  halves of a pg_atomic_uint64 separately.
---
 src/backend/port/atomics.c          | 47 +++++++++++++++++++++++++++++
 src/include/port/atomics.h          | 42 ++++++++++++++++++++++++++
 src/include/port/atomics/fallback.h |  4 +++
 src/include/port/atomics/generic.h  | 46 ++++++++++++++++++++++++++++
 4 files changed, 139 insertions(+)

diff --git a/src/backend/port/atomics.c b/src/backend/port/atomics.c
index 7a300a5c16d..861a7fe2755 100644
--- a/src/backend/port/atomics.c
+++ b/src/backend/port/atomics.c
@@ -70,4 +70,51 @@ pg_atomic_fetch_add_u64_impl(volatile pg_atomic_uint64 *ptr, int64 add_)
 	return oldval;
 }
 
+bool
+pg_atomic_compare_exchange_u64_lo_impl(volatile pg_atomic_uint64 *ptr,
+									   uint64 *expected, uint64 newval)
+{
+	bool		ret;
+	u64_halves *cur = (u64_halves *) &ptr->value;
+	u64_halves *exp = (u64_halves *) expected;
+	u64_halves *new = (u64_halves *) &newval;
+
+	SpinLockAcquire((slock_t *) &ptr->sema);
+
+	/* perform compare/exchange logic */
+	ret = cur->lo == exp->lo;
+	if (ret)
+		cur->lo = new->lo;
+	else
+		exp->lo = cur->lo;
+	/* and release lock */
+	SpinLockRelease((slock_t *) &ptr->sema);
+
+	return ret;
+}
+
+bool
+pg_atomic_compare_exchange_u64_hi_impl(volatile pg_atomic_uint64 *ptr,
+									   uint64 *expected, uint64 newval)
+{
+	bool		ret;
+	u64_halves *cur = (u64_halves *) &ptr->value;
+	u64_halves *exp = (u64_halves *) expected;
+	u64_halves *new = (u64_halves *) &newval;
+
+	SpinLockAcquire((slock_t *) &ptr->sema);
+
+	/* perform compare/exchange logic */
+	ret = cur->hi == exp->hi;
+	if (ret)
+		cur->hi = new->hi;
+	else
+		exp->hi = cur->hi;
+
+	/* and release lock */
+	SpinLockRelease((slock_t *) &ptr->sema);
+
+	return ret;
+}
+
 #endif							/* PG_HAVE_ATOMIC_U64_SIMULATION */
diff --git a/src/include/port/atomics.h b/src/include/port/atomics.h
index a605ea81d07..b7302b28ba1 100644
--- a/src/include/port/atomics.h
+++ b/src/include/port/atomics.h
@@ -611,6 +611,48 @@ pg_atomic_monotonic_advance_u64(volatile pg_atomic_uint64 *ptr, uint64 target)
 	return currval;
 }
 
+/*
+ * Compare and exchange the low half of a pg_atomic_uint64.
+ *
+ * Compare low half [31:0] of ptr with the low half of expected.
+ * If they are equal, change ptr.
+ * If they are not equal, refresh expected.
+ * The high half is not touched.
+ * Return true if the update changed ptr, false if it changed expected.
+ *
+ * Full barrier semantics.
+ */
+static inline bool
+pg_atomic_compare_exchange_u64_lo(volatile pg_atomic_uint64 *ptr,
+								  uint64 *expected, uint64 newval)
+{
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	AssertPointerAlignment(ptr, 8);
+#endif
+	return pg_atomic_compare_exchange_u64_lo_impl(ptr, expected, newval);
+}
+
+/*
+ * Compare and exchange the high half of a pg_atomic_uint64.
+ *
+ * Compare high half [31:0] of ptr with the high half of expected.
+ * If they are equal, change ptr.
+ * If they are not equal, refresh expected.
+ * The low half is not touched.
+ * Return true if the update changed ptr, false if it changed expected.
+ *
+ * Full barrier semantics.
+ */
+static inline bool
+pg_atomic_compare_exchange_u64_hi(volatile pg_atomic_uint64 *ptr,
+								  uint64 *expected, uint64 newval)
+{
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	AssertPointerAlignment(ptr, 8);
+#endif
+	return pg_atomic_compare_exchange_u64_hi_impl(ptr, expected, newval);
+}
+
 #undef INSIDE_ATOMICS_H
 
 #endif							/* ATOMICS_H */
diff --git a/src/include/port/atomics/fallback.h b/src/include/port/atomics/fallback.h
index f37225189fc..99eeac0bd91 100644
--- a/src/include/port/atomics/fallback.h
+++ b/src/include/port/atomics/fallback.h
@@ -35,6 +35,10 @@ extern void pg_atomic_init_u64_impl(volatile pg_atomic_uint64 *ptr, uint64 val_)
 #define PG_HAVE_ATOMIC_COMPARE_EXCHANGE_U64
 extern bool pg_atomic_compare_exchange_u64_impl(volatile pg_atomic_uint64 *ptr,
 												uint64 *expected, uint64 newval);
+extern bool pg_atomic_compare_exchange_u64_lo_impl(volatile pg_atomic_uint64 *ptr,
+												   uint64 *expected, uint64 newval);
+extern bool pg_atomic_compare_exchange_u64_hi_impl(volatile pg_atomic_uint64 *ptr,
+												   uint64 *expected, uint64 newval);
 
 #define PG_HAVE_ATOMIC_FETCH_ADD_U64
 extern uint64 pg_atomic_fetch_add_u64_impl(volatile pg_atomic_uint64 *ptr, int64 add_);
diff --git a/src/include/port/atomics/generic.h b/src/include/port/atomics/generic.h
index daa772e9a6d..624ba8177b6 100644
--- a/src/include/port/atomics/generic.h
+++ b/src/include/port/atomics/generic.h
@@ -428,3 +428,49 @@ pg_atomic_write_membarrier_u64_impl(volatile pg_atomic_uint64 *ptr, uint64 val)
 	(void) pg_atomic_exchange_u64_impl(ptr, val);
 }
 #endif
+
+/*
+ * Plain uint32 halves of a uint64. lo is bits [31:0] and hi is bits [63:32].
+ */
+typedef struct u64_halves
+{
+#ifndef WORDS_BIGENDIAN
+	uint32		lo;
+	uint32		hi;
+#else
+	uint32		hi;
+	uint32		lo;
+#endif
+} u64_halves;
+
+static_assert(sizeof(u64_halves) == sizeof(uint64),
+			  "u64_halves must overlay a uint64");
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+
+static_assert(sizeof(pg_atomic_uint32) == sizeof(uint32),
+			  "pg_atomic_uint32 must overlay a uint32");
+
+static inline bool
+pg_atomic_compare_exchange_u64_lo_impl(volatile pg_atomic_uint64 *ptr,
+									   uint64 *expected, uint64 newval)
+{
+	volatile pg_atomic_uint32 *p;
+	uint32 *exp = &((u64_halves *) expected)->lo;
+	uint32 new = ((u64_halves *) &newval)->lo;
+	p = (volatile pg_atomic_uint32 *) &((u64_halves *) &ptr->value)->lo;
+	return pg_atomic_compare_exchange_u32_impl(p, exp, new);
+}
+
+static inline bool
+pg_atomic_compare_exchange_u64_hi_impl(volatile pg_atomic_uint64 *ptr,
+									   uint64 *expected, uint64 newval)
+{
+	volatile pg_atomic_uint32 *p;
+	uint32 *exp = &((u64_halves *) expected)->hi;
+	uint32 new = ((u64_halves *) &newval)->hi;
+	p = (volatile pg_atomic_uint32 *) &((u64_halves *) &ptr->value)->hi;
+	return pg_atomic_compare_exchange_u32_impl(p, exp, new);
+}
+
+#endif							/* !PG_HAVE_ATOMIC_U64_SIMULATION */
-- 
2.53.0

