/*
 * Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
 *
 * SPDX-License-Identifier: MIT
 */

#include "OCLPerfKernelThroughput.h"

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

#include <sstream>

#include "CL/cl.h"
#include "Timer.h"

#define DO_GPU_KERNELS 1

#if 0
#define ENTER(X) printf("Entering %s\n", X);
#define EXIT(X) printf("Exiting  %s\n", X);
#define PKT(X) X
#else
#define ENTER(X)
#define EXIT(X)
#define PKT(X)
#endif

// work with multiples of 128
#define ROUND_MULT(VAL, MULT) ((VAL / MULT) * MULT)
/*
int roundUp( int numToRound, int multiple)
{
    int r = numToRound % multiple;
    if (r == 0)
    {
        return numToRound;
    } else {
        return numToRound + multiple - remainder;
    }
}
*/
// quiety warnings
#ifdef WIN_OS
#define SNPRINTF sprintf_s
#else
#define SNPRINTF snprintf
#endif

#define WORK_GROUP_SIZE 256

/*******************************************************************************
 * Enumerated Types for Tests
 ******************************************************************************/

// memory operations
const LARGE_INT numKernelTypes = 2;
static const char* kernelType[numKernelTypes] = {"MatMul", "Madds"};

// source/read memory locations
const LARGE_INT numMemPaths = 2;
static const char* memPath[numMemPaths] = {"Host", "Device"};

// buffer size
const LARGE_INT numNumElements = 12;  // 15;
static const LARGE_INT numElements[numNumElements] = {
    4,       16, 64, 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304,
    16777216  //,
              // 67108864,
              // 268435456
};

// flops/byte
const LARGE_INT numWorkSizes = 5;
static const LARGE_INT workSize[numWorkSizes] = {1, 4, 16, 64, 256};

const float initFloat = 0.001f;
const float zeroFloat = 0.0f;

#define WORK_GROUP_SIZE 256

/*******************************************************************************
 * Write the Matrix Multiply Shader Kernel
 ******************************************************************************/
void OCLPerfKernelThroughput::genShaderMatrixMultiply() {
  ENTER("genShaderMatrixMultiply");

  std::stringstream ss;
  ss.clear();
#if 0
    printf("%ix%i * %ix%i = %ix%i:\n",
            matrixDim1_, matrixDim2_,
            matrixDim2_, matrixDim1_,
            matrixDim1_, matrixDim1_
            );
#endif
  ss << "#define BLOCK_SIZE 16\n"
        "#define HA "
     << matrixDim1_
     << "\n"
        "#define WA "
     << matrixDim2_
     << "\n"
        "#define HB WA\n"
        "#define WB HA\n"
        "#define HC HA\n"
        "#define WC WB\n"
        "__kernel void\n"
        "__attribute__((reqd_work_group_size(16,16,1)))\n"
        "kernel1(\n"
        "       __global float * restrict C,\n"
        "       __global float * restrict A,\n"
        "       __global float * restrict B )\n"
        "{\n"
        "   int bx = get_group_id(0);\n"
        "   int by = get_group_id(1);\n"
        "   int tx = get_local_id(0);\n"
        "   int ty = get_local_id(1);\n"
        "   int aBegin = WA * BLOCK_SIZE * by;\n"
        "   int aEnd   = aBegin + WA - 1;\n"
        "   int aStep  = BLOCK_SIZE;\n"
        "   int bBegin = BLOCK_SIZE * bx;\n"
        "   int bStep  = BLOCK_SIZE * WB;\n"
        "   __private float c = 0.f;\n"
        "   __local float localA[BLOCK_SIZE][BLOCK_SIZE];\n"
        "   __local float localB[BLOCK_SIZE][BLOCK_SIZE];\n"
        "   for (\n"
        "           int a = aBegin, b = bBegin;\n"
        "           a <= aEnd;\n"
        "           a += aStep, b += bStep)\n"
        "   {\n"
        "       localA[ty][tx] = (get_global_id(0) < WA && get_global_id(1) < "
        "HA) ? A[a + WA * ty + tx] : 0;\n"
        "       localB[ty][tx] = (get_global_id(0) < WB && get_global_id(1) < "
        "HB) ? B[b + WB * ty + tx] : 0;\n"
        "       barrier(CLK_LOCAL_MEM_FENCE);\n"
        "       for (int k = 0; k < BLOCK_SIZE; ++k)\n"
        "           c += localA[ty][k] * localB[k][tx];\n"
        "       barrier(CLK_LOCAL_MEM_FENCE);\n"
        "   }\n"
        "   int cIdx = WB * BLOCK_SIZE * by + BLOCK_SIZE * bx + WB * ty + tx;\n"
        "   if (get_global_id(0) < WC && get_global_id(1) < WC)\n"
        "   {\n"
        "       C[cIdx] = c;\n"
        "   }\n"
        "}\n";

  shader_ = ss.str();
  gold_ = 0.f;
  for (int i = 0; i < matrixDim2_; i++) gold_ += initFloat * initFloat;
  // gold_ = initFloat * initFloat * matrixDim2_;
  // printf("shader:\n%s\n", shader_.c_str());
  // printf("gold_: %f\n", gold_);
  EXIT("genShaderMatrixMultiply");
}

