Weil's so schön und lecker war, heute gleich noch mal (leicht veränderte Rezeptur).
Außerdem haben wir ja gestern das meiste an die Meute verfüttert. 🙂
#Quiche#compiler is now alive! (At least Conway's variant of alive). The initial version was slow - about four seconds per generation. It was multiplying coordinates for each cell read and write.
The second variant uses offsets into each liner buffer, and only redraws changed cells. It's now running at three to four generations per second.
This week I added the Peek() and Poke() intrinsics to the #Quiche#compiler. That means I can now write my first non-trivial program.
I spend this morning fixing a few bugs in the parser and code generator and it's successfully generating the assembler file.
The assembler is choking on a couple of issues with identifiers, and the output code has a couple of bugs to do with parameter parsing and result processing.
I'm looking to optimise the primitive selection routine in #Quiche. Currently it searches the table of available primitives multiple times until it finds a match, each time 'expanding' the types of the parameters. It's slow and it's messy.
My new plan is to calculate a 'fitness' value for each primitive using the type(s) of the input parameters. It's taken me three days just to work out how to create a spreadsheet to calculate and verify the values.
All of the operators are now passed over to the new data tables and primitive search. I'm moving onto intrinsics. These are small routines with function-like that often generate inline code, such as peek, inp and sizeof.
Many of these have quirks such as accepting multiple types, or a typedef. The quirk of Abs is unsigned values: in doesn't affect them. I could raise an error but it's nicer to fake the data to not generate any code.
I just merged my Z80 emulator into the #quiche compiler project. It can now run the compiled code without having to shell out to a separate program.
I'm wishing I did this ages ago. It wasn't nearly as hard as I thought it would be. At the moment it doesn't make any 'real' difference but it will make it possible to breakpoint, single step and modify in situ.
Before Christmas I decided the #Quiche#compiler needed two big refactorings. The first is nearly done: the data tables for operands and primitives.
The OG version had grown confusing due to some poor initial decisions. It also put too much intelligence into the parser regarding the available types for each operator.
The new version allows the parser to scan the table to confirm if an operator can handle the operator types. It can also 'expand' types to find a match...
I've been itching to make some more progress on the #Quiche#compiler. I'm working to finally get function calls up and running.
I now have the function dispatch code for stack based calls, and the parameters are being added to the variables list for the function so the function body can use them.
But I can't actually test any of this until I tackle the horrors of the stack frame set up and tear down code. Wish me luck.
A bit of a cooking afternoon today. I bought some marzipan for Xmas baking use last year, but never got around to using it
The package says "use within a year of purchase," so the clock has been ticking. Being the end of apple season, I've decided to builtld an apple & marzipan #galette. That's baking now.
Hope it's successful, I've still got half that marzipan. Might try some choco-marzi cookies someday soon too.
Adding a config file to the #compiler so I can store settings between sessions. Very handy now I'm getting towards being able to write useful code.
I figure at some point the settings will be at the project level, allowing different settings per project. And a command line compiler would be able to accept a config filename as a parameter.
The procedure declarations are importing operating system routines. For the calls in the body the compiler is marshalling data into the required registers.
Once I have includes working you'll be able to use library files for the imports.
And you can call pretty much any assembler code using this.
I'm starting with a register based calling convention. It saves me from dealing with stack frames, and will allow calls assembly routines - especially OS routines.
But it means loading lots of values into registers, then saving return values. My IL code data structure needs some updates for this scenario.
(Whereas stack frames should just need data pushing on the stack).
Some compiler routines such as sizeof() need to be able to handle a type name as a parameter, for example sizeof(Integer).
I've added a type called TypeDef to handle this. When the parser hits an identifier which is a type name but not a typecast it returns a value of type TypeDef.
Almost all the code generation is table driven. Inc and Dec are one of the exceptions that require code. In this case it's a loop to generate the INC or DEC instructions.
Only thing left to do is to generate add or subtract if the offset is too large. For now I'm stabbing at doing this for offset greater than four. Optimising here is much more complex than it might seem. For example you can INC any register whereas ADD requires A.
The Odd operator is beautifully easy after Inc and Dec. I've highlighted to two instructions for the Odd itself and the CPL for NOT. Ignore the references to temp0 which will get optimised away at some point.
Note how the allocator is smart enough to only load the low byte of the 16-bit variable.
I think I'll also add a version that targets a branch. It'll be a lot shorter if optimised.
I'm working my way through 'intrinsics' in the #compiler. These are operations that look like functions or procedures to the programmer but are generated inline code. Examples are Write, Inc, Peek, Poke, Inp, Out, Sizeof and High.
Support for these includes adding an 'Enumerable' type set for the parameters and some flags. Inc and Dec can only accept a variable reference as the first parameter and a constant as the optional second parameter.
I'm adding typecasts to the #Quiche#compiler. This is the next step toward full function support. In #Pascal typecast syntax is the same as a function.
b := integer(a);
This gives me the hooks I need to call functions within expressions and handle the return value. But typecasts generate inline code so I don't have to do stack frames yet.