
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>


// #define TRACE emit
#define TRACE

typedef int BOOL;
#define TRUE 1
#define FALSE 0

#define SINGLE 0
#define DOUBLE 1
#define COMPLEX_SINGLE 2
#define COMPLEX_DOUBLE 3

#define BETAN1 -1
#define BETA0 0
#define BETA1 1
#define BETAX 2

#define PARAMETER 0
#define USE_KB -1

#define ALIGNED 0
#define ALIGN_NALIGN 1
#define NALIGN_ALIGN 2
#define NALIGNED 3

#define CACHE_LINE_SIZE 64

/* Prefetch Options */
typedef struct
{
   BOOL ACols;
   BOOL ABlock;
   BOOL BRows;
   BOOL fetchC;
} Prefetch;


typedef struct
{
	int a;
	int b;
	int k;
   int mb;
   int nb;
   int kb;
} Unrolling;


typedef struct
{
	int size;
	int vector_stride;
	int vector_length_bytes;
	int shift;
	int type;
   char cType;
   char type_name[7];
   char aLoad[25];
   char uLoad[25];
	char sLoad[25];
   char aStore[25];
   char uStore[25];
	char sStore[25];
   char intrinsic[7];
} Element;

typedef struct
{
	int mainLoopType;
	int lda;
	int ldb;
   int ldc;
	BOOL verifyUnrollings;
	BOOL treatLoadsAsFloat;
	BOOL treatStoresAsFloat;
   int beta;

   FILE* outputLocation;
} GlobalOpts;

/* Load options from the command line */
void loadOptions( int argc, char **argv );
static void loadInt( char* tag, int *value, int argc, char** argv );
static void loadFloat( char* tag, float *value, int argc, char** argv );
static void loadBool( char* tag, int *value, int argc, char** argv );
static char* loadString( char* tag, int argc, char** argv );
static int requestHelp( int argc, char** argv );
static void convertElementType( char specifier );
static void setOutputLocation( char* file );
static void printHelp();
int numArgsProcessed = 0;


void getNBString( char* out );
void getMBString( char* out );
void getKBString( char* out );



/* These functions print the sections of the kernel */
void printMainLoops( int alignmentOfC, char *name );
void printILoop( int alignmentOfC, BOOL prefetchA, BOOL prefetchB );

void printPreamble();
void printIntro();
void printBody( int simple );

/* These functions print the unrollings of the k loop */
void printAllKUnrollings( BOOL prefetchA, BOOL prefetchB );
void k_unrolling0();
void k_unrolling( int delta, const char* base );
void printKRolled( BOOL prefetchA, BOOL prefetchB );
void printPartiallyUnrolledK( BOOL prefetchA, BOOL prefetchB );
void printFullyUnrolledK( BOOL prefetchA, BOOL prefetchB );

/* This compresses the vectors back to scalars */
void printScalarCompression();
void printScalarCompressionSingle();
void storeResults( int alignmentOfC );
void applyBeta();

/* These functions print the code to the console *
 * and manage the indention */
void emit( const char *fmt, ...);
void emitCat( const char *fmt, ...);
void indent( int delta );


/*
 * Keeps track of the number of indents in the emitted code.
 * This allows the system to properly nest braces in the output.
 */
int tabwidth=0;

/*
 * Create some shorthand for the load/store instructions
 * These are aliased to strings because one option adds a
 * typecast to the load/store instructions.  It wouldn't do
 * to have to test that option all the time....
 */
char nb[20];
char mb[20];




/* Options Variables */
Prefetch prefetch;
Unrolling unroll;
Element element;
GlobalOpts options;



int main(
      int argc,   /* Number of command line args */
      char** argv /* Array of command line args */
)
/*
 * Prints an implementation of GEMM given the parameters on the
 * command line.
 */
{
   loadOptions( argc, argv );  /* Read options from the command line */

	if( unroll.nb == USE_KB )
		assert( unroll.kb % unroll.a == 0 );
	else if( unroll.nb != PARAMETER )
		assert( unroll.nb % unroll.a == 0 );

   printPreamble();  /* Emit includes, defines, and variables */


/*
 * Emit three cases: Beta = 0, Beta = 1, Beta != 0,1
 * This allows conditional compilation of complex calculaions
 * which need all three cases.
 *
 * Each case includes a call to printIntro() which emits the
 * function header, and a call to printBody() which emits the
 * alignment cases as needed.
 */

   if( element.type == COMPLEX_SINGLE || element.type == COMPLEX_DOUBLE )
   {
      emit( "#ifdef BETA0\n" );
      options.beta = BETA0;
      printIntro();
      printBody( options.mainLoopType );

      emit( "#elif defined( BETA1 )\n" );
      options.beta = BETA1;
      printIntro();
      printBody( options.mainLoopType );

      emit( "#else\n" );
      options.beta = BETAX;
      printIntro();
      printBody( options.mainLoopType );

      emit( "#endif\n" );
   } else {
      printIntro();
      printBody( options.mainLoopType );
   }

   assert( tabwidth == 0 );

   return 0;
}



void printPreamble()
/*
 * Print setup information such as CPP includes, data type defininitions,
 * and defines used to name constants.
 */
{

   emit( "/* *************\n" );
   emit( " *  Selected Options:\n" );
   emit( " * prefetch.ACols = %d\n", prefetch.ACols );
   emit( " * prefetch.ABlock = %d\n", prefetch.ABlock );
   emit( " * prefetch.BRows= %d\n", prefetch.BRows );
   emit( " * prefetch.fetchC = %d\n", prefetch.fetchC );
   emit( " * unroll.a = %d\n", unroll.a );
   emit( " * unroll.b = %d\n", unroll.b );
   emit( " * unroll.k = %d\n", unroll.k );
   emit( " * options.lda = %d\n", options.lda );
   emit( " * options.ldb = %d\n", options.ldb );
   emit( " * options.ldc = %d\n", options.ldc );
   emit( " * element.size = %d\n", element.size );
   emit( " * element.vector_stride = %d\n", element.vector_stride );
   emit( " * element.vector_length_bytes = %d\n", element.vector_length_bytes );
   emit( " * element.shift = %d\n", element.shift );
   emit( " ************** */\n" );


/* Store some strings to represent the NB and MB constants */
   getNBString( nb );
   getMBString( mb );


/* Print includes */
   emit("#include <stdio.h>\n" );
   emit("#include <stdint.h>\n" );
   emit("#include <pmmintrin.h>\n" );
   emit("#include <assert.h>\n" );
   emit("\n");


/* Emit some defines, so that the code is readable */
   emit( "#define I_UNROLL %d\n", unroll.a );
   emit( "#define J_UNROLL %d\n", unroll.b );


/* Setup the prefetch options */
   emit("/* Is prefetched data written or just read? */\n");
   emit( "#define PF_READONLY 0\n" );
   emit( "#define PF_READWRITE 1\n" );
   emit( "#define PF_NO_REUSE 0\n" );

   emit("\n/* Default temporality of cache prefetch (1-3) */\n");
   emit( "#define PF_DEF 2\n" );
   emit( "#define CACHE_LINE_SIZE %d\n", CACHE_LINE_SIZE );
}