/*******************************************************************************
 * Write the Madds Shader Kernel
 ******************************************************************************/
void OCLPerfKernelThroughput::genShaderMadds() {
  ENTER("genShaderMadds");

  int flopLoopIter = 2 * (flopsPerByte_ * 4 * 4) / 16;  // bytes, flops

  std::stringstream ss;
  ss.clear();
  float a, b;

  ss <<  // begin kernel
      "__kernel void\n"
      "__attribute__((reqd_work_group_size("
     << 256
     << ",1,1)))\n"
        "kernel1(\n"
        "   __global float4 * restrict input,\n"
        "   __global float4 * restrict output )\n"
        "{\n";

  // begin loop
  ss << "   for ( uint idx = get_global_id(0);\n"
        "         idx < "
     << numElements[numElementsIdx_]
     << ";\n"
        "         idx += get_global_size(0) )\n"
        "   {\n";

  // do load
  ss << "       float4 prefetch = input[ idx ];\n"
        "       float a0 = prefetch.x;\n"
        "       float a1 = prefetch.y;\n"
        "       float a2 = prefetch.z;\n"
        "       float a3 = prefetch.w;\n"
        "       float b0 = a0;\n"
        "       float b1 = a1;\n"
        "       float b2 = a2;\n"
        "       float b3 = a3;\n";
  a = initFloat;
  b = a;

  // do math
  for (int i = 0; i < flopLoopIter; i++) {
    ss << "       a0 += b3*b1;\n"
          "       a1 += b0*b2;\n"
          "       a2 += b1*b3;\n"
          "       a3 += b2*b0;\n"
          "       b0 += a3*a1;\n"
          "       b1 += a0*a2;\n"
          "       b2 += a1*a3;\n"
          "       b3 += a2*a0;\n";
    // printf("a += b*b; %f += %f*%f\n", a, b, b);
    a += b * b;
    // printf("b += a*a; %f += %f*%f\n", b, a, a);
    b += a * a;
  }

  // do write or accumulate
  ss << "       __private float4 tmp;\n"
        "       tmp.x = b0;\n"
        "       tmp.y = b1;\n"
        "       tmp.z = b2;\n"
        "       tmp.w = b3;\n"
        "       output[ idx ] = tmp;\n";
  gold_ = b;
  // printf("GPU gold_ Tmp: %f\n", gold_);

  // end loop
  ss << "   } // end loop\n";
  // end kernel
  ss << " } // end kernel\n\n";

  shader_ = ss.str();
  // printf("shader:\n%s\n", shader_.c_str());
  // printf("gold_: %f\n", gold_);
  EXIT("genShaderMadds");
}

static void CL_CALLBACK notify_callback(const char* errinfo, const void* private_info, size_t cb,
                                        void* user_data) {}

/*******************************************************************************
 * Constructor
 ******************************************************************************/
OCLPerfKernelThroughput::OCLPerfKernelThroughput() {
  ENTER("constructor");
  _numSubTests = numKernelTypes * numMemPaths * numNumElements * numWorkSizes;

  cl_uint numPlatforms;
  cl_platform_id platform = NULL;
  cl_uint num_devices = 0;
  cl_device_id* devices = NULL;
  cl_device_id device = NULL;
  context_ = 0;

  error_ = _wrapper->clGetPlatformIDs(0, NULL, &numPlatforms);
  CHECK_RESULT(error_ != CL_SUCCESS, "clGetPlatformIDs failed");
  if (0 < numPlatforms) {
    cl_platform_id* platforms = new cl_platform_id[numPlatforms];
    error_ = _wrapper->clGetPlatformIDs(numPlatforms, platforms, NULL);
    CHECK_RESULT(error_ != CL_SUCCESS, "clGetPlatformIDs failed");
    // Get last for default
    platform = platforms[numPlatforms - 1];
    for (unsigned i = 0; i < numPlatforms; ++i) {
      char pbuf[100];
      error_ =
          _wrapper->clGetPlatformInfo(platforms[i], CL_PLATFORM_VENDOR, sizeof(pbuf), pbuf, NULL);
      num_devices = 0;
      /* Get the number of requested devices */
      error_ = _wrapper->clGetDeviceIDs(platforms[i], type_, 0, NULL, &num_devices);
      // Runtime returns an error when no GPU devices are present
      // instead of just returning 0 devices
      // CHECK_RESULT(error_ != CL_SUCCESS, "clGetDeviceIDs failed");
      // Choose platform with GPU devices
      if (num_devices > 0) {
        // printf("NumDevices: %i\n", num_devices);
        platform = platforms[i];
        break;
      }
    }
    delete platforms;
  }

  /*
   * If we could find our platform, use it, else die.
   */
  CHECK_RESULT(platform == 0, "Couldn't find AMD platform, cannot proceed");

  devices = (cl_device_id*)malloc(num_devices * sizeof(cl_device_id));
  CHECK_RESULT(devices == 0, "no devices");

  /* Get the requested device */
  error_ = _wrapper->clGetDeviceIDs(platform, type_, num_devices, devices, NULL);
  CHECK_RESULT(error_ != CL_SUCCESS, "clGetDeviceIDs failed");

  CHECK_RESULT(_deviceId >= num_devices, "Requested deviceID not available");
  device = devices[_deviceId];

  // get gpu speed
  error_ = _wrapper->clGetDeviceInfo(device, CL_DEVICE_MAX_CLOCK_FREQUENCY,
                                     sizeof(maxClockFrequency_), &maxClockFrequency_, NULL);
  CHECK_RESULT(error_ != CL_SUCCESS, "clGetDeviceInfo failed");
  error_ = _wrapper->clGetDeviceInfo(device, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(maxComputeUnits_),
                                     &maxComputeUnits_, NULL);
  CHECK_RESULT(error_ != CL_SUCCESS, "clGetDeviceInfo failed");
  if (maxComputeUnits_ > 8) {
    // printf("%i CUs reported; assuming 8 instead.", maxComputeUnits_);
    maxComputeUnits_ = 8;
  }
  // printf("Compute Units: %i\n", maxComputeUnits_);

  // printf("Subtests: %i\n", _numSubTests);

  // create context
  context_ = _wrapper->clCreateContext(NULL, 1, &device, notify_callback, NULL, &error_);
  CHECK_RESULT(context_ == 0, "clCreateContext failed");

  char charbuf[1024];
  size_t retsize;
  error_ = _wrapper->clGetDeviceInfo(device, CL_DEVICE_EXTENSIONS, 1024, charbuf, &retsize);
  CHECK_RESULT(error_ != CL_SUCCESS, "clGetDeviceInfo failed");

  if (context_) {
    error_ = _wrapper->clReleaseContext(context_);
    CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseContext failed");
  }

  cl_uint tmp;
  error_ = _wrapper->clGetDeviceInfo(device, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(tmp), &tmp, NULL);
  CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clGetDeviceInfo failed");
  // printf("NumComputeUnits: %u\n", tmp);
  maxComputeUnits_ = static_cast<LARGE_INT>(tmp);
  // printf("NumComputeUnits: %lld\n", maxComputeUnits_);
  EXIT("constructor");
}

