1

I have read a number of explanations about compile barriers and memory barriers, though I am not sure yet about how the compiler knows where the prevention of compile memory ordering starts and where it ends. (On the other hand I understand how a cpu memory barrier works...)

Below is an arbitrary example, with no compile barrier.

int func(int *x, int *y) {
        int var = 0;
        x[0] += 1;
        var += y[0];
        y[0] += 1;
        x[1] += 1;
        var += y[1];
        y[0] += 1;

        return var;
}

For example if I want to prevent the compile memory ordering only in this function, and not in other functions, should I insert the asm volatile("" ::: "memory") to end of the function, before returning var?

Like:

int func(int *x, int *y) {
        int var = 0;
        x[0] += 1;
        var += y[0];
        y[0] += 1;
        x[1] += 1;
        var += y[1];
        y[0] += 1;

        asm volatile("" ::: "memory");
        return var;
}
tozak
  • 401
  • 4
  • 12
  • That actually **is** a compiler barrier. Why do you think a statement inside a function may have side-effects outside that function other than mandated by the standard? Also not that "scope" is not about "area effects" (whatever that is), but about "visibility" of an identifier. It is not even about the life-time. – too honest for this site Feb 06 '16 at 20:41
  • @Olaf You are absolutely correct. However, what confuses me is, I understand the statement works like a compiler option and it is considered in the compile time. So I am not sure if the side-effects are percieved outside the function or not... About the scope, yes life-time is was actually the term I was looking. – tozak Feb 06 '16 at 20:53
  • 1
    Why do you think you need a barrier in that code? What is the ordering (or optimization) of operations you are trying influence? – e0k Feb 06 '16 at 21:02
  • @e0k Compiler might or might not reorder the memory accesses in the example code. I am trying the prevent the possible reordering done by the compiler for all the memory accesses in the function. I don't claim a barrier is needed in the function. – tozak Feb 06 '16 at 21:13
  • After writing my answer, I found this [similar question](http://stackoverflow.com/questions/14950614/working-of-asm-volatile-memory). – e0k Feb 06 '16 at 21:45

1 Answers1

3

The barrier prevents reordering (or optimization) wherever you put it. There is no magical "scope". Just look at the inline assembly instruction:

asm volatile (""::: "memory");

The volatile keyword means to put the asm statement exactly where I put it, and don't optimize it away (i.e. remove it). After the third : is the list of clobbers, so this means "I have clobbered the memory." You are basically telling the compiler "I have done something to affect the memory."

In your example, you have something like

y[0] += 1;
y[0] += 1;

The compiler is very clever and knows this is not as efficient as it could be. It will probably compile this into something like

load y[0] from memory to register
add 2 to this register
store result to y[0]

Because of pipelining reasons, it may also be more efficient to combine this with other load/modify/store operations. So the compiler may reorder this even further by merging it with nearby operations.

To prevent this, you can place a memory barrier between them:

y[0] += 1;
asm volatile (""::: "memory");
y[0] += 1;

This tells the compiler that after the first instruction, "I have done something to the memory, you may not know about it, but it happened." So it can not use its standard logic and assume that adding one twice to the same memory location is the same as adding two to it, since something happened in between. So this would be compiled into something more like

load y[0] from memory to register
add 1 to this register
store result to y[0]
load y[0] from memory to register
add 1 to this register
store result to y[0]

Again, it could possibly reorder things on each side of the barrier, but not across it.

Another example: Once, I was working with memory-mapped I/O on a microcontroller. The compiler saw that I was writing different values to the same address with no read in between, so it kindly optimized it into a single write of the last value. Of course, this made my I/O activity not work as expected. Placing a memory barrier between writes told the compiler not to do this.

e0k
  • 6,961
  • 2
  • 23
  • 30
  • Your answer is making things a lot cleaner for me. However I am having a difficulty in this sentence: "Again, it could possibly reorder things on each side of the barrier, but not across it." What is the across here for? (I am trying to do my best with my English) – tozak Feb 06 '16 at 21:49
  • 1
    The compiler can reorder instructions for optimization. The memory barrier is like a wall. It can reorder instructions only on one side of the wall. It can not move something from one side of the wall to the other side. ("not across it" = not to cross it) – e0k Feb 06 '16 at 21:58
  • Thank you, it is clear for me now. No magical scope, compiler may reorder accesses on each side of the barrier. Barrier statement is like a seperator wall, indicating do not reorder memory from above side of the barrier to the below side, and vice versa. – tozak Feb 06 '16 at 22:11
  • @e0k: Corrections: The `memory` clobber not only tells the compiler the assembler code clobber**ed** the memory, but in general that is wants to change the memory outside the compiler's view. So the compiler also has to write back cached variables (i.e. sync memory) **before** entering the assember code, etc. And your last paragraph does not make sense. You just should have declared the register variables `volatile` with external linkage. The compiler is not allowed to remove them (see the standard). A compiler barrier like the one asked is the wrong approach for that. – too honest for this site Feb 08 '16 at 21:45