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}