Jump to content
IGNORED

Implement a call stack by piling workspaces


JCCyC

Recommended Posts

Probably lots of people had this idea already, and I've been reading about TMS9900 assembly only sporadically. Let CALLEE be the address of a subroutine like this:

CALLEE  (instruction)
        (instruction)
        (instruction)
        ...
        RTWP

And my idea for a "reentrant call":

        STWP R8
        S 36,R8
        MOV R8, @32(R8)
        MOV CALLEE, @34(R8)
        BLWP @32(R8)

Could be turned into a macro, and the trashed register cound be any one that doesn't have a special use (R12-R15). I did this on paper only, so it may have a few holes. Also, this is NOT for hardware interrupts! And, of course, for machines with a respectable amount of RAM -- definitely not a base 99/4A!

 

So, makes sense? Is it horribly slow and wasteful and a much better method exists?

 

EDIT: No, wait, wait. I don't need that additional space for the transfer vector. It can be located in the middle of the new workspace.

        STWP R8
        S 32,R8
        MOV R8, @8(R8)
        MOV CALLEE, @10(R8)
        BLWP @8(R8)

EDIT II, THE SEQUEL: And if I didn't mind trashing the first few registers of my current WP I could even make them overlap!

 

What do you think?

Edited by JCCyC
Link to comment
Share on other sites

You could stack all your regs by simply adding 32 to the ws vector before issuing a blwp and then subtracting 32 after the return. At least that's how it looks from here. Maybe that's what your thinking?

 

Yep. Alas, there seems to be no easy way to load the WP with a calculated value other than BLWP, and for that I have to make up a memory structure with the new WP and the new PC.

 

And now that you mention it, increase-on-push and decrease-on-push stacks are equally doable -- but with the decrease-on-push stack you can do the overlap trick without trashing critical registers.

Link to comment
Share on other sites

 

Alas, there seems to be no easy way to load the WP with a calculated value other than BLWP, and for that I have to make up a memory structure with the new WP and the new PC.

 

If you store the calculated value in the word after a LWPI instruction, and then execute that instruction, I think that would do it?

  • Like 1
Link to comment
Share on other sites

yep, self-modifying code can be very useful, as long as you are not using a cpu running 99105 or 68689 with pre-fetch pipleline then it could fail. :)

 

I'll say! I got epically burned by that when developing a DOS device driver in 1990! Everything worked OK in PCXTs and crashed badly in PCATs. And when we debugged step-by-step... ta-da, worked again! 16-byte prefetch queue. (8088 has only four.)

 

To be fair, the LWPI idea could avoid that by constructing it somewhere other than the next instructions and reaching it with a branch/jump.

  • Like 1
Link to comment
Share on other sites

I always use a method I learned from mathew180's assembly tutorials. R10 is designated to be stack pointer. I always use BL instead of BLWP for my own routines.

      LI   R10,STACK        * Initialize stack pointer (once)
      ...
      BL   @MYSUB           * Call subroutine
      ...
MYSUB MOV  R11,*R10+        * Push return address onto the stack
      ...
*     Return
      DECT R10              * Pop return address off the stack
      MOV  *R10,R11
      B	   *R11
Link to comment
Share on other sites

I always use a method I learned from mathew180's assembly tutorials. R10 is designated to be stack pointer. I always use BL instead of BLWP for my own routines.

I've always used R13 as the stack pointer, since I never use BLWP for my own routines, that means R13-R15 are freed up for other uses. Saves R10 for another general purpose register.
Link to comment
Share on other sites

RXB uses a replacement for CALL LINK that is 5 times faster and very small foot print.

Mostly as it does no checking or pushing and popping onto the VDP Stack for saving stuff.

Instead the Registers are preloaded before the CALL EXECUTE

*******************************************************
* CPU PROGRAM FOR >8300 SCATCH PAD SUBROUTINE EXECUTE *
*******************************************************
*                          AORG >8300
CPUPGM DATA >8302 * CPUPGM DATA >8302  First address. *
       DATA >0420 *        BLWP >834A  Switch contex  *
       DATA >834A *                    FAC not used   *
       DATA >04E0 *        CLR  @>837C Clear for GPL  *
       DATA >837C *                                   *
       DATA >045B *        RT          Return to GPL. *
                  *        END                        *
*******************************************************
EXECUT CALL SPNUM1
EXAGN  CALL SUBLP3
       MOVE 12,@VAR0,V@VROAZ   Save CPU values
       MOVE 12,G@CPUPGM,@VAR0  Load PGM
       DST  @FAC,@VARY         Load address    
       XML  >F0                Execute address 
       MOVE 12,V@VROAZ,@VAR0   Restore CPU values
       CEQ  >B3,@CHAT          Comma?
       BS   EXAGN              Repeat
       BR   LNKRTN

