Re: Inconsistent use of "volatile" when accessing shared memory? - Mailing list pgsql-hackers

From Andres Freund
Subject Re: Inconsistent use of "volatile" when accessing shared memory?
Date
Msg-id 20231103225919.ovxv6oow4bw2uve7@awork3.anarazel.de
Whole thread Raw
In response to Inconsistent use of "volatile" when accessing shared memory?  (Jeff Davis <pgsql@j-davis.com>)
Responses Re: Inconsistent use of "volatile" when accessing shared memory?
List pgsql-hackers
Hi,

On 2023-11-02 23:19:03 -0700, Jeff Davis wrote:
> I noticed that we occasionally use "volatile" to access shared memory,
> but usually not; and I'm not clear on the rules for doing so. For
> instance, AdvanceXLInsertBuffer() writes to XLogCtl->xlblocks[nextidx]
> through a volatile pointer; but then immediately writes to XLogCtl-
> >InitializedUpTo with a non-volatile pointer. There are also places in
> procarray.c that make use of volatile through UINT32_ACCESS_ONCE(), and
> of course there are atomics (which use volatile as well as guaranteeing
> atomicity).

> In theory, I think we're always supposed to access shared memory
> through a volatile pointer, right? Otherwise a sufficiently smart (and
> cruel) compiler could theoretically optimize away the load/store in
> some surprising cases, or hold a value in a register longer than we
> expect, and then any memory barriers would be useless.

I don't think so. We used to use volatile for most shared memory accesses, but
volatile doesn't provide particularly useful semantics - and generates
*vastly* slower code in a lot of circumstances. Most of that usage predates
spinlocks being proper compiler barriers, which was rectified in:

commit 0709b7ee72e
Author: Robert Haas <rhaas@postgresql.org>
Date:   2014-09-09 17:45:20 -0400

    Change the spinlock primitives to function as compiler barriers.

or the introduction of compiler/memory barriers in

commit 0c8eda62588
Author: Robert Haas <rhaas@postgresql.org>
Date:   2011-09-23 17:52:43 -0400

    Memory barrier support for PostgreSQL.


Most instances of volatile used for shared memory access should be replaced
with explicit compiler/memory barriers, as appropriate.

Note that use of volatile does *NOT* guarantee anything about memory ordering!


> But in practice we don't do that even for sensitive structures like the
> one referenced by XLogCtl. My intuition up until now was that if we
> access through a global pointer, then the compiler wouldn't completely
> optimize away the store/load.

That's not guaranteed at all - luckily, as it'd lead to code being more
bulky. It's not that the global variable will be optimized away entirely in
many situations, but repeated accesses can sometimes be merged and the access
can be moved around.


> What is the guidance here? Is the volatile pointer use in
> AdvanceXLInsertBuffer() required, and if so, why not other places?

I don't think it's required. The crucial part is to avoid memory reordering
between zeroing the block / initializing fields and changing that field - the
relevant part for that is the pg_write_barrier(); *not* the volatile.

The volatile does prevent the compiler from deferring the update of
xlblocks[idx] to the next loop iteration. Which I guess isn't a bad idea - but
it's not required for correctness.


When an individual value is read/written from memory, and all that's desired
is to prevent the compiler for eliding/moving that operation, it can lead to
better code to use volatile *on the individual access* compared to using
pg_compiler_barrier(), because it allows the compiler to keep other variables
in registers.

Greetings,

Andres Freund



pgsql-hackers by date:

Previous
From: Thomas Munro
Date:
Subject: Re: Pre-proposal: unicode normalized text
Next
From: Jacob Champion
Date:
Subject: Re: [PoC] Federated Authn/z with OAUTHBEARER