OCLPerfKernelThroughput::~OCLPerfKernelThroughput() {}

/*******************************************************************************
 * Open - initializes test, compile GPU kernel
 ******************************************************************************/
void OCLPerfKernelThroughput::open(unsigned int test, char* units, double& conversion,
                                   unsigned int deviceId) {
  ENTER("open");
  /***********************************************************
   * select subtest
   **********************************************************/
  int testIdx = test + numKernelTypes * numMemPaths * numNumElements * numWorkSizes;
  memPathIdx_ = testIdx % numMemPaths;
  testIdx /= numMemPaths;
  numElementsIdx_ = testIdx % numNumElements;
  testIdx /= numNumElements;
  workSizeIdx_ = testIdx % numWorkSizes;
  testIdx /= numWorkSizes;
  kernelTypeIdx_ = testIdx % numKernelTypes;
  testIdx /= numKernelTypes;

  // float md1;

  // kernel values
  switch (kernelTypeIdx_) {
    case 0:  // Matrix Multiply
      // md1 = sqrt(1.f*numElements[numElementsIdx_]);
      // printf("MD1: sqrt(%f) = %f\n", 1.f*numElements[numElementsIdx_],md1);
      matrixDim1_ = static_cast<int>(sqrt(1.f * numElements[numElementsIdx_]));
      matrixDim2_ = matrixDim1_ * (int)workSize[workSizeIdx_];
      genShaderMatrixMultiply();
      work_dim_ = 2;
      global_work_size_ = new size_t[work_dim_];
      global_work_size_[0] =
          ((matrixDim1_ - 1) / 16 + 1) * 16;  // matrixDim1_ < 16 ? 16 : matrixDim1_;
      global_work_size_[1] = global_work_size_[0];
      local_work_size_ = new size_t[work_dim_];
      local_work_size_[0] = 16;
      local_work_size_[1] = local_work_size_[0];
      /*
      printf("Global: %ix%i; Local: %ix%i; Matrix: %ix%i\n",
              global_work_size_[0],
              global_work_size_[1],
              local_work_size_[0],
              local_work_size_[1],
              matrixDim1_,
              matrixDim2_
              );
      */
      input1BufferSize_ = static_cast<size_t>(matrixDim1_ * matrixDim2_ * sizeof(float));
      input2BufferSize_ = static_cast<size_t>(matrixDim2_ * matrixDim1_ * sizeof(float));
      output1BufferSize_ = static_cast<size_t>(matrixDim1_ * matrixDim1_ * sizeof(float));
      _reqDataSize = (1.0 * matrixDim1_ * matrixDim2_ * sizeof(float)) +
                     (1.0 * matrixDim2_ * matrixDim1_ * sizeof(float)) +
                     (1.0 * matrixDim1_ * matrixDim1_ * sizeof(float));
      break;
    case 1:                                         // Flops/Byte
      flopsPerByte_ = (int)workSize[workSizeIdx_];  // for kernelType == 0
      genShaderMadds();
      numWorkGroupsPerComputeUnit_ = 32;  // TODO
      numThreads_ = numWorkGroupsPerComputeUnit_ * maxComputeUnits_ * WORK_GROUP_SIZE;
      work_dim_ = 1;
      global_work_size_ = new size_t[work_dim_];
      local_work_size_ = new size_t[work_dim_];
      global_work_size_[0] = numThreads_;
      local_work_size_[0] = WORK_GROUP_SIZE;
      input1BufferSize_ = static_cast<size_t>(numElements[numElementsIdx_] * sizeof(float4));
      input2BufferSize_ = 0;
      output1BufferSize_ = static_cast<size_t>(numElements[numElementsIdx_] * sizeof(float4));
      _reqDataSize = 2.0 * numElements[numElementsIdx_] * sizeof(float4);
      break;
  }

  PKT(printf("Test Parameters:\n"
             "\tkernelTypeIdx: %i\n"
             "\tmemPathIdx: %i\n"
             "\tnumElementsIdx: %i\n"
             "\tworkSizeIdx: %i\n"
             "\n\n",
             kernelTypeIdx_, memPathIdx_, numElementsIdx_, workSizeIdx_);)

  /***********************************************************
   * get context and queue
   **********************************************************/
  cl_uint numPlatforms;
  cl_platform_id platform = NULL;
  cl_uint num_devices = 0;
  cl_device_id* devices = NULL;
  cl_device_id device = NULL;
  _crcword = 0;
  conversion = 1.0;
  _deviceId = deviceId;

  context_ = 0;
  cmd_queue_ = 0;
  program_ = 0;
  kernel_ = 0;
  input1Buffer_ = 0;
  output1Buffer_ = 0;
  _errorFlag = false;  // Reset error code so a single error
                       // doesn't prevent other subtests from running
  _errorMsg = "";

  error_ = _wrapper->clGetPlatformIDs(0, NULL, &numPlatforms);
  CHECK_RESULT(error_ != CL_SUCCESS, "clGetPlatformIDs failed");
  if (0 < numPlatforms) {
    cl_platform_id* platforms = new cl_platform_id[numPlatforms];
    error_ = _wrapper->clGetPlatformIDs(numPlatforms, platforms, NULL);
    CHECK_RESULT(error_ != CL_SUCCESS, "clGetPlatformIDs failed");

    platform = platforms[_platformIndex];
    char pbuf[100];
    error_ = _wrapper->clGetPlatformInfo(platforms[_platformIndex], CL_PLATFORM_VENDOR,
                                         sizeof(pbuf), pbuf, NULL);
    num_devices = 0;
    /* Get the number of requested devices */
    error_ = _wrapper->clGetDeviceIDs(platforms[_platformIndex], type_, 0, NULL, &num_devices);
    // Runtime returns an error when no GPU devices are present
    // instead of just returning 0 devices
    // CHECK_RESULT(error_ != CL_SUCCESS, "clGetDeviceIDs failed");
    delete platforms;
  }

  /*
   * If we could find our platform, use it, else die.
   */
  CHECK_RESULT(platform == 0, "Couldn't find AMD platform, cannot proceed");

  devices = (cl_device_id*)malloc(num_devices * sizeof(cl_device_id));
  CHECK_RESULT(devices == 0, "no devices");

  /*
   * Get the requested device
   */
  error_ = _wrapper->clGetDeviceIDs(platform, type_, num_devices, devices, NULL);
  CHECK_RESULT(error_ != CL_SUCCESS, "clGetDeviceIDs failed");

  device = devices[0];

  context_ = _wrapper->clCreateContext(NULL, 1, &device, notify_callback, NULL, &error_);
  CHECK_RESULT(context_ == 0, "clCreateContext failed");

  cmd_queue_ = _wrapper->clCreateCommandQueue(context_, device, CL_QUEUE_PROFILING_ENABLE, NULL);
  CHECK_RESULT(cmd_queue_ == 0, "clCreateCommandQueue failed");

  // Global memory size
  cl_ulong _maxMemoryAllocationSize;
  error_ = _wrapper->clGetDeviceInfo(device, CL_DEVICE_MAX_MEM_ALLOC_SIZE, sizeof(cl_ulong),
                                     &_maxMemoryAllocationSize, NULL);
  CHECK_RESULT(error_ != CL_SUCCESS, "clGetDeviceIDs(CL_DEVICE_GLOBAL_MEM_SIZE) failed");
#if 0
    printf("Buffer Sizes: %i %i %i = %f\n",
            input1BufferSize_,
            input2BufferSize_,
            output1BufferSize_,
            _reqDataSize);
#endif
  _dataSizeTooBig = (_reqDataSize > _maxMemoryAllocationSize);
  if (_dataSizeTooBig) {
    // printf("DATA TOO LARGE FOR DEVICE !!!");
    return;
  }

  // create kernel
  char* tmp = (char*)shader_.c_str();
  program_ = _wrapper->clCreateProgramWithSource(context_, 1, (const char**)&tmp, NULL, &error_);
  CHECK_RESULT(program_ == 0, "clCreateProgramWithSource failed");

  std::string args;
  args.clear();
  error_ = _wrapper->clBuildProgram(program_, 1, &device, args.c_str(), NULL, NULL);
  if (error_ != CL_SUCCESS) {
    cl_int intError;
    char log[16384];
    intError = _wrapper->clGetProgramBuildInfo(program_, device, CL_PROGRAM_BUILD_LOG,
                                               16384 * sizeof(char), log, NULL);
    printf("Build error -> %s\n", log);
    CHECK_RESULT(0, "clBuildProgram failed");
  }
  kernel_ = _wrapper->clCreateKernel(program_, "kernel1", &error_);
  CHECK_RESULT(kernel_ == 0, "clCreateKernel failed");

  /***********************************************************
   * Allocate GPU Memory
   **********************************************************/
  cl_mem_flags inputBufferFlags = 0;
  cl_mem_flags outputBufferFlags = 0;

  // choose gpu source buffer type
  switch (memPathIdx_) {
    case 0:  // host memory
      // printf("Allocating Host Memories\n");
      // allocate "device" memory
      inputBufferFlags = CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR;
      outputBufferFlags = CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR;
      input1Buffer_ =
          _wrapper->clCreateBuffer(context_, inputBufferFlags, input1BufferSize_, NULL, &error_);
      CHECK_RESULT(input1Buffer_ == 0, "clCreateBuffer Input failed");
      if (input1Buffer_ == 0) printf("Error: %i\n", error_);
      if (input2BufferSize_) {
        input2Buffer_ =
            _wrapper->clCreateBuffer(context_, inputBufferFlags, input2BufferSize_, NULL, &error_);
        CHECK_RESULT(input2Buffer_ == 0, "clCreateBuffer Input failed");
      }
      output1Buffer_ =
          _wrapper->clCreateBuffer(context_, outputBufferFlags, output1BufferSize_, NULL, &error_);
      CHECK_RESULT(output1Buffer_ == 0, "clCreateBuffer Input failed");
      if (output1Buffer_ == 0) printf("Error: %i\n", error_);

      // map host memory
      input1Ptr_ =
          (float*)_wrapper->clEnqueueMapBuffer(cmd_queue_, input1Buffer_, true, CL_MAP_WRITE, 0,
                                               input1BufferSize_, 0, NULL, NULL, &error_);
      if (input2BufferSize_) {
        input2Ptr_ =
            (float*)_wrapper->clEnqueueMapBuffer(cmd_queue_, input2Buffer_, true, CL_MAP_WRITE, 0,
                                                 input2BufferSize_, 0, NULL, NULL, &error_);
      }
      output1Ptr_ =
          (float*)_wrapper->clEnqueueMapBuffer(cmd_queue_, output1Buffer_, true, CL_MAP_READ, 0,
                                               output1BufferSize_, 0, NULL, NULL, &error_);
      _wrapper->clFinish(cmd_queue_);
      break;

    case 1:  // device memory
      // printf("Allocating Device Memories\n");
      // allocate device memory
      inputBufferFlags = CL_MEM_READ_WRITE;
      outputBufferFlags = CL_MEM_READ_WRITE;
      input1Buffer_ =
          _wrapper->clCreateBuffer(context_, inputBufferFlags, input1BufferSize_, NULL, &error_);
      CHECK_RESULT(input1Buffer_ == 0, "clCreateBuffer Input failed");
      if (input2BufferSize_) {
        input2Buffer_ =
            _wrapper->clCreateBuffer(context_, inputBufferFlags, input2BufferSize_, NULL, &error_);
        CHECK_RESULT(input2Buffer_ == 0, "clCreateBuffer Input failed");
      }
      output1Buffer_ =
          _wrapper->clCreateBuffer(context_, outputBufferFlags, output1BufferSize_, NULL, &error_);
      CHECK_RESULT(output1Buffer_ == 0, "clCreateBuffer Input failed");
      // printf("\tDone Allocating Device Memory\n");

      // allocate host memory
      input1Ptr_ = new float[input1BufferSize_ / sizeof(float)];
      if (input2BufferSize_) {
        input2Ptr_ = new float[input2BufferSize_ / sizeof(float)];
      }
      output1Ptr_ = new float[output1BufferSize_ / sizeof(float)];
      // printf("\tDone Allocating Host Memory\n");

      break;
    default:
      CHECK_RESULT(1, "Invalid Memory Path Idx");
      // invalid
  }
  for (unsigned int i = 0; i < input1BufferSize_ / sizeof(float); i++) {
    input1Ptr_[i] = initFloat;
  }
  for (unsigned int i = 0; i < input2BufferSize_ / sizeof(float); i++) {
    input2Ptr_[i] = initFloat;
  }
  for (unsigned int i = 0; i < output1BufferSize_ / sizeof(float); i++) {
    output1Ptr_[i] = zeroFloat;
  }

#if 0
    printf("Allocating GPU: %.0fMB, %.0fMB\n",
            static_cast<float>(1.f*input1BufferSize_/1024.f/1024.f),
            static_cast<float>(1.f*output1BufferSize_/1024.f/1024.f));
    input1Buffer_ = _wrapper->clCreateBuffer(
            context_, inputBufferFlags, input1BufferSize_, NULL, &error_);
    CHECK_RESULT(input1Buffer_ == 0, "clCreateBuffer Input failed");
    output1Buffer_ = _wrapper->clCreateBuffer(
            context_, outputBufferFlags, output1BufferSize_, NULL, &error_);
    CHECK_RESULT(output1Buffer_ == 0, "clCreateBuffer Output failed");
    error_ = /*_wrapper->*/clEnqueueFillBuffer(
            cmd_queue_, input1Buffer_, &initFloat, sizeof(initFloat),
            0, input1BufferSize_, 0, NULL, NULL);
    CHECK_RESULT(error_ != CL_SUCCESS, "clEnqueueFillBuffer failed");
    error_ = /*_wrapper->*/clEnqueueFillBuffer(
            cmd_queue_, output1Buffer_, &zeroFloat, sizeof(zeroFloat),
            0, output1BufferSize_, 0, NULL, NULL);
    CHECK_RESULT(error_ != CL_SUCCESS, "clEnqueueFillBuffer failed");

    /***********************************************************
     * Set Kernel Args
     **********************************************************/
    error_ = _wrapper->clSetKernelArg(
                kernel_, 0, sizeof(input1Buffer_), (void *) &input1Buffer_);
    CHECK_RESULT(error_ != CL_SUCCESS, "clSetKernelArg failed");
    error_ = _wrapper->clSetKernelArg(
                kernel_, 1, sizeof(output1Buffer_), (void *) &output1Buffer_);
    CHECK_RESULT(error_ != CL_SUCCESS, "clSetKernelArg failed");
#endif

  EXIT("open");
}

