Lec 7: Machine Level Programming: Control¶
Outline¶
- Stack Structure
 - Calling Conventions
 - Passing Control
 - Passing Data
 - Managing Local Data
 - Illustrations of Recursions & Pointers
 
Mechanism in Procedures¶

Note: reducing the overhead of procedure calls is very important (and it is well optimized in x86-64), esp. in OOP and FP, where each procedure only does a small amount of actual useful stuff.
Stack Structure¶
The top address of x86-64 stack is very high, and when the stack grows, %rsp decrements.
                   Stack Bottom
          +---------------------------+
          |                           |  
          |                           |
          |                           |
          |                           |
          |                           |
          |                           |
                     ........
          |                           |
          |                           |
          |                           |
          |                           |
          |                           |
          |                           |  <== Stack Pointer: %rsp
          |                           |
          +---------------------------+
                    Stack Top          
Operations:
pushq [Src]- Fetch operand at 
[Src] - Decrement 
%rspby 8 - Write operand at address given by 
%rsp popq [Dest]- Read value at address given by 
%rsp - Increment 
%rspby 8 - Store value at 
[Dest](must be register) 
Calling Conventions¶
Passing Control¶
call [Address/Label]- push 
%rip(i.e. instruction pointer) onto stack - set 
%ripto[Address/Label] ret- in some sense, equivalent to 
popq %rip(but notice%ripisn't allowed to be accessed directly, so in reality this won't work ) 
Passing Data¶

Example:
my_function:
.LFB0:
    # x is %rdi, y is %rsi, dest is %rdx
    .cfi_startproc
    endbr64
    movq    %rdi, %rax    # let return_val = x
    imulq   %rsi, %rax    # return_val *= y
    movq    %rax, (%rdx)  # let *dest = return_val
    ret                   
    .cfi_endproc
Managing Local Data: Stack Frame¶

Example:

call_incr:
    subq    $16, %rsp        # allocate 16 byte of stack space
    movq    $18213, 8(%rsp)  # store 18213 at [7:15]
    movl    $3000, %esi      # store 3000 at $esi
    leaq    8(%rsp), %rdi    # let rdi (i.e. the first argument of the function `incr`) point to [7:15]
    call    incr
    # now, rax := rtn_val of `incr`
    addq    8(%rsp), %rax    # let rtn_val += [7:15]
    addq    $16, %rsp        # free the 16 byte of stack space
    ret
Caller-Saved vs. Callee-Saved¶
When
yoocallswho:
yoois the callerwhois the callee
- caller-saved
  

 - callee-saved
  

 
Example:

In this example, call_incr2 want to use %rbx. Since %rbx is callee-saved register, call_incr2 should save %rbx in its own stack frame.
Recursive Function¶
Recursive function is not too different from non-recursive ones.
Example: pcount_r¶
pcount_r:
    movl    $0, %eax
    testq   %rdi, %rdi  # i.e. rdi&rdi
    je      .L6
    pushq   %rbx        # callee-saved
    movq    %rdi, %rbx  
    # Note: operations involving `%e..` will automatically set the higher 32 bits of `%r..` to 0
    andl    %1, %ebx
    shrq    %rdi
    call    pcount_r
    addq    %rbx, %rax  # %rax is pcount_r(...), 
    # %rbx is (x&1)
    popq    %rbx        # callee-saved
.L6:
    ret