MVE's rows were, in the table's own words, educated estimates pending
LLVM verification: one element size per mnemonic, contiguous loads given
a 12-bit offset, the accumulating and plain reductions sharing a word,
VPT's mask in the wrong bits. On the M-profile target 2114 random words
decoded to nothing, 410 to the wrong instruction, and the Cortex-M55
object failed on 300 words.
tablegen/mve_table.odin replaces them: a flat list, every row of it read
off llvm-mc. Each instruction is assembled, each operand varied over its
range to find the bits it occupies -- which must be the bits of the
encoding the row names -- and every other bit fixed. Where one word has
several spellings (vmladav is vmlav, vldrb.s8 is vldrb.u8, vmla.s16 is
vmla.i16) the one llvm prints decodes and the others are encode-only.
VPT is VCMP opening a block, so its rows are VCMP's with the mask freed.
What MVE needed that nothing had:
* Addresses: [Rn, #imm7] scaled by the element, by the memory element
for the widening loads, a vector of bases [Qm, #imm], a vector of
offsets [Rn, Qm, uxtw #s], and the interleaving loads' [Rn]!.
* VPT and VPST masks, spelled on the mnemonic as IT's are, and the
T or E an instruction inside the block takes, which the decoder now
follows and the printer writes -- `vaddt.i32`. Without it a listing
with a VPT block does not reassemble.
* VCMP's three condition bits, lanes and lane pairs of Q registers,
MVE's shift amounts, which lack Advanced SIMD's L bit, VCMUL's
rotation, and the scalar long shifts' register pair -- which may not
be SP, and where the decoder now falls back to ORRS.
* The mnemonics llvm has and the table did not: VMULH, VADC, VMAXA,
VMOVLB, ASRL, SQRSHRL and the rest of the scalar shifts. VMLALB and
its kin, which do not exist, are gone.
Also on v8.1-M: VLDR and VSTR of the floating-point system registers,
BFCSEL's else target and condition, which it printed as a number,
LDC2L and STC2L on coprocessors 10 and 11 where llvm takes them.
The harness has a VPT-block suite, and the corpus mode takes VPT blocks
as units the way it takes IT blocks. tests/pipeline.odin has an MVE
golden listing, assembled by llvm-mc, that round-trips byte for byte.
Every suite now passes outright -- A32, T16, T32, IT and VPT -- with no
word decoded wrong, extra, missing or unassemblable, and every decoded
word re-encodes; so do the Cortex-M0, M7, M33 and M55 and the A15 and
A53 objects.
The last #partial switch statements in the package are exhaustive now,
and the sweep's fill values are a table.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbUtG4UwNiMydtMW2qGhw4
What was left of 32-bit Thumb outside MVE:
* PAC, PACBTI and AUT printed `ip!`: the decoder took bit 21 as the
writeback of any form with an implicit SP. Only SRS's SP is a base.
* v8.1-M's conditional selects took AL, NV and SP; DLS, WLS and their
tail-predicated forms took SP and PC; LCTP is DLSTP with PC at any
element size, not just one; CLRM -- the LDM whose base would be PC --
was missing, with its APSR in the list.
* A B<c>.W with condition 111x decoded as a branch: those words are the
miscellaneous control space.
* The saturates and bitfield operations left bit 26 and bit 5 free, a
store addressed relative to PC decoded, and a saturate shifted right
by nothing is the SSAT16 space, not an `asr #32`.
* AUTG and BXAUT lost to SMMUL and SMMULR, whose words they are with
Rd 1111; ERET and SUBS PC, LR are not in the M profile; the SYSm forms
of MRS and MSR are not in the A profile; Advanced SIMD is the A
profile's even where its feature is not (VFMA's is VFPv4).
Half precision. v8.2 adds a .f16 form, on coprocessor 9, of most VFP data
processing; the table had a dozen of them. The generator now derives one
from every single-precision form the architecture gives a half-precision
twin, where the table does not list it already.
Printing depends on the profile in one place: v8-M has no load-only
barriers, so `dmb ishld` is `dmb #9` there. Instruction carries whether
it was decoded as M-profile code in its last spare bit, and the printer
follows it.
The harness gave up on any word whose llvm text held a branch target
(`wls lr, sp, 0xcef4`), as llvm had resolved it against its own layout.
It now gives the target back as an offset from the word, the way it does
for rexcode's own text, and asks whether llvm's text reassembles.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbUtG4UwNiMydtMW2qGhw4
The A32 Advanced SIMD rows were written out by hand, one mnemonic at a
time, and it showed: most mnemonics had one element size where the
architecture has three or four, masks left the size and the Q bit free,
so one word decoded as whichever row sorted first, the shift-by-immediate
family read the element size as part of the shift, and the element and
structure loads knew neither alignment nor writeback. Against llvm, 1243
random A32 words decoded where llvm decodes nothing and 139 decoded to
the wrong instruction.
Those rows are gone. tablegen/simd_table.odin describes the space the
way the Architecture Reference Manual lays it out -- three registers of
the same length, of different lengths, by scalar, by shift, two-register
miscellaneous, modified immediate, the loads and stores, the v8.x dot
product, complex, FHM, BFloat16 and matrix additions -- and expands each
class into a row per element size, data type and register shape. The
size and type are fixed bits of every row, so a word decodes to one
`.<dt>` and the encoder picks the row by it. The Thumb forms follow
from these as they did before.
The fields whose meaning depends on the rest of the word -- a structure
load's register count, spacing, lane and alignment; L:imm6, whose
leading one is the element size and the rest the shift -- are read off
the whole word (simd.odin), by the decoder from the word and by the
encoder from the form. That replaces thirty-eight per-size encodings
with twelve. Memory gains an alignment (`[r0:64]`) and a post-increment
mode (`[r0]!`), the Advanced SIMD loads' writeback; disp gives up three
of its nineteen bits for it.
Along the way:
* The decoder reused one Instruction across candidate rows, so a row
turned away after unpacking left its operands for the next one: an
LDC2 picked up a fourth operand from a VCMLA it was not.
* VMRS and VMSR reach different system registers per profile; a
register run that is empty or passes D31 is not an instruction;
VDUP from a core register had one size of three and left its
should-be-zero bits free; FLDMX and FSTMX start in D0-D15; SWP's
address is an address; MSR names its PSR fields instead of a
number; PSB CSYNC is AArch64's, and in AArch32 is HINT #17.
* `vmov.i8` is the byte, not four copies of it.
The harness had one blind spot of its own: a literal load's address in
llvm's trailing comment stopped it from asking whether llvm's text
reassembles. And llvm prints a D list that runs past D31 with the names
of the registers after D31 in its own table -- `{d31, fpinst2}` -- and
takes that back; the harness counts that word as having no text, which
is what it is.
The random A32 suite now passes outright: of 42,408 words none decodes
wrong, extra, missing or unassemblable, and every one re-encodes. The
Cortex-A15 and A53 objects, which had 150 Advanced SIMD words rexcode
could not decode, are exact.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbUtG4UwNiMydtMW2qGhw4
Most of A32's remaining residue came from a handful of systematic gaps:
* A conditional row matched a word whose condition is 1111. That is
the unconditional space -- other instructions -- and the decoder
now refuses it before trying the row.
* Load and store had no register-shifted, pre-indexed or post-indexed
register forms; LDM and STM had no writeback-free or `^` forms; the
16-bit immediates of MOVW, MOVT, BKPT, HVC, UDF and HLT were read
from one field instead of two. A modified immediate whose rotation
is not the one an assembler would pick now decodes to its parts,
`#imm8, #rot`, which is the only text that gives the word back.
* The extends ignored their rotation. CPS, SETPAN, VEXT, the BLX
halfword bit, the exclusive pairs and VMOVL each read a field from
the wrong place.
* Generic coprocessor instructions decoded in coprocessors 10 and 11,
which are the floating-point unit's; on v8.1-M also in 8, 9, 14 and
15 (MVE's). MRC's Rt of 1111 is APSR_nzcv. LDC and STC had no
unindexed form, `[Rn], {option}`.
* A register pair (LDRD, LDREXD, STREXD, the CDE D forms) starts on an
even register; the decoder accepted an odd one and the encoder took
any second register at all, not just the next.
CDE was laid out wrong throughout, and is re-specified from llvm-mc
bit by bit. Whether coprocessors 0 to 7 hold CDE or generic coprocessor
instructions is a property of the core, not of the code, so decode takes
a CDE_Coprocessors set, empty by default.
T32 data processing (shifted register) did not pin its bit 15, and LDM,
STM, PUSH and POP did not keep SP and PC out of their lists.
The encoder had bugs no check reached: SVC emitted a branch relocation
in place of its immediate, MRS could not name SPSR, and VMRS and VMSR
ORed the register number onto a canonical one left in the form's bits.
The llvm harness could not see any of them, since llvm only ever reads
rexcode's text. It now also re-encodes every word rexcode decodes, with
rexcode's own encoder, and requires the bytes back wherever llvm agrees
the word is the one way to write that text. The sweep fills operands
around the bits a form pins, so a form that requires an even register
gets one.
Dead code removed: the printer's unused data-type inference, with the
#partial switch and the temporary allocation it carried, and nineteen
Operand_Encodings no row used.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbUtG4UwNiMydtMW2qGhw4
A Cortex-M7 image decoded to nonsense. Partly the caller asked for A32,
which is the default -- but in Thumb mode it was no better. The first
four words of the image came out as a PUSH whose list was read from the
wrong bits, an LDR whose literal and an LDRB whose base came from fields
that are not theirs, and a CBNZ landing four bytes short. Thumb had
been sketched rather than built: most 16-bit forms left their immediates
and registers unencoded, there were no VFP or Advanced SIMD Thumb forms
at all, IT blocks were not followed, and no branch target in either
instruction set counted the PC bias.
The 16-bit and 32-bit integer Thumb instruction set is re-specified in
tablegen/thumb_table.odin, a flat list in the order the Architecture
Reference Manual's encoding index gives it, merged into each mnemonic's
forms by the generator. The VFP, Advanced SIMD and coprocessor Thumb
forms are not listed: they are their A32 encodings with the condition
fixed, or with the U bit moved from 24 to 28, and the generator derives
them -- so the two instruction sets cannot drift apart.
What Thumb needs that A32 did not have:
* IT blocks. The decoder and encoder both keep ITSTATE. Inside a block
an instruction takes the block's condition, and most 16-bit data
processing stops setting the flags -- `adds` outside, `add` inside --
so forms carry a flags_outside_it flag and the encoder picks by it.
* `.w`, printed exactly when a 16-bit form could also encode the
instruction, which is what an assembler would otherwise choose. Not
more often: llvm rejects `.w` on instructions with no narrow form.
MOV with an immediate always needs it, or `mov r0, #648` is MOVW.
* Operand ranges. Shape alone cannot pick between `add r0, r1, r2` and
`add r8, r9, r10`; operands_fit checks each value against its field,
for every encoding (an exhaustive switch -- a new encoding has to
say whether it narrows anything).
* Profiles. A- and M-profile cores disagree about part of the 32-bit
space: MVE sits in Advanced SIMD's words, v8.1-M's CSEL in ORRS's
should-be-zero bit, BXNS in BX's. decode takes a Profile (A-profile
by default, as before) and filters forms by it.
Decoded branch and literal targets are now absolute offsets, PC bias
and Align(PC, 4) included, as arm64 already does; the A32 ones were
eight bytes short too.
Shared rows that were wrong in both sets came out on the way: VFP
arithmetic masks that let coprocessor 14 and 15 decode as VADD, an
operand list count three bits wide that cut `vpush {d8-d15}` to
nothing, fixed-point VCVT reading a second register out of the fraction
bits, VMOV scalar-to-core with only the .32 lane, VSEL's condition never
encoded, NEON modified immediates that dropped odd cmodes and truncated
.i64, FLDMX/FSTMX missing, and MVE rows on NEON register fields.
None of it was caught before because nothing compared against an
assembler. The table sweep checked only the fixed bits after a
decode-encode round trip, so an operand read from the wrong field passed
as long as the encoder put it back there; it compares the whole word
now. tools/verify_against_llvm.odin replaces a mnemonic-name comparison
that read files from /tmp and passed when they were missing: it decodes
every 16-bit Thumb halfword, every table form with random operand
fields, random A32 and Thumb words and every IT shape, prints them,
assembles the text with llvm-mc, and requires the original bytes back,
under A- and M-profile targets. Object files named on its command line
are checked the same way, instruction by instruction; a Cortex-M0, M7
and M33 build of core:fmt, strconv and math (some 120,000 instructions)
and the reporter's own objects now round-trip byte for byte, as does a
golden Thumb listing that also lives in the pipeline test.
What the verifier still finds is older A32 trouble -- register-offset
shifts and post-indexed register forms of LDR/STR, MOVW/MOVT and BKPT
immediates, non-canonical modified-immediate rotations, NEON structure
loads and the shift-by-immediate family -- and MVE and CDE rows that
need re-deriving. Those come next.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbUtG4UwNiMydtMW2qGhw4
When every argument was constant, the result took the value and type of the
selected argument without checking the arguments against each other, so
mismatched constants like `min(42, "string_literal")`, `min(i32(1), u8(2))`
and `clamp(1.5, 42, "string_test")` were accepted, and `min(1, 2.5)` was an
`int`. Unify the argument types before the constant and non-constant paths, as
the non-constant path already did.
Fixes#7975Fixes#7976
The checker accepts converting a struct to a type reached through a
`using` pointer field, e.g. `Foo_Again :: struct { using foo: ^Foo }` to
`^VTable` where `Foo` has `using vtable: VTable`, but the backend's
subtype selection lookup only descended into struct fields, so the
conversion (and `foo_again->bar()`) panicked in `lb_emit_conv`. Descend
into pointer-to-struct fields too, and when converting from a value
return the pointer into the referenced value rather than loading it.
Fixes#2039
lb_emit_matrix_ep built a constant GEP whenever the matrix pointer was
constant, even when the computed index was a runtime value, producing
invalid IR ("Use of instruction is not an instruction!"). Only use a
constant GEP when the index is also constant.
Fixes#7560
This addresses two issues:
- LLVM errors if the sizes of the two sides don't match, e.g.
`simd_shr(#simd[4]i32, #simd[4]u8)`
- Negative values on the LHS becoming 0 if a shr overflows.
The checker did not carry its operands' viral state flags up to these
expressions, so an `or_break` nested within them was invisible to the
break analysis. An infinite `for` loop whose only exit was such an
`or_break` was treated as never terminating, allowing a missing return
and lowering the break target to `unreachable`.
Fixes#7938