Tool — copt (z88dk-copt)

SkillFiles & storage

z88dk-copt text peephole optimiser: rule files lib/z80rules.*, what zcc applies by -O, and required hand-asm finalisation (library never runs copt). Use when writing sccz80 rules, reviewing compiler dumps, or cleaning library asm.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Tool — copt (z88dk-copt) skill

What this skill tells your AI

The instructions your AI receives, as published by z88dk/z88dk in .agents/skills/tool-copt/SKILL.md and read by ahel’s review.

Source: src/copt/. Man: src/copt/copt.1, doc/copt.man. Wiki: wiki/tools/Tool---copt.md.

Peephole rules and codegen hygiene

z88dk-copt is z88dk’s text peephole optimiser: it reads assembly on stdin, applies pattern → replacement rules from named files, and writes improved assembly on stdout. zcc runs it on compiler-generated output (sccz80 .opt / similar), not on hand-written library sources.

Source and man page: src/copt/copt.c, src/copt/copt.1. Rule files live under lib/z80rules.* (plus target/CPU/user extras).

What agents must remember when writing library asm

FactConsequence
Library .asm under libsrc/ is assembled directlycopt never runs on it in the normal build
Dead moves stay deadWrite clean sequences yourself (ld b,a then ld a,b is never cleaned up later)
Whitespace is not a style warMatch the target file: tabs vs spaces, column layout, comment style. Most classic libsrc/l/sccz80/** and math32 cores use spaces (often four-space indent). copt rule files and sccz80 dumps use tabs — that is only for matching those pipelines
copt matches whole lines (mostly literal)Trailing comments, different spacing, or a space-indented library line will not match a tab rule. That is irrelevant when editing library sources in their native style

Do not reformat a library file to “look like copt input”. Follow neighbours in the same file. Do do apply the semantic lessons of the rules (remove redundancies copt would drop on compiler output).

How a rule is written

	<pattern line 1>
	<pattern line 2>
	...
=
	<replacement line 1>
	...

  • Blank line ends the rule. Lines starting with ;; (column 0) are comments in the rule file.
  • %1%9 — wildcards; same index must bind the same text within one fire.
  • %% — literal %.
  • %eval(...), %check min <= %n <= max, %is / %not, %notSame, %cpu / %notcpu — preconditions (evaluated after binds).
  • %title ... — label for debug; does not match source.
  • %L / %M / %N in replacements — unique labels.
  • %activate / %once — dynamic rule activation (advanced; see man page).

Matching walks the input in reverse along each candidate window so shorter cascades can fire after a replacement without backing up far. Multiple passes run until quiet (capped).

CLI (manual experiments)

# Rules as argv; source on stdin. -m sets %cpu checks; -D prints firings.
z88dk-copt -m8085 [-D] lib/z80rules.9 lib/z80rules.2 lib/z80rules.1 \
  lib/z80rules.0 [lib/z80rules.8] < input.asm > output.asm

Expect no change if you feed space-indented, commented library asm into rules written for sccz80 tab style — that is expected, not a broken tool.

Which rule files zcc applies (classic sccz80 path)

Configured via COPTEXE / COPTRULES* (defaults in src/zcc/zcc.c):

Peephole -ORule set (order matters)
0z80rules.9
1.9 + .1
2 (common default in target OPTIONS).9 + .2 + .1
3+.9 + .2 + .1 + .0

Also, when present:

ConfigRole
COPTRULESINLINEz80rules.8sccz80 inline ints path only (c_sccz80_inline_ints); otherwise not loaded
COPTRULESTARGETTarget-specific (e.g. z88)
CPU map rulesCPU-specific if the file exists
-custom-copt-rules=User file

Other compilers have their own sets (lib/sdcc/…, 80cc_rules.1, clang_rules.1, …). This section is about the classic z80rules.* family.

Rough file roles:

FileRole
z80rules.9Intrinsics / RST-style substitutions (always first among the numbered set)
z80rules.2Aggressive / higher-O peepholes (includes dead re-load into same dest)
z80rules.1Large main sccz80 peephole set
z80rules.0Extra / lower-priority patterns (linked as COPTRULES3)
z80rules.8Inline common sccz80 helpers and a few general cleanups
z80rules.frameTiny frame-related snippet (not the main pipeline by itself)

Rules that matter for register hygiene (codegen lessons)

These teach what not to emit in hand-written cores either.

Copy-back is a no-op (z80rules.8 — only if that file is loaded):

	ld	%1,%2
	ld	%2,%1
=
	ld	%1,%2

Example: ld b,a / ld a,b → keep only ld b,a. On 8085/8080/gbz80 long-div prologue this is exactly the redundant high-byte re-load after building BC.

Register-specific variant already in z80rules.0 (always available at -O3+):

	ld	l,a
	ld	a,l
=
	ld	l,a

Only L↔A, not a general B/C/D/E/H rule — so do not assume every copy-back is stripped unless .8 is active or you remove it by hand.

Dead second load into the same destination (z80rules.2):

	ld	%1,%2
	ld	%1,%3
=
	ld	%1,%3

Implication for new code: after ld r,a (or any ld dst,src), if the next instruction only reloads src from dst so you can or / test the original, drop the reloadA (or the source) still holds the value. Same for any symmetric copy-back. copt may clean that on sccz80 output; library authors must.

Using copt knowledge while generating or reviewing code

  1. Hand-written libsrc/**: write the optimised form yourself; match file whitespace/comments; never rely on a later copt pass.
  2. Compiler dumps / .opt / .asm from zcc: expect copt to have already run; remaining slop is either not covered by rules, blocked by comments/labels between instructions, or needs a new rule / better frontend codegen.
  3. Proposing new copt rules: put them in the right file for the intended -O level; use tab + sccz80 operand style so they match compiler output; add %cpu / %notcpu when a pattern is unsafe on 8080/8085/gbz80.
  4. Experimentation: normalise a snippet to tab style only in a temp file if you want to see whether existing rules fire; do not commit that reformat into library sources that use spaces.
  5. A/B of “copt would fix this”: if a library bug is a pure redundancy the rules already express, the fix is a one-line delete in the library — same binary effect as copt on compiler text, without depending on rule load order.

Before finalising hand-coded library work (required)

Hand-written math16 / math32 / sccz80 runtime asm is not passed through z88dk-copt. Before calling an edit done (and before staging / TIMER publish):

  1. Scan the touched hand sources for copt-equivalent idioms (semantic matches to lib/z80rules.{0,1,2,8}, not mechanical tab reformatting).
  2. Apply safe cleanups in the library file itself. Typical wins:
SmellPrefer
ld r,a then ld a,r (copy-back)Drop second insn (z80rules.8 / .0)
add a,a / rla then ld e,a then ld a,e before next shift/testDrop ld a,eA still holds the value
ld b,a then ld a,b when A unchangedDrop ld a,b
push de / pop hl (DE→HL, DE dead)ex de,hl (or ld h,d / ld l,e if DE must live)
push hl / pop bc (HL→BC)synthetic ld bc,hl
Triple unstack ending push hl / pop bc / pop de / pop hlld bc,hl then two pops
ld h,0 / ld l,0 (and DE/BC pairs)ld hl,0 / ld de,0 / ld bc,0
Dead second ld into same destKeep only the last load (z80rules.2)
  1. Do not blindly apply ld a,0xor a: ld preserves flags; xor a clears C. Unsafe before sbc / rla / rra that need the prior carry (common in float sign / multiprecision negate paths).
  2. Skip intentional stack peeks (pop de / push de) and real callee argument pushes — those are not dead transfers.
  3. Gate: suite / probe on the CPUs that link the objects (e.g. test_math32_8085.bin after 8085 math32 edits).

Ignore sccz80/sdcc generated dumps under c/asm/ for this scan; fix or regenerate those only via the C sources / rebuild pipeline.

Related

  • compiler-sccz80, methodology-measure, style-libsrc-layout

Signals

GitHub stars
1k
Forks
206
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
tool-copt
Source
github.com/z88dk/z88dk