2

I was reading some answers and questions on here and kept coming up with this suggestion but I noticed no one ever actually explained "exactly" what you need to do to do it, On Windows using Intel and GCC compiler. Commented below is exactly what I am trying to do.

#include <stdio.h>

int main()
{
    int x = 1;
    int y = 2;
    //assembly code begin
    /*
      push x into stack; < Need Help
      x=y;               < With This
      pop stack into y;  < Please
    */
    //assembly code end
    printf("x=%d,y=%d",x,y);
    getchar();
    return 0;
}
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
CodeCamper
  • 6,609
  • 6
  • 44
  • 94
  • 2
    Why would you want to do that? I see no I see no advantage against the normal `tmp = x; x = y; y = tmp;`, especially since what you want is the same thing (but using `push`/`pop` for the temporary variable). – Some programmer dude Jul 29 '13 at 10:29
  • 2
    @JoachimPileborg I want to measure the difference, and also for educational/experimental purposes. – CodeCamper Jul 29 '13 at 10:41
  • 1
    "Measuring the difference" in the given example is meaningless, because the compiler would completely optimize out the "pure C" swap code, and treat it as if you'd written `printf("x=%d,y=%d",2,1)` directly. – FrankH. Jul 30 '13 at 22:20
  • Also see [GCC inline assembly with stack operation](https://stackoverflow.com/q/48853757/608639) for a discussion of stack maintenance on x86_64. It is too bad no one took the time to explain it in your context or find a duplicate for you. – jww Jun 22 '18 at 03:28

3 Answers3

3

You can't just push/pop safely from inline asm, if it's going to be portable to systems with a red-zone. That includes every non-Windows x86-64 platform. (There's no way to tell gcc you want to clobber it). Well, you could add rsp, -128 first to skip past the red-zone before pushing/popping anything, then restore it later. But then you can't use an "m" constraints, because the compiler might use RSP-relative addressing with offsets that assume RSP hasn't been modified.

But really this is a ridiculous thing to be doing in inline asm.

Here's how you use inline-asm to swap two C variables:

#include <stdio.h>

int main()
{
    int x = 1;
    int y = 2;

    asm(""                  // no actual instructions.
        : "=r"(y), "=r"(x)   // request both outputs in the compiler's choice of register
        :  "0"(x),  "1"(y)   // matching constraints: request each input in the same register as the other output
        );
    // apparently "=m" doesn't compile: you can't use a matching constraint on a memory operand

    printf("x=%d,y=%d\n",x,y);
    // getchar();  // Set up your terminal not to close after the program exits if you want similar behaviour: don't embed it into your programs
    return 0;
}

gcc -O3 output (targeting the x86-64 System V ABI, not Windows) from the Godbolt compiler explorer:

.section .rodata
.LC0:
    .string "x=%d,y=%d"
.section .text
main:
    sub     rsp, 8
    mov     edi, OFFSET FLAT:.LC0
    xor     eax, eax
    mov     edx, 1
    mov     esi, 2
#APP
# 8 "/tmp/gcc-explorer-compiler116814-16347-5i3lz1/example.cpp" 1
            # I used "\n" instead of just "" so we could see exactly where our inline-asm code ended up.

# 0 "" 2
#NO_APP
    call    printf
    xor     eax, eax
    add     rsp, 8
    ret

C variables are a high level concept; it doesn't cost anything to decide that the same registers now logically hold different named variables, instead of swapping the register contents without changing the varname->register mapping.

When hand-writing asm, use comments to keep track of the current logical meaning of different registers, or parts of a vector register.


The inline-asm didn't lead to any extra instructions outside the inline-asm block either, so it's perfectly efficient in this case. Still, the compiler can't see through it, and doesn't know that the values are still 1 and 2, so further constant-propagation would be defeated. https://gcc.gnu.org/wiki/DontUseInlineAsm

Community
  • 1
  • 1
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • I'd be interested to see the push/pop version. I suspect there's a little more to it because of x86_64 and stack maintenance. – jww Jun 22 '18 at 03:22
  • @jww: Use @GiuseppePes's currently-unsafe answer, but put `add $-128, %%rsp` before it, and `sub $-128, %%rsp` after it, for the x86-64 SysV ABI with a red-zone. (`-128` fits in an imm8, but +128 doesn't, hence the reversal). You don't have to keep the stack 16-byte aligned or anything inside inline asm, you just have to preserve it (because you can't declare a clobber on it). Or compile with `-mno-red-zone`. What are you really interested in? The only good reason to use the stack from inline asm is making a function call, which you should avoid doing. Otherwise use dummy tmp outputs. – Peter Cordes Jun 22 '18 at 03:52
