/* * Copyright © 2012 Intel Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ #include #include #include "CL/cl.h" #include "CL/cl_gl.h" #include "cl_alloc.h" #include "cl_mutex.h" #include "cl_internals.h" #include "cl_driver.h" #include "cl_device_id.h" #include "cl_platform_id.h" #include "cl_context.h" #include "cl_program.h" typedef struct _cl_program_notify_data { void (CL_CALLBACK *pfn_notify)(cl_program program, void *user_data); void *user_data; pthread_mutex_t lock; cl_bool* is_done; } _cl_program_notify_data; typedef _cl_program_notify_data* cl_program_notify_data; #if 0 static cl_int cl_create_program_notify_data(cl_program p, cl_uint num_devices, const cl_device_id *device_list, void (CL_CALLBACK *pfn_notify)(cl_program program, void* user_data), void *user_data) { if (p->notify_data) { /* There is still some pending notification, we will not generate a new one.*/ DEBUGP("Can not add notify callback, because the last callback has not finished\n"); return CL_INVALID_OPERATION; } p->notify_data = } #endif LOCAL cl_int cl_retain_program(cl_program p) { assert(p); int ret = cl_ref_inc_if_positive(&p->ref_n); if (ret > 0) return ret; return 0; } static void cl_program_delete(cl_program p) { uint32_t i; assert(p); /* Remove it from the list */ assert(p->ctx); CL_MUTEX_LOCK(&p->ctx->lock); if (p->prev) p->prev->next = p->next; if (p->next) p->next->prev = p->prev; if (p->ctx->programs == p) p->ctx->programs = p->next; CL_MUTEX_UNLOCK(&p->ctx->lock); /* Program belongs to their parent context */ cl_release_context(p->ctx); for (i = 0; i < p->ctx->device_num; i++) { /* Release the build options. */ if (p->device_status[i].build_opts) { CL_FREE(p->device_status[i].build_opts); p->device_status[i].build_opts = NULL; } } if (p->kernel_names) CL_FREE(p->kernel_names); /* Destroy the sources if still allocated */ if (p->source) { CL_FREE(p->source); p->source = NULL; } if (p->device_status) CL_FREE(p->device_status); if (p->ctx->device_num > 1) { CL_FREE(p->pdata); } CL_MUTEX_DESTROY(&p->lock); p->magic = CL_MAGIC_DEAD_HEADER; /* For safety */ CL_FREE(p); } LOCAL void cl_release_program(cl_program p) { uint32_t i; assert(p); /* We are not done with it yet */ if (cl_ref_dec(&p->ref_n) > 1) return; /* No need to lock here and consider MT safe. The ref is 0 now, and no other one can access us. */ assert(p->kernel_created == 0); assert(p->kernels == NULL); for (i = 0; i < p->ctx->device_num; i++) { if (p->device_status[i].build_status == CL_BUILD_IN_PROGRESS) /* Some one still in building progress? */ assert(0); } for (i = 0; i < p->ctx->device_num; i++) { p->ctx->devices[i]->driver->release_program(p, p->ctx->devices[i]); } cl_program_delete(p); } static cl_program cl_program_new(cl_context ctx) { cl_program p = NULL; int i; /* Allocate the structure */ TRY_ALLOC_NO_ERR(p, CL_CALLOC(1, sizeof(struct _cl_program))); /* Create the private pointer array if device > 1 */ if (ctx->device_num > 1) { TRY_ALLOC_NO_ERR(p->pdata, CL_CALLOC(ctx->device_num, sizeof(void*))); } TRY_ALLOC_NO_ERR(p->device_status, CL_CALLOC(ctx->device_num, sizeof(_cl_program_device_status))); for (i = 0; i < ctx->device_num; i++) { p->device_status[i].build_status = CL_BUILD_NONE; p->device_status[i].binary_type = CL_PROGRAM_BINARY_TYPE_NONE; } cl_ref_set_val(&p->ref_n, 1); p->magic = CL_MAGIC_PROGRAM_HEADER; p->ctx = ctx; CL_MUTEX_INIT(&p->lock); /* The queue also belongs to its context */ cl_retain_context(ctx); CL_MUTEX_LOCK(&p->ctx->lock); p->next = ctx->programs; if (ctx->programs != NULL) ctx->programs->prev = p; ctx->programs = p; CL_MUTEX_UNLOCK(&p->ctx->lock); return p; error: if (p == NULL) return NULL; if (p->ctx->device_num > 1) { if (p->pdata) CL_FREE(p->pdata); } if (p->device_status) CL_FREE(p->device_status); CL_FREE(p); return NULL; } static cl_program cl_program_create_from_source(cl_context ctx, cl_uint count, const char **strings, const size_t *lengths, cl_int *errcode_ret) { cl_program program = NULL; cl_int err = CL_SUCCESS; cl_uint i; int32_t *lens = NULL; int32_t len_total = 0; assert(ctx); char *p = NULL; // the real compilation step will be done at build time since we do not have // yet the compilation options program = cl_program_new(ctx); if (UNLIKELY(program == NULL)) { err = CL_OUT_OF_HOST_MEMORY; goto exit; } TRY_ALLOC(lens, CL_CALLOC(count, sizeof(int32_t))); for (i = 0; i < (int) count; ++i) { size_t len; if (lengths == NULL || lengths[i] == 0) len = strlen(strings[i]); else len = lengths[i]; lens[i] = len; len_total += len; } TRY_ALLOC(program->source, CL_CALLOC(len_total+1, sizeof(char))); p = program->source; for (i = 0; i < (int) count; ++i) { memcpy(p, strings[i], lens[i]); p += lens[i]; } *p = '\0'; program->source_type = FROM_SOURCE; exit: CL_FREE(lens); lens = NULL; if (errcode_ret) *errcode_ret = err; return program; error: cl_program_delete(program); program = NULL; goto exit; } static cl_int check_cl_device_list(cl_context ctx, cl_uint num_devices, const cl_device_id *device_list) { cl_uint i = 0; /* Every device should belong to the context. */ if (num_devices != 0) { assert(ctx); for (i = 0; i < num_devices; i++) { if (cl_context_get_device_index(ctx, device_list[i]) < 0) { return CL_INVALID_DEVICE; } } } return CL_SUCCESS; } /* Before we do the real work, we need to check whether our platform cl version can meet -cl-std= */ static int check_cl_version_option(cl_program p, const char* options) { const char* s = NULL; int ver1 = 0; int ver2 = 0; char version_str[64]; cl_uint i = 0; if (options && (s = strstr(options, "-cl-std="))) { if (s + strlen("-cl-std=CLX.X") > options + strlen(options)) { return 0; } if (s[8] != 'C' || s[9] != 'L' || s[10] > '9' || s[10] < '0' || s[11] != '.' || s[12] > '9' || s[12] < '0') { return 0; } ver1 = (s[10] - '0') * 10 + (s[12] - '0'); for (i = 0; i < p->ctx->device_num; i++) { if (cl_get_device_info(p->ctx->devices[i], CL_DEVICE_OPENCL_C_VERSION, sizeof(version_str), version_str, NULL) != CL_SUCCESS) { return 0; } assert(strstr(version_str, "OpenCL") && version_str[0] == 'O'); ver2 = (version_str[9] - '0') * 10 + (version_str[11] - '0'); if (ver2 < ver1) return 0; } return 1; } return 1; } static cl_int cl_get_kernel_names(cl_program p, const cl_device_id device) { cl_int err = CL_SUCCESS; cl_uint ker_n; char* kernel_names = NULL; cl_uint kernel_names_sz = 0; char* s; cl_uint sz = 0; /* First, we get the name size. */ err = device->driver->get_program_kernel_names(p, device, NULL, 0, &kernel_names_sz, &ker_n); if (err != CL_SUCCESS) goto error; if (ker_n == 0) { err = CL_BUILD_PROGRAM_FAILURE; goto error; } kernel_names = CL_CALLOC(1, kernel_names_sz); if (kernel_names == NULL) { err = CL_OUT_OF_HOST_MEMORY; goto error; } /* Get the real name. */ err = device->driver->get_program_kernel_names(p, device, kernel_names, kernel_names_sz, &sz, &ker_n); if (err != CL_SUCCESS) goto error; /* All the devices should have same kernel number. */ CL_MUTEX_LOCK(&p->lock); if (p->kernel_num == 0) { p->kernel_num = ker_n; assert(p->kernel_names == NULL); } else { if (p->kernel_num != ker_n) { CL_MUTEX_UNLOCK(&p->lock); err = CL_BUILD_PROGRAM_FAILURE; goto error; } } /* Analyse the kernel names. */ if (p->kernel_names == NULL) { p->kernel_names = kernel_names; kernel_names = NULL; } else { /* The kernel names should be seperated by ; But the name sequence may be different, make the this check some trouble. */ s = strtok(kernel_names, ";"); while (s) { char* t = strstr(p->kernel_names, s); /* Make sure this kernel name is valid. */ if (t == NULL) { err = CL_BUILD_PROGRAM_FAILURE; CL_MUTEX_UNLOCK(&p->lock); goto error; } if (t != p->kernel_names && p->kernel_names[t - p->kernel_names - 1] != ';') { err = CL_BUILD_PROGRAM_FAILURE; CL_MUTEX_UNLOCK(&p->lock); goto error; } if (t[strlen(s)] != 0 && t[strlen(s)] != ';') { err = CL_BUILD_PROGRAM_FAILURE; CL_MUTEX_UNLOCK(&p->lock); goto error; } s = strtok(NULL, ";"); } } CL_MUTEX_UNLOCK(&p->lock); error: if (kernel_names) CL_FREE(kernel_names); return err; } static cl_int cl_program_build(cl_program p, const char *options, cl_uint num_devices, const cl_device_id *device_list) { cl_int err = CL_SUCCESS; cl_uint i; cl_bool b; cl_int index; if (cl_ref_get_val(&p->ref_n) > 1) { err = CL_INVALID_OPERATION; goto error; } /* if the build of a program executable for any of the devices listed in device_list by a previous call to clBuildProgram for program has not completed, return CL_INVALID_OPERATION */ CL_MUTEX_LOCK(&p->lock); for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); assert(index >= 0); if (p->device_status[index].build_status == CL_BUILD_IN_PROGRESS) { CL_MUTEX_UNLOCK(&p->lock); return CL_INVALID_OPERATION; } } for (i = 0; i < num_devices; i++) { if (p->device_status[index].binary_type != CL_PROGRAM_BINARY_TYPE_NONE) { // already builded ? CL_MUTEX_UNLOCK(&p->lock); return CL_INVALID_OPERATION; } index = cl_context_get_device_index(p->ctx, device_list[i]); p->device_status[index].build_status = CL_BUILD_IN_PROGRESS; } CL_MUTEX_UNLOCK(&p->lock); for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); assert(index >= 0); err = cl_get_device_info(device_list[i], CL_DEVICE_COMPILER_AVAILABLE, sizeof(b), &b, NULL); assert(err == CL_SUCCESS); // should always get it. if (b == CL_FALSE) { err = CL_COMPILER_NOT_AVAILABLE; goto error; } } if (p->source_type != FROM_SOURCE && p->source_type != FROM_SPIR) { err = CL_INVALID_OPERATION; goto error; } if (!check_cl_version_option(p, options)) { err = CL_BUILD_PROGRAM_FAILURE; goto error; } /* Save the options. Just set build_status with mutex, we can be MT safe, the build will not re-entry for the same device, so from here we are MT safe, and no neeed for lock*/ for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); if (options) { if(p->device_status[index].build_opts == NULL || strcmp(options, p->device_status[index].build_opts) != 0) { if(p->device_status[index].build_opts) { CL_FREE(p->device_status[index].build_opts); p->device_status[index].build_opts = NULL; } TRY_ALLOC(p->device_status[index].build_opts, CL_CALLOC(strlen(options) + 1, sizeof(char))); memcpy(p->device_status[index].build_opts, options, strlen(options)); } } else if (options == NULL && p->device_status[index].build_opts) { CL_FREE(p->device_status[index].build_opts); p->device_status[index].build_opts = NULL; } } for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); err = device_list[i]->driver->build_program(p, device_list[i]); if (err != CL_SUCCESS) goto error; err = cl_get_kernel_names(p, device_list[i]); /* We need to make sure all the devives has same kernel number avaible and same kernel names and parameters. */ if (err != CL_SUCCESS) goto error; } CL_MUTEX_LOCK(&p->lock); for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); p->device_status[index].build_status = CL_BUILD_SUCCESS; p->device_status[index].binary_type = CL_PROGRAM_BINARY_TYPE_EXECUTABLE; } CL_MUTEX_UNLOCK(&p->lock); return CL_SUCCESS; error: CL_MUTEX_LOCK(&p->lock); for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); p->device_status[index].build_status = CL_BUILD_ERROR; } CL_MUTEX_UNLOCK(&p->lock); return err; } static cl_int cl_program_compile(cl_program p, cl_uint num_devices, const cl_device_id *device_list, cl_uint num_input_headers, const cl_program *input_headers, const char **header_include_names, const char*options) { cl_int err = CL_SUCCESS; cl_uint i; cl_bool b; cl_int index; if (cl_ref_get_val(&p->ref_n) > 1) { err = CL_INVALID_OPERATION; goto error; } /* if the build of a program executable for any of the devices listed in device_list by a previous call to clBuildProgram for program has not completed, return CL_INVALID_OPERATION */ CL_MUTEX_LOCK(&p->lock); for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); assert(index >= 0); if (p->device_status[index].build_status == CL_BUILD_IN_PROGRESS) { CL_MUTEX_UNLOCK(&p->lock); return CL_INVALID_OPERATION; } } for (i = 0; i < num_devices; i++) { if (p->device_status[index].binary_type != CL_PROGRAM_BINARY_TYPE_NONE) { // already compiled ? CL_MUTEX_UNLOCK(&p->lock); return CL_INVALID_OPERATION; } index = cl_context_get_device_index(p->ctx, device_list[i]); p->device_status[index].build_status = CL_BUILD_IN_PROGRESS; } CL_MUTEX_UNLOCK(&p->lock); for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); assert(index >= 0); err = cl_get_device_info(device_list[i], CL_DEVICE_COMPILER_AVAILABLE, sizeof(b), &b, NULL); assert(err == CL_SUCCESS); // should always get it. if (b == CL_FALSE) { err = CL_COMPILER_NOT_AVAILABLE; goto error; } } if (p->source_type != FROM_SOURCE && p->source_type != FROM_SPIR) { err = CL_INVALID_OPERATION; goto error; } if (!check_cl_version_option(p, options)) { err = CL_BUILD_PROGRAM_FAILURE; goto error; } /* Save the options. Just set build_status with mutex, we can be MT safe, the build will not re-entry for the same device, so from here we are MT safe, and no neeed for lock*/ for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); if (options) { if(p->device_status[index].build_opts == NULL || strcmp(options, p->device_status[index].build_opts) != 0) { if(p->device_status[index].build_opts) { CL_FREE(p->device_status[index].build_opts); p->device_status[index].build_opts = NULL; } TRY_ALLOC(p->device_status[index].build_opts, CL_CALLOC(strlen(options) + 1, sizeof(char))); memcpy(p->device_status[index].build_opts, options, strlen(options)); } } else if (options == NULL && p->device_status[index].build_opts) { CL_FREE(p->device_status[index].build_opts); p->device_status[index].build_opts = NULL; } } for (i = 0; i < num_devices; i++) { err = device_list[i]->driver->compile_program(p, device_list[i], options, num_input_headers, input_headers, header_include_names); if (err != CL_SUCCESS) { goto error; } } CL_MUTEX_LOCK(&p->lock); for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); p->device_status[index].build_status = CL_BUILD_SUCCESS; p->device_status[index].binary_type = CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT; } CL_MUTEX_UNLOCK(&p->lock); return CL_SUCCESS; error: CL_MUTEX_LOCK(&p->lock); for (i = 0; i < num_devices; i++) { index = cl_context_get_device_index(p->ctx, device_list[i]); p->device_status[index].build_status = CL_BUILD_ERROR; } CL_MUTEX_UNLOCK(&p->lock); return err; } static cl_program cl_program_create_from_binary(cl_context ctx, cl_uint num_devices, const cl_device_id *device_list, const size_t *lengths, const unsigned char **binaries, cl_int *binary_status, cl_int *errcode_ret) { cl_program program = NULL; cl_int err = CL_SUCCESS; cl_uint i; // lengths[i] cannot be zero and binaries[i] cannot be a NULL pointer for (i = 0; i < num_devices; i++) { if (binaries[i] == NULL || lengths[i] == 0) { err = CL_INVALID_VALUE; if (binary_status) binary_status[i] = CL_INVALID_VALUE; goto error; } } assert(ctx); program = cl_program_new(ctx); if (UNLIKELY(program == NULL)) { err = CL_OUT_OF_HOST_MEMORY; goto error; } // source_type will be FROM_BINARY/FROM_SPIR, drivers need to set it. // Call the driver->create_from_binary to set the binary and binary types. for (i = 0; i < num_devices; i++) { err = device_list[i]->driver->create_program_with_binary(program, device_list[i], lengths[i], binaries[i]); if (err) goto error; } exit: if (errcode_ret) *errcode_ret = err; return program; error: cl_release_program(program); program = NULL; goto exit; } static cl_program cl_program_link(cl_context context, cl_uint num_devices, const cl_device_id *device_list, cl_uint num_input_programs, const cl_program *input_programs, const char *options, cl_int* errcode_ret) { cl_program p = NULL; cl_int err = CL_SUCCESS; cl_int i = 0; int avialable_program = 0; // TODO: for(i = 0; i < num_input_programs; i++) { //num_input_programs >0 and input_programs MUST not NULL. // if(input_programs[i]->binary_type == CL_PROGRAM_BINARY_TYPE_LIBRARY || // input_programs[i]->binary_type == CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT) { avialable_program++; // } } //None of program contain a compilerd binary or library. if(avialable_program == 0) { goto done; } //Must all of program contain a compilerd binary or library. if(avialable_program < num_input_programs) { err = CL_INVALID_OPERATION; goto error; } p = cl_program_new(context); if (UNLIKELY(p == NULL)) { err = CL_OUT_OF_HOST_MEMORY; goto error; } if (!check_cl_version_option(p, options)) { err = CL_BUILD_PROGRAM_FAILURE; goto error; } for (i = 0; i < num_devices; i++) { err = device_list[i]->driver->link_program(p, device_list[i], options, num_input_programs, input_programs); if (err) goto error; } done: // if (p) // p->build_status = CL_BUILD_SUCCESS; if (errcode_ret) *errcode_ret = err; return p; error: cl_release_program(p); if (errcode_ret) *errcode_ret = err; return NULL; } /************************************************************************************** ************************* CL APIs ******************************** **************************************************************************************/ cl_int clRetainProgram(cl_program program) { cl_int err = CL_SUCCESS; CHECK_PROGRAM (program); if (cl_retain_program(program) == 0) { err = CL_INVALID_PROGRAM; } error: return err; } cl_program clCreateProgramWithSource(cl_context context, cl_uint count, const char ** strings, const size_t * lengths, cl_int * errcode_ret) { cl_program program = NULL; cl_int err = CL_SUCCESS; cl_uint i; CHECK_CONTEXT(context); INVALID_VALUE_IF(count == 0); INVALID_VALUE_IF(strings == NULL); for(i = 0; i < count; i++) { if(UNLIKELY(strings[i] == NULL)) { err = CL_INVALID_VALUE; goto error; } } program = cl_program_create_from_source(context, count, strings, lengths, &err); error: if (errcode_ret) *errcode_ret = err; return program; } cl_int clBuildProgram(cl_program program, cl_uint num_devices, const cl_device_id * device_list, const char * options, void (CL_CALLBACK *pfn_notify) (cl_program, void*), void * user_data) { cl_int err = CL_SUCCESS; CHECK_PROGRAM(program); INVALID_VALUE_IF(num_devices == 0 && device_list != NULL); INVALID_VALUE_IF(num_devices != 0 && device_list == NULL); INVALID_VALUE_IF(pfn_notify == 0 && user_data != NULL); INVALID_VALUE_IF(num_devices > program->ctx->device_num); err = check_cl_device_list(program->ctx, num_devices, device_list); if (err != CL_SUCCESS) goto error; assert(program->source_type == FROM_SOURCE || program->source_type == FROM_SPIR || program->source_type == FROM_BINARY); /* TODO: We do not support async build now. */ if((err = cl_program_build(program, options, num_devices, device_list)) != CL_SUCCESS) { goto error; } if (pfn_notify) pfn_notify(program, user_data); error: return err; } cl_int clCompileProgram(cl_program program , cl_uint num_devices , const cl_device_id * device_list , const char * options , cl_uint num_input_headers , const cl_program * input_headers , const char ** header_include_names , void (CL_CALLBACK * pfn_notify )(cl_program, void *), void * user_data ) { cl_int err = CL_SUCCESS; CHECK_PROGRAM(program); INVALID_VALUE_IF(num_devices == 0 && device_list != NULL); INVALID_VALUE_IF(num_devices != 0 && device_list == NULL); INVALID_VALUE_IF(pfn_notify == 0 && user_data != NULL); INVALID_VALUE_IF(num_input_headers == 0 && input_headers != NULL); INVALID_VALUE_IF(num_input_headers != 0 && input_headers == NULL); INVALID_VALUE_IF(num_devices > program->ctx->device_num); err = check_cl_device_list(program->ctx, num_devices, device_list); if (err != CL_SUCCESS) goto error; assert(program->source_type == FROM_SOURCE || program->source_type == FROM_SPIR || program->source_type == FROM_BINARY); if((err = cl_program_compile(program, num_devices, device_list, num_input_headers, input_headers, header_include_names, options)) != CL_SUCCESS) { goto error; } if (pfn_notify) pfn_notify(program, user_data); error: return err; } cl_program clCreateProgramWithBinary(cl_context context, cl_uint num_devices, const cl_device_id * device_list, const size_t * lengths, const unsigned char ** binaries, cl_int * binary_status, cl_int * errcode_ret) { cl_program program = NULL; cl_int err = CL_SUCCESS; cl_uint i; CHECK_CONTEXT(context); /* device_list must be a non-NULL value. */ INVALID_VALUE_IF(num_devices == 0); INVALID_VALUE_IF(device_list == NULL); INVALID_VALUE_IF(binaries == NULL); INVALID_VALUE_IF(lengths == NULL); INVALID_VALUE_IF(num_devices > program->ctx->device_num); /* lengths[i] cannot be zero and binaries[i] cannot be a NULL pointer. */ for (i = 0; i < num_devices; i++) { if (binaries[i] == NULL || lengths[i] == 0) { err = CL_INVALID_VALUE; goto error; } } err = check_cl_device_list(context, num_devices, device_list); if (err != CL_SUCCESS) goto error; program = cl_program_create_from_binary(context, num_devices, device_list, lengths, binaries, binary_status, &err); error: if (errcode_ret) *errcode_ret = err; return program; } cl_program clLinkProgram(cl_context context, cl_uint num_devices, const cl_device_id * device_list, const char * options, cl_uint num_input_programs, const cl_program * input_programs, void (CL_CALLBACK * pfn_notify)(cl_program program, void * user_data), void * user_data, cl_int * errcode_ret) { cl_int err = CL_SUCCESS; cl_program program = NULL; CHECK_CONTEXT (context); INVALID_VALUE_IF (num_devices == 0 && device_list != NULL); INVALID_VALUE_IF (num_devices != 0 && device_list == NULL); INVALID_VALUE_IF (pfn_notify == 0 && user_data != NULL); INVALID_VALUE_IF (num_input_programs == 0 && input_programs != NULL); INVALID_VALUE_IF (num_input_programs != 0 && input_programs == NULL); INVALID_VALUE_IF (num_input_programs == 0 && input_programs == NULL); program = cl_program_link(context, num_devices, device_list, num_input_programs, input_programs, options, &err); if (pfn_notify) pfn_notify(program, user_data); error: if (errcode_ret) *errcode_ret = err; return program; } cl_int clReleaseProgram(cl_program program) { cl_int err = CL_SUCCESS; CHECK_PROGRAM (program); cl_release_program(program); error: return err; }