0

I'm learning about using inline assembly inside the C++ code.

Here is the very simple example:

// Power2_inline_asm.c
// compile with: /EHsc
// processor: x86

#include <stdio.h>

int power2( int num, int power );

int main( void )
{
    printf_s( "3 times 2 to the power of 5 is %d\n", \
              power2( 3, 5) );
}
int power2( int num, int power )
{
   __asm
   {
      mov eax, num    ; Get first argument
      mov ecx, power  ; Get second argument
      shl eax, cl     ; EAX = EAX * ( 2 to the power of CL )
   }
   // Return with result in EAX
}

Since the power2 function returns the result WHY isn't there a ret instruction a the end of the asm code?

Or a C++ return keyword outside the asm block, before the end of the function?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
derek911
  • 19
  • 5
  • 2
    The C compiler automatically adds a `ret` instruction and whatever other instructions are needed before that. Do not add a `ret` yourself as that will most likely break the code. – fuz Jul 19 '21 at 09:31
  • @fuz from where should the compiler take the value to return? – Dee Jul 19 '21 at 09:34
  • 1
    Apparently you are learning how to use inline assembly in 32-bit x86 MSVC. The `__asm` statement is not supported by x64 MSVC, and it is different from how other compilers support inline assembly. I mean you might be learning something that has a limited use. – Alex Guteniev Jul 19 '21 at 09:40
  • 1
    `__asm` is a compiler extension, for a specific compiler and a specific set of platforms. You may be interested in standard C++ [`asm`](https://en.cppreference.com/w/cpp/language/asm) keyword, although its steep caveats makes it not much different than `__asm`. – Eljay Jul 19 '21 at 09:45
  • The value to return is taken from `eax`. There's even a nice comment saying so. – fuz Jul 19 '21 at 10:29

3 Answers3

2

EAX is implied to contain return value, and there's ret generated by complier (some code is generated by compiler, if __declspec(naked) is not specified). Since there's no C++ return statement, from C++ point of view the behavior is undefined, the manifestation of undefined behavior is to return whatever EAX contains, which is the result.

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • how about returning float, is it still in eax? – Dee Jul 19 '21 at 09:35
  • 1
    float is in `ST0` – Alex Guteniev Jul 19 '21 at 09:37
  • @Alex Guteniev Ok. I've rewiritten the example with the naked form like `that:__declspec(naked) int my_mul2(int num, int power) { __asm { mov eax, num ; Get first argument mov ecx, power ; Get second argument mul ecx ; EAX = EAX * (2 to the power of CL) ret // final value is in eax } }` but it's NOT returning the proper result.. – derek911 Jul 19 '21 at 09:53
  • 2
    @derek911 You can't refer to function arguments in naked functions like that. Do not use inline assembnly unless you know what you are doing! This is a really bad way to learn assembly programming. – fuz Jul 19 '21 at 10:30
  • @fuz So WHAT's the proper way for accessing the function arguments in naked functions!? – derek911 Jul 19 '21 at 15:28
  • @derek911 manually compute the appropriate stack pointer offsets. – fuz Jul 19 '21 at 15:42
  • 1
    MSVC specifically does support falling off the end of a non-void function after an `asm` statement, treating EAX or ST0 as the return value. Or at least that's how MSVC de-facto works, whether it's intentional support or not, but it does even support inlining such functions, so it's not just a calling-convention abuse of UB. (clang `-fasm-blocks` does *not* work that way; IDK about `clang-cl`. But it does not define the behaviour of falling off the end of a non-void function, fully omitting the `ret` because that path of execution must not be reachable.) – Peter Cordes Jul 19 '21 at 19:36
1

It seems you're unclear about the relationship between the ret instruction and return values. There is none.

The operand to the ret instruction is not the return value, it's the number of bytes to remove from the stack for calling conventions where the callee handles argument cleanup.

The return value is passed in some other way, controlled by the calling convention, and must be stored before reaching the ret instruction.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
1

Not having a ret instruction in the asm is totally normal; you want to let the compiler generate function prologues / epilogues, including a ret instruction, like they normally do for paths of execution that reach the } in a function. (Or you'd use __declspec(naked) to write the whole function in asm, including handling the calling convention, which would let you use fastcall to take args in registers instead of needing to load them from stack memory).

The more interesting thing is falling off the end of a non-void function without a return. (I edited your question to ask about that, too).

In ISO C++, that's undefined behaviour. (So compilers like clang -fasm-blocks can assume that path of execution is never reached, and not even emit any instructions for it, not even a ret.) But MSVC does at least de-facto define the behaviour of doing that,

MSVC specifically does support falling off the end of a non-void function after an asm statement, treating EAX or ST0 as the return value. Or at least that's how MSVC de-facto works, whether it's intentional support or not, but it does even support inlining such functions, so it's not just a calling-convention abuse of UB. (clang -fasm-blocks does not work that way; IDK about clang-cl. But it does not define the behaviour of falling off the end of a non-void function, fully omitting the ret because that path of execution must not be reachable.)


Not using ret in the asm{} block

ESP isn't pointing at the return address when the asm{} block executes; I think MSVC always forces functions using asm{} to set up EBP as a frame pointer.

You definitely can't just ret out of the middle of a function without giving the compiler a chance to restore call-preserved registers and clean up the stack in the function epilogue.

Also, what if the compiler had inlined power2 into a caller?
Then you'd be returning from that caller (if you did leave / ret in an asm block).


Look at the compiler-generated asm.

(TODO: I was going to write more and link something on https://godbolt.org/, but never got back to it.)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • I found this half-written answer in an old tab; hopefully it's more useful than nothing, feel free to edit or let me know if there are any major gaps I didn't notice in a quick look over it. – Peter Cordes Nov 16 '21 at 11:56
  • See also [Does \_\_asm{}; return the value of eax?](https://stackoverflow.com/q/36802683) – Peter Cordes Oct 23 '22 at 17:36