Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How Do JSR and RTS work?

795 views
Skip to first unread message

alx...@aol.com

unread,
Mar 17, 2016, 1:02:56 PM3/17/16
to
I am up to using CMP and BNE/BEQ commands in my book, but JSR and RTS still aren't sticking with me. Can anyone explain exactly how they work, and what the requirements are for using them? It seems that every time a JSR is used an RTS should be required, but none of the instructional programs I study are setup like that.

David Schmidt

unread,
Mar 17, 2016, 1:19:55 PM3/17/16
to
On 3/17/2016 1:02 PM, alx...@aol.com wrote:
> I am up to using CMP and BNE/BEQ commands in my book, but JSR and RTS
> still aren't sticking with me. Can anyone explain exactly how they
> work, and what the requirements are for using them?

http://www.6502.org/tutorials/6502opcodes.html#JSR

JSR pushes the program counter (minus 1) onto the stack, then jumps to
the location specified.

http://www.6502.org/tutorials/6502opcodes.html#RTS

RTS pulls the top two bytes off the stack, puts them into the program
counter, which then auto-increments as the next instruction is
fetched... following the location that JSR pushed! Voila, jump to a
subroutine, and return from whence it came.

> It seems that every time a JSR is used an RTS should be required,

It is - or else your stack will burst. (Unless you do some other
unnatural chicanery to clean up the stack.)

> but none of the
> instructional programs I study are setup like that.

That would be... unusual. Of course it's possible to manually pop the
pushed addresses off the stack yourself and clean up a missing RTS, and
some very tricky code does things like that - but it would be cruel and
unusual for "instructional programs" to teach that, at least at first.
Are you _sure_ they don't do normal JSR-RTS things? It's just like
GOSUB and RETURN in BASIC. If you GOSUB too much without returning,
eventually you crash.

Michael J. Mahon

unread,
Mar 17, 2016, 1:29:05 PM3/17/16
to
In general, you are correct--JSR and RTS are complementary halves of a
subroutine linkage mechanism.

JSR first pushes the "return address" on the stack, then jumps to the entry
point of the subroutine. Upon completion of the subroutine, it executed an
RTS, which pops the return address off the stack and jumps to it,
"returning" to the instruction following the "calling" JSR.

This provides a simple method for calling a commonly used piece of code
from multiple places in a program.

The details are a bit more subtle, but are unimportant for straightforward
JSR/RTS subroutine linkage.

Now for the subtlety: the actual address pushed onto the stack is the
address of the third byte of the JSR, not the address of the first byte of
the following instruction, so the RTS increments the address it pops off
the stack by one before jumping to it. This is only important when using
the address on the stack for other purposes, or when explicitly placing an
address on the stack to be used by a subsequent RTS.

As in all other uses of the stack, it's important that "pushes" and "pops"
be balanced, so that the stack neither grows nor shrinks overall during
program execution.
--
-michael - NadaNet 3.1 and AppleCrate II: http://michaeljmahon.com

wss...@gmail.com

unread,
Mar 17, 2016, 1:29:24 PM3/17/16
to
On Friday, March 18, 2016 at 2:02:56 AM UTC+9, alx...@aol.com wrote:
> I am up to using CMP and BNE/BEQ commands in my book, but JSR and RTS still aren't sticking with me. Can anyone explain exactly how they work, and what the requirements are for using them? It seems that every time a JSR is used an RTS should be required, but none of the instructional programs I study are setup like that.

JSR is like a gosub in BASIC or a procedure call in Pascal. It causes the current PC to be pushed onto the stack and then the PC is loaded with the operand of the JSR instruction -- that is, it is loaded with the target address.

When RTS is encountered, the two top bytes on the stack are popped off the stack and loaded into the PC. This is supposed to set the PC to the address immediately after the original JSR -- thus flow of control returns to the original routine that used JSR to call a subroutine.

As for your instructional programs, if they are attempting to teach the use of JSR, then I suspect they are set up correctly, but perhaps your intuition about what how JSR and RTS are used is wrong. Here is a short example:

Suppose I have a subroutine that is supposed to divide the value in A by 8. It might look like this:

mul8
asl
asl
asl
rts

This works because a right shift is the same as a division by two. The rts at the end of the routine allows me to use this as a subroutine to be called from some other code. I might have some other code that needs to divide by 8 for some reason. It would look like this:

...
jsr mul8
sta var
...

When the instruction "jsr mul8" is executed, the PC is loaded with the address of the mul8 subroutine, and execution continues with that routine. The three asl instructions are executed. Then the rts is executed, which causes the PC to be loaded with the address of the "sta var" instruction, which follows the jsr instruction, and execution continues with that instruction.

