shazz Posted April 21, 2017 Share Posted April 21, 2017 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.... Quote Link to comment Share on other sites More sharing options...
intvnut Posted April 21, 2017 Share Posted April 21, 2017 (edited) 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 April 21, 2017 by intvnut Quote Link to comment Share on other sites More sharing options...
intvnut Posted April 21, 2017 Share Posted April 21, 2017 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 Quote Link to comment Share on other sites More sharing options...
shazz Posted April 21, 2017 Author Share Posted April 21, 2017 (edited) 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 April 21, 2017 by shazz Quote Link to comment Share on other sites More sharing options...
shazz Posted April 21, 2017 Author Share Posted April 21, 2017 (edited) 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 April 21, 2017 by shazz Quote Link to comment Share on other sites More sharing options...
intvnut Posted April 21, 2017 Share Posted April 21, 2017 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 Quote Link to comment Share on other sites More sharing options...
shazz Posted April 21, 2017 Author Share Posted April 21, 2017 Yes, like that. It clears the screen, but nothing is written. If I remove the CLRSCR, it writes "THIS IS RED." Quote Link to comment Share on other sites More sharing options...
shazz Posted April 21, 2017 Author Share Posted April 21, 2017 Ok found.... I was using the beta4 and not the last stable dev. Sorry again. And thanks for the tips ! Quote Link to comment Share on other sites More sharing options...
+DZ-Jay Posted April 22, 2017 Share Posted April 22, 2017 (edited) 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 April 22, 2017 by DZ-Jay Quote Link to comment Share on other sites More sharing options...
shazz Posted April 22, 2017 Author Share Posted April 22, 2017 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.intellivision.us/index.php?title=Calling_Conventions) should be the first one to read in the tutorials section 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.