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.