It does save the 12 bytes at >8300 before the CALL EXECUTE(cpu-address) in VDP and FAC has the address to execute assembly.

So it restores the 12 bytes at >8300 and returns to XB, unless there is another address like you could do this:

 

CALL EXECUTE(cpu-address,cpu-address,cpu-address)

 

The cool thing in all the Registers are preloaded and this is extremely faster then CALL LINK in normal XB.

Link to comment
Share on other sites

 

I'll say! I got epically burned by that when developing a DOS device driver in 1990! Everything worked OK in PCXTs and crashed badly in PCATs. And when we debugged step-by-step... ta-da, worked again! 16-byte prefetch queue. (8088 has only four.)

 

To be fair, the LWPI idea could avoid that by constructing it somewhere other than the next instructions and reaching it with a branch/jump.

 

I construct most of my non-recursive routines in this manner. Matthew explains it in a later post in the same thread.

 

 

http://atariage.com/forums/topic/162941-assembly-on-the-994a/page-3?do=findComment&comment=2021522

Link to comment
Share on other sites

RXB uses a replacement for CALL LINK that is 5 times faster and very small foot print.

Mostly as it does no checking or pushing and popping onto the VDP Stack for saving stuff.

Instead the Registers are preloaded before the CALL EXECUTE


I've been doing some speed tests on CALL EXECUTE compared to CALL LINK to find out how much time it takes to start up an assembly program.

The CALL EXECUTE assembly program is simply RTWP to return to XB.

For CALL LINK the program is:

LWPI WKSP

LWPI >83E0

B @>006A

So these don't really do anything other than to test how long it takes to get into an assembly subroutine and then return to XB. After determining how long a loop takes (10 N=N+1::CALL LINK("FILL")::GOTO 10) and then subtracting the time taken by the N=N+1 and GOTO I found:

CALL EXECUTE takes 14.04ms to get into the A/L sub and return to XB

CALL LINK takes 15.44 ms if the subprogram is 1st in the list

CALL LINK takes 37.54 ms if the subprogram is 257th on the list. (Most packages such as TML or XB256 have 30 to 40 subroutines - if the routine is the 40th in the DEF table then it would take about 18.88 ms)

If you do the math you find that it takes about .086 ms to read each entry in the DEF table

If you are not passing variables then CALL EXECUTE is somewhat faster.

 

One thing to take away from this is that when you are setting up your DEF table, the last entry in the A/L code is the first to be checked by CALL LINK, so the subroutines where speed is really important should have their entries among the last in the DEF table.

 

The speed situation changes when you are passing variables to the A/L subprogram. Unless I'm missing something, the only way to pass values to CALL EXECUTE is using CALL LOAD to preload the registers with the desired values. (Rich, correct me if I am wrong on this) So for example, if my workspace is at 10000 and I want 123 in R3 then I have to:

10 CALL LOAD(10006,0,123)::CALL EXECUTE(address)

That CALL LOAD negates any speed advantage for CALL EXECUTE. According to my tests:

a CALL LINK that fetches 1 value to a register from XB using NUMREF and CFI takes 24.04 ms

a CALL EXECUTE that preloads 1 register with CALL LOAD takes 32.84ms

a CALL LINK that fetches 8 values to registers from XB using NUMREF and CFI takes 83.74 ms

a CALL EXECUTE that preloads 8 registers with CALL LOAD takes 95.64 ms

  • Like 1
Link to comment
Share on other sites

I've been doing some speed tests on CALL EXECUTE compared to CALL LINK to find out how much time it takes to start up an assembly program.

The CALL EXECUTE assembly program is simply RTWP to return to XB.

For CALL LINK the program is:

LWPI WKSP

LWPI >83E0

B @>006A

So these don't really do anything other than to test how long it takes to get into an assembly subroutine and then return to XB. After determining how long a loop takes (10 N=N+1::CALL LINK("FILL")::GOTO 10) and then subtracting the time taken by the N=N+1 and GOTO I found:

CALL EXECUTE takes 14.04ms to get into the A/L sub and return to XB

CALL LINK takes 15.44 ms if the subprogram is 1st in the list

CALL LINK takes 37.54 ms if the subprogram is 257th on the list. (Most packages such as TML or XB256 have 30 to 40 subroutines - if the routine is the 40th in the DEF table then it would take about 18.88 ms)

If you do the math you find that it takes about .086 ms to read each entry in the DEF table

If you are not passing variables then CALL EXECUTE is somewhat faster.

 

One thing to take away from this is that when you are setting up your DEF table, the last entry in the A/L code is the first to be checked by CALL LINK, so the subroutines where speed is really important should have their entries among the last in the DEF table.

 

The speed situation changes when you are passing variables to the A/L subprogram. Unless I'm missing something, the only way to pass values to CALL EXECUTE is using CALL LOAD to preload the registers with the desired values. (Rich, correct me if I am wrong on this) So for example, if my workspace is at 10000 and I want 123 in R3 then I have to:

