Run SVE/AdvSIMD immediate operations after an operation of another element size, at every SVE vector length, against scalar references.
Catches backends encoding the replicated constant or using a stale element size, as tcg/riscv64 did. AI-used-for: Regression tests Signed-off-by: Philippe Mathieu-Daudé <[email protected]> --- tests/tcg/aarch64/sve-vec-imm.c | 233 ++++++++++++++++++++++++++++++++ tests/tcg/aarch64/meson.build | 1 + 2 files changed, 234 insertions(+) create mode 100644 tests/tcg/aarch64/sve-vec-imm.c diff --git a/tests/tcg/aarch64/sve-vec-imm.c b/tests/tcg/aarch64/sve-vec-imm.c new file mode 100644 index 000000000000..62391824946b --- /dev/null +++ b/tests/tcg/aarch64/sve-vec-imm.c @@ -0,0 +1,233 @@ +/* + * Vector operations with immediates, at every SVE vector length. + * + * Each case first runs an operation with a given element size, then + * an operation with an immediate at another element size, within the + * same TB. This catches TCG backends that encode the immediate from + * the whole replicated constant, or apply it at a stale element size. + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <sys/prctl.h> + +#define MAXVL 256 + +static uint8_t in[MAXVL], out[MAXVL]; + +typedef void test_fn(void); +typedef uint64_t ref_fn(uint64_t x); + +#define TEST(NAME, SETUP, OP) \ +static void NAME(void) \ +{ \ + asm volatile("ldr z0, [%0]\n\t" \ + "ldr z1, [%0]\n\t" \ + "mov z2.d, #0\n\t" \ + SETUP "\n\t" \ + OP "\n\t" \ + "str z0, [%1]" \ + : : "r"(in), "r"(out) : "z0", "z1", "z2", "memory"); \ +} + +#define SET_B "add z1.b, z1.b, z1.b" +#define SET_D "add z1.d, z1.d, z1.d" + +static int64_t sext(uint64_t x, int bits) +{ + return (int64_t)(x << (64 - bits)) >> (64 - bits); +} + +static uint64_t sat_s(int64_t x, int bits) +{ + int64_t max = (1ll << (bits - 1)) - 1; + return x > max ? max : x < -max - 1 ? -max - 1 : x; +} + +static uint64_t sat_u(int64_t x, int bits) +{ + int64_t max = (1ll << bits) - 1; + return x > max ? max : x < 0 ? 0 : x; +} + +static uint64_t ror(uint64_t x, int n, int bits) +{ + uint64_t m = bits == 64 ? -1ull : (1ull << bits) - 1; + return ((x >> n) | (x << (bits - n))) & m; +} + +/* Arithmetic immediates (add_vec, sub_vec, saturating ops). */ +TEST(add_b5, SET_D, "add z0.b, z0.b, #5") +static uint64_t r_add_b5(uint64_t x) { return x + 5; } +TEST(add_h3, SET_B, "add z0.h, z0.h, #3") +static uint64_t r_add_h3(uint64_t x) { return x + 3; } +TEST(sub_s7, SET_B, "sub z0.s, z0.s, #7") +static uint64_t r_sub_s7(uint64_t x) { return x - 7; } +TEST(subr_h3, SET_B, "subr z0.h, z0.h, #3") +static uint64_t r_subr_h3(uint64_t x) { return 3 - x; } +TEST(sqadd_b3, SET_D, "sqadd z0.b, z0.b, #3") +static uint64_t r_sqadd_b3(uint64_t x) { return sat_s(sext(x, 8) + 3, 8); } +TEST(uqadd_h9, SET_B, "uqadd z0.h, z0.h, #9") +static uint64_t r_uqadd_h9(uint64_t x) { return sat_u(x + 9, 16); } +TEST(sqsub_s4, SET_B, "sqsub z0.s, z0.s, #4") +static uint64_t r_sqsub_s4(uint64_t x) { return sat_s(sext(x, 32) - 4, 32); } +TEST(uqsub_s2, SET_B, "uqsub z0.s, z0.s, #2") +static uint64_t r_uqsub_s2(uint64_t x) { return sat_u((int64_t)x - 2, 32); } + +/* Logical immediates (and_vec, or_vec, xor_vec). */ +TEST(and_d_f, SET_B, "and z0.d, z0.d, #0xf") +static uint64_t r_and_f(uint64_t x) { return x & 0xf; } +TEST(and_b_f, SET_D, "and z0.b, z0.b, #0xf") +TEST(orr_d_m16, SET_B, "orr z0.d, z0.d, #0xfffffffffffffff0") +static uint64_t r_orr_m16(uint64_t x) { return x | -16ull; } +TEST(eor_b_f, SET_D, "eor z0.b, z0.b, #0xf") +static uint64_t r_eor_f(uint64_t x) { return x ^ 0xf; } +TEST(orr_h_f, SET_D, "orr z0.h, z0.h, #0xf") +static uint64_t r_orr_f(uint64_t x) { return x | 0xf; } +TEST(eor_s_1, SET_B, "eor z0.s, z0.s, #0x1") +static uint64_t r_eor_1(uint64_t x) { return x ^ 1; } + +/* Shift immediates, including counts above 15 and 31. */ +TEST(lsl_d40, SET_B, "lsl z0.d, z0.d, #40") +static uint64_t r_lsl_d40(uint64_t x) { return x << 40; } +TEST(asr_d63, SET_B, "asr z0.d, z0.d, #63") +static uint64_t r_asr_d63(uint64_t x) { return sext(x, 64) >> 63; } +TEST(lsr_s20, SET_D, "lsr z0.s, z0.s, #20") +static uint64_t r_lsr_s20(uint64_t x) { return x >> 20; } +TEST(lsl_s31, SET_D, "lsl z0.s, z0.s, #31") +static uint64_t r_lsl_s31(uint64_t x) { return x << 31; } +TEST(asr_h15, SET_D, "asr z0.h, z0.h, #15") +static uint64_t r_asr_h15(uint64_t x) { return sext(x, 16) >> 15; } +TEST(xar_d40, SET_B, "xar z0.d, z0.d, z2.d, #40") +static uint64_t r_xar_d40(uint64_t x) { return ror(x, 40, 64); } +TEST(xar_s20, SET_D, "xar z0.s, z0.s, z2.s, #20") +static uint64_t r_xar_s20(uint64_t x) { return ror(x, 20, 32); } + +/* Compare with zero (cmp_vec), including the adjusted LT/GE forms. */ +TEST(cmeq_h0, SET_B, "cmeq v0.8h, v0.8h, #0") +static uint64_t r_cmeq0(uint64_t x) { return x == 0 ? -1ull : 0; } +TEST(cmgt_b0, SET_D, "cmgt v0.16b, v0.16b, #0") +static uint64_t r_cmgt_b0(uint64_t x) { return sext(x, 8) > 0 ? -1ull : 0; } +TEST(cmlt_b0, SET_D, "cmlt v0.16b, v0.16b, #0") +static uint64_t r_cmlt_b0(uint64_t x) { return sext(x, 8) < 0 ? -1ull : 0; } +TEST(cmlt_h0, SET_B, "cmlt v0.8h, v0.8h, #0") +static uint64_t r_cmlt_h0(uint64_t x) { return sext(x, 16) < 0 ? -1ull : 0; } +TEST(cmge_s0, SET_B, "cmge v0.4s, v0.4s, #0") +static uint64_t r_cmge_s0(uint64_t x) { return sext(x, 32) >= 0 ? -1ull : 0; } +TEST(cmle_d0, SET_B, "cmle v0.2d, v0.2d, #0") +static uint64_t r_cmle_d0(uint64_t x) { return sext(x, 64) <= 0 ? -1ull : 0; } + +/* Vector constants (dupi_vec): vmv.v.i, 0/-1, and wide immediates. */ +TEST(movi_s5, SET_B, "movi v0.4s, #5") +static uint64_t r_5(uint64_t x) { return 5; } +TEST(movi_h99, SET_D, "movi v0.8h, #0x99") +static uint64_t r_99(uint64_t x) { return 0x99; } +TEST(movi_d_mask, SET_B, "movi v0.2d, #0xff00ff00ff00ff00") +static uint64_t r_mask(uint64_t x) { return 0xff00ff00ff00ff00ull; } +TEST(mvni_s0, SET_D, "mvni v0.4s, #0") +static uint64_t r_m1(uint64_t x) { return -1ull; } + +static const struct { + const char *name; + test_fn *fn; + ref_fn *ref; + int esz; /* element size in bytes */ + int neon; /* AdvSIMD op: only the low 16 bytes, rest zeroed */ +} tests[] = { + { "add .d; add .b, #5", add_b5, r_add_b5, 1 }, + { "add .b; add .h, #3", add_h3, r_add_h3, 2 }, + { "add .b; sub .s, #7", sub_s7, r_sub_s7, 4 }, + { "add .b; subr .h, #3", subr_h3, r_subr_h3, 2 }, + { "add .d; sqadd .b, #3", sqadd_b3, r_sqadd_b3, 1 }, + { "add .b; uqadd .h, #9", uqadd_h9, r_uqadd_h9, 2 }, + { "add .b; sqsub .s, #4", sqsub_s4, r_sqsub_s4, 4 }, + { "add .b; uqsub .s, #2", uqsub_s2, r_uqsub_s2, 4 }, + + { "add .b; and .d, #0xf", and_d_f, r_and_f, 8 }, + { "add .d; and .b, #0xf", and_b_f, r_and_f, 1 }, + { "add .b; orr .d, #-16", orr_d_m16, r_orr_m16, 8 }, + { "add .d; eor .b, #0xf", eor_b_f, r_eor_f, 1 }, + { "add .d; orr .h, #0xf", orr_h_f, r_orr_f, 2 }, + { "add .b; eor .s, #1", eor_s_1, r_eor_1, 4 }, + + { "add .b; lsl .d, #40", lsl_d40, r_lsl_d40, 8 }, + { "add .b; asr .d, #63", asr_d63, r_asr_d63, 8 }, + { "add .d; lsr .s, #20", lsr_s20, r_lsr_s20, 4 }, + { "add .d; lsl .s, #31", lsl_s31, r_lsl_s31, 4 }, + { "add .d; asr .h, #15", asr_h15, r_asr_h15, 2 }, + { "add .b; xar .d, #40", xar_d40, r_xar_d40, 8 }, + { "add .d; xar .s, #20", xar_s20, r_xar_s20, 4 }, + + { "add .b; cmeq .8h, #0", cmeq_h0, r_cmeq0, 2, 1 }, + { "add .d; cmgt .16b, #0", cmgt_b0, r_cmgt_b0, 1, 1 }, + { "add .d; cmlt .16b, #0", cmlt_b0, r_cmlt_b0, 1, 1 }, + { "add .b; cmlt .8h, #0", cmlt_h0, r_cmlt_h0, 2, 1 }, + { "add .b; cmge .4s, #0", cmge_s0, r_cmge_s0, 4, 1 }, + { "add .b; cmle .2d, #0", cmle_d0, r_cmle_d0, 8, 1 }, + + { "add .b; movi .4s, #5", movi_s5, r_5, 4, 1 }, + { "add .d; movi .8h, #0x99", movi_h99, r_99, 2, 1 }, + { "add .b; movi .2d, #0xff00...", movi_d_mask, r_mask, 8, 1 }, + { "add .d; mvni .4s, #0", mvni_s0, r_m1, 4, 1 }, +}; + +static int check(int vl, int t) +{ + int esz = tests[t].esz; + int n = tests[t].neon ? 16 : vl; + uint64_t mask = esz == 8 ? -1ull : (1ull << (esz * 8)) - 1; + + for (int i = 0; i < vl; i += esz) { + uint64_t x = 0, got = 0, exp = 0; + + memcpy(&x, in + i, esz); + memcpy(&got, out + i, esz); + if (i < n) { + exp = tests[t].ref(x) & mask; + } + if (got != exp) { + fprintf(stderr, "vl %d: %s: byte %d: got 0x%llx, " + "expected 0x%llx\n", vl, tests[t].name, i, + (unsigned long long)got, (unsigned long long)exp); + return 1; + } + } + return 0; +} + +int main(void) +{ + int err = 0, nvl = 0; + + for (int i = 0; i < MAXVL; i++) { + in[i] = 0xa5 ^ (i * 0x37); + } + /* Zero, saturating and sign-boundary elements in the low 16 bytes. */ + in[0] = 0x7e; /* sqadd .b overflow */ + in[2] = in[3] = 0; /* cmeq .h, zero */ + in[8] = 1; /* uqsub .s underflow */ + in[9] = in[10] = in[11] = 0; + in[12] = 0xfa; in[13] = 0xff; /* uqadd .h overflow */ + + for (int vl = 16; vl <= MAXVL; vl *= 2) { + if (prctl(PR_SVE_SET_VL, vl, 0, 0, 0, 0) != vl) { + continue; + } + nvl++; + for (int t = 0; t < sizeof(tests) / sizeof(tests[0]); t++) { + memset(out, 0xee, sizeof(out)); + tests[t].fn(); + err |= check(vl, t); + } + } + if (nvl == 0) { + fprintf(stderr, "no SVE vector length available\n"); + return 1; + } + return err; +} diff --git a/tests/tcg/aarch64/meson.build b/tests/tcg/aarch64/meson.build index 52eb67ae6ace..2033356e3625 100644 --- a/tests/tcg/aarch64/meson.build +++ b/tests/tcg/aarch64/meson.build @@ -145,6 +145,7 @@ tests += { tests += { 'test-826.c': {'cc_feat': 'sve2'}, 'sve-while-ptr.c': {'cc_feat': 'sve2'}, + 'sve-vec-imm.c': {'cc_feat': 'sve2'}, } # FPRCVT test -- 2.55.0
