PL/M cross-compiler for ZX Spectrum

People are still making stuff for the Sinclair related machines. Tell us about new games and other software that runs on the Spectrum, ZX80/ZX81, Pentagon and Next.
Post Reply
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

PL/M cross-compiler for ZX Spectrum

Post by ketmar »

as Wikipedia says, "PL/M was the first higher level programming language for microprocessor-based computers and was the original implementation language for those parts of the CP/M operating system which were not written in assembler." it was designed and developed by Gary Kildall, and used to write a lot of software for 8-bit CPUs (mostly running CP/M).

currently, there is no small and usable PL/M cross-compiler out there. there are several PL/M->C converters, and some translations of the old fortran PL/M compiler. converters are not really interesting, and fortran port is… not very easy to use/understand. so i decided to develop my own PL/M cross-compiler from the scratch.

i created this topic to force myself to finish the project (or at least make something useable ;-), so there is no ready to use software here yet. but i'm working on the thing, and i already wrote parser and semantic analyser. what it means? it means that the compiler can parse PL/M code, build and analyze AST. basically, everything is ready for code generation at this stage.

i am planning to support most PL/M-80 features, but the target platform will be ZX Spectrum, not some CP/M machine. the compiler itself is written in GNU C, and the sources will be opened under GPL license.

i hope to release Tech Preview version at the end of this month (i REALLY DO! ;-).

for now, you can download PL/M-80 manual, and check if you like the language. i think that PL/M is more beginner-friendly than C, and programming retro computers with The First Language For Microprocessors is much funnier than using C for that. ;-)

sorry for teasing you and not giving you something to play with. i will try to fix this ASAP. ;-)
User avatar
g0blinish
Manic Miner
Posts: 289
Joined: Sun Jun 17, 2018 2:54 pm

Re: PL/M cross-compiler for ZX Spectrum

Post by g0blinish »

interesting idea. isis-2 setup is too complicated, besides, the generated code uses bdos calls
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: PL/M cross-compiler for ZX Spectrum

Post by ketmar »

also, i must tell you that the initial version will prolly have the worst codegen possible, because i want to get the thing running first. yet i know how to make it better (but no, i don't want to write yet another SSA compiler this time), so please, don't judge the thing by the upcoming Tech Preview. ;-) while the thing will prolly never be a speed demon, i hope to teach it to generate decent code.
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: PL/M cross-compiler for ZX Spectrum

Post by ketmar »

sorry, i have to use this thread as a kind of "developer diary", because working on the project is quite hard. not because i don't know how to implement it (it's not even a tenth compiler i am writing; writing compilers is my hobby), but because of some external… conditions. so i will drop some progress posts here from time to time, and this should force me to continue. ;-)

so i taught the parser to understand the code better, and rewrote semantic analyser several times (this is perfectly normal thing at this stage of development). also implemented very simple constant folding and dead code elimination, and wrote very simple stack-like codegen; and now replacing it with a slightly better one.

also, i made a mistake of adding signed integers as a built-in complier type. PL/M doesn't have signed ints, and i won't support them too. maybe later. the problem is that automatic type conversions with signed types is a mess, and it doesn't worth the efforts (at least for now). so i removed all traces of signed ints: let's stick to the original PL/M specs!

for those interested: the compiler is a classical multipass one. the parser builds very-very abstract AST, the semantic analyzer rewrites it to lower level one (and sets expression types), and the codegen is using delayed loads as described by Wirth. i am also planning to implement saving register info at block boundaries in codegen, and simple live range analysis to help register allocation (much) later.

