Files
velzie 611f57a97b Fixed-point conversions, the scalar shift group, and a cache key that
notices when gex changes

Lua found two decoder gaps that SQLite's corpus had flagged but never
executed.

The first was mine. The scalar Advanced SIMD guard tested bits 28:24
against 11110, but the shift-by-immediate forms sit at 11111 -- bit 24 is
the only thing that distinguishes them -- so the whole family was
unreachable and `shl d1, d1, #52` did not decode. The inner dispatch had
been written correctly; the outer guard hid it.

The second was a documented gap: conversions with a fixed-point
immediate. `scvtf d0, w0, #2` reads its integer as a value with two
fractional bits. Both directions are the ordinary conversion plus a scale
by a power of two, which is exact and commutes with the rounding the
conversion already did, so the result is the single correctly-rounded one
the architecture specifies. Verified against QEMU over both signednesses,
both widths, fbits from 1 to 64, and the saturating and NaN cases --
none of which C can spell, so the test is inline assembly.

And a real design fault in the cache. Its key covered the guest image and
the translation options but not gex itself, so after fixing the decoder
the rebuilt binary cheerfully reused the module the broken one had
compiled. The failure looks exactly like the fix not working, and I spent
a while proving the fix was right before suspecting the cache. The key
now includes the running executable's size and mtime, and a build that
cannot identify itself refuses to use the cache rather than risking
another build's code.

Corpus: across libstdc++, libgfortran, SQLite and Lua -- 853,336
instructions -- every one now disassembles exactly, no undecoded, no
operand-text differences. What is left undecoded anywhere is half
precision and SVE, neither advertised in AT_HWCAP, plus ld3/ld4, addhn,
sqdmulh and the register-shift forms.

Suite is 39 comparisons.
2026-08-05 07:44:49 -04:00

57 lines
2.8 KiB
C

/* The fixed-point conversions, which no compiler emits from C.
*
* `scvtf d0, w0, #n` reads its integer as a value with n fractional bits, so
* the result is w0 / 2^n; `fcvtzs w0, d0, #n` is the inverse, truncating
* toward zero. Both are single correctly-rounded operations on hardware, and
* gex implements them as a conversion plus an exact power-of-two scale, which
* is the same answer everywhere except an underflow into subnormals.
*/
#include <stdio.h>
#include <stdint.h>
#define SCVTF_D(v, n) ({ double _r; int32_t _v = (v); \
__asm__("scvtf %d0, %w1, #" #n : "=w"(_r) : "r"(_v)); _r; })
#define UCVTF_S(v, n) ({ float _r; uint32_t _v = (v); \
__asm__("ucvtf %s0, %w1, #" #n : "=w"(_r) : "r"(_v)); _r; })
#define SCVTF_DX(v, n) ({ double _r; int64_t _v = (v); \
__asm__("scvtf %d0, %x1, #" #n : "=w"(_r) : "r"(_v)); _r; })
#define FCVTZS_W(v, n) ({ int32_t _r; double _v = (v); \
__asm__("fcvtzs %w0, %d1, #" #n : "=r"(_r) : "w"(_v)); _r; })
#define FCVTZU_X(v, n) ({ uint64_t _r; double _v = (v); \
__asm__("fcvtzu %x0, %d1, #" #n : "=r"(_r) : "w"(_v)); _r; })
#define FCVTZS_W_S(v, n) ({ int32_t _r; float _v = (v); \
__asm__("fcvtzs %w0, %s1, #" #n : "=r"(_r) : "w"(_v)); _r; })
int main(void) {
static const int32_t xs[] = {0, 1, -1, 7, -7, 1000, -1000, 65536,
2147483647, -2147483647 - 1};
for (unsigned i = 0; i < sizeof xs / sizeof *xs; i++) {
printf("scvtf.1 %.9f scvtf.15 %.9f scvtf.32 %.12f\n",
SCVTF_D(xs[i], 1), SCVTF_D(xs[i], 15), SCVTF_D(xs[i], 32));
}
static const uint32_t us[] = {0, 1, 3, 4294967295u, 2147483648u, 12345};
for (unsigned i = 0; i < sizeof us / sizeof *us; i++)
printf("ucvtf.4 %.6f ucvtf.31 %.9f\n", (double)UCVTF_S(us[i], 4),
(double)UCVTF_S(us[i], 31));
static const int64_t ls[] = {0, 1, -1, 1LL << 40, -(1LL << 40),
9223372036854775807LL};
for (unsigned i = 0; i < sizeof ls / sizeof *ls; i++)
printf("scvtf64.8 %.9f scvtf64.40 %.12f scvtf64.64 %.17f\n",
SCVTF_DX(ls[i], 8), SCVTF_DX(ls[i], 40), SCVTF_DX(ls[i], 64));
static const double ds[] = {0.0, 1.0, -1.0, 0.5, -0.5, 3.75, -3.75,
1e9, -1e9, 1e-9, 0.9999999};
for (unsigned i = 0; i < sizeof ds / sizeof *ds; i++)
printf("fcvtzs.2 %d fcvtzs.16 %d fcvtzu.8 %llu\n",
FCVTZS_W(ds[i], 2), FCVTZS_W(ds[i], 16),
(unsigned long long)FCVTZU_X(ds[i] < 0 ? -ds[i] : ds[i], 8));
/* Saturation: the result cannot be represented in the target width. */
printf("sat %d %d\n", FCVTZS_W(1e18, 4), FCVTZS_W(-1e18, 4));
printf("satf %d %d\n", FCVTZS_W_S(1e18f, 4), FCVTZS_W_S(-1e18f, 4));
/* NaN converts to zero on AArch64. */
printf("nan %d\n", FCVTZS_W(__builtin_nan(""), 3));
return 0;
}