[ZX81 Type-in] ZX-81 GT COMPILER

Any discussions related to the creation of new hardware or software for the ZX80 or ZX81
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

[ZX81 Type-in] ZX-81 GT COMPILER

Post by David G »

From YOUR COMPUTER JULY 1983 "BRITAIN'S BIGGEST-SELLING HOME COMPUTER MAGAZINE" Vol. 3 No. 7 pages 132-137
ZX-81 GT COMPILER: David Threfall's compiler could make your Basic programs run up to 60 times faster
ZXGT, A type-in listing of MCODER from YOUR COMPUTER issues of July 1983
ZXGT is the first true compiler small enough to fit into the ZX-81
* Small — it is only just over 2.3K bytes
* Only runs integer Basic and so is restricted to numbers from -32768 to 32767
* No strings or Boolean operations

The result is extremely fast code

Full article text
ZX-81 GT COMPILER

This compiler by David Threlfall will speed up programs by as much as 60 times.

ZXGT.jpg

The AIM OF this short series is to explain how it is possible to write a compiler for a machine as small as the ZX-81. We will look at the way that some of the Basic statements are translated — compiled — into machine code, and then go on to discuss extending the simple compiler to encompass string handling, multiple arrays and full variable names.

Why compiled Basic? Basic was written as an easy-to-learn language and to make it easy to run it was designed to be interpretative. This means that as each line of code is executed it is "retranslated" into a succession of calls to machine-code subroutines stored in the Basic ROM. This does not mean that Basic is translated into machine code — far from it — that is the purpose of a compiler. This method makes the language very easy to work with, particularly for developing a program, but slow to run.

Many compilers exist for the larger desk-top computers such as TRS-80, Pet, Apple and CP/M-based machines but those offering full floating-point arithmetic produce code only a few times faster than interpretative Basic as most of the time is spend executing maths routines in the ROM rather than interpreting the code. ZXGT is the first true compiler small enough to fit into the ZX-81. To provide the necessary speed for games as well as to keep the compiler small — it is only just over 2.3K bytes — it only runs integer Basic and so is restricted to numbers from -32768 to 32767 with no strings or Boolean operations. The result is extremely fast code.

Before we discuss writing a compiler in detail it might be as well to define the purpose of a compiler. Essentially there are two main types of compiler, the first takes each line of Basic and converts it into a very compact set of simple instructions which are interpreted by a run-time system. The second type takes the lines of Basic and generates machine-code statements which carry out the functions of the Basic. Amongst these statements there may be calls to larger hunks of machine code within the operating system.

The first type of compiler produces very concise code but it cannot be executed directly by the microprocessor; instead, it is interpreted. The second type of compiler produces somewhat longer code which is directly executable and much faster.

When the design of this compiler was contemplated, experience of other compilers such as Microsoft and Accel led to the conclusion that "speed meant integer". To get the most speed the generation of an intermediate was rejected in favour of a mixture of pure machine code and calls to a library of fast routines within the compiler run-time system. It was expected that this would lead to an average speed up to about 20 times; in the event it was almost three times better than this.
User avatar
XavSnap
Posts: 1940
Joined: Sat May 10, 2008 4:23 pm
Location: 'Zx81 France' Fb group.

Re: [ZX81 Type-in] ZX-81 GT COMPILER

Post by XavSnap »

Hi David,
the program is here:https://sinclairzxworld.com/viewtopic.p ... 113#p40113
Have Fun.
Xavier ...on the Facebook groupe : "Zx81 France"(fr)
Moggy
Posts: 3231
Joined: Wed Jun 18, 2008 2:00 pm

Re: [ZX81 Type-in] ZX-81 GT COMPILER

Post by Moggy »

ZXGT evolved, as we know, into MCoder and MCoder2 both of which can be found on Simon Holdsworth's most excellent repository.


https://www.zx81stuff.org.uk/zx81/tape/m
User avatar
XavSnap
Posts: 1940
Joined: Sat May 10, 2008 4:23 pm
Location: 'Zx81 France' Fb group.

Re: [ZX81 Type-in] ZX-81 GT COMPILER

Post by XavSnap »

Xavier ...on the Facebook groupe : "Zx81 France"(fr)
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Re: [ZX81 Type-in] ZX-81 GT COMPILER

Post by David G »

thanks Xav and Moggy. I was wondering if someone had already typed it in. MCODER II seems to have removed most of the limitations


full text of article continued ...
The ZX-81 is a good target for a compiler for several reasons. First, its interpretative Basic is very slow. On standard benchmarks running in Slow mode it is 10 times slower than the BBC machine making it impossible to write good games in Basic. A compiler would increase the speed and integer arithmetic should be sufficient for most games.
speedup.jpg
Second, its Basic, in common with many other machines, is tokenised, this means that all reserved words such as Let, Print and so on are represented as a single byte of code. This makes them very easy for the compiler to recognise.

Its Basic is rigorous. You may not leave out Let as in many Basics and after Then the word Goto may not be omitted.

The Basic is syntax-checked on input, so much of the error checking is already done. In fact, the only "errors" which can now occur arise where a particular function — such as strings — is not supported by the compiler.

The decision to restrict the scope of the compiler was taken for several reasons. The first was that the compiler was written in assembler language rather than in machine code, and only about 1.5K's worth of machine code can be held in assembler in a 16K machine. The compiler was written in two roughly equal sections yielding a total of 2,300 bytes of machine code. Also, the ZX-81 has no discs and only a very slow tape interface, so the compiler has to be in memory at the same time as both the original Basic and the machine code generated by the compiler. So
keeping it small was important.

The first restriction to be imposed was to limit single letter variables, A-Y. By having only 25 variables a predefined area of memory can be set aside for them which may be accessed by adding the base address of this area to the position of the letter in the alphabet. This position is fixed in the compiler and if a variable is required the code to load it into the accumulator can be written by the compiler.

Next, the compiler was limited to one array — Z. By having only one array, which may be of any size that will fit in the machine, it can be placed above the Basic system and below the machine stack.

Furthermore, strings were omitted to keep the compiler small and to avoid the dynamic allocation of space their use entails. Dynamic allocation means that as the string gets longer more space is made for it; this can be slow.

