Subtilis: A new BASIC compiler for RiscOS

handy tools that can assist in the development of new software
Post Reply
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

You know how it is. You find a load of old computers in your dad's garage. You spent ridiculous amounts of time and money trying to fix them up, connect them to modern displays and find room for them. You then sit down to write that game you always wanted to write as 15 year old but never quite knew how. Here's how this scenario played out in my small London home in 2017.

"Now how do I do structures in BASIC again. Oh wow" and then ten minutes later "H'mm, this is kind of slow. What I really need is some sort of compiler". Three years later my A3000 port of Rebelstar is still no more than a few peeks and pokes in a tokenized BASIC file; but I have written a compiler...

Now before anybody gets too excited, note the following.

1. This is not a BBC BASIC compiler. The language, Subtilis, ressembles and is inpsired by BBC BASIC, but it is not and will never be compatible with it. For one thing, it parses ASCII files rather than tokenized BASIC files. In addition, the grammar is slightly different in places, there's a ton of keywords that aren't implemented and will never be implemented, e.g., GOSUB, READ, DATA, EVAL (well I might do a cut down version of EVAL at some point), RESTORE and many others. As time goes on, and I add more features to Subtilis, it's likely to diverge further and further from BBC BASIC.

2. It's massively untested and massively unfinished. A whole pile of the keywords are missing, including INPUT! There's no optimizer, no linker (so you're currently restricted to a single source file), no assembler, no error-recovery (so you get one terse parse error and that's it), and very little documentation, etc. There are also a pile of known bugs that I haven't got around to fixing yet (and presumably lots of unknown ones). To make matters worse, I still haven't implemented structures. I have just finished implementing strings though, and since you can now write Hello World in Subtilis, I thought the time had come to post about it.

Given the current state of the project, I'm not going to release any binaries for the compiler just yet, but for those of you among us who enjoy messing around with other people's, buggy, unfinished, under-documented code, then head on over to https://github.com/markdryan/subtilis. I'll leave the rest of you with the following attachment, compiled this morning with Subtilis. It's a statically compiled, absolute binary. To run, unzip it, copy the file to your Archimedes or RiscPC of choice, set the file type to Absolute and double click on it (at your own risk).
Attachments
RunImage.zip
(2.39 KiB) Downloaded 35 times
User avatar
IanS
Posts: 1734
Joined: Mon Aug 31, 2009 7:02 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by IanS »

markdryan wrote:
Fri Aug 07, 2020 1:28 pm
I'll leave the rest of you with the following attachment, compiled this morning with Subtilis. It's a statically compiled, absolute binary. To run, unzip it, copy the file to your Archimedes or RiscPC of choice, set the file type to Absolute and double click on it (at your own risk).
That works. Do you have the source for that, to see how it compares to BBC Basic?
User avatar
daveejhitchins
Posts: 6447
Joined: Wed Jun 13, 2012 6:23 pm
Location: Newton Aycliffe, County Durham
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by daveejhitchins »

Never thought I'd see the day :shock:

Well done . . .

Dave H.
User avatar
davidb
Posts: 3076
Joined: Sun Nov 11, 2007 10:11 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by davidb »

It's always good to see people experimenting with languages and compilers. :)

Well done on your achievements so far! :D
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

