/* Copyright (c) 2010 - 2021 Advanced Micro Devices, Inc.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE. */

#include "OCLSVM.h"

#include <stdio.h>

#include <algorithm>
#include <cstdlib>
#ifdef _WIN32
#include <intrin.h>
#include <windows.h>
#endif
#include <iostream>

#define NUM_SIZES 6

#define OCL_CHECK(error)                                                 \
  if (error != CL_SUCCESS) {                                             \
    fprintf(stderr, "OpenCL API invocation failed at %s:%d\n", __FILE__, \
            __LINE__);                                                   \
    exit(-1);                                                            \
  }

#define STR(__macro__) #__macro__

#ifdef _WIN32
size_t getTotalSystemMemory() {
  MEMORYSTATUSEX status;
  status.dwLength = sizeof(status);
  GlobalMemoryStatusEx(&status);
  return status.ullTotalPhys;
}
#endif

template <typename T, unsigned N>
static unsigned countOf(const T (&)[N]) {
  return N;
}

const static char* sources[] = {
    STR(__kernel void test(__global int* ptr) {
      ptr[get_global_id(0)] = 0xDEADBEEF;
    }),
    STR(__kernel void test(__global int* ptr, __global int* ptr2) {
      ptr[get_global_id(0)] = 0xDEADBEEF;
      ptr2[get_global_id(0)] = 0xDEADF00D;
    }),
    STR(__kernel void test(__global long* ptr) {
      ptr[get_global_id(0) * 1024] = 0xBAADF00D;
    }),
    STR(__kernel void test(__global ulong* ptr) {
      while (ptr) {
        *ptr = 0xDEADBEEF;
        ptr = *((__global ulong* __global*)(ptr + 1));
      }
    }),
    STR(__kernel void test(__global volatile int* ptr, int numIterations) {
      for (int i = 0; i < numIterations; i++) {
        // This should be:
        // atomic_fetch_add_explicit(ptr, 1, memory_order_relaxed,
        //                           memory_scope_all_svm_devices);
        // But using device atomics is mapped to the same ISA and compiles
        // in OpenCL 1.2
        atomic_inc(ptr);
      }
    }),
    STR(__kernel void test(){
        // dummy
    }),
    STR(__kernel void test(int8 arg0, __global int* arg1, int arg2,
                           __global int* arg3, __global float* arg4){
        // dummy
    }),
    STR(__kernel void test(__global int* ptr, int to) {
      // dummy kernel that takes a long time to complete
      for (int i = 0; i < to; ++i) {
        // avoid compiler optimizations
        if (ptr[get_global_id(0)] != 17) {
          ptr[get_global_id(0)]++;
        } else {
          ptr[get_global_id(0)] += 2;
        }
      }
    }),
    STR(__kernel void test(){
        // dummy
    })};

OCLSVM::OCLSVM() { _numSubTests = countOf(sources); }

OCLSVM::~OCLSVM() {}

void OCLSVM::open(unsigned int test, char* units, double& conversion,
                  unsigned int deviceId) {
  OCLTestImp::open(test, units, conversion, deviceId);
  CHECK_ERROR(error_, "Error opening test");
  _openTest = test;

  if (!isOpenClSvmAvailable(devices_[_deviceId])) {
    printf("Device does not support any SVM features, skipping...\n");
    return;
  }

  program_ = _wrapper->clCreateProgramWithSource(
      context_, 1, sources + _openTest, NULL, &error_);
  CHECK_ERROR(error_, "clCreateProgramWithSource()  failed");

  error_ = _wrapper->clBuildProgram(program_, 1, &devices_[deviceId],
                                    "-cl-std=CL2.0", NULL, NULL);
  CHECK_ERROR(error_, "clBuildProgram() failed");

  kernel_ = _wrapper->clCreateKernel(program_, "test", &error_);
  CHECK_ERROR(error_, "clCreateKernel() failed");
}

