Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can two sequential assignment statements in C be executed on hardware out of order?

Given the following C program:

static char vals[ 2 ] = {0, 0};

int main() {

char *a = &vals[0];
char *b = &vals[1];

while( 1 ) {

    SOME_STUFF()

    // non-atomic operations in critical section
    if( SOME_CONDITION() )
        {
        *a = 1;
        *b = 2;
        }
    else
        {
        *a = 0;
        *b = 0;
        }


    SOME_OTHER_STUFF()

    }

return 0;
}

int async_interrupt( void ) {

PRINT( a );
PRINT( b );
}

Is it possible for the hardware to actually load the value 2 into the memory location &vals[1] first, such that an interrupt routine could execute and see vals[1] == 2 and vals[0] == 0?

If this is possible, any description of the load/store operations that would result in this scenario would be much appreciated.

EDIT 1: Added a little more context to the code section. Unfortunately, I don't have the machine code from the compiled source.

like image 813
leo1 Avatar asked Nov 19 '18 22:11

leo1


2 Answers

C doesn't run on hardware directly. It has to be compiled first.

The specifics of undefined behaviour (like unsynchronized reads of non-atomic variables) totally depend on the implementation (including compile-time reordering in the compiler, and depending on the target CPU architecture, the runtime reordering rules of the that ISA).

Reads/writes of non-atomic variables are not considered an observable side-effect in C or C++, so they can be optimized away and reordered up to the limit of preserving the behaviour of the program as a whole (except when the program has undefined behaviour- optimizations can do anything in that case even if the compiler can't "see" there will be UB when it's compiling.)

See also https://preshing.com/20120625/memory-ordering-at-compile-time/

like image 81
Peter Cordes Avatar answered Oct 14 '22 11:10

Peter Cordes


Yes, it is possible because the compiler might re-order those statements as described in Peter's answer.

However, you might still be wondering about the other half: what hardware can do. Under the assumption that your stores end up in the assembly in the order you show in your source1, if an interrupt occurs on the same CPU that is running this code, from within the interrupt you'll see everything in a consistent order. That is, from within the interrupt handler, you'll never see the second store having completed, but the first not. The only scenarios you'll see are both not having completed, both completed or the first having completed and the second not.

If multiple cores are involved, and the interrupt may run on a different core, then you simply the classic cross-thread sharing scenarios, whether it is an interrupt or not - and what the other core can observe depends on the hardware memory model. For example, on the relatively strongly ordered x86, you would always observe the stores in order, where as on the more weakly ordered ARM or POWER memory models you could see the stores out of order.

In general, however, the CPU may be doing all sorts of reordering: the ordering you see within an interrupt handler is a special case where the CPU will restore the appearance of sequential execution at the point of handling the interrupt. The same is true of any case where a thread observes its own stores. However, when stores are observed by a different thread - what happens then depends on the hardware memory model, which varies a lot between architectures.


1 Assuming also that they show up separately - there is nothing stopping a smart compiler from noticing you are assigning to adjacent values in memory and hence transforming the two stores into a single wider one. Most compilers can do this in at least some scenarios.

like image 43
BeeOnRope Avatar answered Oct 14 '22 12:10

BeeOnRope