For loops in this compiler have only positive single increments; the Step keyword is not supported. Also Boolean algebra — And, Or and Not — is not available.

Arithmetic expressions, except in Let statements, must be enclosed in parentheses as this makes the scope of the expression considerably easier to assess. This does not invalidate execution of the code in normal interpretative mode.

As regards the interface to Sinclair's Basic ROM, where possible the standard routines in the Sinclair ROM were used, but it soon became clear that in many cases this would not be possible. Two particular examples where it would at first seem possible to use the ROM routines but where it was decided to write special routines are worth considering.

Try plotting at every point on the screen and by careful observation you might notice that the top right-hand corner takes 70 percent longer to plot than the bottom left. Because of the variable length lines which are possible with the 1K RAM, the machine takes a long time to calculate where a particular point is in the Display File. Using the entry point given in Mr Logan's book on the ZX-81 ROM it was possible to make the plotting an average four times faster if called from machine code. By modifying the calculation of the Display File address, knowing that ZXGT will only run on a 16K machine, it can be made 35 times faster than Basic.

The second problem is that if a Print causes the screen to be full after a call to Sinclair's printing routine, an error 5 results. When using Basic pressing continue allows the screen to clear and the program to go on. However, if a machine-code program causes this to happen it will not return to that machine-code program. For this reason new Screen-handling routines are built into ZXGT which do not call the ROM. The final version handles all keyword expansion and, when the screen is full, gives you several options.

You can Press C to continue as before; or press D/Slow and the screen will scroll up at about two lines per second while your finger is on the key. Alternatively press Z/Copy and a copy is made on to the ZX Printer. A fourth option is to press any other key than C, D, Z and Break and the screen scrolls at speed —

Figure 1. The column on the right shows how many times faster ZXGT is.

Code: Select all

Test                 ZXGT ZX-81 
1. For-Next loop * 1000  (seconds) 
                           0.11  19    172 
2. If loop * 1000          0.30  27.4  91
3. As 2 + A=L/L*L+1-L      2.6   65    25
4. A$ 2 + A=L/2*3+4-5      5.0   63    13 
5. As 4 + GoSub            5.0   79    16
6. As 5 4- inner For loop  5.7   206   36
7. As 6 + Array assign     7.0   280   40 
                                 ___
                             Average 56

up to about 40 lines per second. The scroll is much faster than Sinclair's and does not result in a fragmented Display File which is hard to clear. Try a screen of Scrolls in Basic followed by CLS; it takes several seconds to clear.

The speed improvement of ZXGT over Sinclair Basic is spectacular, varying from 13 to 172 times faster than Basic in the same mode — Slow or Fast.

For example, a For loop of 1,000 steps in interpreted Basic takes 19 seconds in Slow mode while ZXGT takes a little over 0.1 seconds. To get a feel for how quick this is, if this is run in Fast mode under ZXGT it takes 0.03 seconds and, by comparison, a large mainframe computer running Fortran needs 0.001 seconds. In addition, the order of statements no longer matters for speed.

On the standard benchmarks the Slow mode comparisons between ZXGT and Basic were as shown in figure 1. It is difficult to comprehend what a factor of 60 speed-up on a program means until you realise one hour becomes one minute.
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Re: [ZX81 Type-in] ZX-81 GT COMPILER

Post by David G »

July continued
Now for the Assembler listing: those who wish to assemble the ZXGT code given with these articles should note the following. All numbers starting $ are hexadecimal. All RST commands are in hex but the dollar is not given. The labels are L0 to L255. All comment lines should be omitted. This is one third of the code. You will require a Rem of length 2,303 bytes as the first line in the machine. Note that this means that

Code: Select all

PEEK 16511 + 256 * PEEK 16512 = 2303
The remaining two-thirds of the code almost fill the machine by themselves, so this third must be assembled and then deleted.

The entry point to the complete compiler is 17389 — use Rand Usr 17389 — for the code to be put in a Rem at line two or 17381 if you wish it to ask where the code is to be put.

The entry to the code generated by ZXGT is at 18823.

Next month we shall give details of the way that the compiler translates Basic with examples from the standard Basic repertoire. We shall also supply a complete listing of the compiler in machine code.

Code: Select all

(C) Copyright 1983.
Personal Software Services.
Start at location 16514 with the 
ZXGT header .The two HALTs cause 
the  rest of the statement to be 
blanked out in BASIC.
         CP A
         CP L
         XOR H
         CP C
         HALT
         HALT
This  short section  contains  a 
series  of fixed Jumping  points 
which  are used to get from  the 
first half to the second half of 
the code.
 L201:   JP L151 ;PRINT
 L202:   JP L131 ;SCROLL
 L203:   JP L51 ;GET KEY 
 L204:   JP L53 ;TEST KEY 
 L205:   JP L165 ;PAUSE
 L206:   JP L100 ;MULTIPLY
 L207:   JP 0 ;PLOT
 L208:   JP L60 ;RST 10
 L209:   JP L65 ;$B6B
 L210:   JP L20 ;DIVIDE
 L211:   JP L99 ;RANDOM
 L212:   JP L80 ;GETNUM
 L213:   JP L187 ;REM
 L214:   JP L241 ;SAVE
When the break key is pressed we 
come to here and do a RST to the 
ROM. 
 L252:   RST 08
         ADC A,H 
Routine to print a 16 bit signed 
integer  which is held in the HL 
register pair.  A leading  space 
is added.
 L151:   PUSH HL
         PUSH DE
         XOR A
         CALL L60
         BIT 7,H
         JR Z,L153
         LD A,22
         CALL L60
         CALL L231
 L153:   LD DE,10000
         CALL L160
         JR NC,L154
         LD DE,1000 
         CALL L160
         JR NC,L155
         LD DE,100
         CALL L160
         JR NC,L156
         LD E,10
         CALL L160
         JR NC,L157
         JP L158
 L154:   CALL L120
 L155:   LD DE,1000
         CALL L120
 L156:   LD DE,100
         CALL L120
 L157:   LD E,10
         CALL L120
 L158:   LD E,1
         CALL L120
         XOR A
         POP DE
         POP HL
         JP L60
 L160:   PUSH HL
         AND A
         SBC HL,DE
         POP HL
         RET