#ifndef CL_VERSION_2_0
// make sure the tests compile in OpenCL <= 1.2
void OCLSVM::runFineGrainedBuffer() {}
void OCLSVM::runFineGrainedSystem() {}
void OCLSVM::runFineGrainedSystemLargeAllocations() {}
void OCLSVM::runLinkedListSearchUsingFineGrainedSystem() {}
void OCLSVM::runPlatformAtomics() {}
void OCLSVM::runEnqueueOperations() {}
void OCLSVM::runSvmArgumentsAreRecognized() {}
void OCLSVM::runSvmCommandsExecutedInOrder() {}
void OCLSVM::runIdentifySvmBuffers() {}
#else

void OCLSVM::runFineGrainedBuffer() {
  if (!(svmCaps_ & CL_DEVICE_SVM_FINE_GRAIN_BUFFER)) {
    printf(
        "Device does not support fined-grained buffer sharing, skipping "
        "test...\n");
    return;
  }
  const size_t numElements = 256;
  int* ptr = (int*)clSVMAlloc(context_,
                              CL_MEM_READ_WRITE | CL_MEM_SVM_FINE_GRAIN_BUFFER,
                              numElements * sizeof(int), 0);
  CHECK_RESULT(!ptr, "clSVMAlloc() failed");

  error_ = clSetKernelArgSVMPointer(kernel_, 0, ptr);
  CHECK_ERROR(error_, "clSetKernelArgSVMPointer() failed");

  size_t gws[1] = {numElements};
  error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1,
                                            NULL, gws, NULL, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueNDRangeKernel() failed");

  error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
  CHECK_ERROR(error_, "Queue::finish() failed");

  size_t matchingElements = std::count(ptr, ptr + numElements, (int)0xDEADBEEF);
  CHECK_RESULT(matchingElements != numElements, "Expected: %zd, found:%zd",
               numElements, matchingElements);
  clSVMFree(context_, ptr);
}

void OCLSVM::runFineGrainedSystem() {
  if (!(svmCaps_ & CL_DEVICE_SVM_FINE_GRAIN_SYSTEM)) {
    printf(
        "Device does not support fined-grained system sharing, skipping "
        "test...\n");
    return;
  }

  const size_t numElements = 256;
  int* ptr = new int[numElements];
  int* ptr2 = new int[numElements];
  error_ = clSetKernelArgSVMPointer(kernel_, 0, ptr);
  CHECK_ERROR(error_, "clSetKernelArgSVMPointer() failed");

  error_ = clSetKernelArgSVMPointer(kernel_, 1, ptr2);
  CHECK_ERROR(error_, "clSetKernelArgSVMPointer() failed");

  size_t gws[1] = {numElements};
  error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1,
                                            NULL, gws, NULL, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueNDRangeKernel() failed");

  error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
  CHECK_ERROR(error_, "Queue::finish() failed");

  size_t matchingElements = std::count(ptr, ptr + numElements, (int)0xDEADBEEF);
  size_t matchingElements2 =
      std::count(ptr2, ptr2 + numElements, (int)0xDEADF00D);
  CHECK_RESULT(matchingElements + matchingElements2 != 2 * numElements,
               "Expected: %zd, found:%zd", numElements * 2,
               matchingElements + matchingElements2);
  delete[] ptr;
  delete[] ptr2;
}

void OCLSVM::runFineGrainedSystemLargeAllocations() {
#ifdef _WIN32
  if (!(svmCaps_ & CL_DEVICE_SVM_FINE_GRAIN_SYSTEM)) {
    printf(
        "Device does not support fined-grained system sharing on Lnx, skipping "
        "test...\n");
    return;
  }

  // Max allowed multiplier for malloc
  size_t allowedMemSize = getTotalSystemMemory() >> 12;

  size_t numElements = 256;

  char* s = getenv("OCLSVM_MALLOC_GB_SIZE");
  char* s2 = getenv("OCLSVM_MEMSET_ALLOC");

  for (int j = 1; j <= NUM_SIZES; j++) {
    numElements = 131072 * j;

    if (s != NULL) numElements = 131072 * atoi(s);

    if (numElements > allowedMemSize) break;

    void* ptr = malloc(numElements * 1024 * sizeof(uint64_t));
    CHECK_ERROR(ptr == NULL, "malloc failure");

    if (s2 != NULL) memset(ptr, 0, numElements * 1024 * sizeof(uint64_t));

    error_ = clSetKernelArgSVMPointer(kernel_, 0, ptr);
    CHECK_ERROR(error_, "clSetKernelArgSVMPointer() failed");

    size_t gws[1] = {numElements};
    error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1,
                                              NULL, gws, NULL, 0, NULL, NULL);
    CHECK_ERROR(error_, "clEnqueueNDRangeKernel() failed");

    error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
    CHECK_ERROR(error_, "Queue::finish() failed");

    uint64_t* ptr64 = reinterpret_cast<uint64_t*>(ptr);
    // Do a check
    for (int i = 0; i < numElements; i++) {
      if ((int)ptr64[i * 1024] != 0xBAADF00D) {
        uint64_t temp = ptr64[i * 1024];
        delete[] ptr;
        CHECK_RESULT(temp != 0xBAADF00D, "Found: %d, Expected:%d", temp,
                     0xBAADF00D);
      }
    }
    delete[] ptr;
  }
