-1

I have a function (C) that modifies "ecx" (or any other registers)

int proc(int n) {
    int ret;
    asm volatile ("movl %1, %%ecx\n\t" // mov (n) to ecx
                  "addl $10, %%ecx\n\t" // add (10) to ecx (n)
                  "movl %%ecx, %0" /* ret = n + 10 */
                  : "=r" (ret) : "r" (n) : "ecx");
    return ret;
}

now i want to call this function in another function which that function moves a value in "ecx" before calling "proc" function

int main_proc(int n) {
    asm volatile ("movl     $55, %%ecx" ::: "ecx"); /// mov (55) to ecx
    int ret;
    asm volatile ("call     proc" : "=r" (ret) : "r" (n) : "ecx"); // ecx is modified in proc function and the value of ecx is not 55 anymore even with "ecx" clobber

    asm volatile ("addl     %%ecx, %0" : "=r" (ret));

    return ret;
}

in this function, (55) is moved into "ecx" register and then "proc" function is called (which modifies "ecx"). in this situation, "proc" function Must push "ecx" first and pop it at the end but it's not going to happen !!!! this is the assembly source with (-O3) optimiaztion level

proc:
        movl %edi, %ecx
        addl $10, %ecx
        movl %ecx, %eax
        ret
main_proc:
        movl     $55, %ecx
        call     proc
        addl     %ecx, %eax
        ret

