The PDP-11: Subroutine linkage

In subroutine linkage, we need to save a return address; we also need to pass parameters and return a result. As well, we have to "back up" and restore any registers (GPRs) we use in the subroutine, because the calling routine might have calculations in progress in those registers.

Much of this involves using a stack, which is described at the end of the rambly PDP-11 notes in this directory.

I have presented / will be presenting three different models of subroutine linkage in lecture, in this order:

1. The old Fortran style:

The call:

         MOV #RETADDR, SUBR
         JMP SUBR+2
RETADDR: ... next instruction goes here ("return address") ...

The subroutine:

SUBR:	.WORD 0
	blah
	blah
	blah
	JMP @SUBR
This is nasty for a number of reasons, including the fact that it doesn't permit recursion. We didn't talk about this very much and you don't need to know it, although I think it's good if you can recognize it, at least dimly, if you come across it in future (outside of this course).

2. The PDP-11 style, with parameters:

The call:

	JSR R5, SUBR
	.WORD 55
	.WORD 50
The PDP-11 JSR instruction has a "linkage register"; it pushes the old R5 (or whichever register you use for the linkage register) onto the stack and puts the current R7 (PC) into the linkage register, so we're basically pushing the PC but it's going through the linkage register. This allows the subroutine to pick up arguments from succeeding memory locations easily, as follows.

The subroutine:

SUBR:	MOV R1, -(R6)   ; pushing R1 so we can use it as scratch
	MOV (R5)+, R0
	MOV (R5)+, R1
	some computation which takes R1,R0 and leaves result in R0
	MOV (R6)+, R1  ; pop
	RTS R5
The picking up of the parameters increments R5, thus skipping the parameters when we return. There is no point "backing up" R0 if we are going to use that as our standard register for returning a result; in fact, backing it up and restoring it contradicts its use for a return value.

One serious problem with this is the mixing of code and data. Those "55" and "50" locations may well not be constants, in which case we'd have to do a MOV into those locations. I'm not sure I can convince you of the magnitude of the problem in mixing code and data; but one thing is that in unix, you have a write-protected program segment, always, including in the original full version of unix which ran on a pdp-11, so you cannot write into it to use this parameter-passing model.

3. Push the arguments on the stack.

Instead of specifying a linkage register, you can specify "R7" and it all works out; look at the table of the semantics and check it out. Basically if you specify R7 as the linkage register, it's like not having one. Which is what we want here. This argument-passing method does not use the linkage register for anything, so we want not to lose the use of a register over it. R5 is freed for general-purpose use.

The call:

	MOV ARG1, -(R6)
	MOV ARG2, -(R6)
	JSR R7, SUBR
	ADD #4, R6  ; pop two two-word args

The subroutine:

SUBR:	MOV 4(R6), R0
	MOV 2(R6), somewhere else
	...
or better yet, in the subroutine just use "4(R6)" and "2(R6)" when you need them. You can't do this in the previous method because you have to pop them off the stack exactly once. But here there is no popping, just a direct reference, so it's a lot cleaner. Return with "RTS R7".

Of course the popping of the arguments has to happen somewhere. It's done in the caller. The caller could do:

	MOV (R6)+, somewhere
	MOV (R6)+, somewhere
to pop the two arguments. This requires somewhere to put them. Of course the caller is not interested in retrieving the arguments; it already knows them, and passed them to the subroutine itself.

The ADD #4, R6 instruction above accomplishes the same effect as this, in that it adjusts the R6 stack pointer by two words. It doesn't pop the data to anywhere, but this is fine; in fact it's preferable. And if there are more than two arguments, the ADD is shorter too.


Example problem

Write both a call sequence and the subroutine in each of methods #2 and #3 to compute a function f(x,y), where the call is f(20,30) and the function is to compute and return x+3y.

With linkage register:

The call:

	JSR R5, SUBR
	.WORD 20
	.WORD 30
	... next instruction goes here ...

The subroutine:

SUBR:	MOV R1, -(R6)
	MOV (R5)+, R0
	MOV (R5)+, R1
	MUL #3, R1  ; MUL into an odd-numbered register yields only the low word
	ADD R1, R0
	MOV (R6)+, R1
	RTS R5
The "MOV R1, -(R6)" pushes the register R1 for later restore.

Without linkage register, pushing the parameters:

The call:

	MOV #20, -(R6)
	MOV #30, -(R6)
	JSR R7, SUBR
	ADD #4, R6
	... next instruction goes here ...

The subroutine:

SUBR:	MOV R1, -(R6)
	MOV #3, R1
	MUL 4(R6), R1
	ADD 6(R6), R1
	MOV R1, R0
	MOV (R6)+, R1
	RTS R7
This, incidentally, does show one of the advantages of this stack-oriented passing of parameters, which is that since operand descriptions such as "6(R6)" have no side-effects, you can just refer to them anywhere in the subroutine, in any order, however many times you want, etc.

And the reason I am using R1, rather than R0 which would seem not to require the "backup" and restore of R1, is that MUL produces a two-word result iff its destination register is even-numbered. See the pdp-11 reference handout. So if you just want a 16-bit result, you just use an odd-numbered register. Furthermore, if writing "MUL 4(R6), R0", this does modify R1, you would have to back it up anyway. My use of R1 there evades this issue.


[list of course notes topics available so far]
[main course page]