Documentation
pdp11-c2 — user guide
pdp11-c2 is Ritchie's c2, the C object-code improver — a peephole optimiser that reads PDP-11 assembly and writes tighter PDP-11 assembly. It is the -O pass of pdp11-cc, run between the code generator and the assembler.
pdp11-c2 — user guide
pdp11-c2 is Ritchie's c2, the C object-code improver — a peephole
optimiser that reads PDP-11 assembly and writes tighter PDP-11 assembly. It is
the -O pass of pdp11-cc, run between the code generator and the assembler. It
is not a command you run directly: it is a compiler pass cc invokes only
when you pass -O. This guide documents its calling convention so you can
recognise it in cc -v output or drive it by hand when debugging.
It computes nothing about the target era — the same assembly in gives the same assembly out. For how it works inside — the instruction list, the transforms, the register tracking — see the design document.
1. Synopsis
pdp11-c2 [+] [-] [infile [outfile]]
With no filenames it filters standard input to standard output, which is how
cc -O runs it. With one name it reads that file and writes stdout; with two,
the first is the input and the second the output. Two optional leading flags come
before the filenames:
| flag | meaning |
|---|---|
+ |
emit a debug dump (developer diagnostic) |
- |
print optimisation statistics to stderr at exit (iterations, jumps threaded, redundant moves, sobs added, literals eliminated, …) |
There are no other options: c2 has no era/universe setting and no knob for the loop-instruction distance guard, which is always on (see design §6).
2. What it does
It rewrites the assembly, it does not regenerate it: branch shortening and jump
threading, dead-code and redundant-instruction removal, cross-jumping and code
motion, redundant-move elimination with register tracking, and turning a counted
dec/jne loop into a single sob. The result reassembles to smaller,
equivalent code — the same answer, fewer bytes.
3. Invocation (via cc)
cc -O runs c2 as the c2 stage of cpp → c0 → c1 → c2 → as → ld. c1 writes
its assembly to a temp file (tmp5) instead of the usual output, and cc
invokes
c2 tmp5 tmp3
so c2 reads tmp5 and writes tmp3, the assembler source as then consumes.
You do not choose these paths; cc allocates and removes them, and prints
Pass 2 under cc -v when it reaches c2. Without -O the pass is not run at
all — the default pipeline matches cc without -O. If c2 fails, cc falls
back to the unoptimised c1 output, so a c2 problem is invisible through cc -O
alone (run c2 directly to see it).
4. Exit status
c2 always exits 0. It does not fail on the content of well-formed assembly
— it either improves a construct or leaves it alone. A genuine failure is an I/O
or resource problem reported to stderr and exited non-zero before optimisation
begins: a file it cannot open (C2: can't find … / can't create …) or no
memory for its node arena (C2: no memory / C Optimizer: out of space).
5. Examples
Normally you never type c2; you turn it on through cc:
pdp11-cc -O prog.c -o prog # compile with the peephole optimiser
pdp11-cc -O -S prog.c # stop after c2: optimised prog.s
pdp11-cc -v -O -c prog.c # see each pass, including "Pass 2" (c2)
To run the pass by hand for debugging (what cc does between its temp files):
pdp11-c2 < foo.s > foo.opt.s # the filter form cc -O uses
pdp11-c2 foo.s foo.opt.s # named input and output
pdp11-c2 - foo.s foo.opt.s # same, and print stats to stderr
pdp11-c2 + foo.s foo.opt.s # same, with the debug dump
Continue to the design document for the transformations c2 runs and the register tracking that keeps them safe.