On 13/06/15 21:51, Andrew Haley wrote:
On 13/06/15 18:49, Sebastian wrote:
On Fri, 12 Jun 2015 19:53:03 +0100
Andrew Haley <aph@xxxxxxxxxx> wrote:
That's the whole point about the example.
No it's not. The only thing moved across the barrier is the
division. A memory op cannot be moved across a memory barrier.
One memory barrier is at the cli(), as the article points out "where
the potentially slow division is moved across cli(),". The
assignment to val is moved, over that.
Ah, okay. I wasn't even thinking of that assignment as a memory
operation: see below.
It was before cli() in the C
source and it's after cli() after optimization. As your own comment
shows.
Here's the code:
00000112 <test2>:
112: bc 01 movw r22, r24
114: f8 94 cli
That's the cli().
116: 8f ef ldi r24, 0xFF ; 255
118: 9f ef ldi r25, 0xFF ; 255
11a: 0e 94 96 00 call 0x12c ; 0x12c <__udivmodhi4>
11e: 70 93 01 02 sts 0x0201, r23
122: 60 93 00 02 sts 0x0200, r22
The store to val is here ^
Thanks. Didn't try to find that, just figured that it would be
difficult to assign the result of the division before the division
happened.
So you just confirmed everything. This is after cli().
Actually, I think you're wrong here - this is not the store to val
(I guess you could r24 consider to be val, since that's it at the
beginning of the function), it's the store to ivar.
Sorry, yes. I made a mistake. I meant the store to ivar.
The store to val never happens, it is optimized away because val is
not a volatile variable, it's just a temporary one which is never
used afterwards.
Yes. val has been eliminated by the compiler.
Which, still, is the point of the article: Because val is not
declared volatile, it is not "memory", so assignments to it (and the
division required to happen before the assignment) can be moved
across the barrier.
The question of whether a local variable is considered by GCC to be
"in memory" depends on whether it has ever had its address taken.
So, if you say
int n;
int *p = &n;
then n is potentially a memory operand. (But be careful not to extend
this too far: if GCC knows that p is not used, it may be eliminated
and n is no longer potentially in memory.) If a variable is global it
is also a memory operand. But if a local variable never has its
address taken, no memory operation can access it (because there is no
way to know where it is.) Therefore it is not affected by a memory
clobber.
So, again, why would it be ok to remove the volatile qualifier from
my array elements? How else can I be sure a variable of mine is
"memory"?
The only way to be really sure is to use volatile. But it depends
on exactly what you're trying to do: if you're more specific we can
provide better advice.
Andrew.
There is another way to force the calculation here - using a fake
assembly input to force the calculation:
#define cli() __asm volatile( "cli" ::: "memory" )
#define sei() __asm volatile( "sei" ::: "memory" )
unsigned int ivar;
void test2( unsigned int val )
{
val = 65535U / val;
asm volatile("" :: "" (val));
cli();
ivar = val;
sei();
}
The memory clobber on cli() and sei() ensures that no memory operations
are moved before or after those statements. But as already noted, the
memory clobber does not affect non-memory operations such as
calculations or register-only manipulation.
But the extra "asm volatile" here with a fake input tells the compiler
that "val" is an input to the (empty) assembly, and must therefore be
calculated before the statement is executed. The empty input constraint
(no "r" or "m") gives the compiler complete freedom about where it wants
to put this fake input - all we are saying is that the value "val" must
be calculated before executing
asm volatile("" :: "" (val))
Generating assembly from this (using gcc-4.5.1, which is the latest
avr-gcc I have installed at the moment) shows the division being done
before the cli() - the code is optimal and correct, with no unnecessary
memory operations (as you would need by making "val" volatile).