void printIntro()
/*
 * Emit code that deduces constants used in this configuration,
 * data types, and the function prototype
 */
{
   int a;
   int b;
   int x;


   emit( "#define TYPE %s\n", element.type_name );
   emit("void ATL_USERMM( const int M, const int N, const int K,\n");
   emit("                 const TYPE alpha, const TYPE *A, const int lda,\n");
   emit("                 const TYPE *B, const int ldb,\n");
   emit("                 const TYPE beta, const TYPE *C, const int ldc )\n");
   emit("{\n");

   indent( 1 );

   emit("register int i, j, k;\n");
   emit("\n");


/* Create variables for each of the vector registers */
   emit( "/* Vector registers to hold the elements of C */\n" );
   for( b=0; b<unroll.b; ++b )
   {
      emit( "%s c%d_0", element.intrinsic, b );
      for( a=1; a<unroll.a; ++a )
      {
         emitCat( ", c%d_%d", b, a );
      }
      emitCat( ";\n" );
   }

/*
 * If beta must be applied, create some registers for the
 * result
 */
   if( options.beta != BETA0 )
   {
      emit( "/* Vector register to hold C*beta */\n" );
      for( b=0; b<unroll.b; ++b )
      {
         int remaining = unroll.a;
         int a = 0;
         emit( "%s ", element.intrinsic );

/*       Create registers that include the entire stride */
         for( ; remaining > element.vector_stride;
                remaining -= element.vector_stride )
         {
            emitCat( "bc%d_%d", b, a );
            a += element.vector_stride;
            if( remaining != element.vector_stride )
            {
               emitCat( ", " );
            }
         }
/*       Create registers for elements that do not fit in the stride */
         for( ; remaining > 0; --remaining )
         {
            emitCat( "bc%d_%d", b, a );
            if( remaining != 1 )
            {
               emitCat( ", " );
            }
            ++a;
         }

         emitCat( ";\n" );
      }
   }

   emit( "/* Temporary vector registers for use in inner loop */\n" );
   emit("%s temp; \n", element.intrinsic );

   for( x=0; x<element.vector_stride; ++x )
   {
      emit("%s temp%d;  \n", element.intrinsic, x );
   }


   if( options.verifyUnrollings == TRUE )
   {
      emit("assert(M%%%d==0);\n", unroll.a );
      emit("assert(N%%%d==0);\n", unroll.b );
   }

/* Start prefetching from B */
   if( prefetch.BRows )
   {
      emit("__builtin_prefetch( B, PF_READONLY, PF_DEF );\n");
   }

/* Load the beta factor so it will be ready to apply later */
   if( options.beta == BETAX || options.beta == BETAN1 )
   {
      if( element.type == SINGLE || element.type == COMPLEX_SINGLE )
      {
         emit("const %s betaV = _mm_set1_ps( beta ); \n", element.intrinsic );
      } else {
         emit("const %s betaV = _mm_set1_pd( beta ); \n", element.intrinsic );
      }
   }

   emit("/* Pointer adjustments */  \n");

   if( options.ldb == USE_KB )
   {
      emit( "register const int ldb_bytes = KB*%d;\n", element.size );
   }
   else if( options.ldb == PARAMETER )
   {
      emit("register const int ldb_bytes = ldb << %d;\n", element.shift );
   } else {
      emit("register const int ldb_bytes = %d << %d;\n", options.ldb, element.shift);
   }

   if( unroll.b > 2 )
   {
      emit("register const int ldb_bytes3 = ldb_bytes*3;\n" );
   }


   switch( options.lda )
   {
   case PARAMETER:
      emit("register const int lda_bytes = lda << %d;\n",
            element.shift );
      emit("register const int lda_bytes3 = lda_bytes * 3;\n");
      break;
   case USE_KB:
      emit( "const int lda_bytes = KB*%d;\n", element.size );
      break;

   default:
      emit( "const int lda_bytes = %d*%d;\n", element.size,
            options.lda );
   }


/* Since complex matricies are strided by 2, ldc is off by a factor of 2 */
  if( element.type == COMPLEX_SINGLE || element.type == COMPLEX_DOUBLE )
  {
      switch( options.ldc )
      {
      case PARAMETER:
         emit("register const int ldc_bytes = ldc << %d;\n", element.shift+1);
         break;

      case USE_KB:
         emit( "register const int ldc_bytes = 2*KB*%d;\n", element.size );
         break;

      default:
         emit( "register const int ldc_bytes = 2*%d*%d\n",
               element.size, options.ldc );
      }
      emit("\n");
   }
   else
   {
      switch( options.ldc )
      {
      case PARAMETER:
         emit("register const int ldc_bytes = ldc << %d;\n", element.shift);
         break;

      case USE_KB:
         emit( "register const int ldc_bytes = KB*%d;\n", element.size );
         break;

      default:
         emit( "register const int ldc_bytes = %d*%d\n",
               element.size, options.ldc );
      }
      emit("\n");
   }


   emit("register void const *B0_off = (void*)B;\n");
   emit("   \n");

   if( prefetch.ABlock )
   {
      emit("register void const * prefetchABlock = " );
      if( options.lda == PARAMETER )
         emitCat(" (void*)(A + %s*lda); \n", nb );
      else if( options.lda == USE_KB )
         emitCat(" (void*)(A + %s*KB); \n", nb );
      else
         emitCat(" (void*)(A + %s*%d);\n", nb, options.lda );
   }

   if( prefetch.ACols )
   {
      emit("register void const * prefetchACols = " );
      if( options.lda == PARAMETER )
         emitCat(" (void*)(A + %s*lda); \n", nb );
      else if( options.lda == USE_KB )
         emitCat(" (void*)(A + %s*KB); \n", nb );
      else
         emitCat(" (void*)(A + %s*%d);\n", nb, options.lda );
   }

   if( prefetch.BRows )
   {
      emit("register void const *prefetchB = " );
      if( unroll.mb == PARAMETER )
         emitCat(" (void*)(B + MB*ldb);\n" );
      else if( unroll.mb == USE_KB )
         emitCat(" (void*)(B + %d*ldb);\n", unroll.kb );
      else
         emitCat(" (void*)(B + %d*ldb);\n", unroll.mb );
   }

   if( prefetch.ACols )
   {
      emit("__builtin_prefetch( prefetchACols, PF_READONLY, PF_DEF );\n");
   }

   if( prefetch.BRows )
   {
      emit("__builtin_prefetch( prefetchB, PF_READONLY, PF_DEF );\n");
   }

   emit("\n");

   emit( "/* Unroll A */\n");
   emit( "%s A0", element.intrinsic );
   for( a=1; a < unroll.a; ++a )
      emitCat( ", A%d", a );
   emitCat( ";\n" );

   emit( "/* Unroll B */\n" );
   emit( "%s B0", element.intrinsic );
   for( b=1; b < unroll.b; ++b )
      emitCat( ", B%d", b );
   emitCat( ";\n" );


   emit("\n\n");

   if( options.lda == PARAMETER )
      emit("register const int unroll_a = I_UNROLL*lda_bytes;\n");
   else if( options.lda == USE_KB )
      emit("register const int unroll_a = I_UNROLL*KB*%d;\n", element.size );
   else
      emit( "register const int unroll_a = I_UNROLL*%d*%d;\n",
            options.lda, element.size );

   emit("register void* cPtr = (void*)(C);\n" );
   emit("\n\n");

}


