0

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.

Shiro
  • 2,610
  • 2
  • 20
  • 36
  • You haven't even specified which compiler ... – Jester Jun 26 '16 at 14:33
  • 2
    inline asm is totally implementation defined. You can find the [link](http://en.cppreference.com/w/cpp/language/asm) usefull – Incomputable Jun 26 '16 at 14:34
  • 6
    C has very little protection from attempts to shoot oneself in a foot; assembly has none of it. – Sergey Kalinichenko Jun 26 '16 at 14:37
  • It's not C it's C++. – Shiro Jun 26 '16 at 14:38
  • See the [manual](https://gcc.gnu.org/onlinedocs/gcc/Basic-Asm.html). In particular, "GCC assumes that no changes to general purpose registers occur" – Jester Jun 26 '16 at 14:45
  • @dwelch you know you can edit comments? :) – Jester Jun 26 '16 at 14:50
  • That's why I used the smiley ... this can't be the first time you have seen that on the internet. Still, those things don't look too separate to me, but whatever. – Jester Jun 26 '16 at 14:56
  • 1
    @Jester Judging by the implementation-specific `__asm` keyword, and the Intel ASM syntax, it would appear to be Visual Studio. From what I can tell, G++ prefers the implementation-specific `__asm__` keyword (or just plain `asm`, but neither compiler seems to consider that optimal), and has ASM (preferably AT&T ASM) code enclosed in quotation marks. – Justin Time - Reinstate Monica Jun 26 '16 at 17:22
  • 1
    @JustinTime in a deleted comment OP said gcc. Unless I imagined it :) – Jester Jun 26 '16 at 17:51
  • @Jester Ah, okay. Might help if they check GCC syntax, then. – Justin Time - Reinstate Monica Jun 26 '16 at 18:32
  • @Jester : I can't read deleted comments but the deleted comment came across as a perturbed one that seemed like he didn't care and said along the lines 'just assume g++.` as if he didn't think it mattered. The fact it is gone suggests maybe they realized that MSVC++ and G++ inline assembler are different lol? – Michael Petch Jun 26 '16 at 21:58
  • @MichaelPetch yeah could be. Still, he should have edited that information (g++ or visual studio) into the question. I don't understand why people upvote... – Jester Jun 26 '16 at 22:00
  • @Jester : I agree. I was pointing out that you weren't insane to think there was a comment in that regard. There was, I'm just not sure if the OP knew what they were saying. I'm not an upvoter on this one since one still has to guess as to the platform, some extra tags would be nice too. As it is it borders (or is) too broad. – Michael Petch Jun 26 '16 at 22:06
  • No it perfectly answers the question. I just wanted to clear things up. – Shiro Jun 26 '16 at 23:12

3 Answers3

7

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 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.

