24

I've seen How many usage does "volatile" keyword have in C++ function, from grammar perspective? about use of the volatile keyword on functions, but there was no clear explanation of what Case 1 from that question did. Only a statement by one of the respondents that it seemed pointless/useless.

Yet I cannot quite accept that statement, since the AES software implementations for GNUC have been used for literally years, and they have a number of functions like this:

INLINE volatile void functionname( /* ... */ ) {
    /* ... */
    asm( /* ... */ ) // embedded assembly statements
    /* ... */
}

There has to have been a reason for that usage. Can anyone:

A. tell me what the original reason was; and

B. how to achieve the desired effect now?

I'm using Ubuntu, and GCC 4.6.3.


Note: The closest I've come to an explanation is that prior to GCC 2.5, you could spoof the 'noreturn' attribute that was implemented in 2.5 via the following:
void fatal( /* ... */ ) { /* ... */ exit(1); }

typedef void voidfn ();

volatile voidfn fatal;

This would allow the compiler to recognize that 'fatal' was not going to return.

But that scenario doesn't appear to apply to the AES code. It's been a long time since I did anything in assembly, but I think I'd recognize a jump or something like that.

Community
  • 1
  • 1
EdwinW
  • 1,007
  • 2
  • 13
  • 32
  • 1
    Note that GNU C still supported `volatile void` return type in `gcc 4.5` version. – ouah Jan 12 '13 at 00:19
  • 1
    Yow that cod's insane. Declaring a function as doesn't return when it does return to convince the optimizer to do something specific is asking for trouble. – Joshua Oct 12 '16 at 16:36
  • Yup. It's dangerous as anything I've ever seen, which is why I noted that it's definitely discouraged. In later AES implementations, I haven't seen this sort of 'trick' used at all. Of course, they're also not getting any benefit from any AES features built into the CPU. At the time the code was written, I believe that the AES features were essential to getting the best possible speed out of the CPU's available. Nowadays, not so much. – EdwinW Oct 12 '16 at 17:01
  • @EdwinW "_In later AES implementations, I haven't seen this sort of 'trick' used at all._" where can I find a newer AES implementation? I'm stuck with volatile void warnings from https://github.com/BrianGladman/aes/blob/master/aes_via_ace.h – Cœur Jun 21 '17 at 02:37
  • @Cœur - It's been almost three years, so there's probably more, and some of the ones I looked at may have died. I haven't checked the list presented on [wikipedia](https://en.wikipedia.org/wiki/AES_implementations), but you might find something there. – EdwinW Jun 24 '17 at 19:03
  • 1
    @EdwinW After [checking with the author of aes_via_ace.h](https://github.com/BrianGladman/aes/issues/24) and presenting him alternatives from that Wikipedia page, I've purely [removed aes_via_ace.h from minizip](https://github.com/nmoinvaz/minizip/pull/121) and [SSZipArchive](https://github.com/ZipArchive/ZipArchive/pull/341), which with time should reduce the occurrences of `volatile void` [in the wild](https://github.com/search?q=aes_via_ace&type=Code&utf8=%E2%9C%93). – Cœur Jun 25 '17 at 05:15

3 Answers3

16

According to the gcc documentation (until February 2015), volatile void as a function return value in C (but not in C++) is equivalent to __attribute__((noreturn)) on the function and tells the compiler that the function never returns.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
9

The C99 standard says this in §6.7.3/3:

The properties associated with qualified types are meaningful only for expressions that are lvalues.114)

114) The implementation may place a const object that is not volatile in a read-only region of storage. Moreover, the implementation need not allocate storage for such an object if its address is never used

§6.2.5/19 says:

The void type comprises an empty set of values; it is an incomplete type that cannot be completed.

And §6.3.2.1/1 says:

An lvalue is an expression with an object type or an incomplete type other than void;53) [...]

Hence, void is not an lvalue, so the type qualifiers (const, volatile, and restrict) are not meaningful for expressions of type void. So, in any C99-compliant compiler, const void and volatile void are meaningless (although pointers to const void and const volatile are meaningful).

Furthermore, the constraints of §6.9.1/3 disallow a function to return a qualified type of void:

The return type of a function shall be void or an object type other than array type.

Since this is a constraint, a conforming compiler must issue a diagnostic (§5.1.1.3/1). So a function returning volatile void is not allowed in C99.