/*******************************************************************************
 * Run - execute full test once and return performance
 ******************************************************************************/
void OCLPerfKernelThroughput::run(void) {
  ENTER("run");
  CPerfCounter timer;
  if (!_dataSizeTooBig) {
    // set kernel args
#if 1
    switch (kernelTypeIdx_) {
      case 0:  // Matrix Multiply
        error_ =
            _wrapper->clSetKernelArg(kernel_, 0, sizeof(output1Buffer_), (void*)&output1Buffer_);
        CHECK_RESULT(error_ != CL_SUCCESS, "clSetKernelArg failed");
        error_ = _wrapper->clSetKernelArg(kernel_, 1, sizeof(input1Buffer_), (void*)&input1Buffer_);
        CHECK_RESULT(error_ != CL_SUCCESS, "clSetKernelArg failed");
        error_ = _wrapper->clSetKernelArg(kernel_, 2, sizeof(input2Buffer_), (void*)&input2Buffer_);
        CHECK_RESULT(error_ != CL_SUCCESS, "clSetKernelArg failed");
        break;
      case 1:  // Flops/Byte
        error_ = _wrapper->clSetKernelArg(kernel_, 0, sizeof(input1Buffer_), (void*)&input1Buffer_);
        CHECK_RESULT(error_ != CL_SUCCESS, "clSetKernelArg failed");
        error_ =
            _wrapper->clSetKernelArg(kernel_, 1, sizeof(output1Buffer_), (void*)&output1Buffer_);
        CHECK_RESULT(error_ != CL_SUCCESS, "clSetKernelArg failed");
        break;
    }
#endif
    launchKernel();
    timer.Reset();
    timer.Start();
    for (int i = 0; i < MAX_LOOP_ITER; i++) {
      launchKernel();
    }
    timer.Stop();
  }  // data not too large
  double totalSec = _dataSizeTooBig ? 1 : timer.GetElapsedTime();
  // printf("Total Time: %f seconds\n", totalSec);
  // printf("Average Kernel Time: %f seconds\n", totalSec / MAX_LOOP_ITER);

  // analyze performance
  avgKernelTime_ = (float)(totalSec / MAX_LOOP_ITER * 1000000);  // microseconds
  double flopCount;
  switch (kernelTypeIdx_) {
    case 0:  // Matrix Multiply
      flopCount = (2.0 * matrixDim1_ * matrixDim1_ * matrixDim2_);
      // printf("FlopCount = 2*%i*%i*%i=%f\n",
      // matrixDim1_,matrixDim1_,matrixDim2_,flopCount);
      bandwidth_ = (float)(1.f * _reqDataSize / 1024.f / 1024.f / 1024.f) * 1000000.f /
                   avgKernelTime_;  // GB/s
      gflops_ = (float)(1000000.f * flopCount / avgKernelTime_ / 1000000000.0);
      break;
    case 1:  // Madds
      flopCount = _reqDataSize * flopsPerByte_;
      bandwidth_ = (float)(1.f * _reqDataSize / 1024.f / 1024.f / 1024.f) * 1000000.f /
                   avgKernelTime_;  // GB/s
      gflops_ = bandwidth_ * flopsPerByte_;
      break;
  }
  if (_dataSizeTooBig) {
    printf("REQUESTED DATA SIZE EXCEEDS GLOBAL MEMORY !!!\n");
    bandwidth_ = 0;
    gflops_ = 0;
    avgKernelTime_ = 0;
  }
  // here print out details
  char buf[512];
  int bytesWritten;
  bytesWritten = SNPRINTF(buf, sizeof(buf),
                          "Kernel:%7s; "
                          "Work:%4i; "
                          "Buff:%11.0f; "
                          "Path:%7s; "
                          "%10.5e GB/s; "
                          "%10.5e GFlop/s; ",
                          kernelType[kernelTypeIdx_], static_cast<int>(workSize[workSizeIdx_]),
                          _reqDataSize, memPath[memPathIdx_], bandwidth_, gflops_);
  testDescString = buf;
  _perfInfo = avgKernelTime_;
  if (!_dataSizeTooBig) checkData();
  EXIT("run");
}

