Just keep in mind GNAT may be wrong, it does occasionally have bugs and does wrong stuff. It’s also technically not fully Ada conformant. That said, if you have an example where a volatile variable access is being optimized away, it is probably worth posting an example. I can file a bug report, then the GNAT folks could decide based on their interpretation of the RM.
The RM is pretty clear on it:
Implementation Requirements:
The external effect of a program (see 1.1.3) is defined to include each read and update of a
volatile or atomic object. The implementation shall not generate any memory reads or
updates of atomic or volatile objects other than those specified by the program. However,
there may be target-dependent cases where reading or writing a volatile but nonatomic
object (typically a component) necessarily involves reading and/or writing neighboring
storage, and that neighboring storage can overlap a volatile object.
The first sentence here says it must include each read and write (update). The last sentence distinguishes between atomic and volatile since atomic operations are volatile operations with the extra rule that the operations must operate atomically over the data (running overlapped onto another value would not be considered atomic, but it would still be volatile).
The pertinent reference mentioned above from 1.1.3:
The external effect of the execution of an Ada program is defined in terms of its interactions
with its external environment. The following are defined as external interactions:
9 * Any interaction with an external file (see A.7);
10 * The execution of certain code_statements (see 13.8); which code_statements
cause external interactions is implementation defined.
11 * Any call on an imported subprogram (see Annex B), including any parameters
passed to it;
12 * Any result returned or exception propagated from a main subprogram (see 10.2)
or an exported subprogram (see Annex B) to an external caller;
13 * Any read or update of an atomic or volatile object (see C.6);
14 * The values of imported and exported objects (see Annex B) at the time of any
other interaction with the external environment.
Are you able to post the example where it does the optimization of a volatile access?
It may not be redundant. I’ve worked on some dsPIC processors where reading a DMA register increments an internal address counter that increments so you can keep reading the same register over and over in a loop to read the entire DMA bank. If you are reading back a chunk of data from an external IC and are only interested in the 12th byte of the response, you can definitely do 11 throw away reads there and they wouldn’t be redundant, they would be incrementing the internal counter, but the compiler wouldn’t see that.
There’s also various errata issues with microcontrollers that can have very interesting workarounds, like reading the register 3x in a row to clear an error condition (I’ve seen this for hardware bugs dealing with break conditions in UARTS). Here the reads into a discarded variable might also elicit additional MOV instructions which may be important for the work around.
The idea here is that volatile tells the computer it can’t know if it is redundant or not, the reads (or writes) are there for a reason that the compiler may not understand. There may be unseen external effects.