/*
 * LIBOIL - Library of Optimized Inner Loops
 * Copyright (c) 2003,2004 David A. Schleef <ds@schleef.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <liboil/liboilfunction.h>
#include <liboil/liboildebug.h>
#include <liboil/liboilcpu.h>
#include <liboil/liboilfault.h>
#include <liboil/liboilutils.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <time.h>

#if defined(__FreeBSD__) || defined(__APPLE__)
#include <sys/types.h>
#include <sys/sysctl.h>
#endif

#ifdef __sun
#include <sys/auxv.h>
#endif

/***** i386, amd64 *****/

#if defined(__sun)
#define USE_I386_GETISAX
#else
#define USE_I386_CPUID
#endif


#ifdef USE_I386_CPUINFO
static void
oil_cpu_i386_getflags_cpuinfo (char *cpuinfo)
{
  char *cpuinfo_flags;
  char **flags;
  char **f;

  cpuinfo_flags = get_tag_value (cpuinfo, "flags");
  if (cpuinfo_flags == NULL) {
    free (cpuinfo);
    return;
  }

  flags = strsplit(cpuinfo_flags);
  for (f = flags; *f; f++) {
    if (strcmp (*f, "cmov") == 0) {
      OIL_DEBUG ("cpu flag %s", *f);
      oil_cpu_flags |= OIL_IMPL_FLAG_CMOV;
    }
    if (strcmp (*f, "mmx") == 0) {
      OIL_DEBUG ("cpu flag %s", *f);
      oil_cpu_flags |= OIL_IMPL_FLAG_MMX;
    }
    if (strcmp (*f, "sse") == 0) {
      OIL_DEBUG ("cpu flag %s", *f);
      oil_cpu_flags |= OIL_IMPL_FLAG_SSE;
    }
    if (strcmp (*f, "mmxext") == 0) {
      OIL_DEBUG ("cpu flag %s", *f);
      oil_cpu_flags |= OIL_IMPL_FLAG_MMXEXT;
    }
    if (strcmp (*f, "sse2") == 0) {
      OIL_DEBUG ("cpu flag %s", *f);
      oil_cpu_flags |= OIL_IMPL_FLAG_SSE2;
      oil_cpu_flags |= OIL_IMPL_FLAG_MMXEXT;
    }
    if (strcmp (*f, "3dnow") == 0) {
      OIL_DEBUG ("cpu flag %s", *f);
      oil_cpu_flags |= OIL_IMPL_FLAG_3DNOW;
    }
    if (strcmp (*f, "3dnowext") == 0) {
      OIL_DEBUG ("cpu flag %s", *f);
      oil_cpu_flags |= OIL_IMPL_FLAG_3DNOWEXT;
    }
    if (strcmp (*f, "sse3") == 0) {
      OIL_DEBUG ("cpu flag %s", *f);
      oil_cpu_flags |= OIL_IMPL_FLAG_SSE3;
      oil_cpu_flags |= OIL_IMPL_FLAG_SSE2;
      oil_cpu_flags |= OIL_IMPL_FLAG_MMXEXT;
    }
    if (strcmp (*f, "ssse3") == 0) {
      OIL_DEBUG ("cpu flag %s", *f);
      oil_cpu_flags |= OIL_IMPL_FLAG_SSSE3;
      oil_cpu_flags |= OIL_IMPL_FLAG_SSE3;
      oil_cpu_flags |= OIL_IMPL_FLAG_SSE2;
      oil_cpu_flags |= OIL_IMPL_FLAG_MMXEXT;
    }

    free (*f);
  }
  free (flags);
  free (cpuinfo);
  free (cpuinfo_flags);
}
#endif

#ifdef HAVE_GCC_ASM      
static unsigned long
oil_profile_stamp_rdtsc(void)
{
	unsigned long ts;
	__asm__ __volatile__("rdtsc\n" : "=a" (ts) : : "edx");
	return ts;
}
#endif

#ifdef USE_I386_CPUID
#ifdef __i386__
static void
get_cpuid (uint32_t op, uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d)
{
/*               
  __asm__ (
      "  pushl %%ebx\n"
      "  cpuid\n"
      "  mov %%ebx, %%esi\n"
      "  popl %%ebx\n"
      : "=a" (*a), "=S" (*b), "=c" (*c), "=d" (*d)
      : "0" (op));
*/  
}
#endif

#ifdef __amd64__
static void
get_cpuid (uint32_t op, uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d)
{
  __asm__ (
      "  pushq %%rbx\n"
      "  cpuid\n"
      "  mov %%ebx, %%esi\n"
      "  popq %%rbx\n"
      : "=a" (*a), "=S" (*b), "=c" (*c), "=d" (*d)
      : "0" (op));
}
#endif

static void
test_cpuid (void *ignored)
{
  uint32_t eax, ebx, ecx, edx;

  get_cpuid (0x00000000, &eax, &ebx, &ecx, &edx);
}