i don't want to write SSA-based compiler this time, because i want some PAIN AND SUFFERING. but maybe i'll switch to SSA later anyway.
Spoiler
p.s.: if somebody wants to track my work, drop me a PM to get a link to Fossil repo. i feel that making it "fully public" now may create some wrong impressions (early development work is usually doesn't look good ;-).
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: PL/M cross-compiler for ZX Spectrum

Post by ketmar »

first (stupid) milestone!
this:
Spoiler

Code: Select all

PRINT$STRING: PROCEDURE(ADDR,LEN) EXTERNAL;

PRINT$NUMBER: PROCEDURE(NUMBER,BASE,CHARS,ZERO$SUPPRESS);
  DECLARE NUMBER ADDRESS, (BASE,CHARS,ZERO$SUPPRESS,I,J) BYTE;
  DECLARE TEMP(16) BYTE;
  IF CHARS > LAST(TEMP) THEN CHARS = LAST(TEMP);
    DO I = 1 TO CHARS;
      J=NUMBER MOD BASE + '0';
      IF J > '9' THEN J = J + 7;
      IF ZERO$SUPPRESS AND I <> 1 AND NUMBER = 0 THEN
          J = ' ';
      TEMP(LENGTH(TEMP)-I) = J;
      NUMBER = NUMBER / BASE;
    END;
  CALL PRINT$STRING(.TEMP + LENGTH(TEMP) - CHARS,CHARS);
END PRINT$NUMBER;


MAIN: PROCEDURE;
  CALL PRINT$NUMBER(9679, 10, 5, 1);
  CALL PRINT$NUMBER(9679, 10, 5, 0);
END MAIN;
not only generates some awful asm code, but prints both numbers properly!

for those interested, here's the generated asm. currently, zero attempts were made on data flow analysis or register allocation, so the code is very ugly and slow:
Spoiler

Code: Select all

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PROC: PRINTNUMBER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_loc_PRINTNUMBER__NUMBER: defw 0
_loc_PRINTNUMBER__BASE: defb 0
_loc_PRINTNUMBER__CHARS: defb 0
_loc_PRINTNUMBER__ZEROSUPPRESS: defb 0
_loc_PRINTNUMBER__I: defb 0
_loc_PRINTNUMBER__J: defb 0
_loc_PRINTNUMBER__TEMP: defs 16,0
_PRINTNUMBER:
;; LINE #4
;; LINE #4
;; LINE #4
;; LINE #4
;; LINE #4
;; LINE #4
;; LINE #5
;; LINE #6
  ld    e,15
  ld    a,(_loc_PRINTNUMBER__CHARS)
  sub   a,e
  jr    z,$+4
  ccf
  sbc   a,a
  rra
  jp    nc,._label_1
;; LINE #6
  ld    a,15
  ld    (_loc_PRINTNUMBER__CHARS),a
._label_1:
;; LINE #7
;; LINE #7
  ld    a,1
  ld    (_loc_PRINTNUMBER__I),a
._label_3:
  ld    de,(_loc_PRINTNUMBER__CHARS)  ; 8-bit cheat (lo)
  ld    a,(_loc_PRINTNUMBER__I)
  sub   a,e
  ld    a,0xff
  jr    z,$+3
  sbc   a,a
  rra
  jp    nc,._label_2
;; LINE #8
;; LINE #8
  ld    de,(_loc_PRINTNUMBER__BASE)
  ld    d,0  ; 16-bit cheat
  ld    hl,(_loc_PRINTNUMBER__NUMBER)
  call  _mod_hl_de
  ld    de,48
  add   hl,de
  ld    a,l
  ld    (_loc_PRINTNUMBER__J),a
;; LINE #9
  ld    e,57
  ld    a,(_loc_PRINTNUMBER__J)
  sub   a,e
  jr    z,$+4
  ccf
  sbc   a,a
  rra
  jp    nc,._label_4
;; LINE #9
  ld    e,7
  ld    a,(_loc_PRINTNUMBER__J)
  add   a,e
  ld    (_loc_PRINTNUMBER__J),a
._label_4:
;; LINE #10
  ld    e,1
  ld    a,(_loc_PRINTNUMBER__I)
  cp    e
  ld    a,0
  jr    z,$+3
  dec   a
;; 1 : a
  ld    e,a
  ld    a,(_loc_PRINTNUMBER__ZEROSUPPRESS)
  and   a,e
  push  af
  ld    e,0
  ld    hl,(_loc_PRINTNUMBER__NUMBER)
  dec   h       ; 16/8 (n)equ
  jr    nz,$+5    ; 16/8 (n)equ
  xor   a       ; 8/16 (n)equ
  jr    ._label_6       ; 8/16 (n)equ
  ld    a,l
  cp    e
  ld    a,0
  jr    nz,$+3
  dec   a
._label_6:
;; 1 : a (op0: 1 : a)
  ld    e,a    ; bothreg
  pop   af
  and   a,e
  rra
  jp    nc,._label_5
;; LINE #11
  ld    a,32
  ld    (_loc_PRINTNUMBER__J),a
._label_5:
;; LINE #12
  ld    a,(_loc_PRINTNUMBER__J)
  push  af
  ld    de,(_loc_PRINTNUMBER__I)  ; 8-bit cheat (lo)
  ld    a,16
  sub   a,e
  ld    l,a
  ld    h,0
  ld    de,_loc_PRINTNUMBER__TEMP
  add   hl,de
  pop   af
  ld    (hl),a
;; LINE #13
  ld    de,(_loc_PRINTNUMBER__BASE)
  ld    d,0  ; 16-bit cheat
  ld    hl,(_loc_PRINTNUMBER__NUMBER)
  call  _div_hl_de
  ld    (_loc_PRINTNUMBER__NUMBER),hl
;; LINE #7
  ld    e,1
  ld    a,(_loc_PRINTNUMBER__I)
  add   a,e
  ld    (_loc_PRINTNUMBER__I),a
  jp    ._label_3
._label_2:
;; LINE #15
  ld    de,16
  ld    hl,_loc_PRINTNUMBER__TEMP
  add   hl,de
  ld    de,(_loc_PRINTNUMBER__CHARS)
  ld    d,0  ; 16-bit cheat
  or    a
  sbc   hl,de
  ld    (_loc_PRINTSTRING__ADDR),hl
  ld    hl,(_loc_PRINTNUMBER__CHARS)
  ld    h,0  ; 16-bit cheat
  ld    (_loc_PRINTSTRING__LEN),hl
  call  _PRINTSTRING
;; LINE #3
  ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PROC: MAIN
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
_MAIN:
;; LINE #29
  ld    hl,9679
  ld    (_loc_PRINTNUMBER__NUMBER),hl
  ld    a,10
  ld    (_loc_PRINTNUMBER__BASE),a
  ld    a,5
  ld    (_loc_PRINTNUMBER__CHARS),a
  ld    a,1
  ld    (_loc_PRINTNUMBER__ZEROSUPPRESS),a
  call  _PRINTNUMBER
;; LINE #30
  ld    hl,9679
  ld    (_loc_PRINTNUMBER__NUMBER),hl
  ld    a,10
  ld    (_loc_PRINTNUMBER__BASE),a
  ld    a,5
  ld    (_loc_PRINTNUMBER__CHARS),a
  ld    a,0
  ld    (_loc_PRINTNUMBER__ZEROSUPPRESS),a
  call  _PRINTNUMBER
;; LINE #28
  ret
User avatar
g0blinish
Manic Miner
Posts: 289
Joined: Sun Jun 17, 2018 2:54 pm

Re: PL/M cross-compiler for ZX Spectrum

Post by g0blinish »

kool, as I see you avoid 8080 opcodes 8-)
User avatar
Guesser
Manic Miner
Posts: 642
Joined: Wed Nov 15, 2017 2:35 pm
Contact:

