Skip to content

p32cc — the P32W C compiler

The same compiler in C, which runs on P32W Unix and compiles itself there, is software/cc (M7, D24); the two write the same assembly (make test).

Status: M2 of the operating-system plan (D22, os-design.md). Compiles the C library and the test programs; every test program behaves exactly like the host compiler's build of it (make test).

python3 cc/p32cc.py -c file.c [-o file.o]       compile to an object
python3 cc/p32cc.py -S file.c                   assembly (file.s), with each statement's source line
python3 cc/p32cc.py -E file.c                   preprocessed source
python3 cc/p32cc.py --sa prog.c -o prog.bin     a stand-alone program for the emulators
emulator/p32emu prog.bin < /dev/null            run it (exit status = main's result)

-I DIR, -D NAME[=VAL], -w (no warnings), -M map (link map).

The language

C89, with floating point in software (P32W has no FPU): char (signed) / short / int / long (32 bits), unsigned, pointers, arrays, struct, union, enum, typedef, function pointers, static / extern, initialisers (nested, partial, strings, address constants), switch, goto, variadic functions (stdarg.h), the full preprocessor (#include, #define with # / ## / __VA_ARGS__, #if / #elif, defined, __FILE__ / __LINE__).

From C99: // comments, declarations anywhere in a block and in for (int i = ...), _Bool, inline (accepted, no effect), designated initialisers ([i] =, .member =, chains like .a.b[2] =), brace elision in initialisers, compound literals ((struct pt){ 1, 2 }, at file scope a static object, in a function a fresh one each time it is evaluated) and __func__.

Bit-fields, laid out as System V does (from a unit's lowest bit, never across a unit the size of the field's type; :0 ends the unit; plain int fields are signed), with op= and ++/-- (evaluating the struct's place once) and in initialisers.

Structs by value, as arguments and results (D30): a function with a struct parameter takes every argument on the stack, each struct copied into words of its own (as a variadic function does, so va_arg(ap, struct pt) works); a function returning a struct gets a hidden first argument, where to put it, and returns that address.

long long (64 bits, aligned to 4; D30): kept in memory like a struct, so it passes and returns as one; its operations are calls of libc's helpers (libc/llong.c: __lladd ... __llcmp), constants are objects in the data, and constant expressions fold at compile time. printf has %lld, stdlib strtoll and its family.

double (D30; float and long double are the same 8-byte IEEE type, which C permits): kept in memory like a long long, its operations calls of libc's helpers (libc/dbl.c, on softfp, so + - * / are correctly rounded and agree bit for bit with any IEEE machine). Comparisons follow C's NaN rules. Constants (1.5, .5e-3, 2.f) are objects in the data and fold at compile time, except a division by zero or a NaN result.

Not yet: K&R-style definitions, hexadecimal floating constants. A use is a clear compile-time error. (In progress, D30: the C compiler upgrade.)

How it works

File Stage
clex.py tokens with file, line and spacing
cpp.py the preprocessor, on tokens (hidesets stop recursive macros)
cparse.py recursive-descent parser: a typed syntax tree, C89 conversions, constants folded
cty.py types: sizes, alignment, struct layout, the usual arithmetic conversions
cgen.py P32W assembly for p32asm -c
p32cc.py the driver: assemble with p32asm, link with p32ld

Calling convention (D22, D30): arguments 1–4 in r1–r4, more on the stack (argument 5 lowest); result in r1; r1–r7 caller-saved, r8–r13 callee-saved, r14 frame pointer, r15 stack pointer; CALL pushes the return address. A variadic function, or one with a struct parameter, gets every argument on the stack (a struct in as many words as it takes), so va_list is a plain pointer. A struct result goes where a hidden first argument points. Arguments are evaluated left to right.

Code: expressions are evaluated in a stack of temporaries (the lowest free of r1–r13); locals live in the frame (off(r14)); immediates are used where they fit; conditions compile to direct compare-and-branch. No optimiser yet.

Tests

cc/tests/*.c, run by cc/run_tests.py (in make test): each program is built by p32cc and run in p32emu, and built by the host cc and run natively; output and exit status must match. A NAME.expect file makes a test P32W-only.