1// 2// Copyright 2012 Francisco Jerez 3// 4// Permission is hereby granted, free of charge, to any person obtaining a 5// copy of this software and associated documentation files (the "Software"), 6// to deal in the Software without restriction, including without limitation 7// the rights to use, copy, modify, merge, publish, distribute, sublicense, 8// and/or sell copies of the Software, and to permit persons to whom the 9// Software is furnished to do so, subject to the following conditions: 10// 11// The above copyright notice and this permission notice shall be included in 12// all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 18// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 20// OTHER DEALINGS IN THE SOFTWARE. 21// 22 23#include "api/util.hpp" 24#include "core/program.hpp" 25#include "core/platform.hpp" 26#include "spirv/invocation.hpp" 27#include "util/u_debug.h" 28 29#include <limits> 30#include <sstream> 31 32using namespace clover; 33 34namespace { 35 36 std::string 37 build_options(const char *p_opts, const char *p_debug) { 38 auto opts = std::string(p_opts ? p_opts : ""); 39 std::string extra_opts = debug_get_option(p_debug, ""); 40 41 return detokenize(std::vector<std::string>{opts, extra_opts}, " "); 42 } 43 44 class build_notifier { 45 public: 46 build_notifier(cl_program prog, 47 void (*notifer)(cl_program, void *), void *data) : 48 prog_(prog), notifer(notifer), data_(data) { } 49 50 ~build_notifier() { 51 if (notifer) 52 notifer(prog_, data_); 53 } 54 55 private: 56 cl_program prog_; 57 void (*notifer)(cl_program, void *); 58 void *data_; 59 }; 60 61 void 62 validate_build_common(const program &prog, cl_uint num_devs, 63 const cl_device_id *d_devs, 64 void (*pfn_notify)(cl_program, void *), 65 void *user_data) { 66 if (!pfn_notify && user_data) 67 throw error(CL_INVALID_VALUE); 68 69 if (prog.kernel_ref_count()) 70 throw error(CL_INVALID_OPERATION); 71 72 if (any_of([&](const device &dev) { 73 return !count(dev, prog.devices()); 74 }, objs<allow_empty_tag>(d_devs, num_devs))) 75 throw error(CL_INVALID_DEVICE); 76 } 77 78 enum program::il_type 79 identify_and_validate_il(const std::string &il, 80 const cl_version opencl_version, 81 const context::notify_action ¬ify) { 82 83 enum program::il_type il_type = program::il_type::none; 84 85#ifdef HAVE_CLOVER_SPIRV 86 if (spirv::is_binary_spirv(il)) { 87 std::string log; 88 if (!spirv::is_valid_spirv(il, opencl_version, log)) { 89 if (notify) { 90 notify(log.c_str()); 91 } 92 throw error(CL_INVALID_VALUE); 93 } 94 il_type = program::il_type::spirv; 95 } 96#endif 97 98 return il_type; 99 } 100} 101 102CLOVER_API cl_program 103clCreateProgramWithSource(cl_context d_ctx, cl_uint count, 104 const char **strings, const size_t *lengths, 105 cl_int *r_errcode) try { 106 auto &ctx = obj(d_ctx); 107 std::string source; 108 109 if (!count || !strings || 110 any_of(is_zero(), range(strings, count))) 111 throw error(CL_INVALID_VALUE); 112 113 // Concatenate all the provided fragments together 114 for (unsigned i = 0; i < count; ++i) 115 source += (lengths && lengths[i] ? 116 std::string(strings[i], strings[i] + lengths[i]) : 117 std::string(strings[i])); 118 119 // ...and create a program object for them. 120 ret_error(r_errcode, CL_SUCCESS); 121 return new program(ctx, std::move(source), program::il_type::source); 122 123} catch (error &e) { 124 ret_error(r_errcode, e); 125 return NULL; 126} 127 128CLOVER_API cl_program 129clCreateProgramWithBinary(cl_context d_ctx, cl_uint n, 130 const cl_device_id *d_devs, 131 const size_t *lengths, 132 const unsigned char **binaries, 133 cl_int *r_status, cl_int *r_errcode) try { 134 auto &ctx = obj(d_ctx); 135 auto devs = objs(d_devs, n); 136 137 if (!lengths || !binaries) 138 throw error(CL_INVALID_VALUE); 139 140 if (any_of([&](const device &dev) { 141 return !count(dev, ctx.devices()); 142 }, devs)) 143 throw error(CL_INVALID_DEVICE); 144 145 // Deserialize the provided binaries, 146 std::vector<std::pair<cl_int, binary>> result = map( 147 [](const unsigned char *p, size_t l) -> std::pair<cl_int, binary> { 148 if (!p || !l) 149 return { CL_INVALID_VALUE, {} }; 150 151 try { 152 std::stringbuf bin( std::string{ (char*)p, l } ); 153 std::istream s(&bin); 154 155 return { CL_SUCCESS, binary::deserialize(s) }; 156 157 } catch (std::istream::failure &) { 158 return { CL_INVALID_BINARY, {} }; 159 } 160 }, 161 range(binaries, n), 162 range(lengths, n)); 163 164 // update the status array, 165 if (r_status) 166 copy(map(keys(), result), r_status); 167 168 if (any_of(key_equals(CL_INVALID_VALUE), result)) 169 throw error(CL_INVALID_VALUE); 170 171 if (any_of(key_equals(CL_INVALID_BINARY), result)) 172 throw error(CL_INVALID_BINARY); 173 174 // initialize a program object with them. 175 ret_error(r_errcode, CL_SUCCESS); 176 return new program(ctx, devs, map(values(), result)); 177 178} catch (error &e) { 179 ret_error(r_errcode, e); 180 return NULL; 181} 182 183cl_program 184clover::CreateProgramWithILKHR(cl_context d_ctx, const void *il, 185 size_t length, cl_int *r_errcode) try { 186 auto &ctx = obj(d_ctx); 187 188 if (!il || !length) 189 throw error(CL_INVALID_VALUE); 190 191 // Compute the highest OpenCL version supported by all devices associated to 192 // the context. That is the version used for validating the SPIR-V binary. 193 cl_version min_opencl_version = std::numeric_limits<uint32_t>::max(); 194 for (const device &dev : ctx.devices()) { 195 const cl_version opencl_version = dev.device_version(); 196 min_opencl_version = std::min(opencl_version, min_opencl_version); 197 } 198 199 const char *stream = reinterpret_cast<const char *>(il); 200 std::string binary(stream, stream + length); 201 const enum program::il_type il_type = identify_and_validate_il(binary, 202 min_opencl_version, 203 ctx.notify); 204 205 if (il_type == program::il_type::none) 206 throw error(CL_INVALID_VALUE); 207 208 // Initialize a program object with it. 209 ret_error(r_errcode, CL_SUCCESS); 210 return new program(ctx, std::move(binary), il_type); 211 212} catch (error &e) { 213 ret_error(r_errcode, e); 214 return NULL; 215} 216 217CLOVER_API cl_program 218clCreateProgramWithIL(cl_context d_ctx, 219 const void *il, 220 size_t length, 221 cl_int *r_errcode) { 222 return CreateProgramWithILKHR(d_ctx, il, length, r_errcode); 223} 224 225CLOVER_API cl_program 226clCreateProgramWithBuiltInKernels(cl_context d_ctx, cl_uint n, 227 const cl_device_id *d_devs, 228 const char *kernel_names, 229 cl_int *r_errcode) try { 230 auto &ctx = obj(d_ctx); 231 auto devs = objs(d_devs, n); 232 233 if (any_of([&](const device &dev) { 234 return !count(dev, ctx.devices()); 235 }, devs)) 236 throw error(CL_INVALID_DEVICE); 237 238 // No currently supported built-in kernels. 239 throw error(CL_INVALID_VALUE); 240 241} catch (error &e) { 242 ret_error(r_errcode, e); 243 return NULL; 244} 245 246 247CLOVER_API cl_int 248clRetainProgram(cl_program d_prog) try { 249 obj(d_prog).retain(); 250 return CL_SUCCESS; 251 252} catch (error &e) { 253 return e.get(); 254} 255 256CLOVER_API cl_int 257clReleaseProgram(cl_program d_prog) try { 258 if (obj(d_prog).release()) 259 delete pobj(d_prog); 260 261 return CL_SUCCESS; 262 263} catch (error &e) { 264 return e.get(); 265} 266 267CLOVER_API cl_int 268clBuildProgram(cl_program d_prog, cl_uint num_devs, 269 const cl_device_id *d_devs, const char *p_opts, 270 void (*pfn_notify)(cl_program, void *), 271 void *user_data) try { 272 auto &prog = obj(d_prog); 273 auto devs = 274 (d_devs ? objs(d_devs, num_devs) : ref_vector<device>(prog.devices())); 275 const auto opts = build_options(p_opts, "CLOVER_EXTRA_BUILD_OPTIONS"); 276 277 validate_build_common(prog, num_devs, d_devs, pfn_notify, user_data); 278 279 auto notifier = build_notifier(d_prog, pfn_notify, user_data); 280 281 if (prog.il_type() != program::il_type::none) { 282 prog.compile(devs, opts); 283 prog.link(devs, opts, { prog }); 284 } else if (any_of([&](const device &dev){ 285 return prog.build(dev).binary_type() != CL_PROGRAM_BINARY_TYPE_EXECUTABLE; 286 }, devs)) { 287 // According to the OpenCL 1.2 specification, “if program is created 288 // with clCreateProgramWithBinary, then the program binary must be an 289 // executable binary (not a compiled binary or library).” 290 throw error(CL_INVALID_BINARY); 291 } 292 293 return CL_SUCCESS; 294 295} catch (error &e) { 296 return e.get(); 297} 298 299CLOVER_API cl_int 300clCompileProgram(cl_program d_prog, cl_uint num_devs, 301 const cl_device_id *d_devs, const char *p_opts, 302 cl_uint num_headers, const cl_program *d_header_progs, 303 const char **header_names, 304 void (*pfn_notify)(cl_program, void *), 305 void *user_data) try { 306 auto &prog = obj(d_prog); 307 auto devs = 308 (d_devs ? objs(d_devs, num_devs) : ref_vector<device>(prog.devices())); 309 const auto opts = build_options(p_opts, "CLOVER_EXTRA_COMPILE_OPTIONS"); 310 header_map headers; 311 312 validate_build_common(prog, num_devs, d_devs, pfn_notify, user_data); 313 314 auto notifier = build_notifier(d_prog, pfn_notify, user_data); 315 316 if (bool(num_headers) != bool(header_names)) 317 throw error(CL_INVALID_VALUE); 318 319 if (prog.il_type() == program::il_type::none) 320 throw error(CL_INVALID_OPERATION); 321 322 for_each([&](const char *name, const program &header) { 323 if (header.il_type() == program::il_type::none) 324 throw error(CL_INVALID_OPERATION); 325 326 if (!any_of(key_equals(name), headers)) 327 headers.push_back(std::pair<std::string, std::string>( 328 name, header.source())); 329 }, 330 range(header_names, num_headers), 331 objs<allow_empty_tag>(d_header_progs, num_headers)); 332 333 prog.compile(devs, opts, headers); 334 return CL_SUCCESS; 335 336} catch (invalid_build_options_error &) { 337 return CL_INVALID_COMPILER_OPTIONS; 338 339} catch (build_error &) { 340 return CL_COMPILE_PROGRAM_FAILURE; 341 342} catch (error &e) { 343 return e.get(); 344} 345 346namespace { 347 ref_vector<device> 348 validate_link_devices(const ref_vector<program> &progs, 349 const ref_vector<device> &all_devs, 350 const std::string &opts) { 351 std::vector<device *> devs; 352 const bool create_library = 353 opts.find("-create-library") != std::string::npos; 354 const bool enable_link_options = 355 opts.find("-enable-link-options") != std::string::npos; 356 const bool has_link_options = 357 opts.find("-cl-denorms-are-zero") != std::string::npos || 358 opts.find("-cl-no-signed-zeroes") != std::string::npos || 359 opts.find("-cl-unsafe-math-optimizations") != std::string::npos || 360 opts.find("-cl-finite-math-only") != std::string::npos || 361 opts.find("-cl-fast-relaxed-math") != std::string::npos || 362 opts.find("-cl-no-subgroup-ifp") != std::string::npos; 363 364 // According to the OpenCL 1.2 specification, "[the 365 // -enable-link-options] option must be specified with the 366 // create-library option". 367 if (enable_link_options && !create_library) 368 throw error(CL_INVALID_LINKER_OPTIONS); 369 370 // According to the OpenCL 1.2 specification, "the 371 // [program linking options] can be specified when linking a program 372 // executable". 373 if (has_link_options && create_library) 374 throw error(CL_INVALID_LINKER_OPTIONS); 375 376 for (auto &dev : all_devs) { 377 const auto has_binary = [&](const program &prog) { 378 const auto t = prog.build(dev).binary_type(); 379 return t == CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT || 380 t == CL_PROGRAM_BINARY_TYPE_LIBRARY; 381 }; 382 383 // According to the OpenCL 1.2 specification, a library is made of 384 // “compiled binaries specified in input_programs argument to 385 // clLinkProgram“; compiled binaries does not refer to libraries: 386 // “input_programs is an array of program objects that are compiled 387 // binaries or libraries that are to be linked to create the program 388 // executable”. 389 if (create_library && any_of([&](const program &prog) { 390 const auto t = prog.build(dev).binary_type(); 391 return t != CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT; 392 }, progs)) 393 throw error(CL_INVALID_OPERATION); 394 395 // According to the CL 1.2 spec, when "all programs specified [..] 396 // contain a compiled binary or library for the device [..] a link is 397 // performed", 398 else if (all_of(has_binary, progs)) 399 devs.push_back(&dev); 400 401 // otherwise if "none of the programs contain a compiled binary or 402 // library for that device [..] no link is performed. All other 403 // cases will return a CL_INVALID_OPERATION error." 404 else if (any_of(has_binary, progs)) 405 throw error(CL_INVALID_OPERATION); 406 407 // According to the OpenCL 1.2 specification, "[t]he linker may apply 408 // [program linking options] to all compiled program objects 409 // specified to clLinkProgram. The linker may apply these options 410 // only to libraries which were created with the 411 // -enable-link-option." 412 else if (has_link_options && any_of([&](const program &prog) { 413 const auto t = prog.build(dev).binary_type(); 414 return !(t == CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT || 415 (t == CL_PROGRAM_BINARY_TYPE_LIBRARY && 416 prog.build(dev).opts.find("-enable-link-options") != 417 std::string::npos)); 418 }, progs)) 419 throw error(CL_INVALID_LINKER_OPTIONS); 420 } 421 422 return map(derefs(), devs); 423 } 424} 425 426CLOVER_API cl_program 427clLinkProgram(cl_context d_ctx, cl_uint num_devs, const cl_device_id *d_devs, 428 const char *p_opts, cl_uint num_progs, const cl_program *d_progs, 429 void (*pfn_notify) (cl_program, void *), void *user_data, 430 cl_int *r_errcode) try { 431 auto &ctx = obj(d_ctx); 432 const auto opts = build_options(p_opts, "CLOVER_EXTRA_LINK_OPTIONS"); 433 auto progs = objs(d_progs, num_progs); 434 auto all_devs = 435 (d_devs ? objs(d_devs, num_devs) : ref_vector<device>(ctx.devices())); 436 auto prog = create<program>(ctx, all_devs); 437 auto r_prog = ret_object(prog); 438 439 auto notifier = build_notifier(r_prog, pfn_notify, user_data); 440 441 auto devs = validate_link_devices(progs, all_devs, opts); 442 443 validate_build_common(prog, num_devs, d_devs, pfn_notify, user_data); 444 445 try { 446 prog().link(devs, opts, progs); 447 ret_error(r_errcode, CL_SUCCESS); 448 449 } catch (build_error &) { 450 ret_error(r_errcode, CL_LINK_PROGRAM_FAILURE); 451 } 452 453 return r_prog; 454 455} catch (invalid_build_options_error &) { 456 ret_error(r_errcode, CL_INVALID_LINKER_OPTIONS); 457 return NULL; 458 459} catch (error &e) { 460 ret_error(r_errcode, e); 461 return NULL; 462} 463 464CLOVER_API cl_int 465clUnloadCompiler() { 466 return CL_SUCCESS; 467} 468 469CLOVER_API cl_int 470clUnloadPlatformCompiler(cl_platform_id d_platform) try { 471 find_platform(d_platform); 472 return CL_SUCCESS; 473} catch (error &e) { 474 return e.get(); 475} 476 477CLOVER_API cl_int 478clGetProgramInfo(cl_program d_prog, cl_program_info param, 479 size_t size, void *r_buf, size_t *r_size) try { 480 property_buffer buf { r_buf, size, r_size }; 481 auto &prog = obj(d_prog); 482 483 switch (param) { 484 case CL_PROGRAM_REFERENCE_COUNT: 485 buf.as_scalar<cl_uint>() = prog.ref_count(); 486 break; 487 488 case CL_PROGRAM_CONTEXT: 489 buf.as_scalar<cl_context>() = desc(prog.context()); 490 break; 491 492 case CL_PROGRAM_NUM_DEVICES: 493 buf.as_scalar<cl_uint>() = (prog.devices().size() ? 494 prog.devices().size() : 495 prog.context().devices().size()); 496 break; 497 498 case CL_PROGRAM_DEVICES: 499 buf.as_vector<cl_device_id>() = (prog.devices().size() ? 500 descs(prog.devices()) : 501 descs(prog.context().devices())); 502 break; 503 504 case CL_PROGRAM_SOURCE: 505 buf.as_string() = prog.source(); 506 break; 507 508 case CL_PROGRAM_BINARY_SIZES: 509 buf.as_vector<size_t>() = map([&](const device &dev) { 510 return prog.build(dev).bin.size(); 511 }, 512 prog.devices()); 513 break; 514 515 case CL_PROGRAM_BINARIES: 516 buf.as_matrix<unsigned char>() = map([&](const device &dev) { 517 std::stringbuf bin; 518 std::ostream s(&bin); 519 prog.build(dev).bin.serialize(s); 520 return bin.str(); 521 }, 522 prog.devices()); 523 break; 524 525 case CL_PROGRAM_NUM_KERNELS: 526 buf.as_scalar<cl_uint>() = prog.symbols().size(); 527 break; 528 529 case CL_PROGRAM_KERNEL_NAMES: 530 buf.as_string() = fold([](const std::string &a, const binary::symbol &s) { 531 return ((a.empty() ? "" : a + ";") + s.name); 532 }, std::string(), prog.symbols()); 533 break; 534 535 case CL_PROGRAM_SCOPE_GLOBAL_CTORS_PRESENT: 536 case CL_PROGRAM_SCOPE_GLOBAL_DTORS_PRESENT: 537 buf.as_scalar<cl_bool>() = CL_FALSE; 538 break; 539 540 case CL_PROGRAM_IL: 541 if (prog.il_type() == program::il_type::spirv) 542 buf.as_vector<char>() = prog.source(); 543 else if (r_size) 544 *r_size = 0u; 545 break; 546 default: 547 throw error(CL_INVALID_VALUE); 548 } 549 550 return CL_SUCCESS; 551 552} catch (error &e) { 553 return e.get(); 554} 555 556CLOVER_API cl_int 557clGetProgramBuildInfo(cl_program d_prog, cl_device_id d_dev, 558 cl_program_build_info param, 559 size_t size, void *r_buf, size_t *r_size) try { 560 property_buffer buf { r_buf, size, r_size }; 561 auto &prog = obj(d_prog); 562 auto &dev = obj(d_dev); 563 564 if (!count(dev, prog.context().devices())) 565 return CL_INVALID_DEVICE; 566 567 switch (param) { 568 case CL_PROGRAM_BUILD_STATUS: 569 buf.as_scalar<cl_build_status>() = prog.build(dev).status(); 570 break; 571 572 case CL_PROGRAM_BUILD_OPTIONS: 573 buf.as_string() = prog.build(dev).opts; 574 break; 575 576 case CL_PROGRAM_BUILD_LOG: 577 buf.as_string() = prog.build(dev).log; 578 break; 579 580 case CL_PROGRAM_BINARY_TYPE: 581 buf.as_scalar<cl_program_binary_type>() = prog.build(dev).binary_type(); 582 break; 583 584 case CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE: 585 buf.as_scalar<size_t>() = 0; 586 break; 587 588 default: 589 throw error(CL_INVALID_VALUE); 590 } 591 592 return CL_SUCCESS; 593 594} catch (error &e) { 595 return e.get(); 596} 597