Presentation is loading. Please wait.

Presentation is loading. Please wait.

Chapter 5 Microprocessor Runtime Stack

Similar presentations


Presentation on theme: "Chapter 5 Microprocessor Runtime Stack"— Presentation transcript:

1 Chapter 5 Microprocessor Runtime Stack
ECE 485/585 Microprocessors Chapter 5 Microprocessor Runtime Stack & Recursive Functions Herbert G. Mayer, PSU Status 10/18/2016

2 Syllabus Definition of Recursive Algorithm Recursion vs. Iteration
Q-Sequence Ackermann Function Stack Data Structure for EE Simulate Recursion via Iteration References

3 Definition of Recursive Algorithm
An algorithm is recursive, if it is partly defined by simpler versions of itself [1] Computers have no notion of recursion, yet can use data structures, and combined with instructions, do implement recursive behaviour, i.e. execute recursive algorithms A recursive program is a direct implementation of a recursive algorithm What is the key problem for a programmer, using a language that is non-recursive (e.g. standard Fortran) if the algorithm to be implemented is recursive? Programmer has to use the same techniques a typical microprocessor (or any other processor) uses!

4 Definition of Recursive Algorithm
What are the other parts of a recursive algorithm, the ones that are not simpler versions of itself? Recursive algorithm requires starting point, formally: “base case” Base case could be multiple steps Recursive algorithm a() uses base case as a starting point for the recursive computation, for initial decisions, plus recursive use of a() with simpler arguments! NOT with the same arguments! Body can be directly or indirectly recursive through intermediate function; e.g. a( n )-> b( n )-> a( n-1 ) –via intermediate func b() Simple examples are Factorial or Fibonacci functions for non- negative arguments n; here Fibo( n ) for the Fibonacci sequence: Base case 1: Fibo( 0 ) = 0 Base case 2: Fibo( 1 ) = 1 Definition n>1: Fibo( n ) = Fibo( n-1 ) + Fibo( n-2 )

5 Recursion vs. Iteration
Iteration is expressed in programming languages by loops; e.g. for-, while-, do-, or repeat loops These are readable and efficient methods for expressing iteration, but are not strictly necessary Recursion can replace iterative steps; yet for some people this seems counter-intuitive Neophytes are sometimes unused to recursion; with some practice, recursion is as intuitive as iteration  Read again the seemingly crazy idea: recursion can replace iteration! Admittedly, such recursively running software will not always execute quickly!  An understatement! But it does work!

6 Replace Iteration via Recursion
Even more crazy idea to abstain from using arithmetic operators, such as + - * / etc. for the sake of just exercising recursion! Again: efficiency i.e. runtime speed flies out the door! The trick is to replace such arithmetic operators by functions, and the using such functions recursively Plus a few exceptional arithmetic operators to auto- increment/decrement operators and unary (AKA monadic) minus – All relational operators > >= == != < allowed No other dyadic operators allowed in this experiment, i.e. no: + - * / % **

7 Recursion vs. Iteration: add()
// return a + b without + operation! // o.k. to use > < calls etc. int add( int a, int b ) { // add if ( 0 == b ) { // why 0 on l.h.s.? return a; }else if ( b < 0 ) { return add( --a, ++b ); }else{ return add( ++a, --b ); } //end if } //end add

8 Recursion vs. Iteration: sub()
// return a – b; no dyadic – operation // solely unary minus – and add() int sub( int a, int b ) { // sub return add( a, -b ); // unary - } //end sub

9 Recursion vs. Iteration: mult()
// return a * b, no * allowed, add() ok int mult( int a, int b ) { // mult if ( 0 == b ) { return 0; }else if ( 1 == b ) { return a; }else if ( b < 0 ) { return -mult( a, -b ); }else{ // b > 0 return add( a, mult( a, --b ) ); } //end if } //end mult

10 Recursion vs. Iteration: expo()
// return a ** b, no ** op in C++; requires mult( int, int ) int expo( int a, int b ) { // expo if ( 0 == a ) { if ( 0 == b ) { printf( ”undefined value0^0\n" ); }else if ( b < 0 ) { printf( “0 to - power is undefined\n" ); } //end if return 0; }else if ( 0 == b ) { return 1; }else if ( 1 == a ) { }else if ( -1 == a ) { return b % 2 ? -1 : 1; }else if ( b < 0 ) { // we deal with ints! }else{ return mult( expo( a, --b ), a ); } //end expo

11 Recursion vs. Iteration
We shall skip detail of simulating recursion via iteration in an EE-focused class

