base/regdump.[ch], etc.: Fancy register dumping infrastructure.
[catacomb] / base / regdump.c
diff --git a/base/regdump.c b/base/regdump.c
new file mode 100644 (file)
index 0000000..d4f5fde
--- /dev/null
@@ -0,0 +1,945 @@
+/* -*-c-*-
+ *
+ * Register dumping and other diagnostic tools for assembler code
+ *
+ * (c) 2016 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Catacomb.
+ *
+ * Catacomb is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Catacomb 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with Catacomb; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <mLib/bits.h>
+#include <mLib/macros.h>
+
+#include "dispatch.h"
+#include "regdump.h"
+
+/*----- Low-level printing ------------------------------------------------*/
+
+/* Currently these are good for all of our targets. */
+#define STEP_8         1
+#define TY_HEX_8       uint8
+#define P_HEX_8                "0x%02x"
+#define TY_UNSGN_8     uint8
+#define P_UNSGN_8      "%3u"
+#define PV_CHR_8       " `%c'"
+#define PV_HEX_8       "  %02x"
+#define PV_UNSGN_8     "%4u"
+
+#define STEP_16                2
+#define TY_HEX_16      uint16
+#define P_HEX_16       "0x%04x"
+#define TY_UNSGN_16    uint16
+#define P_UNSGN_16     "%5u"
+#define TY_SGN_16      int16
+#define P_SGN_16       "%6d"
+#define PV_HEX_16      "   0x%04x"
+#define PV_UNSGN_16    "%9u"
+#define PV_SGN_16      "%9d"
+
+#define STEP_32                4
+#define TY_HEX_32      uint32
+#define P_HEX_32       "0x%08x"
+#define TY_UNSGN_32    uint32
+#define P_UNSGN_32     "%10u"
+#define TY_SGN_32      int32
+#define P_SGN_32       "%11d"
+#define TY_FLT_32      float
+#define P_FLT_32       "%15.9g"
+#define PV_HEX_32      "         0x%08x"
+#define PV_UNSGN_32    "%19u"
+#define PV_SGN_32      "%19d"
+#define PV_FLT_32      "%19.9g"
+
+#if ULONG_MAX >> 31 > 0xffffffff
+#  define PL64 "l"
+#else
+#  define PL64 "ll"
+#endif
+#define STEP_64                8
+#define TY_HEX_64      uint64
+#define P_HEX_64       "0x%016"PL64"x"
+#define TY_UNSGN_64    uint64
+#define P_UNSGN_64     "%20"PL64"u"
+#define TY_SGN_64      int64
+#define P_SGN_64       "%20"PL64"d"
+#define TY_FLT_64      double
+#define P_FLT_64       "%24.17g"
+#define PV_HEX_64      "                     0x%016"PL64"x"
+#define PV_UNSGN_64    "%39"PL64"u"
+#define PV_SGN_64      "%39"PL64"d"
+#define PV_FLT_64      "%39.17g"
+
+#if CPUFAM_X86
+#  define STEP_80      12
+#endif
+#if CPUFAM_AMD64
+#  define STEP_80      16
+#endif
+#define TY_FLT_80      long double
+#define P_FLT_80       "%29.21Lg"
+#define PV_FLT_80      P_FLT_80
+
+#if CPUFAM_X86 || CPUFAM_AMD64
+#  define ARCH_FORMATS(_)                                              \
+  _(80, FLT)
+#endif
+#ifndef ARCH_FORMATS
+#  define ARCH_FORMATS(_)
+#endif
+
+#define FORMATS(_)                                                     \
+  ARCH_FORMATS(_)                                                      \
+  _(64, HEX) _(64, FLT) _(64, UNSGN) _(64, SGN)                                \
+  _(32, HEX) _(32, FLT) _(32, UNSGN) _(32, SGN)                                \
+  _(16, HEX) _(16, UNSGN) _(16, SGN)                                   \
+  _(8, HEX) _(8, CHR) _(8, UNSGN)
+
+struct fmtinfo {
+  const unsigned char *p;
+  unsigned wd, f;
+#define FMTF_VECTOR 1u
+};
+
+#define FMTFUNC_STD(w, fmt)                                            \
+  static void dump_##fmt##_##w(struct fmtinfo *fmt)                    \
+  {                                                                    \
+    TY_##fmt##_##w x = *(const TY_##fmt##_##w *)fmt->p;                        \
+                                                                       \
+    if (fmt->f&FMTF_VECTOR) printf(PV_##fmt##_##w, x);                 \
+    else printf(P_##fmt##_##w, x);                                     \
+    fmt->p += STEP_##w; fmt->wd += 8*STEP_##w;                         \
+  }
+
+#define FMTFUNC_HEX(w) FMTFUNC_STD(w, HEX)
+#define FMTFUNC_UNSGN(w) FMTFUNC_STD(w, UNSGN)
+#define FMTFUNC_SGN(w) FMTFUNC_STD(w, SGN)
+#define FMTFUNC_FLT(w) FMTFUNC_STD(w, FLT)
+#define FMTFUNC_CHR(w)
+
+static void dump_CHR_8(struct fmtinfo *fmt)
+{
+  unsigned char x = *(const unsigned char *)fmt->p;
+
+  if (x < 32 || x > 126) printf("\\x%02x", x);
+  else printf(" `%c'", x);
+  fmt->p += 1; fmt->wd += 8;
+}
+
+#define FMTFUNC(w, fmt) FMTFUNC_##fmt(w)
+FORMATS(FMTFUNC)
+#undef FMTFUNC
+
+static const struct fmttab {
+  uint32 mask;
+  void (*fmt)(struct fmtinfo *);
+} fmttab[] = {
+#define FMTTAB(wd, fmt) { REGF_##fmt | REGF_##wd, dump_##fmt##_##wd },
+  FORMATS(FMTTAB)
+#undef FMTTAB
+  { 0, 0 }
+};
+
+/*----- Common subroutines ------------------------------------------------*/
+
+/* --- @regwd@ --- *
+ *
+ * Arguments:  @uint32 f@ = format control word; see @REGF_...@
+ *
+ * Returns:    The actual width of the operand, in bits.
+ *
+ * Use:                If the operand is a vector (the @REGF_WDMASK@ field is
+ *             nonzero) then return the width it denotes; otherwise, return
+ *             the largest width implied by the @REGF_TYMASK@ field.
+ */
+
+static unsigned regwd(uint32 f)
+{
+  unsigned wd = 1 << ((f&REGF_WDMASK) >> REGF_WDSHIFT);
+
+  if (wd > 1) return (wd);
+  else if (f&REGF_80) return (80);
+  else if (f&REGF_64) return (64);
+  else if (f&REGF_32) return (32);
+  else if (f&REGF_16) return (16);
+  else if (f&REGF_8) return (8);
+  else { assert(0); return (1); }
+}
+
+/* --- @regname@ --- *
+ *
+ * Arguments:  @char *buf = pointer to output buffer@
+ *             @uint32 f@ = format control word; see @REGF_...@
+ *
+ * Returns:    Pointer to name string.
+ *
+ * Use:                Return a pointer to the name of the register implied by @f@,
+ *             or null if there is no register.  Systematic register names
+ *             can be built in the provided buffer.
+ */
+
+static const char *regname(char *buf, uint32 f)
+{
+  unsigned wd = regwd(f);
+  unsigned src = f&REGF_SRCMASK;
+  unsigned ix = (f&REGF_IXMASK) >> REGF_IXSHIFT;
+  char *p = buf;
+
+  switch (src) {
+
+    case REGSRC_ABS:
+      return (0);
+
+#if CPUFAM_X86 || CPUFAM_AMD64
+    case REGSRC_GP:
+      if (ix == REGIX_FLAGS) {
+       if (wd == 64) *p++ = 'r';
+       else if (wd == 32) *p++ = 'e';
+       else if (wd != 16) assert(0);
+       p += sprintf(p, "flags");
+#if  CPUFAM_AMD64
+      } else if (REGIX_R8 <= ix && ix <= REGIX_R15) {
+       p += sprintf(p, "r%u", ix - REGIX_R8 + 8);
+       switch (wd) {
+         case 64:             break;
+         case 32: *p++ = 'd'; break;
+         case 16: *p++ = 'w'; break;
+         case  8: *p++ = 'l'; break;
+         default: assert(0);
+       }
+#  endif
+      } else {
+       if (wd == 64) *p++ = 'r';
+       else if (wd == 32) *p++ = 'e';
+       switch (ix) {
+         case REGIX_IP: *p++ = 'i'; *p++ = 'p'; goto longreg;
+         case REGIX_AX: *p++ = 'a'; goto shortreg;
+         case REGIX_BX: *p++ = 'b'; goto shortreg;
+         case REGIX_CX: *p++ = 'c'; goto shortreg;
+         case REGIX_DX: *p++ = 'd'; goto shortreg;
+         case REGIX_SI: *p++ = 's'; *p++ = 'i'; goto longreg;
+         case REGIX_DI: *p++ = 'd'; *p++ = 'i'; goto longreg;
+         case REGIX_BP: *p++ = 'b'; *p++ = 'p'; goto longreg;
+         case REGIX_SP: *p++ = 's'; *p++ = 'p'; goto longreg;
+         default: assert(0);
+       }
+       if (0) {
+       shortreg:
+         switch (wd) {
+           case 64:
+           case 32:
+           case 16: *p++ = 'x'; break;
+           case  8: *p++ = 'l'; break;
+           default: assert(0);
+         }
+       } else {
+       longreg:
+         switch (wd) {
+           case 64:
+           case 32:
+           case 16: break;
+           case  8: *p++ = 'l'; break;
+           default: assert(0);
+         }
+       }
+      }
+      *p++ = 0;
+      return (buf);
+
+    case REGSRC_SEG:
+      assert(wd == 16);
+      switch (ix) {
+       case REGIX_CS: sprintf(buf, "cs"); break;
+       case REGIX_DS: sprintf(buf, "ds"); break;
+       case REGIX_SS: sprintf(buf, "ss"); break;
+       case REGIX_ES: sprintf(buf, "es"); break;
+       case REGIX_FS: sprintf(buf, "fs"); break;
+       case REGIX_GS: sprintf(buf, "gs"); break;
+       default: assert(0);
+      }
+      return (buf);
+
+    case REGSRC_STMMX:
+      if (ix == REGIX_FPFLAGS) return (0);
+      if (f&REGF_80) sprintf(buf, "st(%u)", ix);
+      else sprintf(buf, "mm%u", ix);
+      return (buf);
+
+    case REGSRC_SIMD:
+      if (ix == REGIX_FPFLAGS) return (0);
+      switch (wd) {
+       case 32: case 64: case 128: sprintf(buf, "xmm%u", ix); break;
+       case 256: sprintf(buf, "ymm%u", ix); break;
+       default: assert(0);
+      }
+      return (buf);
+#endif
+
+#if CPUFAM_ARMEL
+    case REGSRC_GP:
+      if (ix == REGIX_CPSR) sprintf(buf, "cpsr");
+      else if (ix == 15) sprintf(buf, "pc");
+      else sprintf(buf, "r%u", ix);
+      return (buf);
+    case REGSRC_FP:
+      if (ix == REGIX_FPSCR) sprintf(buf, "fpscr");
+      else {
+       switch (wd) {
+         case 32: *p++ = 's'; break;
+         case 64: *p++ = 'd'; break;
+         case 128: *p++ = 'q'; break;
+         default: assert(0);
+       }
+       p += sprintf(p, "%u", ix);
+       *p++ = 0;
+      }
+      return (buf);
+#endif
+
+#if CPUFAM_ARM64
+    case REGSRC_GP:
+      if (ix == REGIX_PC) sprintf(buf, "pc");
+      else if (ix == REGIX_NZCV) sprintf(buf, "nzcv");
+      else if (ix == 31 && wd == 64) sprintf(buf, "sp");
+      else {
+       switch (wd) {
+         case 32: *p++ = 'w'; break;
+         case 64: *p++ = 'x'; break;
+         default: assert(0);
+       }
+       p += sprintf(p, "%u", ix);
+       *p++ = 0;
+      }
+      return (buf);
+    case REGSRC_FP:
+      if (ix == REGIX_FPFLAGS) sprintf(buf, "fpflags");
+      else {
+       if (f&REGF_WDMASK)
+         *p++ = 'v';
+       else switch (wd) {
+         case 8: *p++ = 'b'; break;
+         case 16: *p++ = 'h'; break;
+         case 32: *p++ = 's'; break;
+         case 64: *p++ = 'd'; break;
+         default: assert(0);
+       }
+       p += sprintf(p, "%u", ix);
+       *p++ = 0;
+      }
+      return (buf);
+#endif
+
+    default:
+      assert(0);
+      return ("???");
+  }
+}
+
+/*----- x86 and AMD64 -----------------------------------------------------*/
+
+#if CPUFAM_X86 || CPUFAM_AMD64
+
+#if CPUFAM_X86
+#  define P_HEX_GP "0x%08x"
+#  define GP(gp) (gp).u32
+#endif
+#if CPUFAM_AMD64
+#  define P_HEX_GP "0x%016"PL64"x"
+#  define GP(gp) (gp).u64
+#endif
+
+void regdump_init(void) { ; }
+
+static void dump_flags(const char *lbl, const char *reg, gpreg f)
+{
+  printf(";; ");
+  if (lbl) printf("%s: ", lbl);
+  if (reg) printf("%s = ", reg);
+  printf(""P_HEX_GP"\n", GP(f));
+  printf(";;\t\tstatus: %ccf %cpf %caf %czf %csf %cdf %cof\n",
+        (GP(f) >>  0)&1u ? '+' : '-',
+        (GP(f) >>  2)&1u ? '+' : '-',
+        (GP(f) >>  4)&1u ? '+' : '-',
+        (GP(f) >>  6)&1u ? '+' : '-',
+        (GP(f) >>  7)&1u ? '+' : '-',
+        (GP(f) >> 10)&1u ? '+' : '-',
+        (GP(f) >> 11)&1u ? '+' : '-');
+  printf(";;\t\tsystem: %ctf %cif iopl=%d %cnt "
+                       "%crf %cvm %cac %cvif %cvip %cid\n",
+        (GP(f) >>  8)&1u ? '+' : '-',
+        (GP(f) >>  9)&1u ? '+' : '-',
+        (int)((GP(f) >> 12)&1u),
+        (GP(f) >> 14)&1u ? '+' : '-',
+        (GP(f) >> 16)&1u ? '+' : '-',
+        (GP(f) >> 17)&1u ? '+' : '-',
+        (GP(f) >> 18)&1u ? '+' : '-',
+        (GP(f) >> 19)&1u ? '+' : '-',
+        (GP(f) >> 20)&1u ? '+' : '-',
+        (GP(f) >> 21)&1u ? '+' : '-');
+}
+
+static const char
+  *pcmap[] = { "sgl", "???", "dbl", "ext" },
+  *rcmap[] = { "nr", "-∞", "+∞", "0" };
+
+static void dump_fpflags(const char *lbl, const struct fxsave *fx)
+{
+  unsigned top = (fx->fsw >> 11)&7u;
+  unsigned tag = fx->ftw;
+  int skip = lbl ? strlen(lbl) + 2 : 0;
+
+  printf(";; ");
+  if (lbl) printf("%s: ", lbl);
+
+  printf("    fcw = 0x%04x: "
+        "%cim %cdm %czm %com %cum %cpm pc=%s rc=%s %cx\n",
+        fx->fcw,
+        (fx->fcw >>  0)&1u ? '+' : '-',
+        (fx->fcw >>  1)&1u ? '+' : '-',
+        (fx->fcw >>  2)&1u ? '+' : '-',
+        (fx->fcw >>  3)&1u ? '+' : '-',
+        (fx->fcw >>  4)&1u ? '+' : '-',
+        (fx->fcw >>  5)&1u ? '+' : '-',
+        pcmap[(fx->fcw >>  8)&3u],
+        rcmap[(fx->fcw >> 10)&3u],
+        (fx->fcw >> 12)&1u ? '+' : '-');
+  printf(";; %*s    fsw = 0x%04x: "
+        "%cie %cde %cze %coe %cue %cpe %csf %ces %cc0 %cc1 %cc2 %cc3 "
+        "top=%d %cb\n",
+        skip, "",
+        fx->fsw,
+        (fx->fsw >>  0)&1u ? '+' : '-',
+        (fx->fsw >>  1)&1u ? '+' : '-',
+        (fx->fsw >>  2)&1u ? '+' : '-',
+        (fx->fsw >>  3)&1u ? '+' : '-',
+        (fx->fsw >>  4)&1u ? '+' : '-',
+        (fx->fsw >>  5)&1u ? '+' : '-',
+        (fx->fsw >>  6)&1u ? '+' : '-',
+        (fx->fsw >>  7)&1u ? '+' : '-',
+        (fx->fsw >>  8)&1u ? '+' : '-',
+        (fx->fsw >>  9)&1u ? '+' : '-',
+        (fx->fsw >> 10)&1u ? '+' : '-',
+        (fx->fsw >> 14)&1u ? '+' : '-',
+        top,
+        (fx->fsw >> 15)&1u ? '+' : '-');
+  printf(";; %*s    ftw = 0x%02x\n", skip, "", tag);
+}
+
+static void dump_mxflags(const char *lbl, const struct fxsave *fx)
+{
+  printf(";; ");
+  if (lbl) printf("%s: ", lbl);
+
+  printf("  mxcsr = 0x%08x\n"
+        ";;\t\tmask = %cim %cdm %czm %com %cum %cpm\n"
+        ";;\t\t exc = %cie %cde %cze %coe %cue %cpe\n"
+        ";;\t\tmisc = %cdaz %cftz rc=%s\n",
+        fx->mxcsr,
+        (fx->mxcsr >>  7)&1u ? '+' : '-',
+        (fx->mxcsr >>  8)&1u ? '+' : '-',
+        (fx->mxcsr >>  9)&1u ? '+' : '-',
+        (fx->mxcsr >> 10)&1u ? '+' : '-',
+        (fx->mxcsr >> 11)&1u ? '+' : '-',
+        (fx->mxcsr >> 12)&1u ? '+' : '-',
+        (fx->mxcsr >>  0)&1u ? '+' : '-',
+        (fx->mxcsr >>  1)&1u ? '+' : '-',
+        (fx->mxcsr >>  2)&1u ? '+' : '-',
+        (fx->mxcsr >>  3)&1u ? '+' : '-',
+        (fx->mxcsr >>  4)&1u ? '+' : '-',
+        (fx->mxcsr >>  5)&1u ? '+' : '-',
+        (fx->mxcsr >>  6)&1u ? '+' : '-',
+        (fx->mxcsr >> 15)&1u ? '+' : '-',
+        rcmap[(fx->mxcsr >> 13)&3u]);
+}
+
+#if CPUFAM_X86
+#  define REGF_GPWD REGF_32
+#endif
+#if CPUFAM_AMD64
+#  define REGF_GPWD REGF_64
+#endif
+
+void regdump_gp(const struct regmap *map)
+{
+  unsigned i;
+
+  printf(";; General-purpose registers:\n");
+  for (i = REGIX_AX; i < REGIX_GPLIM; i++)
+    regdump(map, 0,
+           REGF_HEX | REGF_UNSGN | REGF_SGN | REGF_GPWD | REGSRC_GP | i);
+  regdump(map, 0, REGF_HEX | REGF_GPWD | REGSRC_GP | REGIX_IP);
+
+  printf(";; Segment registers:\n");
+  for (i = 0; i < REGIX_SEGLIM; i++)
+    regdump(map, 0, REGF_HEX | REGF_16 | REGSRC_SEG | i);
+
+  printf(";; Flags:\n");
+  regdump(map, 0, REGSRC_GP | REGF_GPWD | REGIX_FLAGS);
+}
+
+void regdump_fp(const struct regmap *map)
+{
+  unsigned top = (map->fx->fsw >> 11)&7u;
+  unsigned tag = map->fx->ftw;
+  unsigned i;
+
+  printf(";; Floating-point/MMX registers:\n");
+  if (!top && tag == 0xff)
+    for (i = 0; i < 8; i++)
+      regdump(map, 0,
+             REGF_HEX | REGF_UNSGN | REGF_SGN | REGF_CHR |
+             REGF_32 | REGF_16 | REGF_8 |
+             REGSRC_STMMX | i | (6 << REGF_WDSHIFT));
+  if (tag)
+    for (i = 0; i < 8; i++)
+      regdump(map, 0, REGF_FLT | REGF_80 | REGSRC_STMMX | i);
+
+  printf(";; Floating-point state:\n");
+  dump_fpflags(0, map->fx);
+}
+
+void regdump_simd(const struct regmap *map)
+{
+  unsigned f = REGF_HEX | REGF_FLT | REGF_UNSGN | REGF_SGN | REGF_CHR |
+    REGF_64 | REGF_32 | REGF_16 | REGF_8 |
+    REGSRC_SIMD;
+  unsigned i;
+
+  if (map->avx) f |= 8 << REGF_WDSHIFT;
+  else f |= 7 << REGF_WDSHIFT;
+
+  printf(";; SSE/AVX registers:\n");
+  for (i = 0; i < N(map->fx->xmm); i++)
+    regdump(map, 0, f | i);
+
+  printf(";; SSE/AVX floating-point state:\n");
+  dump_mxflags(0, map->fx);
+}
+
+#endif
+
+/*----- ARM32 -------------------------------------------------------------*/
+
+#if CPUFAM_ARMEL
+
+unsigned regdump__flags = 0;
+
+void regdump_init(void)
+{
+  if (cpu_feature_p(CPUFEAT_ARM_VFP)) regdump__flags |= REGF_VFP;
+  if (cpu_feature_p(CPUFEAT_ARM_D32)) regdump__flags |= REGF_D32;
+}
+
+static void dump_flags(const char *lbl, unsigned f)
+{
+  static const char
+    *modetab[] = { "?00", "?01", "?02", "?03", "?04", "?05", "?06", "?07",
+                  "?08", "?09", "?10", "?11", "?12", "?13", "?14", "?15",
+                  "usr", "fiq", "irq", "svc", "?20", "?21", "mon", "abt",
+                  "?24", "?25", "hyp", "und", "?28", "?29", "?30", "sys" },
+    *condtab[] = { "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
+                  "hi", "ls", "ge", "lt", "gt", "le", "al", "nv" };
+
+  printf(";; ");
+  if (lbl) printf("%s: ", lbl);
+  printf("   cpsr = 0x%08x\n", f);
+  printf(";;\t\tuser: %cn %cz %cc %cv %cq ge=%c%c%c%c\n",
+        (f >> 31)&1u ? '+' : '-',
+        (f >> 30)&1u ? '+' : '-',
+        (f >> 29)&1u ? '+' : '-',
+        (f >> 28)&1u ? '+' : '-',
+        (f >> 27)&1u ? '+' : '-',
+        (f >> 19)&1u ? '1' : '0',
+        (f >> 18)&1u ? '1' : '0',
+        (f >> 17)&1u ? '1' : '0',
+        (f >> 16)&1u ? '1' : '0');
+  printf(";;\t\tsystem: %cj it=%s:%c%c%c%c %ce %ca %ci %cf %ct m=%s\n",
+        (f >> 24)&1u ? '+' : '-',
+        condtab[(f >> 12)&15u],
+        (f >> 11)&1u ? '1' : '0',
+        (f >> 10)&1u ? '1' : '0',
+        (f >> 26)&1u ? '1' : '0',
+        (f >> 25)&1u ? '1' : '0',
+        (f >>  9)&1u ? '+' : '-',
+        (f >>  8)&1u ? '+' : '-',
+        (f >>  7)&1u ? '+' : '-',
+        (f >>  6)&1u ? '+' : '-',
+        (f >>  5)&1u ? '+' : '-',
+        modetab[(f >>  0)&31u]);
+}
+
+static void dump_fpflags(const char *lbl, unsigned f)
+{
+  static const char *rcmap[] = { "nr", "+∞", "-∞", "0" };
+
+  printf(";; ");
+  if (lbl) printf("%s: ", lbl);
+  printf("  fpscr = 0x%08x\n", f);
+  printf(";;\t\tcond: %cn %cz %cc %cv %cqc\n",
+        (f >> 31)&1u ? '+' : '-',
+        (f >> 30)&1u ? '+' : '-',
+        (f >> 29)&1u ? '+' : '-',
+        (f >> 28)&1u ? '+' : '-',
+        (f >> 27)&1u ? '+' : '-');
+  printf(";;\t\ttrap: %cide %cixe %cufe %cofe %cdze %cioe\n",
+        (f >> 15)&1u ? '+' : '-',
+        (f >> 12)&1u ? '+' : '-',
+        (f >> 11)&1u ? '+' : '-',
+        (f >> 10)&1u ? '+' : '-',
+        (f >>  9)&1u ? '+' : '-',
+        (f >>  8)&1u ? '+' : '-');
+  printf(";;\t\terror:  %cide %cixe %cufe %cofe %cdze %cioe\n",
+        (f >>  7)&1u ? '+' : '-',
+        (f >>  4)&1u ? '+' : '-',
+        (f >>  3)&1u ? '+' : '-',
+        (f >>  2)&1u ? '+' : '-',
+        (f >>  1)&1u ? '+' : '-',
+        (f >>  0)&1u ? '+' : '-');
+  printf(";;\t\tcontrol: %cahp %cdn %cfz rm=%s str=%d len=%d\n",
+        (f >> 26)&1u ? '+' : '-',
+        (f >> 25)&1u ? '+' : '-',
+        (f >> 24)&1u ? '+' : '-',
+        rcmap[(f >> 22)&3u],
+        (f >> 20)&3u,
+        (f >> 16)&7u);
+}
+
+void regdump_gp(const struct regmap *map)
+{
+  unsigned i;
+
+  printf(";; General-purpose registers:\n");
+  for (i = 0; i < 16; i++)
+    regdump(map, 0,
+           REGF_HEX | REGF_UNSGN | REGF_SGN | REGF_32 | REGSRC_GP | i);
+
+  printf(";; Flags:\n");
+  regdump(map, 0, REGSRC_GP | REGF_32 | REGIX_CPSR);
+}
+
+void regdump_fp(const struct regmap *map)
+{
+  unsigned i, n;
+
+  if (!(regdump__flags&REGF_VFP)) {
+    printf(";; Floating-point and SIMD not available\n");
+    return;
+  }
+
+  printf(";; Floating-point/SIMD registers:\n");
+  if (regdump__flags&REGF_D32) n = 32;
+  else n = 16;
+  for (i = 0; i < n; i++)
+    regdump(map, 0,
+           REGF_HEX | REGF_UNSGN | REGF_SGN | REGF_FLT | REGF_CHR |
+           REGF_64 | REGF_32 | REGF_16 | REGF_8 |
+           REGSRC_SIMD | i | (6 << REGF_WDSHIFT));
+
+  printf(";; Floating-point state:\n");
+  dump_fpflags(0, map->fp->fpscr);
+}
+
+void regdump_simd(const struct regmap *map) { ; }
+
+#endif
+
+/*----- ARM64 -------------------------------------------------------------*/
+
+#if CPUFAM_ARM64
+
+void regdump_init(void) { ; }
+
+static void dump_flags(const char *lbl, unsigned f)
+{
+  printf(";; ");
+  if (lbl) printf("%s: ", lbl);
+  printf("   nzcv = 0x%08x\n", f);
+  printf(";;\t\tuser: %cn %cz %cc %cv\n",
+        (f >> 31)&1u ? '+' : '-',
+        (f >> 30)&1u ? '+' : '-',
+        (f >> 29)&1u ? '+' : '-',
+        (f >> 28)&1u ? '+' : '-');
+}
+
+static void dump_fpflags(const char *lbl, const struct fpsave *fp)
+{
+  static const char *rcmap[] = { "nr", "+∞", "-∞", "0" };
+  int skip = lbl ? strlen(lbl) + 2 : 0;
+
+  printf(";; ");
+  if (lbl) printf("%s: ", lbl);
+  printf("   fpsr = 0x%08x\n", fp->fpsr);
+  printf(";;\t\tcond: %cn %cz %cc %cv %cqc\n",
+        (fp->fpsr >> 31)&1u ? '+' : '-',
+        (fp->fpsr >> 30)&1u ? '+' : '-',
+        (fp->fpsr >> 29)&1u ? '+' : '-',
+        (fp->fpsr >> 28)&1u ? '+' : '-',
+        (fp->fpsr >> 27)&1u ? '+' : '-');
+  printf(";;\t\terror:  %cidc %cixc %cufc %cofc %cdzc %cioc\n",
+        (fp->fpsr >>  7)&1u ? '+' : '-',
+        (fp->fpsr >>  4)&1u ? '+' : '-',
+        (fp->fpsr >>  3)&1u ? '+' : '-',
+        (fp->fpsr >>  2)&1u ? '+' : '-',
+        (fp->fpsr >>  1)&1u ? '+' : '-',
+        (fp->fpsr >>  0)&1u ? '+' : '-');
+  printf(";; %*s   fpcr = 0x%08x\n", skip, "", fp->fpcr);
+  printf(";;\t\ttrap: %cide %cixe %cufe %cofe %cdze %cioe\n",
+        (fp->fpcr >> 15)&1u ? '+' : '-',
+        (fp->fpcr >> 12)&1u ? '+' : '-',
+        (fp->fpcr >> 11)&1u ? '+' : '-',
+        (fp->fpcr >> 10)&1u ? '+' : '-',
+        (fp->fpcr >>  9)&1u ? '+' : '-',
+        (fp->fpcr >>  8)&1u ? '+' : '-');
+  printf(";;\t\tcontrol: %cahp %cdn %cfz rm=%s str=%d len=%d\n",
+        (fp->fpcr >> 26)&1u ? '+' : '-',
+        (fp->fpcr >> 25)&1u ? '+' : '-',
+        (fp->fpcr >> 24)&1u ? '+' : '-',
+        rcmap[(fp->fpcr >> 22)&3u],
+        (fp->fpcr >> 20)&3u,
+        (fp->fpcr >> 16)&7u);
+}
+
+void regdump_gp(const struct regmap *map)
+{
+  unsigned i;
+
+  printf(";; General-purpose registers:\n");
+  for (i = 0; i < 32; i++)
+    regdump(map, 0,
+           REGF_HEX | REGF_UNSGN | REGF_SGN | REGF_64 | REGSRC_GP | i);
+  regdump(map, 0, REGF_HEX | REGF_64 | REGSRC_GP | REGIX_PC);
+
+  printf(";; Flags:\n");
+  regdump(map, 0, REGSRC_GP | REGF_32 | REGIX_NZCV);
+}
+
+void regdump_fp(const struct regmap *map)
+{
+  unsigned i;
+
+  printf(";; Floating-point/SIMD registers:\n");
+  for (i = 0; i < 32; i++)
+    regdump(map, 0,
+           REGF_HEX | REGF_UNSGN | REGF_SGN | REGF_FLT | REGF_CHR |
+           REGF_64 | REGF_32 | REGF_16 | REGF_8 |
+           REGSRC_SIMD | i | (7 << REGF_WDSHIFT));
+
+  printf(";; Floating-point state:\n");
+  dump_fpflags(0, map->fp);
+}
+
+void regdump_simd(const struct regmap *map) { ; }
+
+#endif
+
+/*----- The main entry point ----------------------------------------------*/
+
+/* --- @regdump@ --- *
+ *
+ * Arguments:  @const void *base@ = pointer to base structure, corresponding
+ *                     to the @REGF_SRCMASK@ part of @f@
+ *             @const char *lbl@ = label to print
+ *             @uint32 f@ = format control word; see @REGF_...@
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value, or chunk of memory.
+ *
+ *             This function is not usually called directly; instead, use
+ *             the `reg' or `mem' assembler macros.
+ */
+
+void regdump(const void *base, const char *lbl, uint32 f)
+{
+  unsigned ix = (f&REGF_IXMASK) >> REGF_IXSHIFT;
+  unsigned wd = 1 << ((f&REGF_WDMASK) >> REGF_WDSHIFT);
+  unsigned fmt, ty;
+  uint32 fmtbit, tybit;
+  const void *p;
+  char regbuf[8]; const char *reg = regname(regbuf, f);
+  const struct regmap *map;
+  const struct fmttab *tab;
+  struct fmtinfo fi;
+  int firstp = 1;
+  int skip;
+  size_t n;
+
+#if CPUFAM_X86 || CPUFAM_AMD64
+  union vreg vr;
+#endif
+
+  if (reg) {
+    n = strlen(reg);
+    if (n < 7) {
+      memmove(regbuf + 7 - n, reg, n + 1);
+      memset(regbuf, ' ', 7 - n);
+    }
+  }
+
+  switch (f&REGF_SRCMASK) {
+    case REGSRC_ABS:
+      p = base;
+      break;
+
+#if CPUFAM_X86 || CPUFAM_AMD64
+    case REGSRC_GP:
+      map = (const struct regmap *)base;
+      if (ix == REGIX_FLAGS && !(f&REGF_FMTMASK))
+       { dump_flags(lbl, reg, map->gp->gp[REGIX_FLAGS]); return; }
+      p = &map->gp->gp[ix];
+      break;
+    case REGSRC_SEG:
+      map = (const struct regmap *)base;
+      assert(wd == 1); assert((f&REGF_TYMASK) == REGF_16);
+      p = &map->gp->seg[ix];
+      break;
+    case REGSRC_STMMX:
+      map = (const struct regmap *)base;
+      if (ix == REGIX_FPFLAGS)
+       { assert(!(f&REGF_FMTMASK)); dump_fpflags(lbl, map->fx); return; }
+      if (!((map->fx->ftw << ix)&128u)) {
+       printf(";; ");
+       if (lbl) printf("%s: ", lbl);
+       if (reg) printf("%s = ", reg);
+       printf("                         dead\n");
+       return;
+      }
+      p = &map->fx->stmmx[ix];
+      break;
+    case REGSRC_SIMD:
+      map = (const struct regmap *)base;
+      if (ix == REGIX_FPFLAGS)
+       { assert(!(f&REGF_FMTMASK)); dump_mxflags(lbl, map->fx); return; }
+      if (wd <= 128)
+       p = &map->fx->xmm[ix];
+      else {
+       vr.v128[0] = map->fx->xmm[ix];
+       vr.v128[1] = map->avx->ymmh[ix];
+       assert(wd == 256);
+       p = &vr;
+      }
+      break;
+#endif
+
+#if CPUFAM_ARMEL
+    case REGSRC_GP:
+      map = (const struct regmap *)base;
+      if (ix == REGIX_CPSR && !(f&REGF_FMTMASK))
+       { dump_flags(lbl, map->gp->r[REGIX_CPSR].u32); return; }
+      p = &map->gp->r[ix];
+      break;
+    case REGSRC_FP:
+    case REGSRC_SIMD:
+      map = (const struct regmap *)base;
+      if (ix == REGIX_FPSCR) {
+       assert(!(f&REGF_FMTMASK));
+       dump_fpflags(lbl, map->fp->fpscr);
+       return;
+      }
+      switch (regwd(f)) {
+       case 32: p = &map->fp->u.s[ix]; break;
+       case 64: p = &map->fp->u.d[ix]; break;
+       case 128: p = &map->fp->u.q[ix]; break;
+       default: assert(0);
+      }
+      break;
+#endif
+
+#if CPUFAM_ARM64
+    case REGSRC_GP:
+      map = (const struct regmap *)base;
+      if (ix == REGIX_NZCV && !(f&REGF_FMTMASK))
+       { dump_flags(lbl, map->gp->r[REGIX_NZCV].u64); return; }
+      p = &map->gp->r[ix];
+      break;
+    case REGSRC_FP:
+    case REGSRC_SIMD:
+      map = (const struct regmap *)base;
+      if (ix == REGIX_FPFLAGS)
+       { assert(!(f&REGF_FMTMASK)); dump_fpflags(lbl, map->fp); return; }
+      p = &map->fp->v[ix];
+      break;
+#endif
+
+    default:
+      assert(0);
+  }
+
+  skip = (lbl ? strlen(lbl) + 2 : 0) + (reg ? strlen(reg) : 0);
+  fi.f = 0; if (wd > 1) fi.f |= FMTF_VECTOR;
+
+  for (ty = (f&REGF_TYMASK) >> REGF_TYSHIFT,
+        tybit = 1 << REGF_TYSHIFT;
+       ty;
+       ty >>= 1, tybit <<= 1) {
+    if (!(ty&1u)) continue;
+
+    for (fmt = (f&REGF_FMTMASK) >> REGF_FMTSHIFT,
+          fmtbit = 1 << REGF_FMTSHIFT;
+        fmt;
+        fmt >>= 1, fmtbit <<= 1) {
+
+      if (!(fmt&1u)) continue;
+
+      for (tab = fmttab; tab->mask; tab++)
+       if (tab->mask == (fmtbit | tybit)) goto found;
+      continue;
+    found:
+
+      if (firstp) {
+       printf(";;");
+       if (lbl) printf(" %s:", lbl);
+       if (reg) printf(" %s =", reg);
+       firstp = 0;
+      } else if (wd > 1)
+       printf("\n;; %*s =", skip, "");
+      else
+       fputs(" =", stdout);
+
+      fi.p = p; fi.wd = 0;
+      while (fi.wd < wd) { putchar(' '); tab->fmt(&fi); }
+    }
+  }
+  putchar('\n');
+}
+
+/*----- Other random utilities --------------------------------------------*/
+
+/* --- @regdump_freshline@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Begin a fresh line of output.
+ */
+
+void regdump_freshline(void) { putchar('\n'); }
+
+/*----- That's all, folks -------------------------------------------------*/