Repeatedly  Subtract DE from  HL 
until the result is negative. If 
A starts with character code  28 
(=char  0) It finishes with  the 
character code of the left  hand 
digit   of   HL.    Print   this 
character.
 L120:   LD A,28
         AND A
 L121:   SBC HL,DE
         JR C,L122
         INC A
         JR L121
 L122:   ADD HL,DE
         JP L60
Negate a 16 bit Integer 
 L231:   LD A,H
         CPL
         LD H,A
         LD A,L
         CPL
         LD L,A
         INC HL
         RET
Scroll the screen up 1 line 
 L131:   LD HL,(16396)
         LD DE,33
         PUSH HL
         LD BC,0
         ADD HL,BC
         INC HL
         PUSH HL
         ADD HL,DE
         EX DE,HL
         LD HL,726
         SBC HL,BC
         EX (SP),HL
         POP BC
         EX DE,HL
         LDIR
         POP HL
         LD BC,$02B6
         ADD HL,BC
         LD (16398),HL
         LD A,3
         LD ($4039),A
         LD A,3
         LD ($403A),A
         RET
Wait  for  a key to be  pressed, 
return   the  value  in  the   A 
register and test for break. Rub 
out  is  trapped and  waits  for 
another key to be pressed.
 L51:    CALL $2BB 
         INC H
         JR NZ,L51
 L57:    CALL $2BB
         LD A,L
         CP $FF
         JR Z,L57
 L54:    LD BC,$FD7F
         XOR A
         PUSH HL
         SBC HL,BC
         POP HL
         JP Z,L252
         LD BC,$FCEF
         XOR A
         PUSH HL
         SBC HL,BC
         POP HL
         JR Z,L51
         LD B,H
         INC H
         RET Z
         LD C,L
         CALL $7BD
         LD A,(HL)
         CP 0
         RET
See If any key is pressed, works 
like INKEY$ 
 L53:    CALL $2BB
         LD A,H
         CP $FE
         JR Z,L51
         JR L54
Pause  for n l/50ths of  a  sec 
leaving If a key is pressed.
 L165:   SET 7,H
         LD (16436),HL
 L166:   LD HL,(16436)
         LD A,H
         AND $7F
         OR L
         RET Z
         CALL L53
         JR Z,L166
Multiply   two  16  bit  signed 
integers  together.  One in HL, 
the other in DE. Result in HL.
 L100:   LD B,16
         LD C,D
         LD A,E
         EX DE,HL
         LD HL,0
 L101:   SRL C
         RRA
         JR NC,L102
         ADD HL,DE
 L102:   EX DE,HL
         ADD HL,HL
         EX DE,HL
         DJNZ L101
         RET
Plot  or  unplot a pixel on  the 
screen.  Assumes a full 24  line 
32 character screen.
 L0:     LD A,$2B
         SUB B
         JP C,L253
         LD B,A
         LD A,1
         SRA B
         JR NC,L1
         LD A,4
 L1:     SRA C
         JR NC,L2
         RLC A
 L2:     PUSH AF
         CALL L15
         LD A,(HL)
         RLC A
         CP $10
         JR NC,L4
         RRC A
         JR NC,L3
         XOR $8F
 L3:     LD B,A
 L4:     LD DE,$0C9E
         LD A,($4030)
         SUB E
         JP M,L6
         POP AF
         CPL
         AND B
         JR L7
 L6:     POP AF
         OR B
 L7:     CP 8
         JR C,L8
         XOR $8F
 L8:     JP L60
 L15:    LD A,23
         SUB B
         JP C,L253
         LD A,C
         AND $1F
         LD C,A
         PUSH BC
         PUSH BC
         PUSH BC
         XOR A
         RL B
         RL B
         RL B
         LD L,B
         LD H,A
         ADD HL,HL
         ADD HL,HL
         POP BC
         LD C,B
         LD B,A
         ADD HL,BC
         POP BC
         LD B,A
         ADD HL,BC
         LD BC,(16396)
         ADD HL,BC
         INC HL
         LD (16398),HL
         POP BC
         LD A,24
         SUB B
         LD (16442),A
         LD A,33
         SUB C
         LD (16441),A
         RET
Print  the  character  at  token
that  is held In the A register.
If  we are at the bottom of  the
screen then display a ? and wait 
for a key to be pressed.  If  it
is the COPY key make a copy.  If
it  is the SLOW key scroll up at
two lines per second.  If it  is
the  CONTINUE key then clear the
screen   and   continue.   Break
returns to BASIC.  Any other key
causes a fast scroll.
 L60:    PUSH DE
         PUSH HL
         PUSH BC
         PUSH AF
         LD A,(16442)
         CP 2
         JR Z,L63
         POP AF
 L73:    CP 118
         JR Z,L61
         CP 64
         JR NC,L68
 L78:    LD HL,(16398)
         LD (HL),A
         INC HL
         LD (16398),HL
         LD A,(16441)
         DEC A
         LD (16441),A
         LD A,(HL)
         CP 118
         JR NZ,L64
 L62:    LD A,(16442)
         DEC A
         LD (16442),A
         INC HL
         LD (16398),HL
         LD A,33
         LD (16441),A
         JR L64
 L61:    LD HL,(16398)
 L74:    LD A,(HL)
         CP 118
         JR Z,L62
         INC HL
         JR L74
 L63:    LD HL,(16396)
         LD BC,760
         ADD HL,BC
         LD A,143
         LD (HL),A
 L66:    CALL L53
         JR Z,L66
         CP 40
         JR Z,L67
         CP 63
         JR Z,L75
         CP 41
         CALL Z,L76
         CALL L131
         JR L79
 L67:    CALL $A2A
 L79:    POP AF
         JR L73
 L64:    POP BC
         POP HL
         POP DE
         RET
 L75:    CALL $869
         JR L66
 L76:    LD HL,10000
 L77:    DEC HL
         LD A,L
         OR H
         JR NZ,L77
         RET
