Add support for AMD64 processors and Microsoft Windows.
[catacomb] / base / dispatch.c
index fe3dcfb..8936ea4 100644 (file)
@@ -30,6 +30,8 @@
 #include "config.h"
 
 #include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 
 #include "dispatch.h"
 
-/*----- Main code ---------------------------------------------------------*/
+/*----- Intel x86/AMD64 feature probing -----------------------------------*/
 
-#ifdef CPUFAM_X86
+#if CPUFAM_X86 || CPUFAM_AMD64
 
-#define EFLAGS_ID (1u << 21)
-#define CPUID1D_SSE2 (1u << 26)
-#define CPUID1D_FXSR (1u << 24)
-#define CPUID1C_AESNI (1u << 25)
+#  define EFLAGS_ID (1u << 21)
+#  define CPUID1D_SSE2 (1u << 26)
+#  define CPUID1D_FXSR (1u << 24)
+#  define CPUID1C_AESNI (1u << 25)
 
 struct cpuid { unsigned a, b, c, d; };
 
@@ -62,6 +64,7 @@ struct cpuid { unsigned a, b, c, d; };
  */
 
 #ifdef __GNUC__
+#  if CPUFAM_X86
 static __inline__ unsigned getflags(void)
   { unsigned f; __asm__ ("pushf; popl %0" : "=g" (f)); return (f); }
 static __inline__ unsigned setflags(unsigned f)
@@ -72,6 +75,18 @@ static __inline__ unsigned setflags(unsigned f)
           : "g" (f));
   return (ff);
 }
+#  else
+static __inline__ unsigned long getflags(void)
+  { unsigned long f; __asm__ ("pushf; popq %0" : "=g" (f)); return (f); }
+static __inline__ unsigned long long setflags(unsigned long f)
+{
+  unsigned long ff;
+  __asm__ ("pushf; pushq %1; popf; pushf; popq %0; popf"
+          : "=g" (ff)
+          : "g" (f));
+  return (ff);
+}
+#  endif
 #endif
 
 static void cpuid(struct cpuid *cc, unsigned a, unsigned c)
@@ -85,22 +100,46 @@ static void cpuid(struct cpuid *cc, unsigned a, unsigned c)
 #ifdef __GNUC__
   /* Stupid dance to detect whether the CPUID instruction is available. */
   f = getflags();
-  if (!(setflags(f |  EFLAGS_ID) & EFLAGS_ID)) return;
-  if (  setflags(f & ~EFLAGS_ID) & EFLAGS_ID ) return;
+  if (!(setflags(f |  EFLAGS_ID) & EFLAGS_ID) ||
+       setflags(f & ~EFLAGS_ID) & EFLAGS_ID) {
+    dispatch_debug("CPUID instruction not available");
+    return;
+  }
   setflags(f);
 
   /* Alas, EBX is magical in PIC code, so abuse ESI instead.  This isn't
    * pretty, but it works.
    */
+#  if CPUFAM_X86
   __asm__ ("pushl %%ebx; cpuid; movl %%ebx, %%esi; popl %%ebx"
           : "=a" (cc->a), "=S" (cc->b), "=c" (cc->c), "=d" (cc->d)
           : "a" (a) , "c" (c));
+#  elif CPUFAM_AMD64
+  __asm__ ("pushq %%rbx; cpuid; movl %%ebx, %%esi; popq %%rbx"
+          : "=a" (cc->a), "=S" (cc->b), "=c" (cc->c), "=d" (cc->d)
+          : "a" (a) , "c" (c));
+#  else
+#    error "I'm confused."
+#  endif
+  dispatch_debug("CPUID(%08x, %08x) -> %08x, %08x, %08x, %08x",
+                a, c, cc->a, cc->b, cc->c, cc->d);
+#else
+  dispatch_debug("GNU inline assembler not available; can't CPUID");
 #endif
 }
 
 static unsigned cpuid_maxleaf(void)
   { struct cpuid c; cpuid(&c, 0, 0); return (c.a); }
 
+/* --- @cpuid_features_p@ --- *
+ *
+ * Arguments:  @unsigned dbits@ = bits to check in EDX
+ *             @unsigned cbits@ = bits to check in ECX
+ *
+ * Returns:    Nonzero if all the requested bits are set in the CPUID result
+ *             on leaf 1.
+ */
+
 static int cpuid_features_p(unsigned dbits, unsigned cbits)
 {
   struct cpuid c;
@@ -109,6 +148,14 @@ static int cpuid_features_p(unsigned dbits, unsigned cbits)
   return ((c.d & dbits) == dbits && (c.c & cbits) == cbits);
 }
 
