//
// Contains the autotuning logic and some utility functions.
// Note that all CUDA kernels have been moved to other .cu files
//

#include <stdio.h>
#include <map>
#include <algorithm>
#include <unistd.h> // usleep
#include <ctype.h> // tolower
#include "cuda_helper.h"

#include "salsa_kernel.h"

#include "nv_kernel2.h"
#include "titan_kernel.h"
#include "nv_kernel.h"
#include "kepler_kernel.h"
#include "fermi_kernel.h"
#include "test_kernel.h"

#include "miner.h"

#if defined(_WIN64) || defined(__x86_64__) || defined(__64BIT__)
#define MAXMEM 0x300000000ULL  // 12 GB (the largest Kepler)
#else
#define MAXMEM  0xFFFFFFFFULL  // nearly 4 GB (32 bit limitations)
#endif

// require CUDA 5.5 driver API
#define DMAJ 5
#define DMIN 5

// define some error checking macros
#define DELIMITER '/'
#define __FILENAME__ ( strrchr(__FILE__, DELIMITER) != NULL ? strrchr(__FILE__, DELIMITER)+1 : __FILE__ )

#undef checkCudaErrors
#define checkCudaErrors(x) \
{ \
	cudaGetLastError(); \
	x; \
	cudaError_t err = cudaGetLastError(); \
	if (err != cudaSuccess && !abort_flag) \
		applog(LOG_ERR, "GPU #%d: Err %d: %s (%s:%d)", device_map[thr_id], err, cudaGetErrorString(err), __FILENAME__, __LINE__); \
}

// some globals containing pointers to device memory (for chunked allocation)
// [MAX_GPUS] indexes up to MAX_GPUS threads (0...MAX_GPUS-1)
int       MAXWARPS[MAX_GPUS];
uint32_t* h_V[MAX_GPUS][TOTAL_WARP_LIMIT*64];          // NOTE: the *64 prevents buffer overflow for --keccak
uint32_t  h_V_extra[MAX_GPUS][TOTAL_WARP_LIMIT*64];    //       with really large kernel launch configurations

KernelInterface *Best_Kernel_Heuristics(cudaDeviceProp *props)
{
	KernelInterface *kernel = NULL;
	uint64_t N = 1UL << (opt_nfactor+1);

	if (IS_SCRYPT() || (IS_SCRYPT_JANE() && N <= 8192))
	{
		// high register count kernels (scrypt, low N-factor scrypt-jane)
		if (props->major > 3 || (props->major == 3 && props->minor >= 5))
			kernel = new NV2Kernel(); // we don't want this for Keccak though
		else if (props->major == 3 && props->minor == 0)
			kernel = new NVKernel();
		else
			kernel = new FermiKernel();
	}
	else
	{
	   // high N-factor scrypt-jane = low registers count kernels
	   if (props->major > 3 || (props->major == 3 && props->minor >= 5))
			kernel = new TitanKernel();
		else if (props->major == 3 && props->minor == 0)
			kernel = new KeplerKernel();
		else
			kernel = new TestKernel();
	}
	return kernel;
}


bool validate_config(char *config, int &b, int &w, KernelInterface **kernel = NULL, cudaDeviceProp *props = NULL)
{
	bool success = false;
	char kernelid = ' ';
	if (config != NULL)
	{
		if (config[0] == 'T' || config[0] == 'K' || config[0] == 'F' || config[0] == 'L' ||
			config[0] == 't' || config[0] == 'k' || config[0] == 'f' ||
			config[0] == 'Z' || config[0] == 'Y' || config[0] == 'X') {
			kernelid = config[0];
			config++;
		}

		if (config[0] >= '0' && config[0] <= '9')
			if (sscanf(config, "%dx%d", &b, &w) == 2)
				success = true;

		if (success && kernel != NULL)
		{
			switch (kernelid)
			{
				case 'T': case 'Z': *kernel = new NV2Kernel(); break;
				case 't':           *kernel = new TitanKernel(); break;
				case 'K': case 'Y': *kernel = new NVKernel(); break;
				case 'k':           *kernel = new KeplerKernel(); break;
				case 'F': case 'L': *kernel = new FermiKernel(); break;
				case 'f': case 'X': *kernel = new TestKernel(); break;
				case ' ': // choose based on device architecture
					*kernel = Best_Kernel_Heuristics(props);
				break;
			}
		}
	}
	return success;
}

std::map<int, int> context_blocks;
std::map<int, int> context_wpb;
std::map<int, bool> context_concurrent;
std::map<int, KernelInterface *> context_kernel;
std::map<int, uint32_t *> context_idata[2];
std::map<int, uint32_t *> context_odata[2];
std::map<int, cudaStream_t> context_streams[2];
std::map<int, uint32_t *> context_X[2];
std::map<int, uint32_t *> context_H[2];
std::map<int, cudaEvent_t> context_serialize[2];

// for SHA256 hashing on GPU
std::map<int, uint32_t *> context_tstate[2];
std::map<int, uint32_t *> context_ostate[2];
std::map<int, uint32_t *> context_hash[2];

int find_optimal_blockcount(int thr_id, KernelInterface* &kernel, bool &concurrent, int &wpb);

