futhark-0.7.3: rts/c/opencl.h
/* The simple OpenCL runtime framework used by Futhark. */
#define CL_USE_DEPRECATED_OPENCL_1_2_APIS
#ifdef __APPLE__
#include <OpenCL/cl.h>
#else
#include <CL/cl.h>
#endif
#define OPENCL_SUCCEED_FATAL(e) opencl_succeed_fatal(e, #e, __FILE__, __LINE__)
#define OPENCL_SUCCEED_NONFATAL(e) opencl_succeed_nonfatal(e, #e, __FILE__, __LINE__)
// Take care not to override an existing error.
#define OPENCL_SUCCEED_OR_RETURN(e) { \
char *error = OPENCL_SUCCEED_NONFATAL(e); \
if (error) { \
if (!ctx->error) { \
ctx->error = error; \
return bad; \
} else { \
free(error); \
} \
} \
}
// OPENCL_SUCCEED_OR_RETURN returns the value of the variable 'bad' in
// scope. By default, it will be this one. Create a local variable
// of some other type if needed. This is a bit of a hack, but it
// saves effort in the code generator.
static const int bad = 1;
struct opencl_config {
int debugging;
int logging;
int preferred_device_num;
const char *preferred_platform;
const char *preferred_device;
const char* dump_program_to;
const char* load_program_from;
size_t default_group_size;
size_t default_num_groups;
size_t default_tile_size;
size_t default_threshold;
size_t transpose_block_dim;
int default_group_size_changed;
int default_tile_size_changed;
int num_sizes;
const char **size_names;
size_t *size_values;
const char **size_classes;
const char **size_entry_points;
};
void opencl_config_init(struct opencl_config *cfg,
int num_sizes,
const char *size_names[],
size_t *size_values,
const char *size_classes[],
const char *size_entry_points[]) {
cfg->debugging = 0;
cfg->logging = 0;
cfg->preferred_device_num = 0;
cfg->preferred_platform = "";
cfg->preferred_device = "";
cfg->dump_program_to = NULL;
cfg->load_program_from = NULL;
cfg->default_group_size = 256;
cfg->default_num_groups = 128;
cfg->default_tile_size = 32;
cfg->default_threshold = 32*1024;
cfg->transpose_block_dim = 16;
cfg->default_group_size_changed = 0;
cfg->default_tile_size_changed = 0;
cfg->num_sizes = num_sizes;
cfg->size_names = size_names;
cfg->size_values = size_values;
cfg->size_classes = size_classes;
cfg->size_entry_points = size_entry_points;
}
/* An entry in the free list. May be invalid, to avoid having to
deallocate entries as soon as they are removed. There is also a
tag, to help with memory reuse. */
struct opencl_free_list_entry {
size_t size;
cl_mem mem;
const char *tag;
unsigned char valid;
};
struct opencl_free_list {
struct opencl_free_list_entry *entries; // Pointer to entries.
int capacity; // Number of entries.
int used; // Number of valid entries.
};
void free_list_init(struct opencl_free_list *l) {
l->capacity = 30; // Picked arbitrarily.
l->used = 0;
l->entries = malloc(sizeof(struct opencl_free_list_entry) * l->capacity);
for (int i = 0; i < l->capacity; i++) {
l->entries[i].valid = 0;
}
}
/* Remove invalid entries from the free list. */
void free_list_pack(struct opencl_free_list *l) {
int p = 0;
for (int i = 0; i < l->capacity; i++) {
if (l->entries[i].valid) {
l->entries[p] = l->entries[i];
p++;
}
}
// Now p == l->used.
l->entries = realloc(l->entries, l->used * sizeof(struct opencl_free_list_entry));
l->capacity = l->used;
}
void free_list_destroy(struct opencl_free_list *l) {
assert(l->used == 0);
free(l->entries);
}
int free_list_find_invalid(struct opencl_free_list *l) {
int i;
for (i = 0; i < l->capacity; i++) {
if (!l->entries[i].valid) {
break;
}
}
return i;
}
void free_list_insert(struct opencl_free_list *l, size_t size, cl_mem mem, const char *tag) {
int i = free_list_find_invalid(l);
if (i == l->capacity) {
// List is full; so we have to grow it.
int new_capacity = l->capacity * 2 * sizeof(struct opencl_free_list_entry);
l->entries = realloc(l->entries, new_capacity);
for (int j = 0; j < l->capacity; j++) {
l->entries[j+l->capacity].valid = 0;
}
l->capacity *= 2;
}
// Now 'i' points to the first invalid entry.
l->entries[i].valid = 1;
l->entries[i].size = size;
l->entries[i].mem = mem;
l->entries[i].tag = tag;
l->used++;
}
/* Find and remove a memory block of at least the desired size and
tag. Returns 0 on success. */
int free_list_find(struct opencl_free_list *l, const char *tag, size_t *size_out, cl_mem *mem_out) {
int i;
for (i = 0; i < l->capacity; i++) {
if (l->entries[i].valid && l->entries[i].tag == tag) {
l->entries[i].valid = 0;
*size_out = l->entries[i].size;
*mem_out = l->entries[i].mem;
l->used--;
return 0;
}
}
return 1;
}
/* Remove the first block in the free list. Returns 0 if a block was
removed, and nonzero if the free list was already empty. */
int free_list_first(struct opencl_free_list *l, cl_mem *mem_out) {
for (int i = 0; i < l->capacity; i++) {
if (l->entries[i].valid) {
l->entries[i].valid = 0;
*mem_out = l->entries[i].mem;
l->used--;
return 0;
}
}
return 1;
}
struct opencl_context {
cl_device_id device;
cl_context ctx;
cl_command_queue queue;
struct opencl_config cfg;
struct opencl_free_list free_list;
size_t max_group_size;
size_t max_num_groups;
size_t max_tile_size;
size_t max_threshold;
size_t lockstep_width;
};
struct opencl_device_option {
cl_platform_id platform;
cl_device_id device;
cl_device_type device_type;
char *platform_name;
char *device_name;
};
/* This function must be defined by the user. It is invoked by
setup_opencl() after the platform and device has been found, but
before the program is loaded. Its intended use is to tune
constants based on the selected platform and device. */
static void post_opencl_setup(struct opencl_context*, struct opencl_device_option*);
static char *strclone(const char *str) {
size_t size = strlen(str) + 1;
char *copy = malloc(size);
if (copy == NULL) {
return NULL;
}
memcpy(copy, str, size);
return copy;
}
static const char* opencl_error_string(unsigned int err)
{
switch (err) {
case CL_SUCCESS: return "Success!";
case CL_DEVICE_NOT_FOUND: return "Device not found.";
case CL_DEVICE_NOT_AVAILABLE: return "Device not available";
case CL_COMPILER_NOT_AVAILABLE: return "Compiler not available";
case CL_MEM_OBJECT_ALLOCATION_FAILURE: return "Memory object allocation failure";
case CL_OUT_OF_RESOURCES: return "Out of resources";
case CL_OUT_OF_HOST_MEMORY: return "Out of host memory";
case CL_PROFILING_INFO_NOT_AVAILABLE: return "Profiling information not available";
case CL_MEM_COPY_OVERLAP: return "Memory copy overlap";
case CL_IMAGE_FORMAT_MISMATCH: return "Image format mismatch";
case CL_IMAGE_FORMAT_NOT_SUPPORTED: return "Image format not supported";
case CL_BUILD_PROGRAM_FAILURE: return "Program build failure";
case CL_MAP_FAILURE: return "Map failure";
case CL_INVALID_VALUE: return "Invalid value";
case CL_INVALID_DEVICE_TYPE: return "Invalid device type";
case CL_INVALID_PLATFORM: return "Invalid platform";
case CL_INVALID_DEVICE: return "Invalid device";
case CL_INVALID_CONTEXT: return "Invalid context";
case CL_INVALID_QUEUE_PROPERTIES: return "Invalid queue properties";
case CL_INVALID_COMMAND_QUEUE: return "Invalid command queue";
case CL_INVALID_HOST_PTR: return "Invalid host pointer";
case CL_INVALID_MEM_OBJECT: return "Invalid memory object";
case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: return "Invalid image format descriptor";
case CL_INVALID_IMAGE_SIZE: return "Invalid image size";
case CL_INVALID_SAMPLER: return "Invalid sampler";
case CL_INVALID_BINARY: return "Invalid binary";
case CL_INVALID_BUILD_OPTIONS: return "Invalid build options";
case CL_INVALID_PROGRAM: return "Invalid program";
case CL_INVALID_PROGRAM_EXECUTABLE: return "Invalid program executable";
case CL_INVALID_KERNEL_NAME: return "Invalid kernel name";
case CL_INVALID_KERNEL_DEFINITION: return "Invalid kernel definition";
case CL_INVALID_KERNEL: return "Invalid kernel";
case CL_INVALID_ARG_INDEX: return "Invalid argument index";
case CL_INVALID_ARG_VALUE: return "Invalid argument value";
case CL_INVALID_ARG_SIZE: return "Invalid argument size";
case CL_INVALID_KERNEL_ARGS: return "Invalid kernel arguments";
case CL_INVALID_WORK_DIMENSION: return "Invalid work dimension";
case CL_INVALID_WORK_GROUP_SIZE: return "Invalid work group size";
case CL_INVALID_WORK_ITEM_SIZE: return "Invalid work item size";
case CL_INVALID_GLOBAL_OFFSET: return "Invalid global offset";
case CL_INVALID_EVENT_WAIT_LIST: return "Invalid event wait list";
case CL_INVALID_EVENT: return "Invalid event";
case CL_INVALID_OPERATION: return "Invalid operation";
case CL_INVALID_GL_OBJECT: return "Invalid OpenGL object";
case CL_INVALID_BUFFER_SIZE: return "Invalid buffer size";
case CL_INVALID_MIP_LEVEL: return "Invalid mip-map level";
default: return "Unknown";
}
}
static void opencl_succeed_fatal(unsigned int ret,
const char *call,
const char *file,
int line) {
if (ret != CL_SUCCESS) {
panic(-1, "%s:%d: OpenCL call\n %s\nfailed with error code %d (%s)\n",
file, line, call, ret, opencl_error_string(ret));
}
}
static char* opencl_succeed_nonfatal(unsigned int ret,
const char *call,
const char *file,
int line) {
if (ret != CL_SUCCESS) {
return msgprintf("%s:%d: OpenCL call\n %s\nfailed with error code %d (%s)\n",
file, line, call, ret, opencl_error_string(ret));
} else {
return NULL;
}
}
void set_preferred_platform(struct opencl_config *cfg, const char *s) {
cfg->preferred_platform = s;
}
void set_preferred_device(struct opencl_config *cfg, const char *s) {
int x = 0;
if (*s == '#') {
s++;
while (isdigit(*s)) {
x = x * 10 + (*s++)-'0';
}
// Skip trailing spaces.
while (isspace(*s)) {
s++;
}
}
cfg->preferred_device = s;
cfg->preferred_device_num = x;
}
static char* opencl_platform_info(cl_platform_id platform,
cl_platform_info param) {
size_t req_bytes;
char *info;
OPENCL_SUCCEED_FATAL(clGetPlatformInfo(platform, param, 0, NULL, &req_bytes));
info = malloc(req_bytes);
OPENCL_SUCCEED_FATAL(clGetPlatformInfo(platform, param, req_bytes, info, NULL));
return info;
}
static char* opencl_device_info(cl_device_id device,
cl_device_info param) {
size_t req_bytes;
char *info;
OPENCL_SUCCEED_FATAL(clGetDeviceInfo(device, param, 0, NULL, &req_bytes));
info = malloc(req_bytes);
OPENCL_SUCCEED_FATAL(clGetDeviceInfo(device, param, req_bytes, info, NULL));
return info;
}
static void opencl_all_device_options(struct opencl_device_option **devices_out,
size_t *num_devices_out) {
size_t num_devices = 0, num_devices_added = 0;
cl_platform_id *all_platforms;
cl_uint *platform_num_devices;
cl_uint num_platforms;
// Find the number of platforms.
OPENCL_SUCCEED_FATAL(clGetPlatformIDs(0, NULL, &num_platforms));
// Make room for them.
all_platforms = calloc(num_platforms, sizeof(cl_platform_id));
platform_num_devices = calloc(num_platforms, sizeof(cl_uint));
// Fetch all the platforms.
OPENCL_SUCCEED_FATAL(clGetPlatformIDs(num_platforms, all_platforms, NULL));
// Count the number of devices for each platform, as well as the
// total number of devices.
for (cl_uint i = 0; i < num_platforms; i++) {
if (clGetDeviceIDs(all_platforms[i], CL_DEVICE_TYPE_ALL,
0, NULL, &platform_num_devices[i]) == CL_SUCCESS) {
num_devices += platform_num_devices[i];
} else {
platform_num_devices[i] = 0;
}
}
// Make room for all the device options.
struct opencl_device_option *devices =
calloc(num_devices, sizeof(struct opencl_device_option));
// Loop through the platforms, getting information about their devices.
for (cl_uint i = 0; i < num_platforms; i++) {
cl_platform_id platform = all_platforms[i];
cl_uint num_platform_devices = platform_num_devices[i];
if (num_platform_devices == 0) {
continue;
}
char *platform_name = opencl_platform_info(platform, CL_PLATFORM_NAME);
cl_device_id *platform_devices =
calloc(num_platform_devices, sizeof(cl_device_id));
// Fetch all the devices.
OPENCL_SUCCEED_FATAL(clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL,
num_platform_devices, platform_devices, NULL));
// Loop through the devices, adding them to the devices array.
for (cl_uint i = 0; i < num_platform_devices; i++) {
char *device_name = opencl_device_info(platform_devices[i], CL_DEVICE_NAME);
devices[num_devices_added].platform = platform;
devices[num_devices_added].device = platform_devices[i];
OPENCL_SUCCEED_FATAL(clGetDeviceInfo(platform_devices[i], CL_DEVICE_TYPE,
sizeof(cl_device_type),
&devices[num_devices_added].device_type,
NULL));
// We don't want the structs to share memory, so copy the platform name.
// Each device name is already unique.
devices[num_devices_added].platform_name = strclone(platform_name);
devices[num_devices_added].device_name = device_name;
num_devices_added++;
}
free(platform_devices);
free(platform_name);
}
free(all_platforms);
free(platform_num_devices);
*devices_out = devices;
*num_devices_out = num_devices;
}
static int is_blacklisted(const char *platform_name, const char *device_name,
const struct opencl_config *cfg) {
if (strcmp(cfg->preferred_platform, "") != 0 ||
strcmp(cfg->preferred_device, "") != 0) {
return 0;
} else if (strstr(platform_name, "Apple") != NULL &&
strstr(device_name, "Intel(R) Core(TM)") != NULL) {
return 1;
} else {
return 0;
}
}
static struct opencl_device_option get_preferred_device(const struct opencl_config *cfg) {
struct opencl_device_option *devices;
size_t num_devices;
opencl_all_device_options(&devices, &num_devices);
int num_device_matches = 0;
for (size_t i = 0; i < num_devices; i++) {
struct opencl_device_option device = devices[i];
if (!is_blacklisted(device.platform_name, device.device_name, cfg) &&
strstr(device.platform_name, cfg->preferred_platform) != NULL &&
strstr(device.device_name, cfg->preferred_device) != NULL &&
num_device_matches++ == cfg->preferred_device_num) {
// Free all the platform and device names, except the ones we have chosen.
for (size_t j = 0; j < num_devices; j++) {
if (j != i) {
free(devices[j].platform_name);
free(devices[j].device_name);
}
}
free(devices);
return device;
}
}
panic(1, "Could not find acceptable OpenCL device.\n");
exit(1); // Never reached
}
static void describe_device_option(struct opencl_device_option device) {
fprintf(stderr, "Using platform: %s\n", device.platform_name);
fprintf(stderr, "Using device: %s\n", device.device_name);
}
static cl_build_status build_opencl_program(cl_program program, cl_device_id device, const char* options) {
cl_int ret_val = clBuildProgram(program, 1, &device, options, NULL, NULL);
// Avoid termination due to CL_BUILD_PROGRAM_FAILURE
if (ret_val != CL_SUCCESS && ret_val != CL_BUILD_PROGRAM_FAILURE) {
assert(ret_val == 0);
}
cl_build_status build_status;
ret_val = clGetProgramBuildInfo(program,
device,
CL_PROGRAM_BUILD_STATUS,
sizeof(cl_build_status),
&build_status,
NULL);
assert(ret_val == 0);
if (build_status != CL_SUCCESS) {
char *build_log;
size_t ret_val_size;
ret_val = clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &ret_val_size);
assert(ret_val == 0);
build_log = malloc(ret_val_size+1);
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, ret_val_size, build_log, NULL);
assert(ret_val == 0);
// The spec technically does not say whether the build log is zero-terminated, so let's be careful.
build_log[ret_val_size] = '\0';
fprintf(stderr, "Build log:\n%s\n", build_log);
free(build_log);
}
return build_status;
}
/* Fields in a bitmask indicating which types we must be sure are
available. */
enum opencl_required_type { OPENCL_F64 = 1 };
// We take as input several strings representing the program, because
// C does not guarantee that the compiler supports particularly large
// literals. Notably, Visual C has a limit of 2048 characters. The
// array must be NULL-terminated.
static cl_program setup_opencl_with_command_queue(struct opencl_context *ctx,
cl_command_queue queue,
const char *srcs[],
int required_types) {
int error;
ctx->queue = queue;
OPENCL_SUCCEED_FATAL(clGetCommandQueueInfo(ctx->queue, CL_QUEUE_CONTEXT, sizeof(cl_context), &ctx->ctx, NULL));
// Fill out the device info. This is redundant work if we are
// called from setup_opencl() (which is the common case), but I
// doubt it matters much.
struct opencl_device_option device_option;
OPENCL_SUCCEED_FATAL(clGetCommandQueueInfo(ctx->queue, CL_QUEUE_DEVICE,
sizeof(cl_device_id),
&device_option.device,
NULL));
OPENCL_SUCCEED_FATAL(clGetDeviceInfo(device_option.device, CL_DEVICE_PLATFORM,
sizeof(cl_platform_id),
&device_option.platform,
NULL));
OPENCL_SUCCEED_FATAL(clGetDeviceInfo(device_option.device, CL_DEVICE_TYPE,
sizeof(cl_device_type),
&device_option.device_type,
NULL));
device_option.platform_name = opencl_platform_info(device_option.platform, CL_PLATFORM_NAME);
device_option.device_name = opencl_device_info(device_option.device, CL_DEVICE_NAME);
ctx->device = device_option.device;
if (required_types & OPENCL_F64) {
cl_uint supported;
OPENCL_SUCCEED_FATAL(clGetDeviceInfo(device_option.device, CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE,
sizeof(cl_uint), &supported, NULL));
if (!supported) {
panic(1, "Program uses double-precision floats, but this is not supported on the chosen device: %s",
device_option.device_name);
}
}
size_t max_group_size;
OPENCL_SUCCEED_FATAL(clGetDeviceInfo(device_option.device, CL_DEVICE_MAX_WORK_GROUP_SIZE,
sizeof(size_t), &max_group_size, NULL));
size_t max_tile_size = sqrt(max_group_size);
if (max_group_size < ctx->cfg.default_group_size) {
if (ctx->cfg.default_group_size_changed) {
fprintf(stderr, "Note: Device limits default group size to %zu (down from %zu).\n",
max_group_size, ctx->cfg.default_group_size);
}
ctx->cfg.default_group_size = max_group_size;
}
if (max_tile_size < ctx->cfg.default_tile_size) {
if (ctx->cfg.default_tile_size_changed) {
fprintf(stderr, "Note: Device limits default tile size to %zu (down from %zu).\n",
max_tile_size, ctx->cfg.default_tile_size);
}
ctx->cfg.default_tile_size = max_tile_size;
}
ctx->max_group_size = max_group_size;
ctx->max_tile_size = max_tile_size; // No limit.
ctx->max_threshold = ctx->max_num_groups = 0; // No limit.
// Now we go through all the sizes, clamp them to the valid range,
// or set them to the default.
for (int i = 0; i < ctx->cfg.num_sizes; i++) {
const char *size_class = ctx->cfg.size_classes[i];
size_t *size_value = &ctx->cfg.size_values[i];
const char* size_name = ctx->cfg.size_names[i];
size_t max_value, default_value;
if (strstr(size_class, "group_size") == size_class) {
max_value = max_group_size;
default_value = ctx->cfg.default_group_size;
} else if (strstr(size_class, "num_groups") == size_class) {
max_value = max_group_size; // Futhark assumes this constraint.
default_value = ctx->cfg.default_num_groups;
} else if (strstr(size_class, "tile_size") == size_class) {
max_value = sqrt(max_group_size);
default_value = ctx->cfg.default_tile_size;
} else if (strstr(size_class, "threshold") == size_class) {
max_value = 0; // No limit.
default_value = ctx->cfg.default_threshold;
} else {
panic(1, "Unknown size class for size '%s': %s\n", size_name, size_class);
}
if (*size_value == 0) {
*size_value = default_value;
} else if (max_value > 0 && *size_value > max_value) {
fprintf(stderr, "Note: Device limits %s to %d (down from %d)\n",
size_name, (int)max_value, (int)*size_value);
*size_value = max_value;
}
}
// Make sure this function is defined.
post_opencl_setup(ctx, &device_option);
if (ctx->cfg.logging) {
fprintf(stderr, "Lockstep width: %d\n", (int)ctx->lockstep_width);
fprintf(stderr, "Default group size: %d\n", (int)ctx->cfg.default_group_size);
fprintf(stderr, "Default number of groups: %d\n", (int)ctx->cfg.default_num_groups);
}
char *fut_opencl_src = NULL;
size_t src_size = 0;
// Maybe we have to read OpenCL source from somewhere else (used for debugging).
if (ctx->cfg.load_program_from != NULL) {
FILE *f = fopen(ctx->cfg.load_program_from, "r");
assert(f != NULL);
fseek(f, 0, SEEK_END);
src_size = ftell(f);
fseek(f, 0, SEEK_SET);
fut_opencl_src = malloc(src_size);
assert(fread(fut_opencl_src, 1, src_size, f) == src_size);
fclose(f);
} else {
// Build the OpenCL program. First we have to concatenate all the fragments.
for (const char **src = srcs; src && *src; src++) {
src_size += strlen(*src);
}
fut_opencl_src = malloc(src_size + 1);
size_t n, i;
for (i = 0, n = 0; srcs && srcs[i]; i++) {
strncpy(fut_opencl_src+n, srcs[i], src_size-n);
n += strlen(srcs[i]);
}
fut_opencl_src[src_size] = 0;
}
cl_program prog;
error = 0;
const char* src_ptr[] = {fut_opencl_src};
if (ctx->cfg.dump_program_to != NULL) {
FILE *f = fopen(ctx->cfg.dump_program_to, "w");
assert(f != NULL);
fputs(fut_opencl_src, f);
fclose(f);
}
prog = clCreateProgramWithSource(ctx->ctx, 1, src_ptr, &src_size, &error);
assert(error == 0);
int compile_opts_size = 1024;
for (int i = 0; i < ctx->cfg.num_sizes; i++) {
compile_opts_size += strlen(ctx->cfg.size_names[i]) + 20;
}
char *compile_opts = malloc(compile_opts_size);
int w = snprintf(compile_opts, compile_opts_size,
"-DFUT_BLOCK_DIM=%d -DLOCKSTEP_WIDTH=%d ",
(int)ctx->cfg.transpose_block_dim,
(int)ctx->lockstep_width);
for (int i = 0; i < ctx->cfg.num_sizes; i++) {
w += snprintf(compile_opts+w, compile_opts_size-w,
"-D%s=%d ", ctx->cfg.size_names[i],
(int)ctx->cfg.size_values[i]);
}
OPENCL_SUCCEED_FATAL(build_opencl_program(prog, device_option.device, compile_opts));
free(compile_opts);
free(fut_opencl_src);
return prog;
}
static cl_program setup_opencl(struct opencl_context *ctx,
const char *srcs[],
int required_types) {
ctx->lockstep_width = 1;
free_list_init(&ctx->free_list);
struct opencl_device_option device_option = get_preferred_device(&ctx->cfg);
if (ctx->cfg.logging) {
describe_device_option(device_option);
}
// Note that NVIDIA's OpenCL requires the platform property
cl_context_properties properties[] = {
CL_CONTEXT_PLATFORM,
(cl_context_properties)device_option.platform,
0
};
cl_int error;
ctx->ctx = clCreateContext(properties, 1, &device_option.device, NULL, NULL, &error);
assert(error == 0);
cl_command_queue queue = clCreateCommandQueue(ctx->ctx, device_option.device, 0, &error);
assert(error == 0);
return setup_opencl_with_command_queue(ctx, queue, srcs, required_types);
}
// Allocate memory from driver. The problem is that OpenCL may perform
// lazy allocation, so we cannot know whether an allocation succeeded
// until the first time we try to use it. Hence we immediately
// perform a write to see if the allocation succeeded. This is slow,
// but the assumption is that this operation will be rare (most things
// will go through the free list).
int opencl_alloc_actual(struct opencl_context *ctx, size_t size, cl_mem *mem_out) {
int error;
*mem_out = clCreateBuffer(ctx->ctx, CL_MEM_READ_WRITE, size, NULL, &error);
if (error != CL_SUCCESS) {
return error;
}
int x = 2;
error = clEnqueueWriteBuffer(ctx->queue, *mem_out, 1, 0, sizeof(x), &x, 0, NULL, NULL);
// No need to wait for completion here. clWaitForEvents() cannot
// return mem object allocation failures. This implies that the
// buffer is faulted onto the device on enqueue. (Observation by
// Andreas Kloeckner.)
return error;
}
int opencl_alloc(struct opencl_context *ctx, size_t min_size, const char *tag, cl_mem *mem_out) {
assert(min_size >= 0);
if (min_size < sizeof(int)) {
min_size = sizeof(int);
}
size_t size;
if (free_list_find(&ctx->free_list, tag, &size, mem_out) == 0) {
// Successfully found a free block. Is it big enough?
//
// FIXME: we might also want to check whether the block is *too
// big*, to avoid internal fragmentation. However, this can
// sharply impact performance on programs where arrays change size
// frequently. Fortunately, such allocations are usually fairly
// short-lived, as they are necessarily within a loop, so the risk
// of internal fragmentation resulting in an OOM situation is
// limited. However, it would be preferable if we could go back
// and *shrink* oversize allocations when we encounter an OOM
// condition. That is technically feasible, since we do not
// expose OpenCL pointer values directly to the application, but
// instead rely on a level of indirection.
if (size >= min_size) {
return CL_SUCCESS;
} else {
// Not just right - free it.
int error = clReleaseMemObject(*mem_out);
if (error != CL_SUCCESS) {
return error;
}
}
}
// We have to allocate a new block from the driver. If the
// allocation does not succeed, then we might be in an out-of-memory
// situation. We now start freeing things from the free list until
// we think we have freed enough that the allocation will succeed.
// Since we don't know how far the allocation is from fitting, we
// have to check after every deallocation. This might be pretty
// expensive. Let's hope that this case is hit rarely.
int error = opencl_alloc_actual(ctx, min_size, mem_out);
while (error == CL_MEM_OBJECT_ALLOCATION_FAILURE) {
cl_mem mem;
if (free_list_first(&ctx->free_list, &mem) == 0) {
error = clReleaseMemObject(mem);
if (error != CL_SUCCESS) {
return error;
}
} else {
break;
}
error = opencl_alloc_actual(ctx, min_size, mem_out);
}
return error;
}
int opencl_free(struct opencl_context *ctx, cl_mem mem, const char *tag) {
size_t size;
cl_mem existing_mem;
// If there is already a block with this tag, then remove it.
if (free_list_find(&ctx->free_list, tag, &size, &existing_mem) == 0) {
int error = clReleaseMemObject(existing_mem);
if (error != CL_SUCCESS) {
return error;
}
}
int error = clGetMemObjectInfo(mem, CL_MEM_SIZE, sizeof(size_t), &size, NULL);
if (error == CL_SUCCESS) {
free_list_insert(&ctx->free_list, size, mem, tag);
}
return error;
}
int opencl_free_all(struct opencl_context *ctx) {
cl_mem mem;
free_list_pack(&ctx->free_list);
while (free_list_first(&ctx->free_list, &mem) == 0) {
int error = clReleaseMemObject(mem);
if (error != CL_SUCCESS) {
return error;
}
}
return CL_SUCCESS;
}