Subtilis: A new BASIC-like compiler for RiscOS

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

Subtilis: A new BASIC-like 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 111 times
Last edited by markdryan on Sun Oct 24, 2021 3:07 pm, edited 1 time in total.
User avatar
IanS
Posts: 2543
Joined: Mon Aug 31, 2009 7:02 pm
Location: UK
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: 7888
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.
Available: ARA II : ARA III-JR/PR : ABR : AP5 : AP6 : ABE : ATI : MGC : Plus 1 Support ROM : Plus 3 2nd DA : Prime's Plus 3 ROM/RAM : Pegasus 400 : Prime's MRB : ARCIN32 : Cross-32
User avatar
davidb
Posts: 3403
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: 267
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: 267
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: 267
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: 998
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: 267
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: 267
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: 267
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: 267
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: 267
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: 267
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: 6287
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: 267
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: 267
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: 267
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: 267
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.
markdryan
Posts: 267
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

Four and a half years have been and gone and Subtilis is still far from finished. It does however, have two fancy new features; range loops and lambda functions.

range https://github.com/markdryan/subtilis/b ... s.md#range

The range keyword introduces a new type of loop. It was primarily created as a safe way to iterate through vectors, which can have zero elements, but it can also be used to iterate through arrays. Here's a small example.

Code: Select all

dim a%(4)
a%() = 0, 1, 2, 3, 4
range v% := a%()
  print v%
endrange
which will print out

Code: Select all

0
1
2
3
4
On each iteration of the loop v% is set to an element of the array. It's possible to provide additional integer variables after the range statement (one per dimension) to hold the indices of the element stored in the first variable. For example, supposing we wanted to double the elements in our array we might write

Code: Select all

range v%, i% := a%()
  a%(i%) = v% * 2
endrange
The range statement works with zero element vectors. For example,

Code: Select all

dim a${}     rem create an empty vector of strings
range v$ := a${}
   print v$
endrange
will print nothing (as the vector is empty). The body of the range loop will not execute at all in this example.

Another nice feature of the range loop is that, in most cases, the compiler can eliminate the bounds checks, resulting in smaller, more efficient code. The binary size of the executable produced by compiling our first example is 48 bytes smaller than this functionally equivalent example that uses a for loop.

Code: Select all

dim a%(4)
a%() = 0, 1, 2, 3, 4
for i% := 0 to dim(a%(),1)
  print a%(i%)
next
Lambda functions, Function Pointers and Higher Order Functions https://github.com/markdryan/subtilis/b ... -functions

Subtilis now supports function pointers, lambda functions and higher order functions. The syntax for these new features takes a bit of getting used to. The difficulty with things like function pointers is that the type of a function pointer can be infinitely complex. A function can return a function that takes two functions as arguments that also return functions and so on. How then do we name a variable that points to such a function in BASIC?

One of the things I really like about BASIC is the way that the type of a variable is included in the variable's name. I find this makes the code really readable. This system works well as BASIC has a simple type system; strings and numeric types and arrays of strings and numeric types. It break down a bit when we add complex types to the language. How can we support complex types like function pointers while continuing to require that a variable's type is included in its name? I'm not sure what the best approach should be here, but this is what I ended up doing.

Before you can use a function pointer or a lambda function you need to declare a new type. A type is declared using the type keyword. For example,

Code: Select all

type FNCombine%(a%,b%)
defines a new type called FNCombine that can be used to declare variables that point to a function that accepts two integers as arguments and returns an integer. We can create a variable with this type and assign a lambda function to it as follows.

Code: Select all

ptr@FNCombine = def FN%(a%, b%) <- (a% * b%) + b%
The @ character separates the variable prefix from the type name. The variable name includes the type information as you would expect, i.e., the name of our new variable is ptr@FNCombine. The lambda function is just like a normal function definition without a name (but with a return type) used in an expression context.

We can then invoke the function through our new variable like this

Code: Select all

print ptr@FNCombine(1,2)
which will print 4. The variable ptr@FNCombine does not contain the full type information. This is not possible in the general case, but we can at least see that it is a variable that holds a pointer to a function. To figure out what the type of that function is we need to look for the type definition.