As for what volatile void may have used to do, I have no idea and can't really speculate. The AES code you're looking at probably just has old cruft that never got cleaned up, I'd guess.

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • `volatile void` as return type is not strictly conforming. *(C99, 6.9.1p3) "The return type of a function shall be void or an object type other than array type"* It was accepted in the past as a GNU extension. – ouah Jan 12 '13 at 00:20
  • volatile doesn't change the type, it's a qualifier - so I don't see how it's not conforming.. albeit meaningless as @Adam points out – hexist Jan 12 '13 at 00:21
  • @hexist a type qualifier changes the type. – ouah Jan 12 '13 at 00:23
  • 1
    @hexist and if you want an authorative answer expliciting `volatile void` as a return type is not strictly conforming, see DR#113 http://www.open-std.org/jtc1/sc22/wg14/docs/rr/dr_113.html – ouah Jan 12 '13 at 00:26
  • 6.7.3/8 states that "if the specification of a function type includes any type qualifiers, the behavior is undefined", so it's not necessarily invalid I don't think. – hexist Jan 12 '13 at 00:36
  • @hexist 6.9.1p3 is a constraint => constraint violation – ouah Jan 12 '13 at 00:38
7

REFERENCES

EVIDENCE

  • volatile void function(...) is not strictly conforming to C99. (Thanks @Adam and @ouah. @Adam for digging into the C99 spec, and @ouah for pointing me at the DR listed above.)

  • GCC added __attribute__((noreturn)) in version 2.5 as a replacement for volatile void, but has continued to accept volatile void as late as version 4.6.3 to support code compatibility with compilers prior to version 2.5. (GCC documentation.)

  • The code referenced above does indeed return control to where it was called from, as the instructions do not appear to manipulate the address register(s), nor do they execute a jump command. Instead they load various values into the 32-bit registers. (Code examination.)

  • The commands in lines 323 through 333 implement special opcodes in support of encryption operations. (Code examination plus the 'padlock' code.)

  • The code using the assembly functions obviously expects them to return. (Code examination.)

  • The noreturn attribute tells the compiler that the function does not return, so the compiler can make optimizations based on that. (GCC documentation.)

  • From the GCC documentation: Do not assume that registers saved by the calling function are restored before calling the noreturn function.

SOLUTION

It was a discussion with a coworker that finally clued me in. The compiler must do something different when a function declares that it isn't going to return. Examination of the GCC documentation confirmed this.

A. The original reason

You need to ask yourself the following question.

Question: The AES code specifically loads values into the 32-bit registers, and performs operations on them. How does it get the answers back to the rest of the code?

Answer: The GCC optimizations mean that the calling function's registers, which otherwise would have overwritten the values upon return, are not saved. The results of the calculations in the assembly language functions remain in the registers for subsequent code to use.

B. Achieving the desired effect now:

Pretty much leave it alone. The only thing you might do is replace the volatile void return type with simply void, and add the noreturn attribute to the functions. Theoretically, that should have the exact same effect. In practice, it ain't broke, don't fix it.

DESIRABILITY

Extensive use of this technique is definitely discouraged. First, it depends on customization for each compiler. Second, it depends on those compilers not changing how they handle the 'no return' case. Third, it's potentially confusing to subsequent maintainers.

The only situation where something like this makes any sense is when you're taking advantage of highly specialized machine code, to achieve an otherwise impossible improvement in speed. Even then, it should be balanced against the trade-offs.

In this example, precisely two compilers are supported, and only if the machines have the specific hardware support to take advantage of. Otherwise, it's all handled through standard C code. That's a lot of effort. Make sure it's going to pay off before you do it.

EdwinW
  • 1,007
  • 2
  • 13
  • 32
  • So... Am I full of it? Or does this make sense? – EdwinW Jan 14 '13 at 19:25
  • 1
    Makes sense to me. It looks like all those `INLINE volatile void` functions are unsafe assembly functions (leaving a result in a register or a given memory location), and the developers have only found a way with Microsoft C and GNU C to make them work. –  Jan 14 '13 at 20:28
  • Not that I can see where those functions are actually used, since the code is an obscene spaghetti of preprocessor statements. –  Jan 14 '13 at 20:48
  • Hi, it's a late question, but I can't understand the "original reason" above. Do you mean if the function is declared 'volatile', it means the function processing result registers are saved and seen at the calling function after return? I don't understand how we control variable assignment to registers without __register__ keyword. My understanding was that the operations should not be optimized inside the function. (whatever assignments should not be optimized away). Am't I correct? – Chan Kim Mar 29 '16 at 08:48
  • Effectively, yes, the author is _assuming_ that the results will be in the registers and available at the calling function after the 'no return' function is done. The **register** keyword doesn't guarantee anything, it's a hint to the compiler, but the compiler can ignore it. The author must have studied the assembly generated in each case, to determine if it would work, and dangerously assumed the compilers wouldn't change something as basic as argument passing. – EdwinW Mar 29 '16 at 13:10
  • See also: [this answer regarding deprecation of **register** keyword](http://stackoverflow.com/a/30809775/1608079) – EdwinW Mar 29 '16 at 13:17