x86 calling convention: should arguments passed by

2019-03-15 02:14发布

It seems state-of-art compilers treat arguments passed by stack as read-only. Note that in the x86 calling convention, the caller pushes arguments onto the stack and the callee uses the arguments in the stack. For example, the following C code:

extern int goo(int *x);
int foo(int x, int y) {
  goo(&x);
  return x;
}

is compiled by clang -O3 -c g.c -S -m32 in OS X 10.10 into:

    .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 10
    .globl  _foo
    .align  4, 0x90
_foo:                                   ## @foo
## BB#0:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    movl    8(%ebp), %eax
    movl    %eax, -4(%ebp)
    leal    -4(%ebp), %eax
    movl    %eax, (%esp)
    calll   _goo
    movl    -4(%ebp), %eax
    addl    $8, %esp
    popl    %ebp
    retl


.subsections_via_symbols

Here, the parameter x(8(%ebp)) is first loaded into %eax; and then stored in -4(%ebp); and the address -4(%ebp) is stored in %eax; and %eax is passed to the function goo.

I wonder why Clang generates code that copy the value stored in 8(%ebp) to -4(%ebp), rather than just passing the address 8(%ebp) to the function goo. It would save memory operations and result in a better performance. I observed a similar behaviour in GCC too (under OS X). To be more specific, I wonder why compilers do not generate:

  .section  __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 10
    .globl  _foo
    .align  4, 0x90
_foo:                                   ## @foo
## BB#0:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    leal    8(%ebp), %eax
    movl    %eax, (%esp)
    calll   _goo
    movl    8(%ebp), %eax
    addl    $8, %esp
    popl    %ebp
    retl


.subsections_via_symbols

I searched for documents if the x86 calling convention demands the passed arguments to be read-only, but I couldn't find anything on the issue. Does anybody have any thought on this issue?

3条回答
时光不老,我们不散
2楼-- · 2019-03-15 02:44

The rules for C are that parameters must be passed by value. A compiler converts from one language (with one set of rules) to a different language (potentially with a completely different set of rules). The only limitation is that the behaviour remains the same. The rules of the C language do not apply to the target language (e.g. assembly).

What this means is that if a compiler feels like generating assembly language where parameters are passed by reference and are not passed by value; then this is perfectly legal (as long as the behaviour remains the same).

The real limitation has nothing to do with C at all. The real limitation is linking. So that different object files can be linked together, standards are needed to ensure that whatever the caller in one object file expects matches whatever the callee in another object file provides. This is what's known as the ABI. In some cases (e.g. 64-bit 80x86) there are multiple different ABIs for the exact same architecture.

You can even invent your own ABI that's radically different (and implement your own tools that support your own radically different ABI) and that's perfectly legal as far as the C standards go; even if your ABI requires "pass by reference" for everything (as long as the behaviour remains the same).

查看更多
倾城 Initia
3楼-- · 2019-03-15 02:55

The C programming language mandates that arguments are passed by value. So any modification of an argument (like an x++; as the first statement of your foo) is local to the function and does not propagate to the caller.

Hence, a general calling convention should require copying of arguments at every call site. Calling conventions should be general enough for unknown calls, e.g. thru a function pointer!

Of course, if you pass an address to some memory zone, the called function is free to dereference that pointer, e.g. as in

int goo(int *x) {
    static int count;
    *x = count++;
    return count % 3;
}

BTW, you might use link-time optimizations (by compiling and linking with clang -flto -O2 or gcc -flto -O2) to perhaps enable the compiler to improve or inline some calls between translation units.

Notice that both Clang/LLVM and GCC are free software compilers. Feel free to propose an improvement patch to them if you want to (but since both are very complex pieces of software, you'll need to work some months to make that patch).

NB. When looking into produced assembly code, pass -fverbose-asm to your compiler!

查看更多
叼着烟拽天下
4楼-- · 2019-03-15 03:01

Actually, I just compiled this function using GCC:

int foo(int x)
{
    goo(&x);
    return x;
}

And it generated this code:

_foo:
        pushl       %ebp
        movl        %esp, %ebp
        subl        $24, %esp
        leal        8(%ebp), %eax
        movl        %eax, (%esp)
        call        _goo
        movl        8(%ebp), %eax
        leave
        ret

This is using GCC 4.9.2 (on 32-bit cygwin if it matters), no optimizations. So in fact, GCC did exactly what you thought it should do and used the argument directly from where the caller pushed it on the stack.

查看更多
登录 后发表回答