Reordering instructions

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!

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.

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.