Re: PL/M cross-compiler for ZX Spectrum

Post by Guesser »

ketmar wrote: Fri May 13, 2022 6:03 pm but the target platform will be ZX Spectrum, not some CP/M machine.
Oh boo, I thought this meant there might actually be some software to run on CP/M on my +3 one day :lol:
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: PL/M cross-compiler for ZX Spectrum

Post by ketmar »

g0blinish wrote: Wed May 18, 2022 5:37 pm kool, as I see you avoid 8080 opcodes 8-)
i don't even remember 8080 mnemonics anymore. ;-) anyway, this codegen will be completely rewritten later, for now i simply made something that allowed me to write "it works!" post here. ;-)
Guesser wrote: Wed May 18, 2022 5:49 pm Oh boo, I thought this meant there might actually be some software to run on CP/M on my +3 one day :lol:
it won't be a problem to add CP/M support later. but my primary goal is writing games for 48K Speccy. PL/M is slightly "closer to the 8-bit metal" than C, and if my ideas for codegen will work as i expect them to work, the generated code will be slightly smaller and faster than that of z88dk.

actually, the compiler itself doesn't assume anything about the machine at all (except that it is using Z80 CPU), so adding CP/M support should be as easy as writing a runtime library for it.
User avatar
ketmar
Manic Miner
Posts: 732
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: PL/M cross-compiler for ZX Spectrum

Post by ketmar »

got distracted by sudden game engine, sorry. scroll link up a little to read some tech description of it. maybe i'll include it into compiler distribution, so people will be able to create platformers almost out-of-the-box. ;-)

p.s.: version published there isn't final by any means. current one is better optimised (one t-state here, two t-states there… ;-), i'm finishing collision detection implementation, and cleaning the code. so there will be new demo soon (i hope). maybe i'll create a separate thead for it later.
Post Reply