coff / start
getting started
1what you need
linux on x86-64, bash for the scripts and, for the very first build only, a
C compiler with as and ld from binutils. tcc is the
recommended C compiler. gcc gives a byte-identical result, but tcc can itself
be built from a seed small enough to check by hand and gcc cannot, which
TRUST.md in the repository explains.
git clone https://github.com/cofflang/coff.git
cd coff
2building
the build happens in four steps. each one uses what the step before it made.
# 1. the seed compiler, in plain C
tcc -o coff0 coff0.c
# 2. the real compiler, written in c0, built by the seed
./coff0 c0/coff.c0 coff1.s
as coff1.s -o coff1.o
ld coff1.o -o coff1
# 3. the assembler and linker, built by the real compiler
./coff1 c0/smed.c0 smed.s
as smed.s -o smed.o
ld smed.o -o smed
# 4. smed rebuilds itself, so the binary you keep is its own output
./smed smed.s smed.new
mv smed.new smed
chmod +x smed
the fourth step matters. the smed from step three was made by
as and ld, the very tools this repository exists to
stop depending on. smed never picks the short encodings binutils picks, so
its own build of the same assembly is a different binary. one more pass
through itself reaches a fixpoint at once. that is the binary the audit
expects to find. the temporary name is because linux will not let a program
write over its own running executable.after step four you never need the C compiler, as or ld again. the test scripts still use them, the C compiler to rebuild the seed and binutils as the reference smed is compared against.
3a first program
// hello.c0
int main() {
print("hello from c0\n");
return 0;
}
./coff1 hello.c0 hello.s
./smed hello.s hello
chmod +x hello
./hello
the exit code of the process is whatever main returns. smed
writes a plain file, so setting the executable bit is up to you.
4usage
coff1 [--elf] [-I dir]... input.c0 output.s
smed [-T linker.ld] [--base addr] [-m symbols.map] input.s [more.s ...] output
-I dir- another directory to look in for an include that is not next to the file including it. it can be given more than once.
--elf- write assembly for moonshot instead of linux.
-T linker.ld- lay the output out by a GNU ld linker script.
--base addr- the load address, when no script gives one.
-m symbols.map- also write every symbol with its address, handy for comparing a layout with another linker's.
smed assembles and links every input together in the order given. coff1 with no file arguments reads source on standard input and writes assembly to standard output, which is the form the test harness uses.
5running the tests
./run_tests.sh # the compiler suite, over five hundred checks
./smed_tests.sh # smed against binutils, then the suite with no GNU tool
./verify_bootstrap.sh # the trusting-trust audit
every test program goes through the real pipeline and has its exit code
checked, plus its exact output or exact error message where the test says
what those should be. then each stage written in c0 is compared byte for byte
with the seed compiler over the whole corpus. last comes the fixpoint, the
compiler compiling itself three generations deep with every generation
identical. CC=tcc ./run_tests.sh runs the same suite with gcc
absent from the machine.