Subroutine calls on the PDP-10

70 views
Skip to first unread message

Ric Werme

unread,
Oct 2, 2025, 1:43:28 AMOct 2
to PiDP-10
I'm creating this in response to a wayward question on "Graphics programming on the PiDP-10".

The PDP-10 is generally used with a stack-based subroutine calling convention.

Typical usage on TOPS 10 is something like:

SEARCH C ;USE DEC'S PARAMETER FILE (defines P, T1, etc.)

MAIN:   MOVE    P,[IOWD STKLEN, STACK]  ; i.e. [XWD -STKLEN,STACK-1] or [-STKLEN,,STACK-1]
        MOVEI T1,^D2025   ;Number to print
        PUSHJ P,DECPRT    ;Print a decimal number
        EXIT

DECPRT: <code on demand>
        POPJ  P,                   ;Return

STKLEN=40
STACK:  BLOCK STKLEN
 
It's late, I'll address more schemes later - the PDP-10 has several!

See https://wermenh.com/folklore/tulip.doc.html for sample code (but a lot of other stuff too).

Ric Werme

unread,
Oct 2, 2025, 4:02:51 PMOct 2
to PiDP-10
JSR <target>/JRST @<target>

The JSR instruction Increments the PC, stores it (and CPU flags) at the target subroutine's address, and sets the PC to that address + 1.  It is useful in cases where it's important to not change any of the general purpose registers so something very similar is done for system calls ans other UUOs - "Unimplemented User Operations."  The two biggest reasons to avoid it are:

1) The code has to be in memory that can be written, i.e. it would be quite a stretch to want to use it in a sharable HISEG.

2) Recursion can't be done.  Well, if the subroutine had something like a "PUSH P,<target>" to save the return address you could, but that would basically emulate a PUSHJ call.

DEC's first Fortran compiler used JSR, which is probably why I used it in https://wermenh.com/folklore/light/index.html (I do have a PiPDP-10 port with support for dealing with with emulation speed and a binary clock function).  IIRC, the compiler passed parameters by call by reference, with the address of the parameter after the JSR, e.g.

    YEAR = 2025
    FOO(YEAR)

may have generated something like:

    MOVEI T1,^d2025
     MOVEM T1, YEAR
    JSR FOO
    XWD YEAR

FOO: BLOCK 1           ;Save a word for the return PC
    MOVE T1,@FOO   ; Fetch the parameter
    ....
    AOS FOO              ;Point to first instruction after parameter
    JRST  @FOO        ;Return to caller

For any instruction, the CPU first evaluates the effective address, increments the PC and performs the operation.  UUOs, rather than being "Unimplemented" all implement storing the instruction being executed - opcode, accumulator, and effective address in location 040 (opcodes 0 and 040-077 switch to exec mode first, 001-037 don't) in location 040 and executes the instruction in location 041 (without mucking with the PC).  This is expected to be a JSR to the UUO handler which will process both the executed instruction and know how to resume execution.

Ric Werme

unread,
Oct 2, 2025, 4:31:18 PMOct 2
to PiDP-10
JSA AC,<target>/JRA AC,

I've never used this sequence.  It's like a JSR except it uses an accumulator instead of the PC.

JSA (Jump and Save Ac) stores the AC at <target> it changes AC to be <target>,,PC.  (N.B. PC has already been incremented to the return address.)  It then sets PC to <target>+1.  The JRA (Jump and Restore Ac) sets PC to the RH of AC, then loads AC from the location that was in it, which will be <target>.  So the subroutine returns with AC restore to its initial value.

Two notes:

1) I see that the "pdp10 reference handbook" says "In Fortran IV, a CALL statement uses JSA with AC 16."  Perhaps my claim that it uses JSR is wrong.

2) While I never used JRA, the Tulip I/O package uses it in code to call to save "permanent" accumulators, call back to its caller, then restore the accumulators and return to the original caller.  Here's how it saves and restore P1 & P2:

SAVE2:: EXCH    P1,(P) ;SAVE P1, GET CALLER PC
        HRLI    P1,(P) ;REMEMBER WHERE SAVED P1 IS
        PUSH    P,P2 ;SAVE P2
        PUSHJ   P,SAVJMP ;STACK NEW RETURN PC AND JUMP
  SOS   -2(P) ;NON-SKIP RETURN, COMPENSATE CPOPJ1
        JRST    P2PJ1 ;SKIP RETURN, RESTORE P2,P1 AND SKIP

P2PJ1:  POP     P,P2 ;RESTORE P2
P1PJ1:  POP     P,P1 ;RESTORE P1
CPOPJ1::AOS     (P) ;INCREMENT PC
CPOPJ:: POPJ    P, ;RETURN

;THE FOLLOWING INSTRUCTION RESTORES P1 AND DISPATCHES TO THE CALLER.
SAVJMP: JRA P1,(P1)

On Thursday, October 2, 2025 at 1:43:28 AM UTC-4 Ric Werme wrote:

Ric Werme

unread,
Oct 2, 2025, 4:55:27 PMOct 2
to PiDP-10
JSP AC,<target>/JRST (AC)

JSP (Jump and Save Pc) saves the current PC in AC and sets PC to <target>.  The return is simply to restore the PC from AC.

This is rarely used because a chain of subroutine calls would need an AC for each step.  However, there are two attractive cases:

1) Neither call nor return modifies memory, so this is the fastest subroutine call and hence worth considering for small "leaf" routines that do little but are called frequently.

2) The instruction JSP AC,(AC) exchanges the contents of the PC with AC.  This makes it useful for "coroutines" - two pieces of code that work together instead of one always calling the other.  A big limitation is that the coroutines can't really share something like a stack - all of the exchanges have to be at the same stack level.

I used a coroutine in the CMU/Harvard TELNET protocol decoder.  While messages came into the interrupt service routine for the IMP's HOST-HOST protocol, the ISR would pass each byte to the telnet code.  The telnet code had no idea it was being called from code that had a message, it would have been just as happy if it were calling code to wait for the next byte to arrive from the other node.

I also used a coroutine in my TULIP documentation.  A sample program includes code to take a command name and match it in a table, handling unique abbreviations but also handling ambiguous ones and invalid commands.  The code needed more lines of comments than instructions!

On Thursday, October 2, 2025 at 1:43:28 AM UTC-4 Ric Werme wrote:

Lars Brinkhoff

unread,
Oct 3, 2025, 12:53:31 AMOct 3
to PiDP-10
Ric wrote:
The JSR instruction Increments the PC, stores it (and CPU flags) at the target subroutine's address, and sets the PC to that address + 1.

This is also what the subroutine call instruction does on earlier PDP's, i.e. PDP-1, PDP-4 (+ successors), and PDP-5 (+ successors).

Lars Brinkhoff

unread,
Oct 3, 2025, 1:08:32 AMOct 3
to PiDP-10
Ric wrote:
This is rarely used because a chain of subroutine calls would need an AC for each step.  However, there are two attractive cases:
1) Neither call nor return modifies memory, so this is the fastest subroutine call and hence worth considering for small "leaf" routines that do little but are called frequently.

When I worked on GCC, this call mechanism was requested as an option.
 
2) The instruction JSP AC,(AC) exchanges the contents of the PC with AC.  This makes it useful for "coroutines" - two pieces of code that work together instead of one always calling the other.  A big limitation is that the coroutines can't really share something like a stack - all of the exchanges have to be at the same stack level.

I'm using this in my LITES program - each pattern is its own coroutine, and in TVBROT for encoding 340 instructions:



I think coroutines can be very useful, and I sometimes miss them in other programming languages.
Reply all
Reply to author
Forward
0 new messages