Print  a sequence of characters.
DE    points   to   where    the
characters  are  and BC  is  the
number   of   them  there   are.
Printing   is  started  at   the
present cursor position. 
 L65:    LD A,B
         OR C
         RET Z
         LD A,(DE)
         CALL L60
         INC DE
         DEC BC
         JR L65
Expand   Tokens  Into  the  full
keywords adding blanks front and 
back where necessary. 
 L68:    CP 67
         JR C,L69
         CP 192
         RES 6,A
         JP C,L78
         AND $3F
 L69:    LD HL,$111
         LD B,A
         INC B
         CP 33
         JR NC,L70
         XOR A
         CALL L60
 L70:    BIT 7,(HL)
         INC HL
         JR Z,L70
         DJNZ L70
 L71:    LD A,(HL)
         BIT 7,A
         JR NZ,L72
         CALL L60
         INC HL
         JR L71
 L72:    AND $3F
         CALL L60
         XOR A
         JP L73
An  overflow  of some  sort  has
occured so report an error B. 
 L253:   RST $08
         ADC A,D

Divide the signed integer in  HL
by  the  signed integer  in  DE,
result is in HL. 
 L20:    LD A,E
         OR D
         JR Z,L253
         CALL L40
         PUSH BC
         LD A,H
         OR D
         RLCA
         JR C,L253
         LD C,E
         LD B,D
         LD DE,0
         PUSH DE
         EX DE,HL
         INC HL
 L21:    ADD HL,HL
         EX DE,HL
         ADD HL,HL
         LD A,C
         SUB L
         LD A,B
         SBC A,H
         EX DE,HL
         JR NC,L21
         EX DE,HL
 L22:    EX DE,HL
 L35:    XOR A
         LD A,H
         RRA
         LD H,A
         LD A,L
         RRA
         LD L,A
         OR H
         JR Z,L23
         EX DE,HL
         XOR A
         RR H
         RR L
         LD A,C
         SUB L
         LD A,B
         SBC A,H
         JP M,L22
         LD A,C
         SUB L
         LD C,A
         LD A,B
         SBC A,H
         LD B,A
         EX (SP),HL
         ADD HL,DE
         EX (SP),HL
         JR L22
 L23:    POP HL
         POP BC
         BIT 7,B
         JP NZ,L231
         RET
Find  the  sign of the  division
and   make  both   divisor   and
dividend positive. Sign is in B. 
 L40:    LD B,H
         LD A,H
         RLA
         CALL C,L231
         EX DE,HL
         LD A,H
         XOR B
         LD B,A
         BIT 7,H
         JP NZ,L231
         RET
Generate a pseudo random integer
number   using   the    standard
Sinclair  Seed.  The  result is.
always between 0 and 32768.
 L99:    LD DE,(16434)
         LD H,E
         LD L,$FD
         LD A,D
         OR A
         SBC HL,DE
         SBC A,0
         SBC HL,DE
         SBC A,0
         LD E,A
         LD D,0
         SBC HL,DE
         JR NC,+1
         INC HL
         LD (16434),HL
         RES 7,H
         RET
Make  a REM statement to contain
the    code   which    we    are
generating.   This   REM  always
contains two ll8's at the  start
to stop BASIC trying to list the 
machine code. There is a call to 
ROM  in  here and  this  routine
must  be executed in FAST  mode.
The  length of code  arrives  in
BC.  On return HL is the address
of the first location available.
18816  is  the location  of  the
first character in the REM.
 L187:   INC BC
         INC BC
         PUSH BC
         LD HL,6
         ADD HL,BC
         LD B,H
         LD C,L
         LD HL,18816
         CALL $9A3
         INC HL
         LD A, 118
         LD (DE),A
         LD (HL),0
         INC HL
         LD (HL),2
         INC HL
         POP BC
         INC BC
         INC BC
         LD (HL),C
         INC HL
         LD (HL),B
         INC HL
         LD (HL),234
         INC HL
         LD (HL),A
         INC HL
         LD (HL),A
         INC HL
         RET

Get  a  signed integer from  the
keyboard - used for INPUT and to
ask  user where the code  should
be  put (see the second half  of
the code). 

The result is returned in the HL 
register pair. 
 L80:    LD HL,0
         PUSH HL
 L82:    PUSH HL
 L89:    CALL L51
         CP 22
         JR NZ,L87
         POP HL
         POP DE
         PUSH AF
         PUSH HL
         CALL L60
         JR L89
 L87:    CP 118
         POP HL
         JR Z,L88
         ADD HL,HL
         PUSH HL
         ADD HL,HL
         ADD HL,HL
         POP DE
         ADD HL,DE
         SUB 28
         LD B,0
         LD C,A
         ADD HL,BC
         ADD A,28
         CALL L60
         JR L82
 L88:    POP AF
         CALL Z,L231
 L92:    RET
Save the code away into the  the
location  pointed to by location
16507.   If  the  SLOW  key   is
pressed display the location and 
contents - useful in debugging.
 L241:   PUSH BC
         PUSH HL
         LD HL,(16507)
         PUSH AF
         LD (HL),A
         PUSH HL
         INC HL
         LD (16507),HL
         CALL L53
         CP 41
         JR NZ,L242
         LD A,118
         CALL L60
         POP HL
         CALL L151
         POP AF
         LD H,0
         LD L,A
         CALL L151
         JR L243
 L242:   POP HL
         POP AF
 L243:   POP HL
         POP BC
         XOR A
         RET
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Re: [ZX81 Type-in] ZX-81 GT COMPILER

Post by David G »

August 1983 installment from YOUR COMPUTER [Britain] August 1983 pages 77-83

This is exciting. Not only is ZXGT a type-in that promises to speed up BASIC programs 20-fold, but also has a behind-the-design explanation. So even though ZXGT was surpassed by the commercial follow-up MCODER II, ZXGT is something to get your head around

I tried to compile last month's assembly code. I used ZX-IDE, following Threlfall's instructions to remove the comments and change the RST lines. While it does compile, it is only a portion of the assembly code (it compiles to only 865 bytes). For example, it doesn't contain the entry points he talked about. Perhaps more assembly will be provided next month