12 Q-Sequence, Definition
Q-Sequence defined by Douglas Hofstadter in [1] as a function q( n ) for positive integers n > 0 Base case n = 1: q(1) = 1 Base case n = 2: q(2) = 1 Recursive definition of q(n), for positive n > 2 q( n ) = q( n – q( n - 1 ) ) + q( n – q( n - 2 ) ) Q-Sequence reminds us of Fibonacci( n ) function, but with surprising difference in the type of result: By contract, the function results of fibonacci( n ) are monotonically increasing with increasing argument Results of q( n ) are non-monotonic! (Yes is Math!) Note: # of: calls( q( 40 ) ) = 1,137,454,741

13 Q-Sequence, Coded in C #define MAX 100 // arbitrary limit; never reached!!!! int calls; // will be initialized each time int q( int arg ) { // q calls++; // track another call if ( arg <= 2 ) { return 1; // base case }else{ // now recurse! return q( arg - q( arg-1 ) ) + q( arg - q( arg-2 ) ); } // end if } // end q int main() { // main for( int i = 1; i < MAX; i++ ) { calls = 0; // initially no calls yet printf( "Q(%2d) = %3d, #calls = %10d\n", i, q(i), calls ); } // end for return( 0 ); } // end main

14 Q-Sequence Results Q( 1) = 1, #calls = Q( 2) = 1, #calls = Q( 3) = 2, #calls = Q( 4) = 3, #calls = Q( 5) = 3, #calls = Q( 6) = 4, #calls = Q( 7) = 5, #calls = Q( 8) = 5, #calls = Q( 9) = 6, #calls = Q(10) = 6, #calls = Q(11) = 6, #calls = . . . Q(26) = 14, #calls = Q(27) = 16, #calls = Q(28) = 16, #calls = Q(29) = 16, #calls = Q(30) = 16, #calls = Q(31) = 20, #calls = Q(32) = 17, #calls = Q(33) = 17, #calls = Q(34) = 20, #calls = Q(35) = 21, #calls = Q(36) = 19, #calls = Q(37) = 20, #calls = Q(38) = 22, #calls = Q(39) = 21, #calls = Q(40) = 22, #calls = . . . Will never reach Q(100) in your life time 

15 Ackermann Definition Ackermann a( m, n ) is defined as a function of two non-negative integers m and n Base case 1: a( 0, n ) = n + 1 Base case 2: a( m, 0 ) = a( m - 1, 1 ) Recursive definition of for m > 0, n > 0: a( m, n ) = a( m - 1, a( m, n - 1 ) ) Ackermann a( m, n ) complexity grows awfully fast; e.g. a( 4,2 ) is an integer number with 19,729 decimal digits: Greater than national US debt! Even greater than number of molecules in the universe!

16 Ackermann Definition Students, code now in C, volunteer in class?! And show result of function design on white-board: Base case 1: a( 0, n ) = n + 1 Base case 2: a( m, 0 ) = a( m - 1, 1 ) Recursive definition of a( m, n ), m, n > 0 a( m, n ) = a( m - 1, a( m, n - 1 ) )