+/* --- @xmm_registers_available_p@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Nonzero if the operating system has made the XMM registers
+ *             available for use.
+ */
+
 static int xmm_registers_available_p(void)
 {
 #ifdef __GNUC__
@@ -117,6 +164,7 @@ static int xmm_registers_available_p(void)
    * XMM registers are actually alive.
    */
   if (!cpuid_features_p(CPUID1D_FXSR, 0)) return (0);
+#  if CPUFAM_X86
   __asm__ ("movl %%esp, %%edx; subl $512, %%esp; andl $~15, %%esp\n"
           "fxsave (%%esp)\n"
           "movl 160(%%esp), %%eax; xorl $0xaaaa5555, 160(%%esp)\n"
@@ -127,14 +175,57 @@ static int xmm_registers_available_p(void)
           : "=a" (f)
           : /* no inputs */
           : "%ecx", "%edx");
+#  elif CPUFAM_AMD64
+  __asm__ ("movq %%rsp, %%rdx; subq $512, %%rsp; andq $~15, %%rsp\n"
+          "fxsave (%%rsp)\n"
+          "movl 160(%%rsp), %%eax; xorl $0xaaaa5555, 160(%%rsp)\n"
+          "fxrstor (%%rsp); fxsave (%%rsp)\n"
+          "movl 160(%%rsp), %%ecx; movl %%eax, 160(%%rsp)\n"
+          "fxrstor (%%rsp); movq %%rdx, %%rsp\n"
+          "xorl %%ecx, %%eax"
+          : "=a" (f)
+          : /* no inputs */
+          : "%ecx", "%rdx");
+#  else
+#    error "I'm confused."
+#  endif
+  dispatch_debug("XMM registers %savailable", f ? "" : "not ");
   return (f);
 #else
+  dispatch_debug("GNU inline assembler not available; can't check for XMM");
   return (0);
 #endif
 }
 
 #endif
 
+/*----- External interface ------------------------------------------------*/
+
+/* --- @dispatch_debug@ --- *
+ *
+ * Arguments:  @const char *fmt@ = a format string
+ *             @...@ = additional arguments
+ *
+ * Returns:    ---
+ *
+ * Use:                Writes a formatted message to standard output if dispatch
+ *             debugging is enabled.
+ */
+
+void dispatch_debug(const char *fmt, ...)
+{
+  va_list ap;
+  const char *e = getenv("CATACOMB_CPUDISPATCH_DEBUG");
+
+  if (e && *e != 'n' && *e != '0') {
+    va_start(ap, fmt);
+    fputs("Catacomb CPUDISPATCH: ", stderr);
+    vfprintf(stderr, fmt, ap);
+    fputc('\n', stderr);
+    va_end(ap);
+  }
+}
+
 /* --- @check_env@ --- *
  *
  * Arguments:  @const char *ftok@ = feature token
@@ -183,30 +274,41 @@ static int IGNORABLE check_env(const char *ftok)
 
 #include <stdio.h>
 
+static int IGNORABLE
+  feat_debug(const char *ftok, const char *check, int verdict)
+{
+  if (verdict >= 0) {
+    dispatch_debug("feature `%s': %s -> %s", ftok, check,
+                  verdict ? "available" : "absent");
+  }
+  return (verdict);
+}
+
 int cpu_feature_p(int feat)
 {
   int IGNORABLE f;
   IGNORE(f);
-#define CHECK_ENV(ftok)                                                        \
-  do { if ((f = check_env(ftok)) >= 0) return (f); } while (0)
+#define CASE_CPUFEAT(feat, ftok, cond) case CPUFEAT_##feat:            \
+  if ((f = feat_debug(ftok, "environment override",                    \
+                     check_env(ftok))) >= 0)                           \
+    return (f);                                                                \
+  else                                                                 \
+    return (feat_debug(ftok, "runtime probe", cond));
 
   switch (feat) {
-#ifdef CPUFAM_X86
-    case CPUFEAT_X86_SSE2: {
-      CHECK_ENV("x86:sse2");
-      return (xmm_registers_available_p() &&
-             cpuid_features_p(CPUID1D_SSE2, 0));
-    }
-    case CPUFEAT_X86_AESNI: {
-      check_env("x86:aesni");
-      return (xmm_registers_available_p() &&
-             cpuid_features_p(CPUID1D_SSE2, CPUID1C_AESNI));
-    }
+#if CPUFAM_X86 || CPUFAM_AMD64
+    CASE_CPUFEAT(X86_SSE2, "x86:sse2",
+                xmm_registers_available_p() &&
+                cpuid_features_p(CPUID1D_SSE2, 0));
+    CASE_CPUFEAT(X86_AESNI, "x86:aesni",
+                xmm_registers_available_p() &&
+                cpuid_features_p(CPUID1D_SSE2, CPUID1C_AESNI));
 #endif
     default:
+      dispatch_debug("denying unknown feature %d", feat);
       return (0);
   }
-#undef CHECK_ENV
+#undef CASE_CPUFEAT
 }
 
 /*----- That's all, folks -------------------------------------------------*/