What happens to registers when you manipulate them using asm code in C++?

北城余情 提交于 2021-02-20 03:00:07

问题


Some code:

int x = 1;
for(int i = 1; i < 10; i++)
{
    x *= i;
    __asm {
        mov eax, x 
    };
}

If this program uses eax in order to increase the value of i, what will happen when I manipulate eax?

Will the compiler save registers from before the __asm call and use them after the asm code was executed or will it ignore that eax was manipulated and continue producing some sort of strange behavior?

What happens to eax internally?

EDIT: Even if my code only works with Visual C++ I want to know what happens in general and how different compilers will handle this.


回答1:


What happens to eax internally?

Inline asm isn't magic or special. C++ compilers already translate C++ into asm.

Your inline asm just gets included in with the compiler-generated asm. If you want to understand what's really happening, look at the asm output from the compiler to see how your code fits in.

i.e. the answer to this part of the question is that it depends on the compiler, the context, and the optimization options, so you should just look at the generated asm to see for yourself.


Your question uses MSVC-style inline asm, which saves/restores registers around inline asm (other than ebp, and of course esp). So I think your example could would always have no effect. MSVC-style has no syntax to communicate anything to the compiler about register usage, or for getting values in/out in registers instead of memory.

You sometimes see MSVC inline asm that leaves a value in eax at the end of an int function with no return statement, making the mostly-safe assumption under limited circumstances that the compiler won't do anything with eax between the end of the inline-asm and the end of the function.


You said in comments you'd like an answer for g++, which couldn't even compile your example, but whatever, I'll write one for you.

GNU C inline asm uses different syntax, which requires you to tell the compiler which registers are inputs (and not modified), and which are read-write or write-only. Also which registers are clobbered scratch regs.

It's up to the programmer to correctly describe the asm to the compiler using constraints, otherwise you will step on its toes. Using GNU C inline asm is like a dance, where you can potentially achieve good results, but only if you're not careful you'll step on the compiler's toes. (Also, usually the compiler can make good asm on its own, and inline asm is a very brittle way to optimize; one of the many major problems is that constant propagation after inlining isn't possible through inline asm.)


Anyway, let's try it with a working example of GNU C inline asm:

int foo_badasm(int n) {
  int factorial = 1;
  for (int i=1 ; i < n ; i++ ) {
    // compile with -masm=intel, since I'm using Intel syntax here
    asm volatile ("mov   eax, %[x]   # THIS LINE FROM INLINE ASM\n"
                  "# more lines\n"
                  // "xor  eax,eax\n"
        : // no outputs, making the volatile implicit even if we didn't specify it
        : [x] "rmi" (factorial)   // input can be reg, memory, or immediate
        : // "eax"   // uncomment this to tell the compiler we clobber eax, so our asm doesn't break step on the compiler's toes.
    );
    factorial *= i;
  }
  return factorial;
}

See the code with asm output on the Godbolt compiler explorer, and for the same function with no asm statement.

The inner loop compiles to this (gcc 6.1 -O3 -fverbose-asm -masm=intel, with -fno-tree-vectorize to keep it simple). You can also try it with clang.

.L10:
    mov   eax, eax   # THIS LINE FROM INLINE ASM    # <retval>

    imul    eax, edx        # <retval>, i
    add     edx, 1    # i,
    cmp     edi, edx  # n, i
    jne     .L10      #,
    ret

As you can see, in this case the inline asm statement produced a no-op. (mov eax,eax truncates rax to 32 bits, zeroing the upper 32 bits, but they were already zero in this function.)

If we'd don't anything else, like zero the register, or mov from a different source, we would have broken the function. The asm generated by the compiler depends only on the constraints listed in the asm statement, not on the text of the code (unlike MSVC).


See the inline-assembly tag wiki for more info, especially this answer on the difference between MSVC and GNU C inline asm.

More importantly, read https://gcc.gnu.org/wiki/DontUseInlineAsm before actually using inline asm for anything.




回答2:


Short answer: "it's your foot ... don't shoot it!"

If you use an asm{} block, C/C++ expects that you know what you are doing. It won't generate any code to "prepare for" your insertion, nor "to clean up after it," and it won't know or consider what you did. In this case, you'd be shooting yourself in the foot. You'd have just introduced a bug, of your own making, by doing something that the compiler did not expect, does not know about, and that probably interferes with what the compiler-generated code is doing.

If you intend to manipulate registers in assembly code, you must take all appropriate steps to preserve and to restore the state of those registers. You must know exactly what you are doing.




回答3:


IMHO, the best method is to not use inline ASM, but to write a separate function in ASM.

The usual steps are:

  1. Write the code in C or C++ and get it functioning correctly.
  2. Print out the assembly listing generated by the compiler.
  3. Use the assembly code generated by compiler as your foundation (which makes writing calling and returning code easier).

Be aware that writing assembly language to optimize the compiler's generated code is usually a waste of development time. You should profile before optimizing.

Also, writing inline assembly is not portable.



来源:https://stackoverflow.com/questions/38039517/what-happens-to-registers-when-you-manipulate-them-using-asm-code-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!