int cuda_throughput(int thr_id)
{
	int GRID_BLOCKS, WARPS_PER_BLOCK;
	if (context_blocks.find(thr_id) == context_blocks.end())
	{
#if 0
		CUcontext ctx;
		cuCtxCreate( &ctx, CU_CTX_SCHED_YIELD, device_map[thr_id] );
		cuCtxSetCurrent(ctx);
#else
		checkCudaErrors(cudaSetDevice(device_map[thr_id]));
		checkCudaErrors(cudaSetDeviceFlags(cudaDeviceScheduleYield));
#endif

		KernelInterface *kernel;
		bool concurrent;
		GRID_BLOCKS = find_optimal_blockcount(thr_id, kernel, concurrent, WARPS_PER_BLOCK);

		if(GRID_BLOCKS == 0)
			return 0;

		unsigned int THREADS_PER_WU = kernel->threads_per_wu();
		unsigned int mem_size = WU_PER_LAUNCH * sizeof(uint32_t) * 32;
		unsigned int state_size = WU_PER_LAUNCH * sizeof(uint32_t) * 8;

		// allocate device memory for scrypt_core inputs and outputs
		uint32_t *tmp;
		checkCudaErrors(cudaMalloc((void **) &tmp, mem_size)); context_idata[0][thr_id] = tmp;
		checkCudaErrors(cudaMalloc((void **) &tmp, mem_size)); context_idata[1][thr_id] = tmp;
		checkCudaErrors(cudaMalloc((void **) &tmp, mem_size)); context_odata[0][thr_id] = tmp;
		checkCudaErrors(cudaMalloc((void **) &tmp, mem_size)); context_odata[1][thr_id] = tmp;

		// allocate pinned host memory for scrypt hashes
		checkCudaErrors(cudaHostAlloc((void **) &tmp, state_size, cudaHostAllocDefault)); context_H[0][thr_id] = tmp;
		checkCudaErrors(cudaHostAlloc((void **) &tmp, state_size, cudaHostAllocDefault)); context_H[1][thr_id] = tmp;

		if (IS_SCRYPT())
		{
			if (parallel < 2)
			{
				// allocate pinned host memory for scrypt_core input/output
				checkCudaErrors(cudaHostAlloc((void **) &tmp, mem_size, cudaHostAllocDefault)); context_X[0][thr_id] = tmp;
				checkCudaErrors(cudaHostAlloc((void **) &tmp, mem_size, cudaHostAllocDefault)); context_X[1][thr_id] = tmp;
			}
			else
			{
				// allocate tstate, ostate, scrypt hash device memory
				checkCudaErrors(cudaMalloc((void **) &tmp, state_size)); context_tstate[0][thr_id] = tmp;
				checkCudaErrors(cudaMalloc((void **) &tmp, state_size)); context_tstate[1][thr_id] = tmp;
				checkCudaErrors(cudaMalloc((void **) &tmp, state_size)); context_ostate[0][thr_id] = tmp;
				checkCudaErrors(cudaMalloc((void **) &tmp, state_size)); context_ostate[1][thr_id] = tmp;
				checkCudaErrors(cudaMalloc((void **) &tmp, state_size)); context_hash[0][thr_id] = tmp;
				checkCudaErrors(cudaMalloc((void **) &tmp, state_size)); context_hash[1][thr_id] = tmp;
			}
		}
		else /* if (IS_SCRYPT_JANE()) */
		{
			// allocate pinned host memory for scrypt_core input/output
			checkCudaErrors(cudaHostAlloc((void **) &tmp, mem_size, cudaHostAllocDefault)); context_X[0][thr_id] = tmp;
			checkCudaErrors(cudaHostAlloc((void **) &tmp, mem_size, cudaHostAllocDefault)); context_X[1][thr_id] = tmp;

			checkCudaErrors(cudaMalloc((void **) &tmp, state_size)); context_hash[0][thr_id] = tmp;
			checkCudaErrors(cudaMalloc((void **) &tmp, state_size)); context_hash[1][thr_id] = tmp;
		}

		// create two CUDA streams
		cudaStream_t tmp2;
		checkCudaErrors( cudaStreamCreate(&tmp2) ); context_streams[0][thr_id] = tmp2;
		checkCudaErrors( cudaStreamCreate(&tmp2) ); context_streams[1][thr_id] = tmp2;

		// events used to serialize the kernel launches (we don't want any overlapping of kernels)
		cudaEvent_t tmp4;
		checkCudaErrors(cudaEventCreateWithFlags(&tmp4, cudaEventDisableTiming)); context_serialize[0][thr_id] = tmp4;
		checkCudaErrors(cudaEventCreateWithFlags(&tmp4, cudaEventDisableTiming)); context_serialize[1][thr_id] = tmp4;
		checkCudaErrors(cudaEventRecord(context_serialize[1][thr_id]));

		context_kernel[thr_id] = kernel;
		context_concurrent[thr_id] = concurrent;
		context_blocks[thr_id] = GRID_BLOCKS;
		context_wpb[thr_id] = WARPS_PER_BLOCK;
	}

	GRID_BLOCKS = context_blocks[thr_id];
	WARPS_PER_BLOCK = context_wpb[thr_id];
	unsigned int THREADS_PER_WU = context_kernel[thr_id]->threads_per_wu();
	return WU_PER_LAUNCH;
}