void printBody
(
  int simple /* If 1, unaligned case is assumed, else, generate aligned cases */
)
/*
 * Generate the I,J,K loops, accounting for the possibility that 4 loops
 * are needed to account for the aligned, unaligned, and two alternating
 * alignment cases.
 */
{
/* MAIN LOOPS */

   if( simple == 1 )
   {
      printMainLoops( NALIGNED, "Non aligned" );
   }
   else
   {
      emit("const intptr_t ci = (intptr_t)cPtr;\n");
      emit("if( (ci + 15) >> %d << %d == ci )\n", element.shift, element.shift );
      emit("{\n" );
         indent(1);
         emit("if( ldc %% 2 == 0 )\n" );
         emit("{\n" );
            indent( 1 );
            printMainLoops( ALIGNED, "C Aligned" );
            indent(-1 );
         emit("} else {\n" );
            indent( 1 );
            printMainLoops( ALIGN_NALIGN, "C Aligned/Nonaligned columns" );
            indent(-1 );
         emit("} \n" );
         indent(-1);
      emit("} else { \n");
         indent( 1);
         emit("if( ldc %% 2 == 0 )\n" );
         emit("{\n");
            indent( 1 );
            printMainLoops( NALIGNED, "C Nonaligned" );
            indent(-1 );
         emit("} else {\n");
            indent( 1 );
            printMainLoops( NALIGN_ALIGN, "C Nonaligned/Aligned columns" );
            indent(-1 );
         emit("}\n");
         indent(-1);
      emit("}\n" );
   }
   indent(-1);
   emit("}\n" );
   return;
}


char* ldaOffset
(
 int times  /* How far to offset from the base value? */
)
/*
 * Returns a compilable code string that will evaluate to
 * a byte offset in terms of lda.
 * RETURNS: char* describing the offset.
 */
{
   if( options.lda == USE_KB )
   {
      char *out = malloc( 255 );
      if( times > 0 )
      {
         sprintf( out, "A0_off + %d*KB*%d", times, element.size );
      } else {
         sprintf( out, "A0_off" );
      }
      return out;
   }
   else if( options.lda == PARAMETER )
   {
      switch( times )
      {
         case 0: return "A0_off";
         case 1: return "A0_off + lda_bytes";
         case 2: return "A0_off + 2*lda_bytes";
         case 3: return "A3_off";
         case 4: return "A0_off + 4*lda_bytes";
         case 5: return "A3_off + 2*lda_bytes";
         case 6: return "A0_off + 2*lda_bytes3";
         case 7: return "A3_off + 4*lda_bytes";
         case 8: return "A0_off + 8*lda_bytes";
         case 9: return "A3_off + 2*lda_bytes3";
         default:
         {
            char *out = malloc( 255 );
            sprintf( out, "A0_off + %d*lda_bytes", times );
            return out;
         }
      }
   }
   else
   {
      char *out = malloc( 255 );
      sprintf( out, "A0_off + %d*%d", times, options.lda );
   }
}

char* ldbOffset
(
 int times  /* Distance to offset */
)
/*
 * Returns a compilable code string that will evaluate to
 * a byte offset in terms of ldb.
 * RETURNS: char* describing the offset.
 */
{
   if( options.ldb == USE_KB )
   {
      char *out = malloc( 255 );
      if( times > 0 )
      {
         sprintf( out, "B0_off + %d*KB*%d", times, element.size );
      } else {
         sprintf( out, "B0_off" );
      }
      return out;
   } else {
      switch( times )
      {
      case 0: return "B0_off";
      case 1: return "B0_off + ldb_bytes";
      case 2: return "B0_off + 2*ldb_bytes";
      case 3: return "B0_off + ldb_bytes3";
      case 4: return "B0_off + 4*ldb_bytes";
      case 6: return "B0_off + 2*ldb_bytes3";
      default:
         {
            char *out = malloc( 255 );
            sprintf( out, "B0_off + %d*ldb_bytes", times );
            return out;
         }
      }
   }
}



void k_unrolling0()
/*
 * Emit code for the initial iteration of the K loop.
 * This iteration is special because it does not need to
 * accumulate, it only needs to initialize the scalar
 * expansion registers.
 */
{
   int a, b;
   emit( "/* K_Unrolling: 0 */\n" );
   emit( "B0 = %s( %s );\n", element.aLoad, ldbOffset(0) );


   for( a=0; a<unroll.a; ++a )
   {
      char* deltaLDA = ldaOffset( a );
      emit( "A%d = %s( %s );\n", a, element.aLoad, deltaLDA );
      emit( "c0_%d = _mm_mul_p%c( B0, A%d );\n", a, element.cType, a );
   }

   for( b=1; b<unroll.b; ++b )
   {
      char* deltaLDB = ldbOffset(b);
      emit( "B0 = %s( %s );\n", element.aLoad, deltaLDB );

      for( a=0; a<unroll.a; ++a )
      {
         emit("c%d_%d = _mm_mul_p%c( B0, A%d );\n", b, a, element.cType, a);
      }
   }
}



void k_unrolling
(
  int delta,    /* Number of bytes to offset during this unrolling */
  const char *base  /* TODO */
)
/* Emit code that performs an unrolling step of the K loop.
 * This function will be called repeatedly, increasing the delta value
 * to account for the unrolling
 */
{
   int a, b;

   assert( delta < unroll.kb*element.size );
   assert( delta % element.size == 0 );

   emit( "/* K_Unrolling: %d */\n", delta );

   for( b=0; b<unroll.b; ++b )
   {
      if( delta > 0 )
      {
         emit("B%d = %s( %s %s + (%d) );\n",
            b, element.aLoad, base, ldbOffset( b ), delta );
      } else {
          emit("B%d = %s( %s %s );\n",
            b, element.aLoad, base, ldbOffset( b ) );
      }
   }

   for( b=0; b<unroll.b; ++b )
   {
      for( a=0; a<unroll.a; ++a )
      {
         if( b == 0 )
         {
            if( delta > 0 )
            {
               emit("A%d = %s( %s %s + (%d) );\n",
                  a, element.aLoad, base, ldaOffset(a), delta );
            } else {
               emit("A%d = %s( %s %s );\n",
                  a, element.aLoad, base, ldaOffset(a) );
            }
         }
         emit("temp = _mm_mul_p%c( B%d, A%d );\n", element.cType, b, a );

         emit("c%d_%d = _mm_add_p%c( temp, c%d_%d );\n",
               b, a, element.cType, b, a );
      }
   }
}




void printAllKUnrollings
(
  BOOL prefetchA,  /* Prefetch A during this call? */
  BOOL prefetchB   /* Prefetch B during this call? */
)
{

	TRACE( "printf(\"Starting to unroll\\n\");\n" );

	if( unroll.k == 1 )
   {
      printKRolled( prefetchA, prefetchB );
   }
   else if( unroll.k < unroll.kb )
   {
      printPartiallyUnrolledK( prefetchA, prefetchB );
   }
   else
   {
      printFullyUnrolledK( prefetchA, prefetchB );
   }


   if( prefetchA && prefetch.ABlock )
   {

      const int numABlockPrefetches =
         unroll.a * unroll.b * element.size / CACHE_LINE_SIZE + 1;
      emit("prefetchABlock += %d*pfBlockDistance;\n", numABlockPrefetches );
   }

   if( prefetchB && prefetch.BRows )
   {
      emit( "prefetchB += J_UNROLL*ldb_bytes;\n" );
   }

}

