Appendix: runtime optimization¶
DCCRTL.MAC is a single ~19,000-line runtime, but most programs use only a
fraction of it. The normal DCC C Compiler build flow runs dccrtlstrip before the final
L80 link to remove unreferenced routines. This appendix explains how it decides
what to keep and what each library feature costs in code size once its
transitive dependencies are linked.
How dccrtlstrip decides what to keep¶
Most library names in the standard headers are ordinary C identifiers. During
code generation, DCC C Compiler maps well-known library calls to short internal assembler
labels (for example memcpy becomes __mcpy, strlen becomes __slen). Do
not write those short names yourself; include the header and call the C
function. These internal names are what dccrtlstrip sees when it scans the
generated .MAC file.
dccrtlstrip is a conservative dead-block eliminator that runs before L80
linking. Its flow:
- Split into blocks.
DCCRTL.MACis split into blocks delimited bypublicdirectives. A run of consecutivepubliclines becomes a shared prelude block, and each real public label after it becomes its own block that depends on the prelude. Everything before the firstpublic(theorg 100h, theextrndeclarations, theerrnoEQUs,HDRSIZE) is an unconditional preamble. - Scan the app for references. For each app
.mac, opcodes are parsed and their symbol operands recorded as roots (extrn,call,jp,jr,dw, andldforms). A fallback whole-token scan also treats any exact mention of a known runtime symbol as a root. - Mark reachable blocks.
startis forced as a root. Each root's owning block (plus its prelude) is kept, then the kept blocks are re-scanned for further references, iterating to a fixpoint. Transitive runtime-to-runtime dependencies are therefore linked automatically. - Write the output. The preamble is emitted unconditionally, then only the
kept blocks;
publiclines are filtered so only kept symbols are re-declared.
Design consequences¶
- Transitivity is automatic — keeping
_printfre-scans its body and links thepf_*helpers; keeping a float op links the classify helpers. - The fallback scan is deliberately over-conservative — any mention of a runtime symbol's exact name keeps it. dcc emits the matching formatted-output entry point after per-call format analysis, so its selected float/long paths are retained automatically.
- Unused features cost nothing — a program that never does
floatarithmetic keeps none of the float blocks.
How to read the size numbers¶
The per-function size tables live on a dedicated, auto-generated page —
Runtime function sizes — which is rebuilt from
DCCRTL.MAC on every docs build so the numbers never drift. Each routine is
reported with three figures:
- self = source lines in the function's own block.
- marginal = self + every additional reachable block that is not already in the always-present baseline. This is the true incremental cost of using that function in a program that otherwise wouldn't need it.
- pulls in = the extra runtime blocks added beyond the baseline.
The rest of this page explains the structure the numbers reflect — the always-present baseline and the shared cores that make the first call into a feature expensive — and the optimisation takeaways that follow from it.
The always-present baseline¶
Every program links these regardless of what it calls, because start is a
forced root:
| Block | Role |
|---|---|
start |
entry, heap init, BSS zeroing, calls _main |
__build_argv (+ __conout, __argbuf, argv) |
command-tail argv builder; also holds __conout, the console writer |
__brk, __hlimit |
heap state words |
_exit (+ __cpm_set_retcode) |
reached from start after _main returns |
Because __conout lives inside the __build_argv block, console output costs
nothing extra — putchar/puts call already-present code. (See
Runtime function sizes for the exact baseline line
count.)
The shared cores¶
The runtime's size is dominated by a handful of shared cores. A feature's
first call links the whole core; additional calls in the same family are then
nearly free. This is why the marginal column on the
sizes page can dwarf a routine's self count.
Console-only output: avoid the file-stream functions
fputc/fputs/fprintf are not lightweight even when you only ever
target the console — they dispatch on the file descriptor and therefore link
the whole low-level file-I/O core. For console-only output prefer
putchar/puts/printf.
- Formatted I/O. Integer
printfis a self-contained monolith; aprintf-family call whose format needs%flinks the entire float stack on top of it. Literal formats are analyzed automatically; non-literal formats conservatively select all optional paths.sprintf/vprintf/vsprintfreuse the formatter for free, whilefprintf/vfprintfcarry the file-I/O core. scanffamily.scanf/sscanfare tiny stubs that jump into the sharedfscanfcore, so using any one links all three plus the read path.- Low-level file I/O.
open/read/write/close/lseek/unlink/fsync/fdatasyncshare one FCB/DMA core. Using any one links that core. - Memory.
malloc/calloc/realloc/freelink the heap helpers (__mlh,__frcoal);callocadds overflow-checked size arithmetic. - 32-bit
long. Multiply/divide/modulo route through a small set of long helpers (__lmd,__lmu, …); the compare operators are self-contained. - Float. A single
floatoperator links the shared normalise/round core.
frexpf / ldexpf are the cheap float functions
They manipulate IEEE-754 bits directly (no arithmetic core), so they only
link a couple of classify helpers. Everything else requires the full float
arithmetic stack, and the exp/log/pow and hyperbolic group is the most
expensive group to link: budget ~2,000–3,300 lines for any one of them.
String and ctype routines are the exception: almost all are self-contained and
link nothing beyond themselves. strdup is the notable outlier: it allocates,
so it inherits the whole malloc chain.
Optimisation takeaways¶
- Console-only output is cheap.
putchar,puts, and integerprintfonly touch already-present code or are self-contained. Avoidfputc/fputs/fprintffor console work — they link the file-I/O core. printfis an 842-line monolith but links nothing else. A%fformatted-output call roughly triples that by linking the entire float stack.vprintf/vsprintfreuse that engine for free;vfprintfcarries the file-I/O core likefprintf.- Any single low-level file call links the whole FCB/DMA core (~470 lines). The first file function is expensive; additional ones are nearly free.
scanf/sscanfare not small — they share the 697-linefscanfcore.- Float is the biggest lever. A single
floatoperator links ~700+ lines;sqrtf/fmodfexceed 1,300 lines, and theexpf/logf/powfand hyperbolic group runs ~2,000–3,300 lines. malloc/calloclink integer mul/div/mod helpers for size arithmetic;strdupinherits the wholemallocchain.- String/ctype routines are individually cheap — they link only themselves.
The practical rule: every call either stays cheap or links a substantial amount
of support code. Use the console functions, integer-only printf, and the
self-contained string helpers when binary size matters. Treat float formatting
and transcendental math functions as deliberate, budgeted choices.