#ifndef HAVE_SPINLOCKS
+
+/*
+ * No TAS, so spinlocks are implemented as PGSemaphores.
+ */
+
+#ifndef HAVE_ATOMICS
+#define NUM_EMULATION_SEMAPHORES (NUM_SPINLOCK_SEMAPHORES + NUM_ATOMICS_SEMAPHORES)
+#else
+#define NUM_EMULATION_SEMAPHORES (NUM_SPINLOCK_SEMAPHORES)
+#endif /* DISABLE_ATOMICS */
+
 PGSemaphore SpinlockSemaArray;
-#endif
+
+#else                          /* !HAVE_SPINLOCKS */
+
+#define NUM_EMULATION_SEMAPHORES 0
+
+#endif                         /* HAVE_SPINLOCKS */
 
 /*
  * Report the amount of shared memory needed to store semaphores for spinlock
 Size
 SpinlockSemaSize(void)
 {
-   return SpinlockSemas() * sizeof(PGSemaphoreData);
+   return NUM_EMULATION_SEMAPHORES * sizeof(PGSemaphoreData);
 }
 
-#ifdef HAVE_SPINLOCKS
-
 /*
  * Report number of semaphores needed to support spinlocks.
  */
 int
 SpinlockSemas(void)
 {
-   return 0;
+   return NUM_EMULATION_SEMAPHORES;
 }
-#else                          /* !HAVE_SPINLOCKS */
 
-/*
- * No TAS, so spinlocks are implemented as PGSemaphores.
- */
-
-
-/*
- * Report number of semaphores needed to support spinlocks.
- */
-int
-SpinlockSemas(void)
-{
-   return NUM_SPINLOCK_SEMAPHORES + NUM_ATOMICS_SEMAPHORES;
-}
+#ifndef HAVE_SPINLOCKS
 
 /*
  * Initialize semaphores.
 /*
  * s_lock.h hardware-spinlock emulation using semaphores
  *
- * We map all spinlocks onto a set of NUM_SPINLOCK_SEMAPHORES semaphores.
- * It's okay to map multiple spinlocks onto one semaphore because no process
- * should ever hold more than one at a time.  We just need enough semaphores
- * so that we aren't adding too much extra contention from that.
+ * We map all spinlocks onto NUM_EMULATION_SEMAPHORES semaphores.  It's okay to
+ * map multiple spinlocks onto one semaphore because no process should ever
+ * hold more than one at a time.  We just need enough semaphores so that we
+ * aren't adding too much extra contention from that.
+ *
+ * There is one exception to the restriction of only holding one spinlock at a
+ * time, which is that it's ok if emulated atomic operations are nested inside
+ * spinlocks. To avoid the danger of spinlocks and atomic using the same sema,
+ * we make sure "normal" spinlocks and atomics backed by spinlocks use
+ * distinct semaphores (see the nested argument to s_init_lock_sema).
  *
  * slock_t is just an int for this implementation; it holds the spinlock
- * number from 1..NUM_SPINLOCK_SEMAPHORES.  We intentionally ensure that 0
+ * number from 1..NUM_EMULATION_SEMAPHORES.  We intentionally ensure that 0
  * is not a valid value, so that testing with this code can help find
  * failures to initialize spinlocks.
  */
 
+static inline void
+s_check_valid(int lockndx)
+{
+   if (lockndx <= 0 || lockndx > NUM_EMULATION_SEMAPHORES)
+       elog(ERROR, "invalid spinlock number: %d", lockndx);
+}
+
 void
 s_init_lock_sema(volatile slock_t *lock, bool nested)
 {
    static uint32 counter = 0;
-
-   *lock = ((++counter) % NUM_SPINLOCK_SEMAPHORES) + 1;
+   uint32      offset;
+   uint32      sema_total;
+   uint32      idx;
+
+   if (nested)
+   {
+       /*
+        * To allow nesting atomics inside spinlocked sections, use a
+        * different spinlock. See comment above.
+        */
+       offset = 1 + NUM_SPINLOCK_SEMAPHORES;
+       sema_total = NUM_ATOMICS_SEMAPHORES;
+   }
+   else
+   {
+       offset = 1;
+       sema_total = NUM_SPINLOCK_SEMAPHORES;
+   }
+
+   idx = (counter++ % sema_total) + offset;
+
+   /* double check we did things correctly */
+   s_check_valid(idx);
+
+   *lock = idx;
 }
 
 void
 {
    int         lockndx = *lock;
 
-   if (lockndx <= 0 || lockndx > NUM_SPINLOCK_SEMAPHORES)
-       elog(ERROR, "invalid spinlock number: %d", lockndx);
+   s_check_valid(lockndx);
+
    PGSemaphoreUnlock(&SpinlockSemaArray[lockndx - 1]);
 }
 
 {
    int         lockndx = *lock;
 
-   if (lockndx <= 0 || lockndx > NUM_SPINLOCK_SEMAPHORES)
-       elog(ERROR, "invalid spinlock number: %d", lockndx);
+   s_check_valid(lockndx);
+
    /* Note that TAS macros return 0 if *success* */
    return !PGSemaphoreTryLock(&SpinlockSemaArray[lockndx - 1]);
 }
 
 #endif
 }
 
+/*
+ * Verify that performing atomic ops inside a spinlock isn't a
+ * problem. Realistically that's only going to be a problem when both
+ * --disable-spinlocks and --disable-atomics are used, but it's cheap enough
+ * to just always test.
+ *
+ * The test works by initializing enough atomics that we'd conflict if there
+ * were an overlap between a spinlock and an atomic by holding a spinlock
+ * while manipulating more than NUM_SPINLOCK_SEMAPHORES atomics.
+ *
+ * NUM_TEST_ATOMICS doesn't really need to be more than
+ * NUM_SPINLOCK_SEMAPHORES, but it seems better to test a bit more
+ * extensively.
+ */
+static void
+test_atomic_spin_nest(void)
+{
+   slock_t lock;
+#define NUM_TEST_ATOMICS (NUM_SPINLOCK_SEMAPHORES + NUM_ATOMICS_SEMAPHORES + 27)
+   pg_atomic_uint32 atomics32[NUM_TEST_ATOMICS];
+
+   SpinLockInit(&lock);
+
+   for (int i = 0; i < NUM_TEST_ATOMICS; i++)
+   {
+       pg_atomic_init_u32(&atomics32[i], 0);
+   }
+
+   /* just so it's not all zeroes */
+   for (int i = 0; i < NUM_TEST_ATOMICS; i++)
+   {
+       EXPECT_EQ_U32(pg_atomic_fetch_add_u32(&atomics32[i], i), 0);
+   }
+
+   /* test whether we can do atomic op with lock held */
+   SpinLockAcquire(&lock);
+   for (int i = 0; i < NUM_TEST_ATOMICS; i++)
+   {
+       EXPECT_EQ_U32(pg_atomic_fetch_sub_u32(&atomics32[i], i), i);
+       EXPECT_EQ_U32(pg_atomic_read_u32(&atomics32[i]), 0);
+   }
+   SpinLockRelease(&lock);
+}
+#undef NUM_TEST_ATOMICS
+
 PG_FUNCTION_INFO_V1(test_atomic_ops);
 Datum
 test_atomic_ops(PG_FUNCTION_ARGS)
     */
    test_spinlock();
 
+   test_atomic_spin_nest();
+
    PG_RETURN_BOOL(true);
 }