FYI, there are some subtleties regarding exactly what is pushed on the stack by JSR and then later popped off by RTS that I have glossed over, but my description is sufficiently accurate for your current needs.

wss...@gmail.com

unread,
Mar 17, 2016, 1:32:41 PM3/17/16
to
> Suppose I have a subroutine that is supposed to divide the value in A by 8. It might look like this:
>
> mul8
> asl
> asl
> asl
> rts
>
> This works because a right shift is the same as a division by two. The rts at the end of the routine allows me to use this as a subroutine to be called from some other code. I might have some other code that needs to divide by 8 for some reason. It would look like this:

Oops I made a mistake here. I wrote about right shifting and dividing, but the code uses ASL, which is a left shift. Mea Culpa. The ASLs should be LSRs.

Michael Pohoreski

unread,
Mar 17, 2016, 6:37:16 PM3/17/16
to
On Thursday, March 17, 2016 at 10:02:56 AM UTC-7, alx...@aol.com wrote:
> I am up to using CMP and BNE/BEQ commands in my book, but JSR and RTS still aren't sticking with me. Can anyone explain exactly how they work, and what the requirements are for using them? It seems that every time a JSR is used an RTS should be required, but none of the instructional programs I study are setup like that.

JSR = Jump to Sub Routine
RTS = Return Sub routine

Enter in this small demo:

CALL-151
300:20 06 03
303:4C ED FD
306:AD 00 C0
308:10 FB
30B:8D 10 C0
30E:60
300G


You'll notice the address $0306 is stored as bytes in reverse order: 06 03 because the 6502 is little endian.


Here's the Disassembly:

300: JSR $0306
303: JMP $FDED
306: LDA $C000
309: BPL $0306
30B: STA $C010
30E: RTS

The equivalent C pseudo code would be:

int main() // @ $0300
{
char a = getkey();
putchar( a );
}

char getkey() // @ $0306
{
_306:
a = getchar();
if( a < 0x80 )
goto _306;
return a;
}

Think of JSR being the function call operator '();'
And RTS being the "return".

Requirements are that you have enough stack space.

So what happens when the 6502 encounters a JSR ?

It save the address of the next instruction -1 (which is $0302) on the hardware stack. The hardware stack resides at $0100 .. $01FF
It then GOTO's the address
it continues executing instructions.

When the 6502 executes a RTS it pulls off 2 bytes from the stack.
It increments the address
It GOTO's _that_ address.

Here's a little demo program to that shows how JSR works:

310:20 13 03
313:68
314:20 DA FD
317:68
318:4C DA FD
310G




Michael Pohoreski

unread,
Mar 17, 2016, 7:16:32 PM3/17/16
to
On Thursday, March 17, 2016 at 3:37:16 PM UTC-7, Michael Pohoreski wrote:

> Here's a little demo program to that shows how JSR works:

Or to make it a little more user friendly and use the same JSR address as the first program:

300:20 06 03
306:68
307:AA
308:68
309:20 DA FD
30C:8A
30D:4C DA FD
300G

Which will print the address 0302

Michael Pohoreski

unread,
Mar 17, 2016, 7:38:52 PM3/17/16
to
On Thursday, March 17, 2016 at 10:19:55 AM UTC-7, schmidtd wrote:
> > It seems that every time a JSR is used an RTS should be required,
>
> It is - or else your stack will burst. (Unless you do some other
> unnatural chicanery to clean up the stack.)

Look ma, no RTS :-)

300:20 06 03 4C ED FD
306:7A FA C8 D0 01 E8
30C:8C 15 03 8E 16 03 A9 41 4C FF
300G

Self-modifying code is not "unnatural chicanery" -- it "natural optimizations" :-)

300: 20 06 03 JSR $306
303: 4C ED FD JMP $FDED
306: 7A PLY ; lo
307: FA PLX ; hi
308: C8 INY
309: D0 01 BNE $30C
30B: E8 INX
30C: 8C 15 03 STY $315
30F: 8E 16 03 STX $316
312: A9 65 LDA #$41
314: 4C FF FF JMP $FFFF

Also, using an RTS to "fake" an JMP is an natural 6502 optimization "trick" once you understand how RTS works.



David Schmidt