void printFullyUnrolledK(
      BOOL prefetchA,  /* Allow prefetches of A? */
      BOOL prefetchB   /* Allow prefetches of B? */
)
/*
 * Print the K loop fully unrolled.
 */
{
   int prefetchB_counter = 0;
   int blockACounter = 0;
   int pfColumn = 0;
   int prefetchACols = 0;
   int k;

   const int numABlockPrefetches =
      unroll.a * unroll.b * element.size / CACHE_LINE_SIZE + 1;

   const int numAColPrefetches =
      unroll.a * element.size / CACHE_LINE_SIZE + 1;

/*
 * (Number of b unrolls: so we can fetch one line each time)
 * (KB * element.size): number of bytes to fetch
 */
   const int numBPrefetches =
      unroll.b * unroll.kb * element.size / CACHE_LINE_SIZE + 1;

   const int prefetchBDelta =
      unroll.kb * element.size / numBPrefetches;

   k_unrolling0(); /* Specialize first unrolling */

/*
 * Emit unrollings by vector size
 */
   for( k=element.vector_stride; k<unroll.kb; k+=element.vector_stride )
   {
/*
 *    Prefetch an element from the next block of A
 */
      if( prefetch.ABlock && prefetchA && blockACounter < numABlockPrefetches )
      {
         emit("/* Prefetch one element from the next block of A */\n");
         emit("__builtin_prefetch( prefetchABlock + %d*pfBlockDistance,"
              "PF_READONLY, PF_DEF );\n", blockACounter );
         blockACounter++;
      }

      k_unrolling( k * element.size, "" );

      if( prefetchB && prefetchB_counter < numBPrefetches )
      {
         int row = prefetchB_counter % unroll.b;

         emit( "__builtin_prefetch( prefetchB + %d*ldb_bytes + %d,"
               "PF_READONLY, PF_DEF);\n",
               row, prefetchB_counter / unroll.b * prefetchBDelta );

         prefetchB_counter++;
      }


/*
 *    Prefetch some columns of A further along
 */
      if( prefetch.ACols )
      {
         switch( options.lda )
         {
            case USE_KB:
            emit( "__builtin_prefetch( prefetchACols+%d+KB*%d,"
                  "PF_READONLY, PF_DEF );\n",
                  prefetchACols, pfColumn*element.size );
            break;

            case PARAMETER:
            emit( "__builtin_prefetch( prefetchACols+%d+lda_bytes*%d,"
                  "PF_READONLY, PF_DEF );\n",
                  prefetchACols, pfColumn );
            break;

            default:
            emit( "__builtin_prefetch( prefetchACols+lda_bytes*%d+%d,"
                  "PF_READONLY, PF_DEF );\n",
                  pfColumn, prefetchACols );
         }

      pfColumn++;
         if( pfColumn == unroll.a )
         {
            prefetchACols += CACHE_LINE_SIZE;
            pfColumn = 0;
         }
      }
   }
}

void printPartiallyUnrolledK
(
   BOOL prefetchA,  /* Allow prefetches of A? */
   BOOL prefetchB   /* Allor prefetches of B? */
)
/*
 * Print a partially unrolled K loop, only used when
 * K != KB, otherwise the fully unrolled k loop function
 * is called.
 *
 * The prefetch parameters allow prefetching on this iteration,
 * but do not require it.  This allows separate passes to enable
 * prefetching on each input (For peeling, etc.).
 */
{
   int k;

	TRACE( "printf(\"Starting to unroll (2)\\n\");\n" );
   k_unrolling0();
	TRACE( "printf(\"-- (2) --\\n\");\n" );
   int prefetchB_counter = 0;
   emit( "/* k unroll factor: %d */\n", unroll.k );



	for( k=element.vector_stride; k<unroll.kb%unroll.k;
        k+=element.vector_stride )
   {
      int pf;

		TRACE( "printf(\"Unrolling %d\\n\");\n", k );
      k_unrolling( k * element.size, "" );


      if( prefetchB && prefetchB_counter < unroll.b )
      {
         emit( "__builtin_prefetch( prefetchB + %d*KB, PF_READONLY, PF_DEF );"
               "\n", prefetchB_counter );
         emit( "prefetchB += CACHE_LINE_SIZE;\n" );
         prefetchB_counter++;
      }
   }



	TRACE( "printf(\"-- 3 -- \\n\");\n" );
   emit( "for( k=%d; k<KB; k+=%d)\n", k, unroll.k );
   emit( "{\n" );
   indent( 1 );

   for( k=0; k<unroll.k; k+=element.vector_stride )
   {
      char offset[100];
		TRACE( "printf(\"-- 3 [%d] -- \\n\");\n", k );
      sprintf( offset, "k*%d + ", element.size );
      k_unrolling( k * element.size, offset );
   }

	TRACE( "printf(\"xx 3 -- \\n\");\n" );

	indent(-1);
   emit( "}\n" );
}


void printKRolled
(
   BOOL prefetchA,  /* Allow prefetch of A */
   BOOL prefetchB   /* Allow prefetch of B */
)
/* Print a rolled K Loop */
{
   char base[100];

   if( prefetchB )
   {
      int k;
      for( k=0; k<unroll.b; ++k )
      {
         emit( "__builtin_prefetch( prefetchB + %d*KB, PF_READONLY, PF_DEF );"
               "\n", k );
         emit( "prefetchB += CACHE_LINE_SIZE;\n" );
      }
   }



/*
 *    If prefetching is allowed on this iteration, and it is
 *    globally enabled, prefetch an element from the next block of A
 */
   if( prefetchA && prefetch.ABlock )
   {
      emit("/* Prefetch one element from the next block of A */\n");
      emit("__builtin_prefetch( prefetchABlock,PF_READONLY,PF_DEF );\n");
      emit("prefetchABlock += pfBlockDistance;\n");
      emit("\n");
   }

   k_unrolling0();  /* Print the initial unrolling */

/*
 * Print the rolled K loop, adjusting for the peeled iteration
 */
   emit( "for( k=%d; k<KB; k+=%d )\n",
        element.vector_stride, element.vector_stride );
   emit( "{\n" );
   indent(1);
   sprintf( base, "k*%d + ", element.size );
   k_unrolling( 0, base );
   indent(-1);
   emit( "}\n" );
}



void printScalarCompressionSingle()
/*
 * Print the scalar compression routine for single precision.
 * This will take the four floats in the vector unit and combine
 * them into one.  If possible, four vector units will be compressed
 * into one vector that can be written directly to memeory.
 */
{
   int a, b;
   emit("/* Single Scalar Compression */\n" );
   for( b=0; b<unroll.b; ++b )
   {
      int remaining;
      remaining = unroll.a;
      a = 0;
      for( ; remaining>=4; remaining-=4 )
      {
/*
 *       -> [c0a+c0b, c0c+c0d, c1a+c1b, c1c+c1d]
 *       -> [c2a+c2b, c2c+c2d, c3a+c3v, c3c+c3d]
 *       -> [c0a+c0b+c0c+c0d, c1a+c1b+c1c+c1d,
 *           c2a+c2b+c2c+c2d, c3a+c3b+c3c+c3d ]
 */
         emit( "c%d_%d = _mm_hadd_ps( c%d_%d, c%d_%d );\n", b, a, b, a, b,a+1 );
         emit( "c%d_%d = _mm_hadd_ps( c%d_%d, c%d_%d );\n", b, a+2, b, a+2, b, a+3 );
         emit( "c%d_%d = _mm_hadd_ps( c%d_%d, c%d_%d );\n", b, a, b, a,b, a+2);
         emit( "\n" );
         a += 4;
      }
      for( ; remaining > 0; remaining-- )
      {
         emit( "/* additional remaining step */\n" );
         emit( "c%d_%d = _mm_hadd_ps( c%d_%d, c%d_%d );\n", b, a, b, a, b, a );
         emit( "c%d_%d = _mm_hadd_ps( c%d_%d, c%d_%d );\n", b, a, b, a, b, a );
         a += 1;
      }
   }
}


