Utilities¶
Utilities for building and testing DCC C Compiler programs.
Use dccmake for application projects.
Toolchain Commands¶
The DCC C Compiler toolchain is a small set of host tools, CP/M tools, and
runtime assets. dccmake resolves these commands from explicit settings or
environment variables first, then from the local checkout or PATH.
| Tool | Role | Notes |
|---|---|---|
dcc |
C compiler | Host command that translates C source to M80-compatible .MAC assembly |
dccmake |
Build pipeline helper | Owns the normal compile, optimize, strip, assemble, and link pipeline; see Build Pipeline Helper (dccmake) |
dccpeep |
Peephole optimizer | Host command that rewrites generated .MAC files when dcc-peep=true |
dccrtlstrip |
Application/runtime stripper | Host command that removes unreachable marked app blocks and writes a reduced runtime; see DCCRTL strip appendix |
DCCRTL.MAC |
Runtime source | Full CP/M runtime consumed by dccrtlstrip |
m80c |
Native assembler | Host command, LINK-80-.REL-compatible; default assembler, no ntvcm needed |
l80c |
Native linker | Host command, consumes the same .REL format; default linker, no ntvcm needed |
ntvcm |
CP/M emulator | Only needed for the real M80/L80 fallback path, and to run the final .COM programs |
m80.com |
CP/M assembler | Real Microsoft assembler; assembles .MAC to .REL under ntvcm when dcc-use-emulated-m80=true |
l80.com |
CP/M linker | Real Microsoft linker; links .REL files to .COM under ntvcm when dcc-use-emulated-l80=true |
Native Assembler (m80c)¶
m80c is the host-native assembler used by the normal DCC build pipeline. It
accepts the 8080 and Z80 source forms used by DCC, performs two assembly passes,
and writes LINK-80-compatible .REL objects without running Microsoft
M80.COM under an emulator.
m80c CLI usage¶
m80c [rel-output[,listing-output]]=source[.MAC] [options]
An omitted extension defaults to .MAC, .REL, or .PRN as appropriate.
Use * in an output position to suppress that explicit name. Output basenames
use CP/M-style uppercase spelling.
Common examples:
m80c "=FOO.MAC" /X /O /Z /L
m80c "FOO.REL,FOO.PRN=FOO.MAC" /Z
m80c "=FOO.MAC" /X /O /Z /L /C
| Option | Default | Purpose |
|---|---|---|
/Z |
on | Select Z80 mnemonic interpretation. For example, operand-free CPI is the Z80 block-compare instruction. |
/I |
off | Select Intel 8080 mnemonic interpretation. For example, CPI value is immediate compare. |
/L |
off | Write the .PRN assembly listing. |
/R |
when a REL output is named | Request .REL object output. |
/O |
off | Request .REL object output in the conventional M80 command form. Native listings remain hexadecimal. |
/H |
accepted | Select hexadecimal listing compatibility; hexadecimal is already the native listing format. |
/M |
off | Materialize DS storage as zero bytes instead of leaving reserved gaps. |
/C |
off | Write the per-module .SYM sidecar used by native l80c to preserve relocated local symbols. |
/X |
accepted | Accepted for M80 command compatibility; native m80c has no separate cross-reference output mode. |
The assembler always writes .LNK segment-size metadata. When DCC source debug
markers are present, it also writes .DBG source/symbol metadata; /C is not
required for .DBG. Assembly errors are recorded in the listing when /L is
enabled and cause a nonzero exit status.
Native Linker (l80c)¶
l80c is the host-native linker used by the normal DCC build pipeline. It
consumes the LINK-80-compatible .REL files produced by
m80c, resolves
PUBLIC/EXTRN symbols, relocates CSEG and DSEG values, and writes a CP/M .COM
image plus a linked .SYM file. Unlike L80.COM, it uses host memory rather
than CP/M's 64K address space, so large links do not exhaust the linker's own
workspace.
l80c CLI usage¶
l80c [/P:origin,]module1,module2,...,output/N/E/Y [-o output[.COM]] [-v]
Module and output names conventionally use CP/M uppercase spelling. .REL is
added to module names automatically. Repeating the final module with /N/E/Y,
as in a Microsoft LINK-80 command, does not link it twice.
| Option | Default | Purpose |
|---|---|---|
/P:<hex-address> |
100 |
Set the linked program origin. The address must fit in 16 bits. |
output/N/E/Y |
Last module | Select the output basename using conventional LINK-80 syntax. /N, /E, and /Y do not otherwise change native non-interactive linking. |
-o <name> |
Output marker or last module | Select the output basename explicitly. Either APP or APP.COM produces APP.COM and APP.SYM. |
-v |
off | Print loaded modules, origin, linked size, and output path. |
The normal DCC command is:
l80c "/P:100,RTLMIN,FOO,FOO/N/E/Y"
Program origins¶
Use /P:100 for ordinary CP/M applications. CP/M loads a headerless .COM
file at 0100H, so this origin writes the linked program directly with no
entry wrapper.
l80c also implements LINK-80-compatible nonstandard origins:
0101Hor0102H: place the program at that address without generating a jump because the program occupies part of LINK-80's0100H-0102Hentry slot.- Above
0102H: putJP <start-address>at0100H, pad to the requested origin, and place the relocated program there. - Below
0100H: store the payload after the CP/M entry point and add a small 8080/Z80-compatible bootstrap that copies it to the linked origin before jumping to the program start.
Nonstandard origins are useful for fixed-address code, overlays or separately loaded modules, programs reserving low TPA space for a loader or shared data, and system utilities that deliberately relocate or take over the machine. High origins remain normal CP/M programs through the generated entry jump. Low origins, especially zero, overwrite CP/M low memory and therefore suit only specialized programs that do not expect CP/M services to remain intact.
Not a bare-metal output mode
/P:0 still produces a CP/M-loadable .COM with an entry/copy bootstrap.
It does not emit a raw image beginning at file offset zero. A future
bare-metal target should use a separate explicit raw-binary option so its
startup, memory map, stack, and runtime assumptions are unambiguous.
Native l80c currently supports the CSEG/DSEG REL records used by DCC. It
rejects ASEG, COMMON, malformed records, undefined externals, duplicate globals,
and images extending past FFFFH rather than guessing and producing a bad
executable.
Build Pipeline Helper (dccmake)¶
dccmake is the lower-level build helper used by the test runner and by
repeatable local builds. It compiles one or more C source files, optionally runs
dccpeep, removes unreachable application functions and objects, strips the
runtime with dccrtlstrip, then assembles and links with
native m80c/l80c by
default (or the real M80/L80 under ntvcm when
dcc-use-emulated-m80/dcc-use-emulated-l80 is set - real L80 runs inside
ntvcm's emulated 64K CP/M address space, so its own symbol/relocation
workspace can run out of memory on large nopeep builds well before the
target program itself would not fit; l80c has no such
ceiling).
Use dccmake directly when you want one command that owns the whole DCC C
Compiler pipeline but still lets you choose the exact source files, output name,
runtime, include directories, and tool paths.
dccmake CLI Usage¶
dccmake [key=value ...] [dcc-style-options]
dccmake --dcc-input main.c,module.c --dcc-output APP
dccmake main.c module.c dcc-output=APP dcc-peep=true
Command-line settings may be written as key=value, --key=value, or
--key value. Positional .c arguments are treated as dcc-input files. Files
after the first input are compiled with -module automatically.
dccmake CLI Examples¶
dccmake tests/sieve.c dcc-output=SIEVE
dccmake tests/sieve.c dcc-output=SIEVE dcc-peep=false
dccmake main.c module1.c module2.c dcc-output=APP dcc-include-directory=include
dccmake tests/attnc99.c dcc-output=ATTNC99 dcc-stack-bytes=768 dcc-peep=true
dccmake also accepts common dcc-style options and maps them onto pipeline
settings:
dccmake tests/tprintf.c dcc-output=TPRINTF -ffloatio # blanket force-on override
dccmake tests/app.c dcc-output=APP -I include -DDEBUG=1 -UOLD
dccmake tests/app.c dcc-output=APP -stack 1024 -fstack-check
dccmake.txt Files¶
When a dccmake.txt file exists in the current directory, dccmake reads it
first and then applies command-line settings as overrides. The file uses one
key=value setting per line. Blank lines are ignored, and text after # is a
comment.
Values may reference environment variables with ${NAME}. The variable must be
set, and malformed references are errors. This is useful for checking a project
configuration into source control without hard-coding checkout-specific paths.
# dccmake configuration for ATTNC99
dcc-input=attnc99.c
dcc-output=ATTNC99
dcc-peep=true
dcc-build-dir=build
dcc-runtime=${DCC_DIR}/DCCRTL.MAC
dcc-include-directory=${DCC_DIR}
dcc-tool=${DCC_DIR}/dcc
dccpeep-tool=${DCC_DIR}/dccpeep
dccrtlstrip-tool=${DCC_DIR}/dccrtlstrip
m80c-tool=${DCC_DIR}/m80c
l80c-tool=${DCC_DIR}/l80c
With that file in place, set the tool roots and build the app:
export DCC_DIR="$HOME/GitHub/dcc"
dccmake
On Windows, set DCC_DIR in PowerShell and append .exe to the five host-tool
paths. Emulator paths are unnecessary for the default native build pipeline.
The generated CP/M executable lands in the configured build directory, for
example build/ATTNC99.COM. To place that executable beside your source,
copy just the selected output:
cp build/ATTNC99.COM .
For source debugging, keep its matching .DBG alongside it. Preserve the
build directory when it contains other applications, fixtures, or outputs you
still need.
Command-line values override the file, so this builds the same app without the peephole optimizer:
dccmake dcc-peep=false
dccmake Settings¶
| Setting | Default | Purpose |
|---|---|---|
dcc-input |
(required) | Comma-separated C sources; positional .c arguments are also accepted |
dcc-output |
First input base name | CP/M 8-character output base name |
dcc-floatio |
false |
Force %f support on every printf-family call when true; literal formats are normally detected per call |
dcc-no-floatio |
false |
Force %f support off even for matching literals or the non-literal fallback |
dcc-flongio |
false |
Force long-format support on every printf-family call when true; literal formats are normally detected per call |
dcc-no-longio |
false |
Force long-format support off even for matching literals or the non-literal fallback |
dcc-hexio |
false |
Force %x/%X support on every printf-family call |
dcc-no-hexio |
false |
Force %x/%X support off, even for matching formats |
dcc-octio |
false |
Force %o support on every printf-family call |
dcc-no-octio |
false |
Force %o support off, even for matching formats |
dcc-stack-bytes |
512 |
Stack reserve passed to dcc with -stack |
dcc-stack-check |
Environment/default | Pass -fstack-check to dcc |
dcc-no-narrow |
false |
Pass -fno-narrow to disable byte-narrowing passes |
dcc-debug |
false |
true emits full conservative debug metadata; lines emits release-identical optimized metadata with ranged variable locations. Both require native m80c |
dcc-include-directory |
Auto-adds . when standard headers are in the current directory |
Comma-separated include directories; dcc-include is an alias |
dcc-define |
none | Comma-separated NAME[=value] entries passed to dcc as -D; dcc-defines is an alias |
dcc-undefine |
none | Comma-separated names passed to dcc as -U; dcc-undefines is an alias |
dcc-peep |
true |
Run dccpeep after compiling each .MAC file |
dcc-peep-debug |
false |
Run dccpeep over full conservative -g output; use dcc-debug=lines for release-identical optimized code |
dcc-strip-unused |
true |
Remove application functions and objects unreachable across all input modules. Set false when producing a module intended for a separate later link |
dcc-build-dir |
build |
Artifact directory |
dcc-runtime |
DCC_RUNTIME, local DCCRTL.MAC, or DCCRTL.MAC |
Runtime source passed to dccrtlstrip |
dcc-tool |
DCC, local dcc, or dcc |
DCC compiler command |
dccpeep-tool |
DCCPEEP, local dccpeep, or dccpeep |
Peephole optimizer command |
dccrtlstrip-tool |
DCCRTLSTRIP, local dccrtlstrip, or dccrtlstrip |
Application/runtime stripper command |
ntvcm-tool |
NTVCM or ntvcm |
Emulator command used to run M80/L80 (only when either is emulated) |
m80-command |
M80 or m80 |
CP/M assembler command passed to ntvcm; emulated-M80 path only |
m80c-tool |
M80C, local m80c, or m80c |
Native host assembler command (default, no ntvcm); see m80c |
dcc-use-emulated-m80 |
false |
Assemble with real M80.COM under ntvcm instead of native m80c |
l80-command |
L80 or l80 |
CP/M linker command passed to ntvcm; emulated-L80 path only |
l80c-tool |
L80C, local l80c, or l80c |
Native host linker command (default, no ntvcm); see l80c |
dcc-use-emulated-l80 |
false |
Link with real L80.COM under ntvcm instead of native l80c |
dccmake initializes its Boolean settings from the matching environment
variables before reading dccmake.txt: DCC_FLOATIO, DCC_NO_FLOATIO,
DCC_LONGIO, DCC_NO_LONGIO, DCC_HEXIO, DCC_NO_HEXIO, DCC_OCTIO,
DCC_NO_OCTIO, DCC_FORCE_STACK_CHECK, DCC_NO_NARROW, DCC_DEBUG,
DCC_DEBUG_LINES,
DCC_PEEP_DEBUG, DCC_USE_EMULATED_M80, and
DCC_USE_EMULATED_L80. File settings and then command-line settings override
those defaults.
With all formatted-I/O settings at their default false, dccmake passes no
override to dcc and adds no forced keep root to dccrtlstrip. dcc therefore
performs its normal per-call detection for float, long, hexadecimal, and octal
formats. The force-on settings are neutral when false; use a dcc-no-*io
setting only when that format support must be forced off.
Source input basenames and the output name must be CP/M 8.3-clean. For example,
module1.c is valid, but a generated module output base longer than eight
characters is not.
dccmake dcc-style Options¶
| Option | Equivalent setting |
|---|---|
-f, -ffloatio |
dcc-floatio=true |
-fno-floatio |
dcc-no-floatio=true |
-fl, -flongio |
dcc-flongio=true |
-fno-longio |
dcc-no-longio=true |
-fhexio |
dcc-hexio=true |
-fno-hexio |
dcc-no-hexio=true |
-foctio |
dcc-octio=true |
-fno-octio |
dcc-no-octio=true |
-s <bytes>, -stack <bytes>, -stack=<bytes> |
dcc-stack-bytes=<bytes> |
-fstack-check |
dcc-stack-check=true |
-fno-narrow |
dcc-no-narrow=true |
-g |
dcc-debug=true |
-gline |
dcc-debug=lines |
-femulated-m80 |
dcc-use-emulated-m80=true |
-femulated-l80 |
dcc-use-emulated-l80=true |
-I <dir>, -Idir |
Add an include directory |
-D <name>[=value], -Dname=value |
Pass a define to dcc |
-U <name>, -Uname |
Pass an undefine to dcc |
-v, --version |
Print dccmake version |
-c and -module are rejected because dccmake decides module mode from the
input order.
Peephole Optimizer (dccpeep)¶
dccmake runs dccpeep after compilation when dcc-peep=true. It can also be
invoked directly:
dccpeep [-Ot|-Os] [-fstats] input.mac output.mac
| Option | Purpose |
|---|---|
-Ot |
Optimize for execution time (default) |
-Os |
Optimize for code size |
-fstats |
Print optimization statistics |
Test Suite Runner (runall.ps1)¶
Builds and runs the test suite against per-app baselines in tests/baselines/.
It uses dccmake for builds and tests/_test_overrides.json for test-specific
runtime arguments, stack sizes, and optional DCC C Compiler build flags.
Runs in parallel by default:
- Each app builds in its own directory below a per-invocation
<build-root>/run-<pid>/folder, so concurrent builds do not clobber shared artifacts. - The per-invocation folder is removed automatically on exit; pass
-KeepBuildto retain it. On Linux, the build root defaults to/dev/shm/dcc-runallwhen available; elsewhere it defaults tobuild. - Failures print as each app completes. PASS lines are suppressed by default;
pass
-FailuresOnly:$falseto show every result. - Use
-Serialto fall back to sequential builds in the sharedbuild/directory. - Pass
-Extendedto also run the imported c-testsuite single-exec corpus (viarunall-extended.ps1) after the main suite. - The lightweight stack-overflow guard (
-fstack-check) is on by default; pass-NoStackCheckto build without it. - Z80 cycle counts and
.COMsizes are checked againsttests/perf_baselines.csvby default, with no separate benchmark pass. Use-NoPerfCheckto skip this check or-UpdatePerfBaselineafter an intentional measured change. - Pass
-Reportto append cycles,.COMsizes, and clock-normalized times to a historical CSV report. Report mode implies-NoStackCheck.
Test Runner Usage¶
./scripts/runall.ps1 [options]
With no options, the suite runs in parallel, enables -fstack-check, and uses
-Mode fast. Use -Mode full to run builds with and without dccpeep.
Test Runner Examples¶
./scripts/runall.ps1 # quick optimized-only default
./scripts/runall.ps1 -Help # show help and exit
./scripts/runall.ps1 -Serial # sequential fallback
./scripts/runall.ps1 -NoStackCheck # build without the stack guard
./scripts/runall.ps1 -ThrottleLimit 8 # cap concurrency
./scripts/runall.ps1 -Emulator altair
./scripts/runall.ps1 -Mode fast # optimized build only
./scripts/runall.ps1 -Mode nopeep # build without dccpeep only
./scripts/runall.ps1 -Apps tprintf,tlong # selected apps only
./scripts/runall.ps1 -FailFast # stop dispatching after a failure
./scripts/runall.ps1 -FailuresOnly:$false # include PASS lines
./scripts/runall.ps1 -Extended # also run extended c-testsuite
./scripts/runall.ps1 -KeepBuild # keep the per-run build folder
./scripts/runall.ps1 -Report # also append build/perf_results.csv
./scripts/runall.ps1 -Mode full -UpdatePerfBaseline
Build Modes¶
The -Mode parameter selects which optimization pass(es) to build and verify.
The default is fast.
fast— optimized: runs thedccpeeppeephole optimizer after compiling. This produces the optimized CP/M Z80 binary.nopeep— skipsdccpeep. Compiler MIR optimizations and application stripping still run; this is not an-O0build.full— builds and verifies each app twice, once in each mode, against the same baseline. This catches optimizer bugs that change a program's output.
Test Runner Parameters¶
| Parameter | Default | Purpose |
|---|---|---|
-Emulator |
ntvcm |
Emulator command for running .COM files |
-NoStackCheck |
(off) | Disable -fstack-check (the guard is ON by default) |
-UseEmulatedM80 |
(off) | Assemble with M80.COM under ntvcm instead of native m80c |
-UseEmulatedL80 |
(off) | Link with L80.COM under ntvcm instead of native l80c |
-BuildDir |
build |
Build directory for artifacts |
-NoRamDisk |
(off) | On Linux, disable the automatic /dev/shm/dcc-runall build root |
-BaselineDir |
tests/baselines |
Directory of per-app <app>.txt baselines |
-Mode |
fast |
Build mode: fast (with dccpeep), nopeep (without dccpeep), or full (both) |
-Apps |
all tests | Comma-separated app names to run |
-RunTimeout |
60 |
Per-build and per-emulator-run timeout in seconds |
-Help |
(off) | Show help text and exit without building or running tests |
-Extended |
(off) | Also run the extended c-testsuite corpus after the main suite |
-Serial |
(off) | Run sequentially instead of the default parallel mode |
-ThrottleLimit |
CPU core count | Max concurrent apps in parallel mode |
-KeepBuild |
(off) | Keep the per-invocation run folder instead of removing it on exit (parallel mode) |
-FailFast |
(off) | Stop dispatching new apps after the first correctness or performance failure |
-FailuresOnly |
on | Suppress PASS lines; pass -FailuresOnly:$false for full output |
-Report |
(off) | Append cycle, normalized-time, and .COM size metrics to a CSV report; implies -NoStackCheck |
-ReportFile |
build/perf_results.csv |
CSV path used by -Report |
-ReportClockHz |
400000000 |
Nominal clock used to derive report milliseconds from Z80 cycles; does not throttle execution |
-NoPerfCheck |
(off) | Skip the default cycle-count and .COM size regression check |
-UpdatePerfBaseline |
(off) | Update checked performance columns for the modes built by this run |
-PerfBaselineFile |
tests/perf_baselines.csv |
Checked cycle-count and .COM size baseline |
-NarrowDiff |
(off) | Compare normal and -fno-narrow runs to detect narrowing behavior changes |
-TimingBreakdown |
(off) | Print suite-phase and aggregate build-pipeline timing percentages |
Output¶
Reports:
- Total apps discovered
- Passed/failed/skipped counts
- Per-app build and execution status (live in parallel mode)
- Output verification against baseline
- Optional CSV performance report when
-Reportis passed - Exit code 0 on success, 1 on failure
MIR Verifier and Loop Regressions¶
The host MIR verifier tests construct valid and deliberately malformed MIR directly against the compiler implementation. Unlike the native C validator below, they test compiler-internal contracts rather than application output. They cover structural and ABI checks, reachable-value dominance, PHI edges, call-argument dominance, loop-entry promotion, and irreducible control flow.
From the repository root, with CMake and a host C compiler installed:
cmake -S src/dcc -B build/mir-tests -DDCC_BUILD_MIR_TESTS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build/mir-tests --target mir-verify-test --config Release --parallel
ctest --test-dir build/mir-tests -C Release --output-on-failure
DCC_BUILD_MIR_TESTS is an opt-in build option, not a switch for compiler
verification. Production dominance verification is always enabled. CI builds
and runs this host test target on Linux, macOS, and Windows.
For Clang/GCC sanitizer testing, configure a separate build directory:
cmake -S src/dcc -B build/mir-tests-sanitize -DDCC_BUILD_MIR_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer"
cmake --build build/mir-tests-sanitize --target mir-verify-test --parallel
ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 ctest --test-dir build/mir-tests-sanitize --output-on-failure
Target execution tests use the built DCC toolchain, PowerShell 7, and ntvcm
on PATH:
pwsh ./scripts/run-mir-clobber-tests.ps1 -Cases domloop
pwsh ./scripts/run-mir-clobber-tests.ps1 -Cases semantics
domloop checks zero-, one-, and multi-iteration loops, initialized and
body-defined values, successive scopes, and branch-dependent loop updates.
The body-defined cases enter the loop before using the value; they do not
assert a result for an uninitialized C read. semantics checks pointer
scaling, call conversions, and volatile access counts and flags in MIR.
Both fixtures run in release, full-debug, and line-debug modes, with and
without peephole optimization and stack checks. The complete clobber suite
runs in CI and can be invoked by omitting -Cases.
See verification, promotion, and allocation for the compiler pipeline these tests exercise.
Host Unit Test Validator (validate-unit-test.ps1)¶
Compiles each tests/*.c program with a native host C compiler, runs the host
executable, and compares stdout with tests/baselines/<app>.txt. This is a
read-only baseline check: it never rewrites baseline files. It is useful for
checking that the unit-test sources and expected output still make sense on a
normal C implementation before comparing them with the DCC C Compiler output.
Host compiler selection follows scripts/build-dcc.ps1:
- Windows uses MSVC
cl.exeafter locating the Visual Studio C++ build tools. On Windows ARM64, it uses the native ARM64 MSVC tools. - macOS uses
clangby default. - Linux uses
gccby default. - Unix-like hosts can override the compiler with
-CCor theCCenvironment variable, for example-CC clangorCC=clang.
Tests that need CP/M or Z80-only behavior, such as BDOS calls, direct port I/O,
getch/kbhit, inline #asm, or CP/M vector reads, are skipped because a host
compiler cannot run those semantics. The script also honors
tests/_test_overrides.json for app arguments, stdin, ignored apps, and
host-only skip settings.
Host Validator Usage¶
./scripts/validate-unit-test.ps1 [options]
Host Validator Examples¶
./scripts/validate-unit-test.ps1 # validate every runnable test
./scripts/validate-unit-test.ps1 -App tprintf # validate one test app
./scripts/validate-unit-test.ps1 -CC clang # use clang on Linux/macOS
./scripts/validate-unit-test.ps1 -Serial # sequential fallback
./scripts/validate-unit-test.ps1 -Help # show help and exit
Host Validator Parameters¶
| Parameter | Default | Purpose |
|---|---|---|
-BuildDir |
build/host-validate |
Directory for host compiler outputs |
-BaselineDir |
tests/baselines |
Directory of per-app <app>.txt baselines |
-CC |
platform default | C compiler override on macOS/Linux; ignored on Windows |
-App |
all tests | Validate one test app, without the .c extension |
-RunTimeout |
10 |
Seconds to allow each host executable to run |
-Serial |
(off) | Run sequentially instead of the default parallel mode |
-ThrottleLimit |
CPU core count | Max concurrent apps in parallel mode |
-Help |
(off) | Show help text and exit without building or running tests |
Linux 32-bit Validation¶
On Linux, the validator can extend coverage by using GCC's -m32 mode when the
compiler can build and link 32-bit executables. The script probes this
automatically: if the probe succeeds, Linux GCC host validations run with
-m32; if it fails, the script keeps using the normal compiler mode.
This matters because the DCC C Compiler has 16-bit pointers and 32-bit long, so a 32-bit host
build can run a few host-only tests that are skipped on a normal 64-bit Linux
compiler. Install the normal C build tools plus the 32-bit development libraries
for your distribution, then rerun the validator.
Common Linux packages:
| Distribution | Command |
|---|---|
| Debian/Ubuntu | sudo apt update && sudo apt install build-essential gcc-multilib libc6-dev-i386 |
| Fedora | sudo dnf groupinstall "Development Tools" && sudo dnf install glibc-devel.i686 libgcc.i686 |
| RHEL/CentOS | sudo dnf groupinstall "Development Tools" && sudo dnf install glibc-devel.i686 libgcc.i686 |
| Arch | Enable the multilib repository, then sudo pacman -S base-devel lib32-glibc |
| openSUSE | sudo zypper install -t pattern devel_C_C++ && sudo zypper install gcc-32bit glibc-devel-32bit |
After installation, this should be enough to enable the extended path:
./scripts/validate-unit-test.ps1
The script prints the selected compiler line near the start of the run. When the
32-bit probe succeeds on Linux GCC, that line includes (-m32).
Test Overrides (tests/_test_overrides.json)¶
Per-test run configuration used by runall.ps1.
It lives in the tests/ folder (alongside the test sources it configures) and
is named with a leading underscore so it sorts to the top of the directory.
Most tests need no entry — they compile cleanly, take no arguments, and use the default 512-byte stack. This file only lists the exceptions.
Schema¶
The file is a single JSON object with an apps array. Each element configures
one test, keyed by name:
{
"apps": [
{
"name": "<app>",
"args": "<string>",
"fixtures": ["<file>"],
"extra_scenarios": [
{ "suffix": "<name>", "args": "<string>", "fixtures": ["<file>"] }
]
}
]
}
| Property | Type | Required | Default | Purpose |
|---|---|---|---|---|
name |
string | yes | — | Test name, without the .c extension (e.g. ttt, cobint) |
args |
string | no | "" |
Command-line arguments passed to the program when run. Multi-token strings are split on whitespace (e.g. "a bb ccc") |
stdin |
string | no | "" |
Text piped to the program's standard input during execution (for keyboard/input-driven tests) |
stack_size |
integer | no | 512 |
C stack reserve in bytes, passed to dcc as -stack. Used by recursive apps that need more headroom |
dcc_args |
string | no | "" |
Extra DCC C Compiler build arguments passed through dccmake (for example -DNAME=1 -UOLD) |
dcc_floatio |
boolean | no | environment/default | True forces -ffloatio; false leaves per-call auto-detection active for this app |
dcc_longio |
boolean | no | environment/default | True forces -flongio; false leaves per-call auto-detection active for this app |
fixtures |
string array | no | [] |
Files from tests/ copied into the app build directory under uppercase CP/M names |
extra_scenarios |
object array | no | [] |
Additional argument/input/fixture scenarios run against the same built .COM |
ignore |
boolean | no | false |
When true, the test is skipped entirely (not built or run) |
perf_ignore |
boolean | no | false |
Exclude nondeterministic apps from cycle-count regression checks |
narrow_diff_ignore |
boolean | no | false |
Exclude layout-sensitive apps from -NarrowDiff |
host |
boolean | no | false |
Skip ordinary host validation; with the 32-bit requirement below, allow only the Linux -m32 path |
requires-32bit-linux-host-compiler |
boolean | no | false |
With host: true, allow host validation only when Linux GCC's probed -m32 mode is active |
requires-non-msvc-host-compiler |
boolean | no | false |
Skip host validation under MSVC |
requires-non-macos-host-compiler |
boolean | no | false |
Skip host validation on macOS |
host-cflags |
string | no | "" |
Replace the default GCC/Clang flags for this app's host build |
Each extra_scenarios object accepts a required suffix plus optional args,
stdin, and fixtures. It reuses the primary .COM and compares output with
tests/baselines/<app>_<suffix>.txt. Entries with none of the optional
properties have no effect, so an app only appears here if it overrides at least
one default.
Example¶
{
"apps": [
{
"name": "pint",
"args": "e.pas",
"stack_size": 768,
"fixtures": ["E.PAS"],
"extra_scenarios": [
{ "suffix": "ttt", "args": "ttt.pas", "fixtures": ["TTT.PAS"] }
]
},
{ "name": "tkbd", "stdin": "x", "perf_ignore": true },
{ "name": "tstackov", "host": true, "narrow_diff_ignore": true },
{ "name": "na", "ignore": true }
]
}
Common reasons to add an entry¶
- Program reads a data file — declare it in
fixturesand pass its CP/M name inargs; only that app's build directory receives the file. - One binary needs several datasets — use
extra_scenariosto rerun the same.COMwith separate arguments, fixtures, and baselines. - Program reads from stdin — set
stdinfor tests that require scripted keyboard/input text (for example,tkbdexpectsx). - Deep recursion — apps such as
triangle(768) andcobint(1536) need a largerstack_sizethan the 512-byte default, especially under-fstack-check. - Cannot be auto-tested — set
ignore: truefor interactive programs (na, an editor that waits for keystrokes), tests that intentionally fail to compile (tc89fltb), or deliberate stack-smashers (spsmash). - Results are inherently nondeterministic — use
perf_ignorefor timing- or filesystem-sensitive apps, andnarrow_diff_ignoreonly when output legitimately depends on stack layout. - Host compiler coverage is conditional — use the host requirement fields
for ABI, compiler, or libc differences;
host-cflagshandles per-test C mode or optimizer requirements.
To change a test's run behavior, edit tests/_test_overrides.json and re-run
the suite. See also tests/README.md in the repository for how tests,
baselines, and this file relate.
Performance Reporting (runall.ps1 -Report)¶
runall.ps1 -Report appends historical performance data during the normal
verified suite; it does not add a separate benchmark pass. Every ntvcm run
already uses -p -s:0, so execution remains unthrottled while the runner records
the emulator's host-independent Z80 cycle count and the .COM size.
Report mode implies -NoStackCheck. The peep_ms and nopeep_ms values are
derived as cycles / ReportClockHz * 1000, using a default nominal clock of
400 MHz; ReportClockHz never changes emulator speed. Set it to 0 to leave
the derived millisecond fields empty while retaining cycles and sizes.
./scripts/runall.ps1 -Report
./scripts/runall.ps1 -Report -ReportFile results.csv
./scripts/runall.ps1 -Report -ReportClockHz 0
Results are written to build/perf_results.csv by default. Results append
to the file, so each report run adds a new row per app:
machine,os,utc-timestamp,app,peep_ms,peep_cycles,peep_size,nopeep_ms,nopeep_cycles,nopeep_size,clock_hz
z80-lab,macOS,2026-08-18T12:00:00Z,sieve,0.75,300000,2176,,,,400000000
Columns:
machine— Name of the machine running the benchmarkos— Host operating-system nameutc-timestamp— UTC timestamp (ISO 8601 format, e.g.,2026-06-16T07:18:39Z)app— Application namepeep_ms— Clock-normalized milliseconds for the optimized buildpeep_cycles— Z80 cycles reported for the optimized buildpeep_size— Binary size in bytes (optimized)nopeep_ms— Clock-normalized milliseconds for the build without dccpeepnopeep_cycles— Z80 cycles reported for the build without dccpeepnopeep_size— Binary size in bytes (without dccpeep)clock_hz— NominalReportClockHzused for the millisecond calculation
The -ReportFile parameter controls the output path. The -Mode parameter
controls which CSV columns are populated: -Mode full fills both peep_* and
nopeep_*; single-mode runs fill only the selected mode's columns. In the CSV,
peep_* columns hold optimized-build measurements.
Checked performance baselines¶
Normal stack-checked runs compare each built mode's cycle count and .COM size
with tests/perf_baselines.csv. This check is on by default and uses the same
execution as output verification. -NoPerfCheck, -NoStackCheck, and -Report
skip it. After an intentional, verified change, run with
-UpdatePerfBaseline; only the mode columns built by that invocation are
rewritten.
Stack Size Measurement (stacksize.sh / stacksize.bat)¶
Finds the minimum C stack reserve an app needs under DCC C Compiler's lightweight
stack-overflow guard (-fstack-check). See the
Building and linking
section for full documentation, or run scripts/stacksize.sh --help.