void OCLPerfKernelThroughput::launchKernel(void) {
  ENTER("launchKernel")
  /***********************************************************
   * Copy Data To
   **********************************************************/
  // printf("Copying Data To Device\n");
  switch (memPathIdx_) {
    case 0:  // zero copy
      // do nothing
      // void *inputPtr = _wrapper->clEnqueueMapBuffer(
      //        cmd_queue_, input1Buffer_, true, CL_MAP_READ,
      //        0, input1BufferSize_, 0, NULL, NULL, &error_);
      // void *outputPtr = _wrapper->clEnqueueMapBuffer(
      //        cmd_queue_, output1Buffer_, true, CL_MAP_READ,
      //        0, output1BufferSize_, 0, NULL, NULL, &error_);
      //_wrapper->clFinish(cmd_queue_);
      break;
    case 1:  // explicit copy to device memory
      // printf("Queue:     %p\n", &cmd_queue_);
      // printf("devBuffer: %i\n", input1Buffer_);
      // printf("hstBuffer: %p\n", input1Ptr_);
      // printf("bufSize:   %i\n", input1BufferSize_);
      error_ = _wrapper->clEnqueueWriteBuffer(cmd_queue_, input1Buffer_, true, 0, input1BufferSize_,
                                              (const void*)input1Ptr_, 0, NULL, NULL);
      if (input2BufferSize_) {
        error_ =
            _wrapper->clEnqueueWriteBuffer(cmd_queue_, input2Buffer_, true, 0, input2BufferSize_,
                                           (const void*)input2Ptr_, 0, NULL, NULL);
      }
      // printf("Error: %i\n", error_);
      std::fflush(stdout);
      _wrapper->clFinish(cmd_queue_);
      CHECK_RESULT(error_ != CL_SUCCESS, "clWriteBuffer failed");
      //_error = _wrapper->clEnqueueWriteBuffer(
      //        cmd_queue_, output1Buffer_, true, 0, output1BufferSize_,
      //        (const void *)output1Ptr_, 0, NULL, NULL );
      // CHECK_RESULT(error_ != CL_SUCCESS, "clWriteBuffer failed");
      break;
  }

    /***********************************************************
     * Set Kernel Args
     **********************************************************/
#if 0
    error_ = _wrapper->clSetKernelArg(
                kernel_, 0, sizeof(input1Buffer_), (void *) &input1Buffer_);
    CHECK_RESULT(error_ != CL_SUCCESS, "clSetKernelArg failed");
    error_ = _wrapper->clSetKernelArg(
                kernel_, 1, sizeof(output1Buffer_), (void *) &output1Buffer_);
    CHECK_RESULT(error_ != CL_SUCCESS, "clSetKernelArg failed");
#endif

  // printf("Launching Kernel: %ix%i threads\n", global_work_size_[0],
  // local_work_size_[0]);

  /***********************************************************
   * Launch Kernel
   **********************************************************/
  error_ = _wrapper->clEnqueueNDRangeKernel(cmd_queue_, kernel_, work_dim_, NULL,
                                            (const size_t*)global_work_size_,
                                            (const size_t*)local_work_size_, 0, NULL, NULL);
  // printf("Error: %i\n", error_);
  CHECK_RESULT(error_, "clEnqueueNDRangeKernel failed");
  _wrapper->clFinish(cmd_queue_);

  /***********************************************************
   * Copy Data From
   **********************************************************/
  // printf("Copying Data From Device\n");
  switch (memPathIdx_) {
    case 0:  // zero copy
      // do nothing
      // void *inputPtr = _wrapper->clEnqueueMapBuffer(
      //        cmd_queue_, input1Buffer_, true, CL_MAP_READ,
      //        0, input1BufferSize_, 0, NULL, NULL, &error_);
      // void *outputPtr = _wrapper->clEnqueueMapBuffer(
      //        cmd_queue_, output1Buffer_, true, CL_MAP_READ,
      //        0, output1BufferSize_, 0, NULL, NULL, &error_);
      //_wrapper->clFinish(cmd_queue_);
      break;
    case 1:  // explicit copy to device memory
      //_error = _wrapper->clEnqueueReadBuffer(
      //        cmd_queue_, input1Buffer_, true, 0, input1BufferSize_,
      //        (void *)input1Ptr_, 0, NULL, NULL );
      // CHECK_RESULT(error_ != CL_SUCCESS, "clWriteBuffer failed");
      // printf("VAL0 %p
      error_ = _wrapper->clEnqueueReadBuffer(cmd_queue_, output1Buffer_, true, 0,
                                             output1BufferSize_, (void*)output1Ptr_, 0, NULL, NULL);
      // printf("Error: %i\n", error_);
      CHECK_RESULT(error_ != CL_SUCCESS, "clWriteBuffer failed");
      break;
  }

  EXIT("launchKernel")
}

