TL:DR: never silently fall back to locking, nobody ever wants that because it defeats a major part of the purpose of std::atomic
. Think of non-lock-free as a portability fallback, not a viable mode of operation.
Being UB makes it legal for the compiler to simply assume without any checking that it's aligned. Being able to assume without any runtime checks is one of the major benefits of the concept of UB. This is what most people want / expect at runtime in an optimized build, not bloating the code with conditional branches that might fall back to using a mutex.
The choice of whether (and how) to define any behaviour here is completely up to the implementation, as a matter of Quality-of-Implementation and trading off performance vs. debugging. I think you know that and are literally asking what users want compilers to pick for those QoI choices, which is fine.
As the P0019 proposal you linked says, it all comes down to a QOI issue:
- Reference-ability Constraints
An object referenced by an atomic reference must satisfy possibly architecture-specific constraints. For example, the object might need to be properly aligned in memory or might not be allowed to reside in GPU register memory. We do not enumerate all potential constraints or specify behavior when these constraints are violated. It is a quality-of-implementation issue to generate appropriate information when constraints are violated.
The "generate appropriate information" phrasing implies they expect implementations to warn / error if they detect a violation, not fall back to locking.
Although an implementation that could fall back to locking could foolishly set required_alignment
to the minimum for correctness (1), rather than the minimum for lock-freedom. Of course nobody wants that, but it's a QoI issue not standards-compliance.
I'd expect (or at least hope) for an implementation to work as follows:
Warn at compile time if atomic_ref
is used on any object whose alignof
is less than required_alignment
. You might know that a certain T *p
happens to be 8 byte aligned even though alignof(T)
is only 1 or 4, so this shouldn't be an error.
Some local way of silencing the warning would be a good thing. (Alternative: promise alignment to the compiler with something like GNU C x = __builtin_assume_aligned(x, 16)
)
At least warn if an object is definitely known to be under-aligned at compile time, e.g. a sub-member of struct whose alignment is known, or a global var where the declaration is visible but didn't include alignas
. Warning for access through pointers that might be under-aligned is noisier and should be separately disable-able.
Extra-slow Debug mode: runtime check of alignment, warn or abort on a specific object being under-aligned for atomicity. (e.g. gcc -fsanitize=undefined
, or MSVC's debug mode which already adds stuff like std::vector::operator[]
bounds checks. I think GCC's UBSan does even more checking than MSVC debug mode, e.g. for signed overflow; I think MSVC debug mode is somewhere in between gcc -O0
and gcc -O0 -fsanitize=undefined
.)
"Release" mode: zero checking, just emit asm whose correctness depends on the object being aligned. (Also gcc -O0
without UBSan, which allows consistent debugging but doesn't add extra checks.)
Nobody ever wants silent fallback to mutexes at compile time or run-time. That mode of operation basically just exists so ISO C++ can require the feature to be supported everywhere without making it impossible to implement on some targets.
The fallback to locking is usually very sub-optimal compared to manual fine-grained locking of a critical section that does a few related atomic ops at once on a data-structure designed for it. People use atomic<T>
(and the upcoming atomic_ref<T>
) for performance, and much of that performance is destroyed by locking. Especially read-side scalability.
Footnote 1: IIRC, alignof()
is only specified for types, not objects, but in GNU C++ it also works on objects. I'm using this as shorthand for the compiler's internal knowledge that a certain object used alignas()
to over-align it.