Jump to content
IGNORED

Gosub vs JSR - Basic vs Assembler


Recommended Posts

In Atari basic and turbobasic its practical to gosub variables , Like This example: 

 

10 X=100

 

50 Gosub X 

60 stop

100 print "Atari Rules":return

 

So the program vil gosub the line corresponding to the value stored in X, Practical to redirect program routines.

 

In assembler this should be possible ? but im doing something wrong.

 

Example using equates:

 

X=35000     ; x=adress 35000

 

JSR X   ; Well it jumps to adress 35000 but not to whats stored in X. maybe one have to use some low byte and high byte calculations

and the tax command perhaps ?

 

Im not exactly sure how to go about this, So any help would be useful..

 

 

 

 

 

 

 

Link to comment
Share on other sites

9 hours ago, Eyvind Bernhardsen said:

You have to use the indirect mode. I think it’s “JSR (X)”, but it’s been a long time since I wrote any 6502 assembly. 

Close, you'd ned to use a JSR to a JMP

CallFunc: .byte $4C ; JMP abs
FuncAddr: .word 0

...

 .macro SetFuncAddr addr
 LDA #<(:addr)
 STA FuncAddr
 LDA #>(:addr)
 STA FuncAddr+1
 .endm

...

ThisAndThat:
 SetFuncAddr DoThis
 JSR CallFunc
 SetFuncAddr DoThat
 JSR CallFunc
 JMP ThisAndThat

DoThis:
 LDA #0
 STA COLOR3
 RTS

DoThat:
 LDA #15
 STA COLOR3
 RTS

 

Edited by Wrathchild
(wrong JMP)
Link to comment
Share on other sites

Hi!

2 hours ago, Grevle said:

So the program vil gosub the line corresponding to the value stored in X, Practical to redirect program routines.

 

In assembler this should be possible ? but im doing something wrong.

First, from a theoretical point, if something is possible in BASIC, it is also possible in assembler, as Basic is written in assembler :) 

 

Now, the 6502 does not have a specific instruction to do that: call a subroutine to an address from a register or a memory location.

 

The standard solution to that in computer sciences is called a "trampoline": You write a subroutine that jumps to the correct location and you call this subroutine (the trampoline) instead.

 

Two possible trampolines are:

 

1.- Code that is "patched" at runtime with the address to jump to (this is how a trampoline is usually written in many computer architectures). Easiest is to reserve three instead of two location for the address:

 

    ; Call our fucntion via the trampoline
    lda #<procedure1
    sta target
    lda #>procedure1
    sta target+1
    jsr trampoline
    ; ........
    ; more code here....
    rts

trampoline:
     byte $4c   ; $4C is the "JMP" instruction
target:
     byte $00
     byte $00

The advantage of this solution is that if the address to call (the value of "X" in your example) does not change a lot, this is fast. On most current operating-systems this king of trampolines is used a lot to call to operating-system code (or code from other modules), as you don't know the address of the target until you load it in memory, so you create one trampoline for each function that you want to call.

 

And, if you store the entire trampoline (the 3 bytes) in zero page, the loading of the address is also faster and smaller.

 

2.- Using the 6502 "indirect jump" instruction. This instruction jumps to an address stored at a memory location, so it looks like:

    ; Call our fucntion via the trampoline
    lda #<procedure1
    sta target
    lda #>procedure1
    sta target+1
    jsr trampoline
    ; ........
    ; more code here....
    rts

trampoline:
     jmp (target)	; Indirect jump - go to the address stored in target and target+1
target:
     byte $00
     byte $00

As you see, here the trampoline is 2 bytes bigger, and also 2 cycles slower (as the 6502 needs to read two more bytes). If you store the target in zero-page location, then the code that set a new address is 2 bytes smaller and 2 cycles faster, but only if you set the target before each call.

 

So, this solution is only good if the target address is already set (for example, as part of the operating-system variables) as you don't need to load the address to the trampoline before using.

 

 

Have Fun!

 

Link to comment
Share on other sites

There is also an advantage of the 2nd (indirect) option as this can be used direct from ROM (cartridge) code where the 'target' is then stored in the RAM area and so can be modified. With the 1st version the trampoline JMP cannot be located in ROM as any store to the target address has no affect. Therefore those 3 bytes would instead have to be relocated to RAM and assembled with a different run address so that calls to it resolved to the relocated address.

Link to comment
Share on other sites

Another trampoline option is to push the target address minus one on the stack and then RTS.

 

Upside is it only takes stack RAM. Can also be smaller code-wise.

 

Downside is that your function pointers all need to be one less then the target address since RTS sets PC to the address on the stack, then adds one. Also a little slower.

 

See also:

 

https://wiki.nesdev.com/w/index.php/RTS_Trick

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...