Jump to content

Photo

Assembly, JSR to address ?

assembly

9 replies to this topic

#1 shazz OFFLINE  

shazz

    Star Raider

  • 66 posts

Posted Fri Apr 21, 2017 10:42 AM

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....

 



#2 intvnut OFFLINE  

intvnut

    River Patroller

  • 2,739 posts
  • Location:@R6 (top of stack)

Posted Fri Apr 21, 2017 10:56 AM

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, Fri Apr 21, 2017 10:56 AM.


#3 intvnut OFFLINE  

intvnut

    River Patroller

  • 2,739 posts
  • Location:@R6 (top of stack)

Posted Fri Apr 21, 2017 11:02 AM

Also, if you're trying to save cycles, the cheapest cycle option for your particular loop is:

.

@@loop      CALL    WAITVBL               ; wait for VBlank
            
            ; jump to the current main routine
            MVII    #@@loop, R5           ; return to @@loop
            MVI     SUBROUT, PC           ; call the routine


#4 shazz OFFLINE  

shazz

    Star Raider

  • Topic Starter
  • 66 posts

Posted Fri Apr 21, 2017 11:12 AM

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, Fri Apr 21, 2017 11:21 AM.


#5 shazz OFFLINE  

shazz

    Star Raider

  • Topic Starter
  • 66 posts

Posted Fri Apr 21, 2017 11:24 AM

Ok..

 

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

 
            PSHR    R5                                  ; save R5 : return address
...
            PULR    PC

 

Sorry :)

 

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


Edited by shazz, Fri Apr 21, 2017 11:34 AM.


#6 intvnut OFFLINE  

intvnut

    River Patroller

  • 2,739 posts
  • Location:@R6 (top of stack)

Posted Fri Apr 21, 2017 11:41 AM

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


#7 shazz OFFLINE  

shazz

    Star Raider

  • Topic Starter
  • 66 posts

Posted Fri Apr 21, 2017 11:44 AM

Yes, like that.

 

It clears the screen, but nothing is written.

 

If I remove the CLRSCR, it writes "THIS IS RED."



#8 shazz OFFLINE  

shazz

    Star Raider

  • Topic Starter
  • 66 posts

Posted Fri Apr 21, 2017 11:50 AM

Ok found.... I was using the beta4 and not the last stable dev.

 

Sorry again.

And thanks for the tips !



#9 DZ-Jay OFFLINE  

DZ-Jay

    Quadrunner

  • 9,760 posts
  • Triple-Stripe Mo' Bro
  • Location:NC, USA

Posted Yesterday, 7:54 AM

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, Yesterday, 3:51 PM.


#10 shazz OFFLINE  

shazz

    Star Raider

  • Topic Starter
  • 66 posts

Posted Yesterday, 12:26 PM

Thanks dZ, I did not know the BEGIN/RETURN aliases.

 

Yup yup, it fully makes sense. I just miss the good old MOVEM.L instruction :)

 

I guess this page (http://wiki.intelliv...ing_Conventions) should be the first one to read in the tutorials section :)







Also tagged with one or more of these keywords: assembly

0 user(s) are browsing this forum

0 members, 0 guests, 0 anonymous users