New Ada Reference Card available - with Ada changes after Ada95

Hi

I have created a new Ada Reference Card.
It contains Attributes, Aspects, Pragmas and a list of packages in the standard library.
It contains the changes from newer versions of Ada up to and including Ada 2022.

It is available in Tex and PDF format at GitHub - bracke/AdaReferenceCard: Reference Card of Ada Attrubutes, Pragmas and Standard Library

This is the first version and it is likely neither entirely correct nor complete.
I would therefore invite everybody to submit pull requests with changes/improvements.

In particular:

  • Missing items.
  • Wrong/incomplete descriptions.
  • Spelling.
  • Improvements to the LaTeX code.

Kind regards
Bent Bracke

11 Likes

Great to see this

I think atomic could be expanded. One use is to ensure reads are not optimised away such as clearing a RNG or FIFO that affects the hardware but appears redundant to the compiler.

Just for clarification, that is what Volatile does. Atomic does include volatile by definition though, so it gets the same benefit.

I believe the Arm disagrees though a quick search didn’t find it.

I need to retest but I believe that I found with -O3 that reads to a dummy variable were optimised out when volatile and not atomic.

If so, it would not be conforming. The whole point of volatile is to tell the compiler not to optimize out reads or writes on the object. Atomics exist to ensure reads and writes are single atomic operations and also sequential, which is why they require the Volatile property (otherwise the compiler could optimize out instructions intended to enforce atomic operations sequentially).

Out of curiosity, what is your expectation on what the Volatile aspect does on its own? What property should it enforce?

Obviously I expected Volatile to work. When I read this then I am not sure it’s behaviour is incorrect. A read of a volatile repeatedly into a discarded (marked unused) volatile/atomic variable is certainly as “redundant” as the compiler could expect.

https://en.m.wikibooks.org/wiki/Ada_Programming/Pragmas/Volatile

Volatile
“When this pragma is used, the compiler must suppress any optimizations that would interfere with the correct reading of the volatile variables.”

https://en.m.wikibooks.org/wiki/Ada_Programming/Pragmas/Atomic

Atomic
“where the compiler must not reorder or suppress any redundant read or write.”

In any case I should test on the latest compiler.

edit: I suppose it is also possible that the volatile rng data was being read but the writes to the local volatile discarded variable were optimised out. I think it is hard to test that though on the rng. I guess I need to check the machine code.

I’ve been working on some registers defined as record types with the SPARK Effective_Reads, Effective_Writes, Async_Readers, Async_Writers aspects recently and have found that these remove a lot of ambiguity as to why a type is Volatile. Does anyone know if the SPARK aspects change GNAT’s optimization behavior?

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.

I’m not sure. I haven’t really dabbled in spark enough to know unfortunately.

Yes the reads of the rng register to clear it upon failure are not redundant but the local writes certainly are. A local variable without address assignment can’t be a magic address as it is an unknown location. I do not see how the RM excerpts that you have posted show that the wiki is incorrect.

The wiki is incomplete. It doesn’t mention updates. The RM does, very specifically. The RM is specific that any reads or updates must be preserved (yes even for discarded variables…it doesn’t make an exception and for good reason).:

The external effect of a program (see 1.1.3) is defined to include each read 
and update of a volatile or atomic object.

Note the use of “each” here. It doesn’t provide for exceptions that I can find.

The local variables aren’t read but only written which the wiki states is a difference between Volatile and Atomic.

By the way, sorry if this ends up sounding too argumentative. I don’t mean it to be. If you feel it is getting this way, I can just drop the conversation. I don’t mind deferring to keep things from sounding to brash. I apologize if I worded it in any way that comes across rude or argumentative.

Yes, but the RM says Volatile includes preserving writes, so the wiki would be missing that info I believe?

I have a thick skin so don’t worry about that. I shall post the example tomorrow and maybe someone can provide a definitive answer. I guess when you are doing something strange then testing is always needed in any case but that doesn’t help for the cheat sheet.

   procedure Recover_From_Seed_Error is
      --  Use Atomic to guarantee the reads are not optimised away.
      --   SPARK_MODE says Volatile must be declared at library level, so we use
      --  No_Caching to avoid That error.
      Discardable_U32 : HAL.UInt32 with
        Atomic, No_Caching => True;
   begin
      Clear_Seed_Error_Occurred;

      for Counter in 1 .. 12 loop
         Discardable_U32 := STM32_SVD.RNG.RNG_Periph.DR;
      end loop;
   end Recover_From_Seed_Error;

I agree with @jere .
Volatile and Atomic are general concepts (not specific to Ada) where

  • Volatile assumes a register/variable content can change between two reads so read operations must not be optimized. Same for writes. This is useful for hardware registers but also for a global variable set in a thread and read in another (In C for example).
  • Atomic operations are back-to-back operations (not only read and write) for which order is of first importance and which can’t be interrupted by an external event.The Atomic concept is implemented in processors (dedicated assembly instructions) up to high level languages.
1 Like