// Beginning of GPU Architecture definitions
inline int _ConvertSMVer2Cores(int major, int minor)
{
	// Defines for GPU Architecture types (using the SM version to determine the # of cores per SM
	typedef struct {
		int SM; // 0xMm (hexidecimal notation), M = SM Major version, and m = SM minor version
		int Cores;
	} sSMtoCores;

	sSMtoCores nGpuArchCoresPerSM[] = {
		{ 0x10, 8   }, // Tesla Generation (SM 1.0) G80 class
		{ 0x11, 8   }, // Tesla Generation (SM 1.1) G8x class
		{ 0x12, 8   }, // Tesla Generation (SM 1.2) G9x class
		{ 0x13, 8   }, // Tesla Generation (SM 1.3) GT200 class
		{ 0x20, 32  }, // Fermi Generation (SM 2.0) GF100 class
		{ 0x21, 48  }, // Fermi Generation (SM 2.1) GF10x class
		{ 0x30, 192 }, // Kepler Generation (SM 3.0) GK10x class - GK104 = 1536 cores / 8 SMs
		{ 0x35, 192 }, // Kepler Generation (SM 3.5) GK11x class
		{ 0x50, 128 }, // Maxwell Generation (SM 5.0) GTX750/750Ti
		{ 0x52, 128 }, // Maxwell Second Generation (SM 5.2) GTX980 = 2048 cores / 16 SMs - GTX970 1664 cores / 13 SMs
		{ -1, -1 },
	};

	int index = 0;
	while (nGpuArchCoresPerSM[index].SM != -1)
	{
		if (nGpuArchCoresPerSM[index].SM == ((major << 4) + minor)) {
			return nGpuArchCoresPerSM[index].Cores;
		}
		index++;
	}

	// If we don't find the values, we default use the previous one to run properly
	applog(LOG_WARNING, "MapSMtoCores for SM %d.%d is undefined. Default to use %d Cores/SM", major, minor, 128);
	return 128;
}

#ifdef WIN32
#include <windows.h>
static int console_width() {
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
	return csbi.srWindow.Right - csbi.srWindow.Left + 1;
}
#else
static inline int console_width() {
	return 999;
}
#endif

