Extract the condition-code suffix table (%conds, @conds, and the c_ccmask/c_nd/c_cc/c_scc bitmasks) out of x86/insns.pl's conditional_forms() into a new shared x86/insns-cc.ph module, adding cc_suffix_list($is_scc) to return the suffix list applicable to a '...cc'-family (Jcc, SETcc, CMOVcc, CFCMOVcc) or '...scc'-family (CCMPscc, CTESTscc, CMPccXADD, SETccZU) placeholder mnemonic. insns.pl is refactored to use it (behavior-preserving: verified byte-identical x86/insnsa.c, insnsb.c, insnsd.c, insnsi.h, insnsn.c, iflag.c, iflaggen.h, asm/tokhash.c, asm/tokens.h before/after). tools/testgen/gen-insn-tests.pl now requires the same module instead of hand-rolling a 16-suffix 'cc'-only expansion table, closing the gap where the APX 'scc'-suffix families (CCMPscc/CTESTscc/CMPccXADD/ SETccZU) were dropped as unsupported. The generator now detects and expands any '...cc'/'...scc' placeholder mnemonic the same way insns.pl's conditional_forms() does (case-sensitive /s?cc/ match and substitution), so future new cc/scc families need no generator changes. Mnemonics with generated tests: 2432 -> 2606 (+174, one per newly-expanded scc-family condition); dropped: 20 -> 16. Regenerated travis/insns/ (174 new mnemonic dirs for the APX scc families; existing 2432 regenerated byte-identically, confirming determinism). Full suite validated both via tools/travis/nasm-t.py --directory=./travis run (4427 total, 4426 PASS, 1 pre-existing SKIP, 0 FAIL) and via make -j32 travis (all tests PASS, ~28s). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|---|---|---|
| .. | ||
| gen-insn-tests.pl | ||
| README.md | ||
gen-insn-tests.pl
Pseudorandom test-case generator for NASM instruction patterns. For every
non-PSEUDO mnemonic in x86/insns.xda, it generates a
travis/<mnemonic>/ test directory (.asm source, .json descriptor,
golden .bin16/32/64[.t]/.stderr16/32/64[.t] files) that plugs directly
into the existing tools/travis/nasm-t.py harness. This gives every
instruction-set pattern regression coverage without hand-writing
thousands of test cases.
The generated tree currently checked in lives at travis/insns/ (2606
mnemonic subdirectories as of the last full run).
Usage
# Regenerate everything into travis/insns (run from repo root, after
# ./nasm has been built and x86/insns.xda is up to date):
perl tools/testgen/gen-insn-tests.pl --outdir travis/insns
# Regenerate just one mnemonic, useful while iterating on the generator:
perl tools/testgen/gen-insn-tests.pl --outdir /tmp/tg-scratch --only vaddps --verbose
# Validate the full existing+generated suite (there's no generated
# Makefile in a fresh checkout without running configure, so invoke the
# harness directly rather than via `make travis`):
python3 tools/travis/nasm-t.py --nasm=./nasm --directory=./travis run
Options (perl tools/testgen/gen-insn-tests.pl --help equivalent, see
the GetOptions block at the top of the script for the authoritative
list):
| Option | Default | Meaning |
|---|---|---|
--xda FILE |
x86/insns.xda |
source instruction-template file |
--nasm PATH |
./nasm |
nasm binary used to probe/assemble and to run nasm-t.py update |
--outdir DIR |
travis |
where to write <mnemonic>/ subdirectories |
--seed N |
1 |
base seed for the deterministic per-mnemonic PRNG |
--per-mnemonic N |
4 |
max distinct operand-arity templates sampled per mnemonic |
--variants N |
2 |
concrete random instruction instances generated per sampled template |
--only MNEMONIC |
(all) | restrict generation to a single mnemonic, for iterating on the generator |
--no-undoc |
off | exclude UNDOC-flagged templates (not recommended — see below) |
--verbose |
off | print per-mnemonic progress as it generates |
Generation is deterministic (fixed default seed), so re-running with no
source changes reproduces byte-identical .asm/.json files. It should
only need to be re-run when x86/insns.dat (and hence the regenerated
x86/insns.xda) changes — day-to-day travis test runs use the
existing harness and scale via make -jN travis as normal; generation is
a separate, infrequent step.
Design: why x86/insns.xda and not insnsa.c/insns.pl hooks
Three data sources were considered:
-
Parse the generated
insnsa.c/insnsb.cdirectly. Rejected: these encode operands as opaque bitmask constants and index into the sharednasm_bytecodes[]array, whichx86/bytecode.txtexplicitly documents as unstable ("byte codes can be moved around and recycled at any time"). Parsing this from outsideinsns.plwould be fragile across NASM releases and high-effort for no real benefit. -
Add new hooks to
x86/insns.plto emit a purpose-built intermediate format. Rejected: it would duplicate informationinsns.xdaalready provides, adding maintenance risk to a build-critical code generator for no real gain. -
Parse
x86/insns.xda(chosen). This file is already generated byinsns.plfrominsns.datas part of a normal build, with macros fully expanded, one instruction template per line, in a small stable text grammar:MNEMONIC optype1,optype2,... [enc: bytecode...] FLAGSCrucially, we never need the bytecode/encoding field — NASM's own assembler selects the encoding from mnemonic + operand syntax at assemble time. So the generator only needs to produce syntactically valid operand text per operand-type token; it doesn't need to understand or replicate NASM's instruction encoding at all. This means the generated tests are regression tests — validated against a golden captured from a known-good
nasmbuild — not independent correctness oracles. That's the same philosophy already used by the rest oftravis/.
Handling of UNDOC / OBSOLETE / NEVER instructions
These are included, not excluded (only PSEUDO templates — which
aren't real ISA instructions, e.g. internal directives — are skipped).
Verifying that OBSOLETE/NEVER-flagged instructions produce the
expected warning (typically -w+obsolete-removed at the assembler's
default/highest CPU level) is itself part of the intended test coverage,
not something to suppress. When the generator's probe step detects
nonempty stderr for a template, it adds a "stderr" target to the
.json descriptor so the warning text becomes part of the golden.
UNDOC alone does not currently trigger a warning by default (verified
against SALC), so most UNDOC mnemonics just get ordinary output-only
targets.
Condition-code mnemonic families
insns.xda leaves condition-code instruction families as literal,
unexpanded placeholders: Jcc, SETcc, CMOVcc, CFCMOVcc (the "cc"
family) and the newer APX CCMPscc, CTESTscc, CMPccXADD, SETccZU
(the "scc" family). Unlike $bwdq-style width macros (which insns.pl
does pre-expand into concrete lines in .xda), NASM's parser expands
condition codes against its own cc-table at parse time — and
x86/insns.pl's own conditional_forms() performs the equivalent
expansion for the C-code generators, but only after insns.xda has
already been written by preinsns.pl, so the placeholders reach this
tool unexpanded either way.
The condition-code table (%conds, plus the $c_cc/$c_scc masks
distinguishing which suffixes are valid for which family) is shared
between insns.pl and this generator via x86/insns-cc.ph, a small
Perl module required by both — this guarantees the two expansions
never drift out of sync, since there's only one copy of the
suffix/mask data. cc_suffix_list($is_scc) (defined in that module)
returns the applicable suffix list for either family, and the generator
mirrors insns.pl's own detection ($mnem =~ /s?cc/, case-sensitive)
and substitution ($mnem =~ s/s?cc/\U$suffix/) exactly, so e.g. Jcc
expands to JE/JNE/JGE/... , CCMPscc expands to CCMPE/
CCMPNE/... (excluding the "cc"-only suffixes pe/po/p/np, which
aren't valid for scc-family instructions), and SETccZU expands to
SETNEZU/... . Any future new "cc"/"scc" family added to insns.dat
is picked up automatically without changes to this script.
Bit-width (16/32/64) handling
Bit-mode support isn't derived from CPU/mode flags in insns.xda — the
same generated .asm text is simply probed under --bits 16/32/64, and
only the modes that actually assemble successfully become targets in the
final .json (mirroring the pre-existing travis/jmpxx/-style pattern).
Two wrinkles this created and how they're handled:
- 64-bit-only tokens poisoning 16/32 probes. A single sampled
template using a 64-bit-only operand (e.g.
reg64,imm64) would otherwise make the whole file fail to assemble at 16/32-bit, even though other templates in the same mnemonic don't need 64-bit. The generator classifies operand tokens via a%needs64_tokentable and renders two asm bodies per mnemonic — a "narrow" one (16/32-safe) and a "full" one (includes 64-bit-only forms) — reusing one file when they'd be identical. - Memory/vsib addressing across bit widths. A fixed base-register
name (
raxvseaxvsax) isn't valid syntax across all three bit widths. The generator sidesteps this by using base-register-free addressing: bare-displacement memory operands ([0x1234]) and base-free scaled vsib index forms ([xmm0*1]). This sacrifices coverage of true[base+index*scale+disp]forms — a known, documented limitation, not a bug.
Integration with nasm-t.py
Golden capture is delegated entirely to the existing harness:
python3 tools/travis/nasm-t.py --nasm=./nasm update -t <outdir>/<mnemonic>
rather than hand-rolling nasm invocation, renaming output binaries, or
hand-writing stderr golden files. This matters because
nasm-t.py's stderr/stdout comparison is an exact byte-for-byte
string match with no path normalization — NASM embeds the literal
source-path argument it was invoked with into warning/error text, so
hand-rolled golden capture is easy to get subtly wrong. update uses
its own internal, consistent path construction, so the generator's own
cheap throwaway pre-probe (used only to decide per-bit-width viability
and whether a "stderr" target is needed) doesn't need to match those
paths exactly. If nasm-t.py update fails for a mnemonic (should only
happen if the pre-probe's viability check was wrong), the generator
drops that mnemonic's directory entirely rather than committing a
broken test.
Output naming convention
Multiple bit-width variants generated from one source share the
established repo convention: <mnemonic>.bin16 / .bin32 / .bin64
(binary; see e.g. travis/jmpxx/jmpxx-ox.bin16), with .stderr16 /
.stderr32 / .stderr64 analogously for expected-warning goldens — not
<mnemonic>16.bin.
Known limitations / coverage gaps
The generator prints a coverage summary at the end of each run (mnemonics generated, mnemonics dropped, and a frequency table of unsupported operand-type tokens), so gaps are self-documenting. As of the last full run:
- 2606 / 2622 mnemonics generate at least one assemblable template
(includes all concrete expansions of the
cc/scccondition-code families — see above). - 16 mnemonics produce zero output and are dropped, mostly:
HINT_NOP0..HINT_NOP63placeholder mnemonics,LOADALL/LOADALL286,- a handful of exotic AMX-transpose / APX instructions whose full
operand grammar wasn't targeted (e.g.
T2RPNTLVWZ0*,TCONJT*,TTMMULTF32PS).
- Memory/vsib operands only exercise base-register-free addressing forms (see above) — true base+index+scale+disp combinations aren't covered by generated tests.
- ~70+ distinct operand-type tokens have generator support (see the
%fixed/%gentables at the top of the script); any new/renamed token introduced by a futureinsns.datchange that isn't in those tables will show up in the "unsupported operand-type tokens" summary and cause the affected templates (not necessarily the whole mnemonic) to be skipped.
Extending coverage for any of the above is a matter of adding entries to
the %fixed/%gen operand-generator tables or the @cc_suffixes
expansion loop — no changes to the parser or driver loop are needed.
Validation
Full-suite validation after the last generation run (existing travis/
tests + travis/insns/):
python3 tools/travis/nasm-t.py --nasm=./nasm --directory=./travis run
4427 tests total, 4426 PASS, 1 pre-existing SKIP (time, an
intentional/known skip unrelated to this tool), 0 FAIL, 0 ABORT.