void printScalarCompression()
/*
 * Emit the scalar compression algorithm for double precision
 */
{
   int a, b;

   emit("/* Combine scalar expansion back to scalar */\n");

/*
 * If I is unrolled an even number of times, the horizontal adds
 * will have no cleanup case (for doubles)
 */
   if( unroll.a % element.vector_stride == 0 )
   {
      for( b=0; b<unroll.b; ++b )
      {
         for( a=0; a<unroll.a; a+=2 )
         {
            emit("c%d_%d = _mm_hadd_p%c( c%d_%d, c%d_%d );\n",
                  b, a, element.cType, b, a, b, a+1 );
         }
      }
   }

/*
 * There are not an even number of vectors in this store
 */
   else
   {
      emit( "/* handling uneven case */\n" );
      for( b=0; b<unroll.b; ++b )
      {
         for( a=0; a<unroll.a-1; a+=2 )
         {
            if( element.type == SINGLE || element.type == COMPLEX_SINGLE  )
            {
            emit("c%d_%d = _mm_hadd_p%c( c%d_%d, c%d_%d );\n",
                  b, a, element.cType, b, a, b, a+1 );
            emit("c%d_%d = _mm_hadd_p%c( c%d_%d, c%d_%d );\n",
                  b, a, element.cType, b, a, b, a+1 );
            } else {
               emit( "/* double */\n" );
               emit("c%d_%d = _mm_hadd_p%c( c%d_%d, c%d_%d );\n",
                     b, a, element.cType, b, a, b, a+1 );
            }
         }

         if( element.type == SINGLE || element.type == COMPLEX_SINGLE )
         {
/*
 *       Cleanup case, run when there are not an even number of vectors.
 */
         emit("c%d_%d = _mm_hadd_p%c( c%d_%d, c%d_%d );\n",
               b, a, element.cType, b, a, b, a );
         emit("c%d_%d = _mm_hadd_p%c( c%d_%d, c%d_%d );\n",
               b, a, element.cType, b, a, b, a );
         } else {
         emit("c%d_%d = _mm_hadd_p%c( c%d_%d, c%d_%d );\n",
               b, a, element.cType, b, a, b, a );
         }
      }
   }
}


void storeResults(
   int alignmentOfC  /* Is C aligned, unaligned, or alternating? */
)
/*
 * Store the results of the I loop iteration to memory.
 * This will update the C matrix
 */
{
   int a, b;
   emit("/* Store results back to memory  */\n");

	TRACE( "printf( \"\t\tBEGIN\\n\" );\n" );

	for( b=0; b<unroll.b; ++b )
   {
      int remaining;
      remaining = unroll.a;
      a = 0;



		char* store;
		if( alignmentOfC == ALIGNED )
		{
			store = element.aStore;
		}
		else
		{
			store = element.uStore;
		}


		if( element.type == SINGLE || element.type == DOUBLE )
      {
			TRACE( "printf( \"\t\tb=%d (if)\\n\");\n", b );
         for( ; remaining>=element.vector_stride; remaining-=element.vector_stride )
         {
				TRACE( "printf( \"\t\t\tvector case\\n\");\n" );
            emit("%s( cPtrI%d+%d,  c%d_%d );\n",
                  store, b, a*element.size,b,a );
            a += element.vector_stride;
         }

         for( ; remaining > 0; remaining-- )
         {

				TRACE( "printf( \"\t\t\tcleanup case\\n\");\n" );
            emit("%s( cPtrI%d+%d,  c%d_%d );\n",
                 element.sStore, b, a*element.size,b,a );
            a += 1;
         }
      }
      else
      {

			TRACE( "printf( \"\t\tb=%d (else)\\n\");\n", b );

			int x = 0;
         for( ; remaining>=element.vector_stride; remaining -= element.vector_stride )
         {
            if( element.type == COMPLEX_SINGLE )
            {

               emit( "_mm_store_ss( cPtrI%d+%d, temp );\n", b, (b) );

               emit( "temp = _mm_shuffle_ps( c%d_%d, c%d_%d,"
                     "_MM_SHUFFLE(1,1,1,1));\n", b, a, b, a );
               emit( "_mm_store_ss( cPtrI%d+%d, temp );\n", b, (b+16) );

               emit( "temp = _mm_shuffle_ps( c%d_%d, c%d_%d,"
                     "_MM_SHUFFLE(2,2,2,2));\n", b, a, b, a );
               emit( "_mm_store_ss( cPtrI%d+%d, temp );\n", b, (b+32) );

               emit( "temp = _mm_shuffle_ps( c%d_%d, c%d_%d,"
                     "_MM_SHUFFLE(3,3,3,3));\n", b, a, b, a );
               emit( "_mm_store_ss( cPtrI%d+%d, temp );\n", b, (b+48) );

            } else if( element.type == COMPLEX_DOUBLE ) {

               emit( "_mm_store_sd( cPtrI%d+%d, c%d_%d );\n",
                     b, x*2*element.size, b, x+a );

               emit( "temp = _mm_shuffle_pd( c%d_%d, c%d_%d,"
                     "_MM_SHUFFLE2(1,1));\n", b, x+a, b, x+a );
               emit( "_mm_store_sd( cPtrI%d+%d, temp );\n\n",
                     b, (x+1)*2*element.size );

               x+=2;
            }
         }

         for( ; remaining >0; --remaining )
         {
            emit( "_mm_store_s%c( cPtrI%d+%d, c%d_%d );\n",
                  element.cType, b, 2*(a+x)*element.size, b,  a+x );
            a+=1;
         }

      }
   }
}