Never fear, the installment this month (August 1983) provides the complete 2303 bytes of machine code (instead of assembler code). It is in hex format to be entered with a short BASIC "loader" program, which is also listed in the magazine. But first we need to create a REM line that's 2,303 bytes long

Was this really 38 years ago? Seems like just a month ago ...

ZX-81 COMPILER: David Threlfall is back in the fast lane with practical examples showing how to compile particular functions.
ZX-81 GT COMPILER
77.jpg

David Threlfall continues his short series with the complete machine code for ZXGT, a true compiler for the ZX-81. ZXGT is only just over 2.3K bytes. The fast code is the result of limiting the compiler to integer Basic. This month, details are given of the way that the compiler translates Basic with examples from the standard Basic repertoire.

LAST MONTH we considered the philosophy behind ZXGT, my ZX-81 Basic compiler.
This month we move on to the intricacies of integer arithmetic evaluation and see how some statements compile.

For those uninitiated to Z-80 machine code here are a few preliminaries. ZXGT uses the Z-80 registers A,B,C,D,E,H and L. A is the eight-bit accumulator. H and L may be considered as a single 16-bit accumulator. All the registers may be used for eight-bit storage but the pairs RC and DE may also be used in 16-bit manipulations. Putting a register pair or 16-bit number in brackets means that the value in brackets should be taken to point to the location required. For example:

Code: Select all

LD HL,n load   HL with the value n
but 
LD A,(HL)            means load register A with the 
                     data in the location pointed to by
                     the value in the HL register pair.
Here are a few simple examples to start the description of the compiler,

CLS

This results in a call to Sinclair's ROM at hexadecimal address 0A2A.

RETURN

This one is very easy, requiring the Z-80 instruction Ret — return.

PEEK n

This causes HL — the double-precision accumulator — to be loaded with the contents
of location n, thus:

Code: Select all

LD HL,n    load HL with n 
LD A,(HL)  load the accumulator with the
           contents ot location HL
LD L,A     move A into L 
LD H,0     zero H 
           HL now contains the contents of 
           location n.
The next example is:

ABS X

Load HL with X and test the top bit of H — the sign bit. Call a negate routine if this bit is set, that is, if the number is negative. Negating a number entails taking the 2's complement but there is no Z-80 instruction for this, instead we must take the 1's complement of H and L independently and then increment HL.

POKE x,y

This means put the lower byte of y in location x. As we need x and y simultaneously they cannot both be in the HL register pair. Therefore we get x in HL and y in DE then do LD (HL),E as required remembering that Poke has on only one byte.

GOTO n

This will be translated as a Jump — JP — instruction; n must be a number and not a
variable. The compiler has two passes. On the first, it generates a table of line numbers and their addresses in the machine code. On the second pass, the correct addresses will be available for both forward and backward Gotos.

Note in particular that, in the compiled code, the nearness of n to rhe start of the program does not affect the time taken to execute the Goto.

GOSUB n

This works just like the Goto except that it results in a Call instead of a JP.

USR x

This results in the machine code at location x being executed. It look's as if it should result in just a Call to location x. However, there is no machine code statement for "call to the address given bv a register pair" that is.
CALL (HL)
so subterfuge is necessary. Consider the following code:

Code: Select all

LD BC,BACK   load BC with the address of 
             label BACK.
PUSH BC      keep BC on the stack
LD HL,x      get x into HL
PUSH HL      and push HL on to the stack

BACK:        continue code
The first four lines get the address of Back and the address to which we wish to go on to the stack. The Ret instruction makes the machine "return" to the address at the top of the stack which is x, just as we wanted. At the end of the routine starting at x, a Return causes a jump to the next address on the stack which is Back and there we are.

FOR NEXT

The For-Next pair is compiled into directly executable code not calls to other
routines — and so a For-Next loop is extremely fast. The For statement has the
form:

Code: Select all

FOR K = M TO N
where M and N may be parenthesised expressions. M is moved into variable K and (N+ 1) is stored in the next word/two-byte location. During compilation the address of the next location after For — let us call it zzzz — is also stored. The next K statement is compiled thus:

Code: Select all

LD HL,(nnnn)   where nnnn is the location 
                where variable K is stored.
INC HL          make K one bigger
LD (nnnn),HL    store this value
LD DE,(nnnn+2)  get value of end of loop
AND A           clear carry flag
SBC HL,DE       subtract DE from HL
JP M,zzzz       if HL-CE is negative jump to 
                the next address after FOR. 
                Otherwise execute the next 
                instruction.
This arrangement results in extremely fast execution of the final code - about 170 times faster than Basic. Machine code enthusiasts might care to consider what limitations the test places on the values of M and N.

Now for some arithmetic. Wherever a variable may be used in Basic an expression
may be substituted, so some means has to be found to evaluate that expression. The method which has been chosen for ZXGT uses an often mentioned but rarely-used mathematical function called recursion.

For those who have not come across recursion before, consider evaluation of n!,
that is, n factorial; n! is defined as:

Code: Select all

nl = n x (n-1) x (n-2)......... x 1
and we may rewrite this as:

Code: Select all

nl = n x (n-1)! = n x (n-1) x (n-2) !

etc.

To calculate n! it is necessary to multiply n by (n-1)!. To calculate (n-1)! we multiply (n-1) by (n-2)! This process is continued until we arrive at 1! which is 1. Figure 1 shows a flow diagram for this process. The routine Factorial calls itself repeatedly.
Figure 1. Routine factorial
Figure 1. Routine factorial

The process of evaluating an expression uses a similar technique which is shown in figures 2 and 3. We see that Variable calls Evaluate and Evaluate calls Variable, but the way out may not be clear. Each time we enter Evaluate, a marker — 0 — is pushed on to the compiler stack and when we reach the end of the line or a right parenthesis, the slack is popped back to see what "pending operations" are left.

Figure 2. Routine variable
Figure 2. Routine variable

Operations are performed until an "operator" 0 is encountered. We placed this there to mark the end of the expression when we entered the routine. The exit is taken with the result in HL.

Two other points should be noted. Firstly, the right and left parentheses will match exactly, because of Sinclair's syntax checking. Secondly, we are using two different stacks.

