GCC Trampolines

Paul Wayper writes about trampolines. This is something I've come across before, when I was learning about function pointers with IA64.

Nested functions are a strange contraption, and probably best avoided (people who should know agree). For example, here is a completely obfuscated way to double a number.

#include <stdio.h>

int function(int arg) {

        int nested_function(int nested_arg) {
                return arg + nested_arg;
        }

        return nested_function(arg);
}


int main(void)
{
        printf("%d\n", function(10));
}

Let's disassemble that on IA64 (because it is so much easier to understand).

0000000000000000 :
   0:   00 10 15 08 80 05       [MII]       alloc r34=ar.pfs,5,4,0
   6:   c0 80 33 7e 46 60                   adds r12=-16,r12
   c:   04 08 00 84                         mov r35=r1
  10:   01 00 00 00 01 00       [MII]       nop.m 0x0
  16:   10 02 00 62 00 80                   mov r33=b0
  1c:   04 00 01 84                         mov r36=r32;;
  20:   0a 70 40 18 00 21       [MMI]       adds r14=16,r12;;
  26:   00 00 39 20 23 e0                   st4 [r14]=r32
  2c:   01 70 00 84                         mov r15=r14
  30:   10 00 00 00 01 00       [MIB]       nop.m 0x0
  36:   00 00 00 02 00 00                   nop.i 0x0
  3c:   48 00 00 50                         br.call.sptk.many b0=70
  40:   0a 08 00 46 00 21       [MMI]       mov r1=r35;;
  46:   00 00 00 02 00 00                   nop.m 0x0
  4c:   20 02 aa 00                         mov.i ar.pfs=r34
  50:   00 00 00 00 01 00       [MII]       nop.m 0x0
  56:   00 08 05 80 03 80                   mov b0=r33
  5c:   01 61 00 84                         adds r12=16,r12
  60:   11 00 00 00 01 00       [MIB]       nop.m 0x0
  66:   00 00 00 02 00 80                   nop.i 0x0
  6c:   08 00 84 00                         br.ret.sptk.many b0;;

0000000000000070 :
  70:   0a 40 00 1e 10 10       [MMI]       ld4 r8=[r15];;
  76:   80 00 21 00 40 00                   add r8=r32,r8
  7c:   00 00 04 00                         nop.i 0x0
  80:   11 00 00 00 01 00       [MIB]       nop.m 0x0
  86:   00 00 00 02 00 80                   nop.i 0x0
  8c:   08 00 84 00                         br.ret.sptk.many b0;;

Note that nothing funny happens there at all. The nested function looks exactly like a real function, but we don't do any of the alloc or anything to get us a new stack frame -- we're using the stack frame of the old function. No trampoline involved, just a straight branch.

So why do we need a trampoline? Because a nested function has an extra property over a normal function; the stack pointer must be set to be the stack pointer of the function it is nested within. This means that if we were to pass around pointers to the nested function, we would have to keep track that this isn't a pointer to just any old function, but a pointer to special function that needs its stack setup to come from the nested function. C doesn't work like this, there is only one type "pointer to function".

The easiest way is therefore to create a little function that sets the stack to the right place and calls the code. This "trampoline" is a normal function, so no need for trickery. For example, we can modify this to use a function pointer to the nested function:

#include <stdio.h>

int function(int arg) {

        int nested_function(int nested_arg) {
                return arg + nested_arg;
        }

        int (*function_pointer)(int arg) = nested_function;

        return function_pointer(arg);
}


int main(void)
{
        printf("%d\n", function(10));
}

We can see that now we are calling through a function pointer, we go through __ia64_trampoline, which is a little code stub in libgcc.a which loads up the right stack pointer and calls the "nested" function.

function:
    .prologue 12, 33
    .mmb
    .save ar.pfs, r34
    alloc r34 = ar.pfs, 1, 3, 1, 0
    .fframe 48
    adds r12 = -48, r12
    nop 0
    .mmi
    addl r15 = @ltoffx(__ia64_trampoline#), r1
    mov r35 = r1
    .save rp, r33
    mov r33 = b0
    .body
    ;;
    .mmi
    nop 0
    adds r8 = 24, r12
    adds r14 = 16, r12
    .mmi
    ld8.mov r15 = [r15], __ia64_trampoline#
    ;;
    st4 [r14] = r32
    nop 0
    .mmi
    mov r14 = r8
    ;;
    st8 [r14] = r15, 8
    adds r15 = 40, r12
    ;;
    .mfi
    st8 [r14] = r15, 8
    nop 0
    addl r15 = @ltoff(@fptr(nested_function.1814#)), gp
    ;;
    .mmi
    ld8 r15 = [r15]
    ;;
    st8 [r14] = r15, 8
    adds r15 = 16, r12
    ;;
    .mmb
    st8 [r14] = r15
    ld8 r14 = [r8], 8
    nop 0
    .mmi
    ld4 r36 = [r15]
    ;;
    nop 0
    mov b6 = r14
    .mbb
    ld8 r1 = [r8]
    nop 0
    br.call.sptk.many b0 = b6
    ;;
    .mii
    mov r1 = r35
    mov ar.pfs = r34
    mov b0 = r33
    .mib
    nop 0
    .restore sp
    adds r12 = 48, r12
    br.ret.sptk.many b0
    .endp function#
    .section    .rodata.str1.8,"aMS",@progbits,1
    .align 8

So, the summary is that if you're taking the address of a nested function, to avoid having different types of pointers to functions if they are nested or not gcc puts in the stub "trampoline" code.