CSC 3210 Computer Organization and Programming Chapter 5 THE STACK D.M. Rasanjalee Himali
Outline Memory The Stack Addressing Stack Variables Defining Stack Variable Offsets One-Dimensional Arrays
Memory The SPARC architecture specifies a 32-bit address providing 2 32 bytes in memory Variables may be stored in memory occupying one, two, four, or eight bytes of memory. These memory data types are : Byte (occupy 1 byte of memory), Halfword (occupy 2 bytes of memory), Word (occupy 4 bytes of memory) Doubleword (occupy 8 bytes of memory)
Memory These data types correspond to the following C data types and have the following ranges: All memory references must be aligned. n-byte quantities may only be addressed in memory with addresses that are divisible by n.
Stack A program is loaded into low memory The first executable instruction is located above 0x2000 Subsequent instructions occupy higher memory addresses. O/S provide additional memory for automatic variables near the top of memory. This space is called Stack Stack used in LIFO manner Low memory Top memory program
Stack Functions may use the stack to allocate space for automatic variables (in LIFO order). The address of the last occupied stack memory element is always kept in register %o6. This register is also know to the assembler as %sp, the stack pointer.
Stack This stack, and the stack we discussed in Chapter 1, are entirely different. We do not push items onto this stack, nor do we pop items off the stack; nor does the machine perform arithmetic between the top two elements of the stack. The only reason that the space at the top of memory is called the stack is because it provides a first-in last-out data structure.
Stack Stack (located on top memory) always grows downward. The stack pointer %sp marks the top of the stack (addr. Of last occupied stack memory element) To obtain additional memory, subtract the number of bytes of additional storage needed from the stack pointer %sp. Ex: to obtain an additional 64 bytes of memory:
Stack The stack is always kept doubleword aligned To ensure that the stack is doubleword aligned, the address in the stack pointer, %sp, must be evenly divisible by 8. Ex: If we want 94 bytes of stack memory space, we must ask for 96 to keep the stack aligned. How can we make a number divisible by 8 easily?
Stack Clear some of the low-order bits of a two’s complement binary number to make it evenly divisible by a power of 2. Ex: clear the low three bits, the number will be evenly divisible by 8. The resulting chopped two’s complement number is the next largest number evenly divisible by 8. We can find -96 !.How can we use this in sub %sp, 94, %sp ?
Stack Instead of subtracting a positive number from the stack pointer we can add a chopped negative number. Ex: To clear the low-order three bits form the bitwise and of the number with : +94 = = & = -96 The constant 0xfffffff8 is the hexadecimal presentation of -8, so we might write: This would result in 96 being subtracted from the stack pointer
Stack The stack pointer %sp marks the top of the stack. Memory locations below where the stack pointer is pointing may be changed by the operating system and, indeed, frequently are. We should NEVER refer to an address negative with respect to the stack pointer.
The Frame Pointer The stack pointer frequently change during program execution It does not remain constant enough to reference automatic variables stored on the stack. Ex: if we had a word variable stored at %sp + 20 and subsequently changed the stack pointer to obtain more storage, the variable would no longer be located 20 bytes from the stack pointer. Register, %i6, known as the frame pointer, %fp solves this problem by storing a copy of the stack pointer before it is changed to provide more storage. The frame pointer %fp points to what was the top of the stack before %sp was changed
The Frame Pointer The save instruction both performs addition and updates %fp A save instruction is normally executed once at the beginning of a program to provide storage for all automatic variables. The save instruction is used to provide not only storage for automatic variables but also to provide space on the stack to save some of the registers. In chapter 7 we describe why registers must be saved. But for now, we need to provide 92 extra bytes of storage whenever executing a save instruction.
The Frame Pointer We must provide 92 extra bytes of storage whenever executing a save instruction + any storage needed for local variables: Ex: To store five four-byte variables, a0,a1,…,a4, on the stack instead of in the registers, we first need to make room at the beginning of our program by: This instruction makes room for five four-byte variables together with 92 bytes in which to save registers if necessary.
The Frame Pointer The first variable, a0, will be at the memory addressed by the contents of %fp - 4, that is, four bytes above the old top of the stack. The second variable,a1, will be at %fp – 8 The third at %fp -12, and so on 92
Addressing Stack Variables The only instructions that reference memory load instruction: load data into registers store instruction store data back into memory Load/store handle one (byte)-, two (halfword)-, four(word)-, and eight (doubleword)-byte quantities n-byte quantities may be loaded from/stored into memory addresses evenly divisible by n.
Load Instructions Load Instruction Format: Ex: [ ] indicate that : the first operand is being used as a pointer, and it is the contents of the memory location addressed that is to be loaded into the register. ld,
Store Instructions Store Instruction Format: Ex: to store first variable %l1 back into a0: st,
Defining Stack Variable Offsets Problems with Stack Variable Offsets: The constants in the operand to the ld instruction: Ex: Solution: define constants symbolically, using m4: Ex: Then we can write: This is much more readable !... But still has to compute the offset
Defining Stack Variable Offsets We could define macros to compute the offsets as well as to make the definitions: Macro local_var: Defines last_sym to be zero Macro var: Has two arguments: 1. variable name 2. size of variable in bytes var macro first computes the stack offset, the evaluation of last_sym - $2, and assigns this to last_sym Macro then prints the assignment $1 = last_sym An example of the use of these macros:
Defining Stack Variable Offsets Another Problems with Stack Variable Offsets: To assign variables on the stack corresponding to the following C variables: If, however, we then tried to access these variables c_s and d_s by we would get a nonaligned memory error and our program would stop executing.: the address %fp - 11 is not divisible by 2, which it must be for halfword access; nor is %fp -15 divisible by 4 To correct this situation, we need to make sure that each variable is aligned. C code : Macro: Resulting code
Aligning Variables We do this by performing the bitwise and of the offset with the negative of the alignment. our program now expands into: with the variables on the stack correctly aligned. There is now a wasted byte after the ch variable to align the stack variables
An Example We are now in a position to write quite sophisticated programs. Consider the following C code:
An Example How to Calculate Offsets First you should identify your memory variables and register variables: memorory variables: a,b,c1,c,d register variables: x,y, z Second, find offsets of memory variables manually. This offset calculation should take into account the data type size of the variable and the alignment with its own data type size. For a(4byte), b(4byte), c1(1byte), c(4byte), d(4byte)you need to find negative offsets from %fp: Unaligned Offset = previous ALIGNED offset-mem.var. data type size Aligned Offset = unaligned offset & -mem.var.data type size Memory Variable Size of Variable Unaligned OffsetAligned Offset a_s40-4 = -4-4&-4 = -4 b_s4-4-4 = -8-4&-4 = -8 c1_s1-8-1=-9-9&-1= -9 c_s4-9-4=-13-13&-4= -16 d_s4-16-4=-20-20&-4= -20
An example How to define macros for variables Third, define macros for register variables(registers) and memory variables(offsets). Always use the suffix _r for register variables and suffix _s for automatic/memory/stack variables !for memory variables define(a_s, -4) define(b_s, -8) define(c1_s, -9) define(c_s, -16) define(d_s, -20) !for register variables: define(x_r, l0) define(y_r, l1) define(z_r, l2)
An Example How to use memory variables in your program: Fouth, write body of your program. Make sure any data in memory (i.e. memory variables) required for a computation is loaded to a register before computation and any changes to memory variables are stored back to memory after computation. Also make proper use of memory addresses in load and store commands.Since you have defined negative offsets, in the load instriction they are defined as follows(Note the + sign): ld [%fp + a_s], %o0 !load a_s to register o0 Similarly for storing: st %o2,[%fp + a_s] !store value in o2 to a_s
An Example Code: Use this style of programming in your assignments, exams etc. (manually computed offsets, no Predefined macros used for variables)
An Example To translate this program into assembly language, we first assign variables, to the stack and to registers making use of macros: We might define two more macros for the program entry and exit:
An Example After defining the stack offsets for automatic variables we need the program entry, macro begin_main, followed by the variable initialization statements: In the case of the for loops, we branch to a test at the end of the loop, filling the delay slots with the initialization statements of the fors, before writing the loop code:
C program equivalent:
An Example We may fill the delay slots following the inner loop test by moving the first instruction of the inner loop into the delay slot and annulling the bge instruction. We may also fill the delay slot of the outer loop by branching directly to the inner test and filling the delay slot with the initialization test of the inner loop. We must also annul the bl instruction:
Ifelse macro Ifelse is a built-in m4 macro that evaluates all its arguments, and then, If the first string argument = second string argument the value of the ifelse is the third string argument. Else the value of the ifelse is the fourth argument. Ex: following results in the string “d,” as the string “a” is not the same as the string “b.”
One-Dimensional Arrays A one-dimensional array (vector), is a block of memory into which a number of variables, all of the same type, may be stored. The array address is the address of the first element of the array in memory and is a pointer to the array. The ith array element may be accessed in memory at: Ex: 5 element integer array in c: To access 1 st array element ary[1]: Address of ary[1] = ary + 1*4 = ary+4 Similarly for other elements: Address of ary[2] = ary + 2*4 =ary+8 Address of ary[3] = ary + 3*4 =ary+12 etc. Note that elements of higher indices in array occupy higher memory addresses Memory representation:
Declaring Arrays What are the offsets of following memory variables and how does the stack look like after memory assignment?
Declaring Arrays Ex: Memory Variable Size of Variable Unaligne d Offset Aligned Offset a_s40-4 = -4-4&-4 = -4 c1_s1-4-1 = -5-5&-1 = -5 ary_s4*5 = =-25-25&-4= -28 c2_s1-28-1=-29-29&-1= -29 d_s4-29-4=-33-33&-4= -36
Declaring Arrays To provide space for such an array on the stack, we need to modify our stack offset macros slightly. This provide an optional third argument to var, which if present specifies the total number of bytes of storage with the alignment still specified by the second argument: This allow us to specify a variable of a number of bytes different from its alignment. This checks to see if a 3 rd argument is present If not, then 2 nd argument subtracted from last_sym is 2 nd If so, then 3 rd argument subtracted
Declaring Arrays Ex: to declare the following automatic variables : we would write: which would result in the following expansion: The array begins at %fp-28 allowing successive array elements to be accessed at higher memory locations. Ex: ary[2] is located at %fp
Declaring Arrays How do we access array elements in an Assembly program? We need an additional variable for array index Usually a register variable (ex: %i_r ). Assume we need to access array element ary[3]. The memory address of ary[3] = ? The memory address of ary[3] = %fp+ary+3*4 What is the general equation? The memory address of ary[i] = %fp + ary + (i*element_size) Three additions: First calculate i*element size Second add ary to %fp Third add [ary+%fp] to claculated i*element_size How do we perform the multiplication (i*element_size) easily? Remember sll by 2 = *4 Therefore sll %i_r by 2 will perform i*element_size since element_size=4 here.
Declaring Arrays To load ary[i] into %o0, if i were stored in register %i_r, would be
Using Arrays Consider the following program to find the maximum element in the array nums:
Using Arrays Ex: Memory Variables: Register Variables: %i_r %max_r Memory Variable Size of Variable Unaligned Offset Aligned Offset nums_s4*100= = &-4 = -400 n_s = &-4 = -404
Using Arrays Its translation into assembly language is as follows:
Using Arrays The program’s expansion into assembly language is as follows:
Declaring and Initializing Arrays In this class, do not use any macros defined by the textbook to write programs. If array values for all indices/subscripts are already known, or can be computed, use a for loop to calculate memory address offset of array element and store value. Ex: store value 1 in all elements in array mov %i_r, 0!initialize index i=0 for: sll %i_r,2, %o0 !i*4 assuming element size 4 add %fp, %o0, %o0 mov 1, %o2 st %o2, [%o0+ary_s] !ary[0]=1 inc %i_r !i++ for_test: cmp %i_r, array_legth bl for If only some array values are known, sequentially calculate and store values in given subscripts. Ex: short ary[5] = {10,20} mov 10, %o0 st %o0, [%fp+ary+0] mov 20, %o0 st %o0, [%fp+ary+2]
Initializing Arrays Stack variables have to be initialized by loading the constant into a register and then storing it on the stack. A macro, initialize was written, to do this instead of writing a number of mov and St instructions. The arguments to initialize are the stack offset in bytes from the beginning of the array on the stack, followed by up to eight initializers. The macro generates a mov and st instruction to initialize the first array element and then uses an ifelse to check if there is an additional array element to initialize. If there is, it calls initialize again, with the first argument incremented by four and all the remaining arguments one place to the left (remember that missing arguments are replaced by null strings).