void applyBeta()
/*
 * Apply the beta factor, if one is needed.
 */
{
   int a, b;

   if( options.beta == BETA0 )
   {
      emit("/* No beta will be appied */\n" );
   }
   else
   {
      indent( 1 );
      emit( "/* Apply Beta Factor */\n" );
      for( b=0; b<unroll.b; ++b )
      {
         int remaining = unroll.a;

         if( element.type == COMPLEX_SINGLE || element.type == COMPLEX_DOUBLE )
         {

            emit( "/* Load C from memory */\n" );
            for( a=0;
               remaining>=element.vector_stride;
               remaining-=element.vector_stride )
            {
               int x=0;
               for( ; x<element.vector_stride; ++x )
               {
                  emit("temp%d = %s( cPtrI%d + %d );\n", x,
                        element.sLoad, b, 2*(element.size*(x+a)) );
               }

               if( element.type == COMPLEX_SINGLE )
               {
/*                temp0 = temp0[0], temp1[0], temp0[1], temp1[1] */
                  emit( "temp0 = _mm_unpacklo_ps( temp0, temp1 );\n" );

/*                temp2 = temp2[0], temp3[0], temp2[1], temp3[1] */
                  emit( "temp2 = _mm_unpacklo_ps( temp2, temp3 );\n" );

/*                b?_? = temp0[0], temp1[0], temp2[0], temp2[0] */
                  emit( "b%d_%d = _mm_shuffle_ps( temp0, temp2,"
                        "_MM_SHUFFLE(0, 1, 0, 1 )  );\n" );
               }

               if( element.type == COMPLEX_DOUBLE )
               {
                  emit( "bc%d_%d = _mm_shuffle_pd( temp0, temp1,"
                        "_MM_SHUFFLE2(0, 0 )  );\n", b, a );
               }


                  if( options.beta != BETA1 )
                  {
                     emit("bc%d_%d = _mm_mul_pd( bc%d_%d, betaV );\n",
                        b,a, b,a );
                  }
               a+= element.vector_stride;
            }
            for( ; remaining > 0; --remaining )
            {
               emit( "/* %d remaining */\n", remaining );
               emit("bc%d_%d = %s( cPtrI%d+%d );\n",
                    b, a, element.sLoad, b, 2*a*element.size );

               if( options.beta != BETA1 )
               {
                  emit("bc%d_%d = _mm_mul_s%c( bc%d_%d, betaV );\n",
                        b,a, element.cType, b,a );
               }
               a++;
            }

         } else {
            emit( "/* Load C from memory */\n" );
            for( a=0;
               remaining>=element.vector_stride;
               remaining-=element.vector_stride )
            {
               emit("bc%d_%d = _mm_load_p%c( cPtrI%d+%d );\n", b, a,
                  element.cType, b, a*element.size );

                  if( options.beta != BETA1 )
                  {
                     emit("bc%d_%d = _mm_mul_p%c( bc%d_%d, betaV );\n",
                        b,a, element.cType, b,a );
                  }
               a+= element.vector_stride;
            }
            for( ; remaining > 0; --remaining )
            {
               emit( "/* %d remaining */\n", remaining );
               emit("bc%d_%d = %s( cPtrI%d+%d );\n",
                     b, a, element.sLoad, b, a*element.size );
               if( options.beta != BETA1 )
               {
                  emit("bc%d_%d = _mm_mul_s%c( bc%d_%d, betaV );\n",
                        b,a, element.cType, b,a );
               }
               a++;
            }
         }

      }
      emit( "/* C = (beta*C) + (matrix multiply) */\n" );
      for( b=0; b<unroll.b; ++b )
      {
         int remaining = unroll.a;
         a = 0;
         for( ; remaining >= element.vector_stride;
                remaining -= element.vector_stride )
         {
            emit("c%d_%d = _mm_add_p%c( c%d_%d, bc%d_%d );\n",
                  b, a, element.cType, b, a, b, a );
            a += element.vector_stride;
         }

         for( ; remaining > 0; --remaining )
         {
            emit( "c%d_%d = _mm_add_s%c( c%d_%d, bc%d_%d );\n",
                  b, a, element.cType, b, a, b, a );
            ++a;
         }
      }

      indent(-1 );
   }
}


void printMainLoops
(
   int alignmentOfC,  /* How is C aligned? */
   char* name      /* What is the name of this alignment */
)
/*
 * Print the I,J,K loops, accounting for a specific form of alignment,
 * aligned, unaligned, alternating
 */
{
   int a, b, k;
   int pf;

/*
 * We are fetching (MB*KB*eltsize) bytes (one block of A).
 * If we prefetch outside the KB loop, we will have (MB/mu)*(NB/nu)
 * prefetch opportunities.
 *
 * Therefore, we must fetch (MB*KB*eltsize)/ [(MB/mu)*(NB/nu)]
 * which is equal to (mu*nu*KB*eltsize)/NB.
 *
 * We still need to take care of the m-loop peeled case,
 * when there is one less iteration of the m loop.
 */

   if( prefetch.ABlock )
   {
      emit("const int pfBlockDistance = (%d * %d * KB * %d) / %s;\n",
            unroll.a, unroll.b, element.size, nb );
   }


	emit("/* =======================================\n" );
   emit(" * Begin generated inner loops for case %s\n", name );
   emit(" * ======================================= */\n" );

   switch( unroll.nb )
   {
      case PARAMETER:
      emit("for( j=-NB; j!=0; j+=J_UNROLL) \n" );
      break;
      case USE_KB:
      emit("for( j=-KB; j!=0; j+=J_UNROLL) \n" );
      break;
      default:
      emit("for( j=-%d; j!=0; j+=J_UNROLL) \n", unroll.nb );
      break;
   }

   emit("{\n");
   indent( 1 );

   emit("register void const *A0_off = (void*)A; \n");

   if( options.lda == PARAMETER && unroll.a > 2 )
   {
      emit("register void const *A3_off = A0_off + lda_bytes3;\n");
      if( unroll.a > 4 )
      {
         emit( "register void const *A5_off = A3_off + lda_bytes*2;\n" );
      }
   }
   emit("\n");

   emit( "register void *cPtrI0 = cPtr;\n" );

   for( b=1; b<unroll.b; ++b )
   {
      emit("register void *cPtrI%d = cPtrI%d + ldc_bytes;\n", b, b-1);
   }

   emit("\n\n");


   if( prefetch.fetchC == TRUE )
   {
      for( b=0; b<unroll.b; ++b )
      {
         emit("__builtin_prefetch( cPtrI%d, PF_READONLY, PF_DEF );\n", b );
      }
   }


   char* deltaStr = "";
/*
 * Peel the last iteration of the inner loop if prefetch should run on B
 */
   if( prefetch.BRows == TRUE )
   {
      deltaStr = "+I_UNROLL";
   }

   emit("for( i=-%s%s; i != 0; i+= I_UNROLL )\n", mb, deltaStr );


   emit("{\n");
   indent( 1 );
		printILoop( alignmentOfC, TRUE, FALSE );
   indent( -1 );
   emit("} /* End i/MB loop */\n\n");

   if( prefetch.BRows == TRUE )
   {
      printILoop( alignmentOfC, FALSE, TRUE );
   }

   emit( "B0_off += J_UNROLL*ldb_bytes;\n");
   emit( "cPtr += J_UNROLL*ldc_bytes;\n" );

   indent( -1 );
   emit("} /* End j/NB loop */\n");
   emit("/* End of generated inner loops */\n");
}


/*
 * Print one iteration of the middle loop, including beta adjustments,
 * summing of the inner loop, and iteration along the matricies.
 */
void printILoop(
  int alignmentOfC,   /* How is the data aligned? */
  BOOL prefetchA,  /* Allow prefetch of A during this iteration? */
  BOOL prefetchB   /* Allow prefetch of B during this iteration? */
)
{
   int pf;
   int b;


   TRACE( "printf( \"\tBegining loop for i=\");\n" );
   TRACE( "printf( \"%%d\\n\", i );\n" );

	printAllKUnrollings( prefetchA, prefetchB );


	TRACE( "printf( \"\tScalar Compression\\n\");\n" );

/*
 * Scalar compression of singles and doubles behaves differently
 */
   if( element.type == SINGLE || element.type == COMPLEX_SINGLE )
   {
      printScalarCompressionSingle();
   }
   else
   {
      printScalarCompression();
   }


	TRACE( "printf( \"\tBeta Factor\\n\");\n" );

/*
 * Apply the beta scaling factor
 */
   applyBeta();


	TRACE( "printf( \"\tLoop Control\\n\");\n" );

/*
 * Move to the next iteration
 */
   emit("/* Move pointers to next iteration */  \n");
   emit("A0_off += unroll_a;\n");

   if( options.lda == PARAMETER && unroll.a > 2 )
   {
      emit("A3_off += unroll_a;\n");
      if( unroll.a > 5 )
         emit( "A5_off += unroll_a;\n" );
   }
   emit("\n");


	TRACE( "printf( \"\tStore Results\\n\");\n" );
/*
 * Store the results of the computation back to memory
 */
   storeResults( alignmentOfC );


	TRACE( "printf( \"\tPointers\\n\");\n" );

/*
 * Increment Pointers
 */
   for( b=0; b<unroll.b; ++b )
   {
      if( element.type == COMPLEX_SINGLE || element.type == COMPLEX_DOUBLE )
      {
         emit("cPtrI%d += %d*2*I_UNROLL;\n", b, element.size );
      } else {
         emit("cPtrI%d += %d*I_UNROLL;\n", b, element.size );
      }
   }

   emit("\n\n");

   if( prefetchA )
   {
      if( prefetch.ACols )
      {
         if( options.lda == PARAMETER )
            emit( "prefetchACols += %d*lda;\n", unroll.a );
         else if( options.lda == USE_KB )
            emit( "prefetchACols += %d*KB;\n", unroll.a );
         else
            emit( "prefetchACols += %d;\n", unroll.a * options.lda );
      }
   }
}


