PARAMETER PASSING Subroutines are used where a section of code must be executed more than once, or to improve program structuring. Subroutines may be passed different values each time they are called. These values are called parameters. Parameters to functions (subroutines which return a value) are often called arguments. In the subroutine definition, the parameters are given names called formal parameters, and whenever the subroutine is called it is passed values called actual parameters. There are a number of ways to pass parameters, examples given below. In examples 2 and 3, the macros PUSH and POP are defined as follows: .MACRO PUSH,X MOV X,-(SP) .ENDM .MACRO POP,X MOV (SP)+,X .ENDM 1) Parameters may be put in selected memory locations (such as registers) which are used inside the subroutine. This technique is common in assembly language subroutines, and must also be used in languages such as COBOL and BASIC where subroutines cannot be defined with formal parameters and access to the stack is forbidden. Example: MOV #12.,R1 MOV #37.,R2 MOV #42.,R3 JSR PC,ADD3 ;Add the numbers in R1, R2 and R3. MOV R1,SUM ;Put the result in SUM . . . ADD3: ADD R2,R1 ADD R3,R1 RTS PC ;Value is in R1. 2) They may be placed in the words immediately following the subroutine call instruction. They can then be accessed inside the subroutine using the saved program counter on top of the stack. Example: JSR PC,ADD3 ;add the following three numbers .WORD 12. .WORD 37. .WORD 42. MOV R1,SUM ;put the result in SUM . . . ;*** SEE NEXT PAGE ADD3: POP R0 ;Put the return address in R0. ;This points to the word ; immediately following the ; subroutine call (value 12). MOV (R0)+,R1 ;Get first param. ADD (R0)+,R1 ;Add second param. ADD (R0)+,R1 ;Add third param. R0 now points ; to the word immediately ; following the last param (the ; MOV instruction). This is the ; address which the subroutine ; should return to. PUSH R0 ;Push it back on to the stack. RTS PC ;Return 3) They may be placed on top of the stack before the subroutine is called. They can then be accessed inside the subroutine after the return address is removed. Example: PUSH #12. ;Push the numbers on to the stack. PUSH #37. PUSH #42. JSR PC,ADD3 ;Add them. POP SUM ;Put the result in SUM. . . . ADD3: POP R0 ;Save the return address. POP R1 ;Pop the three values. POP R2 POP R3 ADD R2,R1 ;Add them. ADD R3,R1 PUSH R1 ;Push the result on to the stack. PUSH R0 ;Push the return addr before RTS. RTS PC ;Return. RECURSION "To iterate is human, to recures divine" A recursive routine is one which calls itself. An example has already been given (lecture 4), namely the Fibonacci function. Here is its definition in Pascal: function fibo(n: integer): integer; begin if (n = 0) or (n = 1) then fibo := 1 else fibo := fibo(n - 1) + fibo(n - 2) (*end if*) end; This example should be sufficiently easy to understand even without a knowledge of Pascal. A recursive routine may be a function (a routine which returns a value, which is the value of the function), or a procedure (which doesn't have a value, and is executed for its side-effects). In order that a recursive routine terminates (finishes execution), it must have terminating conditions. In the Fibonacci function definition above, there is one: (n = 0) or (n = 1). When the terminating condition is true, the function immediately returns a value (here, 1). If it is false, the Fibonacci function is called again, with arguments closer to the terminating condition (here, n - 1 and n - 2). Most high level languages allow recursive procedure and function definitions. Examples are Pascal (see above), Algol, C and LISP. Recursion is not allowed in FORTRAN (because parameter passing is implemented in a way that forbids it), and COBOL and most BASICs (because subroutines do not have formal parameters and access to the stack is forbidden). Recursion is usually implemented using a stack, though other implementations are possible. The parameters are passed using the stack in much the same way as in example 3 above, but they are not removed from the stack until the subroutine is exited. More information must be given to the called subroutine. One item of extra information required is a pointer to the first parameter. This is called the dynamic link. It enables the parameters to be accessed during execution of the subroutine, and is also used for clearing the parameters from the stack when the subroutine is exited. Recursive functions and procedures can be implemented in MACRO-11. Here, we will examine implementation of recursive functions. Because subroutine calling and returning, and parameter access require several instructions, and all manipulate the stack, three instructions will be defined, as macros: ARG, CALL and RETURN. R5 will be used to hold the current dynamic link (the others are held on the stack). DL=%5 ;Dynamic link is held in R5. .MACRO ARG,N ;Copy the Nth param on to the ; top of stack. MOV N,R0 ASL R0 NEG R0 ADD DL,R0 ;R0 <- DL - 2 * N. R0 now ; points to the required ; parameter. PUSH (R0) ;Copy it on to top of stack. .ENDM .MACRO CALL,LABEL,N ;Call function with N params ; defined at label. MOV SP,R0 MOV N,R1 ;R1 <- number of params. ASL R1 ADD R1,R0 ;R0 <- SP + 2 * N. PUSH DL ;Save old dynamic link. MOV R0,DL ;Dynamic link <- R0. JSR PC,LABEL ;Push PC & branch to LABEL. .ENDM .MACRO RETURN ;Return from function. At ; this stage the value to be ; returned is on top of ; stack, and beneath it are ; the return address and ; previous dynamic link. POP R0 ;R0 <- value to be returned. POP R1 ;R1 <- return address. POP R2 ;R2 <- prev. dynamic link MOV DL,SP ;Clear stack of parameters MOV R2,DL ;Restore prev. dynamic link. PUSH R0 ;Push value to be returned. JMP R1 ;Branch to return address. .ENDM There is a neater way of handling parameter passing, using the MARK instruction. However, it is less easy to understand. REVERSE POLISH This is an arithmetic notation which uses a stack for storing numbers, and whose operations work on the top few stack entries. It is named after a Polish mathematician whose name is difficult to pronounce. It is used in a number of electronic calculators, and also in the languages POP1 (now extinct) and FORTH. The operands of an arithmetic operation precede the operation itself. For example: 3 4 + is equal to 7. Reverse Polish removes the need for brackets in complex arithmetic expressions; for example 3 4 + 5 2 - * 2 / is the equivalent of (3 + 4) * (5 - 2) / 2. Reverse Polish expressions are particularly simple to parse. If a number is scanned (read in), push it on to the top of the stack. If a symbol for an arithmetic operation is scanned, perform the operation on the first few stack entries (the number of operands depends on the particular operation), and leave the result on top of the stack. During evaluation of the arithmetic expression 3 4 + 5 *, the stack contains the following entries: scanning 3 3 scanning 4 4 3 scanning + 7 scanning 5 5 7 scanning * 35 Compilers for languages such as Algol, Pascal and C translate an arithmetic expression in ordinary infix notation into Reverse Polish machine code (LISP source code uses Cambridge Polish, or prefix notation; (3 + 4) * 5 is written as (times (plus 3 4) 5) This, again, is easier to parse than infix notation, but more difficult for people to read.) The point of all this is that we can define macros which perform arithmetic operations on the top few stack entries, and use them in conjunction with the macros ARG, CALL and return, to simulate operations on a "stack machine". .MACRO PLUS ;Pops the top two stack ADD (SP)+,(SP) ; entries, adds them, and .ENDM ; puts the result on top of ; stack. .MACRO DIFFCE ;Same, except subtracts them. SUB (SP)+,(SP) .ENDM .MACRO EQ ;Pops the top two stack MOV (SP)+,R0 ; entries, and sets Z flag CMP (SP)+,R0 ; if they are equal. .ENDM We also need a conditional branch instruction, the low level equivalent of the if...then...else... statement of the Pascal function shown above. We might use the following macro: .MACRO IFNOT,LABEL ;Branch if Z flag is clear. BNE LABEL .ENDM With these few instructions, it is now possible to write a recursive factorial function in MACRO-11. Here it is FIBO: ARG #1 PUSH #0 EQ IFNOT L1 ;If first arg <> 0 goto L1, PUSH #1 ; otherwise return 1. RETURN L1: ARG #1 PUSH #1 EQ IFNOT L2 ;If first arg <> 1 goto L2, PUSH #1 ; otherwise return 1 RETURN L2: ARG #1 PUSH #1 ;Subtract 1 from copy of DIFFCE ; first arg. CALL FIBO #1 ;Call fibo(first arg - 1) ARG #1 PUSH #2 ;Subtract 2 from copy of DIFFCE ; first arg. CALL FIBO #1 ;Call fibo(first arg - 2) PLUS ;Add the two fibos. RETURN ;Return their sum as the value ; of fibo. A SIMPLE SOFT MACHINE The above code now bears very little resemblance PDP-11 machine language. Every instruction is a macro, and everything except conditional branch instructions uses the stack. We can easily make these instructions into soft machine instructions. The effect of this is that the code will run slower, but take up much less space on the PDP-11 and be machine-independent. Soft machines are, at least in principle, very easy to build. Assuming there are not more than 256 different instructions, each instruction can occupy a single byte. On each instruction decode cycle, the soft machine fetches the next S-instruction from memory and uses it as an index into a table of addresses called a switch table. Each entry contains the address of the code which carries out the instruction. After this code has been executed, the next instruction is decoded and executed. Here is some of the code of a typical soft machine: FETCH: MOVB (R0)+,R1 ;Fetch instruction BIC #177400,R1 ;Clear top byte. ASL R1 ;Multiply by 2. JMP SWTB(R1) ;Branch to instruction code. SWTB: .WORD ......................... .WORD ......................... .WORD PLUS,DIFFCE,............. .WORD ......................... .WORD ......................... PLUS: ADD (SP)+,(SP) JMP FETCH DIFFCE: SUB (SP)+,(SP) JMP FETCH Projects: 1) Write a routine which prints out the value of a register in octal. 2) Write a routine which reads in a number in octal, and stores it in a register. 3) Use these as input/output routines for the Fibonacci program (either recursive or iterative). 4) Use them, together with some of the macro definitions given above (write any more that you need), to write an octal reverse Polish calculator. 5) Write a recursive algorithm for the factorial function. Implement it in MACRO-11 using the macro definitions given above, plus a TIMES macro (for multiplication).