why GCC is not going to use (push) and (pop) for "ecx" register ?? i used "ecx" clobber too !!!!!

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Jason
  • 75
  • 1
  • 6
  • 1
    You're manually writing the assembly. How do you expect the c compiler to know you're calling a function? – Federico klez Culloca Aug 28 '19 at 08:07
  • 1
    Just don't write inline assembly. It's almost *never* worth it. – Some programmer dude Aug 28 '19 at 08:07
  • And what is the *actual* problem you have, the one you want to solve with the inline assembly? What is the reason you use inline assembly to begin with? Right now this is to much of an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Some programmer dude Aug 28 '19 at 08:10
  • Also, pushing and popping is on the caller, not on the callee. So it should be done in `main_proc`, not in `proc`. – Federico klez Culloca Aug 28 '19 at 08:10
  • Federico klez Culloca ok but why still there is no push or pop !!! – Jason Aug 28 '19 at 08:11
  • 2
    Because the C compiler doesn't interpret the assembly, so it doesn't know there's a call in there. – Federico klez Culloca Aug 28 '19 at 08:11
  • On x86, the *caller* is responsible for saving `ECX`. So your `main_proc` must assume that after calling `proc`, `ECX` has been overwritten, and `push`/`pop` it before/after the call. The clobber will only make the compiler not assume the value is kept during the inline assembly; it will *not* make the compiler save everything in clobbered registers, especially if it hasn't put any variable in there! The compiler doesn't know that the other inline assembly blocks require `ECX` to be saved. – Erlkoenig Aug 28 '19 at 08:13
  • 3
    No, it isn't. It's your duty. The GCC only saves its own data that might have been clobbered; *not* the data you have put there in inline assembly blocks. How would GCC know that `asm volatile ("movl $55, %%ecx" ::: "ecx");` saves something to `ECX` that is needed later? It might just overwrite `ECX` with temporary stuff that isn't needed ever again. – Erlkoenig Aug 28 '19 at 08:16
  • but still i used "ecx" clobber in calling "proc" function so compiler knows that "ecx" will going to be changed in "proc" function so it's must takes care about "ecx" pushing !!!! – Jason Aug 28 '19 at 08:17
  • @Jason what happens if you put the whole body of `main` inside an `asm volatile` directive? – Marco Bonelli Aug 28 '19 at 08:20
  • How should GCC know that `proc` is called at all? The call happens in inline assembly, so it is invisible to GCC. Also, how would GCC know that the value written to `ECX` is needed later? There is no indication to GCC that `asm volatile ("addl %%ecx, %0" : "=r" (ret));` uses `ECX` as an *input*. – Erlkoenig Aug 28 '19 at 08:20
  • I think you're misunderstanding what clobbering does. You're just telling gcc that you're touching those registers, but you're not telling it how it should transform your hand-written assembly to accommodate this. (On further reading, it's basically what @Erlkoenig said upthread). – Federico klez Culloca Aug 28 '19 at 08:22
  • 2
    @Jason when you insert your own assembly, GCC has no more responsibilities. GCC is a C compiler and does not interpret your assembly language. You are responsible for *everything*. If you don't want to be, write C code instead. :-) – John Szakmeister Aug 28 '19 at 08:23
  • Store the 55 in a C variable before the call, do the call as a C function call (otherwise GCC doesn't know a call that might clobber things happens!), and read it back from the C variable via an input operand. This way, GCC will save the register. – Erlkoenig Aug 28 '19 at 08:23
  • even i use (proc(n)) directly, there will be no push and pop ... i used call asm, to tell the compiler that "ecx" will be changed .... !!!!! even if i add "ecx" clobber to asm ("addl %%ecx, %0" : "=r" (ret) :: "ecx");, there is no push and pop !!!! – Jason Aug 28 '19 at 08:24
  • Of course, the compiler *still* doesn't know that `ECX` contains anything valuable. GCC saves only its own sheep (all C variables), but not what inline assembly puts there. Therefore, save the 55 in a C variable! – Erlkoenig Aug 28 '19 at 08:26

1 Answers1

4

You are using inline asm completely wrong. Your input/output constraints need to fully describe the inputs / outputs of each asm statement. To get data between asm statements, you have to hold it in C variables between them.

Also, call isn't safe inside inline asm in general, and specifically in x86-64 code for the System V ABI it steps on the red-zone where gcc might have been keeping things. There's no way to declare a clobber on that. You could use sub $128, %rsp first to skip past the red zone, or you could make calls from pure C like a normal person so the compiler knows about it. (Remember that call pushes a return address.) Your inline asm doesn't even make sense; your proc takes an arg but you didn't do anything in the caller to pass one.

The compiler-generated code in proc could have also destroyed any other call-clobbered registers, so you at least need to declare clobbers on those registers. Or hand-write the whole function in asm so you know what to put in clobbers.

why GCC is not going to use (push) and (pop) for "ecx" register ?? i used "ecx" clobber too !!!!!

An ecx clobber tells GCC that this asm statement destroys whatever GCC had in ECX previously. Using an ECX clobber in two separate inline-asm statements doesn't declare any kind of data dependency between them.

It's not equivalent to declaring a register-asm local variable like
register int foo asm("ecx"); that you use as a "+r" (foo) operand to the first and last asm statement. (Or more simply that you use with a "+c" constraint to make an ordinary variable pick ECX).

From GCC's point of view, your source means only what the constraints + clobbers tell it.

int main_proc(int n) {
    asm volatile ("movl     $55, %%ecx" ::: "ecx");
      // ^^ black box that destroys ECX and produces no outputs
    int ret;
    asm volatile ("call     proc" : "=r" (ret) : "r" (n) : "ecx");
      // ^^ black box that can take `n` in any register, and can produce `ret` in any reg.  And destroys ECX.

    asm volatile ("addl     %%ecx, %0" : "=r" (ret));
     // ^^ black box with no inputs that can produce a new value for `ret` in any register

    return ret;
}

I suspect you wanted the last asm statement to be "+r"(ret) to read/write the C variable ret instead of telling GCC that it was output-only. Because your asm uses it as an input as well as output as the destination of an add.

It might be interesting to add comments like # %%0 = %0 %%1 = %1 inside your 2nd asm statement to see which registers the "=r" and "r" constraints picked. On the Godbolt compiler explorer:

# gcc9.2 -O3 
main_proc:
        movl     $55, %ecx
        call     proc         # %0 = %edi   %1 = %edi
        addl     %ecx, %eax    # "=r" happened to pick EAX,
                      # which happens to still hold the return value from  proc
        ret

That accident of picking EAX as the add destinatino might not happen after this function inlines into something else. or GCC happens to put some compiler-generated instructions between asm statements. (asm volatile is barrier to compile-time reordering but not not a strog one. It only definitely stops optimizing away entirely).

Remember that inline asm templates are purely text substitution; asking the compiler to fill in an operand into a comment is no different from anywhere else in the template string. (Godbolt strips comment lines by default so sometimes it's handy to tack them onto other instructions, or onto a nop).

As you can see, this is 64-bit code (n arrives in EDI as per the x86-64 SysV calling convention, like how you built your code), so push %ecx wouldn't be encodeable. push %rcx would be.

Of course if GCC actually wanted to keep a value around past an asm statement with an "ecx" clobber, it would have just used mov %ecx, %edx or whatever other call-clobbered register that wasn't in the clobber list.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847