Community
  • 1
  • 1
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 2
    MSVC doesn't save and restore all registers that may be used in an _ASM_ block. In general you are free to use _ESI_, _EDI_, _EAX_, _EBX_, _ECX_, _EDX_. (There are some rules regarding _EBX_ under some circumstances, avoiding usage is a safer bet). Specifically modifying , _ESI_, _EDI_, _EBX_ will force MSVC to emit save and restore code for those registers (push/pop) in the encapsulating functions prologue and epilogue code. – Michael Petch Jun 26 '16 at 16:51
  • 1
    I have to retract that last part about the inline MSVC and the `RET`. That only applies in separate assembler modules that use the PROC directive for defining a function. Haven't had morning coffee so will remove it. – Michael Petch Jun 26 '16 at 17:14
  • What would be true is doing this (or equivalent) to return from a _CDECL_ assembler function `mov eax, 42` `leave` `ret`. You could of course replace `leave` with the equivalent long form epilogue code. But my original comment that setting a register and allowing it to pass out of the inline assembler should be avoided. Other alternative is to pass _EAX_ to a _C_ variable and return that variable with `return`. – Michael Petch Jun 26 '16 at 17:31
  • @MichaelPetch: Thanks for the edit to clear that up. Using `leave` / `ret` isn't safe, because you can't be sure that MSVC doesn't need to restore another reg. So looks like no way around the round-trip through memory for the return value, if you don't want to check the asm for every build to make sure the leave-something-in-eax hack worked safely. – Peter Cordes Jun 26 '16 at 17:36
  • @MichaelPetch: I can never remember: Can MSVC inline a function containing an `__asm{}` block? Some examples seem to assume that it won't be inlined, but I forget if any of those examples are known to actually work safely with optimization enabled. I'm trying to decide whether to say that leaving something in `eax` is a "mostly-safe assumption" or not. I should probably change it to either "unsafe" or "sometimes safe" – Peter Cordes Jun 26 '16 at 17:40
  • What I didn't say about leave ret is that will only apply if your function doesn't have any C code and the asm block is the only thing in the function – Michael Petch Jun 26 '16 at 17:40
  • 1
    Yes, MSVC will definitely inline a function containing an `__asm{}` block. I was pretty certain that it would, and I just tested it in VS 2010. This doesn't even require link-time code generation (LTCG/FPO), assuming of course that the two functions are in the same compiland. __forceinline is not needed either, assuming that the function qualifies for inlining using the compiler's normal heuristics. – Cody Gray - on strike Jun 27 '16 at 11:00
  • @CodyGray: So that "leave something in `eax`" technique and omit a `return` in a non-void function is just complete garbage, then? Thanks. – Peter Cordes Jun 27 '16 at 12:28
  • Actually, no. That works fine. The calling convention mandates that EAX contains the return value, so the compiler knows it'll be there. I've used that trick several times because you often end up with slightly more optimal code than if you store the result in a temporary variable and return that temporary at the end of the block. [Here's an example](https://gist.github.com/anonymous/2910a6b7b2ca9ffa4d129871b287e81b), showing original source and compiler-generated assembly. Focusing on code emitted for `Function`, you see it inserts the inline assembly directly, then immediately pushes EAX. – Cody Gray - on strike Jun 27 '16 at 12:45
  • @CodyGray: Wow, that's super-weird. In gcc, register allocation happens after inlining, so the return value of an inlined function doesn't ever have to go through `eax`. e.g. with [that code ported to GNU C](https://godbolt.org/g/rN26Jb), gcc decides to just `xor esi,esi` as the second arg to printf, because it sees that `Helper` has no return statement, so its return value is effectively an uninitialized variable. – Peter Cordes Jun 27 '16 at 12:59
3

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.

Mike Robinson
  • 8,490
  • 5
  • 28
  • 41
  • 4
    Actually, MSVC-style inline asm (like the OP posted) *does* look at your asm and saves/restores any registers you use around your inline-asm block. The only permanent effects are from storing to memory. – Peter Cordes Jun 26 '16 at 15:37
1

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.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • Yes, there are intrinsics for most things you might want to do with inline asm, esp if you're doing it for performance reasons, or for memory-ordering reasons. Intrinsics are highly preferable, because the compiler understands them and can do constant-propagation through them, and other stuff. https://gcc.gnu.org/wiki/DontUseInlineAsm is a great summary. I'd agree with the recommendation to write whole functions in asm if you're going to use it at all, or write whole loops in inline asm so you can leave the calling-convention and stack-unwind metadata stuff up to the compiler. – Peter Cordes Jun 26 '16 at 20:31
  • You can achieve the same thing if you use naked functions, you are then responsible for handing the calling conventions. – doron Jun 26 '16 at 20:50
  • @doron: If you're compiling for a target where naked functions are supported. e.g. g++ targeting ARM, but not g++ targeting x86. What's the point of a naked function vs. writing a proper asm source file? Is it just so you can get access to CPP macros? gcc uses cpp on `.S` files. You do have to keep your definitions separate from your prototypes, though. – Peter Cordes Jun 26 '16 at 20:56
  • For c++ naked functions give you offsetof to access object members and I think you get name mangling as well for function calls. – doron Jun 27 '16 at 00:04