2

I'm quite new to 68k and I was wondering if it's possible to call a specific subroutine by values held in memory.

pseudo code example:

X: dc.w 0

routine1: 
code
rts

routine2:
more code
rts

and somewhere in the code something like:

move.w #2,X
JSR routine(X)

to have routine2 executed, ore move.w #1,X before for routine1

I have no idea and can't find any example, my guess is to make a label containing the routines then using an address register jump to the specific offset but don't know how.

Any help would be welcome!

KONEY
  • 73
  • 6
  • Instead of editing an answer into your question, you should either accept a given answer or provide your own answer (which eventually you can also accept). Also, your solution allows several optimizations ... – chtz Nov 24 '20 at 22:14

3 Answers3

1

You're looking for an indirect JSR that takes the target address in a register, after loading the register from an array of addresses. (Been a long time since I did any m68k, but those are the keywords and concepts you're looking for in the instruction set reference.) update: see @chtz's comment.

The lookup won't be by name, you'll have to use dc.l routine1, routine2 somewhere to make a table of 32-bit function pointers.

(Unless both/all routines are the same length and you calculate a jump target in a register like routine1 + <constant> * index, using some ALU instructions instead of indexing into an array in memory. An addressing-mode for JSR can be part of this calculation; e.g. jsr 4(a3) sets PC = A3+4).

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • OK so after loading the table address in ex. A3 I should be able to trigger routine2 with jsr 1(A3)? – KONEY Nov 10 '20 at 10:52
  • @KONEY: Remember that in assembly, everything uses byte offsets. So you'd want `jsr 4(A3)`, just like for indexing an array of 32-bit integers. (Because pointers *are* 32-bit integers). – Peter Cordes Nov 10 '20 at 10:57
  • 1
    `jsr 4(A3)` would call the function starting at address `A3+4`. You probably want `movea.l 4(A3),A3; jsr (A3);` If your jump-table is inside your code, and you have the index in `D0`, you can multiply `D0` by 4 using shifts and use PC-relative addressing to get the jump-address: `lsl.w #2,D0; movea.l table(PC,D0.w),A3; jsr (A3);` If your routines are in the same binary, you can even just store 16 bit offsets relative to some label. – chtz Nov 11 '20 at 16:07
  • @chtz: Thanks, interesting design choice to have a non-register operand for JSR do a calculated jump with the addressing mode instead of x86's choice of being a memory-indirect jump/call that loads the address from that memory location. Updated my answer a bit, feel free to edit yourself and put in your actual code suggestion. Or post a separate answer and I'd upvote it. – Peter Cordes Nov 11 '20 at 20:50
  • Yes, `jsr X` is essentially like a `lea X, PC` not a `move X, PC` (to me this looks more natural, but I learned 68k assembler long before learning x86, so I'm a bit biassed). The other way does certainly make sense as well, but probably did not fit into some other design decisions -- e.g., immediate values are not a valid effective address (which is why many other instructions have extra opcodes, like `andi`, `addi`, etc.) so another `jmp`/`jsr` opcode would have been needed to encode jumps to absolute addresses. – chtz Nov 12 '20 at 21:07
1

I'm not really sure what OP wants here. If you literally want:

move #2,X
jsr  "routine(X)"

just do

bsr routine2

If you want to decide at some part of the code whether to later call routine1 or routine2, I would load that address into an address register and call that whenever you need to (in most circumstances, you should not run short of address registers -- but you have to carefully keep track which register you use in what parts of your code)

; under some condition:
lea  routine1(PC),a4
; under another condition:
lea  routine2(PC),a4

; later:
jsr (a4)

If you have a variable (in memory or inside a register) and want to call one of two subroutines depending on its value, do some branching like:

  tst.w d0 ; lets assume for d0==0 we call routine1, otherwise routine2
  bne.s \callr2
  bsr   routine1
  bra.s \continue
\callr2:
  bsr   routine2
\continue:
  ; more code

If more code is just a rts, replace bne.s \callr2 by bne routine2 and bsr routine1 by bra routine1 (i.e., a tailcall).

Third alternative, if you have a range of values in d0 and you want to branch to a specific method depending on that value, that would be a jump-table, which can be implemented like this (assuming all routines are within a 16bit address range -- you also need to verify that d0 does not contain a value outside the size of your jumptable):

  add.w d0,d0                  ; multiply d0 by two
  move.w jumptable(PC,d0.w),d0 ; d0 contains the offset relative to `jumptable` 
  jsr    jumptable(PC,d0.w)    ; do the actual function call
  ; more code -- if this is just a `rts` use `jmp` instead of `jsr`

  ; somewhere else:
jumptable:
  dc.w  routine0-jumptable, routine1-jumptable, routine2-jumptable, ...

If additionally, all routines are exactly the same size (ideally a power of two -- maybe after some padding, or using some trampolines if necessary), you could also directly jump to something like PC+offset+d0*size_of_method:

lsl.w  #4,d0             ; d0 = 16*d0
jsr    routine0(PC,d0.w) ; PC = routine0+d0
; more code

routine0:
   ; exactly 16 bytes of code
routine1:
   ; exactly 16 bytes of code
routine2:
   ; exactly 16 bytes of code
routine3:
   ; (last method could be arbitrary long)
chtz
  • 17,329
  • 4
  • 26
  • 56
0

So this is how I ended up doing this, combining Peter Corder solution with some external suggestions:

  TIMELINE:       DC.L __BLOCK_0,__BLOCK_1,__BLOCK_1
    DC.L __BLOCK_2,__BLOCK_2
    DC.L __BLOCK_2,__BLOCK_3 ... etc


 __BLOCK_0:
   ; SOME CODE
   RTS

-- in mainloop --

MOVE.W  P61_LAST_POS,D5
LEA TIMELINE,A3
MULU.W  #4,D5     ; OFFSET IN BYTES
MOVE.L  (A3,D5),A4
JSR (A4)        ; EXECUTE SUBROUTINE BLOCK#

where P61_LAST_POS being and incremental index which changes over periods of time.

This way I have a control of what is executed at any given point by just editing the LUT "TIMELINE".

KONEY
  • 73
  • 6