MESYETI's Website
<- HomeDay 15 - Callisto on Fox32
I decided that now that Callisto is a presentable language, it's finally the time to return to making a Fox32 backend for Callisto. If you don't know, Fox32 is a fantasy computer. This means it has a CPU architecture (among other hardware) designed by its creator, ryfox. A fox designing a computer is very cool.
This means I can take you, the reader, on a journey through the journey of
writing a Callisto backend. First, I create a backend class. There are some
types needed for each backend, so I define a Word
type. This type
contains data for defined functions. It contains the function type, contents
(if it's an inline function, this means that when its called, its contents
are simply inserted at where the function is called), whether the function
throws an exception, and the parameters of the function.
Next, there are some functions from the base backend class that I have to
implement. I will leave most of them blank for now, so I can implement
them later. First of all, the GetVersions
function. If you
don't know about Callisto versions, then read this.
This will return the versions on for this platform. Here they are:
override string[] GetVersions() => [
// CPU features
"Fox32", "LittleEndian", "16Bit", "32Bit",
// OS features
"Fox32OS", "IO", "Exit"
];
Then, there's the FinalCommands
function. This defines what
commands need to be used to assemble the compiled code into an executable.
In the Fox32 backend, it calls the assembler to create the executable, then
a ryfs script to create a disk image. (If the make-img
backend
option is passed)
Then, there's the MaxInt
function. This just returns the biggest
integer this platform can store. As Fox32 is 32-bit, this is the unsigned
32-bit integer limit.
Then, there's the Init
function. This defines the assembly code
that goes at the top of the program. First, it calls the standard library's
init function, which I will go into later. It allocates 512 cells for the data stack,
which on a 32-bit platform is 2048 bytes. After that, there is a similar
function called End
. This calls the standard library's exit
function, and then defines the init function. The init function is defined
at the end because at the start, the compiler isn't certain that the standard
library's init function exists, as the compiler hasn't seen its definition
yet. Then, it allocates space for global variables, and then includes the
.def files for the fox32 rom and OS functions.
Now to compile AST nodes. The first is CompileWord
. This just
calls a defined function, so it just compiles to a call
instruction.
Next, is the CompileInteger
function. This is also very simple.
First, it writes the integer into memory at the data stack pointer's address.
The data stack pointer is in register r31
. Then, there's the
CompileFuncDef
function. This adds a label with the function's
name, and then calls CompileNode
on each node in the function's
contents. After that, there is the CompileLet
function. This takes
the info from the let node and defines a global, and appends that to the globals
array. It's just globals for now because I need them for printing, you'll
see why later.
Printing A
Printing 'A' is the first thing I do when writing a backend. Since there is currently no core library implementation for Fox32 (because the backend is very incomplete), I will have to define some functions myself in the test file. First, I need a place to store the terminal stream.
let u32 __fox32_stream
Now to make the init function, this is called at the start of the program.
inline unsafe __fox32_program_init begin asm
"mov r1, [rsp + 4]"
"mov [__global___us____us__fox32__us__stream], r1"
end end
The OS puts the terminal's stream at the top of the stack. I have to add
4 because the return address for the caller of __init
is at the
top of the stack. I take that value and store it in the stream variable. Now
for the exit function
inline unsafe __fox32_program_exit begin asm
"call end_current_task"
end end
Now, to make the print function. This sets the buffer address to the top of the data stack, the size as 1, and the stream as what was stored in the variable before:
func unsafe man print_ch cell ch begin asm
"sub r31, 4"
"mov r0, 1"
"mov.32 r1, [__global___us____us__fox32__us__stream]"
"mov r2, r31"
"call write"
end end
Now all the functions that have to be defined are done. I can now just call the function with 'A', then again with a new line:
'A' print_ch
10 print_ch
Now to try running it
Next, I will work on if & while loops, and then local variables