chiark / gitweb /
crypto-test: Introduce first version of new .c file.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 25 Sep 2019 19:38:06 +0000 (20:38 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 2 Jan 2020 00:42:03 +0000 (00:42 +0000)
This is not complete and does not compile yet.  It is not wired into
the build system.

That will come right at the end, because I have chosen not to generate
a fake history for the build machinery, which in the original
work-in-progress series underwent some churn and was then refactored
at the end.

(This commit was originally an `@@@' commit from Mark Wooding,
"@@@ crypto-test".  I have added this commit message and folded
in a couple of subsequent commits which I felt should be included.)

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
crypto-test.c [new file with mode: 0644]
crypto-test.h [new file with mode: 0644]
ec-field-test.c [new file with mode: 0644]

diff --git a/crypto-test.c b/crypto-test.c
new file mode 100644 (file)
index 0000000..4a53a07
--- /dev/null
@@ -0,0 +1,420 @@
+/*
+ * crypto-test.c: common test vector processing
+ */
+/*
+ * This file is Free Software.  It was originally written for secnet.
+ *
+ * Copyright 2017, 2019 Mark Wooding
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later
+ * version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "secnet.h"
+#include "util.h"
+
+#include "crypto-test.h"
+
+/*----- Utilities ---------------------------------------------------------*/
+
+static void *xmalloc(size_t sz)
+{
+    void *p;
+
+    if (!sz) return 0;
+    p = malloc(sz);
+    if (!p) {
+       fprintf(stderr, "out of memory!\n");
+       exit(2);
+    }
+    return p;
+}
+
+static void *xrealloc(void *p, size_t sz)
+{
+    void *q;
+
+    if (!sz) { free(p); return 0; }
+    else if (!p) return xmalloc(sz);
+    q = realloc(p, sz);
+    if (!q) {
+       fprintf(stderr, "out of memory!\n");
+       exit(2);
+    }
+    return q;
+}
+
+static int lno;
+
+void bail(const char *msg, ...)
+{
+    va_list ap;
+    va_start(ap, msg);
+    fprintf(stderr, "unexpected error (line %d): ", lno);
+    vfprintf(stderr, msg, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(2);
+}
+
+struct linebuf {
+    char *p;
+    size_t sz;
+};
+#define LINEBUF_INIT { 0, 0 };
+
+static int read_line(struct linebuf *b, FILE *fp)
+{
+    size_t n = 0;
+    int ch;
+
+    ch = getc(fp); if (ch == EOF) return EOF;
+    for (;;) {
+       if (n >= b->sz) {
+           b->sz = b->sz ? 2*b->sz : 64;
+           b->p = xrealloc(b->p, b->sz);
+       }
+       if (ch == EOF || ch == '\n') { b->p[n++] = 0; return 0; }
+       b->p[n++] = ch;
+       ch = getc(fp);
+    }
+}
+
+void parse_hex(uint8_t *b, size_t sz, char *p)
+{
+    size_t n = strlen(p);
+    unsigned i;
+    char bb[3];
+
+    if (n%2) bail("bad hex (odd number of nibbles)");
+    else if (n/2 != sz) bail("bad hex (want %zu bytes, found %zu)", sz, n/2);
+    while (sz) {
+       for (i = 0; i < 2; i++) {
+           if (!isxdigit((unsigned char)p[i]))
+               bail("bad hex digit `%c'", p[i]);
+           bb[i] = p[i];
+       }
+       bb[2] = 0;
+       p += 2;
+       *b++ = strtoul(bb, 0, 16); sz--;
+    }
+}
+
+void dump_hex(FILE *fp, const uint8_t *b, size_t sz)
+    { while (sz--) fprintf(fp, "%02x", *b++); fputc('\n', fp); }
+
+void trivial_regty_init(union regval *v) { ; }
+void trivial_regty_release(union regval *v) { ; }
+
+/* Define some global variables we shouldn't need.
+ *
+ * Annoyingly, `secnet.h' declares static pointers and initializes them to
+ * point to some external variables.  At `-O0', GCC doesn't optimize these
+ * away, so there's a link-time dependency on these variables.  Define them
+ * here, so that `f25519.c' and `f448.c' can find them.
+ *
+ * (Later GCC has `-Og', which optimizes without making debugging a
+ * nightmare, but I'm not running that version here.  Note that `serpent.c'
+ * doesn't have this problem because it defines its own word load and store
+ * operations to cope with its endian weirdness, whereas the field arithmetic
+ * uses `unaligned.h' which manages to include `secnet.h'.)
+ */
+uint64_t now_global;
+struct timeval tv_now_global;
+
+/* Bletch.  util.c is a mess of layers. */
+int consttime_memeq(const void *s1in, const void *s2in, size_t n)
+{
+    const uint8_t *s1=s1in, *s2=s2in;
+    register volatile uint8_t accumulator=0;
+
+    while (n-- > 0) {
+       accumulator |= (*s1++ ^ *s2++);
+    }
+    accumulator |= accumulator >> 4; /* constant-time             */
+    accumulator |= accumulator >> 2; /*  boolean canonicalisation */
+    accumulator |= accumulator >> 1;
+    accumulator &= 1;
+    accumulator ^= 1;
+    return accumulator;
+}
+
+/*----- Built-in types ----------------------------------------------------*/
+
+/* Signed integer. */
+
+static void parse_int(union regval *v, char *p)
+{
+    char *q;
+
+    errno = 0;
+    v->i = strtol(p, &q, 0);
+    if (*q || errno) bail("bad integer `%s'", p);
+}
+
+static void dump_int(FILE *fp, const union regval *v)
+    { fprintf(fp, "%ld\n", v->i); }
+
+static int eq_int(const union regval *v0, const union regval *v1)
+    { return (v0->i == v1->i); }
+
+const struct regty regty_int = {
+    trivial_regty_init,
+    parse_int,
+    dump_int,
+    eq_int,
+    trivial_regty_release
+};
+
+/* Unsigned integer. */
+
+static void parse_uint(union regval *v, char *p)
+{
+    char *q;
+
+    errno = 0;
+    v->u = strtoul(p, &q, 0);
+    if (*q || errno) bail("bad integer `%s'", p);
+}
+
+static void dump_uint(FILE *fp, const union regval *v)
+    { fprintf(fp, "%lu\n", v->u); }
+
+static int eq_uint(const union regval *v0, const union regval *v1)
+    { return (v0->u == v1->u); }
+
+const struct regty regty_uint = {
+    trivial_regty_init,
+    parse_uint,
+    dump_uint,
+    eq_uint,
+    trivial_regty_release
+};
+
+/* Byte string, as hex. */
+
+void allocate_bytes(union regval *v, size_t sz)
+    { v->bytes.p = xmalloc(sz); v->bytes.sz = sz; }
+
+static void init_bytes(union regval *v) { v->bytes.p = 0; v->bytes.sz = 0; }
+
+static void parse_bytes(union regval *v, char *p)
+{
+    size_t n = strlen(p);
+
+    allocate_bytes(v, n/2);
+    parse_hex(v->bytes.p, v->bytes.sz, p);
+}
+
+static void dump_bytes(FILE *fp, const union regval *v)
+    { dump_hex(fp, v->bytes.p, v->bytes.sz); }
+
+static int eq_bytes(const union regval *v0, const union regval *v1)
+{
+    return v0->bytes.sz == v1->bytes.sz &&
+       !memcmp(v0->bytes.p, v1->bytes.p, v0->bytes.sz);
+}
+
+static void release_bytes(union regval *v) { free(v->bytes.p); }
+
+const struct regty regty_bytes = {
+    init_bytes,
+    parse_bytes,
+    dump_bytes,
+    eq_bytes,
+    release_bytes
+};
+
+/*----- Core test machinery -----------------------------------------------*/
+
+/* Say that a register is `reset' by releasing and then re-initializing it.
+ * While there is a current test, all of that test's registers are
+ * initialized.  The input registers are reset at the end of `check', ready
+ * for the next test to load new values.  The output registers are reset at
+ * the end of `check_test_output', so that a test runner can run a test
+ * multiple times against the same test input, but with different context
+ * data.
+ */
+
+#define REG(rvec, i)                                                   \
+    ((struct reg *)((unsigned char *)state->rvec + (i)*state->regsz))
+
+void check_test_output(struct test_state *state, const struct test *test)
+{
+    const struct regdef *def;
+    struct reg *reg, *in, *out;
+    int ok = 1;
+    int match;
+
+    for (def = test->regs; def->name; def++) {
+       if (def->i >= state->nrout) continue;
+       in = REG(in, def->i); out = REG(out, def->i);
+       if (!def->ty->eq(&in->v, &out->v)) ok = 0;
+    }
+    if (ok)
+       state->win++;
+    else {
+       printf("failed test `%s'\n", test->name);
+       for (def = test->regs; def->name; def++) {
+           in = REG(in, def->i);
+           if (def->i >= state->nrout) {
+               printf("\t   input `%s' = ", def->name);
+               def->ty->dump(stdout, &in->v);
+           } else {
+               out = REG(out, def->i);
+               match = def->ty->eq(&in->v, &out->v);
+               printf("\t%s `%s' = ",
+                      match ? "  output" : "expected", def->name);
+               def->ty->dump(stdout, &in->v);
+               if (!match) {
+                   printf("\tcomputed `%s' = ", def->name);
+                   def->ty->dump(stdout, &out->v);
+               }
+           }
+       }
+       state->lose++;
+    }
+
+    for (def = test->regs; def->name; def++) {
+       if (def->i >= state->nrout) continue;
+       reg = REG(out, def->i);
+       def->ty->release(&reg->v); def->ty->init(&reg->v);
+    }
+}
+
+void run_test(struct test_state *state, const struct test *test)
+{
+    test->fn(state->out, state->in, 0);
+    check_test_output(state, test);
+}
+
+static void check(struct test_state *state, const struct test *test)
+{
+    const struct regdef *def, *miss = 0;
+    struct reg *reg;
+    int any = 0;
+
+    if (!test) return;
+    for (def = test->regs; def->name; def++) {
+       reg = REG(in, def->i);
+       if (reg->f&REGF_LIVE) any = 1;
+       else if (!miss && !(def->f&REGF_OPT)) miss = def;
+    }
+    if (!any) return;
+    if (miss)
+       bail("register `%s' not set in test `%s'", def->name, test->name);
+
+    test->run(state, test);
+
+    for (def = test->regs; def->name; def++) {
+       reg = REG(in, def->i);
+       reg->f = 0; def->ty->release(&reg->v); def->ty->init(&reg->v);
+    }
+}
+
+int run_test_suite(unsigned nrout, unsigned nreg, size_t regsz,
+                  const struct test *tests, FILE *fp)
+{
+    struct linebuf buf = LINEBUF_INIT;
+    struct test_state state[1];
+    const struct test *test;
+    const struct regdef *def;
+    struct reg *reg;
+    char *p;
+    const char *q;
+    int total;
+    size_t n;
+
+    for (test = tests; test->name; test++)
+       for (def = test->regs; def->name; def++)
+           assert(def->i < nreg);
+
+    state->in = xmalloc(nreg*regsz);
+    state->out = xmalloc(nrout*regsz);
+    state->nrout = nrout;
+    state->nreg = nreg;
+    state->regsz = regsz;
+    state->win = state->lose = 0;
+
+    test = 0;
+    lno = 0;
+    while (!read_line(&buf, fp)) {
+       lno++;
+       p = buf.p; n = strlen(buf.p);
+
+       while (isspace((unsigned char)*p)) p++;
+       if (*p == '#') continue;
+       if (!*p) { check(state, test); continue; }
+
+       q = p;
+       while (*p && !isspace((unsigned char)*p)) p++;
+       if (*p) *p++ = 0;
+
+       if (!strcmp(q, "test")) {
+           if (!*p) bail("missing argument");
+           check(state, test);
+           if (test) {
+               for (def = test->regs; def->name; def++) {
+                   def->ty->release(&REG(in, def->i)->v);
+                   if (def->i < state->nrout)
+                       def->ty->release(&REG(out, def->i)->v);
+               }
+           }
+           for (test = tests; test->name; test++)
+               if (!strcmp(p, test->name)) goto found_test;
+           bail("unknown test `%s'", p);
+       found_test:
+           for (def = test->regs; def->name; def++) {
+               reg = REG(in, def->i);
+               reg->f = 0; def->ty->init(&reg->v);
+               if (def->i < state->nrout) {
+                   reg = REG(out, def->i);
+                   reg->f = 0; def->ty->init(&reg->v);
+               }
+           }
+           continue;
+       }
+
+       if (!test) bail("no current test");
+       for (def = test->regs; def->name; def++)
+           if (!strcmp(q, def->name)) goto found_reg;
+       bail("unknown register `%s' in test `%s'", q, test->name);
+    found_reg:
+       reg = REG(in, def->i);
+       if (reg->f&REGF_LIVE) bail("register `%s' already set", def->name);
+       def->ty->parse(&reg->v, p); reg->f |= REGF_LIVE;
+    }
+    check(state, test);
+
+    total = state->win + state->lose;
+    if (!state->lose)
+       printf("PASSED all %d test%s\n", state->win, total == 1 ? "" : "s");
+    else
+       printf("FAILED %d of %d test%s\n", state->lose, total,
+              total == 1 ? "" : "s");
+    return state->lose ? 1 : 0;
+}
diff --git a/crypto-test.h b/crypto-test.h
new file mode 100644 (file)
index 0000000..94bb009
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * crypto-test.h: common test vector processing
+ */
+/*
+ * This file is Free Software.  It was originally written for secnet.
+ *
+ * Copyright 2017, 2019 Mark Wooding
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later
+ * version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#ifndef crypto_test_h
+#define crypto_test_h
+
+/* Basic model.
+ *
+ * There is a collection of `registers', each of which can store a value.
+ * Some registers are designated as `input': their values are set while
+ * reading the test vector.  Other registers are designated as `output': the
+ * test function is expected to calculate their values, which are then
+ * compared against final values supplied by the test vector.  Any
+ * discrepancies are reported.
+ *
+ * While a test suite is running, there is a single vector of registers, and
+ * registers are identified by index, starting from zero.  The number of
+ * registers, `nreg', and a threshold `nrout', are defined by the test suite:
+ * registers with index less than `nrout' are for output; other registers up
+ * to, but not including, `nreg' are for input.  Finally, the test suite
+ * defines the size of a register.  The common test machinery treats
+ * registers as entirely opaque, acting on them only through their defined
+ * types.
+ *
+ * Each kind of test defines a register mapping, which assigns types and
+ * (textual) names to some subset of the registers.  A register can be marked
+ * optional; by default, the test vector parser will report an error if a
+ * defined register is not assigned a value.
+ *
+ * The register type is responsible for handling the register on behalf of
+ * the common code.  (Test functions can have built-in knowledge of which
+ * registers have which types, and can manipulate registers directly, of
+ * course.)
+ *
+ * Finally, each test defines a test runner, which is responsible for
+ * invoking the test function and checking that the output registers are
+ * correct.  There is a generic test runner, but some tests might benefit
+ * from special arrangements.
+ */
+
+union regval {
+    long i;                            /* signed integer */
+    unsigned long u;                   /* unsigned integer */
+    struct { void *p; size_t sz; } bytes; /* buffer of bytes */
+#ifdef REG_MEMBERS
+    REG_MEMBERS                                /* your members here */
+#endif
+};
+
+struct reg {
+    unsigned f;                                /* flags */
+#define REGF_LIVE 1u                   /*   input register has a value */
+    union regval v;                    /* register value */
+};
+
+struct regty {
+    void (*init)(union regval *v);     /* set up raw memory */
+    void (*parse)(union regval *v, char *p); /* parse text input as value */
+    void (*dump)(FILE *fp, const union regval *v); /* dump value as text */
+    int (*eq)(const union regval *v0, const union regval *v1); /* equal? */
+    void (*release)(union regval *v);  /* release any resources */
+};
+
+struct regdef {
+    const char *name;                  /* register name (for input files) */
+    unsigned i;                                /* register index */
+    const struct regty *ty;            /* register type descriptor */
+    unsigned f;                                /* flags */
+#define REGF_OPT 1u                    /*   (input) register is optional */
+};
+#define REGLIST_END { 0 }
+
+struct test_state {
+    struct reg *in, *out;              /* vectors of registers */
+    unsigned nrout;                    /* number of output registers */
+    unsigned nreg;                     /* total number of registers */
+    size_t regsz;                      /* size of an individual register */
+    int win, lose;                     /* number of tests passed/failed */
+};
+
+struct test {
+    const char *name;                  /* name of the test */
+    void (*run)(struct test_state *state, const struct test *test);
+                                       /* test runner (`run_test') */
+    const struct regdef *regs;         /* register definitions */
+    void (*fn)(struct reg *out, const struct reg *in, void *ctx);
+                                       /* test function */
+};
+
+/* Utility functions. */
+extern NORETURN(bail(const char *msg, ...))
+    FORMAT(printf, 1, 2);
+extern void parse_hex(uint8_t *b, size_t sz, char *p);
+extern void dump_hex(FILE *fp, const uint8_t *b, size_t sz);
+extern void trivial_regty_init(union regval *v);
+extern void trivial_regty_release(union regval *v);
+extern void allocate_bytes(union regval *v, size_t sz);
+
+/* Built-in register types. */
+extern const struct regty
+    regty_int,
+    regty_uint,
+    regty_bytes;
+
+/* Running tests. */
+extern void check_test_output(struct test_state *state,
+                             const struct test *test);
+extern void run_test(struct test_state *state, const struct test *test);
+extern int run_test_suite(unsigned nrout, unsigned nreg, size_t regsz,
+                         const struct test *tests, FILE *fp);
+
+#endif
diff --git a/ec-field-test.c b/ec-field-test.c
new file mode 100644 (file)
index 0000000..5c29704
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * ec-field-test.c: test harness for elliptic-curve field arithmetic
+ *
+ * (The implementations originally came with different test arrangements,
+ * with complicated external dependencies.  This file replicates the original
+ * tests, but without the dependencies.)
+ */
+/*
+ * This file is Free Software.  It was originally written for secnet.
+ *
+ * Copyright 2017 Mark Wooding
+ *
+ * You may redistribute secnet as a whole and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3, or (at your option) any
+ * later version.
+ *
+ * You may redistribute this file and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later
+ * version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
+#include <stdio.h>
+
+#include "secnet.h"
+
+#include "f25519.h"
+#include "fgoldi.h"
+
+#define f25519_FESZ 32u
+#define fgoldi_FESZ 56u
+
+#define GLUE(x, y) GLUE_(x, y)
+#define GLUE_(x, y) x##y
+#define FIELDOP(op) GLUE(FIELD, _##op)
+
+#define REG_MEMBERS                                                    \
+    uint8_t fe[FIELDOP(FESZ)];
+#include "crypto-test.h"
+
+enum {
+    RZ, RZ0 = RZ, RZ1, RXX = RZ0, RYY = RZ1, NROUT,
+    RM = NROUT, RI = RM, RN = RM,
+    RU, RV, RW, RX, RY, RA,
+    RV0 = RU, RV31 = RV0 + 31,
+    NREG
+};
+
+static void parse_fe(union regval *v, char *p)
+{
+    size_t n = strlen(p);
+    size_t sz = sizeof(v->fe);
+
+    if (!*p)
+       memset(v->fe, 0xff, sizeof(v->fe));
+    else {
+       if (sz > n/2) sz = n/2;
+       parse_hex(v->fe, sz, p); memset(v->fe + sz, 0, sizeof(v->fe) - sz);
+    }
+}
+
+static void dump_fe(FILE *fp, const union regval *v)
+    { dump_hex(fp, v->fe, sizeof(v->fe)); }
+
+static int eq_fe(const union regval *v0, const union regval *v1)
+    { return (memcmp(v0->fe, v1->fe, sizeof(v0->fe)) == 0); }
+
+static const struct regty regty_fe = {
+    trivial_regty_init,
+    parse_fe,
+    dump_fe,
+    eq_fe,
+    trivial_regty_release
+};
+
+#define BINOP(op)                                                      \
+    static void test_##op(struct reg *out,                             \
+                         const struct reg *in, void *ctx)              \
+    {                                                                  \
+       FIELD x, y, z;                                                  \
+                                                                       \
+       FIELDOP(load)(&x, in[RX].v.fe);                                 \
+       FIELDOP(load)(&y, in[RY].v.fe);                                 \
+       FIELDOP(op)(&z, &x, &y);                                        \
+       FIELDOP(store)(out[RZ].v.fe, &z);                               \
+    }
+
+#define UNOP(op)                                                       \
+    static void test_##op(struct reg *out,                             \
+                         const struct reg *in, void *ctx)              \
+    {                                                                  \
+       FIELD x, z;                                                     \
+                                                                       \
+       FIELDOP(load)(&x, in[RX].v.fe);                                 \
+       FIELDOP(op)(&z, &x);                                            \
+       FIELDOP(store)(out[RZ].v.fe, &z);                               \
+    }
+
+BINOP(add)
+BINOP(sub)
+BINOP(mul)
+UNOP(neg)
+UNOP(sqr)
+UNOP(inv)
+
+static void test_condneg(struct reg *out, const struct reg *in, void *ctx)
+{
+    FIELD x, z;
+
+    FIELDOP(load)(&x, in[RX].v.fe);
+    FIELDOP(condneg)(&z, &x, in[RM].v.u);
+    FIELDOP(store)(out[RZ].v.fe, &z);
+}
+
+static void test_mulconst(struct reg *out, const struct reg *in, void *ctx)
+{
+    FIELD x, z;
+
+    FIELDOP(load)(&x, in[RX].v.fe);
+    FIELDOP(mulconst)(&z, &x, in[RA].v.i);
+    FIELDOP(store)(out[RZ].v.fe, &z);
+}
+
+static void test_condswap(struct reg *out, const struct reg *in, void *ctx)
+{
+    FIELD x, y;
+
+    FIELDOP(load)(&x, in[RX].v.fe);
+    FIELDOP(load)(&y, in[RY].v.fe);
+    FIELDOP(condswap)(&x, &y, in[RM].v.u);
+    FIELDOP(store)(out[RXX].v.fe, &x);
+    FIELDOP(store)(out[RYY].v.fe, &y);
+}
+
+static void test_pick2(struct reg *out, const struct reg *in, void *ctx)
+{
+    FIELD x, y, z;
+
+    FIELDOP(load)(&x, in[RX].v.fe);
+    FIELDOP(load)(&y, in[RY].v.fe);
+    FIELDOP(pick2)(&z, &x, &y, in[RM].v.u);
+    FIELDOP(store)(out[RZ].v.fe, &z);
+}
+
+static void test_pickn(struct reg *out, const struct reg *in, void *ctx)
+{
+    FIELD v[32], z;
+    unsigned i;
+
+    for (i = 0; in[RV0 + i].f&REGF_LIVE; i++)
+       FIELDOP(load)(&v[i], in[RV0 + i].v.fe);
+    FIELDOP(pickn)(&z, v, i, in[RI].v.u);
+    FIELDOP(store)(out[RZ].v.fe, &z);
+}
+
+static void test_quosqrt(struct reg *out, const struct reg *in, void *ctx)
+{
+    FIELD x, y, z;
+
+    FIELDOP(load)(&x, in[RX].v.fe);
+    FIELDOP(load)(&y, in[RY].v.fe);
+    if (FIELDOP(quosqrt)(&z, &x, &y))
+       memset(out[RZ0].v.fe, 0xff, sizeof(out[RZ].v.fe));
+    else
+       FIELDOP(store)(out[RZ].v.fe, &z);
+}
+
+static void run_quosqrt(struct test_state *state, const struct test *test)
+{
+    test->fn(state->out, state->in, 0);
+
+    /* ..._quosqrt returns an arbitrary square root.  The test vector
+     * contains both.  We win if we match either.
+     */
+    if (eq_fe(&state->in[RZ1].v, &state->out[RZ].v))
+       state->out[RZ0].v = state->in[RZ0].v;
+    state->out[RZ1].v = state->in[RZ1].v;
+    check_test_output(state, test);
+}
+
+static void test_sub_mulc_add_sub_mul(struct reg *out,
+                                     const struct reg *in, void *ctx)
+{
+    FIELD u, v, w, x, y, z;
+
+    FIELDOP(load)(&u, in[RU].v.fe);
+    FIELDOP(load)(&v, in[RV].v.fe);
+    FIELDOP(load)(&w, in[RW].v.fe);
+    FIELDOP(load)(&x, in[RX].v.fe);
+    FIELDOP(load)(&y, in[RY].v.fe);
+
+    FIELDOP(sub)(&z, &u, &v);
+    FIELDOP(mulconst)(&z, &z, in[RA].v.i);
+    FIELDOP(add)(&z, &z, &w);
+    FIELDOP(sub)(&x, &x, &y);
+    FIELDOP(mul)(&z, &z, &x);
+    FIELDOP(store)(out[RZ].v.fe, &z);
+}
+
+#define REG_U { "u", RU, &regty_fe, 0 }
+#define REG_V { "v", RV, &regty_fe, 0 }
+#define REG_W { "w", RW, &regty_fe, 0 }
+#define REG_X { "x", RX, &regty_fe, 0 }
+#define REG_Y { "y", RY, &regty_fe, 0 }
+#define REG_A { "a", RA, &regty_int, 0 }
+#define REG_M { "m", RM, &regty_uint, 0 }
+#define REG_I { "i", RI, &regty_uint, 0 }
+#define REG_XX { "xx", RXX, &regty_fe, 0 }
+#define REG_YY { "yy", RYY, &regty_fe, 0 }
+#define REG_Z { "z", RZ, &regty_fe, 0 }
+#define REG_Z0 { "z0", RZ0, &regty_fe, 0 }
+#define REG_Z1 { "z1", RZ1, &regty_fe, 0 }
+#define REG_BIGY { "Y", RY, &regty_fe, 0 }
+#define REG_BIGZ { "Z", RZ, &regty_fe, 0 }
+#define REG_N { "n", RN, &regty_uint, 0 }
+#define REG_Vi(i) { "v[" # i "]", RV0 + i, &regty_fe, REGF_OPT }
+#define REG_VV                                                         \
+    REG_Vi( 0), REG_Vi( 1), REG_Vi( 2), REG_Vi( 3),                    \
+    REG_Vi( 4), REG_Vi( 5), REG_Vi( 6), REG_Vi( 7),                    \
+    REG_Vi( 8), REG_Vi( 9), REG_Vi(10), REG_Vi(11),                    \
+    REG_Vi(12), REG_Vi(13), REG_Vi(14), REG_Vi(15),                    \
+    REG_Vi(16), REG_Vi(17), REG_Vi(18), REG_Vi(19),                    \
+    REG_Vi(20), REG_Vi(21), REG_Vi(22), REG_Vi(23),                    \
+    REG_Vi(24), REG_Vi(25), REG_Vi(26), REG_Vi(27),                    \
+    REG_Vi(28), REG_Vi(29), REG_Vi(30), REG_Vi(31)
+static const struct regdef
+    unop_regs[] = { REG_X, REG_Z, REGLIST_END },
+    binop_regs[] = { REG_X, REG_Y, REG_Z, REGLIST_END },
+    condneg_regs[] = { REG_X, REG_M, REG_Z, REGLIST_END },
+    mulconst_regs[] = { REG_X, REG_A, REG_Z, REGLIST_END },
+    pick2_regs[] = { REG_X, REG_Y, REG_M, REG_Z, REGLIST_END },
+    pickn_regs[] = { REG_VV, REG_I, REG_Z, REGLIST_END },
+    condswap_regs[] = { REG_X, REG_Y, REG_M, REG_XX, REG_YY, REGLIST_END },
+    quosqrt_regs[] = { REG_X, REG_Y, REG_Z0, REG_Z1, REGLIST_END },
+    sub_mulc_add_sub_mul_regs[] =
+       { REG_U, REG_V, REG_A, REG_W, REG_X, REG_Y, REG_Z, REGLIST_END };
+
+static const struct test tests[] = {
+    { "add", run_test, binop_regs, test_add },
+    { "sub", run_test, binop_regs, test_sub },
+    { "neg", run_test, unop_regs, test_neg },
+    { "condneg", run_test, condneg_regs, test_condneg },
+    { "condswap", run_test, condswap_regs, test_condswap },
+    { "mulconst", run_test, mulconst_regs, test_mulconst },
+    { "mul", run_test, binop_regs, test_mul },
+    { "sqr", run_test, unop_regs, test_sqr },
+    { "inv", run_test, unop_regs, test_inv },
+    { "pick2", run_test, pick2_regs, test_pick2 },
+    { "pickn", run_test, pickn_regs, test_pickn },
+    { "quosqrt", run_quosqrt, quosqrt_regs, test_quosqrt },
+    { "sub-mulc-add-sub-mul", run_test,
+      sub_mulc_add_sub_mul_regs, test_sub_mulc_add_sub_mul },
+    { 0 }
+};
+
+int main(void)
+    { return run_test_suite(NROUT, NREG, sizeof(struct reg), tests, stdin); }