001/* 002 * JavaCL - Java API and utilities for OpenCL 003 * http://javacl.googlecode.com/ 004 * 005 * Copyright (c) 2009-2013, Olivier Chafik (http://ochafik.com/) 006 * All rights reserved. 007 * 008 * Redistribution and use in source and binary forms, with or without 009 * modification, are permitted provided that the following conditions are met: 010 * 011 * * Redistributions of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * * Redistributions in binary form must reproduce the above copyright 014 * notice, this list of conditions and the following disclaimer in the 015 * documentation and/or other materials provided with the distribution. 016 * * Neither the name of Olivier Chafik nor the 017 * names of its contributors may be used to endorse or promote products 018 * derived from this software without specific prior written permission. 019 * 020 * THIS SOFTWARE IS PROVIDED BY OLIVIER CHAFIK AND CONTRIBUTORS ``AS IS'' AND ANY 021 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 022 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 023 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 024 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 025 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 026 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 027 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 029 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 030 */ 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050package com.nativelibs4java.opencl; 051import java.util.Iterator; 052import java.util.Arrays; 053import com.nativelibs4java.util.Pair; 054import static com.nativelibs4java.opencl.CLException.error; 055import static com.nativelibs4java.opencl.CLException.errorString; 056import static com.nativelibs4java.opencl.CLException.failedForLackOfMemory; 057import static com.nativelibs4java.opencl.JavaCL.CL; 058import static com.nativelibs4java.opencl.JavaCL.log; 059import java.util.logging.Level; 060import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_PROGRAM_BINARIES; 061import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_PROGRAM_BINARY_SIZES; 062import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_PROGRAM_BUILD_LOG; 063import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_PROGRAM_SOURCE; 064import static com.nativelibs4java.opencl.library.IOpenCLLibrary.CL_SUCCESS; 065import static org.bridj.util.DefaultParameterizedType.paramType; 066import java.io.IOException; 067import java.io.File; 068import java.io.FileInputStream; 069import java.io.FileOutputStream; 070import java.net.MalformedURLException; 071import java.net.URLConnection; 072import java.net.URL; 073 074import java.nio.ByteOrder; 075import java.nio.IntBuffer; 076import java.util.ArrayList; 077import java.util.HashMap; 078import java.util.HashSet; 079import java.util.Set; 080import java.util.Collections; 081import java.util.LinkedHashMap; 082import java.util.List; 083import java.util.Arrays; 084import java.util.Map; 085 086import com.nativelibs4java.opencl.library.IOpenCLLibrary.cl_device_id; 087import com.nativelibs4java.opencl.library.IOpenCLLibrary.cl_kernel; 088import com.nativelibs4java.opencl.library.IOpenCLLibrary.cl_program; 089import com.nativelibs4java.util.IOUtils; 090import com.ochafik.util.string.StringUtils; 091import java.io.BufferedInputStream; 092import java.io.BufferedOutputStream; 093import java.io.ByteArrayOutputStream; 094import java.io.File; 095import java.io.FileOutputStream; 096import java.io.InputStream; 097import java.io.OutputStream; 098import java.lang.Process; 099import java.net.URL; 100import java.util.Collection; 101import java.util.regex.Pattern; 102import java.util.regex.Matcher; 103import java.util.zip.CRC32; 104import java.util.zip.ZipEntry; 105import java.util.zip.ZipInputStream; 106import java.util.zip.ZipOutputStream; 107import java.util.zip.GZIPOutputStream; 108import java.util.zip.GZIPInputStream; 109 110import org.bridj.*; 111import static org.bridj.Pointer.*; 112 113/** 114 * OpenCL program.<br> 115 * An OpenCL program consists of a set of kernels that are identified as functions declared with the __kernel qualifier in the program source. OpenCL programs may also contain auxiliary functions and constant data that can be used by __kernel functions. The program executable can be generated online or offline by the OpenCL compiler for the appropriate target device(s).<br> 116 * A program object encapsulates the following information: 117 * <ul> 118 * <li>An associated context.</li> 119 * <li>A program source or binary.</li> 120 * <li>The latest successfully built program executable</li> 121 * <li>The list of devices for which the program executable is built</li> 122 * <li>The build options used and a build log. </li> 123 * <li>The number of kernel objects currently attached.</li> 124 * </ul> 125 * 126 * A program can be compiled on the fly (costly) but its binaries can be stored and 127 * loaded back in subsequent executions to avoid recompilation.<br> 128 * By default, program binaries are automatically cached on stable platforms (which currently exclude ATI Stream), 129 * but the caching can be forced on/off with * see {@link CLContext#setCacheBinaries(boolean) }.<br> 130 * To create a program from sources, please use see {@link CLContext#createProgram(java.lang.String[]) } 131 * @author Olivier Chafik 132 */ 133public class CLProgram extends CLAbstractEntity { 134 135 protected final CLContext context; 136 protected boolean loadedFromBinary; 137 138 protected static CLInfoGetter infos = new CLInfoGetter() { 139 @Override 140 protected int getInfo(long entity, int infoTypeEnum, long size, Pointer out, Pointer<SizeT> sizeOut) { 141 return CL.clGetProgramInfo(entity, infoTypeEnum, size, getPeer(out), getPeer(sizeOut)); 142 } 143 }; 144 145 CLDevice[] devices; 146 CLProgram(CLContext context, CLDevice... devices) { 147 super(0, true); 148 this.context = context; 149 this.devices = devices == null || devices.length == 0 ? context.getDevices() : devices; 150 } 151 CLProgram(CLContext context, Map<CLDevice, byte[]> binaries, String source) { 152 super(0, true); 153 this.context = context; 154 this.source = source; 155 156 setBinaries(binaries); 157 } 158 159 protected void setBinaries(Map<CLDevice, byte[]> binaries) { 160 if (this.devices == null) { 161 this.devices = new CLDevice[binaries.size()]; 162 int iDevice = 0; 163 for (CLDevice device : binaries.keySet()) 164 this.devices[iDevice++] = device; 165 } 166 int nDevices = this.devices.length; 167 if (binaries.size() != nDevices) 168 throw new IllegalArgumentException("Not enough binaries in provided map : expected " + nDevices + " (devices = " + Arrays.asList(devices) + "), got " + binaries.size() + " (" + binaries.keySet() + ")"); 169 170 ReusablePointers ptrs = ReusablePointers.get(); 171 Pointer<Integer> pErr = ptrs.pErr; 172 Pointer<SizeT> lengths = ptrs.sizeT3_1.allocatedSizeTs(nDevices); 173 Pointer<SizeT> deviceIds = ptrs.sizeT3_2.allocatedSizeTs(nDevices); 174 Pointer<SizeT> binariesArray = ptrs.sizeT3_3.allocatedSizeTs(nDevices); 175 Pointer<Byte>[] binariesMems = new Pointer[nDevices]; 176 177 int iDevice = 0; 178 for (CLDevice device : devices) { 179 byte[] binary = binaries.get(device); 180 if (binary == null) 181 throw new IllegalArgumentException("No binary for device " + device + " in provided binaries"); 182 183 binariesArray.setPointerAtIndex(iDevice, binariesMems[iDevice] = pointerToBytes(binary)); 184 lengths.setSizeTAtIndex(iDevice, binary.length); 185 deviceIds.setSizeTAtIndex(iDevice, device.getEntity()); 186 187 iDevice++; 188 } 189 nDevices = iDevice; 190 if (nDevices == 0) 191 return; 192 193 int previousAttempts = 0; 194 Pointer<Integer> statuses = ptrs.int3_1.allocatedInts(nDevices); 195 do { 196 setEntity(CL.clCreateProgramWithBinary(context.getEntity(), nDevices, getPeer(deviceIds), getPeer(lengths), getPeer(binariesArray), getPeer(statuses), getPeer(pErr))); 197 } while (failedForLackOfMemory(pErr.getInt(), previousAttempts++)); 198 if (getEntity() != 0) 199 loadedFromBinary = true; 200 } 201 202 /** 203 * Write the compiled binaries of this program (for all devices it was compiled for), so that it can be restored later using {@link CLContext#loadProgram(java.io.InputStream) } 204 * @param out will be closed 205 * @throws CLBuildException 206 * @throws IOException 207 */ 208 public void store(OutputStream out) throws CLBuildException, IOException { 209 writeBinaries(getBinaries(), getSource(), null, out); 210 } 211 212 private static final void addStoredEntry(ZipOutputStream zout, String name, byte[] data) throws IOException { 213 ZipEntry ze = new ZipEntry(name); 214 ze.setMethod(ZipEntry.STORED); 215 ze.setSize(data.length); 216 CRC32 crc = new CRC32(); 217 crc.update(data,0,data.length); 218 ze.setCrc(crc.getValue()); 219 zout.putNextEntry(ze); 220 zout.write(data); 221 zout.closeEntry(); 222 } 223 224 private static final String BinariesSignatureZipEntryName = "SIGNATURE", SourceZipEntryName = "SOURCE", textEncoding = "utf-8"; 225 public static void writeBinaries(Map<CLDevice, byte[]> binaries, String source, String contentSignatureString, OutputStream out) throws IOException { 226 Map<String, byte[]> binaryBySignature = new HashMap<String, byte[]>(); 227 for (Map.Entry<CLDevice, byte[]> e : binaries.entrySet()) 228 binaryBySignature.put(e.getKey().createSignature(), e.getValue()); // Maybe multiple devices will have the same signature : too bad, we don't care and just write one binary per signature. 229 230 ZipOutputStream zout = new ZipOutputStream(new GZIPOutputStream(new BufferedOutputStream(out))); 231 if (contentSignatureString != null) 232 addStoredEntry(zout, BinariesSignatureZipEntryName, contentSignatureString.getBytes(textEncoding)); 233 234 if (source != null) 235 addStoredEntry(zout, SourceZipEntryName, source.getBytes(textEncoding)); 236 237 for (Map.Entry<String, byte[]> e : binaryBySignature.entrySet()) 238 addStoredEntry(zout, e.getKey(), e.getValue()); 239 240 zout.close(); 241 } 242 public static Pair<Map<CLDevice, byte[]>, String> readBinaries(List<CLDevice> allowedDevices, String expectedContentSignatureString, InputStream in) throws IOException { 243 Map<CLDevice, byte[]> ret = new HashMap<CLDevice, byte[]>(); 244 Map<String, List<CLDevice>> allowedDevicesBySignature = CLDevice.getDevicesBySignature(allowedDevices); 245 246 ZipInputStream zin = new ZipInputStream(new GZIPInputStream(new BufferedInputStream(in))); 247 ZipEntry ze; 248 String source = null; 249 250 boolean first = true; 251 byte[] b = new byte[65536]; 252 while ((ze = zin.getNextEntry()) != null) { 253 String signature = ze.getName(); 254 boolean isSignature = signature.equals(BinariesSignatureZipEntryName); 255 if (first && !isSignature && expectedContentSignatureString != null || 256 !first && isSignature) 257 throw new IOException("Expected signature to be the first zip entry, got '" + signature + "' instead !"); 258 259 first = false; 260 int len; 261 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 262 while ((len = zin.read(b)) > 0) 263 bout.write(b, 0, len); 264 265 byte[] data = bout.toByteArray(); 266 if (isSignature) { 267 if (expectedContentSignatureString != null) { 268 String contentSignatureString = new String(data, textEncoding); 269 if (!expectedContentSignatureString.equals(contentSignatureString)) 270 throw new IOException("Content signature does not match expected one :\nExpected '" + expectedContentSignatureString + "',\nGot '" + contentSignatureString + "'"); 271 } 272 } else if (signature.equals(SourceZipEntryName)) { 273 source = new String(data, textEncoding); 274 } else { 275 List<CLDevice> devices = allowedDevicesBySignature.get(signature); 276 if (devices != null) 277 for (CLDevice device : devices) 278 ret.put(device, data); 279 } 280 } 281 zin.close(); 282 return new Pair<Map<CLDevice, byte[]>, String>(ret, source); 283 } 284 285 List<String> sources = new ArrayList<String>(); 286 Map<CLDevice, cl_program> programByDevice = new HashMap<CLDevice, cl_program>(); 287 288 public CLDevice[] getDevices() { 289 return devices.clone(); 290 } 291 292 /// Workaround to avoid crash of ATI Stream 2.0.0 final (beta 3 & 4 worked fine) 293 public static boolean passMacrosAsSources = true; 294 295 public synchronized void allocate() { 296 if (isAllocated()) 297 throw new IllegalThreadStateException("Program was already allocated !"); 298 299 if (passMacrosAsSources) { 300 if (macros != null && !macros.isEmpty()) { 301 StringBuilder b = new StringBuilder(); 302 for (Map.Entry<String, Object> m : macros.entrySet()) 303 b.append("#" + "define " + m.getKey() + " " + m.getValue() + "\n"); 304 this.sources.add(0, b.toString()); 305 } 306 } 307 308 if (!"false".equals(System.getProperty("javacl.adjustDoubleExtension")) && !"0".equals(System.getenv("JAVACL_ADJUST_DOUBLE_EXTENSION"))) { 309 for (int i = 0, len = sources.size(); i < len; i++) { 310 String source = sources.get(i); 311 for (CLDevice device : getDevices()) 312 source = device.replaceDoubleExtensionByExtensionActuallyAvailable(source); 313 sources.set(i, source); 314 // TODO keep different sources for each device !!! 315 } 316 } 317 318 String[] sources = this.sources.toArray(new String[this.sources.size()]); 319 ReusablePointers ptrs = ReusablePointers.get(); 320 Pointer<Integer> pErr = ptrs.pErr; 321 322 Pointer<SizeT> pLengths = ptrs.sizeT3_1.allocatedSizeTs(sources.length); 323 long[] lengths = new long[sources.length]; 324 for (int i = 0; i < sources.length; i++) { 325 pLengths.setSizeTAtIndex(i, sources[i].length()); 326 } 327 328 long program; 329 int previousAttempts = 0; 330 Pointer<Pointer<Byte>> pSources = pointerToCStrings(sources); 331 do { 332 program = CL.clCreateProgramWithSource( 333 context.getEntity(), 334 sources.length, 335 getPeer(pSources), 336 getPeer(pLengths), 337 getPeer(pErr) 338 ); 339 } while (failedForLackOfMemory(pErr.getInt(), previousAttempts++)); 340 Pointer.release(pSources); 341 setEntity(program); 342 } 343 344 private boolean isAllocated() { 345 return super.getEntity() != 0; 346 } 347 /* 348 @Override 349 protected synchronized cl_program getEntity() { 350 if (!isAllocated()) 351 allocate(); 352 353 return super.getEntity(); 354 }*/ 355 356 @Override 357 protected synchronized long getEntity() { 358 if (!isAllocated()) 359 allocate(); 360 361 return super.getEntity(); 362 } 363 364 List<String> includes; 365 366 /** 367 * Add a path (file or URL) to the list of paths searched for included files.<br> 368 * OpenCL kernels may contain <code> "subpath/file.cl"</code> statements.<br> 369 * This automatically adds a "-Ipath" argument to the compilator's command line options.<br> 370 * Note that it's not necessary to add include paths for files that are in the classpath. 371 * @param path A file or URL that points to the root path from which includes can be resolved. 372 */ 373 public synchronized void addInclude(String path) { 374 if (includes == null) 375 includes = new ArrayList<String>(); 376 includes.add(path); 377 resolvedInclusions = null; 378 } 379 public synchronized void addSource(String src) { 380 if (isAllocated()) 381 throw new IllegalThreadStateException("Program was already allocated : cannot add sources anymore."); 382 sources.add(src); 383 resolvedInclusions = null; 384 } 385 386 Map<String, URL> resolvedInclusions; 387 388 protected Runnable copyIncludesToTemporaryDirectory() throws IOException { 389 Map<String, URL> inclusions = resolveInclusions(); 390 File includesDir = JavaCL.createTempDirectory("includes", "", "includes"); 391 final List<File> filesToDelete = new ArrayList<File>(); 392 for (Map.Entry<String, URL> e : inclusions.entrySet()) { 393 assert log(Level.INFO, "Copying include '" + e.getKey() + "' from '" + e.getValue() + "' to '" + includesDir + "'"); 394 File f = new File(includesDir, e.getKey().replace('/', File.separatorChar)); 395 File p = f.getParentFile(); 396 filesToDelete.add(f); 397 if (p != null) { 398 p.mkdirs(); 399 filesToDelete.add(p); 400 } 401 InputStream in = e.getValue().openStream(); 402 OutputStream out = new FileOutputStream(f); 403 IOUtils.readWrite(in, out); 404 in.close(); 405 out.close(); 406 f.deleteOnExit(); 407 } 408 filesToDelete.add(includesDir); 409 addInclude(includesDir.toString()); 410 return new Runnable() { public void run() { 411 for (File f : filesToDelete) 412 f.delete(); 413 }}; 414 } 415 public Map<String, URL> resolveInclusions() throws IOException { 416 if (resolvedInclusions == null) { 417 resolvedInclusions = new HashMap<String, URL>(); 418 for (String source : sources) 419 resolveInclusions(source, resolvedInclusions); 420 } 421 return resolvedInclusions; 422 } 423 424 static Pattern includePattern = Pattern.compile("\\s*include\\s*\"([^\"]+)\""); 425 private void resolveInclusions(String source, Map<String, URL> ret) throws IOException { 426 List<String> includedPaths = new ArrayList<String>(); 427 Matcher m = includePattern.matcher(source); 428 while (m.find()) { 429 includedPaths.add(m.group(1)); 430 } 431 for (String includedPath : includedPaths) { 432 if (ret.containsKey(includedPath)) 433 continue; 434 URL url = getIncludedSourceURL(includedPath); 435 if (url == null) { 436 assert log(Level.SEVERE, "Failed to resolve include '" + includedPath + "'"); 437 } else { 438 String s = IOUtils.readText(url); 439 ret.put(includedPath, url); 440 resolveInclusions(s, ret); 441 } 442 } 443 } 444 445 public String getIncludedSourceContent(String path) throws IOException { 446 URL url = getIncludedSourceURL(path); 447 if (url == null) 448 return null; 449 450 String src = IOUtils.readText(url); 451 return src; 452 } 453 454 public URL getIncludedSourceURL(String path) throws MalformedURLException { 455 File f = new File(path); 456 if (f.exists()) 457 return f.toURI().toURL(); 458 URL url = Platform.getClassLoader(getClass()).getResource(path); 459 if (url != null) 460 return url; 461 462 if (includes != null) 463 for (String include : includes) { 464 f = new File(new File(include), path); 465 if (f.exists()) 466 return f.toURI().toURL(); 467 468 url = Platform.getClassLoader(getClass()).getResource(f.toString()); 469 if (url != null) 470 return url; 471 472 try { 473 url = new URL(include + (include.endsWith("/") ? "" : "/") + path); 474 url.openStream().close(); 475 return url; 476 } catch (IOException ex) { 477 // Bad URL or impossible to read from the URL 478 } 479 480 } 481 482 return null; 483 } 484 485 String source; 486 /** 487 * Get the source code of this program 488 */ 489 public synchronized String getSource() { 490 if (source == null && !loadedFromBinary) 491 source = infos.getString(getEntity(), CL_PROGRAM_SOURCE); 492 493 return source; 494 } 495 496 /** 497 * Get the binaries of the program (one for each device, in order) 498 * @return map from each device the program was compiled for to the corresponding binary data 499 */ 500 public Map<CLDevice, byte[]> getBinaries() throws CLBuildException { 501 synchronized (this) { 502 if (!built) 503 build(); 504 } 505 506 Pointer<?> s = infos.getMemory(getEntity(), CL_PROGRAM_BINARY_SIZES); 507 int n = (int)s.getValidBytes() / Platform.SIZE_T_SIZE; 508 long[] sizes = s.getSizeTs(n); 509 //int[] sizes = new int[n]; 510 //for (int i = 0; i < n; i++) { 511 // sizes[i] = s.getNativeLong(i * Native.LONG_SIZE).intValue(); 512 //} 513 514 Pointer<?>[] binMems = (Pointer<?>[])new Pointer[n]; 515 ReusablePointers ptrs = ReusablePointers.get(); 516 Pointer<SizeT> binPtrs = ptrs.sizeT3_1.allocatedSizeTs(n);//allocatePointers(n); 517 for (int i = 0; i < n; i++) { 518 binPtrs.setPointerAtIndex(i, binMems[i] = allocateBytes(sizes[i])); 519 } 520 error(infos.getInfo(getEntity(), CL_PROGRAM_BINARIES, n * Pointer.SIZE, binPtrs, null)); 521 522 Map<CLDevice, byte[]> ret = new HashMap<CLDevice, byte[]>(devices.length); 523 int iBin = n == devices.length + 1 ? 1 : 0; 524 for (int i = 0; i < devices.length; i++) { 525 CLDevice device = devices[i]; 526 Pointer<?> bytes = binMems[iBin + i]; 527 if (bytes != null) { 528 ret.put(device, bytes.getBytes((int)sizes[iBin + i])); 529 Pointer.release(bytes); 530 } 531 } 532 return ret; 533 } 534 535 /** 536 * Returns the context of this program 537 */ 538 public CLContext getContext() { 539 return context; 540 } 541 Map<String, Object> macros; 542 public CLProgram defineMacro(String name, Object value) { 543 createMacros(); 544 macros.put(name, value); 545 return this; 546 } 547 public CLProgram undefineMacro(String name) { 548 if (macros != null) 549 macros.remove(name); 550 return this; 551 } 552 553 private void createMacros() { 554 if (macros == null) 555 macros = new LinkedHashMap<String, Object>(); 556 } 557 public void defineMacros(Map<String, Object> macros) { 558 createMacros(); 559 this.macros.putAll(macros); 560 } 561 List<String> extraBuildOptions; 562 563 /** 564 * Add the -cl-fast-relaxed-math compile option.<br> 565 * Sets the optimization options -cl-finite-math-only and -cl-unsafe-math-optimizations. 566 * This allows optimizations for floating-point arithmetic that may violate the IEEE 754 standard and the OpenCL numerical compliance requirements defined in the specification in section 7.4 for single-precision floating-point, section 9.3.9 for double-precision floating-point, and edge case behavior in section 7.5. 567 * This option causes the preprocessor macro __FAST_RELAXED_MATH__ to be defined in the OpenCL program. <br> 568 * Also see : <a href="http://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/clBuildProgram.html">Khronos' documentation for clBuildProgram</a>. 569 */ 570 public void setFastRelaxedMath() { 571 addBuildOption("-cl-fast-relaxed-math"); 572 } 573 574 /** 575 * Add the -cl-no-signed-zero compile option.<br> 576 * Allow optimizations for floating-point arithmetic that ignore the signedness of zero. 577 * IEEE 754 arithmetic specifies the behavior of distinct +0.0 and -0.0 values, which then prohibits simplification of expressions such as x+0.0 or 0.0*x (even with -clfinite-math only). 578 * This option implies that the sign of a zero result isn't significant. <br> 579 * Also see : <a href="http://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/clBuildProgram.html">Khronos' documentation for clBuildProgram</a>. 580 */ 581 public void setNoSignedZero() { 582 addBuildOption("-cl-no-signed-zero"); 583 } 584 585 /** 586 * Add the -cl-mad-enable compile option.<br> 587 * Allow a * b + c to be replaced by a mad. The mad computes a * b + c with reduced accuracy. For example, some OpenCL devices implement mad as truncate the result of a * b before adding it to c.<br> 588 * Also see : <a href="http://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/clBuildProgram.html">Khronos' documentation for clBuildProgram</a>. 589 */ 590 public void setMadEnable() { 591 addBuildOption("-cl-mad-enable"); 592 } 593 /** 594 * Add the -cl-finite-math-only compile option.<br> 595 * Allow optimizations for floating-point arithmetic that assume that arguments and results are not NaNs or infinites. This option may violate the OpenCL numerical compliance requirements defined in in section 7.4 for single-precision floating-point, section 9.3.9 for double-precision floating-point, and edge case behavior in section 7.5.<br> 596 * Also see : <a href="http://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/clBuildProgram.html">Khronos' documentation for clBuildProgram</a>. 597 */ 598 public void setFiniteMathOnly() { 599 addBuildOption("-cl-finite-math-only"); 600 } 601 /** 602 * Add the -cl-unsafe-math-optimizations option.<br> 603 * Allow optimizations for floating-point arithmetic that (a) assume that arguments and results are valid, (b) may violate IEEE 754 standard and (c) may violate the OpenCL numerical compliance requirements as defined in section 7.4 for single-precision floating-point, section 9.3.9 for double-precision floating-point, and edge case behavior in section 7.5. This option includes the -cl-no-signed-zeros and -cl-mad-enable options.<br> 604 * Also see : <a href="http://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/clBuildProgram.html">Khronos' documentation for clBuildProgram</a>. 605 */ 606 public void setUnsafeMathOptimizations() { 607 addBuildOption("-cl-unsafe-math-optimizations"); 608 } 609 /** 610 * Add the <a href="http://www.cs.cmu.edu/afs/cs/academic/class/15668-s11/www/cuda-doc/OpenCL_Extensions/cl_nv_compiler_options.txt">-cl-nv-verbose</a> compilation option (<b><i>NVIDIA GPUs only</i></b>)<br> 611 * Enable verbose mode. Output will be reported in JavaCL's log at the INFO level 612 */ 613 public void setNVVerbose() { 614 addBuildOption("-cl-nv-verbose"); 615 } 616 /** 617 * Add the <a href="http://www.cs.cmu.edu/afs/cs/academic/class/15668-s11/www/cuda-doc/OpenCL_Extensions/cl_nv_compiler_options.txt">-cl-nv-maxrregcount=N</a> compilation option (<b><i>NVIDIA GPUs only</i></b>)<br> 618 * Specify the maximum number of registers that GPU functions can use. 619 * Until a function-specific limit, a higher value will generally increase 620 * the performance of individual GPU threads that execute this function. 621 * However, because thread registers are allocated from a global register 622 * pool on each GPU, a higher value of this option will also reduce the 623 * maximum thread block size, thereby reducing the amount of thread 624 * parallelism. Hence, a good maxrregcount value is the result of a 625 * trade-off. 626 * If this option is not specified, then no maximum is assumed. Otherwise 627 * the specified value will be rounded to the next multiple of 4 registers 628 * until the GPU specific maximum of 128 registers. 629 * @param N positive integer 630 */ 631 public void setNVMaximumRegistryCount(int N) { 632 addBuildOption("-cl-nv-maxrregcount=" + N); 633 } 634 /** 635 * Add the <a href="http://www.cs.cmu.edu/afs/cs/academic/class/15668-s11/www/cuda-doc/OpenCL_Extensions/cl_nv_compiler_options.txt">-cl-nv-opt-level</a> compilation option (<b><i>NVIDIA GPUs only</i></b>)<br> 636 * Specify optimization level (default value: 3) 637 * @param N positive integer, or 0 (no optimization). 638 */ 639 public void setNVOptimizationLevel(int N) { 640 addBuildOption("-cl-nv-opt-level=" + N); 641 } 642 643 /** 644 * Please see <a href="http://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/clBuildProgram.html">OpenCL's clBuildProgram documentation</a> for details on supported build options. 645 */ 646 public synchronized void addBuildOption(String option) { 647 if (option.startsWith("-I")) { 648 addInclude(option.substring(2)); 649 return; 650 } 651 if (extraBuildOptions == null) 652 extraBuildOptions = new ArrayList<String>(); 653 654 extraBuildOptions.add(option); 655 } 656 657 protected String getOptionsString() { 658 StringBuilder b = new StringBuilder("-DJAVACL=1 "); 659 660 if (extraBuildOptions != null) { 661 if (JavaCL.debug) { 662 boolean isCPUOnly = true; 663 for (CLDevice device : getDevices()) { 664 if (!device.getType().contains(CLDevice.Type.CPU)) { 665 isCPUOnly = false; 666 break; 667 } 668 } 669 if (!isCPUOnly) 670 log(Level.WARNING, "Debug mode : cannot only compile with debug flags on a CPU-only context"); 671 else { 672 for (Iterator<String> it = extraBuildOptions.iterator(); it.hasNext();) { 673 String opt = it.next(); 674 if ((opt.startsWith("-O") || opt.startsWith("-g:")) && !JavaCL.DEBUG_COMPILER_FLAGS.contains(opt)) { 675 log(Level.WARNING, "Debug mode : removed argument \"" + opt + "\" from OpenCL compiler command-line arguments"); 676 it.remove(); 677 } 678 } 679 extraBuildOptions.addAll(JavaCL.DEBUG_COMPILER_FLAGS); 680 log(Level.ALL, "Debug mode : added OpenCL compiler command-line arguments \"" + StringUtils.implode(JavaCL.DEBUG_COMPILER_FLAGS, " ") + "\""); 681 } 682 } 683 for (String option : extraBuildOptions) 684 b.append(option.contains(" ") || option.contains("\"") ? "\"" + option.replaceAll("\"", "\\\"") + "\"" : option).append(' '); 685 686 } 687 688 // http://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/clBuildProgram.html 689 //b.append("-O2 -cl-no-signed-zeros -cl-unsafe-math-optimizations -cl-finite-math-only -cl-fast-relaxed-math -cl-strict-aliasing "); 690 691 if (!passMacrosAsSources && macros != null && !macros.isEmpty()) 692 for (Map.Entry<String, Object> m : macros.entrySet()) 693 b.append("-D" + m.getKey() + "=" + m.getValue() + " "); 694 695 if (includes != null) 696 for (String path : includes) 697 if (new File(path).exists()) // path can be an URL as well, in which case it's copied to a local file copyIncludesToTemporaryDirectory() 698 b.append("-I").append(path).append(' '); 699 700 return b.toString(); 701 } 702 703 private volatile Boolean cached; 704 public synchronized void setCached(boolean cached) { 705 this.cached = cached; 706 } 707 public synchronized boolean isCached() { 708 if (cached == null) 709 cached = context.getCacheBinaries(); 710 return cached; 711 } 712 713 protected String computeCacheSignature() throws IOException { 714 StringBuilder b = new StringBuilder(16 * 1024); 715 getContext().getPlatform().toString(b); 716 b.append('\n'); 717 for (CLDevice device : getDevices()) 718 b.append(device).append('\n'); 719 720 b.append(getOptionsString()).append('\n'); 721 if (macros != null && !macros.isEmpty()) 722 for (Map.Entry<String, Object> m : macros.entrySet()) 723 b.append("-D").append(m.getKey()).append('=').append(m.getValue()).append('\n'); 724 725 if (includes != null && !includes.isEmpty()) 726 for (String path : includes) 727 b.append("-I").append(path).append('\n'); 728 729 if (sources != null && !sources.isEmpty()) 730 for (String source : sources) 731 b.append(source).append('\n'); 732 733 Map<String, URL> inclusions = resolveInclusions(); 734 if (inclusions != null && !inclusions.isEmpty()) 735 for (Map.Entry<String, URL> e : inclusions.entrySet()) { 736 URLConnection con = e.getValue().openConnection(); 737 InputStream in = con.getInputStream(); 738 b.append('#').append(e.getKey()).append(con.getLastModified()).append('\n'); 739 in.close(); 740 } 741 742 for (String name : propsToIncludeInSignature) 743 b.append(name).append('=').append(System.getProperty(name)).append('\n'); 744 745 return b.toString(); 746 } 747 private final String[] propsToIncludeInSignature = new String[] { 748 //"java.vm.version", // probably superfluous... 749 "os.version", 750 "os.name" 751 }; 752 753 private String getProgramBuildInfo(long pgm, long deviceId) { 754 Pointer<SizeT> pLen = allocateSizeT(); 755 error(CL.clGetProgramBuildInfo(pgm, deviceId, CL_PROGRAM_BUILD_LOG, 0, 0, getPeer(pLen))); 756 long len = pLen.getSizeT(); 757 if (len == 0) { 758 return null; 759 } 760 Pointer<?> buffer = allocateBytes(len); 761 error(CL.clGetProgramBuildInfo(pgm, deviceId, CL_PROGRAM_BUILD_LOG, len, getPeer(buffer), 0)); 762 String s = buffer.getCString(); 763 Pointer.release(pLen); 764 Pointer.release(buffer); 765 return s; 766 } 767 private Set<String> getProgramBuildInfo(long pgm, Pointer<SizeT> deviceIds) { 768 Set<String> errs = new HashSet<String>(); 769 if (deviceIds == null) { 770 String s = getProgramBuildInfo(pgm, 0); 771 if (s != null && s.length() > 0) 772 errs.add(s); 773 } else { 774 for (SizeT device : deviceIds) { 775 String s = getProgramBuildInfo(pgm, device.longValue()); 776 if (s != null && s.length() > 0) 777 errs.add(s); 778 } 779 } 780 return errs; 781 } 782 boolean built; 783 /** 784 * Returns the context of this program 785 */ 786 public synchronized CLProgram build() throws CLBuildException { 787 if (built) 788 throw new IllegalThreadStateException("Program was already built !"); 789 790 String contentSignature = null; 791 File cacheFile = null; 792 boolean readBinaries = false; 793 if (!loadedFromBinary && isCached()) { 794 try { 795 contentSignature = computeCacheSignature(); 796 byte[] sha = java.security.MessageDigest.getInstance("MD5").digest(contentSignature.getBytes(textEncoding)); 797 StringBuilder shab = new StringBuilder(); 798 for (byte b : sha) 799 shab.append(Integer.toHexString(b & 0xff)); 800 String hash = shab.toString(); 801 cacheFile = new File(JavaCL.userCacheDir, hash); 802 if (cacheFile.exists()) { 803 Pair<Map<CLDevice, byte[]>, String> bins = readBinaries(Arrays.asList(getDevices()), contentSignature, new FileInputStream(cacheFile)); 804 setBinaries(bins.getFirst()); 805 this.source = bins.getSecond(); 806 assert log(Level.INFO, "Read binaries cache from '" + cacheFile + "'"); 807 readBinaries = true; 808 } 809 } catch (Throwable ex) { 810 assert log(Level.WARNING, "Failed to load cached program : " + ex.getMessage()); 811 setEntity(0); 812 } 813 } 814 815 if (!isAllocated()) 816 allocate(); 817 818 Runnable deleteTempFiles = null; 819 if (!readBinaries) 820 try { 821 deleteTempFiles = copyIncludesToTemporaryDirectory(); 822 } catch (IOException ex) { 823 throw new CLBuildException(this, ex.toString(), Collections.EMPTY_LIST); 824 } 825 826 int nDevices = devices.length; 827 Pointer<SizeT> deviceIds = null; 828 if (nDevices != 0) { 829 deviceIds = allocateSizeTs(nDevices); 830 for (int i = 0; i < nDevices; i++) 831 deviceIds.setSizeTAtIndex(i, devices[i].getEntity()); 832 } 833 Pointer<Byte> pOptions = pointerToCString(getOptionsString()); 834 int err = CL.clBuildProgram( 835 getEntity(), 836 nDevices, 837 getPeer(deviceIds), 838 getPeer(pOptions), 839 0, 840 0 841 ); 842 Pointer.release(pOptions); 843 Set<String> errors = getProgramBuildInfo(getEntity(), deviceIds); 844 845 if (err != CL_SUCCESS) { 846 throw new CLBuildException(this, "Compilation failure : " + errorString(err) + " (devices: " + Arrays.asList(getDevices()) + ")", errors); 847 } else { 848 if (!errors.isEmpty()) 849 JavaCL.log(Level.INFO, "Build info :\n\t" + StringUtils.implode(errors, "\n\t")); 850 } 851 built = true; 852 if (deleteTempFiles != null) 853 deleteTempFiles.run(); 854 855 if (isCached() && !readBinaries && !loadedFromBinary) { 856 JavaCL.userCacheDir.mkdirs(); 857 try { 858 Map<CLDevice, byte[]> binaries = getBinaries(); 859 if (!binaries.isEmpty()) { 860 writeBinaries(getBinaries(), getSource(), contentSignature, new FileOutputStream(cacheFile)); 861 assert log(Level.INFO, "Wrote binaries cache to '" + cacheFile + "'"); 862 } 863 } catch (Exception ex) { 864 new IOException("[JavaCL] Failed to cache program", ex).printStackTrace(); 865 } 866 } 867 868 return this; 869 } 870 871 @Override 872 protected void clear() { 873 error(CL.clReleaseProgram(getEntity())); 874 } 875 876 /** 877 * Calls <a href="http://www.khronos.org/registry/cl/sdk/1.2/docs/man/xhtml/clCreateKernelsInProgram.html">clCreateKernelsInProgram</a>.<br> 878 * Return all the kernels found in the program. 879 */ 880 public CLKernel[] createKernels() throws CLBuildException { 881 synchronized (this) { 882 if (!built) 883 build(); 884 } 885 ReusablePointers ptrs = ReusablePointers.get(); 886 Pointer<Integer> pCount = ptrs.int1; 887 int previousAttempts = 0; 888 while (failedForLackOfMemory(CL.clCreateKernelsInProgram(getEntity(), 0, 0, getPeer(pCount)), previousAttempts++)) {} 889 890 int count = pCount.getInt(); 891 Pointer<SizeT> kerns = ptrs.sizeT3_1.allocatedSizeTs(count); 892 previousAttempts = 0; 893 while (failedForLackOfMemory(CL.clCreateKernelsInProgram(getEntity(), count, getPeer(kerns), getPeer(pCount)), previousAttempts++)) {} 894 895 CLKernel[] kernels = new CLKernel[count]; 896 for (int i = 0; i < count; i++) 897 kernels[i] = new CLKernel(this, null, kerns.getSizeTAtIndex(i)); 898 899 return kernels; 900 } 901 902 /** 903 * Calls <a href="http://www.khronos.org/registry/cl/sdk/1.2/docs/man/xhtml/clCreateKernel.html">clCreateKernel</a>.<br> 904 * Find a kernel by its functionName, and optionally bind some arguments to it. 905 */ 906 public CLKernel createKernel(String name, Object... args) throws CLBuildException { 907 synchronized (this) { 908 if (!built) 909 build(); 910 } 911 ReusablePointers ptrs = ReusablePointers.get(); 912 Pointer<Integer> pErr = ptrs.pErr; 913 Pointer<Byte> pName = pointerToCString(name); 914 long kernel; 915 int previousAttempts = 0; 916 do { 917 kernel = CL.clCreateKernel(getEntity(), getPeer(pName), getPeer(pErr)); 918 } while (failedForLackOfMemory(pErr.getInt(), previousAttempts++)); 919 Pointer.release(pName); 920 921 CLKernel kn = new CLKernel(this, name, kernel); 922 if (args.length != 0) 923 kn.setArgs(args); 924 return kn; 925 } 926}