static void
oil_cpu_detect_cpuid (void)
{
  uint32_t eax, ebx, ecx, edx;
  uint32_t level;
  char vendor[13] = { 0 };
  int ret;

  oil_fault_check_enable ();
  ret = oil_fault_check_try(test_cpuid, NULL);
  oil_fault_check_disable ();
  if (!ret) {
    /* CPU thinks cpuid is an illegal instruction. */
    return;
  }

  get_cpuid (0x00000000, &level, (uint32_t *)(vendor+0),
      (uint32_t *)(vendor+8), (uint32_t *)(vendor+4));

  OIL_DEBUG("cpuid %d %s", level, vendor);

  if (level < 1) {
    return;
  }

  get_cpuid (0x00000001, &eax, &ebx, &ecx, &edx);

#ifdef HAVE_GCC_ASM
  if (edx & (1<<4)) {
    _oil_profile_stamp = oil_profile_stamp_rdtsc;
  }
#endif

  /* Intel flags */
  if (edx & (1<<15)) {
    oil_cpu_flags |= OIL_IMPL_FLAG_CMOV;
  }
  if (edx & (1<<23)) {
    oil_cpu_flags |= OIL_IMPL_FLAG_MMX;
  }
  if (edx & (1<<25)) {
    oil_cpu_flags |= OIL_IMPL_FLAG_SSE;
  }
  if (edx & (1<<26)) {
    oil_cpu_flags |= OIL_IMPL_FLAG_SSE2;
    oil_cpu_flags |= OIL_IMPL_FLAG_MMXEXT;
  }
  if (ecx & (1<<0)) {
    oil_cpu_flags |= OIL_IMPL_FLAG_SSE3;
  }
  
  if (memcmp (vendor, "AuthenticAMD", 12) == 0) {
    get_cpuid (0x80000001, &eax, &ebx, &ecx, &edx);

    /* AMD flags */
    if (edx & (1<<22)) {
      oil_cpu_flags |= OIL_IMPL_FLAG_MMXEXT;
    }
    if (edx & (1<<31)) {
      oil_cpu_flags |= OIL_IMPL_FLAG_3DNOW;
    }
    if (edx & (1<<30)) {
      oil_cpu_flags |= OIL_IMPL_FLAG_3DNOWEXT;
    }

    get_cpuid (0x80000005, &eax, &ebx, &ecx, &edx);

    OIL_INFO("L1 D-cache: %d kbytes, %d-way, %d lines/tag, %d line size",
        (ecx>>24)&0xff, (ecx>>16)&0xff, (ecx>>8)&0xff, ecx&0xff);
    OIL_INFO("L1 I-cache: %d kbytes, %d-way, %d lines/tag, %d line size",
        (edx>>24)&0xff, (edx>>16)&0xff, (edx>>8)&0xff, edx&0xff);

    get_cpuid (0x80000006, &eax, &ebx, &ecx, &edx);
    OIL_INFO("L2 cache: %d kbytes, %d assoc, %d lines/tag, %d line size",
        (ecx>>16)&0xffff, (ecx>>12)&0xf, (ecx>>8)&0xf, ecx&0xff);
  }
}
#endif

#ifdef USE_I386_GETISAX
static void
oil_cpu_detect_getisax (void)
{
  uint_t ui;

  getisax (&ui, 1);

  if (ui & AV_386_CMOV) {
     oil_cpu_flags |= OIL_IMPL_FLAG_CMOV;
  }
  if (ui & AV_386_MMX) {
     oil_cpu_flags |= OIL_IMPL_FLAG_MMX;
  }
  if (ui & AV_386_SSE) {
     oil_cpu_flags |= OIL_IMPL_FLAG_SSE;
  }
  if (ui & AV_386_SSE2) {
     oil_cpu_flags |= OIL_IMPL_FLAG_SSE2;
     oil_cpu_flags |= OIL_IMPL_FLAG_MMXEXT;
  }
  if (ui & AV_386_SSE3) {
     oil_cpu_flags |= OIL_IMPL_FLAG_SSE3;
  }
  if (ui & AV_386_AMD_3DNow) {
    oil_cpu_flags |= OIL_IMPL_FLAG_3DNOW;
  }
  if (ui & AV_386_AMD_3DNowx) {
    oil_cpu_flags |= OIL_IMPL_FLAG_3DNOWEXT;
  }
  if (ui & AV_386_AMD_MMX) {
    oil_cpu_flags |= OIL_IMPL_FLAG_MMXEXT;
  }
}
#endif

/* Reduce the set of CPU capabilities detected by whatever detection mechanism
 * was chosen, according to kernel limitations.  SSE requires kernel support for
 * use.
 */
static void
oil_cpu_detect_kernel_support (void)
{
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__APPLE__)
  int ret, enabled;
  size_t len;

  len = sizeof(enabled);
  ret = sysctlbyname("hw.instruction_sse", &enabled, &len, NULL, 0);
  if (ret || !enabled) {
    oil_cpu_flags &= ~(OIL_IMPL_FLAG_SSE | OIL_IMPL_FLAG_SSE2 |
		       OIL_IMPL_FLAG_MMXEXT | OIL_IMPL_FLAG_SSE3);
  }
#elif defined(__linux__)
  /*
   * Might also want to grow a check for the old RedHat + Linux 2.2
   * unmasked SSE FPU exception bug.  Other than that, if /proc/cpuinfo
   * reported SSE, then it's safe.
   */
#elif defined(__sun)
  /* Solaris is OK */
#elif defined(__NetBSD__)
  /* NetBSD is OK */
#else
   
  OIL_WARNING("Operating system is not known to support SSE.  "
      "Assuming it does, which might cause problems");
#if 0
  oil_cpu_flags &= ~(OIL_IMPL_FLAG_SSE | OIL_IMPL_FLAG_SSE2 |
		     OIL_IMPL_FLAG_MMXEXT | OIL_IMPL_FLAG_SSE3);
#endif
#endif
}

void
oil_cpu_detect_arch(void)
{
#ifdef USE_I386_CPUID
  oil_cpu_detect_cpuid ();
#endif
#ifdef USE_I386_GETISAX
  oil_cpu_detect_getisax ();
#endif
#ifdef USE_I386_CPUINFO
  oil_cpu_detect_cpuinfo ();
#endif

  oil_cpu_detect_kernel_support ();
}