int find_optimal_blockcount(int thr_id, KernelInterface* &kernel, bool &concurrent, int &WARPS_PER_BLOCK)
{
	int cw = console_width();
	int optimal_blocks = 0;

	cudaDeviceProp props;
	checkCudaErrors(cudaGetDeviceProperties(&props, device_map[thr_id]));
	concurrent = (props.concurrentKernels > 0);

	WARPS_PER_BLOCK = -1;

	// if not specified, use interactive mode for devices that have the watchdog timer enabled
	if (device_interactive[thr_id] == -1)
		device_interactive[thr_id] = props.kernelExecTimeoutEnabled;

	// turn off texture cache if not otherwise specified
	if (device_texturecache[thr_id] == -1)
		device_texturecache[thr_id] = 0;

	// if not otherwise specified or required, turn single memory allocations off as they reduce
	// the amount of memory that we can allocate on Windows Vista, 7 and 8 (WDDM driver model issue)
	if (device_singlememory[thr_id] == -1) device_singlememory[thr_id] = 0;

	// figure out which kernel implementation to use
	if (!validate_config(device_config[thr_id], optimal_blocks, WARPS_PER_BLOCK, &kernel, &props)) {
		kernel = NULL;
		if (device_config[thr_id] != NULL) {
				 if (device_config[thr_id][0] == 'T' || device_config[thr_id][0] == 'Z')
				kernel = new NV2Kernel();
			else if (device_config[thr_id][0] == 't')
				kernel = new TitanKernel();
			else if (device_config[thr_id][0] == 'K' || device_config[thr_id][0] == 'Y')
				kernel = new NVKernel();
			else if (device_config[thr_id][0] == 'k')
				kernel = new KeplerKernel();
			else if (device_config[thr_id][0] == 'F' || device_config[thr_id][0] == 'L')
				kernel = new FermiKernel();
			else if (device_config[thr_id][0] == 'f' || device_config[thr_id][0] == 'X')
				kernel = new TestKernel();
		}
		if (kernel == NULL) kernel = Best_Kernel_Heuristics(&props);
	}

	if (kernel->get_major_version() > props.major || kernel->get_major_version() == props.major && kernel->get_minor_version() > props.minor)
	{
		applog(LOG_ERR, "GPU #%d: FATAL: the '%c' kernel requires %d.%d capability!", device_map[thr_id], kernel->get_identifier(), kernel->get_major_version(), kernel->get_minor_version());
		return 0;
	}

	// set whatever cache configuration and shared memory bank mode the kernel prefers
	checkCudaErrors(cudaDeviceSetCacheConfig(kernel->cache_config()));
	checkCudaErrors(cudaDeviceSetSharedMemConfig(kernel->shared_mem_config()));

	// some kernels (e.g. Titan) do not support the texture cache
	if (kernel->no_textures() && device_texturecache[thr_id]) {
		applog(LOG_WARNING, "GPU #%d: the '%c' kernel ignores the texture cache argument", device_map[thr_id], kernel->get_identifier());
		device_texturecache[thr_id] = 0;
	}

	// Texture caching only works with single memory allocation
	if (device_texturecache[thr_id]) device_singlememory[thr_id] = 1;

	if (kernel->single_memory() && !device_singlememory[thr_id]) {
		applog(LOG_WARNING, "GPU #%d: the '%c' kernel requires single memory allocation", device_map[thr_id], kernel->get_identifier());
		device_singlememory[thr_id] = 1;
	}

	if (device_lookup_gap[thr_id] == 0) device_lookup_gap[thr_id] = 1;
	if (!kernel->support_lookup_gap() && device_lookup_gap[thr_id] > 1)
	{
		applog(LOG_WARNING, "GPU #%d: the '%c' kernel does not support a lookup gap", device_map[thr_id], kernel->get_identifier());
		device_lookup_gap[thr_id] = 1;
	}

	if (opt_debug) {
		applog(LOG_INFO, "GPU #%d: interactive: %d, tex-cache: %d%s, single-alloc: %d", device_map[thr_id],
		   (device_interactive[thr_id]  != 0) ? 1 : 0,
		   (device_texturecache[thr_id] != 0) ? device_texturecache[thr_id] : 0, (device_texturecache[thr_id] != 0) ? "D" : "",
		   (device_singlememory[thr_id] != 0) ? 1 : 0 );
	}

	// number of threads collaborating on one work unit (hash)
	unsigned int THREADS_PER_WU = kernel->threads_per_wu();
	unsigned int LOOKUP_GAP = device_lookup_gap[thr_id];
	unsigned int BACKOFF = device_backoff[thr_id];
	unsigned int N = (1 << (opt_nfactor+1));
	double szPerWarp = (double)(SCRATCH * WU_PER_WARP * sizeof(uint32_t));
	//applog(LOG_INFO, "WU_PER_WARP=%u, THREADS_PER_WU=%u, LOOKUP_GAP=%u, BACKOFF=%u, SCRATCH=%u", WU_PER_WARP, THREADS_PER_WU, LOOKUP_GAP, BACKOFF, SCRATCH);
	applog(LOG_INFO, "GPU #%d: %d hashes / %.1f MB per warp.", device_map[thr_id], WU_PER_WARP, szPerWarp / (1024.0 * 1024.0));

	// compute highest MAXWARPS numbers for kernels allowing cudaBindTexture to succeed
	int MW_1D_4 = 134217728 / (SCRATCH * WU_PER_WARP / 4); // for uint4_t textures
	int MW_1D_2 = 134217728 / (SCRATCH * WU_PER_WARP / 2); // for uint2_t textures
	int MW_1D = kernel->get_texel_width() == 2 ? MW_1D_2 : MW_1D_4;

	uint32_t *d_V = NULL;
	if (device_singlememory[thr_id])
	{
		// if no launch config was specified, we simply
		// allocate the single largest memory chunk on the device that we can get
		if (validate_config(device_config[thr_id], optimal_blocks, WARPS_PER_BLOCK)) {
			MAXWARPS[thr_id] = optimal_blocks * WARPS_PER_BLOCK;
		}
		else {
			// compute no. of warps to allocate the largest number producing a single memory block
			// PROBLEM: one some devices, ALL allocations will fail if the first one failed. This sucks.
			size_t MEM_LIMIT = (size_t)min((unsigned long long)MAXMEM, (unsigned long long)props.totalGlobalMem);
			int warpmax = (int)min((unsigned long long)TOTAL_WARP_LIMIT, (unsigned long long)(MEM_LIMIT / szPerWarp));

			// run a bisection algorithm for memory allocation (way more reliable than the previous approach)
			int best = 0;
			int warp = (warpmax+1)/2;
			int interval = (warpmax+1)/2;
			while (interval > 0)
			{
				cudaGetLastError(); // clear the error state
				cudaMalloc((void **)&d_V, (size_t)(szPerWarp * warp));
				if (cudaGetLastError() == cudaSuccess) {
					checkCudaErrors(cudaFree(d_V)); d_V = NULL;
					if (warp > best) best = warp;
					if (warp == warpmax) break;
					interval = (interval+1)/2;
					warp += interval;
					if (warp > warpmax) warp = warpmax;
				}
				else
				{
					interval = interval/2;
					warp -= interval;
					if (warp < 1) warp = 1;
				}
			}
			// back off a bit from the largest possible allocation size
			MAXWARPS[thr_id] = ((100-BACKOFF)*best+50)/100;
		}

		// now allocate a buffer for determined MAXWARPS setting
		cudaGetLastError(); // clear the error state
		cudaMalloc((void **)&d_V, (size_t)SCRATCH * WU_PER_WARP * MAXWARPS[thr_id] * sizeof(uint32_t));
		if (cudaGetLastError() == cudaSuccess) {
			for (int i=0; i < MAXWARPS[thr_id]; ++i)
				h_V[thr_id][i] = d_V + SCRATCH * WU_PER_WARP * i;

			if (device_texturecache[thr_id] == 1)
			{
				if (validate_config(device_config[thr_id], optimal_blocks, WARPS_PER_BLOCK))
				{
					if ( optimal_blocks * WARPS_PER_BLOCK > MW_1D ) {
						applog(LOG_ERR, "GPU #%d: '%s' exceeds limits for 1D cache. Using 2D cache instead.", device_map[thr_id], device_config[thr_id]);
						device_texturecache[thr_id] = 2;
					}
				}
				// bind linear memory to a 1D texture reference
				if (kernel->get_texel_width() == 2)
					kernel->bindtexture_1D(d_V, SCRATCH * WU_PER_WARP * min(MAXWARPS[thr_id],MW_1D_2) * sizeof(uint32_t));
				else
					kernel->bindtexture_1D(d_V, SCRATCH * WU_PER_WARP * min(MAXWARPS[thr_id],MW_1D_4) * sizeof(uint32_t));
			}
			else if (device_texturecache[thr_id] == 2)
			{
				// bind pitch linear memory to a 2D texture reference
				if (kernel->get_texel_width() == 2)
					kernel->bindtexture_2D(d_V, SCRATCH/2, WU_PER_WARP * MAXWARPS[thr_id], SCRATCH*sizeof(uint32_t));
				else
					kernel->bindtexture_2D(d_V, SCRATCH/4, WU_PER_WARP * MAXWARPS[thr_id], SCRATCH*sizeof(uint32_t));
			}
		}
		else
		{
			applog(LOG_ERR, "GPU #%d: FATAL: Launch config '%s' requires too much memory!", device_map[thr_id], device_config[thr_id]);
			return 0;
		}
	}
	else
	{
		if (validate_config(device_config[thr_id], optimal_blocks, WARPS_PER_BLOCK))
			MAXWARPS[thr_id] = optimal_blocks * WARPS_PER_BLOCK;
		else
			MAXWARPS[thr_id] = TOTAL_WARP_LIMIT;

		// chunked memory allocation up to device limits
		int warp;
		for (warp = 0; warp < MAXWARPS[thr_id]; ++warp) {
			// work around partition camping problems by adding a random start address offset to each allocation
			h_V_extra[thr_id][warp] = (props.major == 1) ? (16 * (rand()%(16384/16))) : 0;
			cudaGetLastError(); // clear the error state
			cudaMalloc((void **) &h_V[thr_id][warp], (SCRATCH * WU_PER_WARP + h_V_extra[thr_id][warp])*sizeof(uint32_t));
			if (cudaGetLastError() == cudaSuccess) h_V[thr_id][warp] += h_V_extra[thr_id][warp];
			else {
				h_V_extra[thr_id][warp] = 0;

				// back off by several warp allocations to have some breathing room
				int remove = (BACKOFF*warp+50)/100;
				for (int i=0; warp > 0 && i < remove; ++i) {
					warp--;
					checkCudaErrors(cudaFree(h_V[thr_id][warp]-h_V_extra[thr_id][warp]));
					h_V[thr_id][warp] = NULL; h_V_extra[thr_id][warp] = 0;
				}

				break;
			}
		}
		MAXWARPS[thr_id] = warp;
	}
	kernel->set_scratchbuf_constants(MAXWARPS[thr_id], h_V[thr_id]);

	if (validate_config(device_config[thr_id], optimal_blocks, WARPS_PER_BLOCK))
	{
		if (optimal_blocks * WARPS_PER_BLOCK > MAXWARPS[thr_id])
		{
			applog(LOG_ERR, "GPU #%d: FATAL: Given launch config '%s' requires too much memory.", device_map[thr_id], device_config[thr_id]);
			return 0;
		}

		if (WARPS_PER_BLOCK > kernel->max_warps_per_block())
		{
			applog(LOG_ERR, "GPU #%d: FATAL: Given launch config '%s' exceeds warp limit for '%c' kernel.", device_map[thr_id], device_config[thr_id], kernel->get_identifier());
			return 0;
		}
	}
	else
	{
		if (device_config[thr_id] != NULL && strcasecmp("auto", device_config[thr_id]))
			applog(LOG_WARNING, "GPU #%d: Given launch config '%s' does not validate.", device_map[thr_id], device_config[thr_id]);

		if (opt_autotune)
		{
			applog(LOG_INFO, "GPU #%d: Performing auto-tuning, please wait 2 minutes...", device_map[thr_id]);

			// allocate device memory
			uint32_t *d_idata = NULL, *d_odata = NULL;
			unsigned int mem_size = MAXWARPS[thr_id] * WU_PER_WARP * sizeof(uint32_t) * 32;
			checkCudaErrors(cudaMalloc((void **) &d_idata, mem_size));
			checkCudaErrors(cudaMalloc((void **) &d_odata, mem_size));

			// pre-initialize some device memory
			uint32_t *h_idata = (uint32_t*)malloc(mem_size);
			for (unsigned int i=0; i < mem_size/sizeof(uint32_t); ++i) h_idata[i] = i*2654435761UL; // knuth's method
			checkCudaErrors(cudaMemcpy(d_idata, h_idata, mem_size, cudaMemcpyHostToDevice));
			free(h_idata);

			double best_hash_sec = 0.0;
			int best_wpb = 0;

			// auto-tuning loop
			{
				// we want to have enough total warps for half the multiprocessors at least
				// compute highest MAXWARPS number that we can support based on texture cache mode
				int MINTW = props.multiProcessorCount / 2;
				int MAXTW = (device_texturecache[thr_id] == 1) ? min(MAXWARPS[thr_id],MW_1D) : MAXWARPS[thr_id];

				// we want to have blocks for half the multiprocessors at least
				int MINB = props.multiProcessorCount / 2;
				int MAXB = MAXTW;

				double tmin = 0.05;

				applog(LOG_INFO, "GPU #%d: maximum total warps (BxW): %d", (int) device_map[thr_id], MAXTW);

				for (int GRID_BLOCKS = MINB; !abort_flag && GRID_BLOCKS <= MAXB; ++GRID_BLOCKS)
				{
					double Hash[32+1] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
					for (WARPS_PER_BLOCK = 1; !abort_flag && WARPS_PER_BLOCK <= kernel->max_warps_per_block(); ++WARPS_PER_BLOCK)
					{
						double hash_sec = 0;
						if (GRID_BLOCKS * WARPS_PER_BLOCK >= MINTW &&
							GRID_BLOCKS * WARPS_PER_BLOCK <= MAXTW)
						{
							// setup execution parameters
							dim3  grid(WU_PER_LAUNCH/WU_PER_BLOCK, 1, 1);
							dim3  threads(THREADS_PER_WU*WU_PER_BLOCK, 1, 1);

							struct timeval tv_start, tv_end;
							double tdelta = 0;

							checkCudaErrors(cudaDeviceSynchronize());
							gettimeofday(&tv_start, NULL);
							int repeat = 0;
							do  // average several measurements for better exactness
							{
								kernel->run_kernel(
									grid, threads, WARPS_PER_BLOCK, thr_id, NULL, d_idata, d_odata, N,
									LOOKUP_GAP, device_interactive[thr_id], true, device_texturecache[thr_id]
								);
								if(cudaDeviceSynchronize() != cudaSuccess)
									break;
								++repeat;
								gettimeofday(&tv_end, NULL);
								// for a better result averaging, measure for at least 50ms (10ms for Keccak)
							} while ((tdelta=(1e-6 * (tv_end.tv_usec-tv_start.tv_usec) + (tv_end.tv_sec-tv_start.tv_sec))) < tmin);
							if (cudaGetLastError() != cudaSuccess) continue;

							tdelta /= repeat; // BUGFIX: this averaging over multiple measurements was missing

							// for scrypt: in interactive mode only find launch configs where kernel launch times are short enough
							// TODO: instead we could reduce the batchsize parameter to meet the launch time requirement.
							if (IS_SCRYPT() && device_interactive[thr_id]
								&& GRID_BLOCKS > 2*props.multiProcessorCount && tdelta > 1.0/30)
							{
								if (WARPS_PER_BLOCK == 1) goto skip; else goto skip2;
							}

							hash_sec = (double)WU_PER_LAUNCH / tdelta;
							Hash[WARPS_PER_BLOCK] = hash_sec;
							if (hash_sec > best_hash_sec) {
								optimal_blocks = GRID_BLOCKS;
								best_hash_sec = hash_sec;
								best_wpb = WARPS_PER_BLOCK;
							}
						}
					}
skip2:
					if (opt_debug) {

						if (GRID_BLOCKS == MINB) {
							char line[512] = "    ";
							for (int i=1; i<=kernel->max_warps_per_block(); ++i) {
								char tmp[16]; sprintf(tmp, i < 10 ? "   x%-2d" : "  x%-2d ", i);
								strcat(line, tmp);
								if (cw == 80 && (i % 8 == 0 && i != kernel->max_warps_per_block()))
									strcat(line, "\n                          ");
							}
							applog(LOG_DEBUG, line);
						}

						char kMGT = ' '; bool flag;
						for (int j=0; j < 4; ++j) {
							flag=false; for (int i=1; i<=kernel->max_warps_per_block(); flag|=Hash[i] >= 1000, i++);
							if (flag)   for (int i=1; i<=kernel->max_warps_per_block(); Hash[i] /= 1000, i++);
							else break;
								 if (kMGT == ' ') kMGT = 'k';
							else if (kMGT == 'k') kMGT = 'M';
							else if (kMGT == 'M') kMGT = 'G';
							else if (kMGT == 'G') kMGT = 'T';
						}
						const char *format = "%5.4f%c";
						flag = false; for (int i=1; i<=kernel->max_warps_per_block(); flag|=Hash[i] >= 1, i++); if (flag) format = "%5.3f%c";
						flag = false; for (int i=1; i<=kernel->max_warps_per_block(); flag|=Hash[i] >= 10, i++); if (flag) format = "%5.2f%c";
						flag = false; for (int i=1; i<=kernel->max_warps_per_block(); flag|=Hash[i] >= 100, i++); if (flag) format = "%5.1f%c";

						char line[512]; sprintf(line, "%3d:", GRID_BLOCKS);
						for (int i=1; i<=kernel->max_warps_per_block(); ++i) {
							char tmp[16];
							if (Hash[i]>0)
								sprintf(tmp, format, Hash[i], (i<kernel->max_warps_per_block())?'|':' ');
							else
								sprintf(tmp, "     %c", (i<kernel->max_warps_per_block())?'|':' ');
							strcat(line, tmp);
							if (cw == 80 && (i % 8 == 0 && i != kernel->max_warps_per_block()))
								strcat(line, "\n                          ");
						}
						int n = strlen(line)-1; line[n++] = '|'; line[n++] = ' '; line[n++] = kMGT; line[n++] = '\0';
						strcat(line, "H/s");
						applog(LOG_DEBUG, line);
					}
				}
skip:           ;
			}

			checkCudaErrors(cudaFree(d_odata));
			checkCudaErrors(cudaFree(d_idata));

			WARPS_PER_BLOCK = best_wpb;
			applog(LOG_INFO, "GPU #%d: %7.2f hash/s with configuration %c%dx%d", device_map[thr_id], best_hash_sec, kernel->get_identifier(), optimal_blocks, WARPS_PER_BLOCK);
		}
		else
		{
			// Heuristics to find a good kernel launch configuration

			// base the initial block estimate on the number of multiprocessors
			int device_cores = props.multiProcessorCount * _ConvertSMVer2Cores(props.major, props.minor);

			// defaults, in case nothing else is chosen below
			optimal_blocks = 4 * device_cores / WU_PER_WARP;
			WARPS_PER_BLOCK = 2;

			// Based on compute capability, pick a known good block x warp configuration.
			if (props.major >= 3)
			{
				if (props.major == 3 && props.minor == 5) // GK110 (Tesla K20X, K20, GeForce GTX TITAN)
				{
					// TODO: what to do with Titan and Tesla K20(X)?
					// for now, do the same as for GTX 660Ti (2GB)
					optimal_blocks = (int)(optimal_blocks * 0.8809524);
					WARPS_PER_BLOCK = 2;
				}
				else // GK104, GK106, GK107 ...
				{
					if (MAXWARPS[thr_id] > (int)(optimal_blocks * 1.7261905) * 2)
					{
						// this results in 290x2 configuration on GTX 660Ti (3GB)
						// but it requires 3GB memory on the card!
						optimal_blocks = (int)(optimal_blocks * 1.7261905);
						WARPS_PER_BLOCK = 2;
					}
					else
					{
						// this results in 148x2 configuration on GTX 660Ti (2GB)
						optimal_blocks = (int)(optimal_blocks * 0.8809524);
						WARPS_PER_BLOCK = 2;
					}
				}
			}
			// 1st generation Fermi (compute 2.0) GF100, GF110
			else if (props.major == 2 && props.minor == 0)
			{
				// this results in a 60x4 configuration on GTX 570
				optimal_blocks = 4 * device_cores / WU_PER_WARP;
				WARPS_PER_BLOCK = 4;
			}
			// 2nd generation Fermi (compute 2.1) GF104,106,108,114,116
			else if (props.major == 2 && props.minor == 1)
			{
				// this results in a 56x2 configuration on GTX 460
				optimal_blocks = props.multiProcessorCount * 8;
				WARPS_PER_BLOCK = 2;
			}

			// in case we run out of memory with the automatically chosen configuration,
			// first back off with WARPS_PER_BLOCK, then reduce optimal_blocks.
			if (WARPS_PER_BLOCK==3 && optimal_blocks * WARPS_PER_BLOCK > MAXWARPS[thr_id])
				WARPS_PER_BLOCK = 2;
			while (optimal_blocks > 0 && optimal_blocks * WARPS_PER_BLOCK > MAXWARPS[thr_id])
				optimal_blocks--;
		}
	}

	applog(LOG_INFO, "GPU #%d: using launch configuration %c%dx%d", device_map[thr_id], kernel->get_identifier(), optimal_blocks, WARPS_PER_BLOCK);

	if (device_singlememory[thr_id])
	{
		if (MAXWARPS[thr_id] != optimal_blocks * WARPS_PER_BLOCK)
		{
			MAXWARPS[thr_id] = optimal_blocks * WARPS_PER_BLOCK;
			if (device_texturecache[thr_id] == 1)
				kernel->unbindtexture_1D();
			else if (device_texturecache[thr_id] == 2)
				kernel->unbindtexture_2D();
			checkCudaErrors(cudaFree(d_V)); d_V = NULL;

			cudaGetLastError(); // clear the error state
			cudaMalloc((void **)&d_V, (size_t)SCRATCH * WU_PER_WARP * MAXWARPS[thr_id] * sizeof(uint32_t));
			if (cudaGetLastError() == cudaSuccess) {
				for (int i=0; i < MAXWARPS[thr_id]; ++i)
					h_V[thr_id][i] = d_V + SCRATCH * WU_PER_WARP * i;

				if (device_texturecache[thr_id] == 1)
				{
					// bind linear memory to a 1D texture reference
					if (kernel->get_texel_width() == 2)
						kernel->bindtexture_1D(d_V, SCRATCH * WU_PER_WARP * MAXWARPS[thr_id] * sizeof(uint32_t));
					else
						kernel->bindtexture_1D(d_V, SCRATCH * WU_PER_WARP * MAXWARPS[thr_id] * sizeof(uint32_t));
				}
				else if (device_texturecache[thr_id] == 2)
				{
					// bind pitch linear memory to a 2D texture reference
					if (kernel->get_texel_width() == 2)
						kernel->bindtexture_2D(d_V, SCRATCH/2, WU_PER_WARP * MAXWARPS[thr_id], SCRATCH*sizeof(uint32_t));
					else
						kernel->bindtexture_2D(d_V, SCRATCH/4, WU_PER_WARP * MAXWARPS[thr_id], SCRATCH*sizeof(uint32_t));
				}

				// update pointers to scratch buffer in constant memory after reallocation
				kernel->set_scratchbuf_constants(MAXWARPS[thr_id], h_V[thr_id]);
			}
			else
			{
				applog(LOG_ERR, "GPU #%d: Unable to allocate enough memory for launch config '%s'.", device_map[thr_id], device_config[thr_id]);
			}
		}
	}
	else
	{
		// back off unnecessary memory allocations to have some breathing room
		while (MAXWARPS[thr_id] > 0 && MAXWARPS[thr_id] > optimal_blocks * WARPS_PER_BLOCK) {
			(MAXWARPS[thr_id])--;
			checkCudaErrors(cudaFree(h_V[thr_id][MAXWARPS[thr_id]]-h_V_extra[thr_id][MAXWARPS[thr_id]]));
			h_V[thr_id][MAXWARPS[thr_id]] = NULL; h_V_extra[thr_id][MAXWARPS[thr_id]] = 0;
		}
	}

	return optimal_blocks;
}

