Jump to content
IGNORED

Assembly, JSR to address ?


shazz

Recommended Posts

Hi,

 

I think I'm doing something dumb but... I cannot see it.

 

I have the address of a sub routine in a variable (System Ram location).

I can branch on this address using

 

@@loop      CALL    WAITVBL               ; wait for VBlank
            
            ; jump to the current main routine
            MVI     SUBROUT, R1
            JR      R1
            B       @@loop                ; loop forever

But in this case the return address is not set in R5 so within my sub routine, JR R5 won't come back to the @@loop

So currently my sub routine ends with B @@loop but that's ugly.

 

Is there a way to do something JSR R1 ?

 

Note, as SUBROUT points to different subroutines along the time, I cannot do B @@ASUBROUT.

 

Thanks....

 

Link to comment
Share on other sites

Unfortunately, there isn't a "CALL Rx" similar instruction. There's also no "CALL direct".

 

In the past I've used a couple different approaches, but the easiest option is to use some macros:

.

;; ------------------------------------------------------------------------ ;;
;;  PCALL   Call through a function pointer in RAM                          ;;
;; ------------------------------------------------------------------------ ;;
MACRO   PCALL   p
        MVII    #$ + 4,     R5
        MVI     %p%,        PC
ENDM

;; ------------------------------------------------------------------------ ;;
;;  RCALL   Call through a function pointer in a register (Rx != R5)        ;;
;; ------------------------------------------------------------------------ ;;
MACRO   RCALL   p
        MVII    #$ + 3,     R5
        MOVR    %r%,        PC
ENDM 

.

This creates two new "instructions":

 

PCALL will call through a pointer stored in memory. For your example, you could then just write PCALL SUBROUT, and that would do exactly what you need, since your pointer is already in a memory location. You don't need to move it into a register first, since the machine can directly read the value into the program counter.

 

RCALL will call through a pointer stored in a register. RCALL R1 will call the function whose address is in R1. The instruction sequence MVI SUBROUT, R1; RCALL R1 will also call your subroutine.

 

 

I've also tried other tricks, such as a JSR to a MOVR instruction, but those aren't as fast, and they're not much smaller. Example:

.

@@loop      CALL    WAITVBL               ; wait for VBlank
            
            ; jump to the current main routine
            CALL    @@gosub
            B       @@loop                ; loop forever

@@gosub:    MVI     SUBROUT, PC

.

That works, but it ends up being slower than the macro, and not actually smaller.

Edited by intvnut
Link to comment
Share on other sites

ok, it was not so dumb after all...

 

So I tried the PCALL and I have the error : Invalid opcode. I added the macro in a file (branch.mac) and added as an include at the end

 

@@loop      CALL    WAITVBL               ; wait for VBlank
            
            ; jump to the current main routine
            PCALL     SUBROUT
            B       @@loop                ; loop forever

EDIT: Ok I have added them to the beginning, and replaced p by r. It assembles but never goes back to the loop.

 

 

And I tried the "optimized version"

@@loop CALL WAITVBL ; wait for VBlank

; jump to the current main routine
MVII #@@loop, R5 ; return to @@loop
MVI SUBROUT, PC ; call the routine

but it never goes back to the loop, SUBROUT contains the address of RED_MAIN:

RED_MAIN    PROC 


            ; CALL    CLRSCR    ; commented, creates bugs          


            CALL    PRINT.FLS          
            DECLE   7
            DECLE   $200 + 3*20 + 1
            DECLE   "THIS IS RED...", 0


            JR      R5
            ;B    MAIN.loop
            ENDP
is it due to the CALL PRINT which changes the R5 ?
Edited by shazz
Link to comment
Share on other sites

Any idea why after a call to CLRSCR I cannot print anything ?

 

Not sure, really, as that should work.

 

Does it start working now that you're saving/restoring R5 in your function? ie. this should work:

.

RED_MAIN  PROC
          PSHR    R5

          CALL    CLRSCR

          CALL    PRINT.FLS
          DECLE   7
          DECLE   $200 + 3*20 + 1
          DECLE   "THIS IS RED...", 0

          PULR    PC
          ENDP
Link to comment
Share on other sites

Ok..

 

If I save the R5 register into my sub routine, it works now

 

Well, yeah, if you clobber the return address by making another subroutine CALL within your subroutine, it'll never know where to return. ;)

 

The typical pattern is:

MYSUB  PROC
       BEGIN  ; Save return address

       ; ... code here

       RETURN ; Return to caller
       ENDP

Where "BEGIN" and "RETURN" are just pseudo-instruction for "PSHR" and "PULR" on R5, respectively (in the same way that CALL is a pseudo-instruction for "JSR R5, XXX."

 

Forgoing the BEGIN/RETURN for the sake of cycle-efficiency should be an exception only prompted by late-stage optimizations, when you can guarantee that the return address will remain intact.

 

An alternative pattern I use is to store the return address in a "less volatile" register, say R2 (which I typically reserve for numeric arguments to functions, and thus is free most of the time):

MYSUB PROC
      MOVR R5, R2  ; Save return address

      ; code here...

      MOVR R2, PC  ; Return to caller
      ENDP

This works because MOVR is cheaper than PSHR/PULR, the latter having to go all the way to and from RAM. Again, just as long as you guarantee that the target subroutine does not clobber your reserved register.

 

Establishing such patterns and conventions pays dividends in the end. For instance, one of my conventions is to always document in comments the INPUT, OUTPUT, and USED registers of a subroutine. That way, I can see at a glance which register is left untouched for me to co-op in the calling routine to store my return address or other state. :)

 

For example:

;; ======================================================================== ;;
;;  QUEUE.CLR                                                               ;;
;;  Procedure to empty a task queue.                                        ;;
;;                                                                          ;;
;;  NOTE:   This routine is expected to be called from within a critical    ;;
;;          section.  It does not disable interrupts on entry, nor          ;;
;;          re-enables them upon return, so make sure you do so on your own.;;
;;          You can achieve this by calling it with the DCALL directive.    ;;
;;                                                                          ;;
;;  There are two entry points to this procedure:                           ;;
;;      QUEUE.CLR               Receives argument in input record.          ;;
;;                                                                          ;;
;;      QUEUE.CLR.1             Receives argument in register.              ;;
;;                                                                          ;;
;;  INPUT for QUEUE.CLR                                                     ;;
;;      R5      Pointer to invocation record, followed by return address.   ;;
;;              Pointer to Queue Record.                1 DECLE             ;;
;;                                                                          ;;
;;  INPUT for QUEUE.CLR.1                                                   ;;
;;      R5      Pointer to return address.                                  ;;
;;      R4      Pointer to Queue Record.                                    ;;
;;                                                                          ;;
;;  OUTPUT                                                                  ;;
;;      R0      Zero.                                                       ;;
;;      R4      Trashed.                                                    ;;
;; ======================================================================== ;;
 

The flip-side is, of course, that any changes to the interface of a sub-routine must include an update to the documentation as well as a careful understanding of its impact on existing calling routines.

 

However, I would say you would need that anyway whenever you change code. Documenting it is just common sense. ;)

 

I hope this proves useful.

 

-dZ.

Edited by DZ-Jay
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...