You can also assign the value of an existing named function to a function pointer using the ! operator, e.g,

Code: Select all

ptr@FNCombine = !FNSub%
print ptr@FNCombine(1,2)

def FNSub%(a%, b%) <- a% - b%
which will print out -1.

Functions and procedures can be passed to and returned from functions. Here's a simple example

Code: Select all

type FNMap$(a$)

PROCMapper("hello", def FN$(a$) <- a$ + a$)

def PROCMapper(s$, a@FNMap)
  print a@FNMap(s$)
endproc
which will print out "hellohello"


It's also possible to create a type that references another type so you can take a pointer to a higher order function, e.g.,

Code: Select all

type PROC_dummy(a%)
type PROChigher(a%, b@PROC_dummy, c@PROC_dummy)
local f@PROChigher
So that's function pointers. The syntax is a bit odd but I'm getting used to it now and it's growing on me.

Next I'm going to work on slices, and once these are implemented, there should be enough in the language to write a WIMP application. We'll see.
Deleted User 9295

Re: Subtilis: A new BASIC compiler for RiscOS

Post by Deleted User 9295 »

markdryan wrote: Tue Oct 19, 2021 9:29 pm The range keyword introduces a new type of loop.

Code: Select all

range v% := a%()
  print v%
endrange
I am interested that you seem to be fairly relaxed about adding a new keyword, even though that (presumably) means that existing programs which happen to use that word as a variable name will stop working. I have always felt that retaining compatibility with existing programs is paramount, so I try very hard to reuse existing keywords rather than adding new ones.

When this functionality was discussed as a possible extension to my BASICs (in a thread to which you contributed at the time) my preferred approach was to extend the FOR keyword in this kind of context:

Code: Select all

FOR v% OF a%()
  PRINT v%
NEXT
or

Code: Select all

FOR v% OF a,b,c,d,e
  PRINT v%
NEXT
In the event I didn't implement it, and my failing health makes it unlikely that I will.
The @ character separates the variable prefix from the type name.

Code: Select all

ptr@FNCombine = def FN%(a%, b%) <- (a% * b%) + b%
@ is of course permitted within a BBC BASIC variable or function name, so ptr@FNCombine is a valid identifier in all versions of BBC BASIC. I tend to use @ (purely as a convention, it has no significance to the interpreter) to make library function names unique, as in FNfunction@library.
We can then invoke the function through our new variable like this

Code: Select all

print ptr@FNCombine(1,2)
Function pointers in BBC BASIC for Windows and BBC BASIC for SDL 2.0 borrow the same syntax as pointers to other objects, so to invoke a function via a pointer one would do:

Code: Select all

DEF FNCombine(a%, b%) = (a% * b%) + b%
ptr@FNCombine = ^FNCombine()

PRINT FN(ptr@FNCombine)(1,2)
Functions and procedures can be passed to and returned from functions. Here's a simple example

Code: Select all

type FNMap$(a$)

PROCMapper("hello", def FN$(a$) <- a$ + a$)

def PROCMapper(s$, a@FNMap)
  print a@FNMap(s$)
endproc
which will print out "hellohello"
In my BASIC's that would be:

Code: Select all

      DEF FN2$(a$) = a$ + a$

      PROCMapper("hello", ^FN2$())
      END

      DEF PROCMapper(s$, FnPtr)
      PRINT FN(FnPtr)(s$)
      ENDPROC
