COP 3402 meeting -*- Outline -*- * Stacks to support subroutines ** VM design for subroutines ------------------------------------------ VM DESIGN FOR SUBROUTINES For each call: - storage for a subroutine's variables (local storage) organized as - storage for a single call is called an AR: def: An *activation record* (AR) ------------------------------------------ ... a stack (called the runtime stack) Q: Why use a stack? Because if client calls subroutine P, then P must return before client can call another subroutine thus last-called is first to finish (LIFO) ... *activation record* (AR) is a portion of the runtime stack that holds local storage for one call of a subroutine ** runtime stack design ------------------------------------------ RUNTIME STACK ORGANIZATION In the code: P calls Q, Q calls R # code in PL/0 procedure P; call Q; procedure Q; call R; end; end; end. Initially: [ 0 ] Call of subroutine P: [ AR for P ] After P calls Q: [ AR for P ] [ AR for Q ] After Q calls R: [ AR for P ] [ AR for Q ] [ AR for R ] After R returns: [ AR for P ] [ AR for Q ] After Q returns: [ AR for P ] After P returns: ------------------------------------------ Point out starts and finishes of ARs *** design decisions: how to store the stack in memory Q: Since a computer's memory is like a big (1D) array, how should we implement the runtime stack? tracking indexes of each AR in the array We could have the stacks group up (pushes to larger indexes) or down (pushes to smaller indexes) We'll follow Unix in growing stacks downwards... ------------------------------------------ STACK IMPLEMENTATION AR delimited by two indexes: - fp: - sp: Notes, assuming stack is word-addressed, and grows towards lower addresses (downwards) ------------------------------------------ frame pointer, byte address of the first element in AR stack pointer, byte address of the top (last allocated) element in AR Notes: We are assuming that the stack grows downwards, towards lower addresses and that the stack is word addressed There are other choices that could be made, but on Unix stacks grow downward and on many machines (x86, MIPS) memory is byte-addressed On some machines, ARs must be aligned, e.g., on MIPS ARs must start on a double-word boundary ... $sp <= $fp ... AR is stored in memory from $sp to $fp (inclusive) Q: Does the storage for a subroutine vary dynamically? Yes, supports block structure in programming languages ** stacks in C (In this directory, see stack.h and stack.c) ------------------------------------------ STACK HEADER FILE #ifndef STACK_H #define STACK_H #include #include "machine_types.h" // size of the stack in words #define MAX_STACK_HEIGHT 4096 // Initialize the stack data structure // This should be called first extern void stack_initialize(); // the stack's invariant extern void stack_okay(); // Return the stack's total size extern uword_type stack_size(); // Return the current AR's size extern uword_type stack_AR_size(); // Return the address of the base // of the current AR (FP value) extern address_type stack_AR_base(); // Return the address of the top word // element in the current AR // (i.e., return the value of SP) extern address_type stack_top_AR_address(); // Is the stack empty? extern bool stack_empty(); // Is the stack full? extern bool stack_full(); // Requires: there is space for the given // number of words // Allocate the given number of words extern void stack_allocate(unsigned int words); // Requires: !stack_full() // Push the word val onto the stack extern void stack_push(word_type val); // Requires: stack_size() >= words // Decrease the size of the stack by // the given number of words extern void stack_deallocate(unsigned int words); // Requires: !stack_empty() // pop the stack and return the top word. extern word_type stack_pop(); // Requires: !stack_empty() // Return the top word's value // without popping extern word_type stack_peek(); // Requires: 1 <= words // Requires: there is space for allocating // the given number of words // Start a new AR, allocating // the given number of words, // putting (the old) FP // in the first allocated word, // and setting FP to be the old SP-1. extern void start_AR(unsigned int words); // Requires: sp+words <= memory[fp] // the given number of words // Finish the AR, by setting FP to // memory[FP], and deallocating // the given number of words. extern void finish_AR(unsigned int words); #endif ------------------------------------------ ------------------------------------------ STACK CODE IN C #include #include #include #include "utilities.h" #include "stack.h" // the stack's storage word_type memory[MAX_STACK_HEIGHT]; // stacks will grow downwards #define ORIGINAL_FP (MAX_STACK_HEIGHT - 1) // first index of current AR static int fp; // index of top element of current AR static int sp; // Initialize the stack data structure void stack_initialize() { } // the stack's invariant // this is for stacks that grow downwards! void stack_okay() { } // Return the stack's total size uword_type stack_size() { } // Return the current AR's num. uword_type stack_AR_size() { } // Return the address of the base // of the current AR (FP value) address_type stack_AR_base() { return fp; } // Return the address of the top word // element in the current AR // (i.e., return the value of SP) address_type stack_top_AR_address() { return sp; } // Is the stack empty? bool stack_empty() { return stack_size() <= 0; } // Is the stack full? bool stack_full() { return stack_size() >= MAX_STACK_HEIGHT; } // Requires: there is space for the given // number of words // Allocate the given number of words void stack_allocate(unsigned int words) { } // Requires: !stack_full() // Push the word val onto the stack void stack_push_word(word_type val) { } // Requires: stack_size() >= words // Decrease the size of the stack by // the given number of words void stack_deallocate(unsigned int words) { assert((sp+words) <= fp); sp += fp; stack_okay(); } // Requires: !stack_empty() // pop the stack and return the top word. word_type stack_pop() { } // Requires: !stack_empty() // Return the top word's value // without popping word_type stack_peek() { stack_okay(); return memory[sp]; } // Requires: 1 <= words // Requires: there is space for allocating // the given number of words // Start a new AR, allocating // the given number of words, // putting (the old) FP // in the first allocated word, // and setting FP to be the old SP-1. void start_AR(unsigned int words) { } // Requires: sp+words <= memory[fp] // the given number of words // Finish the AR, by setting FP to // memory[FP], and deallocating // the given number of words. void finish_AR(unsigned int words) { } ------------------------------------------ ... void stack_initialize() { fp = ORIGINAL_FP; sp = fp; stack_okay(); for (int i = ORIGINAL_FP; 0 <= i; i--) { memory[i] = 0; } stack_okay(); } void stack_okay() { assert(0 <= sp); assert(sp <= fp); assert(fp <= ORIGINAL_FP); } uword_type stack_size() { // check that sp is less than ORIGINAL_FP stack_okay(); return ORIGINAL_FP - sp; } address_type stack_AR_base() { return fp; } void stack_allocate(unsigned int words) { assert(0 <= (sp-words)); sp -= words; stack_okay(); } void stack_push_word(word_type val) { assert(0 < sp); sp--; memory[sp] = val; stack_okay(); } word_type stack_pop() { stack_okay(); assert((sp+1) <= fp); return memory[sp++]; } void start_AR(unsigned int words) { assert(1 <= words); assert(0 <= (sp-words)); stack_okay(); address_type old_sp = sp; sp -= words; // allocate (update sp) memory[old_sp-1] = fp; // save old fp fp = old_sp - 1; // set new fp stack_okay(); } void finish_AR(unsigned int words) { } ** stacks in SSM assembler ------------------------------------------ STACKS AND SUBROUTINES IN THE VM Calling convention: - push arguments on the stack - return result on top of stack ------------------------------------------ Q: Does it make sense to have a subroutine to do a push? No, because to call it you would need to push the argument! ------------------------------------------ STACK IMPLEMENTATION IN SSM ASSEMBLER # The stack is word addressed # The stack grows down, # towards lower addresses # The virtual machine puts address, # from BOF, say STKB, in $sp and $fp. # Assume that STKB is greater than # value of $gp. addrs memory GPRs 32768 [---------------] | ... | { [ old FP ] <- FP AR{ | ... | { [ ] <- SP |vvvvvvvvvvvvvvv| | ... ] | | |---------------| global | | data [ ] <- GP | ... | | | instrs [ ] <----- PC | | 0 [---------------] ------------------------------------------ Assembly code that follows is in stack_demo.asm ------------------------------------------ INITIALIZATION FOR THE DEMO # file stack_demo.asm .text main # start at "main" # put initial stack size # in memory[$gp] stack_initialize: RTN # ... .data 1024 WORD InitFP # ... more data here ... .stack 4096 .end ------------------------------------------ ... # put initial stack size # in memory[$gp] stack_initialize: SWR $gp, 0, $fp RTN ------------------------------------------ INVARIANT CHECK stack_okay: NOP # check that 0 <= $sp # check that $sp <= $fp, i.e., 0 <= $fp - $sp # check that $fp <= $InitFP, i.e., $fp - InitFP <= 0 # deallocate words from the stack RTN # invariant passed failSP: PSTR $gp, 1 EXIT 1 failSPFP: PSTR $gp, 8 EXIT 1 failFP: PSTR $gp, 14 EXIT 1 # ... more code could go here ... .data 1024 WORD InitFP STRING[7] GPError = "Error: GP must be..." STRING[7] SPError = "Error: SP must be..." STRING[7] FPError = "Error: FP must be..." STRING[2] Wrong = "Wrong!\n" STRING[3] Passed = "Passed!\n" ------------------------------------------ (Explain what each instruction does) ... stack_okay: SRI $sp, 4 # allocate 4 words # check that 0 <= $sp, SWR $sp, 1, $sp BGEZ $sp, 1, +2 JMPA failSP # check that $sp <= $fp, i.e., 0 <= $fp - $sp SWR $sp, 0, $fp # put FP in top of stack # $sp is in $sp,1 already from above SUB $sp, 1, $sp, 1 BGEZ $sp, 1, +2 JMPA failSPFP # check that $fp <= InitFP, i.e., $fp - InitFP <= 0 # FP is already in top of stack CPW $sp, 1, $gp, 0 SUB $sp, 2, $sp, 1 BLEZ $sp, 2, +2 JMPA failFP ARI $sp, 4 # deallocate words from stack RTN # invariant passed ------------------------------------------ MORE STACK FUNCTIONS IN ASSEMBLER # allocate a word stack_allocate: RTN # deallocate a word stack_deallocate: RTN # return size of stack stack_size: RTN # return size of current AR stack_AR_size: RTN # return current AR base stack_AR_base: RTN # return address in $sp stack_top_AR_addr: RTN # is the stack full? stack_full: RTN ------------------------------------------ ... # allocate a word stack_allocate: SRI $sp, 1 RTN # deallocate a word stack_deallocate: ARI $sp, 1 RTN # return size of stack stack_size: SRI $sp, 3 CPW $sp, 0, $gp, 0 SWR $sp, 1, $sp SUB $sp, 2, $sp, 1 ADDI $sp, 2, 2 # i.e., 3-1 ARI $sp, 2 # leave size on top RTN # return size of current AR stack_AR_size: SRI $sp, 3 SWR $sp, 0, $fp SWR $sp, 1, $sp SUB $sp, 2, $sp, 1 ADDI $sp, 2, 2 # i.e., 3-1 ARI $sp, 2 RTN # return current AR base stack_AR_base: SRI $sp, 1 SWR $sp, 0, $fp RTN # return the address in $sp stack_top_AR_addr: SRI $sp, 1 SWR $sp, 0, $sp RTN # is the stack full? stack_full: CALL stack_top_AR_addr SRI $sp, 1 BLEZ $sp, 0, +4 LIT $sp, 0, 0 # not full JREL +2 LIT $sp, 0, 1 # full RTN *** running it Examine the stack_demo.asm file ------------------------------------------ TESTING IT $ make stack_demo.bof $ make stack_demo.myp $ make stack_demo.myo ------------------------------------------ The .myp file has the listing of the code, from (the Unix command): ./vm -p stack_demo.bof For the .myp file: Q: What's all the data locations 1024 onward? It's the initial values of the .data section Q: Where is the runtime stack and the PC? Those are not shown in the .myp file The .myo file has the trace of the execution, from: ./vm stack_demo.bof Q: Why is the PC initially 21? That's where main starts, it's what was specified in the assembly code's .text directive Q: What is a GPR? A "general purpose register" Q: What is the initial value of GP? 1024, as specified in the assembly code's .data directive Q: What is the initial value of SP and FP? 4096, as specified in the assembly code's .stack directive Q: How does the VM get the initial values of PC, GP, SP, and FP? from the header of the BOF Trace the execution of some instructions