Whenever, I tell anyone I'm writing a compiler, the first thing they ask me is how's the performance. So to pre-empt this question let me start by saying the performance is okay but not great. This is understandable really as there's no optimizer yet and the register allocator is, to be polite, a little naive. Integer performance is reasonably okay, floating point performance is awful (although it might be okay if you have an FPA accelerator, which I don't) and string performance is mixed. My plan is to add the missing features to the language, build the toolset and then establish a large body of Subtilis source code I can use to test and benchmark the compiler. When I have this, I'll do the optimizer and re-write the register allocator. With this in mind, I've started to collect a set of benchmarks in a repository (https://github.com/markdryan/basic-benchmarks). Most of these benchmarks were originally written by people on the forum and it's not clear to me how they're licensed. If anyone would like me to remove the code from my repo, please let me know. The original source of each of the benchmarks is noted in comments at the top of the code.

Anyway, here are the benchmark results for BBC Basic V (RiscOS 4.39), Subtilis (68b5f4b), and ABC 4.18. All these results were obtained on my 130MB Strong ARM RiscPC running RiscOS 4.39. All the benchmarks were run directly from the desktop. For these first set of benchmarks, lower scores are better.

Mandel
  • BBC BASIC : 29.2
  • Subtilis : 33.68
  • ABC : 42.92
Here we see the poor performance of floating point. The reason for this is that Subtilis is using the FPA instruction set. My RiscPC doesn't have an FPA accelerator so the floating point is handled by the FPE, which seems to be very slow. On the plus side, Subtilis is performing better than ABC here, which also uses the FPE. Also note that Subtilis is using 64 bit floats as opposed to 32 bit floats used by ABC and 40 bit floats by BBC BASIC. So you get a little bit more accuracy here.

Fannkuch
  • BBC BASIC : 625
  • Subtilis : 25
  • ABC : 39
Both compilers do well on this one. Subtilis is 25x times faster than BBC BASIC on this benchmark.

Life
  • BBC BASIC : 1535
  • Subtilis : 211
  • ABC : n/a
This benchmark wouldn't compile under ABC. It doesn't seem to support swap on arrays. However, we can see Subtilis is about 7x faster than BBC BASIC here.

Sieve
  • BBC BASIC : 26
  • Subtilis : 5
  • ABC : 9
Here we see a 5x speed improvement.

Finally, I ran some of the micro benchmarks in ClockSp through BBC BASIC on the RiscPC and the compilers. The results here are more mixed, although we see the same patterns; integer good, floating point awful, strings okay. Note I had to disable some of the tests for ABC as it was optimising the entire tests away, which is quite impressive and is something that Subtilis cannot yet do. Going forward though I guess we should be able to optimise away some of these benchmarks. In the meantime, here are the results. Note in these benchmarks, bigger is better.

Real REPEAT Loop
  • BBC BASIC : 2204
  • Subtilis : 1389
  • ABC : 1322
Integer REPEAT loop
  • BBC BASIC : 1659
  • Subtilis : 34142
  • ABC : 7468
Real FOR loop
  • BBC BASIC : 2064
  • Subtilis : 208
  • ABC : n/a
Yikes, this one is horrible. The local register allocator in conjunction with the FPE yield a horrible result.

Integer FOR loop
  • BBC BASIC : 1854
  • Subtilis : 3869
  • ABC : n/a
Trig/Log Test
  • BBC BASIC : 12509
  • Subtilis : 1600
  • ABC : 1563
String Manipulation
  • BBC BASIC : 3277
  • Subtilis : 6165
  • ABC : 6062
Note, I've modified this benchmark to use DIV instead of /, otherwise the results for ABC and Subtilis are skewed by the floating point calculations. Also, note that Subtilis's strings are unlimited in length, so it's at a disadvantage in this benchmark.

Procedure Call
  • BBC BASIC : 3571
  • Subtilis : 9252
  • ABC : 2605
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

daveejhitchins wrote:
Fri Aug 07, 2020 1:52 pm
Never thought I'd see the day :shock:
Neither to be honest, did I.
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

IanS wrote:
Fri Aug 07, 2020 1:45 pm
That works. Do you have the source for that, to see how it compares to BBC Basic?

Code: Select all

MODE 2

LOCAL DIM coords(43)
coords() = 0,0,0,2,0,-2,0,4,0,-4,0,5,0,-5,
    -2,2,2,-2,-2,-2,2,2,
     3,3,4,4,
     3,-3,4,-4,
    -3,-3,-4,-4,
    -3,3,-4,4,
    10,-6,-255,-255

VDU [23;8202;0;0;0;]
B%=0
FOR C%=0 TO 15
  IF C%=8 THEN B%=7 ENDIF
  VDU [19,C%,B%;0;]
NEXT

VDU [19,2,2;0;19,10,2;0;]
VDU [19,4,3;0;19,12,3;0;]
VDU [19,6,1;0;19,14,1;0;]
VDU [29,640;564;]

p=0.001

G%=0
PROCcircle(PI/20,0,2,0)
PROCcircle(PI/20,PI/5,4,0)
PROCcircle(PI/20,-PI/5,6,0)
S%=40
GCOL 0,8
counter% := 0
LOCAL X%
REPEAT
  X% = coords(counter%)
  Y% := coords(counter%+1)
  counter% += 2
  IF X%<>-255 THEN PROCsquare(X%,Y%) ENDIF
UNTIL X%=-255

G%=1
PROCcircle(PI/20,0,2,PI)
PROCcircle(PI/20,PI/5,4,PI)
PROCcircle(PI/20,-PI/5,6,PI)
C%=0
D%=1
REPEAT
    T% := TIME
    VDU [19,2+C%,2;0;19,10+C%,2;0;]
    VDU [19,2+D%,0;0;19,10+D%,7;0;]
    VDU [19,4+C%,3;0;19,12+C%,3;0;]
    VDU [19,4+D%,0;0;19,12+D%,7;0;]
    VDU [19,6+C%,1;0;19,14+C%,1;0;]
    VDU [19,6+D%,0;0;19,14+D%,7;0;]
    D%=C%
    C%=C%EOR1
    REPEAT UNTIL TIME>8+T%
UNTIL FALSE

DEF PROCsquare(X%,Y%)
  Y%=Y%-2
  MOVE X%*S%+S%,Y%*S%-S%
  MOVE X%*S%-S%,Y%*S%-S%
  PLOT 85,X%*S%+S%,Y%*S%+S%
  PLOT 85,X%*S%-S%,Y%*S%+S%
ENDPROC

DEF PROCcircle(xr,zr,c,b)
  C%=0
  FOR a := b TO PI+b STEP PI/48
    GCOL G%,c+C%
    Z% := SIN(a)*450
    X% := COS(a)*450
    Y% := 0
    IF xr <> 0 THEN
        T1% := COS(xr)*Y%+SIN(xr)*Z%
        T2% := -SIN(xr)*Y%+COS(xr)*Z%
        Y% = T1%
        Z% = T2%
    ENDIF
    IF zr<>0 THEN
        T1% := COS(zr)*X%+SIN(zr)*Y%
        T2% :=- SIN(zr)*X%+COS(zr)*Y%
        X% = T1%
        Y% = T2%
    ENDIF
    X%=X%/((Z%+1000)*p)
    Y%=Y%/((Z%+1000)*p)
    PLOT 69,X%,Y%
    PLOT 69,X%,Y%+4
    C%=C% EOR 1
  NEXT
ENDPROC
Needless to say I didn't write this from scratch. It's a modified version that appeared on the stardot logo thread. I think I modified this one (viewtopic.php?f=5&t=19431&hilit=stardot+logo#p270353) by ChrisB.

Note the lack of a DATA statement, the square brackets around the VDU statements, and variable declaration with := which creates a local variable.
User avatar
Dave Footitt
Posts: 963
Joined: Thu Jun 22, 2006 10:31 am
Location: Abandoned Uranium Workings
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by Dave Footitt »

Looks interesting Mark I really enjoyed your talk on it =D>
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

There's been a few little updates to Subtilis since I last posted. I've fixed some bugs and added two new keywords; GET$ and VAL.

VAL is one of those compiler unfriendly keywords where the return type of the expression it generates cannot always be known at compile time. The compiler has to assume that it will be a floating point expression and so VAL is implemented by converting a string to a floating point number using floating point arithmetic, which is wonderfully slow on most Archimedes. For this reason, I've added variant of VAL which takes an extra parameter. The extra parameter denotes the base of the number represented by the string in the first parameter, and when specified, forces Subtilis to perform a string to integer conversion using integer arithmetic. For example,

Code: Select all

print val("256", 10)
print val("100", 16)
will print

Code: Select all

256
256
Valid values for the base are 2-10 and 16. The floating point version of VAL doesn't yet support 'E', but at least this is consistent with the lexer which doesn't either.

I was planning to implement INSTR next, but I've been working on strings for almost 6 months now and am suffering from a case of string fatigue. So instead, I'm going to start looking at SYS and the assembler.
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

I've added support for SYS calls. SYS calls mostly work as expected with one big limitation. The id of the SYS call needs to be known at compile time. It can be an integer or a string, but it needs to be constant. So you can do

Code: Select all

sys &6a
or

Code: Select all

sys "OS_Reset"
but not

Code: Select all

a% := &6a
sys a%
On the plus side, knowing the id of the SYS call at compile time allows us to perform some optimisations. Firstly, the conversion from the name of the SYS call ("OS_Reset") to the integer ID (&6a) needed to generate the SWI instruction can be done at compile time. The two code samples above then generate the exact same code. The other advantage is that the compiler knows which registers the various SYS calls corrupt, and it passes this information to the register allocator. This means that we don't necessarily need to preserve and restore all registers when making a SYS call. For example, if we do a

Code: Select all

sys "OS_Write0", "hello world"
The compiler knows that "OS_Write0" only corrupts R0 and so it only needs to preserve and restore R0 and it will only do that if the value in R0 is still needed by the function. This works better for some SYS calls than others. It's not so useful for SYS calls that have multiple entry points such as "OS_SpriteOP", but it does still help a little. A maximum of 6 registers will be preserved and restored when making a call to "OS_SpriteOp".

The other problem is that to make this work, the compiler needs to know about all the RiscOS SYS calls, and for the time being it only knows about a subset of them. For example, it doesn't currently know anything about the WIMP. I'll add these missing calls over time. I'll probably also allow the programmer to make a sys call using a constant integer ID that the compiler doesn't recognize. Such calls will be slower however as the compiler will need to assume all the registers have been corrupted.

For more information about SYS calls in Subtilis check out https://github.com/markdryan/subtilis/b ... lis.md#sys.

Now we can do SYS calls, we can do sprites. So there's probably enough working in Subtilis to write a simple (silent) game now. I did consider giving this a go, but decided that Subtilis was still a bit too irritating to develop in right now, so I'll continue with the language improvements and missing features for the being. Next on the list is GCOL TINT and COLOUR TINT.
PMF
Posts: 12
Joined: Sun Sep 06, 2020 2:13 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by PMF »

Hi I'd be interested to see how you arrived at some of your figures for ABC, and in particular if you chose to make use of some of its optional optimisation features.

There are a bunch of compiler directives that can be used to tell it that you know what you are doing, and that strict adherance to some of the features of BBC Basic can be skipped. These make a huge difference to the overall speed.

For example -- you can turn off Array Bounds Checking - so that it doesn't generate loads of tediuos compare instructions for all the array indecies.

-- You can turn off Zeroing of the contents of local variables - which speeds up entry to proceedures.

-- And if you are using ! operators you can promise that they are 4-byte aligned and so it can use just one instruction rather than the whole song-and-dance sequence.

To do these try REM {NOZEROLOCALS} and REM {ALIGNEDPLING} and REM {NOARRAYCHECK} in the start of your program - Might be interesting

Paul
Paul Fellows
Head of Languages, Acornsoft, 1983-85
Microsystems Software Manager, Acorn 85-88
PMF
Posts: 12
Joined: Sun Sep 06, 2020 2:13 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by PMF »

In fact here are a list of the handy compiler directives that you can use to tune the code that ABC Generates

REM {NOSTACKCHECK} turn off stack checking - this speeds up PROC and FN calls a lot

REM {NOESCAPECHECK} turns off the placement of a call to SWi OS_EscapeCheck between each statement. This is necessary for the strict behaviour, but very slow. If you want to escape from the code use this with its partner REM {ESCAPECHECK} selectively to control where the checks get done

REM {NOGOTOSUSED} If you promise me not to use GOTOs then I can remember whats in registers from one statement to another, without fear of you jumping into the middle and stuffing up. It also means I don't need to add a table of where all the GOTO targets might be on the end of your code, so it will be smaller.

REM {NOZEROLOCALS} Don't labouriously write zero into every newly instanced local varaible only to have it overwritten shortly afterwards.

REM {SYSCONSTONLY} and REM {SYSKNOWNONLY} With these I can guarentee to generate the native SWI call rather than code to have to go and look up the name of the SWI at runtime.

REM {ALIGNEDPLING} promise that all your use of ! is 4-byte aligned and I'll generate LDR and STR instructions that assume it too. Otherwise, we get to pick up the bytes one at a time and mash them together. Again, you could use these selectively arount different bits of code if there were certain sections that could be relied on the be aligned and others that might not be.

REM{NOARRAYCHECK} dont bother checking array indecies for out of bounds. Saves a reasonable number of instructions for array accesses

A bunch of these are ganged-together by the -quick option which can be added to the command line and which there was once a UI check box for in version 3 of the compiler, I think it has vanished in later ones.

There are more of these directives to deal with more code-saving measures... such as not clearing out registers on return from a SWI call

Anyway, just interested to see how this sits with what your doing - for me there was always the conflict between "Compatibility" with the range of things that could be writen for the interpreter and "Performance" where dropping certain checks saved lots of code - my answer was to default to the former, but offer the latter through the simple use of these directives.
Paul Fellows
Head of Languages, Acornsoft, 1983-85
Microsystems Software Manager, Acorn 85-88
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

Paul, thanks for all the information. This is excellent. Someone to discuss BASIC compilers with :-).

The sources for all the benchmarks I used are here https://github.com/markdryan/basic-benchmarks. I built them using the version of ABC that came with my copy of the DDE purchased from ROOL about 3 years ago. The version number of the DDE is 28d. I didn't enable any specific optimisations when compiling with ABC. I just dragged the BASIC programs to the ABC icon and executed the resulting binary. Reading your posts I think I need to re-run the ABC benchmarks with some of these optmisation enabled to get a fairer result. I'll try this out at the weekend.

These ones I'm not going to enable

REM {NOARRAYCHECK}
REM {NOZEROLOCALS}
REM {ALIGNEDPLING}

Subtilis always bounds checks arrays and always zeros locals and arrays and there's no way to tell it not to do so. Also, Subtilis doesn't support the indirection operators so none of the benchmarks I ran make use of these.

These I am

REM {NOESCAPECHECK}
REM {ESCAPECHECK}

Subtilis does check for the escape button but only does so at the start of each iteration of a loop and on entries to procedures and functions. So I can modify the ABC BASIC benchmarks to do the same thing to make things fairer. This is going to make a big difference I imagine.

REM {NOGOTOSUSED}

Subtilis doesn't support GOTO and so it's only fair to enable this.

REM {NOSTACKCHECK}

I need to enable this as well. It's on my TODO list, but right now, Subtilis doesn't check for stack overflow on procedure call. It will happily overwrite the heap for fun and profit. So for the time being I will enable this one to make things fairer.

REM {SYSCONSTONLY} and REM {SYSKNOWNONLY}

Subtilis doesn't support non constant SWI names, either strings or integers, so it's only fair to enable this one as well.
There are more of these directives to deal with more code-saving measures... such as not clearing out registers on return from a SWI call
This one might be worth enabling. What does it do exactly? Subtilis only preserves and restores the registers that may be corrupted by a syscall and also are still live, i.e., still needed by the function. The other registers remain unchanged by a call to SYS. It also doesn't zero any of the registers not explicitly specified in the input or output lists, which BBC BASIC appears to do. I actually wasted a couple of hours the other day trying to figure out why the rocks example in chapter 4 of the Archimedes Game Makers Manual (http://www.riscos.com/support/developer ... .htm#l0046) didn't work in Subtilis. The reason was that the rocks example doesn't provide all the trailing parameters expected by some of the SYS calls it makes. It seems to assume that the parameters it doesn't pass will default to zero, and in BBC BASIC they appear to do so.
Anyway, just interested to see how this sits with what your doing - for me there was always the conflict between "Compatibility" with the range of things that could be writen for the interpreter and "Performance" where dropping certain checks saved lots of code
I guess things are simpler for me as I broke compatibility with BBC BASIC on day one when I decided to use text rather than tokenized source files. Since then I've taken all sorts of liberties with the language.

Regarding compiler options, currently there are none. I will probably have to add some, like the ability to turn off escape checking, but I intend to keep these few in number. I haven't written the optimizer yet, but when I write it, it will always be on. There won't be any way to disable it. My current plan is that the options that are provided would be specified in a text file in the same directory as the rest of the source code, rather than inline in the code or via compiler options. None of this is implemented yet.

I have some questions about ABC, if you have time

1. How do you generate sys calls when the SYS call id is not known at compile time? Are you using self modifying code?
2. I noticed that ABC was executing some of the ClockSP benchmarks in 0 seconds, causing a division by zero error in ClockSP itself. Does it optimise away code like

Code: Select all

FOR i% = 0 TO 10000 : NEXT i%
to

Code: Select all

i% = 10000
?
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

I've been playing around with the optimisation options in ABC a little and the results are quite interesting. The first thing I tried was to enable the following options:

Code: Select all

REM {NOESCAPECHECK}
REM {NOGOTOSUSED}
REM {NOSTACKCHECK}
REM {SYSCONSTONLY}
REM {SYSKNOWNONLY}
Additionally, I placed a

Code: Select all

REM {ESCAPECHECK}
at the start of every procedure, function and loop. As far as I can tell, this set of options matches most closely what Subtilis does. I then re-ran some of the larger benchmarks but didn't see much difference.


Mandel:

  • Subtilis : 33.68
  • ABC : 42.92
  • ABC Opt Escape 42.93


Fannkuch:

  • Subtilis : 25
  • ABC: 39
  • ABC Opt Escape : 39


Sieve:

  • Subtilis : 5
  • ABC : 9
  • ABC Opt Escape: 9



I then removed all the

Code: Select all

REM {ESCAPECHECK}
statements in the ABC code and also disabled escape checking in Subtilis. There's an internal setting in Subtilis that allows you to do this, although it's not currently exposed to the user so requires a recompile to disable. This had a dramatic effect on ABC's performance in two of the benchmarks. Remember, smaller is better for these benchmarks.


Fannkuch:

  • Subtilis : 25
  • Subtilis No Escape : 21
  • ABC: 39
  • ABC Opt No Escape : 17


Sieve:

  • Subtilis : 5
  • Subtilis No Escape : 5
  • ABC : 9
  • ABC Opt No Escape: 5


Mandel:

  • Subtilis : 33.68
  • Subtilis No escape : 33.52
  • ABC : 42.92
  • ABC Opt No Escape 42.07


As the results for Sieve seemed to be tied I created a new version of the benchmark which increased the maximum number of iterations to 500000 to see whether there were any differences between the compilers.

Sieve 500000:
  • Subtilis No Escape : 27
  • ABC Opt No Escape: 30


It turns out there was a small difference.

In summary, ABC seems to be quite far ahead now in the Fannkuch benchmark, Subtilis is still far ahead in the mandel benchmark and it also does a little bit better in Sieve.

I also re-ran ClockSp with escape handling turned off for both compilers.

Subtilis No Escape
  • Real REPEAT Loop: 1408
  • Integer REPEAT Loop: 70294
  • REAL FOR Loop: 209
  • Integer FOR Loop: 6768
  • Trig/Log Test: 1598
  • String Manipulation: 6223
  • Procedure Call: 16735


ABC No Escape
  • Real REPEAT Loop: 1448
  • Integer REPEAT Loop: 59750
  • REAL FOR Loop: n/a
  • Integer FOR Loop: n/a
  • Trig/Log Test: 1570
  • String Manipulation: 6217
  • Procedure Call: 21756



ABC is doing better than Subtilis in general on these microbenchmarks. It's ahead on the for loops and procedure call but behind on the integer REPEAT loop. There's not much difference between the compilers with the other benchmarks.

It's hard to draw too many conclusions from this limited set of benchmarks, but the trend I'm seeing here is that ABC's escape handling seems to have a much bigger impact on the benchmark results than Subtilis's, even when both compilers are checking for escapes at the same locations in the code. With escape handling turned off there's not too much difference between the compilers. Subtilis does better on some benchmarks, ABC on others.

Regarding escape handling, Subtilis checks for the escape button at the entry to every loop, procedure and function call. It does so however, not by calling OS_EscapeCheck, but by reading an internal variable, stored in memory that is set by an escape handler, initialised right at the start of the program. Perhaps, this explains the apparent difference in performance of escape handling between the two compilers.

The source code for all the benchmarks I ran can be found here: https://github.com/markdryan/basic-benchmarks. I used the same version of the compilers that were mentioned in my initial benchmark post above. I'll update this original post later on with the results for escape checking disabled.
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

Subtilis now has an assembler. Currently, it supports all the ARM2 instructions and most of the FPA instructions. WFC, RFC, LFM and SFM aren't implemented yet. The assembler works differently to the BBC BASIC assembler. Assembly takes place at compile time rather than runtime. Inline assembly is not supported but it is possible to mix BASIC and assembly code in the same source file. Functions and procedures may be written in one of two different languages; BASIC or ARM assembly. The assembler is strongly typed and supports strings, integers, doubles, integer registers, FPA registers and identifiers. It also supports most of the BASIC expression syntax and functions, e.g, STRING$ and COS. There are no macros but there is a simple FOR loop for generating code at compile time. You can also define constants. Here's an example of what the assembler looks like

Code: Select all

MODE 1
PROCbanner

def PROCbanner
[
	def screen_width = 40
	def header = STRING$(screen_width,"*")
	def gap = STRING$(13," ")

	ADR R0, header_label
	SWI "OS_Write0"
	SWI "OS_NewLine"
	ADR R0, message
	SWI "OS_Write0"
	SWI "OS_NewLine"
	ADR R0, header_label
	SWI "OS_Write0"
	SWI "OS_NewLine"
	MOV PC, R14

header_label:
	EQUS header
message:
	EQUS "*" + gap + "Hello World!" + gap + "*"
]
Note the use of def (borrowed from ABC) to define constants, named sys calls which are translated at compile time, STRING$ and string concatenation.

There's an example of the FOR loop here https://github.com/markdryan/subtilis/b ... s/demo#L54 where it's used to create a table of coordinates for a SIN wave at compile time and also here https://github.com/markdryan/subtilis/b ... es/regs#L3 where it's used to initialise a bunch of registers.

There are still a few things missing, such as not being able to call other functions and procedures from an assembly language procedure. However, there's enough of it working now for me to move onto the next phase of the project. There's a small amount of documentation available about the assembler here: https://github.com/markdryan/subtilis/b ... #assembler.

Some fun facts about Subtilis. It's now almost 60000 lines of C code. It takes 2.5 seconds to build on my Haswell laptop, 9.5 minutes to build with the DDE on a StrongARM RiscPC. The RiscOS binary, compiled with -Otime, is 177kb. It runs fairly well on the RiscPC with most examples, which in fairness are all pretty small at the moment, taking about a second to compile. It does run on an A3000 but it's a bit too slow. The Fannkuch benchmark https://github.com/markdryan/basic-benc ... /FannkuchS takes 50 seconds to compile. The bottleneck in the compiler is the register allocator so hopefully things will speed up when I re-write it next year. The assembler, which doesn't use the register allocator is much quicker and is probably usable on the A3000 right now, as long as you keep the BASIC to a minimum. This small example https://github.com/markdryan/subtilis/b ... s/arm_fill takes 5 seconds to build on the A3000.
Last edited by markdryan on Fri Jan 08, 2021 9:51 pm, edited 1 time in total.
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

Subtilis now supports the native ARM copro of PiTube direct, which is fun. It also emits VFP2 code for floating point operations, so for the first time, floating point is fast in Subtilis, rather than crippling slow. The Mandelbrot benchmark ( https://github.com/markdryan/basic-benc ... ter/mandel ) takes 9.5 seconds to run in ARMBASIC on my PiZero. The compiled Subtilis version takes only .27 seconds.

Unfortunately, this isn't as useful as it sounds, at least not yet. Most prorgams I've tried on the Native copro are IO bound instead of CPU bound, so they don't actually run any faster when compiled with Subtilis, even though the computations are much faster. Also, as VFP2 doesn't support things like COS or SIN, some of the math functions aren't currently implemented. The PiTubeDirect compiler will assert if you try to build a Subtilis program that uses these functions at runtime. In addition, there's still the small problem that Subtilis itself isn't very useful at the moment. You're still limited to a single source file, and some important language features, such as file handling aren't implemented yet.

Still, the assembler has been updated to support most of the VFP2 instructions. MSR and MRS have been added as well. The rest of the ARMv6 instructions are missing but will be added over time. At some point I'll also get around to implementing COS, SIN and the other missing functions. For now though, I'm going to work on finishing off the language, starting with improvements to error handling and then file handling.

When you build Subtilis you now get two compilers:
  • subtro - the RiscOS 3 and 4 compiler, previously called basicc
  • subtptd - the Native ARM copro compiler
subtptd compiles programs to run at f000 and requires the Gecko release to work properly. The compiled programs will run on earlier versions of PiTubeDirect but some things like escape handling won't work.

There's an example of a binary created with the new compiler, along with instructions of how to run it, here: viewtopic.php?f=29&t=21358#p302284
User avatar
BigEd
Posts: 4046
Joined: Sun Jan 24, 2010 10:24 am
Location: West Country
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by BigEd »

markdryan wrote:
Fri Jan 08, 2021 9:51 pm
Subtilis now supports the native ARM copro of PiTube direct, which is fun. It also emits VFP2 code for floating point operations, so for the first time, floating point is fast in Subtilis...
...
...as VFP2 doesn't support things like COS or SIN, some of the math functions aren't currently implemented.
As a possibly useful reference, the Raspberry Pi Pico's boot room has math function implementations, and it's BSD licensed which should be compatible with other open source licenses. See here and nearby. (The basic math functions have special implementations, which won't be portable or necessarily applicable, but if the higher level functions build on the basics in a straightforward way, they could instead be built on top of VFP2. Perhaps.)

[Thanks to Dominic for the pointer. See also 2.7.2 in the Pico SDK doc (pdf).]
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

BigEd wrote:
Sat Feb 06, 2021 3:02 pm
markdryan wrote:
Fri Jan 08, 2021 9:51 pm
Subtilis now supports the native ARM copro of PiTube direct, which is fun. It also emits VFP2 code for floating point operations, so for the first time, floating point is fast in Subtilis...
...
...as VFP2 doesn't support things like COS or SIN, some of the math functions aren't currently implemented.
As a possibly useful reference, the Raspberry Pi Pico's boot room has math function implementations, and it's BSD licensed which should be compatible with other open source licenses. See here and nearby. (The basic math functions have special implementations, which won't be portable or necessarily applicable, but if the higher level functions build on the basics in a straightforward way, they could instead be built on top of VFP2. Perhaps.)
Thanks for the link Ed. This sounds very interesting.

I haven't made any progress with the compiler itself recently, but there have been a few updates to the assembler. It now understands some of the newer instructions supported on the PiZero that aren't present on the Archimedes (well not on my A3000 at least). MSR/MRS are now supported as are the miscellaneous load and store instructions, e.g., LDRSB, LDRSH, and all the SIMD instructions. None of these instructions, apart from MSR/MRS, are currently used by the compiler, but I might make use of the SIMD instructions when I get around to implementing byte arrays.
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

I've made a bit of progress over the last month.

Byte Data Types

I've added two new types, byte and arrays of byte. I borrowed the & suffix used in other basics to denote the byte type, but just to confuse you all, I made the type signed rather than unsigned. This only really matters when converting from byte to other types. I've also added a new keyword, INTZ that allows you to explicitly zero extend rather than sign extend. For example,

Code: Select all

b& := true
print b&
prints -1, whereas

Code: Select all

b& := true
print intz(b&)
prints 255

To support sign extension on the PDT build, the assembler now understands three new mnemomics; SXTB, SXTB16, SXTH. INTZ will be faster than implicit conversion on the RiscOS build, which lacks access to the sign extend instructions.

Byte Based File Handling

Basic file handling is now supported. OPENUP, OPENIN, OPENOUT, EXT#, PTR#, BGET#, BPUT#, and CLOSE# are now all supported. The syntax for PTR# when setting the file pointer is a little different to BBC Basic's syntax. A ',' is used rather than an '=' to separate the file handle from the location.

Try and Tryone

When an error occurs in Subtilis, normal execution of the procedure in which the error occurs is terminated and control jumps to the nearest error handler. The error can be consumed by the error handler or propagated, but normal execution of the procedure cannot be resumed. The try and tryone keywords allow you to override this behaviour. Try is a compound statement that can be used as either a statement on an expression. If an error occurs within a try block the try block stops executing and returns an error code (if used in an expression). The procedure that executed the try statement can then proceed to execute normally. Tryone is similar to try except that it only catches errors produced by the following statement. Here's a quick example of how you might use tryone to retry a failing function a number of times.

Code: Select all

retry% := 0
while tryone PROCMayFail and retry% < 5
  retry% += 1
endwhile
One place where the try statement is needed is inside error handlers. Subtilis doesn't allow statements that can generate implicit errors to be used inside error handlers, unless they are enclosed within a try block. So

Code: Select all

onerror
  PROCCleanup
enderror
will not compile whereas

Code: Select all

onerror
  tryone PROCCleanup
enderror
will and will ignore any error that may have been generated by PROCleanup.

Over the next few weeks, I'm going to continue working on file handling and arrays. I'll probably implement OSCLI and SWAP as well.
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

I've been working on arrays and file handling lately. Some new keywords have been added, some bugs fixed and some new liberties have been taken.

OSCLI

OSCLI is now implemented. *command isn't however. The grammar just can't cope with it. I guess I could introduce a form of * command using a different character that isn't used, say '|' for example, but I guess I'll stick with just OSCLI for now.

Memset

BBC BASIC V array memsetting syntax has now been implemented, e.g., to set the contents of an entire array to a single value you can now type

Code: Select all

a%() = 1
Previously in Subtilis this code merely set the first element of the array to 1. Now it sets all of them.

COPY

A new keyword, copy, has been introduced that in most cases does a memcpy between its arguments. It allows you to copy the contents of an array or string to another array or string. This should be faster than manually copying the elements using a loop as it eliminates most of the bounds checking, although I haven't actually benchmarked this. The copy command allows you to copy between variables of different types in limited ways. For example to copy between a byte array and a string one might write.

Code: Select all

copy b&(), a$
GET# and PUT#

Two new keywords, GET# and PUT# have been added that permit reading and writing of blocks of data from and to files. For example, to read an entire file into a string in Subtilis one might write.

Code: Select all

print FNReadFile$("README")

def FNReadFile$(fname$)
  f% := openin(fname$)
  onerror
    tryone close# f%
  enderror
  a$ := string$(ext#(f%), " ")
  read% := get#(f%, a$)
  tryone close# f%
<- a$
Note that it's not currently possible to write a similar function that returns an array of bytes as it's not currently possible to create a zero length array (which might happen if the file was empty). I plan to fix this next by allowing single dimensional arrays to be created with a length of 0, appended to and sliced (if I can get it to work).
Last edited by markdryan on Mon May 03, 2021 7:28 pm, edited 2 times in total.
markdryan
Posts: 217
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

markdryan wrote:
Mon Apr 05, 2021 11:24 am
Note that it's not currently possible to write a similar function that returns an array of bytes as it's not currently possible to create a zero length array (which might happen if the file was empty). I plan to fix this next by allowing single dimensional arrays to be created with a length of 0, appended to and sliced (if I can get it to work).
I couldn't, not nicely anyway. I did modify the compiler locally to allow zero length one dimensional arrays whose size could be adjusted after creation, but it just broke too many things. So instead I've opted for a brand new type, the vector. Vectors work like one dimensional arrays except that

1. They are created, indexed and referenced using {} instead of ()
2. It's possible to create a zero length vector
3. Elements can be added to a vector after it has been created using the new append keyword

Zero length vectors can be created by specifying a negative dimension, or no dimension at all, e.g.,

Code: Select all

dim a%{}
dim b${-1}
and can be appended to using the append keyword

Code: Select all

append(a%, 1)
Here's a more useful example that uses vectors to concatenate two files.

Code: Select all

f% := openout("three")
onerror
    tryone close# f%
enderror

put# f%, append(FNReadFile&{}("one"), FNReadFile&{}("two"))

close# f%

def FNReadFile&{}(fname$)
    f% := openin(fname$)
    onerror
        tryone close# f%
    enderror
    local dim data&{ext#(f%) - 1}
    read% := get#(f%, data&{})
    close# f%
<-data&{}
Here, the contents of two files are read into two vectors, the second vector is appended to the first and we write the resulting vector to the output file. Note that the second argument of append can be a vector or an array, in addition to a scalar a value, and there's an expression form of append, which evaluates to its first argument. This is useful when appending to temporary variables as in this example. In practice, you probably wouldn't concatenate two files this way as it's quite wasteful of memory (although maybe you would on PiTubeDirect, where you have lots of memory and small file sizes), but I do like this example as it demonstrates the new features of the language working together. We have the byte data type, the error handlers which ensure no file gets left open if we encounter an error, vectors which allow us to easily concatenate the contents of the two files in memory and deal gracefully with the case where either file may be empty, and the get# and put# statements for reading and writing blocks of data in one go. On the downside, it doesn't look much like BASIC anymore.

So, one problem solved but another one created. It's not really safe to iterate through vectors, which may have no elements, using BASIC's for loop, so I'm going to need a new type of loop for iterating through things. I was planning to add one at some point as a cheap way to avoid some of the bounds checking, but this can't now be put off any longer.
Post Reply

Return to “development tools”