/*******************************************************************************
 * Check Data
 ******************************************************************************/
void OCLPerfKernelThroughput::checkData() {
  _wrapper->clFinish(cmd_queue_);
  float errorThreshhold = 0.00001f;
  float eqMax = gold_ + errorThreshhold * gold_;
  float eqMin = gold_ - errorThreshhold * gold_;
  /*
  printf("%ix%i * %ix%i = %ix%i:\n",
          matrixDim1_, matrixDim2_,
          matrixDim2_, matrixDim1_,
          matrixDim1_, matrixDim1_
          );
  */
  for (unsigned int i = 0; i < output1BufferSize_ / sizeof(float); i++) {
    float value = output1Ptr_[i];
    bool equal = (value > eqMin && value < eqMax);
    if (!equal) {
#if 0
            printf("Output[%i] = %.6e; gold_ = %.6e; %s\n",
                    i,
                    value,
                    gold_,
                    equal ? "Equal" : "NOT Equal");
#endif
      // printf("FAILURE\n");
      // CHECK_RESULT_NO_RETURN(1, "Data validation failed!\n");
      _errorFlag = true;
      break;
    } else {
      // printf("M[%i] = %.6e\n", i, output1Ptr_[i]);
    }
  }
}

/*******************************************************************************
 * Close - delete all data and release opencl objects
 ******************************************************************************/