10 CALL LOAD(10006,0,123)::CALL EXECUTE(address)

That CALL LOAD negates any speed advantage for CALL EXECUTE. According to my tests:

a CALL LINK that fetches 1 value to a register from XB using NUMREF and CFI takes 24.04 ms

a CALL EXECUTE that preloads 1 register with CALL LOAD takes 32.84ms

a CALL LINK that fetches 8 values to registers from XB using NUMREF and CFI takes 83.74 ms

a CALL EXECUTE that preloads 8 registers with CALL LOAD takes 95.64 ms

The idea behind CALL EXECUTE is you only have to pass values once.

To make a point this is the CALL LINK in XB:

***********************************************************
* LINK INSTRUCTION : SE Sep 1980
***********************************************************
*  FORMAT:
*  CALL LINK("file-name",parameter1,parameter2,...)
*
*  LINK ROUTINE READS THE FILE NAME SPECIFIED BY THE USER A
*  SAVE THE ADDRESS OF THE NAME FOR LATER USE. THE FILE WIL
*  BE SEARCHED IN UTILITY CODE LATER ON.
*
*  PARAMETERS ARE PASSED EITHER BY REFERENCE OR BY VALUE.
*  NUMERIC OR STRING VARIABLES AND NUMERIC OR STRING ARRAYS
*  ARE PASSED BY REFERENCE AND ALL OTHERS INCLUDING A USER
*  DEFINED FUNCTION ARE PASSED BY VALUE.
*
*  PARAMETER INFORMATION IS STORED IN CPU >8300 THROUGH >83
*  THAT GIVES A PARAMETER TYPE CODE OF EACH PARAMETER.
*        CODE 0 ... Numeric expression
*        CODE 1 ... String experession
*        CODE 2 ... Numeric variable
*        CODE 3 ... String variable
*        CODE 4 ... Numeric array
*        CODE 5 ... String array
*
*  IF A PARAMETER IS PASSED AS A NUMERIC EXPRESSION ITSL
*  ACTUAL VALUE GETS PUSHED INTO THE VALUE STACK. IN CASE O
*  A STRING EXPRESSION , ITS VALUE STACK CONTAINS AN ID(>65
*  POINTER TO THE VALUE SPACE AND ITS LENGTH. IF A PARAMETE
*  GETS PASSED AS A REFERENCE THE PRODUCT OF XML SYM AND XM
*  SMB IN THE @FAC AREA GETS PUSHED INTO STACK.
*
*  AFTER AN ASSEMBLY LANGUAGE SUBPROGRAM IS EXECUTED LINK
*  ROUTINE WILL POP THE STACK TO GET RID OF PARAMETER
*  INFORMATION. CONTROL WILL BE TRANSFERED TO THE XB MAIN
*  PROGRAM AFTERWARDS.
*
***********************************************************
* CALL LINK program
***********************************************************
LINKIT CALL CHKIN             Check if INIT has been called
       DST  @VSPTR,@OLDS      Save VSPTR for later use
       CEQ  LPARZ,@CHAT       Check for "("
       BR   ERRSYN
       XML  PGMCHR            Advance program pointer
       XML  PARSE             Get the routine name.
       BYTE RPARZ           * Read up to ")"
       CEQ  >65,@FAC2         Should be a string
       BR   ERRBA
       DCZ  @FAC6             Don't accept null string
       BS   ERRBA
       CH   6,@FAC7           Should be less then 6 char
       BS   ERRBA
       XML  VPUSH             Push to make it semi-permanen
       CLR  @COUNT            Initialize parameter counter
***********************************************************
* PARAMETERS get evaluated here
***********************************************************
PAR01  CEQ  RPARZ,@CHAT       No arg. So execute it
       BS   EXE01
       CEQ  COMMAZ,@CHAT      Should have a comma
       BR   ERRSYN
       DST  @PGMPTR,@ERRCOD   Save text pointer
       XML  PGMCHR            Get the character
       CHE  >80,@CHAT         Must be an expression
       BS   VAL01
* If CHAT = LPARZ then pass by expression
       CALL CLRFAC            Clear FAC entry for SYM
       XML  SYM               Read in the symbol table info
* After XML SYM @FAC area contains a pointer to symbo table
* Below statement checks if it is a UDF.
       CLOG >40,V*FAC         Pass by value
       BR   VAL01
       CEQ  COMMAZ,@CHAT      Pass by reference
       BS   REF01
       CEQ  RPARZ,@CHAT       Pass by reference
       BS   REF01
       CEQ  LPARZ,@CHAT       An array
       BS   ARRAY
       CHE  >80,@CHAT         Pass by value
       BS   VAL01
       BR   ERRSYN
***********************************************************
* ARRAY case gets checked here
***********************************************************
* Should look like A(,,) etc.
* Stack entry for an array will look like
* +--------------+-------+---+-------------+---------------
* | Pointer to   |  >00  |   | Pointer to  |
* | symbol table |   or  |   | dim info in |
* | entry        |  >65  |   | real v.s.   |
* +- FAC --------+ FAC2 -+---+- FAC4 ------+- FAC6 --------
*
ARRAY  XML  PGMCHR            Get the next character
       CEQ  RPARZ,@CHAT       Pass by reference
       BS   ARRAY2
       CEQ  COMMAZ,@CHAT      More array information
       BS   ARRAY
       DDEC @PGMPTR           Adjust the pointer
       ST   LPARZ,@CHAT
       BR   REF01             Pass by reference
* In array cases the symbol table address gets stored at FA
* area, and the pointer to the value space (dimension info)
* goes into FAC4
ARRAY2 XML  PGMCHR            Advance the program pointer
       CLOG >80,V*FAC         Test string bit
       BR   GC39D
       ST   4,*COUNT          Numeric array
       BR   GC3A1
GC39D  ST   5,*COUNT          String array case
* Check if array is being shared. If it is then go back
* through the linkage to get the actuals symbol table
* pointer. Put the pointer to the value space (dimension in
* into FAC4.
GC3A1  CLOG >20,V*FAC         Shared array?
       BS   GC3BE
       MOVE 2,V@6(@FAC),@FAC4 If so, get pointer
       CLOG >20,V@-6(@FAC4)   Shared also?
       BS   GC3BC
       MOVE 2,V*FAC4,@FAC4    Array is not shared
GC3BC  BR   GC3C5
GC3BE  DST  @FAC,@FAC4        Array is not shared
       DADD 6,@FAC4           Point to value space
GC3C5  BR   PUSH
***********************************************************
* VALUE
*  Passing the parameter by value
***********************************************************
VAL01  DST  @ERRCOD,@PGMPTR   Restore program pointer
       XML  PGMCHR            Skip the first character
       DST  @BYTES,@TEMP      In case of passing a string
       XML  PARSE             Parsing up to comma
       BYTE RPARZ
       DST  @TEMP,@BYTES      Restore the value in >0C area
* After parsing @FAC area contains its actual numeric value
*  in a numeric case, and the following information in a
*  string case.
* +----------------+-----+--+------------+-----------------
* | >001C  or      | >65 |  | Pointer to | Length of string
* | value pointer  |     |  | string     | string
* | address        |     |  |            |
* +- FAC ----------+-FAC2+--+-FAC4 ------+- FAC6 ----------
*
       CGT  >63,@FAC2         If more then 99 then
       BR   GC3E0
       ST   1,*COUNT          Store flag for string express
       BR   GC3E3
GC3E0  CLR  *COUNT            Otherwise it is a numeric exp
GC3E3  BR   PUSH              Push into stack
***********************************************************
* REFERENCE
*   Passing the parameter by reference
***********************************************************
* Variables, array element and whole array passing.
*
* After SMB @FAC entry shold look like;
* +--------------+------+-----+-------------+--------------
* | Pointer to   | >00  |     | Pointer to  |
* | symbol table |      |     | value space |
* | entry        |      |     |             |
* +-- FAC -------+ FAC2 +-----+- FAC4 ------+- FAC6 -------
*  for numeric case, and
* +--------------+------+-----+-------------+--------------
* | Pointer to   | >65  |     | Pointer to  | String
* | value space  |      |     | string      | length
* | entry        |      |     |             |
* +- FAC --------+ FAC2 +-----+- FAC4 ------+- FAC6 -------
* for a string case.
REF01  XML  SMB               Get the location
       CHE  >B8,@CHAT         Pass array expression
       BS   VAL01
       CZ   @FAC2
       BR   GC3F6
       ST   2,*COUNT          Must be a numeric variable
       BR   PUSH
GC3F6  ST   3,*COUNT          Must be a string variable
***********************************************************
* PUSH routine
*  Pushes @FAC entry into a value stack.
***********************************************************
PUSH   INC  @COUNT
       CGT  16,@COUNT         Too many parameters
       BS   ERRBA
       XML  VPUSH
       BR   PAR01             Get the next argument.
***********************************************************
* EXECUTE routine
*  Restore file name info transfer control over to ALC
***********************************************************
EXE01  ST   >20,@FAC          Store blank in the FAC area.
       MOVE 5,@FAC,@FAC1
       MOVE 4,V@12(@OLDS),@STORE   Get the file name info
       MOVE @STORE+2,V*STORE,@FAC  Move to FAC
       DCLR @ERRCOD           Clear program pointer for
*                              error code
       XML  ALSUP             Go to CPU at >2000 to execute
       BS   ERROR             Error found
*                             If no error, start checking s
***********************************************************
* RETURN to the XB main program.
***********************************************************
NOERR  DCH  @OLDS,@VSPTR      Pop the stack
       BR   GC429
       XML  VPOP              Pop the stack
       B    NOERR
GC429  B    LNKRTN            Check ")" and end of statemen
***********************************************************
* SUBROUTINES used in this file.
***********************************************************
CLRFAC CLR  @FAC
       MOVE 7,@FAC,@FAC1
       RTN
**********************************************************

As you can see it has tons of housekeeping and does lots of PUSH and POP onto the stack. (CALL EXECUTE has zero housekeeping.)

The more CALL LINKs the less memory you have available for Assembly and the slowing it gets. (Your tests agree with me on this.)

If you need to push values more often with CALL EXECUTE then pass the address of that variable in memory to Assembly,

now with CALL LINK you can do this but you do cut the amount of RAM for Assembly and pay a price of slowing execution. (More LINKs and more variable passing.)

 

I would think once you get used to using Assembly like my game IN THE DARK uses CALL EXECUTE instead of CALL LINK. (Most of the variables are all kept in Assembly lower 8K)

I could have preloaded the locations of the variable into Registers and that would be a space saver. Thus when Assembly changes variables no pass of values is needed. (CALL LOAD/PEEK)

 

But overall thanks for the tests, and keep up the good work.

Edited by RXB
Link to comment
Share on other sites

It looks like you are using BLWP to choose from a bunch of workspaces, each of which contains unique numbers useful to the subroutine, and the numbers were created at the same time as the assembly program. So you're using canned numbers and not actually passing variables to the subroutine. For what it's worth, this can be done the same way in CALL LINK by having the 1st instruction be LWPI >xxxx. This is a bit bulkier ( by 8 bytes) and a bit slower than CALL EXECUTE.

 

 

If you wanted to write an assembly equivalent for, say, CALL HCHAR then the XB program needs to calculate the values for row, column, ascii, and # repeats. These would need to be passed to the program and with CALL EXECUTE you'd need to use CALL LOAD to get the values into the registers before CALL EXECUTE. In this case, CALL LINK will be faster then CALL EXECUTE.

 

 

All this got me thinking about an improvement to CALL LINK that runs at the same speed regardless of how many subroutines there are. You could create a jump table ending at >4000. That's essentially what the DEF table is, but you wouldn't use the subroutine name; just the addresses of the subroutines. At >2004 put a pointer to the start of the jump table. Then you would need to modify the CALL LINK subroutine lookup routine which starts at >205A. The code below is one way to do it. This expects 2 digits giving a number from "00" to "99"

 

CLINK MOV @>834A,R0 put the two byte long name in R0
AI R0,>CFD0 subtract >30 from each byte, 1st byte has tens digit; 2nd byte has ones digit
MOV R0,R2
SWPB R2 ones digit is now in MSB
SRL R2,7 multiply ones digit by 2
SRL R0,8 tens digit is now in LSB
LI R1,20
MPY R1,R0 multiply tens digit by 20
A R1,R2 now R2 has offset in jump table
A @>2004,R2 add jump table offset
MOV *R2,R2 get address and put into R2
B *R2 run subroutine

AORG >2000 change pointer to routine
DATA CLINK

 

You would CALL LINK("23",optional varialbles) to go to the 23rd subprogram in the jump table. This expects 2 numeric characters in the program name for CALL LINK, so subprograms from 00 to 99 could be accessed. (If you used A to Z you could have 676 subprograms) With this method, there is no speed penalty if there are a lot of subroutines in the list, but you still have the option of passing variables from or to XB as usual in CALL LINK. This would be only 10% slower than CALL EXECUTE but with the ability to pass variables easily. Also, remember that the assembly subroutines take some time, so the actual amount of slowdown would be less.

Edited by senior_falcon
Link to comment
Share on other sites

The entire main loop in my game IN THE DARK has this Main Loop:

160 ! MAIN LOOP
170 CALL SOUND(50,-6,15)
180 U=W+351+C :: CALL LOAD(U,225)! FOOTSTEPS
190 CALL LOCATE(#1,90,C*8-7) :: CALL MOVES("RV",768,W,0)! SHOW ME
200 ! ARROWS OR L,R,D,U,SPACE,ENTER,FCTN7,P,p
210 CALL ONKEY(Z$,0,K,Z)GOTO 370,370,370,410,410,410,450,450,450,510,510,510,580,690,810,1590,1590
220 ! ENEMY MOVEMENTS
230 IF F+T<9 THEN 210
240 CALL PEEK(-12276,G) :: IF G=218 THEN E=I*5 :: CALL LOAD(-12276,0)
250 IF M<>B THEN 280
260 IF E=0 THEN 270 ELSE E=E-1 :: GOTO 210
270 IF U-X<256 AND M=B THEN CALL SOUND(-1,2500,2)
280 CALL AMSBANK(M,M+1) :: CALL EXECUTE(-12256)
290 CALL PEEK(-12286,G,H) :: X=(G*256)+H :: IF U=X AND B=M THEN 750! MISSION FAILED
300 IF(U<X AND M>B)OR(X<U AND B>M)THEN M=B :: CALL LOAD(-12286,33,129)
310 IF X<8227 AND M=0 THEN 360
320 IF X>16348 AND M=42 THEN 360
330 IF X<8227 THEN M=M-1 :: X=X+4096
340 IF X>16348 THEN M=M+1 :: X=X-4096 ELSE 360
350 N=INT(X/256) :: O=X-(N*256) :: CALL AMSBANK(M,M+1) :: CALL LOAD(-12286,N,O)
360 CALL AMSBANK(B,B+1) :: GOTO 190
370 ! MOVE LEFT
380 CALL GCHAR(12,C-1,G) :: IF G=143 OR G=119 OR G=111 OR G=103 THEN 640
390 IF G=122 THEN 760
400 C=C-1 :: GOTO 160
410 ! MOVE RIGHT
420 CALL GCHAR(12,C+1,G) :: IF G=143 OR G=119 OR G=111 OR G=103 THEN 640
430 IF G=122 THEN 760
440 C=C+1 :: GOTO 160
450 ! MOVE DOWN
460 IF W=15488 AND B=42 THEN 1020
470 CALL GCHAR(13,C,G) :: IF G=143 OR G=119 OR G=111 OR G=103 THEN 640
480 IF G=122 THEN 760
490 IF W>=15615 THEN B=B+1 :: W=W-4096 :: GOTO 570
500 W=W+32 :: GOTO 160
510 ! MOVE UP
520 IF W=8192 AND B=0 THEN CALL HCHAR(1,1,119,480) :: CALL MOVES("VR",768,0,W) :: GOTO 640
530 CALL GCHAR(11,C,G) :: IF G=143 OR G=119 OR G=111 OR G=103 THEN 640
540 IF G=122 THEN 760
550 IF W=8192 THEN B=B-1 :: W=W+4096 :: GOTO 570
560 W=W-32 :: GOTO 160
570 CALL AMSBANK(B,B+1) :: GOTO 160
580 ! FEEL AROUND ME
590 CALL CHAR(128,"0000187E7E242466")
600 N=INT(U/256) :: O=U-(N*256) :: CALL LOAD(-12282,N,O)
610 CALL EXECUTE(-12034)
620 CALL PEEK(-12288,N,O) :: F=F+N :: S=S+(I*N) :: T=T+O
630 FOR Z=1 TO N+O :: CALL SOUND(1,-5,3) :: NEXT Z :: CALL CHAR(128,"187E5A5A3C242424") :: GOTO 190
640 CALL SOUND(99,200,20,-7,0) :: GOTO 160
650 ! SHOW SCORE
660 CALL MOVES("VR",768,0,W) :: CALL CLEAR :: CALL HPUT(10,1,"TRAPS=",10,7,T,10,13,"SCANS=",10,19,S,10,24,"FOOD=",10,29,F)
670 CALL HPUT(14,1,"YOU ARE INJURED "&STR$(D)&" TIMES!") :: GOTO 1570
680 ! SHOW AREA SCAN
690 IF S THEN 700 ELSE GOSUB 660 :: GOTO 160
700 CALL CLEAR :: CALL SOUND(10,2E4,6,770,5,440,6,-7,3) :: U=W+352 :: K=U+32 :: N=352 :: O=384 :: FOR Z=1 TO 12
710 CALL MOVES("RV",32,U,N,"RV",32,K,O) :: U=U-32 :: K=K+32 :: N=N-32 :: O=O+32 :: NEXT Z
720 CALL COLOR(11,16,1,12,7,1) :: FOR Z=1 TO 100 :: CALL KEY(0,K,J) :: IF K=13 THEN 740
730 NEXT Z :: GOTO 700
740 CALL COLOR(11,2,1,12,2,1) :: S=S-1 :: GOTO 160
750 ! GAME OVER FAILED
760 FOR Z=1 TO 24 STEP 4 :: CALL SOUND(100,-7,Z) :: NEXT Z
770 D=D+1 :: IF I=0 THEN I=1 ELSE I=I-1 :: E=I
780 GOSUB 660 :: IF U=X AND M=B THEN CALL HPUT(22,5,"REPAIR BOT CAUGHT YOU!",20,12,"YOU DIE!") :: END
790 IF D=25 THEN CALL HPUT(22,2,"YOUR INJURIES ARE TO GREAT...",20,12,"YOU DIE!") :: END
800 GOTO 120

If I had used CALL LINK instead of CALL EXECUTE then reading a value would still need a CALL PEEK value for a single value to check.

I did use CALL LOAD for 2 values and CALL PEEK for those same values. But those only needed to be checked or loaded when needed.

 

I could have had all of this handled in Assembly totally but I wanted to use my RXB CALL ONKEY new routine instead of an Assembly SCAN routine.

Link to comment
Share on other sites

RXB wrote:

If I had used CALL LINK instead of CALL EXECUTE then reading a value would still need a CALL PEEK value for a single value to check.

------------------------

Not so. CALL LINK can do much more than simply starting up an assembly language program. You have forgotten the optional parameter list that can be used.

In your program,

280 - - - - -::CALL EXECUTE(-12256)
290 CALL PEEK(-12286,G,H) :: X=(G*256)+H ::- - - - -

could be:

290 CALL LINK("PRNAME",X)

If written for it, the A/L sub PRNAME can take the value at -12286, put it at FAC, convert to floating point, then use NUMASG to pass the value back to XB as the value X. All this conversion sounds tedious, but it actually turns out to be somewhat faster than using EXECUTE and PEEK. This ability to easily pass strings and numbers back and forth between assembly and XB is one of the great things about CALL LINK. I don't think Commodore or Atari had anything like it. Pages 277-280 and 284-287 of the EA manual cover this. The equates for XB are on pages 415-416.

 

You can use HMLOADER which is part of the Missing Link package to fool the XB loader into embedding assembly code in high memory. This is all handled automatically by HMLOADER. One possible killer for your purposes is that the word at >2000 has to point to the lookup routine in high memory. Except for that word, the entire low memory can be used if needed.

Link to comment
Share on other sites

Hmm that is why I do not use Floating Point as it is slower than integer math by huge factor.

 

Every number using CALL LINK is Floating Point passed to or back to XB.

 

Only way to get integers is to convert them first using GINT routine but that requires you load them using CALL INIT.

Or use a CALL PEEK or CALL LOAD, then then again they are ALWAYS Floating Point so I fail to see the advantage you gain.

I did not use any routines built into XB CALL INIT for that reason.

 

By the way the Lower 8K in my game IN THE DARK uses 960K SAMS memory. All of the Assembly in game is in Higher Memory with no need for LINK names.

I really should have done a better job of just passing the location of those Variables that way I could just use CALL PEEK and CALL LOAD once at set up and never again.

Just have the Assembly modify those variables and XB would use those variables when it returns so no calculations needed as already done.

And no variables would ever need to be passed back and forth from XB to Assembly as both would use the same ones.

 

And I do know about the CALL INIT Assembly Routines as my first Project was WINDYXB a pure Assembly version that later became RXB in GPL using Assembly too.

Edited by RXB
Link to comment
Share on other sites

Hmm that is why I do not use Floating Point as it is slower than integer math by huge factor.

 

Every number using CALL LINK is Floating Point passed to or back to XB.

Every number (except for line numbers) in XB is floating point. Just because there is no decimal point does not mean it is not a floating point number.

From your program:

290 CALL PEEK(-12286,G,H) :: X=(G*256)+H :: IF U=X AND B=M THEN 750! MISSION FAILED

Both G and H are integers on the assembly side but are converted to floating point by PEEK and then passed back as floating point numbers to XB.

 

(edit) Did some speed tests:

10 A=3::B=7

20 N=N=1:: X=A*B::GOTO 20 -after subtracting the overhead of the loop

X=A*B takes 6.45 ms to multiply the "integers"

 

After changing line 10 to A=.0003::B=.00000007 and:

X=A*B takes 6.45 ms to multiply the floating point numbers

 

Then changing line10 to A=1234.56789::B=987654.321 and:

X=A*B takes 8.65 ms for each multiplication.

 

So fewer digits can lead to a slight speed increase, but the lack of a decimal point makes no difference.

Edited by senior_falcon
  • Like 1
Link to comment
Share on other sites

Makes sense to me. That's one of the reasons the BBC B's basic was so fast. You could declare integer variables (by appending a % to the variable name). It went like hell.

 

TIB/XB sucks so much I just can't go near it these days! It's a straight-jacket. Though, in its defence, it has great file handling support. It's great that they built that into the language, but they left out other equally useful things, such as types. That would have been awesome.

 

Bah! Bloody TI!

Link to comment
Share on other sites

TIB/XB sucks so much I just can't go near it these days! It's a straight-jacket. Though, in its defence, it has great file handling support. It's great that they built that into the language, but they left out other equally useful things, such as types. That would have been awesome.

 

Indeed, I've been wondering how hard it would be to write an Extended Basic interpreter in assembler (or C, in my case) that adds goodies such as type support and the ability to run programs without using VDP RAM.

Link to comment
Share on other sites

 

Indeed, I've been wondering how hard it would be to write an Extended Basic interpreter in assembler (or C, in my case) that adds goodies such as type support and the ability to run programs without using VDP RAM.

XB only uses VDP for File access, String Variables and Buffers.

But areas like >03C0 and >0820 are XB buffers used for various routines and that is not counting VDP value stack routines of PUSH and POP that are called all the time for many subprograms.

[0208]               ***********************************************************
[0209]               *    VDP addresses
[0210] 02E2          NLNADD EQU  >02E2             New LiNe ADDress
[0211] 02FE          ENDSCR EQU  >02FE             END of SCReen address
[0212] 0371          LODFLG EQU  >0371             Auto-boot needed flag
[0213] 0372          START  EQU  >0372             Line to start execution at
[0214] 0376          SYMBOL EQU  >0376             Saved symbol table pointer
[0215] 0382          SPGMPT EQU  >0382             Saved PGMPTR for continue
[0216] 0384          SBUFLV EQU  >0384             Saved BUFLEV for contiue
[0217] 0386          SEXTRM EQU  >0386             Saved EXTRAM for continue
[0218]               * SAVEVP EQU  >0388           Saved VSPRT for continue
[0219]               * ERRLN  EQU  >038A           On-error line pointer
[0220] 038C          BUFSRT EQU  >038C             Edit recall start addr (VARW)
[0221] 038E          BUFEND EQU  >038E             Edit recall end addr (VARA)
[0222] 0392          TABSAV EQU  >0392             Saved main symbol table ponte
[0223] 0396          SLSUBP EQU  >0396             Saved LSUBP for continue
[0224] 0398          SFLAG  EQU  >0398             Saved on-warning/break bits
[0225] 039A          SSTEMP EQU  >039A             To save subprogram program ta
[0226] 039C          SSTMP2 EQU  >039C             Same as above. Used in SUBPRO
[0227] 039E          MRGPAB EQU  >039E             MERGEd temporary for pab ptr
[0228]               *----------------------------------------------------------
[0229]               * Added 6/8/81 for NOPSCAN feature
[0230] 03B7          PSCFG  EQU  >03B7
[0231]               *----------------------------------------------------------
[0232]               *    Flag 0:  99/4  console, 5/29/81
[0233]               *         1:  99/4A console
[0234] 03BB          CONFLG EQU  >03BB
[0235]               *----------------------------------------------------------
[0236]               * Temporary
[0237] 0374          NOTONE EQU  >0374             NO-TONE for SIZE in ACCEPT us
[0238]               *                              in FLMGRS (4 bytes used)
[0239] 0388          SAVEVP EQU  >0388
[0240] 038A          ERRLN  EQU  >038A
[0241] 03AC          ACCVRW EQU  >03AC             Temoporary used in ERRZZ, als
[0242]               *                              used in FLMGRS
[0243] 03B0          VALIDP EQU  >03B0             Use as two values passing fro
[0244] 03B2          VALIDL EQU  >03B2             VALIDATE code to READL1
[0245] 03BC          OLDTOP EQU  >03BC             Temporary used in ERRZZ, also
[0246] 0820          CRNBUF EQU  >0820             CRuNch BUFfer address
[0247] 08BE          CRNEND EQU  >08BE             CRuNch buffer END
[0248] 08C0          RECBUF EQU  >08C0             Edit RECall BUFfer
[0249] 0958          VRAMVS EQU  >0958             Default base of value stack
[0250] 0390          CNSTMP EQU  >0390             Use as temporary stored place
[0251] 03C0          VROAZ  EQU  >03C0             Temporary VDP Roll Out Area
[0252]               ***********************************************************

I think that VDP Stack routines and these VDP buffer areas are mostly the slow down for XB.

Every single access to VDP shows down XB and way to much is dependent on VDP.

GPL is much slower then Assembly but if XB was pure Assembly it would be more than 1Meg in size.

Edited by RXB
Link to comment
Share on other sites

I wouldn't say that on the size, Rich--Myarc's Advanced BASIC for the Geneve isn't over 128K. . .and you can get 4 times that into one of the 512K cartridges. . .so it would be doable, but it would also be difficult, due to the need for the banking to get to routines. . .

 

I've actually had the idea to make a Rom cart with Myarc Advanced Basic, and yes it would be quite a project. I don't know if the TI source is available, but the Geneve source is. Maybe some sort of insane person might be amenable to teaming up for the project. ;)

 

Gazoo

Link to comment
Share on other sites

 

I've actually had the idea to make a Rom cart with Myarc Advanced Basic, and yes it would be quite a project. I don't know if the TI source is available, but the Geneve source is. Maybe some sort of insane person might be amenable to teaming up for the project. ;)

 

Gazoo

 

I can help you if I can clone myself. I'll leave my clone doing all the drudgery and just fudge with retro stuff all day.

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