#endif
}

void OCLSVM::runLinkedListSearchUsingFineGrainedSystem() {
  if (!(svmCaps_ & CL_DEVICE_SVM_FINE_GRAIN_SYSTEM)) {
    printf(
        "Device does not support fined-grained system sharing, skipping "
        "test...\n");
    return;
  }

  uint64_t input[] = {34, 6, 0, 11, 89, 34, 6, 6, 6, 0xDEADBEEF};
  int inputSize = countOf(input);
  Node* ptr = NULL;
  for (int i = 0; i < inputSize; i++) {
    ptr = new Node(input[i], ptr);
  }
  error_ = clSetKernelArgSVMPointer(kernel_, 0, ptr);
  CHECK_ERROR(error_, "clSetKernelArgSVMPointer() failed");

  size_t gws[1] = {1};
  error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1,
                                            NULL, gws, NULL, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueNDRangeKernel() failed");

  error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
  CHECK_ERROR(error_, "Queue::finish() failed");

  int matchingElements = 0;
  // verify result while deallocating resources at the same time
  while (ptr) {
    if (ptr->value_ == 0xDEADBEEF) {
      matchingElements++;
    }
    Node* tmp = ptr;
    ptr = (Node*)ptr->next_;
    delete tmp;
  }
  CHECK_RESULT(matchingElements != inputSize, "Expected: %d, found:%d",
               inputSize, matchingElements);
}

static int atomicIncrement(volatile int* loc) {
#if defined(_MSC_VER)
  return _InterlockedIncrement((volatile long*)loc);
#elif defined(__GNUC__)
  return __sync_fetch_and_add(loc, 1);
#endif
  printf("Atomic increment not supported, aborting...");
  std::abort();
  return 0;
}

void OCLSVM::runPlatformAtomics() {
  if (!(svmCaps_ & CL_DEVICE_SVM_ATOMICS)) {
    printf("SVM atomics not supported, skipping test...\n");
    return;
  }

  volatile int* value = (volatile int*)clSVMAlloc(
      context_, CL_MEM_SVM_FINE_GRAIN_BUFFER | CL_MEM_SVM_ATOMICS, sizeof(int),
      0);
  CHECK_RESULT(!value, "clSVMAlloc() failed");
  *value = 0;
  const int numIterations = 1000000;
  error_ = clSetKernelArgSVMPointer(kernel_, 0, (const void*)value);
  CHECK_ERROR(error_, "clSetKernelArgSVMPointer() failed");

  error_ = clSetKernelArg(kernel_, 1, sizeof(numIterations), &numIterations);
  CHECK_ERROR(error_, "clSetKernelArg() failed");

  size_t gws[1] = {1};
  error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1,
                                            NULL, gws, NULL, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueNDRangeKernel() failed");

  for (int i = 0; i < numIterations; i++) {
    atomicIncrement(value);
  }

  error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
  CHECK_ERROR(error_, "Queue::finish() failed");

  int expected = numIterations * 2;
  CHECK_RESULT(*value != expected, "Expected: %d, found:%d", expected, *value);
  clSVMFree(context_, (void*)value);
}

