Warm tip: This article is reproduced from serverfault.com, please click

Extended Inline Assembly in C++: Is it necessary to preserve volatile registers?

发布于 2020-12-03 00:27:18

I am analyzing the following snippet of extended inline assembly code that returns the sum of two numbers given as parameters into a function.

int addAB(int a, int b){
    int result;

    __asm__(
        "movl %%ebx, %%eax;"
        "addl %%ecx, %%eax;":
        "=a" (result) :
        "b" (b), "c" (a) :
        "cc"
    );


    return result;
}

From my understanding, since EBX is a callee-saved register, it's value means it needs to be preserved across calls (is it necessary to do a push-pop here)? Furthermore, the CC clobber is specified which means that the condition codes are changed, but it seems that condition codes are almost always changed (from adding registers together to comparing registers)--when aren't condition codes potentially changed? If they are almost always potentially changed, would it just be a safe idea to put the "cc" clobber option for every clobber?

Questioner
Adam Lee
Viewed
0
238k 2020-12-03 22:18:41

There is really just one rule to know:

If you modify any(*) register, you must tell the compiler about it, either by declaring that register as an output operand, or by listing it as a clobber.

That's all. It doesn't matter whether the register is "volatile" (aka caller-saved) or not. And if you do inform the compiler appropriately, then you do not need to save and restore the register yourself, so no push/pop in your inline asm. The compiler will take care of it for you if needed. (Indeed, on x86-64, you must not push anything to the stack with inline asm, because of the red zone.)

(*) "Any" means "any register that compiled code might use"; in particular, all general-purpose integer, floating point, and vector registers. It does not apply to system or control registers that the compiler would not otherwise touch, nor to the x86 segment registers for the same reason. If you modify any of these, you're responsible for the consequences, and making sure the compiled code will still work correctly in the new state. (For example, leaving the direction flag DF set will typically cause compiled code to fail, as it expects it to always be clear.)

Your code doesn't actually modify ebx. It's true that the compiler will have to modify ebx to load your argument b there, but the compiler is well aware that ebx is non-volatile and so it will take care of saving and restoring it. (Indeed, by the time your asm gets control, it would be too late to save it anyway.) The only register your code does modify is eax, and that's declared as an output operand, so you're good. If you did modify ebx, you would need to list it as a "+b" input-output operand (since you need it as an input) or as a clobber (if it wasn't already an input) - but the same would be true if you modified the callee-saved ecx.

Note that if this function inlines into a larger function, the compiler might already want to save/restore EBX for some other purpose. Or this function might inline multiple times. You only want to save/restore a register once per function, not once per asm statement that might be in a loop, so leaving it to the compiler is a good and sensible design.

As for the "cc" clobber, my opinion is that it is good practice to list it whenever your code modifies the flags register, as your add instruction does. However, on x86, since so many instructions modify the flags, and so many programmers forget to list it as a clobber, the compiler assumes that every inline asm clobbers the flags. So, technically, you can safely omit the clobber. However, this may develop bad habits, since on other architectures the clobber is not assumed and must be specified if applicable. See What happens in the assembly output when we add "cc" to clobber list.