17 Ackermann Coded in C unsigned a( unsigned m, unsigned n ) { // a
calls++; // global unsigned if ( 0 == m ) { // note operand order return n + 1; // first base case }else if ( 0 == n ) { // m > 0 return a( m - 1, 1 ); // other base case }else{ // m > 0, n > 0 return a( m-1, a( m, n-1 ) ); // recurse! } // end if } // end q <- EE students, why no return before ending } ? int main() { // main for( int i = 0; i < MAX; i++ ) { printf( "\nFor m = %d\n", i ); for( int j = 0; j < MAX; j++ ) { calls = 0; printf( "a(%1d,%1d) = %10u, calls = %12u\n", i, j, a( i, j ), calls ); } // end for return 0; // <- EE students, no parens? } // end main

18 Ackermann Results For m = 0 a(0,0) = 1, calls = 1 . . .
a(1,7) = , calls = For m = 2 a(2,0) = , calls = a(2,1) = , calls = a(2,2) = , calls = a(2,3) = , calls = a(2,4) = , calls = a(2,5) = , calls = a(2,6) = , calls = a(2,7) = , calls = For m = 3 a(3,0) = , calls = a(3,1) = , calls = a(3,2) = , calls = a(3,3) = , calls = a(3,4) = , calls = a(3,5) = , calls = a(3,6) = , calls = a(3,7) = , calls = For m = 4 a(4,0) = , calls = don’t even dream about computing a( 4, 2 )  or higher!

19 Stack Data Structure for EE
High-level language functions call each other in a nested fashion, even recursively, even indirectly recursively And return in strictly the reverse order (LIFO), but with any number of further calls in between Stack is the natural data structure to track callée return information Languages also allow local data per function, of which formal parameters are just one variation Natural to have locals also live and die on the runtime stack, synchronized with call-return Possible but wasteful to have a separate stack for automatic locals; conclusion: have unified stack

20 Stack Data Structure for EE
Other temps 0 or more locals Field 1: Function return value Caller pushes formals 0..32 registers saved Field 2: return address Field 3: dynamic link Field 4: static link sp bp hp Stack Marker Stack Frame Stack Frame of callee Stack grows downward arbitrarily in EE course Stack grows downwards

21 Stack Data Structure for EE
Stack is natural runtime data structure for recursive call: Before call: push all actual parameters During the call, these will become formal parameters Then execute call Push return address on stack = current pc + 1 instruction Provide space on stack for return value if needed, e.g. for functions; not for procedures And push bp register, pointing to the frame of the caller: known as dynamic link Before executing callée’s code: allocate locals on stack And allocate temps, e.g. copies of all registers to be used, save them and later restore saved registers before returning

22 Stack Data Structure for EE
At call, stack grows by 3 logical sections, some could be empty: Formal parameters, some of them may be just addresses: For so-called reference parameters Stack Marker, AKA Activation Record: return address RA, return value RV, Dynamic Link = old bp value Locals + temps+ saved regs For statically nested functions –not covered here– we need Static Link, one more HW register; not taught at PSU ECE, as focus is C!

23 Stack Data Structure for EE
Stack is addressed by 2 HW resources, typically registers: bp and sp (AKA tos, short for top-of-stack register; we use: top) It is computable to have a single register address stack via current frame, since the compiler knows at each place, by how much stack must have grown; very complex! Actually so done by Greenhills compilers in 1990s for register- starved Intel x86 architecture Base Pointer register bp points to defined, fixed place of stack marker, typically to dynamic link –static during call Top of stack register sp points to the dynamically changing, momentary top of stack –dynamic during call The bp register stays invariant during the call; changes only at further calls and returns –static, during the call The sp register changes with each call preparation, each temp pushed on top, each intermediate result, etc.

24 Simulate Recursion via Iteration
Understand stack, recursion, call, return, parameters! What to do, if you implement a recursive algorithm using a language that does not support recursion? a.) Replace recursive by a non-recursive algorithm! b.) Or simulate recursion via non-recursive methods μP chip has no notion of recursion; is a sequential machine simulating recursion via non-recursive methods Compiler plus runtime system perform the necessary transformation! Done so at local industry in the past: FPS used Fortran  to implement System SW and compilers Steps of simulating recursion via iteration; must use language with Goto –terrible sin  in academia

25 Steps of Simulating Recursion
Consider directly recursive call for one simulated C function; apply these 6 steps: Define data structure for array s[] of type struct s_tp, to hold parameters, locals, temps, register copies, etc. Define explicit stack with top of stack (top) register as an index or subscript, initially top = 0; like a real HW stack identified by HW register sp, may overflow, so check! s[ top ] holds parameters, function return value, return location (labels after a recursive call), and automatic data, AKA locals in C Define labels for each point of recursive call, more precisely at each point after the call; number these points of return, e.g. l1, l2, l3, l4 etc. There shall be branches = gotos to these points of return

26 Steps of Simulating Recursion
4. At each point of recursive call: Increment the stack: i.e. top++, like recursive execution on real machine that grows + shrinks the sp register Move (i.e. simulate a push) all actual parameters for “this call” onto stack; e.g. assign: s[ top ].arg1 = actual_parameter1; s[ top ].arg2 = actual_parameter2; ... etc. Store the place of return: s[ top ].ret = 1, or 2, or 3 alluding to L1, L2 Allocate and initialize local, automatic objects: s[ top ].local1 = value Jump (Goto, the SW-Engineering sin!) to function head, not including initialization code; cannot use Java to simulate 

27 Steps of Simulating Recursion
5. Point of return: In simple cases, all explicitly coded returns and the implied return at end of recursive function body can be re-coded (or redirected) into a single place; if not, the code to simulate a return is replicated: Decrement the top of stack index: top-- Check, to which of the stored labels the flow of control has to branch to (return) in order to continue execution; e.g.: if ( s[ top+1 ].ret == xyz ) goto label_xyz; // did top-- And if no other branch is open, then fall through to the end For void functions this is a literal fall-through For true functions, the return value has to be computed before the fall-through, e.g.: return s[ top ].return_val; // top is that of caller!

