Judge Maygarden
Judge Maygarden

Reputation: 27593

How to force an unused memory read in C that won't be optimized away?

Microcontrollers often require a register to be read to clear certain status conditions. Is there a portable way in C to ensure that a read is not optimized away if the data is not used? Is it sufficient that the pointer to the memory mapped register is declared as volatile? In other words, would the following always work on standard compliant compilers?

void func(void)
{
   volatile unsigned int *REGISTER = (volatile unsigned int *) 0x12345678;

   *REGISTER;
}

I understand that dealing with functionality like this runs into compiler-dependent issues. So, my definition of portable is a bit loose in this case. I just mean that it would work as widely as possible with the most popular toolchains.

Upvotes: 28

Views: 7053

Answers (6)

Marcelo Pacheco
Marcelo Pacheco

Reputation: 276

I just tested the volatile pointer way on gcc with -O1 and -O3, it doesn't optimize away accesses to volatile registers. You'd need to test it on other compilers as the suggested solution of gcc asm logic doesn't work on some C compilers so it's not portable to most non gcc compilers. But if gcc does the memory access on x86_64 it will do it on other architectures as its a compiler code generation choice.

Upvotes: 0

sfrank
sfrank

Reputation: 131

Perhaps GNU C specific extensions is not considered very portable, but here is another alternative.

#define read1(x)  \
({ \
  __typeof(x) * _addr = (volatile __typeof(x) *) &(x); \
  *_addr; \
})

This will translate to the following assembler line (compiled with gcc x86 and optimized with -O2) : movl SOME_REGISTER(%rip), %eax?

I get the same assembler from:

inline read2(volatile uint32_t *addr) 
{ 
   return *addr; 
}`

... as suggested in another answer, but read1() will handle different register sizes. Even though I'm not sure if usingread2() with 8 or 16-bit registers would ever be an issue, there are at least no warnings on parameter type.

Upvotes: 1

zwol
zwol

Reputation: 140748

People argue quite strenuously about exactly what volatile means. I think most people agree that the construct you show was intended to do what you want, but there is no general agreement that the language in the C standard actually guarantees it as of C99. (The situation may have been improved in C2011; I haven't read that yet.)

A nonstandard, but fairly widely supported by embedded compilers, alternative that may be more likely to work is

void func(void)
{
  asm volatile ("" : : "r" (*(unsigned int *)0x12345678));
}

(The 'volatile' here appies to the 'asm' and means 'this may not be deleted even though it has no output operands. It is not necessary to put it on the pointer as well.)

The major remaining drawback of this construct is that you still have no guarantee that the compiler will generate a one-instruction memory read. With C2011, using _Atomic unsigned int might be sufficient, but in the absence of that feature, you pretty much have to write a real (nonempty) assembly insert yourself if you need that guarantee.

EDIT: Another wrinkle occurred to me this morning. If reading from the memory location has the side-effect of changing the value at that memory location, you need

void func(void)
{
  unsigned int *ptr = (unsigned int *)0x12345678;
  asm volatile ("" : "=m" (*ptr) : "r" (*ptr));
}

to prevent mis-optimization of other reads from that location. (To be 100% clear, this change will not change the assembly language generated for func itself, but may affect optimization of surrounding code, particularly if func is inlined.)

Upvotes: 10

Lundin
Lundin

Reputation: 214300

Yes, the C standard guarantees that code accessing a volatile variable will not be optimized away.

C11 5.1.2.3/2

"Accessing a volatile object, " ... "are all side effects"

C11 5.1.2.3/4

"An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object)."

C11 5.1.2.3/6

"The least requirements on a conforming implementation are:

— Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine."

Upvotes: 5

rodrigo
rodrigo

Reputation: 98436

IIRC, the C standard is a bit loose in the definition of use, so the *REGISTER is not necessarily interpreted as doing a read.

But the following should do:

int x = *REGISTER;

That is, the result of the memory reference has to be used somewhere. The x does not need to be volatile, however.

UPDATE: To avoid the warning of _unused variable you could do with a no-op function. A static and/or inline function should be optimized away without runtime penalty:

static /*inline*/ void no_op(int x)
{ }

no_op(*REGISTER);

UPDATE 2: I've just came up with a nicer function:

static unsigned int read(volatile unsigned int *addr)
{
    return *addr;
}

read(REGISTER);

Now, this function can be used both for read-and-use and for read-and-discard. 8-)

Upvotes: 2

Dmytro Sirenko
Dmytro Sirenko

Reputation: 5083

Compilers usually do not optimize assembly inlines (it's hard to analyze them properly). Moreover, it seems to be a proper solution: you want more explicit control over the registers and it's natural for assembly.

Since you're programming a microcontroller, I assume that there is some assembly already in your code, so a bit of inline assembly won't be a problem.

Upvotes: 0

Related Questions