Reading the answers of this question it came to my attention that register
is no longer a valid storage specifier in C++17. Some comments even suggest that the compiler had already ignored register
for some time.
I use GCC 6.x with an ARM Cortex-M MCU and have some piece of code with inline assembly which absolutely need to have a variable in a register. Previously I had assumed that the register
keyword will do this for me, but apparently it doesn't.
EDIT: Why do I need to store something in a register?
I'm implemeting a lock-free ring buffer with the ARM LDREX
/ STREX
instructions. I need to store the result of the ARM LDREX
instruction in a register, because storing it in memory would defeat the whole mechanism on a Cortex-M.
EDIT: Example code.
This is a code snippet cut from the ring buffer to illustrate the point of the question. The points of interest are __LDREXW
, __STREXW
and __CLREX
which are all defined in cmsis_gcc.h
. They are intrinsic functions of the ARM synchronization primitives. I use them to implement a lock-free mechanism.
template<typename T, uint32_t maxCount>
class RingBuffer final {
__attribute__((aligned(8)))
T buffer[maxCount];
uint32_t start;
uint32_t end;
bool pushBack(const T &item) {
register uint32_t exclusiveEnd;
register uint32_t oldEnd;
do {
// Load current end value exclusively
exclusiveEnd = __LDREXW(&end);
__DMB();
// Remember old end value so that
// we can store the item at that location
oldEnd = exclusiveEnd;
// Check if ring buffer is full
if (isFull()) {
__CLREX();
__DMB();
return false;
}
// Figure out correct new value
if (exclusiveEnd == (maxCount - 1)) {
exclusiveEnd = 0;
}
else {
exclusiveEnd ++;
}
// Attempt to store new end value
} while (0 != __STREXW(exclusiveEnd, &end));
__CLREX();
__DMB();
// Store new item
//memcpy(buffer + oldEnd, &item, sizeof(T));
buffer[oldEnd] = item;
return true;
}
// ... other methods ...
}
Why the LDREX
result must be stored in a register:
On the Cortex-M4 the implemented exclusives reservation granule is the entire memory address range (quoted from Cortex-M4 TRM), which means if the variable storing the LDREX
result ends up in memory instead of a register, then the following STREX
will always fail.
NOTE: this code runs on "bare-metal" hardware, there is no operating system, etc.
Each variable is given a "stack offset", which is where in the stack it's stored. Then, when the program needs to access a variable x , the compiler replaces x with STACK_POINTER + x_offset , to get the actual physical place it's stored in memory.
Register variables are stored in registers. Static variable is stored in the memory of the data segment. In register variables, CPU itself stores the data and access quickly.
At some point, for local variables, the compiler assigns either a stack location or a CPU register (it can be more complex in that the variable can actually have multiple locations, such as a stack location for some parts of the generated code and a CPU register for other sections).
Register variables tell the compiler to store the variable in CPU register instead of memory. Frequently used variables are kept in registers and they have faster accessibility. We can never get the addresses of these variables. “register” keyword is used to declare the register variables.
What is the correct way to tell the compiler that I want a variable to be always stored in a register?
You can't do that (in portable standard C++ or C code). You need to trust your compiler, so you should not even want to do that.
Notice that:
Recent C & C++ standards (e.g. C11 or C++14 or C++17) don't speak of processor registers in an imperative way, and they mention that the register
keyword was (in the previous century) only a hint for compilers.
Some processors don't (at least in the past) even have any real programmer accessible processor register.
Most importantly, you should trust your compiler for good enough optimizations and in some cases putting a value in a register is not the best for performance (in particular, because that register could be better used for some other value).
However, as an extension the GCC compiler enables you to put a variable in a specified register. I don't recommend using that without very good reasons (at least be sure to benchmark your code with and without using that feature).
You really need to understand that current compilers are most of the time optimizing better than you can do. Be sure to benchmark your code (e.g. compiled with g++ -O3
and appropriate -mtune=
argument) before trying to optimize by hand. For performance sensitive routines, examine also the generated assembler code (e.g. with g++ -O3 -fverbose-asm -S
).
On the Cortex-M4 the implemented exclusives reservation granule is the entire memory address range (quoted from Cortex-M4 TRM),
Then I recommend either using a small extended assembler code (for GCC) or, if absolutely necessary, declare a variable in a specified register
Perhaps you also need to compile all your code (including any used library, including standard C and C++ libraries!) with -ffixed-
reg option.
But I insist: you need to trust your compiler more than you currently do. Are you sure you can't find (and perhaps configure and build from source) a recent GCC (e.g. GCC 7) which enables, as a builtin or something else, your low-level synchronization mechanism?
register
has mostly been treated as a hint by C++ compilers, even before the first standard, which was ratified in 1998. And, in a lot of cases, the compiler has been able to do a significantly better job of register allocation than programmers, so it has ignored that hint.
There is no general or portable way in standard C++ (i.e. one that works with compilers from different vendors, and on different host systems) to ensure a particular variable is placed in a register.
It is possible, with some compilers, to use inline assembler to explicitly use registers. The catch with this approach is that inline assembler varies between compilers and hosts (essentially, it is implementation defined). Several modern compilers are also aggressive enough that they optimise inline assembler as well, so may remove usage of a register even in inline assembler. The analysis and transformations are often relatively simple (in comparison with other types of optimisations, at least), so may happen even with low optimisation settings.
The only way to know for sure is to examine the output assembler to determine what the compiler does, for your chosen settings (optimisation, etc).
Probably the only sure-fire way is to write your code in assembler, rather than as inline assembler in C++. (I'm not aware of any assembler that optimises code to a great extent, but I've never had reason to try and find one either). By definition, that is not portable between systems - assembler is often machine dependent.
Your choice of words
Previously I had assumed that the
register
keyword will do this for me, but apparently it doesn't.
also raises another potential concern - that you are attempting to force use of registers unnecessarily.
If you previously assumed the register
keyword was sufficient, and your program appears to have worked as required anyway, it's quite possible that there is no need for you to worry if any variable is in a register.
My point is, if performance is critical enough to justify forcing any variable into a register, you should have known that your compiler was placing your chosen variable in a register, not just assumed that to be so. If you have just assumed that is true, and not encountered difficulties, it's quite possible the compiler was ignoring the hint, and achieving your required performance for your code anyway.
I suggest you need to question and revalidate your belief that your particular variable actually needs to be stored in a register.
Without information about your design requirements - and evidence through substantial testing that compiled code does not meet those requirements in realistic scenarios - the assumption that you need to use a register is often misplaced.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With