gigatron/rom/Compilers/glcc/README.md
2025-01-28 19:17:01 +03:00

437 lines
18 KiB
Markdown

# GLCC 2.0
This LCC--derived compiler and C library targets the [Gigatron](http://gigatron.io) VCPU.
It keeps many of the ideas of the previous attempt to port LCC to the
Gigatron (pgavlin's). For instance it outputs assembly code that can be parsed by
Python and it features a linker writen in Python that can directly
read these files. It also differs in important ways. For instance the
code generator is fundamentally different.
## 1. Status
This project provides a complete toolchain and C library for ANSI C 1989.
### 1.1. Implementation details
Some useful things to know:
* Types `short` and `int` are 16 bits long. Type `long` is 32 bits
long. Types `float` and `double` are 40 bits long, using the
Microsoft Basic floating point format. Both long arithmetic or
floating point arithmetic incur a substantial speed penalty,
vastly improved with the dev7 rom.
* Type `char` is unsigned by default. This is more efficient because
the C language always promotes `char` values into `int` values to
perform arithmetic. Promoting a signed byte involves a clumsy sign
extension. Promoting an unsigned byte comes for free with most
VCPU opcodes. If you really want signed chars, use `signed char`
or maybe use the compiler option `-Wf-unsigned_char=0`. This
is not recommended.
* The nonstandard type qualifier `__near` can be used to indicate
that the data lives in page zero. This allows the compiler to
generate more compact code and tells the linker to place such
variables in page zero. The nonstandard type qualifier `__far`
is also recognized but currently inoperative. It will eventually
indicate that the data lives in banked memory.
* The traditional C standard library offers feature rich functions
like `printf()` and `scanf()`. These functions are present but their
implementation requires a lot of space. Instead one can call some of
the lower level functions that either are standard like `fputs()` or
non standard functions such as `itoa()`, `dtoa()` whose prototypes
are provided by [`<gigatron/libc.h>`](include/gigatron/gigatron/libc.h).
* Alternatively one can completely bypass stdio and use the
low-level console functions whose prototypes are provided in
[`<gigatron/console.h>`](include/gigatron/gigatron/console.h).
The function `cprintf()` has all the formatting abilities of `printf`
but saves memory by bypassing standard io and printing to the console.
The function `mincprintf()` further saves space but only recognizes
`%d` and `%s` in format strings.
* The include file [`<gigatron/sys.h>`](include/gigatron/gigatron/sys.h)
provides declarations to access the gigatron hardware and wrappers
to call native SYS routines.
* Many parts of the main library can be overriden to provide special
functionalities. For instance the console has the usual 15x26 characters,
but this can be changed by linking with a library that redefines
what is in `cons_geom.c`. This is what happens when one uses argument
`-map=conx` to save memory with a 10x26 console. Another more
important example is the standard i/o library. By default
it is only connected to the console and `fopen()` always fail.
But one just has to redefine a few functions to change that.
This is one happens when one compiles with `-map=sim` which
[forwards](gigatron/mapsim/libsim) all the stdio calls to
the emulator. Note that such binaries only run in the command
line gigatron emulator `gtsim` because they attempt to
communicate with `gtsim` to forward the stdio calls.
* Over time the linker `glink` has accumulated
a lot of capabilites. It supports common symbols,
weak symbols, and conditional imports. It can synthetize magic lists
such as a list of initialization functions that are called before main,
a list of data segments to be cleared before running main, a list
of memory segments that can be used as heap by `malloc()`, or a list
of finalization functions called when the program exits.
Using `glink -h` provides some help. Using `glink --info` provides
help for specific memory maps. But the more advanced functions
are documented by comments in the [source](gigatron/glink.py)
or comments in the library source files that use them...
## 2. Compiling and installing
You can build GLCC from source using two methods.
* The first method relies on the traditional `make` command
using the usual Posix command line utilities. This is the method used
for development and therefore is the recommended method
for Linux machines or Macs. It can also be used
on Windows when compiling with `cygwin` (https://cygwin.org)
or `mingw64/msys2` (https://www.mingw-w64.org/).
* The second method relies on `cmake` (https://cmake.org)
and supports many different toolchains such as Microsoft
Visual Studio, etc.
### 2.1 Building gigatron-lcc with Make
Because the primary GLCC development platform is Linux.
building `gigatron-lcc` with make on a Unix platform
should be very easy provided that a C compiler, bison,
gnu-make >= 4.0, and python >= 3.8 are installed.
Simply type:
```
$ git clone https://github.com/lb3361/gigatron-lcc.git
$ cd gigatron-lcc
$ make
```
Then you can either invoke the compiler from its build location `./build/glcc` or
install it into your system with command
```
$ make PREFIX=/usr/local install
```
where variable `PREFIX` indicates where the compiler should be installed.
This command copies the compiler files into `${PREFIX}/lib/gigatron-lcc/`
and symlinks the compiler driver `glcc`, the linker driver `glink`,
and the simulator `gtsim` into `${PREFIX}/bin`. All the other files are located under
`${PREFIX}/lib/gigatron-lcc`. Note that this directory can be relocated
elsewhere in the system as long as its contents is preserved.
You just need to either invoke `glcc` using the full path of
the `gigatron-lcc` directory. Alternatively you can place symbolic links
to these files somewhere in the executable search path.
There is also
```
$ make test
```
to run the current test suite.
### 2.2 Building gigatron-lcc with CMake
The prerequisites are python >= 3.8 and cmake >= 3.16.
In order to compile with cmake, you must first create a build directory
and invoke CMake from that build directory.
For instance, on a Unix machine,
```
$ git clone https://github.com/lb3361/gigatron-lcc.git
$ cd gigatron-lcc
$ mkdir build
$ cd build
$ cmake ..
```
This operation creates a Makefile in the build directory.
You can then compile with `make`
```
$ make
```
Then you can either invoke the compiler from its build location `./glcc` or
install it into your system with command
```
$ cmake --install . -DCMAKE_INSTALL_PREFIX=/usr/local
```
where variable `CMAKE_INSTALL_PREFIX` is the directory where glcc
should be installed. Note that this directory can be relocated
elsewhere as long as the relative position of the glcc files
is preserved.
### 2.3 Windows notes
#### 2.3.1 Cygwin GLCC
Thanks to the feedback of axelb, you can use the `make` method
to compile gigatron-lcc under cygwin. For this, you must first
install cygwin >= 3.2 from http://cygwin.org, make sure to
select the packages `gcc-core`, `make`, `bison`, `git`, and `python3`,
then issue the `make`, `make install`, or `make test`
command from the cygwin shell.
The main drawback of building `gigatron-lcc` under cygwin is
that you have to execute it from the cygwin shell as well since
it depends on the cygwin infrastructure.
#### 2.3.2 Native Windows GLCC
For this, you need a native version of Python (https://python.org).
It is also highly recommended to install Git-for-Windows (https://gitforwindows.org/)
and a version of GNU make for windows, for instance using
Chocolatey (https://community.chocolatey.org/packages/make).
* A first option is to use the mingw64 compiler (https://mingw-w64.org).
This compiler comes in various guises. After a bit of experimentation,
the recommended approach is to download the 32 bits version of
[Git for Windows SDK](https://github.com/git-for-windows/build-extra/releases/latest).
This is certainly an overkill, but a very reliable one.
You can then start the Git for Windows SDK shell, clone the Gigatron LCC repository,
and use the `make` method discussed above. A precompiled version with installation
and usage instructions can be found in the forum
post https://forum.gigatron.io/viewtopic.php?p=2484#p2484.
* A second option is to use the CMake approach with any supported toolchain.
For this you need to create a build directory and run the CMake program
to generate project files. Please follow the instructions that come
with CMake to select the proper toolchain, compile the project, and
optionally set CMAKE_INSTALL_PREFIX and trigger the installation target.
Both options create a `bin` directory with Window batch files, e.g
`glcc.cmd`, etc., that can be used to invoke GLCC from the DOS command
line, from PowerShell, or from the Git Bash. These batch files rely on
the `py` launcher that is installed by default with recent versions of
Python for windows. Just add this directory to the executable search
path, e.g. `PATH=/c/glcc/bin;%PATH%` at the DOS command line or
`PATH=/c/glcc/bin:$PATH` at the Git Bash command line. T
## 3 Compiler invocation
Besides the options listed in the [lcc manual page](doc/lcc.1),
the compiler driver `glcc` recognizes a few Gigatron-specific options.
Additional options recognized by the assembler/linker `glink`
are documented by typing `glink -h`
* Option `-rom=<romversion>` is passed to the linked and helps
selecting runtime code that uses the SYS functions implemented by
the indicated rom version. The default is `v5a` which does not
provide much support at this point.
* Option `-cpu=[4567]` indicates which VCPU version should be
targeted. Version 5 adds the instructions `CALLI`, `CMPHS` and
`CMPHU` that came with ROMv5a. Version 6, which comes with ROMvX0,
is not a strict supersed of version 6 because it changes the
encodings of CMPHS/CMPHU. Version 7, which comes with DEV7ROM
is a strict superset of version 5. Version 6 and 7 are mutually
incompatible. GLCC offers primary support for version 7 but can
generate version 6 encodings for some of its instructions.
The default CPU is the one implemented by the selected ROM.
* Option `-map=<memorymap>{,<overlay>}` is also passed to the linker
and specifya memory layout for the generated code. The default
map, `32k` uses all little bits of memory available on a 32KB
Gigatron, starting with the video memory holes `[0x?a0-0x?ff]`,
the low memory `[0x200-0x6ff]`. There is also a `64k` map, a `128k` map,
and a `conx` map which uses a reduced console to save memory.
Additional information about each map can be displayed by
using option `-info` as in `glcc -map=sim -info`
Maps can also manipulate the linker arguments, insert libraries,
and define the initialization function that checks the rom type
and the ram configuration. For instance, map `sim` produces gt1
files that only run in the emulator [`gtsim`](gigatron/mapsim),
redirecting `printf` and all standard i/o functions to
the emulator itself. This is my main debugging tool.
## 3. Examples
### 3.1. Running the LCC 8 queens program:
```
$ ./build/glcc -map=sim tst/8q.c
tst/8q.c:30: warning: missing return value
tst/8q.c:37: warning: implicit declaration of function `printf'
tst/8q.c:39: warning: missing return value
$ ./build/gtsim -rom gigatron/roms/dev.rom a.gt1
1 5 8 6 3 7 2 4
1 6 8 3 7 4 2 5
1 7 4 6 8 2 5 3
1 7 5 8 2 4 6 3
2 4 6 8 3 1 7 5
2 5 7 1 3 8 6 4
2 5 7 4 1 8 6 3
2 6 1 7 4 8 3 5
2 6 8 3 1 4 7 5
2 7 3 6 8 5 1 4
2 7 5 8 1 4 6 3
2 8 6 1 3 5 7 4
...
```
### 3.2. Running Marcel's simple chess program in gtsim:
I found this program when studying the previous incarnation
of LCC for the Gigatron, with old forums posts where Marcel
mentionned it as a "stretch goal" for the compiler. The main
issue is that MSCP takes about 25KB of code and 25KB of data
meaning that we need to use the video memory. My main change
was to reduce the size of the opening book,
but this is not enough. One could think about using
the 128KB memory extention but this will require a lot
of changes to the code. In the mean time. we can
run it with the `gtsim` emulator which has no screen
but can forward stdio.
```
$ cp stuff/mscp/mscp0.c .
$ cp stuff/mscp/book.txt .
# Using map sim with overlay allout commits all the memory
$ ./build/glcc -map=sim,allout mscp0.c -o mscp0.gt1
```
Now we can run it. Option -f in `gtsim` allows mscp to
open and read the opening book file `book.txt`.
Be patient...
```
$ ./build/gtsim -f -rom gigatron/roms/dev.rom mscp0.gt1
This is MSCP 1.4 (Marcel's Simple Chess Program)
Copyright (C)1998-2003 Marcel van Kervinck
This program is distributed under the GNU General Public License.
(See file COPYING or http://combinational.com/mscp/ for details.)
Type 'help' for a list of commands
8 r n b q k b n r
7 p p p p p p p p
6 - - - - - - - -
5 - - - - - - - -
4 - - - - - - - -
3 - - - - - - - -
2 P P P P P P P P
1 R N B Q K B N R
a b c d e f g h
1. White to move. KQkq
mscp>
```
Now you can type `both` to make it play against itself...
```
mscp> both
book: (88)e4
1. ... e2e4
8 r n b q k b n r
7 p p p p p p p p
6 - - - - - - - -
5 - - - - - - - -
4 - - - - P - - -
3 - - - - - - - -
2 P P P P - P P P
1 R N B Q K B N R
a b c d e f g h
1. Black to move. KQkq
book: (88)c5
1. ... c7c5
8 r n b q k b n r
7 p p - p p p p p
```
This slows down a lot when we leave the opening book.
But it plays!
## 4. Internals
The code generator uses several blocks of page zero variables.
The linker knows the page zero usage of each rom and keeps
track of all free and used page zero locations.
* The most important block of page zero variables contains 24
general purpose word registers named `R0` to `R23`. This block is
can be manually displaced using the command line option
`--register-base=0x90` for instance. Register pairs named `L0` to
`L22` can hold longs. Register triplets named `F0` to `F21` can
hold floats. Registers `R0` to `R7` are callee-saved and are
often used for local variables. Registers `R8` to `R15` are used
to pass arguments to functions. Registers `R15` to `R22` are used
for temporaries.
* Additional registers include: word registers named `T0` to `T3`,
scratch bytes `B0` and `B1`, long accumulator `LAC`, long
accumulator extension byte `LAX`, floating point sign and
exponent bytes `FAS` and `FAE`, and a stack pointer `SP`. ROMs
that provide suitable native support may dictate the location
some of these registers. Otherwise they are allocated by the
linker in the upper half ot page zero. In general, these
locations, as well as `sysArgs[0..7]`, can be used by the
compiler or the runtime, and therefore are not safe to use
from C programs.
* Since the DEV7 rom offers a true 16 bits stack pointer, GLCC-2.0
makes `SP` equal to `vSP`, allowing the use of efficient opcodes
to access non-register local variables.
The function prologue first saves `vLR` and constructs a stack frame
by adjusting `SP`. It then saves the callee-saved registers onto the
stack. Nonleaf functions save 'vLR' in the stack frame and copy the
argument passed in a registers to their final location. In contrast,
leaf functions keep arguments passed in registers where they are
because these registers are no longer needed for further calls. In
the same vein, nonleaf functions allocate callee-saved registers for
local variables, whereas leaf functions use callee-saved registers in
last resort, often avoiding the construction of a stack-frame.
Leaf functions that do not need to allocate space on the
stack can use a register to save VLR and become entirely frameless.
Sometimes one can help this by using `register` when declaring local
variables. Saving `vLR` allows us to use `CALLI` as a long jump
without fearing to erase the function return address.
This is especially useful when one needs to hop over page boundaries.
The VCPU accumulator `vAC` is not treated by the compiler as a normal
register because there is essentially nothing the VCPU can do once the
accumulator is allocated to represent a particular variable or a temporary.
This would force the compiler to spill its content to a stack location
in ways that not only produce less efficient code, but often result
in an infinite loop because the spilling code must itself use `vAC`.
Instead, the GLCC code generator produces VCPU instructions in bursts
that are packed on a single line of the generated assembly code.
Each burst is in fact what LCC calls one instruction. Bursts are
produced by subverting the mechanisms defined by LCC to construct
various parts of a typical CPU instruction such as the mnemonic,
the address mode, etc. The VCPU accumulator `vAC` is treated as a scratch
register inside a burst. Meanwhile LCC allocates zero page registers
to pass data across bursts. This approach avoid the spilling problems
but sometimes needs improving because it does not keep track
of what data is left on the accumulator after each burst.
This has been improved by a `preralloc` pass that tries to eliminate
temporaries that can be passed through vAC, and by a state machine
in the instruction emitter which conservatively maintains assertions
about register or accumulator equality which can be used to
simplify the code.
The compiler produces a python file that first define a function for each
code or data fragment. The file then constructs a module that
holds a list of all the fragments, as well as all the imported and
exported symbols. The linker/assembler `glink` can read such files
or can read a library file that is merely the concatenation
of individual modules. Each fragment is represented as a function
that calls predefined functions whose uppercase name mirrors the name
of the instruction they emit. Additional functions implement synthetic
opcodes that can be implemented differently by different VCPU versions.
More predefined functions are used to define labels or control
when to check for a page boundary. The source of truth for
all this is the file `glink.py`.
The linker collects all the code and data fragments generated by the compiler.
It then analyzes import and exports to determine which ones should be
kept. It tries hard to place short functions into single segments in order
to avoid costly hops. Then it iterates until all symbols are resolved and
all symbol value dependent code is stabilized. Finally it produces a
familar `GT1` file.