1

You can use extended inline assembly. It is a compiler feature whicg allows you to write assembly instructions within your C code. A good reference for inline gcc assembly is available here.

The following code copies the value of x into y using pop and push instructions.
( compiled and tested using gcc on x86_64 )

This is only safe if compiled with -mno-red-zone, or if you subtract 128 from RSP before pushing anything. It will happen to work without problems in some functions: testing with one set of surrounding code is not sufficient to verify the correctness of something you did with GNU C inline asm.

  #include <stdio.h>

    int main()
    {
        int x = 1;
        int y = 2;

   asm volatile ( 
        "pushq  %%rax\n"          /* Push x into the stack */ 
        "movq   %%rbx, %%rax\n"   /* Copy y into x         */ 
        "popq   %%rbx\n"          /* Pop  x into y         */
      : "=b"(y), "=a"(x)          /* OUTPUT values         */ 
      : "a"(x),  "b"(y)           /* INPUT  values         */
      :    /*No need for the clobber list, since the compiler knows
             which registers have been modified            */
    ); 


        printf("x=%d,y=%d",x,y);
        getchar();
        return 0;
    }

Result x=2 y=1, as you expected.

The intel compiler works in a similar way, I think you have just to change the keyword asm to __asm__. You can find info about inline assembly for the INTEL compiler here.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Giuseppe Pes
  • 7,772
  • 3
  • 52
  • 90
  • 1
    The op intends to swap contents of x and y using the stack as the temporary location. Can you modify your code-snippet to reflect the same?... – TheCodeArtist Jul 29 '13 at 11:19
  • Yes, sure! Sorry I forget that operation. – Giuseppe Pes Jul 29 '13 at 11:23
  • @TheCodeArtist I've updated the code as specified in the question. Thanks for your notification! – Giuseppe Pes Jul 29 '13 at 11:32
  • Thank you for the code! I am working on fixing some errors changed the asm to __asm__, it says for me bad register name rax and rbx. Also I am running on a 64 bit machine and my gcc settings I am running are as follows -march=native -O3 -std=iso9899:2011 , did some research native should be working as I am using an i7 processor not sure why it would give that error %rax bad register name... – CodeCamper Jul 29 '13 at 11:39
  • I ran the code on 64 bit machine as well. I compiled with -std=gnu99 and no optimizations! Why did you change asm to __ asm __ ? in gcc should leave the code unchanged. That is the reason because you are getting %rax error. __ asm __ does not require double %% as asm does. – Giuseppe Pes Jul 29 '13 at 11:45
  • I recopy and pasted your code and changed nothing. I also removed all settings from my compiler and tried std=gnu99 and I also tried specifying a second time for corei7 and both times it is still saying bad register name `%rax' bad register name `%rbx' bad register name `%rbx' – CodeCamper Jul 29 '13 at 11:50
  • That s really weird. I don't have any problem compiling it and I have a intel i7 as well. Have you tried the solution of @Brett Hale? It avoids to specify the register name. – Giuseppe Pes Jul 29 '13 at 12:06
  • Can you try removing __volatile__ please? – Giuseppe Pes Jul 29 '13 at 12:07
  • Yes it appears his solution is working. Do you have any command to compile in 64 bit. I think my programs are compiling 32 bit and I am having trouble finding the right command to compile 64 bit. – CodeCamper Jul 29 '13 at 12:07
  • You can compile for 64bit with the option __-m64__ – Giuseppe Pes Jul 29 '13 at 12:09
  • When I use -m64 I get c:1:0: sorry, unimplemented: 64-bit mode not compiled in – CodeCamper Jul 29 '13 at 12:15
1
#include <stdio.h>

int main()
{
    int x=1;
    int y=2;
    printf("x::%d,y::%d\n",x,y);
    __asm__( "movl %1, %%eax;"
             "movl %%eax, %0;"
             :"=r"(y)
             :"r"(x)
             :"%eax"
            );
    printf("x::%d,y::%d\n",x,y);
    return 0;
}

/* Load x to eax
Load eax to y */

If you want to exchange the values, it can also be done using this way. Please note that this instructs GCC to take care of the clobbered EAX register. For educational purposes, it is okay, but I find it more suitable to leave micro-optimizations to the compiler.