(these examples require a function-scan to have been carried out, so won't work in isolation as shown).

I hope you don't think I'm muscling in on your territory, but I thought it might be of interest to compare your approach with mine.
markdryan
Posts: 267
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

Richard Russell wrote: Tue Oct 19, 2021 11:22 pm I am interested that you seem to be fairly relaxed about adding a new keyword, even though that (presumably) means that existing programs which happen to use that word as a variable name will stop working.
At this stage in the project I am. The reason is that there's almost no existing programs to break. Subtilis is a new language, that's not compatible with BBC BASIC (although it's heavily influenced by it) and as far as I'm aware, there's only one user, me. All I use it for at the moment is writing programs to test the compiler. At some point, once the language is complete, I'll do a 1.0.0 release and from then on, there'll be no more compatability breaking changes.

It is true though, that adding new keywords like this will make it harder for people to port BBC BASIC programs to Subtilis, should they want to do so. However, in reality the main barrier to porting isn't the extra keywords I've implemented, but rather the keywords I'm not going to implement, such as DATA, READ, GOTO, GOSUB, EVAL and also operators like ! and ?.
When this functionality was discussed as a possible extension to my BASICs (in a thread to which you contributed at the time) my preferred approach was to extend the FOR keyword in this kind of context:

Code: Select all

FOR v% OF a%()
  PRINT v%
NEXT
or

Code: Select all

FOR v% OF a,b,c,d,e
  PRINT v%
NEXT
The reason I didn't go with FOR in the end is that the RANGE loop has a different behaviour to the FOR loop. The body of a FOR loop will always execute at least once, regardless of the loop condition, but this is not the case with the RANGE loop. The body of a RANGE loop may not execute at all when operating on vectors, as vectors can have zero elements. For this reason, I felt it would be confusing to re-use the FOR keyword for this new loop construct as it doesn't behave like a FOR loop. Incidentally, the RANGE keyword comes from Go (which has been the inspiration for many current and upcoming features of Subtilis) where interestingly it is used in conjunction with the for statement.
I hope you don't think I'm muscling in on your territory, but I thought it might be of interest to compare your approach with mine.
Not at all. All comments, comparisons, criticisims and suggestions are gratefully received. Also thanks for sharing the BBC BASIC examples. Looking at these examples, and assuming I'm reading them correctly, one of the biggest differences I see between our approaches, apart from the syntax, is that the function pointers in Subtilis are strongly typed whereas in BBC BASIC they appear not to be. For example, this code sample will not compile in Subtilis

Code: Select all

type FNMap$(a$)

PROCMapper("hello", def FN%(a$) <- len(a$))

def PROCMapper(s$, a@FNMap)
  print a@FNMap(s$)
endproc
as the labmda function passed to PROCMapper returns an integer instead of a string.

I should probably add that none of the new features I've added to the language are set in stone right now. At this stage I don't really know how well they're going to work, either in isolation, or when used in combination with each other. The only way to really find this out will be to write some sizable programs in Subtilis and see what works and what doesn't. I anticipate there will be changes to existing features before the 1.0.0 release.
Deleted User 9295

Re: Subtilis: A new BASIC compiler for RiscOS

Post by Deleted User 9295 »

markdryan wrote: Wed Oct 20, 2021 10:08 pm the keywords I'm not going to implement, such as DATA, READ, GOTO, GOSUB, EVAL and also operators like ! and ?.
I find it hard to reconcile omitting READ and DATA with the subject line "Subtilis: A new BASIC compiler". I would have thought those keywords were amongst the features that any language calling itself BASIC must have.
The reason I didn't go with FOR in the end is that the RANGE loop has a different behaviour to the FOR loop. The body of a FOR loop will always execute at least once
The behaviour of FOR in always executing the loop once is specifically a feature of BBC BASIC, not BASICs in general; indeed the majority of BASICs don't execute the body of the loop at all if the conditions in the FOR statement are not met. That's why my automatic translators from QBASIC and Liberty BASIC into BBC BASIC create code like this:

Code: Select all

FOR a = b TO c : WHILE (b) > (c) EXIT FOR : ENDWHILE
So, having said Subtilis isn't BBC BASIC, there's no reason why it shouldn't use FOR in the more common way.
function pointers in Subtilis are strongly typed whereas in BBC BASIC they appear not to be.
BBC BASIC isn't a strongly-typed language, it has only two incompatible types: numbers and strings (arrays and structures muddy the waters somewhat, but they are not scalar objects). So pointers are just numbers (as they are in C); the programmer can use a naming convention to hint at what they point to, but that's purely for his convenience.

It's interesting that in some BASICs even strings aren't a distinct type. In Pure BASIC strings are basically just pointers (again like C) and in Liberty BASIC strings and structures alias each other!
User avatar
BigEd
Posts: 6287
Joined: Sun Jan 24, 2010 10:24 am
Location: West Country
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by BigEd »

This might be a horrific idea, but I mention it anyway. There's a 1979 Basic interpreter for the ABC80 which supported the usual Basic types, but also offered arithmetic operations on strings - and therefore could handle very large numbers or high precisions, at a cost. See here:
...we have ADD$, MUL$, DIV$ which take two strings and optionally a precision. A very nice way to provide for conventional integer and floating point, as well as extended precision arithmetic.
markdryan
Posts: 267
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC compiler for RiscOS

Post by markdryan »

Richard Russell wrote: Wed Oct 20, 2021 11:09 pm I find it hard to reconcile omitting READ and DATA with the subject line "Subtilis: A new BASIC compiler". I would have thought those keywords were amongst the features that any language calling itself BASIC must have.
READ and DATA are also absent from Microsoft's Small BASIC, although I note that this does at least support GOTO. So I've only been taking 1 more liberty with the BASIC name than Microsoft :-). Having said that though, I do agree with you. Earlier on in this thread I observed that one of the code samples I'd posted didn't look much like BASIC. I'll update the thread's title.
So, having said Subtilis isn't BBC BASIC, there's no reason why it shouldn't use FOR in the more common way.
I have considered this. Doing so would simplify the compiler and would also be less confusing to people used to languages such as C, Go and Java. On the other hand, the FOR loop construct in Subtilis is taken from BBC BASIC and Subtilis only generates code for Acorn machines, so I feel the current implementation is less surprising for the intended target audience. I'm going to leave it the way it is for now. I may well revisit this decision though before the 1.0.0 release is declared.

Incidentally, I checked locally on the other BASIC implementations I have to hand. Commodore BASIC (on the C64) does seem to execute the body of the FOR loop at least once, while Sinclair BASIC (on the spectrum 128) does not.
markdryan
Posts: 267
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC-like compiler for RiscOS

Post by markdryan »

Subtilis now supports slices. You can slice strings, vectors or one dimensional arrays. Strings are sliced when left$, right$, mid$ are used in an expression context. There’s no new syntax here, just a change in behaviour of Subtilis programs under the hood. Previously,

Code: Select all

b$ := left$(a$,3)
would have allocated a new string with 3 elements and copied the first 3 characters from a$ into this new string. Now that slicing is implemented, the allocation and the memcpy are no longer needed. b$ now aliases a$’s data. As strings are copy on write in Subtilis, the alias will be broken if either strings are modified. Slicing strings in this way should speed up string operations and reduce their memory footprint, particularly when using large strings. I re-ran the ClockSpeed benchmark on my RiscPC, which has a string benchmark test, to see how much of a difference string slicing makes. Here are the results:

Subtilis No Escape (October 2020)
• 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


Subtilis No Escape (November 2021 with slicing)
• Real REPEAT Loop: 1407
• Integer REPEAT Loop: 68285
• REAL FOR Loop: 209
• Integer FOR Loop: 6793
• Trig/Log Test: 1594
• String Manipulation: 12064
• Procedure Call: 16701

The string manipulation benchmark has almost doubled in speed.


Vectors and arrays are sliced using the ‘:' operator inside the indexing brackets, i.e., ‘()’ for arrays and ‘{}’ for vectors. The ‘:’ is preceded by one integer value and followed by a second. The first value specifies the index of the first element of the collection being sliced that is to be present in the slice. The second value indicates the first element of the collection being sliced that will not be present in the slice. Both values are optional. If the first integer value is missing the compiler assumes a default of 0. If the second is missing it assumes a default of the number of elements in the collection being sliced.

This is better explained with some examples. Given we have

Code: Select all

dim a%{10}
a%{} = 1,2,3,4,5,6,7,8,9,10,11
this code

Code: Select all

range v% := a%{1 : 3}
  print v%
endrange
will print out

Code: Select all

2
3
while this

Code: Select all

range v% := a%{8 :}
  print v%
endrange
will output

Code: Select all

9
10
11
Slices can be used in combination with the copy statement to build up arbitrary structures in memory containing mixed types. This is useful when you need to have precise control over the layout of data in memory, for example when programming the WIMP in RiscOS. A procedure to display an error dialog in Subtilis could be written like this:

Code: Select all

def PROCdisplayError(e$)
    local dim eblock&(len(e$) + 4)
    eblock&(0) = 255
    copy(eblock&(4:), e$)
    sys "Wimp_ReportError", eblock&(), 1, "MyApp"
endproc
Note how we use the copy statement in conjunction with a slice of the byte buffer to copy the contents of e$ to the buffer starting at the 5th element (index 4).

When you slice a vector or an array the newly created variable aliases the old one. However, unlike strings, arrays and vectors are not copy on write objects in Subtilis. Changes made to the slice or the original array or vector will be reflected in both collections. For example,

Code: Select all

dim a$(2)
a$() = "hello", "hello", "world"
b$() := a$(1:2)
b$(0) = "hi"
range v$ := a$()
   print v$
endrange
will print

Code: Select all

hello
hi
world
Modifications to the slice b$() are reflected in the original a$() array. The is one exception here though. Vectors in Subtilis are copy on append. Vectors are no longer aliased with any other variables after they have been appended to.
markdryan
Posts: 267
Joined: Sun Aug 20, 2017 11:37 pm
Contact:

Re: Subtilis: A new BASIC-like compiler for RiscOS

Post by markdryan »

Some more updates

SWAP

The SWAP keyword is now implemented. This works much the same way as it does in BBC BASIC with the exception that the variables being swapped must have exactly the same type, so you can't for example, swap a one dimensional array with a two dimensional array, as you can in BBC BASIC.

RECords

This is where it starts getting weird. I've begun to add support for records, or structures, to Subtilis. Before a record can be used a new type needs to be created for it, using the type keyword, much in the same way as new types are created for function pointers, e.g.,

Code: Select all

type RECPoint ( x% y% )                                                                                                                                               
                                                                                                                                                                      
type RECShape (                                                                                                                                                       
  name$                                                                                                                                                               
  p@RECPoint                                                                                                                                                       
  dim vertices@RECPoint{3}                                                                                                                                            
)                                                                                                                                                                   
Record variables consist of an identifier followed by a '@' followed by the type name, e.g.,

Code: Select all

local a@RECShape
or

Code: Select all

a@RECShape := ( "square", ( 100, 450), ( (100, 0 ), (100, 100), (100, 400), (400, 400)))
Records can be global or local, can be passed to and returned from functions and can be elements in an array or vector. They are not reference types and have value semantics. For example,

Code: Select all

a@RECPoint := (100, 100)                                                                                                                                              
b@RECPoint := a@RECPoint                                                                                                                                              
a@RECPoint.x% = 200                                                                                                                                                   
print a@RECPoint.x%                                                                                                                                                   
print b@RECPoint.x%                                                                                                                                                   
will print

Code: Select all

200
100
There's still some work to do here. It's not yet possible to use an initialisation list for an array of structures, although this is implemented for structures of arrays, as demonstrated above, and there's currently no way of having a pointer or a reference to a record, so there's no easy way to use records to build complex data structures like lists or graphs.

For more information on records see
Sophira
Posts: 113
Joined: Mon Sep 26, 2022 9:45 am
Contact:

Re: Subtilis: A new BASIC-like compiler for RiscOS

Post by Sophira »

I just noticed this thread! Nice work. <3

I know of another program you can add to your benchmarks, if you want. There was a BASIC-like compiler called !sBASIC (Simple BASIC) published in the *INFO column of the May 1992 issue of BBC Acorn User.

As the name implies, it was a very simple subset of BASIC that was very limited - no floating-point, no PROCs/FNs, integer division, no GOTOs, your only loop is WHILE..ENDWHILE, etc - so there's no way it could compare feature-wise to any other BASIC compiler. Yours is already far more advanced. But I am curious about how well it would perform given simple programs.

I've attached it to this post, along with the article text about it from the magazine itself (as the program didn't come with its own documentation) and the two example programs that it came with, in case you're interested too! As far as I know, there was never any update to it and I can't find any other information about it.
Attachments
sBASIC.zip
(12.38 KiB) Downloaded 17 times
Post Reply

Return to “development tools”