Reordering instructions


#1

Hello,

I stumbled upon something really interesting happening to the code generated by the toolchain riscv64-unknown-elf-*.

I have something simple like:
uint32_t function (uint32_t param) {
uint32_t result = aux_func(param); /* very simple function */
asm_func(); /* extended assembly code */
return result;
}
uint32_t aux_func(uint32_t param) {
return param + 1;
}
void asm_func(void) {
__asm__ volatile ("slli a0, a0, 2" : : : );
}
The compiled assembly looks like:
patch_function_0:
slli a0, a0, 2 /* asm_function */
addi a0,a0,1 /* aux_function */
ret

So it effectively changed the order of the functions calls! (Sorry I found no good option to format the code)

One might say: “That happened because you haven’t told the compiler about your assembly changes in the clobber list”.
Correct, but I did it on purpose, because I want to change something without interference from the compiler. However the compiler changed the order of the function calls in a way that I haven’t predicted.

So, please how can I create some kind of barrier so that the first function is called before the second?
I read about FENCE and the RISC-V Memory Model, but it doesn’t look like a solution for my problem. Is there anything I can use to instruct the compiler not to re-order those instructions?

Thank you!


(Jim Wilson) #2

You should make the dependencies obvious to the compiler and then you don’t need the volatile anymore. Something like this

typedef unsigned int uint32_t;
uint32_t aux_func (uint32_t);
uint32_t asm_func (uint32_t);

uint32_t function (uint32_t param) {
uint32_t result = aux_func(param); /* very simple function /
result = asm_func(result); /
extended assembly code */
return result;
}
uint32_t aux_func(uint32_t param) {
return param + 1;
}
uint32_t asm_func(uint32_t param) {
uint32_t ret;
asm (“slli %0, %1, 2” : “=r” (ret) : “r” (param) : );
return ret;
}

Otherwise you have to disable optimization to make this work. You can’t lie to the compiler, and then still expect it to produce correct code when optimizations are turned on.


#3

Thank you Jim. It sounds reasonable.

The problem is: I want to do unreasonable things. Besides changing a bunch of the “normal” registers, I’m moving the stack pointer too. And if I warn the compiler that I’m doing it, it tries to save the values before my changes and restores them later.

In the mean time I found this:


The answer says:

“The key to these techniques is to focus on the data.”

And then points to something like this:
__asm__ volatile("" : "+m"(data))

It ruins compiler optimization (just like the warning reads) but it kind of solves my problem.