void cuda_scrypt_HtoD(int thr_id, uint32_t *X, int stream)
{
	unsigned int GRID_BLOCKS = context_blocks[thr_id];
	unsigned int WARPS_PER_BLOCK = context_wpb[thr_id];
	unsigned int THREADS_PER_WU = context_kernel[thr_id]->threads_per_wu();
	unsigned int mem_size = WU_PER_LAUNCH * sizeof(uint32_t) * 32;

	// copy host memory to device
	cudaMemcpyAsync(context_idata[stream][thr_id], X, mem_size, cudaMemcpyHostToDevice, context_streams[stream][thr_id]);
}

void cuda_scrypt_serialize(int thr_id, int stream)
{
	// if the device can concurrently execute multiple kernels, then we must
	// wait for the serialization event recorded by the other stream
	if (context_concurrent[thr_id] || device_interactive[thr_id])
		cudaStreamWaitEvent(context_streams[stream][thr_id], context_serialize[(stream+1)&1][thr_id], 0);
}

void cuda_scrypt_done(int thr_id, int stream)
{
	// record the serialization event in the current stream
	cudaEventRecord(context_serialize[stream][thr_id], context_streams[stream][thr_id]);
}

void cuda_scrypt_flush(int thr_id, int stream)
{
	// flush the work queue (required for WDDM drivers)
	cudaStreamSynchronize(context_streams[stream][thr_id]);
}

