And again "all data structures thet get used by interrupt handlers" should be a very small list. If this is a problem, your issue isn't making them all volatile it is that your interrupt handlers are way to complex.
If you rely on blanket volatile, you won't be able to even call memcpy() without implementing your own version that accepts pointers to volatile, and not only in an ISR - in any code that accesses the data structure.
The problem is that smal MCUs lack the cortex M0+ lack the RMW operations needed to implement lock free atomics. You dont need RMW operations for a simple queue...
You are responding to your own argument here. Single producer single consumer queue does not need atomic rmw, it needs that accesses head/tail pointers are acquire/release, which atomic_load() and atomic_store() will ensure even on M0. But the same C code will also work on other architectures and on multi-core. Apart from being able to move to a different MCU (for a hobbyist it's normal to try things out), you'll be able to test some of your code on PC with threads.
My point was this:
The C standard doesn't make this distinction. You either have atomics or you don't. Since it's not possible to implement C11 atomics in a lock-free manner on Cortex M0/M0+, compiler support is varied and not reliable. In principle a compiler could just guard all atomic operations into locked access which is an allowed implementation but will deadlock if used in an ISR.
I just did some testing on godbolt to see what actual compilers do:
gcc 16 emits standard load/store instructions with memory barriers for atomic load/store. It emits calls to libatomic for atomic_swap and atomic_compare_exchange, which will fail since libatomic doesn't exist. That's fine behavior if you want to use it for a simple queue from an interrupt handler, and you will get an error if you try to call unsupported functions. But if you are using an RTOS and want to use the rest of the atomic operations for inter thread communication you are kind of stuck. The only valid way to implement libatomic here is by disabling interrupts -- you can't do it with ordinary locks because the load/store operations won't respect the lock. But disabling interrupts might be more intrusive than you want for inter-thread communication.
clang emits a warning that you have used an atomic type larger than the maximum lock-free type and always emits calls to libatomic. This is the most flexible, since you can implement the functions either with locks or disabling interrupts depending on your use case. You still have to pick, but it's your choice. However, without mucking around in the platform defintiion, "is_always_lock_free" will be false even if you choose disabling interrupts. clang even emits this warning (atomic object too larger for lock-free behavior) on atomic_flag. This is a violation of the standard which requires that atomic_flag always be lock-free but there just isn't a way to implement that.
gcc 13 (which is only a couple years old), when faced with atomic_flag_test_and_set simply turns it into a load and a store! This is an even worse violation of the standard since the sequence is definitely not atomic.
All this is why I don't recommend people to use C/C++ atomics for on CPUs that don't have RMW instructions, but instead just use volatile for ISRs. It's not ideal, but it works, it's universally supported, and with good code design the amount of code that has to directly interact with the volatile variables is minimized. It's easy enough to convert to a more modern atomics based operations if you ever port to a multi-core system.