void emit( const char *fmt, ...)
/*
 * Write a string to the output file.  It will be indented
 * by the current indent value, vall indent(int) to adjust
 * the indent factor.   indent(1) will do a standard scoping
 * indent, and indent(-1) will remove that indent.
 */
{
   assert( tabwidth >= 0 );

   va_list arg;
   va_start(arg, fmt);
   int t;
   for( t=0; t<tabwidth; ++t )
   {
      fprintf( options.outputLocation, "   ");
   }
   vfprintf( options.outputLocation, fmt, arg);
   va_end(arg);
}


void emitCat( const char *fmt, ...)
/*
 * Write a string to the output file.  Do not do indenting.
 * This is used when a line of code is emitted in parts.
 */
{
   va_list arg;
   va_start(arg, fmt);
   vfprintf( options.outputLocation, fmt, arg);
   va_end(arg);
}



void indent( int delta )
/*
 * Adjust the current indent for an emit call. See emit().
 */
{
   tabwidth += delta;
   assert( tabwidth >= 0 );
}


static void loadDefaults()
/*
 * Load all default settings
 */
{
   prefetch.ACols = FALSE;
   prefetch.ABlock = FALSE;
   prefetch.BRows = FALSE;
   prefetch.fetchC = FALSE;


   unroll.nb = 60;
   unroll.mb = 60;
   unroll.kb = 60;

	unroll.a = 1;
	unroll.b = 1;
	unroll.k = unroll.kb;

	element.vector_length_bytes = 16;
	element.type = DOUBLE;


	options.mainLoopType = 1;
	options.lda = PARAMETER;
	options.ldb = PARAMETER;
   options.ldc = PARAMETER;
	options.verifyUnrollings = FALSE;
	options.treatLoadsAsFloat = FALSE;
	options.treatStoresAsFloat = FALSE;
   options.beta = BETA0;
   options.outputLocation = stdout;
}


void loadOptions
(
   int argc, /* Number of command line elements */
   char **argv  /* Command line elements */
)
/*
 * Load the options from the command line
 */
{
	char *s;
   int tmp;

/* Determine if the user requested the help message */
	requestHelp( argc, argv );

/* Load all default settings */
	loadDefaults();

/* Load unrolling and blocking factors */
	loadInt( "-m", &unroll.a, argc, argv  );
	loadInt( "-n", &unroll.b, argc, argv  );
	loadInt( "-k", &unroll.k, argc, argv  );
	if( unroll.k <= 0 )
		unroll.k = unroll.kb;

   loadInt( "-M", &unroll.mb, argc, argv );
   loadInt( "-N", &unroll.nb, argc, argv );
   loadInt( "-K", &unroll.kb, argc, argv );

/* Load the beta factor */
   loadInt( "-beta", &options.beta, argc, argv );
   if( options.beta > 1 || options.beta < -1 )
      options.beta = BETAX;


/* Determine if alignment checks are requested */
	loadInt( "-MainLoopType", &options.mainLoopType, argc, argv );

/* Load the element type: float or double, complex or real */
   s = loadString( "-p", argc, argv );
	convertElementType( s[0] );

/* Where should the file be written? */
   s = loadString( "-f", argc, argv );
   setOutputLocation( s );


	loadBool( "-verifyUnrollings", &options.verifyUnrollings, argc, argv );
	loadInt( "-lda", &options.lda, argc, argv );
	loadInt( "-ldb", &options.ldb, argc, argv );
	loadInt( "-ldc", &options.ldc, argc, argv );
	loadBool( "-treatLoadsAsFloat", &options.treatLoadsAsFloat, argc, argv );
	loadBool( "-treatStoresAsFloat", &options.treatStoresAsFloat, argc, argv );

   loadBool( "-prefetchACols", &prefetch.ACols, argc, argv );
   loadBool( "-prefetchABlock", &prefetch.ABlock, argc, argv );
   loadBool( "-prefetchBRows", &prefetch.BRows, argc, argv );
   loadBool( "-FF", &prefetch.fetchC, argc, argv );





	switch( element.type )
	{
      case COMPLEX_DOUBLE:
		case DOUBLE:
		element.size = sizeof( double );
		element.shift = log2( sizeof( double ) );
      element.vector_stride = 2;

      strcpy( element.intrinsic,  "__m128d" );


/*    Use the shorter instruction? */
      if( options.treatLoadsAsFloat )
      {
         strcpy( element.aLoad, "(__m128d)_mm_load_ps" );
         strcpy( element.uLoad, "(__m128d)_mm_loadu_ps" );
			strcpy( element.sLoad, "(__m128d)_mm_load_ss" );
      } else {
         strcpy( element.aLoad, "_mm_load_pd" );
         strcpy( element.uLoad, "_mm_loadu_pd" );
			strcpy( element.sLoad, "_mm_load_sd" );
      }

/*    Use the shorter instruction? */
      if( options.treatStoresAsFloat )
      {
         strcpy( element.aStore, "(__m128d)_mm_store_ps" );
         strcpy( element.uStore, "(__m128d)_mm_storeu_ps" );
			strcpy( element.sStore, "(__m128d)_mm_store_ss" );
      } else {
        strcpy( element.aStore, "_mm_store_pd" );
        strcpy( element.uStore, "_mm_storeu_pd" );
		  strcpy( element.sStore, "_mm_store_sd" );
      }

      strcpy( element.type_name, "double" );
		break;

      case COMPLEX_SINGLE:
      case SINGLE:
		element.size = sizeof( float );
		element.shift = log2( sizeof( float ) );
      element.vector_stride = 4;


      strcpy( element.intrinsic, "__m128" );
      strcpy( element.aLoad, "_mm_load_ps" );
      strcpy( element.aStore, "_mm_store_ps" );
      strcpy( element.uLoad, "_mm_loadu_ps" );
      strcpy( element.uStore, "_mm_storeu_ps" );
		strcpy( element.sStore, "_mm_store_ss" );
		strcpy( element.sLoad, "_mm_load_ss" );

      strcpy( element.type_name, "float" );
		break;

		default:
		assert( 0 );
	}




   if( numArgsProcessed != (argc-1)/2 )
   {
      int i;
      fprintf( stderr, "Commandline contained unknown arguments\n" );
      printf( "There were %d args\n", argc );
      for( i=0; i<argc; ++i )
      {
         printf( "  %s\n", argv[i] );
      }
      for( i=0; i<argc; ++i )
      {
         printf( "  %s\n", argv[i] );
      }printHelp();
   }

}




char* loadString
(
   char* tag,  /* The name of the parameter to load */
   int argc,   /* Number of command line arguments */
   char** argv /* Command line arguments */
)
/*
 * Load a string from the command line and return it.
 * RETURNS: The string loaded from the command line,
 *          0 if the parameter was not present.
 */
{
	int i;

	for( i=0; i<argc; ++i )
	{
		if( strcmp( tag, argv[i] ) == 0 )
		{
			assert( i+1 < argc );
         numArgsProcessed++;
			return argv[i+1];
		}
	}
	return 0;
}