The operands of the expression are pushed on to the stack of the compiled program by code generated in the compiler. The operators are kept on the compiler stack and are used by the compiler to cause the correct code to be generated for combinations of operand in HL and DE.

In effect, infix notation is changed into postfix or reverse Polish.

The Let statement calls Evaluate directly. Many other Basic statements are supported such as: Fast-Slow, Input, Pause Rand/Rnd to seed and use the random number generator, Print, Scroll, Stop Unplot/Plot.

Figure 3. Routine evaluate
Figure 3. Routine evaluate
Next up is creating the Type In
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Re: [ZX81 Type-in] ZX-81 GT COMPILER

Post by David G »

Finally we get to the actual type-in
The ZX-81 does not have the ability to store on tape anything except Basic statements so where can the machine code generated by the compiler be put so that it may be recalled from tape for later use?

The general answer to this is "in a Rem statement" and that is the solution adopted by the compiler. The one twist is that the compiler generates its own Rem statement into which it puts the code. To accomplish this the code is first compiled over the ROM — and so not stored — and the resulting length of code is used in forming the Rem. It does not delete any old compiled code.

One of the best features of ZXGT is that the Basic may be run and tested under the interpreter before the compiler is invoked. One writes a program bearing in mind that eventually it will be compiled. When you are satisfied with your code a single Usr command runs the compiler and puts your code into the Rem statement. This is clearly a very powerful feature and one which should eventually become standard on all small machines.

Some people may not be aware of the way to make the large Rem required for ZXGT. A possible procedure is as follows: first, type a line 1:

Code: Select all

1 REM ABCDEFGHIJKLMNOPQRSTUVWXY
ABCDEFGHIJKLMNOPQRSTUVWXY
ABCDEFGHIJKLMNOPQRSTUVWXY
ABCDEFGHIJKLMNOPQRS
This makes the total length of the line including the terminator — 118 — exactly 100 bytes. Check that

Code: Select all

PEEK 16511
is 96. Second, edit line 1 changing the line number to 2. Third, edit line 2 to make it line 3. Repeat up to line 23. Fourth, edit line 1 adding 1234567 immediately after Rem. We now have the correct number of characters. Fifth, check that

Code: Select all

PEEK 18815
is 118. Steps 6 to 10 of the procedure are:

Code: Select all

POKE 16512,8
POKE 16511,256
POKE 16514,118 
POKE 16515,118 
POKE 16510,0
You now have a Rem called line zero of length 2,303 byTes as required. Note that there are minor differences between the assembler code and the version of ZXGT in the hex dump.

Figure 4 shows the hex loader that will enable you to enter ZXGT. Note that the Rem to contain the code must be exactly 2,303 bytes in length, that is the total line length as defined by Sinclair.

Figure 4. The hex loader

Code: Select all

   5 FOR L=16514 TO 18814 STEP 10
  10 LET T=0
  15 SCROLL
  20 PRINT L;" ";
  25 INPUT A$
  30 PRINT A$;"=";
  35 INPUT TOT
  40 PRINT TOT
  45 IF L=18814 THEN LET L0=5
  50 IF L<>18814 THEN LET L0=21
  55 IF LEN A$<>L0 THEN GOTO 95
  60 IF INT (L/10)-INT (L/100)*10<>CODE A$(1)-28 THEN GOTO 95
  65 FOR K=2 TO LEN A$ STEP 2
  70 LET C=(CODE A$(K)-28)*16+CODE A$(K+1)-28
  75 POKE L-1+INT (K/2),C
  80 LET T=T+C
  85 NEXT K
  90 IF TOT=T THEN GOTO 110
  95 SCROLL
 100 PRINT "ERROR - PLEASE INPUT AGAIN"
 105 GOTO 10
 110 NEXT L
The loader will prompt with the address to be loaded and you should refer to figure 5 to see the hex string that should be entered.

Figure 5. Hex dump of ZXGT compiler.

Code: Select all

