390 likes | 522 Vues
COMP 40: Machine Structure and Assembly Language Programming (Spring 2014). UM Macro Assembler Functions. Noah Mendelsohn Tufts University Email: noah@cs.tufts.edu Web: http:// www.cs.tufts.edu/~noah. Review x86-64 Standard Function Linkage.
E N D
COMP 40: Machine Structure and Assembly Language Programming (Spring 2014) UM Macro AssemblerFunctions Noah Mendelsohn Tufts UniversityEmail: noah@cs.tufts.edu Web: http://www.cs.tufts.edu/~noah
Why have a standard AMD 64 “linkage” for calling functions? (Review) • Functions are compiled separately and linked together • We need to standardize enough that function calls can be freely mixed • We may “cheat” when caller and callee are in the same source file
Function calls on Linux/AMD 64 • Caller “pushes” return address on stack • Where practical, arguments passed in registers • Exceptions: • Structs, etc. • Too many • What can’t be passed in registers is at known offsets from stack pointer! • Return values • In register, typically %rax for integers and pointers • Exception: structures • Each function gets a stack frame • Leaf functions that make no calls may skip setting one up • Caller vs. callee saved registers
AMD 64 The stack – general case (review) Before call After callq If callee needs frame Arguments Arguments Arguments %rsp Return address ???? ???? Return address %rsp Callee vars framesize Args to next call? %rsp sub $0x{framesize},%rsp
AMD 64 arguments/return values in registers Arguments and return values passed in registers when types are suitable and when there aren’t too many Return values usually in %rax, %eax, etc.Callee may change these and some other registers! MMX and FP 87 registers used for floating pointRead the specifications for full details!
AMD 64 Factorial Revisited fact: .LFB2: pushq %rbx .LCFI0: movq %rdi, %rbx movl $1, %eax testq %rdi, %rdi je .L4 leaq -1(%rdi), %rdi call fact imulq %rbx, %rax .L4: popq %rbx ret int fact(int n) { if (n == 0) return 1; else return n * fact(n - 1); }
Why have a standard UMASM function linkage? • Functions are compiled separately and linked together • We need to standardize enough that function calls can be freely mixed • It’s tricky, so doing it the same way every time minimizes bugs and makes code clearer • We may “cheat” when caller and callee are in the same source file
What do we need to know about calling conventions? • Which registers hold parameters? [none — use the stack] • Which registers hold results? [r1] • Which registers are preserved across calls? [r0, r2, r3, r4] • Which register is the stack pointer? [r2] • When we call a procedure, where is the return address? [in r1] • What is the layout of the stack frame? [r2 points to arguments] REMEMBER: all of this assumes that a stack with sufficient spacehas been created in segment 0, and that r2 is set as the sp.
Startup code with stack (REVIEW) .section init .temps r6, r7 .section stk .space 100000 endstack: .section init .zero r0 start: r0 := 0 r2 := endstack gotomain linking r1 halt • Goals of startup code: • Set up useful registers • Set up a stack • Let UMASM know about register assignments • Invoke or drop through to user code init (instructions) stk10000 words Richer version with stack start endstack(r2)
Summarizing calling conventions • r0 is always 0 (points to segment 0, used for goto and for the call stack) • r1 is the return-address register and the result register • r2 is the stack pointer and must have same value after return (nonvolatile) • r3, r4 are nonvolatiles • r5 is a helpful volatile register (good to copy return address) • r6, r7 are volatile and are normally .temps (but up to each procedure) • Arguments are on the stack, with first argument at the “bottom” (where r2 points) EXCEPT FOR VOLATILES AND RESULT REGISTER CALLEE MUST LEAVE REGISTERS AND STACK UNCHANGED!
Calling a function with no arguments r1 := return_addr // Offset in seg 0 r7 := somefunc // Offset in seg 0 goto*r7 in program m[r0] // Call the function return_addr: // Function returns here …back in caller… call somefunc();
Calling a function with no arguments r1:= return_addr // Offset in seg 0 r7 := somefunc// Offset in seg 0 goto*r7 in program m[r0] // Call the function return_addr: // Function returns here …back in caller… Use of r1 for return address is required call somefunc();
Calling a function with no arguments r1 := return_addr // Offset in seg 0 r7 := somefunc// Offset in seg 0 goto*r7 in program m[r0] // Call the function return_addr: // Function returns here …back in caller… call somefunc(); Use of r7 for function address is programmer choice
Calling a function with no arguments r1 := return_addr // Offset in seg 0 r7 := somefunc// Offset in seg 0 goto*r7 in program m[r0] // Call the function return_addr: // Function returns here …back in caller… call somefunc(); gotosomefunclinking r1 // goto main, setting r1 to the return address UMASM provides this built-in macro to do the above for you!
Calling a function with no arguments r1 := return_addr // Offset in seg 0 r7 := somefunc// Offset in seg 0 goto*r7 in program m[r0] // Call the function return_addr: // Function returns here …back in caller… call somefunc(); gotosomefunc linking r1 // goto main, setting r1 to the return address UMASM provides this built-in macro to do the above for you! Also remember: r1 is both the linking and the result register. If somefunc calls another function, then r1 must be saved.
We can implement a common version of UMASM startup What’s common to all UMASM program startup? Let’s write it just once and reuse it! Set up stack Set r0 := 0
urt0: A reuseable UMASM startup routine .zero r0 .temps r7 .section stk .space 100000 endstack: .section init _ustart: r0 := 0 r2 := endstack goto main linking r1 halt // Finis urt0.ums
urt0: A reuseable UMASM startup routine Establish r0 as always containing 0 .zero r0 .temps r7 .section stk .space 100000 endstack: .section init _ustart: r0 := 0 r2 := endstack goto main linking r1 halt // Finis
Build stack and set up r2 as stack pointer urt0: A reuseable UMASM startup routine .zero r0 .temps r7 .section stk .space 100000 endstack: .section init _ustart: r0 := 0 r2 := endstack goto main linking r1 halt // Finis
Startup code with stack (REVIEW) .section init .temps r6, r7 .section stk .space 100000 endstack: .section init .zero r0 start: r0 := 0 r2 := endstack gotomain linking r1 halt • Goals of startup code: • Set up useful registers • Set up a stack • Let UMASM know about register assignments • Invoke or drop through to user code init (instructions) stk10000 words Richer version with stack start endstack(r2) WE’VE NOW MADE A COMMON VERSION OF THIS IN urt0.ums!!
Pushing and popping (Review) Expands to: • Push macro • Pop macro .zero r0 .temps r6, r7 push r3 on stack r2 r6 := -1 // macro instruction (not shown)r2 := r2 + r6 m[r0][r2] := r3 Expands to: r3 := m[r0][r2] r6 := 1 r2 := r2 + r6 pop r3 off stack r2 These instructions can be use to save and restore registers andto put function arguments on the stack!
Our program can go in the “main” function .zero r0 .temps r6,r7 main: push r1 on stack r2 // Address main returns to # Can skip one or both of the next two if registers not needed push r3 on stack r2 // Callee saves non-volatile reg push r4 on stack r2 // Callee saves non-volatile reg ...DO REAL WORK HERE... output "Hello world\n" pop r4 off stack r2 // restore caller's r4 pop r3 off stack r2 // restore caller's r3 pop r5 off stack r2 // get return address r1 := 0 // EXIT_SUCCESS is the result goto r5
Remember…we’re assuming urt0 has set up stack and r0 Now our program can go in the “main” function .zero r0 .temps r6,r7 main: push r1 on stack r2 // Address main returns to # Can skip one or both of the next two if registers not needed push r3 on stack r2 // Callee saves non-volatile reg push r4 on stack r2 // Callee saves non-volatile reg ...DO REAL WORK HERE... output "Hello world\n" pop r4 off stack r2 // restore caller's r4 pop r3 off stack r2 // restore caller's r3 pop r5 off stack r2 // get return address r1 := 0 // EXIT_SUCCESS is the result goto r5
Now our program can go in the “main” function Here’s where our useful work will go. .zero r0 .temps r6,r7 main: push r1 on stack r2 // Address main returns to # Can skip one or both of the next two if registers not needed push r3 on stack r2 // Callee saves non-volatile reg push r4 on stack r2 // Callee saves non-volatile reg ...DO REAL WORK HERE... output "Hello world\n" pop r4 off stack r2 // restore caller's r4 pop r3 off stack r2 // restore caller's r3 pop r5 off stack r2 // get return address r1 := 0 // EXIT_SUCCESS is the result goto r5
Return address saved from r1 Now our program can go in the “main” function .zero r0 .temps r6,r7 main: push r1 on stack r2 // Address main returns to # Can skip one or both of the next two if registers not needed push r3 on stack r2 // Callee saves non-volatile reg push r4 on stack r2 // Callee saves non-volatile reg ...DO REAL WORK HERE... output "Hello world\n" pop r4 off stack r2 // restore caller's r4 pop r3 off stack r2 // restore caller's r3 pop r5 off stack r2 // get return address r1 := 0 // EXIT_SUCCESS is the result goto r5 ..but popped into R5
Only save non-volatiles if necessary Now our program can go in the “main” function .zero r0 .temps r6,r7 main: push r1 on stack r2 // Address main returns to # Can skip one or both of the next two if registers not needed push r3 on stack r2 // Callee saves non-volatile reg push r4 on stack r2 // Callee saves non-volatile reg ...DO REAL WORK HERE... output "Hello world\n" pop r4 off stack r2 // restore caller's r4 pop r3 off stack r2 // restore caller's r3 pop r5 off stack r2 // get return address r1 := 0 // EXIT_SUCCESS is the result goto r5
Now our program can go in the “main” function Remember to combine all the pieces when you build .zero r0 .temps r6,r7 main: push r1 on stack r2 // Address main returns to # Can skip one or both of the next two if registers not needed push r3 on stack r2 // Callee saves non-volatile reg push r4 on stack r2 // Callee saves non-volatile reg ...DO REAL WORK HERE... output "Hello world\n" pop r4 off stack r2 // restore caller's r4 pop r3 off stack r2 // restore caller's r3 pop r5 off stack r2 // get return address r1 := 0 // EXIT_SUCCESS is the result goto r5 umasm urt0.ums hello.ums ...more ums here if needed... > hello
UMASM function arguments (Review) At function entry • At entry to a function, the stack must look like thism[r0][r2 + 0] // first parameter m[r0][r2 + 1] // second parameterm[r0][r2 + 2] // third parameter • The called function may push more data on the stack which will change r2… • …when this happens the parameters will appear to be at larger offsets from the (modified) r2! Third parm Second parm First parm r2 ????
Can we build UMASM code for this? int fact(int n) { if (n == 0) return 1; else return n * fact(n - 1); } REMEMBER THE CONTRACT BETWEEN CALLER AND CALLEE!
Summarizing calling conventions • r0 is always 0 (points to segment 0, used for goto and for the call stack) • r1 is the return-address register and the result register • r2 is the stack pointer and must have same value after return (nonvolatile) • r3, r4 are nonvolatiles • r5 is a helpful volatile register (good to copy return address) • r6, r7 are volatile and are normally .temps (but up to each procedure) • Arguments are on the stack, with first argument at the “bottom” (where r2 points) EXCEPT FOR VOLATILES AND RESULT REGISTER CALLEE MUST LEAVE REGISTERS AND STACK UNCHANGED!
Factorial .section init .zero r0 .temp r6,r7 fact: push r1 on stack r2 // save return address push r3 on stack r2 // save nonvolatile register r3 := m[r0][r2 + 2] // first parameter 'n' // Handle base case: if n == 0 if (r3 == 0) gotobase_case // recursive call r5 := r3 - 1 // OK to step on volatile register push r5 on stack r2 // now (n - 1) is a paraemter goto fact linking r1 // make the recursive call pop stack r2 // pop parameter r1 := r3 * r1 // n * fact(n-1) gotofinish_fact base_case: r1 := 1 finish_fact: // What are invariants here? pop r3 off stack r2 // restore saved register pop r5 off stack r2 // put return address in r5 goto r5
Factorial .section init .zero r0 .temp r6,r7 fact: push r1 on stack r2 // save return address push r3 on stack r2 // save nonvolatile register r3 := m[r0][r2 + 2] // first parameter 'n' // Handle base case: if n == 0 if (r3 == 0) gotobase_case // recursive call r5 := r3 - 1 // OK to step on volatile register push r5 on stack r2 // now (n - 1) is a paraemter goto fact linking r1 // make the recursive call pop stack r2 // pop parameter r1 := r3 * r1 // n * fact(n-1) gotofinish_fact base_case: r1 := 1 finish_fact: // What are invariants here? pop r3 off stack r2 // restore saved register pop r5 off stack r2 // put return address in r5 goto r5 What are the invariants here?
Main program to call factorial void main(void) { for (inti = 10; i != 0; i--) { print_d(i); puts("! == "); print_d(fact(i)); putchar('\n'); } return EXIT_SUCCESS; }
Main program to call factorial .zero r0 .temps r6,r7 main: push r1 on stack r2 // Address main returns to push r3 on stack r2 // Callee saves non-volatile reg r3 := 10 // Using r3 for i - loop var gotomain_test main_loop: push r3 on stack r2 // Pass i as first arg to function gotoprint_d linking r1 // Call print_d pop stack r2 // Function leaves arg on stack output "! == " // puts() is inlined push r3 on stack r2 // Pass i as first arg to function goto fact linking r1 // Call fact push r1 on stack r2 // Pass fact's return value as arg gotoprint_d linking r1 // Call print_d r2 := r2 + 2 // pop 2 parameters, 1 for each call output '\n' // putc() inlined --- it's a C macro r3 := r3 - 1 // i-- main_test: if (r3 != 0) gotomain_loop // while (i != 0) pop r3 off stack r2 // restore caller's r3 pop r5 off stack r2 // get return address r1 := 0 // EXIT_SUCCESS is the result goto r5 // return