28 Steps of Simulating Recursion
For nested recursive calls or several recursive calls in a row or both: Use this scheme meticulously 

29 Simulate Recursion, fact()
#include <stdio.h> #define MAX_STACK 100 // never reached or exceeded! #define MAX 14 // higher factorial overflows 32bits unsigned calls = 0; // track # of calls typedef struct s_tp { unsigned arg; // for formal parameters unsigned fact; // function return value unsigned ret; // code address after call, return! } struct_s_tp; // first the recursive fact() function for reference // includes tracking # of calls unsigned fact( unsigned arg ) { // fact calls++; // gotta be global if ( 0 == arg ) { // why strange order? return 1; }else{ return fact( arg - 1 ) * arg; } // end if // for complex code, assertion: “this is unreachable” }// end fact

30 Simulate Recursion, fact()
unsigned nrfact( unsigned arg ) { // nrfact struct_s_tp s[ MAX_STACK ]; // simulate RT stack! unsigned top = 0; // simulate sp register s[ top ].arg = arg; // this call’s argument s[ top ].ret = 3; // 3 alludes to label l3 l1: if ( 0 == s[ top ].arg ) { s[ top ].fact = 1; }else{ top++; // recursion! s[ top ].arg = s[ top-1 ].arg-1; s[ top ].ret = 2; // remember label l2 goto l1; // now simulate recursion l2: // back from recursive call top--; // sp-- s[ top ].fact = s[ top + 1 ].fact * s[ top ].arg; } // end if if ( s[ top ].ret == 2 ) { // test, where to branch to goto l2; // unstructured goto into if l3: return s[ top ].fact; } // end nrfact

31 Simulate Recursion, fact() Result
r_fact( 0) = , calls = r_fact( 1) = , calls = r_fact( 2) = , calls = r_fact( 3) = , calls = r_fact( 4) = , calls = r_fact( 5) = , calls = r_fact( 6) = , calls = r_fact( 7) = , calls = r_fact( 8) = , calls = r_fact( 9) = , calls = r_fact(10) = , calls = r_fact(11) = , calls = r_fact(12) = , calls = r_fact(13) = , calls = nr_fact( 0) = nr_fact( 1) = nr_fact( 2) = nr_fact( 3) = nr_fact( 4) = nr_fact( 5) = nr_fact( 6) = nr_fact( 7) = nr_fact( 8) = nr_fact( 9) = nr_fact(10) = nr_fact(11) = nr_fact(12) = nr_fact(13) =

32 Simulate Recursion, fibo()
#define MAX_STACK 100 // never to be reached or exceeded! #define MAX 30 // higher fibo(n) not computable! unsigned calls; // in case we track # of calls typedef struct s_tp { // type of stack unsigned arg; // copy of fibo’s arg unsigned fibo; // return value for fibo unsigned ret; // to which label to goto? } struct_s_tp; // recursive function for reference: unsigned fibo( unsigned arg ) { // fibo calls++; if ( arg <= 1 ) { // base case? return arg; // if so: done! }else{ return fibo( arg-1 ) + fibo( arg-2 ); } // end if } // end fibo

33 Simulate Recursion, fibo()
unsigned nr_fibo( unsigned arg ) { //nr_fibo struct_s_tp s[ MAX_STACK ]; // stack can be local unsigned top = 0; // initially s[ top ].arg = arg; // copy arg to stack s[ top ].ret = 4; // if all fails, return l1: if ( s[ top ].arg <= 1 ) { s[ top ].fibo = s[ top ].arg; }else{ top++; // ready to recurse s[ top ].arg = s[ top - 1 ].arg - 1; s[ top ].ret = 2; // to place of 1. return goto l1; // recurse! l2: top++; // ready to recurse again s[ top ].arg = s[ top - 2 ].arg - 2; s[ top ].ret = 3; // to place of 2nd return l3: // two returns simulated top -= 2; // simulate 2 returns s[ top ].fibo = s[ top + 1 ].fibo + s[ top + 2 ].fibo; } // end if if ( 2 == s[ top ].ret ) { // second recursive call goto l2; }else if ( 3 == s[ top ].ret ) { goto l3; l4: return s[ top ].fibo; // all done } // end nr_fibo

34 Simulate Recursion, fibo() Result
r_fibo( 0) = , calls = r_fibo( 1) = , calls = r_fibo( 2) = , calls = r_fibo( 3) = , calls = r_fibo( 4) = , calls = r_fibo(22) = , calls = r_fibo(23) = , calls = r_fibo(24) = , calls = r_fibo(25) = , calls = r_fibo(26) = , calls = r_fibo(27) = , calls = r_fibo(28) = , calls = r_fibo(29) = , calls = nr_fibo( 0) = nr_fibo( 1) = nr_fibo( 2) = nr_fibo( 3) = nr_fibo( 4) = nr_fibo(22) = nr_fibo(23) = nr_fibo(24) = nr_fibo(25) = nr_fibo(26) = nr_fibo(27) = nr_fibo(28) = nr_fibo(29) =

35 Simulating Return of fibo()
Must the computation of the continuation place be after the if-statement? Or can we relocate it into the Else-Clause? That would lead to a partial simulation, in which only the case arg > 1 continues correctly Yet even cases for arg <= 1 must compute the right continuation via (unstructured) brute-force gotos: if ( 2 == s[ top ].ret ) { // second recursive call goto l2; }else if ( 3 == s[ top ].ret ) { goto l3; } // end if

36 Towers of hanoi() The game of the “Towers of Hanoi” is a game to move a stack of n discs, while obeying certain rules; here coded in C++, not in C All n discs are of different sizes, residing on top of one another, a smaller disc always over a larger The goal is to move the whole tower from start, to the goal position, using one additional buffer location But only moving 1 single disc at a time And never placing a larger disc on top of a smaller During various times, any disc may be placed on the start position, the goal, or the buffer

37 Towers of hanoi(), Recursive
#include <iostream.h> #define MAX … some small integer < 32 void hanoi( int discs, char* start, char* goal, char* buff ) { // hanoi if ( discs > 0 ){ hanoi( discs-1, start, buff, goal ); cout << "move disc " << discs << " from " << start << " to “ << goal << endl; hanoi( discs-1, buff, goal, start ); } // end if // hanoi() is void function, so no need to simulate end label } // end hanoi int main() { // main for ( int discs = 1; discs <= MAX; discs++ ) { cout << ” hanoi for " << discs << " discs" << endl; hanoi( discs, "start", "goal ", "buff " ); cout << endl; } // end for return 0; } // end main

38 Towers of hanoi(), Recursive
move disc 1 from start to goal < For 1 disc move disc 1 from start to buff < For 2 discs move disc 2 from start to goal move disc 1 from buff to goal move disc 1 from start to goal < For 3 discs move disc 2 from start to buff move disc 1 from goal to buff move disc 3 from start to goal move disc 1 from buff to start move disc 2 from buff to goal move disc 1 from start to goal move disc 1 from start to buff < For 4 discs move disc 3 from start to buff move disc 1 from goal to start move disc 2 from goal to buff move disc 1 from start to buff move disc 4 from start to goal move disc 2 from buff to start move disc 3 from buff to goal

39 Simulate Recursion, hanoi()
void nr_hanoi( unsigned discs, char* start, char* goal, char* buff ) { // nr_hanoi struct_h_type s[ MAX_STACK ]; unsigned top = 0; s[ top ].discs = discs; s[ top ].start = start; s[ top ].buff = buff; s[ top ].goal = goal; s[ top ].ret = 4; l1: if ( s[ top ].discs > 0 ) { top++; s[ top ].discs = s[ top-1 ].discs - 1; s[ top ].start = s[ top-1 ].start; s[ top ].buff = s[ top-1 ].goal; s[ top ].goal = s[ top-1 ].buff; s[ top ].ret = 2; goto l1; l2: cout << "nr move disc “ << s[ top ].discs << “ from “ << s[ top ].start << “ to “ << s[ top ].goal << endl; s[ top ].start = s[ top-1 ].buff; s[ top ].buff = s[ top-1 ].start; s[ top ].goal = s[ top-1 ].goal; s[ top ].ret = 3; } // end if l3: if ( 2 == s[ top ].ret ) { top--; goto l2; }else if ( 3 == s[ top ].ret ) { goto l3; } // end nr_hanoi

40 References Douglas R. Hofstadter, “Gödel, Escher, Bach: an eternal golden braid”, Basic Books, 1999, ISBN Ackermann function at NIST: Herbert G Mayer: “Advanced C Programming on the IBM PC”, 1989, Windcrest, ISBN Non-recursive solution to Towers of Hanoi: Herbert G Mayer, Don Perkins, SIGLAN Notices, 1984, non-recursive Towers of Hanoi Solution:


Download ppt "Chapter 5 Microprocessor Runtime Stack"

Similar presentations


Ads by Google