void OCLSVM::runEnqueueOperations() {
  size_t numElements = 32;
  size_t size = numElements * 4;
  int* ptr0 = (int*)clSVMAlloc(context_, 0, size, 0);
  CHECK_RESULT(!ptr0, "clSVMAlloc() failed");
  int* ptr1 = (int*)clSVMAlloc(context_, 0, size, 0);
  CHECK_RESULT(!ptr1, "clSVMAlloc() failed");
  cl_event userEvent = clCreateUserEvent(context_, &error_);
  CHECK_ERROR(error_, "clCreateUserEvent() failed");

  cl_command_queue queue = cmdQueues_[_deviceId];
  // coarse-grained buffer semantics: the SVM pointer needs to be mapped
  // before the pointer can write to it
  error_ =
      clEnqueueSVMMap(queue, CL_TRUE, CL_MAP_WRITE, ptr0, size, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueSVMMap() failed");
  std::fill(ptr0, ptr0 + numElements, 1);
  error_ = clEnqueueSVMUnmap(queue, ptr0, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueSVMUnmap() failed");

  // we copy the 1st buffer into the 2nd buffer
  error_ = clEnqueueSVMMemcpy(queue, true, ptr1, ptr0, size, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueSVMMemcpy() failed");

  // verification: the 2nd buffer should be identical to the 1st
  error_ = clEnqueueSVMMap(queue, CL_TRUE, CL_MAP_READ, ptr1, size, 0, NULL,
                           &userEvent);
  CHECK_ERROR(error_, "clEnqueueSVMMap() failed");

  error_ = clWaitForEvents(1, &userEvent);
  CHECK_ERROR(error_, "clWaitForEvents() failed");

  size_t observed = std::count(ptr1, ptr1 + numElements, 1);
  size_t expected = numElements;
  CHECK_RESULT(observed != expected, "Expected: %zd, found:%zd", expected,
               observed);

  void* ptrs[2] = {ptr0, ptr1};
  error_ =
      clEnqueueSVMFree(queue, countOf(ptrs), ptrs, NULL, NULL, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueSVMFree() failed");
  error_ = clFinish(queue);
  CHECK_ERROR(error_, "clFinish() failed");
}

/**
 * Simple test to ensure that SVM pointer arguments are identified properly in
 * the runtime, since kernel arguments of pointer type can be bound to either
 * SVM pointers or cl_mem objects.
 */
void OCLSVM::runSvmArgumentsAreRecognized() {
  cl_int8 arg0;
  error_ = clSetKernelArg(kernel_, 0, sizeof(arg0), &arg0);
  CHECK_ERROR(error_, "clSetKernelArg() failed");

  error_ = clSetKernelArgSVMPointer(kernel_, 1, NULL);
  CHECK_ERROR(error_, "clSetKernelArgSVMPointer() failed");

  cl_int arg2;
  error_ = clSetKernelArg(kernel_, 2, sizeof(arg2), &arg2);
  CHECK_ERROR(error_, "clSetKernelArg() failed");

  error_ = clSetKernelArgSVMPointer(kernel_, 3, NULL);
  CHECK_ERROR(error_, "clSetKernelArgSVMPointer() failed");

  cl_mem arg4 = NULL;
  error_ = clSetKernelArg(kernel_, 4, sizeof(arg4), &arg4);
  CHECK_ERROR(error_, "clSetKernelArg() failed");

  size_t gws[1] = {1};

  // run dummy kernel
  error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1,
                                            NULL, gws, NULL, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueNDRangeKernel() failed");
  error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
  CHECK_ERROR(error_, "Queue::finish() failed");

  // now we bind a pointer argument to a standard buffer instead of a SVM one
  cl_mem buffer = NULL;
  error_ = clSetKernelArg(kernel_, 1, sizeof(buffer), &buffer);
  CHECK_ERROR(error_, "clSetKernelArg() failed");

  // re-execute the dummy kernel using different actual parameters
  error_ = _wrapper->clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1,
                                            NULL, gws, NULL, 0, NULL, NULL);
  CHECK_ERROR(error_, "clEnqueueNDRangeKernel() failed");
  error_ = _wrapper->clFinish(cmdQueues_[_deviceId]);
  CHECK_ERROR(error_, "Queue::finish() failed");
}

void OCLSVM::runSvmCommandsExecutedInOrder() {
#if EMU_ENV
  // Small number is enough to verify functionality in Emu environment
  const int numElements = 5000;
#else
  const int numElements = 100000;
#endif // EMU_ENV
  size_t size = numElements * sizeof(int);
  // allocate SVM memory
  int* data = (int*)clSVMAlloc(context_, CL_MEM_READ_WRITE, size, 0);
  CHECK_RESULT(!data, "clSVMAlloc failed");

  // map the SVM buffer to host
  cl_int status = clEnqueueSVMMap(cmdQueues_[_deviceId], CL_TRUE, CL_MAP_WRITE,
                                  data, size, 0, NULL, NULL);
  CHECK_ERROR(status, "Error when mapping SVM buffer");

  // fill buffer with 0s
  std::fill(data, data + numElements, 0);

  // unmap the SVM buffer to host
  status = clEnqueueSVMUnmap(cmdQueues_[_deviceId], data, 0, NULL, NULL);
  CHECK_ERROR(status, "Error when unmapping SVM buffer");

  // enqueue kernel
  status = clSetKernelArgSVMPointer(kernel_, 0, data);
  CHECK_ERROR(status, "Error when setting kernel argument");
  status = clSetKernelArg(kernel_, 1, sizeof(int), &numElements);
  CHECK_ERROR(status, "clSetKernelArg() failed");

  cl_event event;
  size_t overallSize = (size_t)numElements;
  status = clEnqueueNDRangeKernel(cmdQueues_[_deviceId], kernel_, 1, NULL,
                                  &overallSize, NULL, 0, NULL, &event);
  CHECK_ERROR(status, "Error when enqueuing kernel");
  error_ = clFinish(cmdQueues_[_deviceId]);
  CHECK_ERROR(status, "clFinish()");

  // map the SVM buffer to host
  status = clEnqueueSVMMap(cmdQueues_[_deviceId], CL_TRUE, CL_MAP_READ, data,
                           size, 0, NULL, NULL);
  CHECK_ERROR(status, "Error when mapping SVM buffer");

  bool pass = true;
  // verify the data. Using descending order might increase the chance of
  // finding an error since the GPU (when used) might not have finished
  // updating the data array by the time we do the verification
  for (int i = numElements - 1; i >= 0; i--) {
    if (data[i] != numElements + 1) {
      pass = false;
      break;
    }
  }

  // unmap the SVM buffer to host
  status = clEnqueueSVMUnmap(cmdQueues_[_deviceId], data, 0, NULL, NULL);
  CHECK_ERROR(status, "Error when unmapping SVM buffer");

  // free the SVM buffer
  status = clEnqueueSVMFree(cmdQueues_[_deviceId], 1, (void**)&data, NULL, NULL,
                            0, NULL, NULL);
  CHECK_ERROR(status, "Error when freeing the SVM buffer");
  error_ = clFinish(cmdQueues_[_deviceId]);
  CHECK_ERROR(error_, "clFinish() failed");
  CHECK_RESULT(!pass, "Wrong result");
}

void OCLSVM::runIdentifySvmBuffers() {
  size_t size = 1024 * 1024;

  // dummy allocation to force the runtime to track several SVM buffers
  clSVMAlloc(context_, CL_MEM_READ_WRITE, size * 10, 0);

  void* ptr = clSVMAlloc(context_, CL_MEM_READ_WRITE, size, 0);
  cl_int status;
  cl_bool usesSVMpointer = CL_FALSE;

  // dummy allocation to force the runtime to track several SVM buffers
  clSVMAlloc(context_, CL_MEM_READ_WRITE, size * 4, 0);

  // buffer using the entire SVM region should be identified as such
  cl_mem buf1 =
      clCreateBuffer(context_, CL_MEM_USE_HOST_PTR, size, ptr, &status);
  CHECK_ERROR(status, "clCreateBuffer failed.");

  size_t paramSize = 0;
  status = clGetMemObjectInfo(buf1, CL_MEM_USES_SVM_POINTER, 0, 0, &paramSize);
  CHECK_ERROR(status, "clGetMemObjectInfo failed");
  CHECK_RESULT(paramSize != sizeof(cl_bool),
               "clGetMemObjectInfo(CL_MEM_USES_SVM_POINTER) "
               "returned wrong size.");

  status = clGetMemObjectInfo(buf1, CL_MEM_USES_SVM_POINTER, sizeof(cl_bool),
                              &usesSVMpointer, 0);
  CHECK_ERROR(status, "clGetMemObjectInfo failed");
  CHECK_RESULT(usesSVMpointer != CL_TRUE,
               "clGetMemObjectInfo(CL_MEM_USES_SVM_POINTER) "
               "returned CL_FALSE for buffer created from SVM pointer.");

  // Buffer that uses random region within SVM buffers
  cl_mem buf2 = clCreateBuffer(context_, CL_MEM_USE_HOST_PTR, 256,
                               (char*)ptr + size - 256, &status);
  CHECK_ERROR(status, "clCreateBuffer failed.");

  status = clGetMemObjectInfo(buf2, CL_MEM_USES_SVM_POINTER, sizeof(cl_bool),
                              &usesSVMpointer, 0);
  CHECK_ERROR(status, "clGetMemObjectInfo failed");
  CHECK_RESULT(usesSVMpointer != CL_TRUE,
               "clGetMemObjectInfo(CL_MEM_USES_SVM_POINTER) "
               "returned CL_FALSE for buffer created from SVM pointer.");

  // for any other pointer the query should return false
  void* randomPtr = malloc(size);
  cl_mem buf3 =
      clCreateBuffer(context_, CL_MEM_USE_HOST_PTR, size, randomPtr, &status);
  CHECK_ERROR(status, "clCreateBuffer failed.");

  status = clGetMemObjectInfo(buf3, CL_MEM_USES_SVM_POINTER, sizeof(cl_bool),
                              &usesSVMpointer, 0);
  CHECK_ERROR(status, "clGetMemObjectInfo failed");
  CHECK_RESULT(usesSVMpointer == CL_TRUE,
               "clGetMemObjectInfo(CL_MEM_USES_SVM_POINTER) "
               "returned CL_TRUE for buffer not created from SVM pointer.");

  clReleaseMemObject(buf3);
  clReleaseMemObject(buf2);
  clReleaseMemObject(buf1);
  clSVMFree(context_, ptr);
}
#endif

cl_bool OCLSVM::isOpenClSvmAvailable(cl_device_id device_id) {
#ifdef CL_VERSION_2_0
  error_ = clGetDeviceInfo(devices_[_deviceId], CL_DEVICE_SVM_CAPABILITIES,
                           sizeof(svmCaps_), &svmCaps_, NULL);
  CHECK_ERROR_NO_RETURN(error_, "clGetDeviceInfo() failed");
  if (!(svmCaps_ & CL_DEVICE_SVM_COARSE_GRAIN_BUFFER)) {
    return CL_FALSE;
  } else {
    return CL_TRUE;
  }
#endif
  // -Device does not support OpenCL >= 2.0
  // -Device supports OpenCL >= 2.0, but available headers are <= 1.2
  return CL_FALSE;
}

void OCLSVM::run() {
  if (!isOpenClSvmAvailable(devices_[_deviceId])) {
    printf("Device does not support any SVM features, skipping...\n");
    return;
  }

  if (_openTest == 0) {
    runFineGrainedBuffer();
  } else if (_openTest == 1) {
    runFineGrainedSystem();
  } else if (_openTest == 2) {
    runFineGrainedSystemLargeAllocations();
  } else if (_openTest == 3) {
    runLinkedListSearchUsingFineGrainedSystem();
  } else if (_openTest == 4) {
    runPlatformAtomics();
  } else if (_openTest == 5) {
    runEnqueueOperations();
  } else if (_openTest == 6) {
    runSvmArgumentsAreRecognized();
  } else if (_openTest == 7) {
    runSvmCommandsExecutedInOrder();
  } else if (_openTest == 8) {
    runIdentifySvmBuffers();
  }
}

unsigned int OCLSVM::close(void) { return OCLTestImp::close(); }
