text: /* Subroutines for the gcc driver.
     Copyright (C) 2006 Free Software Foundation, Inc.
  
  This file is part of GCC.
  
  GCC is free software; you can redistribute it 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.
  
  GCC 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 GCC; see the file COPYING.  If not, write to
  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
  Boston, MA 02110-1301, USA.  */
  
  #include "config.h"
  #include "system.h"
  #include "coretypes.h"
  #include "tm.h"
  #include <stdlib.h>
  
  const char *host_detect_local_cpu (int argc, const char **argv);
  
  #ifdef GCC_VERSION
  #define cpuid(num,a,b,c,d) \
    asm volatile ("xchgl %%ebx, %1; cpuid; xchgl %%ebx, %1" \
  		: "=a" (a), "=r" (b), "=c" (c), "=d" (d)  \
  		: "0" (num))
  
  #define bit_CMPXCHG8B (1 << 8)
  #define bit_CMOV (1 << 15)
  #define bit_MMX (1 << 23)
  #define bit_SSE (1 << 25)
  #define bit_SSE2 (1 << 26)
  
  #define bit_SSE3 (1 << 0)
  #define bit_SSE4a (1 << 6)
  #define bit_CMPXCHG16B (1 << 13)
  
  #define bit_3DNOW (1 << 31)
  #define bit_3DNOWP (1 << 30)
  #define bit_LM (1 << 29)
  
  /* This will be called by the spec parser in gcc.c when it sees
     a %:local_cpu_detect(args) construct.  Currently it will be called
     with either "arch" or "tune" as argument depending on if -march=native
     or -mtune=native is to be substituted.
  
     It returns a string containing new command line parameters to be
     put at the place of the above two options, depending on what CPU
     this is executed.  E.g. "-march=k8" on an AMD64 machine
     for -march=native.
  
     ARGC and ARGV are set depending on the actual arguments given
     in the spec.  */
  const char *host_detect_local_cpu (int argc, const char **argv)
  {
    const char *cpu = NULL;
    enum processor_type processor = PROCESSOR_I386;
    unsigned int eax, ebx, ecx, edx;
    unsigned int max_level;
    unsigned int vendor;
    unsigned int ext_level;
    unsigned char has_mmx = 0, has_3dnow = 0, has_3dnowp = 0, has_sse = 0;
    unsigned char has_sse2 = 0, has_sse3 = 0, has_cmov = 0;
    unsigned char has_longmode = 0, has_cmpxchg8b = 0, has_sse4a=0;
    unsigned char is_amd = 0;
    unsigned int family = 0;
    bool arch;
  
    if (argc < 1)
      return NULL;
  
    arch = strcmp (argv[0], "arch") == 0;
    if (!arch && strcmp (argv[0], "tune"))
      return NULL;
  
  #ifndef __x86_64__
    /* See if we can use cpuid.  */
    asm volatile ("pushfl; pushfl; popl %0; movl %0,%1; xorl %2,%0;"
  		"pushl %0; popfl; pushfl; popl %0; popfl"
  		: "=&r" (eax), "=&r" (ebx)
  		: "i" (0x00200000));
  
    if (((eax ^ ebx) & 0x00200000) == 0)
      goto done;
  #endif
  
    processor = PROCESSOR_PENTIUM;
  
    /* Check the highest input value for eax.  */
    cpuid (0, eax, ebx, ecx, edx);
    max_level = eax;
    /* We only look at the first four characters.  */
    vendor = ebx;
    if (max_level == 0)
      goto done;
  
    cpuid (1, eax, ebx, ecx, edx);
    has_cmpxchg8b = !!(edx & bit_CMPXCHG8B);
    has_cmov = !!(edx & bit_CMOV);
    has_mmx = !!(edx & bit_MMX);
    has_sse = !!(edx & bit_SSE);
    has_sse2 = !!(edx & bit_SSE2);
    has_sse3 = !!(ecx & bit_SSE3);
    /* We don't care for extended family.  */
    family = (eax >> 8) & ~(1 << 4);
  
    cpuid (0x80000000, eax, ebx, ecx, edx);
    ext_level = eax;
    if (ext_level >= 0x80000000)
      {
        cpuid (0x80000001, eax, ebx, ecx, edx);
        has_3dnow = !!(edx & bit_3DNOW);
        has_3dnowp = !!(edx & bit_3DNOWP);
        has_longmode = !!(edx & bit_LM);
        has_sse4a = !!(ecx & bit_SSE4a);
      }
  
    is_amd = vendor == *(unsigned int*)"Auth";
  
    if (is_amd)
      {
        if (has_mmx)
  	processor = PROCESSOR_K6;
        if (has_3dnowp)
  	processor = PROCESSOR_ATHLON;
        if (has_sse2 || has_longmode)
  	processor = PROCESSOR_K8;
        if (has_sse4a)
          processor = PROCESSOR_AMDFAM10;
      }
    else
      {
        switch (family)
  	{
  	case 5:
  	  /* Default is PROCESSOR_PENTIUM.  */
  	  break;
  	case 6:
  	  processor = PROCESSOR_PENTIUMPRO;
  	  break;
  	case 15:
  	  processor = PROCESSOR_PENTIUM4;
  	  break;
  	default:
  	  /* We have no idea.  Use something reasonable.  */
  	  if (arch)
  	    {
  	      if (has_sse3)
  		{
  		  if (has_longmode)
  		    cpu = "nocona";
  		  else
  		    cpu = "prescott";
  		}
  	      else if (has_sse2)
  		cpu = "pentium4";
  	      else if (has_cmov)
  		cpu = "pentiumpro";
  	      else if (has_mmx)
  		cpu = "pentium-mmx";
  	      else if (has_cmpxchg8b)
  		cpu = "pentium";
  	      else
  		cpu = "i386";
  	    }
  	  else
  	    cpu = "generic";
  	  goto done;
  	  break;
  	}
      }
  
    switch (processor)
      {
      case PROCESSOR_I386:
        cpu = "i386";
        break;
      case PROCESSOR_I486:
        cpu = "i486";
        break;
      case PROCESSOR_PENTIUM:
        if (has_mmx)
  	cpu = "pentium-mmx";
        else
  	cpu = "pentium";
        break;
      case PROCESSOR_PENTIUMPRO:
        if (arch)
  	{
  	  if (has_sse3)
  	    {
  	      if (has_longmode)
  		{
  		  /* It is Core 2 Duo.  */
  		  cpu = "nocona";
  		}
  	      else
  		{
  		  /* It is Core Duo.  */
  		  cpu = "prescott";
  		}
  	    }
  	  else if (has_sse2)
  	    {
  	      /* It is Pentium M.  */
  	      cpu = "pentium4";
  	    }
  	  else if (has_sse)
  	    {
  	      /* It is Pentium III.  */
  	      cpu = "pentium3";
  	    }
  	  else if (has_mmx)
  	    {
  	      /* It is Pentium II.  */
  	      cpu = "pentium2";
  	    }
  	  else
  	    {
  	      /* Default to Pentium Pro.  */
  	      cpu = "pentiumpro";
  	    }
  	}
        else
  	{
  	  /* For -mtune, we default to -mtune=generic.  */
  	  cpu = "generic";
  	}
        break;
      case PROCESSOR_K6:
        if (has_3dnow)
          cpu = "k6-3";
        else
  	cpu = "k6";
        break;
      case PROCESSOR_ATHLON:
        if (has_sse)
  	cpu = "athlon-4";
        else
  	cpu = "athlon";
        break;
      case PROCESSOR_PENTIUM4:
        if (has_sse3)
  	{
  	  if (has_longmode)
  	    cpu = "nocona";
  	  else
  	    cpu = "prescott";
  	}
        else
  	cpu = "pentium4";
        break;
      case PROCESSOR_K8:
        cpu = "k8";
        break;
      case PROCESSOR_NOCONA:
        cpu = "nocona";
        break;
      case PROCESSOR_GENERIC32:
      case PROCESSOR_GENERIC64:
        cpu = "generic";
        break;
      case PROCESSOR_AMDFAM10:
        cpu = "amdfam10";
        break;
      default:
        abort ();
        break;
      }
  
  done:
    return concat ("-m", argv[0], "=", cpu, NULL);
  }
  #else
  /* If we aren't compiling with GCC we just provide a minimal
     default value.  */
  const char *host_detect_local_cpu (int argc, const char **argv)
  {
    const char *cpu;
    bool arch;
  
    if (argc < 1)
      return NULL;
  
    arch = strcmp (argv[0], "arch") == 0;
    if (!arch && strcmp (argv[0], "tune"))
      return NULL;
    
    if (arch)
      {
        /* FIXME: i386 is wrong for 64bit compiler.  How can we tell if
  	 we are generating 64bit or 32bit code?  */
        cpu = "i386";
      }
    else
      cpu = "generic";
  
    return concat ("-m", argv[0], "=", cpu, NULL);
  }
  #endif /* GCC_VERSION */
  