void cuda_scrypt_core(int thr_id, int stream, unsigned int N)
{
	unsigned int GRID_BLOCKS = context_blocks[thr_id];
	unsigned int WARPS_PER_BLOCK = context_wpb[thr_id];
	unsigned int THREADS_PER_WU = context_kernel[thr_id]->threads_per_wu();
	unsigned int LOOKUP_GAP = device_lookup_gap[thr_id];

	// setup execution parameters
	dim3 grid(WU_PER_LAUNCH/WU_PER_BLOCK, 1, 1);
	dim3 threads(THREADS_PER_WU*WU_PER_BLOCK, 1, 1);

	context_kernel[thr_id]->run_kernel(grid, threads, WARPS_PER_BLOCK, thr_id,
		context_streams[stream][thr_id], context_idata[stream][thr_id], context_odata[stream][thr_id],
		N, LOOKUP_GAP, device_interactive[thr_id], opt_benchmark, device_texturecache[thr_id]
	);
}

void cuda_scrypt_DtoH(int thr_id, uint32_t *X, int stream, bool postSHA)
{
	unsigned int GRID_BLOCKS = context_blocks[thr_id];
	unsigned int WARPS_PER_BLOCK = context_wpb[thr_id];
	unsigned int THREADS_PER_WU = context_kernel[thr_id]->threads_per_wu();
	unsigned int mem_size = WU_PER_LAUNCH * sizeof(uint32_t) * (postSHA ? 8 : 32);
	// copy result from device to host (asynchronously)
	checkCudaErrors(cudaMemcpyAsync(X, postSHA ? context_hash[stream][thr_id] : context_odata[stream][thr_id], mem_size, cudaMemcpyDeviceToHost, context_streams[stream][thr_id]));
}