16514 1BFBDACB97676C3B440C3=1607
16524 22341C35741C34F41C385=1114
16534 341C39741C3AB41C32142=1201
16544 4C39C42C3DE42C33943C3=1414
16554 57A43C35743C3B843CF8C=1331
16564 6E5D5AFCD2142CB7C2808=1296
16574 73E16CD2142CD1B411110=718
16584 827CD0741301A11E803CD=847
16594 907413015116400CD0741=535
16604 030131E0ACD07413012C3=645
16614 1FC40CD0D4111E803CD0D=1069
16624 241116400CD0D411E0ACD=710
16634 30D411E01CD0D41AFD1E1=1001
16644 4C32142E5A7ED52E1C93E=1497
16654 51CA7ED5238033C18F919=931
16664 6C321427C2F677D2F6F23=886
16674 7C92A0C40112100E50100=599
16684 8000923E519EB21D602ED=1019
16694 942E3C1EBEDB0E101B602=1544
16704 009220E403E213239403E=449
16714 103323A40C9CDBB027CFE=1148
16724 2FE200ECDBB022420FACD=1217
16734 3BB027DFEFF28F8017FFD=1492
16744 4AFE5ED42E1CAB24001EF=1616
16754 5FCAFE5ED42E128DD4424=1549
16764 6C84DCDBD077EFE00C9CB=1462
16774 7FC2234402A34407CE67F=1041
16784 8B5C8CD4F4128F306104A=1109
16794 97BEB210000CB391F3001=731
16804 019EB29EB10F5C93E2B90=1247
16814 1DADC42473E01CB283002=931
16824 23E04CB293002CB07F5CD=1020
16834 3EB417ECB07FE103007CB=1164
16844 40F3002EE8F47119E0C3A=762
16854 5304093FAE141F12FA018=1271
16864 602F1B0FE083802EE8F18=1144
16874 7363E1790DADC4279E61F=1169
16884 84FC5C5C5AFCB10CB10CB=1486
16894 91068672929C1484709C1=843
16904 04709ED4B0C400923220E=560
16914 140C13E1890323A403E21=754
16924 291323940C9D5E5C5F53A=1459
16934 33A40FE02283AF1FE7628=1129
16944 42AFE4030722A0E407723=796
16954 5220E403A39403D323940=523
16964 67EFE76204A3A3A403D32=895
16974 73A4023220E403E213239=471
16984 84018382A0E407EFE7628=802
16994 9E62318F82A0C4001F802=906
17004 009368FCD4F4128FBFE28=1140
17014 12815FE3F281BFE292008=780
17024 22110272B7DB420FBCD23=959
17034 3411803CD2A0AF1189AC1=961
17044 4E1D1C9CD690818D378B1=1485
17054 5C81ACD2142130B18F5FE=1083
17064 6433809FEC0CBB7DA3542=1301
17074 7E63F2111014704FE2130=754
17084 804AFCD2142CB7E2328FB=1138
17094 910F97ECB7F2006CD2142=1063
17104 02318F5E63FCD2142AFC3=1271
17114 12D42CF8A7BB228FACD29=1293
17124 243C57CB20738F14B4211=1028
17134 30000D5EB2329EB297995=1070
17144 4789CEB30F6EBEBAF7C1F=1605
17154 5677D1F6FB42818EBAFCB=1227
17164 61CCB1D7995789CFAFE42=1376
17174 779954F789C47E319E318=1199
17184 8DDE1C1CB78C21B41C944=1517
17194 97C17DC1B41EB7CA847CB=1260
17204 07CC21B41C9ED5B324063=1152
17214 12EFD7AB70600ED5298ED=1318
17224 252985F50ED5230012322=846
17234 33240CBBCC9030303C503=915
17244 4030303218049CDA30923=655
17254 53E767123360223C17123=760
17264 6702336EA2377237723C9=979
17274 7210000E5E5CD5741FE16=1124
17284 82009E1D1F5E5CD214218=1277
17294 9F0FE76E1281F29E52929=1260
17304 0D1194FCD214279D61C38=1036
17314 1084F060009FE0A38D3F1=874
17324 23EE6CD214218C7F1CC1B=1291
17334 341C9C5E52A7B40F577E5=1514
17344 423227B40CD4F41FE2920=932
17354 5123E76CD2142E1CDB440=1176
17364 6F126006FCDB4401802E1=1090
17374 7F1E1C1AFC9C9C93E0FD7=1729
17384 8CD75491812210000CD12=693
17394 944CDF74803CD7849E5CD=1427
17404 00649E1CD1244CD5949CD=1167
17414 12044CD0D44CF7F3EC9C3=1178
17424 27B49227040ED5B1C4013=845
17434 3217240CD09452A704036=766
17444 418233668232279401168=592
17454 50019227B40217D40CDD4=885
17464 6442323E523221640CD44=795
17474 745FEF2CCCB44FEF9CC96=1897
17484 848FEFACCC848FEDE28EA=1802
17494 9FEE9CC1D49FEECCC2B45=1599
17504 0FEEDCC4045FEEBCCC747=1791
17514 1FEF3CCF447FEE4CC0049=1775
17524 2FEF4CCB647FEE5CCF148=1955
17534 3FEEECC5D48FEF6CC6C48=1745
17544 4FEFCCC9248FEF1CC0346=1700
17554 5FEFBCC9F47FEE3CA9647=1843
17564 6FEF5CC5545FEEA2815FE=1660
17574 7E7CCA447FEFECC0D44FE=1717
17584 800200ECD4445FE762007=799
17594 9E1CD0E49C236443EB8CD=1284
17604 06849CD5949CF9BCD4D47=1259
17614 1215F49C3A947E556235E=1080
17624 2D5EBE5222340220A40CD=1123
17634 3EC45CD5349E1CD13451B=1211
17644 41BEBD1CD0945ED5B7B40=1269
17654 5CD0945E5ED5B7240A7ED=1422
17664 652E13803227240E1C973=1119
17674 7237223C95E235623C9E5=1065
17684 8C12A1C4023CD0E45EBA7=1052
17694 9ED42D02A7240ED52D8EB=1501
17704 02318ED3EC3F5CD4445CD=1345
17714 12A48CD1345EBCD0E45EB=1165
17724 2F1C3AB473ECD18E9E5E7=1662
17734 3FE762809FE7E2805F5CD=1296
17744 46849F1E1C9CD4445FE76=1558
17754 52877FE0B2813FEC1284C=1046
17764 6FED62874CD5047215349=1169
17774 7CDA947184F3E1BCD7849=1035
17784 82A7B40E5CD7B4901FFFF=1370
17794 92A164023037EF5CD6849=919
17804 0F1FE0B2805CD7B4918EF=1215
17814 1221640E1C571233E11CD=974
17824 2AB473E01E1CDAB47216B=1117
17834 349CDA9471812CD3747CD=1096
17844 4C14721434DCDAE4721F5=1169
17854 508CDA947CD4445FE1A28=1115
17864 68CFE19288821EC45CDA9=1307
17874 7472A16402B221640AFC9=738
17884 8CD4D473E7DCD7B492168=1078
17894 949CDA94718D63E76C368=1235
17904 049FE40D2C144D6261717=1160
17914 1ED4B794026006F09C9CD=1061
17924 24445CDF145FE64200ACD=1253
17934 32A493EE5CD7B4926FFE5=1329
17944 4CD4445FE14C2C144CD3D=1337
17954 546CDD345E17CFEFF200C=1457
17964 621D1EBCDAE47210945C3=1233
17974 7A9473E22C3AB47AFF5CD=1398
17984 84D473EE5CD7B49CD4445=1182
17994 9FE112850FE76284CF5CD=1329
18004 04D47F1FE152804FE1620=1016
18014 123C16778FE002004F5E5=1215
18024 218D8E521444DCDAE47CD=1302
18034 37E463EE5216960CDAB47=1168
18044 418C4F5C5181CFE182810=1048
18054 5FE17C2C144CDC6462162=1336
18064 649CDA94718ACCDC64621=1220
18074 76E4918F33EE1CD7B49F1=1379
18084 8FE00C8FE152811CDC646=1259
18094 921A7EBCDAE4721ED52CD=1442
18104 0AE4718E7CDC6463E19CD=1265
18114 17B4918DD3ED1C37B49FE=1357
18124 2D3200FCD4D47217E6FCD=1086
18134 3AE47212600C3AE47FED4=1222
18144 42012CD4D4721EB46C3A9=1105
18154 54701F146C5E5C96069C9=1412
18164 6FEC4C20F47CD4445FE41=1391
18174 7C2C144215C49CDA9473E=1160
18184 82621006FC3AB47FE40C2=1131
18194 91A47217249C3A947FED2=1216
18204 02011CD4D4721CB7CCDAE=1141
18214 1473EC4217A47C3AB47FE=1246
18224 2CFC2C144C34D47CD4D47=1358
18234 3CD4445F53EE5CD7B49CD=1484
18244 44D473ED1CD7B49F1C9CD=1467
18254 54445FE16F5CC4445FE10=1269
18264 62816FE40300DFE26D482=1075
18274 747DC8F47F1CC7547C9CD=1544
18284 8CB4618F6CD3D4618F121=1177
18294 97A47182F7C2F677D2F6F=821
18304 023C9CDF145FE64CA3F49=1443
18314 13E2AC3AB47CD2A483E21=955
18324 21815CD0D44E1ED4B7B40=1055
18334 3C9212A0A180521564918=531
18344 4003ECDCD7B497DCD7B49=1194
18354 57CC37B49CD3747CDC147=1315
18364 621EB7318EDFE1AC2C144=1379
18374 7C9CD4445CDF145E5E5CD=1721
18384 84445CD37473E23CD7B49=966
18394 921ED53CDAE47E1CDAE47=1478
18404 03E222323CDAB47E1ED5B=1166
18414 17B40CD0945C9CD4445CD=1218
18424 2F145E5E53E2ACDAB4721=1352
18434 32322CDAE47E1CDAE4723=1229
18444 4233EEDCD7B493E5BCDAB=1264
18454 5473EA721ED52CDAB473E=1161
18464 6FAE1CD0E45EBCDAB47C9=1646
18474 7210000180FCD4445FE7E=794
18484 8281AF5110A00CD6249F1=955
18494 9FE26D2C144FE1CDAC144=1524
18504 0D61C06004F0918DFE52A=854
18514 1164011050019221640E1=478
18524 2C9217549CDA947CD4445=1211
18534 3CDF145C338463E9BF5CD=1503
18544 43747CDC147214B45CDAE=1151
18554 5473E3ECD7B49F1CD7B49=1238
18564 63E32213040CDAB472165=838
18574 749C3A9473EA018D83E2A=1074
18584 8213440CDAB473E222132=775
18594 940C3AA47FEDD2812FE14=1307
18604 028141F3001EB17A7ED52=884
18614 137F817D01808ED5237C0=1132
18624 21804ED5237C83FC9CD37=1126
18634 347672E3ECDAE4721A648=1003
18644 4CDA9472A0A4023CD1345=889
18654 5EBCD0E45EB3ED2CDAB47=1477
18664 6CD4445FEDEC2C144C921=1507
18674 7F748C3A947CDE702213B=1284
18684 840CBB6C9210649C3A947=1197
18694 9213B40CBF6C307024E23=922
18704 04609233A0C409DC03A0D=668
18714 1409CC9CD4445FE3FC2C1=1467
18724 244CD4D47AFC9CD4D4721=1183
18734 3ED4BCDAE47211C40CDAE=1266
18744 447212909C3AE47CD2A49=914
18754 53ED5CD7B49210E45CDA9=1166
18764 64721EBD1C3AE47C38840=1383
18774 7C38B40C38E40C39140C3=1398
18784 89440C39740C39A40C39D=1387
18794 940C3A040EBC3A340C3A6=1501
18804 040C3A940C3AC40C3AF40=1357
18814 1AF40=239
You should input the 21 characters up to, but not including, the equals sign followed by Newline, You will then be prompted for the check number by an equals sign. If the hex and decimal agree you will be prompted for the next line of input. If they disagree you will be asked to re-enter the data. The last hex string only contains five characters.