unread,
Mar 17, 2016, 8:34:19 PM3/17/16
to
On 3/17/2016 7:38 PM, Michael Pohoreski wrote:
> On Thursday, March 17, 2016 at 10:19:55 AM UTC-7, schmidtd wrote:
>>> It seems that every time a JSR is used an RTS should be required,
>>
>> It is - or else your stack will burst. (Unless you do some other
>> unnatural chicanery to clean up the stack.)
>
> Look ma, no RTS :-)
>
> 300:20 06 03 4C ED FD
> 306:7A FA C8 D0 01 E8
> 30C:8C 15 03 8E 16 03 A9 41 4C FF
> 300G
>
> Self-modifying code is not "unnatural chicanery" -- it "natural optimizations" :-)
>
> [...]
>
> Also, using an RTS to "fake" an JMP is an natural 6502 optimization "trick" once you understand how RTS works.

"...once you understand how RTS works."

Ummm, exactly what we're trying to do here. _Before_ we get into
self-modifying code and other optimization chicanery. ;-)

mark...@yahoo.com.sg

unread,
Mar 17, 2016, 9:41:38 PM3/17/16
to
On Friday, March 18, 2016 at 1:02:56 AM UTC+8, alx...@aol.com wrote:
> I am up to using CMP and BNE/BEQ commands in my book, but JSR and RTS still aren't sticking with me. Can anyone explain exactly how they work, and what the requirements are for using them? It seems that every time a JSR is used an RTS should be required, but none of the instructional programs I study are setup like that.

My two cents worth: If you have the time, learn how to use BUGBYTER.

Mark

wss...@gmail.com

unread,
Mar 17, 2016, 11:22:26 PM3/17/16
to
Also, learn to use the Virtual II inspector (debugger).

Michael Pohoreski

unread,
Mar 18, 2016, 11:10:11 AM3/18/16
to
Or *cough* AppleWin's debugger.


CALL-151
F7
300:20
303:4C ED FD
306:A9 41
308:60
BP 300
G
F7
300G
<SPACE>
<SPACE>
<SPACE>
G
F7

wss...@gmail.com

unread,
Mar 18, 2016, 11:47:45 AM3/18/16
to
Sure but he already said he's using Virtual II.
I find the Virtual II debugger to be fairly weak but it's a lot better than nothing at all.

alx...@aol.com

unread,
Mar 18, 2016, 12:22:38 PM3/18/16
to
The instructional program I'm on now is all about using a built-in routine for COUT. The only RTS in this listing seems to be the one for JSR HOME. Wouldn't the program need an RTS for each time I use COUT?

CTR EQU $06
HOME EQU $FC58
COUT EQU $FDED
START JSR HOME
LDA #$FF
STA CTR
LOOP LDA CTR
JSR COUT
DEC CTR
BEQ END
JMP LOOP
END RTS

David Schmidt

unread,
Mar 18, 2016, 12:31:33 PM3/18/16
to
Oh, certainly - the RTS is part of the COUT and HOME routines, which
lives off in ROM somewhere separate from your program. So you're
calling a subroutine that does the returning to you as part of its work.
So that HOME routine is doing something like this, elsewhere in memory
($FC58, to be exact):

HOME (do some work)
(do some more work)
(just about done)
RTS

alx...@aol.com

unread,
Mar 18, 2016, 12:36:47 PM3/18/16
to
Hmmmm, I wish the book had mentioned that.
But if RTS is built in to these routines, what is my RTS command actually doing?

David Schmidt

unread,
Mar 18, 2016, 12:39:20 PM3/18/16
to
On 3/18/2016 12:36 PM, alx...@aol.com wrote:
> Hmmmm, I wish the book had mentioned that.
> But if RTS is built in to these routines, what is my RTS command actually doing?

_Your_ RTS is returning to the "system," whatever that is - the monitor
if you start from the monitor, or BASIC if you're CALLing it from there.

Michael Pohoreski

unread,
Mar 18, 2016, 3:25:51 PM3/18/16
to
On Friday, March 18, 2016 at 9:39:20 AM UTC-7, schmidtd wrote:
> On 3/18/2016 12:36 PM, alxmist
> > Hmmmm, I wish the book had mentioned that.
> > But if RTS is built in to these routines, what is my RTS command actually doing?
>
> _Your_ RTS is returning to the "system," whatever that is - the monitor
> if you start from the monitor, or BASIC if you're CALLing it from there.

Exactly. You can probe the return address *non-destructively* via the TSX instruction:

PRHEX EQU $FDDA

START TSX
LDA $102,X
JSR PHDEX
LDA $101,X
END JMP PRHEX

which assembles to:

300:BA BD 02 01 20 DA FD BD 01 01 4C DA FD
Ctrl-C
CALL 768

It will print FF84, which is -1; the real next address is FF85.



gid...@sasktel.net

unread,
Mar 18, 2016, 4:09:07 PM3/18/16
to
You would only have to worry about ReTurnS from the subroutines you create within your own program. All of ROM's major supported entry points automatically return to your calling program.
0 new messages