bool cuda_scrypt_sync(int thr_id, int stream)
{
	cudaError_t err;
	uint32_t wait_us = 0;

	if (device_interactive[thr_id] && !opt_benchmark)
	{
		// For devices that also do desktop rendering or compositing, we want to free up some time slots.
		// That requires making a pause in work submission when there is no active task on the GPU,
		// and Device Synchronize ensures that.

		// this call was replaced by the loop below to workaround the high CPU usage issue
		//err = cudaDeviceSynchronize();

		while((err = cudaStreamQuery(context_streams[0][thr_id])) == cudaErrorNotReady ||
			  (err == cudaSuccess && (err = cudaStreamQuery(context_streams[1][thr_id])) == cudaErrorNotReady)) {
			usleep(50); wait_us+=50;
		}

		usleep(50); wait_us+=50;
	} else {
		// this call was replaced by the loop below to workaround the high CPU usage issue
		//err = cudaStreamSynchronize(context_streams[stream][thr_id]);

		while((err = cudaStreamQuery(context_streams[stream][thr_id])) == cudaErrorNotReady) {
			usleep(50); wait_us+=50;
		}
	}

	if (err != cudaSuccess) {
		if (!abort_flag)
			applog(LOG_ERR, "GPU #%d: CUDA error `%s` while waiting the kernel.", device_map[thr_id], cudaGetErrorString(err));
		return false;
	}

	//if (opt_debug) {
	//	applog(LOG_DEBUG, "GPU #%d: %s %u us", device_map[thr_id], __FUNCTION__, wait_us);
	//}

	return true;
}

uint32_t* cuda_transferbuffer(int thr_id, int stream)
{
	return context_X[stream][thr_id];
}

uint32_t* cuda_hashbuffer(int thr_id, int stream)
{
	return context_H[stream][thr_id];
}