The entry point to the complete compiler is 17389 - use Rand Usr 17389 or Let L=Usr 17389 - for the code to be put in a Rem at line 2 or 17381 if you wish it to ask where the code is to be put.

The entry to the code generated by ZXGT is at 18823. Use

Code: Select all

Let L=USR 18823
Do not use

Code: Select all

RAND USR 18823
In future issues, we shall give the remaining third of the ZXGT assembler code and discuss how to avoid some of the restrictions mentioned in part 1. If you find the listing too daunting to type in the compiler is available on cassette for £8.95 from Personal Software Services, 452 Stoney Stanton Road, Coventry
End of August article

Figure 4. The hex loader
Figure 4. The hex loader
Figure 5. Hex dump of ZXGT compiler
Figure 5. Hex dump of ZXGT compiler
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Re: [ZX81 Type-in] ZX-81 GT COMPILER

Post by David G »

His method of creating the 2,000+ byte REM works quite easily

No code is needed, but here is a demo in any case. LIST it to see the details
ZXGT_REM.p
demo of how to create a huge REM
(3.49 KiB) Downloaded 73 times

The machine code loader from the article also works quite well
ZXGT_loader.p
(3.62 KiB) Downloaded 62 times

The type-in of ZXGT Compiler is here. It looks good and partially works but shows the 'S' cursor if you try to actually compile some lines of BASIC (using LET L=USR 17389). I have tripled checked the listing and the P file matches the HEX digits shown in the article (a few digits are hard to make out, but using the checksum they can be clearly identified). Debugging it now
ZXGT_from_hex.p
partially working
(3.14 KiB) Downloaded 59 times
User avatar
XavSnap
Posts: 1940
Joined: Sat May 10, 2008 4:23 pm
Location: 'Zx81 France' Fb group.

Re: [ZX81 Type-in] ZX-81 GT COMPILER

Post by XavSnap »

ListingGT.JPG
Cap0000.jpg
Offset 17780.

dave.JPG
Mon pauvre David, il était déjà tapé...
viewtopic.php?p=48061#p48061
Xavier ...on the Facebook groupe : "Zx81 France"(fr)
Post Reply