int requestHelp
(
   int argc,   /* Number of command line parameters */
   char** argv /* The command line parameters */
)
/*
 * Determine if the user requested the help message
 * by passing a special switch to the program.
 */
{
	int i;
	int flag;
	const char* tags[] = { "-?", "-h", "--help" };

	for( flag=0; flag<3; ++flag )
	{
		for( i=0; i<argc; ++i )
		{
			if( strcmp( tags[ flag ], argv[i] ) == 0 )
			{
            printHelp();
         }
      }
   }
return 1;
}

void printHelp()
/*
 * Print the help message to standard output.
 */
{
   fprintf( stdout, "Prints a listing of a GEMM kernel\n" );
   fprintf( stdout, "Optional Arguments:\n" );

   fprintf( stdout, "  -p [s,c,z,d] \n" );
   fprintf( stdout, "  -f <filename> => File to generate\n" );

/* Print Unrolling Options */
   fprintf( stdout, "  -m <int>  => Number of columns of A to unroll\n" );
   fprintf( stdout, "  -n <int>  => Number of rows of B to unroll\n" );
   fprintf( stdout, "  -k <int>  => Numver of times to unroll along K\n" );

/* Print blocking options */
   fprintf( stdout, "  -M <int> => block size, integer or 0 for runtime\n" );
   fprintf( stdout, "  -N <int> => block size, integer or 0 for runtime\n" );
   fprintf( stdout, "  -K <int> => block size, integer or 0 for runtime\n" );
   fprintf( stdout, "  -lda <int> => lda, or 0 for runtime, -1 to use KB\n" );
   fprintf( stdout, "  -ldb <int> => ldb, or 0 for runtime, -1 to use KB\n" );
   fprintf( stdout, "  -ldc <int> => ldc, or 0 for runtime, -1 to use KB\n" );

/* print algorithm optimizations */
   fprintf( stdout, "  -beta <int> => Assume beta value 0, 1, or" );
   fprintf( stdout, "other.\n" );
   fprintf( stdout, "  -MainLoopType  => 0 to test all alignments, " );
   fprintf( stdout, "1 to assume misaligned.\n" );
   fprintf( stdout, "  -verifyUnrollings <bool> ==> assert that unrolling "
                    "params are correct\n" );
   fprintf( stdout, "  -treatLoadsAsFloat <bool> ==> use loadps instead of "
                    "loadpd\n" );
   fprintf( stdout, "  -treatStoresAsFloat <bool> ==> use storeps instead of "
                    "storepd\n" );

/* Print prefetch options */
   fprintf( stdout, "  -FF <0/1> => Fetch C at top of loop\n" );
   fprintf( stdout, "  -prefetchACols <bool> ==> Prefetch the next "
                    "unrolling of A\n" );
   fprintf( stdout, "  -prefetchABlock <bool> ==> Prefetch the next "
                    "block of A\n");
   fprintf( stdout, "  -prefetchBRows <bool> ==> Prefetch the next rows "
                    " of B\n" );

   exit(1);
}



void loadInt
(
   char* tag,   /* The parameter to find */
   int *value,  /* (output) Populated with the value following the tag */
   int argc,    /* Number of command line parameters */
   char** argv  /* The command line parameters */
)
/*
 * Reads an integer value from the command line, as specified with a given
 * tag.  Value is unchanged if the tag is not found on the command line.
 */
{
	int i;

	for( i=0; i<argc; ++i )
	{
		if( strcmp( tag, argv[i] ) == 0 )
		{
			assert( i+1 < argc );
			*value = atoi( argv[i+1] );
         numArgsProcessed++;
			return;
		}
	}
}


void loadFloat
(
   char* tag,    /* The parameter to find */
   float *value, /* (output) Populated with the value found */
   int argc,     /* The number of command line parameters */
   char** argv   /* The command line parameters */
)
/*
 * Reads a float value from the command line, as specified with a given
 * tag.  Value is unchanged if the tag is not found on the command line.
 */
{
	int i;

	for( i=0; i<argc; ++i )
	{
		if( strcmp( tag, argv[i] ) == 0 )
		{
			assert( i+1 < argc );
			*value = atof( argv[i+1] );
         numArgsProcessed++;
			return;
		}
	}
}

void loadBool
(
    char* tag,  /* The parameter to find */
    int *value, /* The value of the parameter */
    int argc,   /* Number of command line parameters */
    char** argv /* Command line parameters */
)
/*
 * Reads a boolean value from the command line, as specified with a given
 * tag.  Value is unchanged if the tag is not found on the command line.
 */
{
	int i;

	for( i=0; i<argc; ++i )
	{
		if( strcmp( tag, argv[i] ) == 0 )
		{
			assert( i+1 < argc );
			if( strcmp( argv[i+1], "1" ) == 0 )
			{
				*value = TRUE;
			} else if( strcmp( argv[i+1], "0" ) == 0 ) {
				*value = FALSE;
			} else {
				fprintf( stderr, "ERROR: option tag \"%s\" requires"
				        " 1 or 0 value.\n", tag );
				exit( 1 );
			}

         numArgsProcessed++;
			return;
		}
	}
}



void getNBString
(
   char* out   /* The string to write to */
)
/* Generate a c compatible expression that defines
 * the value of NB.  This may be a constant or a
 * variable from the generated program.
 */
{
   switch( unroll.nb )
   {
   case PARAMETER:
      sprintf( out, "%s", "N" );
      break;
   case USE_KB:
      sprintf( out, "%s", "K" );
      break;
   default:
      sprintf( out, "%d", unroll.nb );
   }
}



void getMBString
(
   char* out  /* The string to write to */
)
/* Generate a c compatible expression that defines
 * the value of MB.  This may be a constant or a
 * variable from the generated program.
 */
{
   switch( unroll.mb )
   {
   case PARAMETER:
      sprintf( out, "%s", "M" );
      break;
   case USE_KB:
      sprintf( out, "%s", "K" );
      break;
   default:
      sprintf( out, "%d", unroll.kb );
   }
}


void getKBString
(
   char* out  /* The string to write to */
)
/*
 * Generate a c compatible expression that defines
 * the value of KB.  This may be a constant or a
 * variable from the generated program.
 */
{
   switch( unroll.kb )
   {
   case PARAMETER:
      sprintf( out, "%s", "K" );
      break;
   case USE_KB:
      assert( 0 );
      break;
   default:
      sprintf( out, "%d", unroll.kb );
   }
}



void convertElementType
(
   char specifier   /* The type specifier */
)
/*
 * Interpret a type specifier to determine what
 * datatype is generated.
 */
{

   switch( specifier )
   {
      case 's':
      element.type = SINGLE;
      element.cType = 's';
      break;

      case 'd':
      element.type = DOUBLE;
      element.cType = 'd';
      break;

      case 'z':
      element.type = COMPLEX_DOUBLE;
      element.cType = 'd';
      break;

      case 'c':
      element.type = COMPLEX_SINGLE;
      element.cType = 's';
      break;

      default:
      fprintf( stderr, "Element type \"%c\" is not valid\n", specifier );
      printHelp();
   }
}


void setOutputLocation
(
   char* file  /* The name of the file to generate */
)
/*
 * Set the output file location.
 * Use NULL for stdout, "" will not change the current setting.
 */
{
   if( file == NULL )
   {
      options.outputLocation = stdout;
      return;
   }

   if( strcmp( file, "" ) != 0 )
   {
      options.outputLocation = fopen( file, "w" );
   }
}