unsigned int OCLPerfKernelThroughput::close(void) {
  ENTER("close");
  _wrapper->clFinish(cmd_queue_);

  if (global_work_size_) {
    delete[] global_work_size_;
    global_work_size_ = NULL;
  }
  if (local_work_size_) {
    delete[] local_work_size_;
    local_work_size_ = NULL;
  }
  // switch for memory type
  switch (memPathIdx_) {
    case 0:  // zero copy
      // unmap ptr
      if (input1Ptr_) {
        error_ = /*_wrapper->*/ clEnqueueUnmapMemObject(cmd_queue_, input1Buffer_, input1Ptr_, 0,
                                                        NULL, NULL);
        CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clEnqueueUnmapMemObject(input_) failed");
        _wrapper->clFinish(cmd_queue_);
        error_ = _wrapper->clReleaseMemObject(input1Buffer_);
        CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseMemObject(input1Buffer_) failed");
        input1Buffer_ = 0;
      }
      if (input2Ptr_) {
        error_ = /*_wrapper->*/ clEnqueueUnmapMemObject(cmd_queue_, input2Buffer_, input2Ptr_, 0,
                                                        NULL, NULL);
        CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clEnqueueUnmapMemObject(input_) failed");
        _wrapper->clFinish(cmd_queue_);
        error_ = _wrapper->clReleaseMemObject(input2Buffer_);
        CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseMemObject(input2Buffer_) failed");
        input2Buffer_ = 0;
      }
      if (output1Ptr_) {
        error_ = /*_wrapper->*/ clEnqueueUnmapMemObject(cmd_queue_, output1Buffer_, output1Ptr_, 0,
                                                        NULL, NULL);
        CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clEnqueueUnmapMemObject(output_) failed");
        _wrapper->clFinish(cmd_queue_);
        error_ = _wrapper->clReleaseMemObject(output1Buffer_);
        CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseMemObject(input1Buffer_) failed");
        output1Buffer_ = 0;
      }
      break;
    case 1:  // explicit copy to device memory
      // release object
      if (input1Buffer_) {
        error_ = _wrapper->clReleaseMemObject(input1Buffer_);
        CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseMemObject(input1Buffer_) failed");
        input1Buffer_ = 0;
      }
      if (input2Buffer_) {
        error_ = _wrapper->clReleaseMemObject(input2Buffer_);
        CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseMemObject(input2Buffer_) failed");
        input2Buffer_ = 0;
      }
      if (output1Buffer_) {
        error_ = _wrapper->clReleaseMemObject(output1Buffer_);
        CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseMemObject(input1Buffer_) failed");
        output1Buffer_ = 0;
      }
      if (input1Ptr_) {
        delete[] input1Ptr_;
        input1Ptr_ = 0;
      }
      if (input2Ptr_) {
        delete[] input2Ptr_;
        input2Ptr_ = 0;
      }
      if (output1Ptr_) {
        delete[] output1Ptr_;
        output1Ptr_ = 0;
      }
      break;
  }

  if (kernel_) {
    error_ = _wrapper->clReleaseKernel(kernel_);
    CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseKernel failed");
    kernel_ = 0;
  }
  if (program_) {
    error_ = _wrapper->clReleaseProgram(program_);
    CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseProgram failed");
    program_ = 0;
  }
  if (cmd_queue_) {
    error_ = _wrapper->clReleaseCommandQueue(cmd_queue_);
    CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseCommandQueue failed");
    cmd_queue_ = 0;
  }
  if (context_) {
    error_ = _wrapper->clReleaseContext(context_);
    CHECK_RESULT_NO_RETURN(error_ != CL_SUCCESS, "clReleaseContext failed");
    context_ = 0;
  }
  _wrapper->clFinish(cmd_queue_);

  EXIT("close");
  return _crcword;
}
