1 module dcl.program;
2 
3 import dcl.base;
4 import dcl.device;
5 import dcl.context;
6 import dcl.kernel;
7 
8 class CLBuildException : CLException
9 {
10     CLError error;
11     CLProgram.BuildInfo[] info;
12     this( CLError error, CLProgram.BuildInfo[] info,
13             string file=__FILE__, size_t line=__LINE__ )
14     {
15         this.error = error;
16         this.info = info;
17         super( format( "cl program build failed: %s\n%s", error,
18                     this.info.map!(a=>format( "build for dev <%s> %s log:\n%s",
19                             a.device.name, a.status, a.log )).array.join("\n") ), file, line );
20     }
21 }
22 
23 ///
24 class CLProgram : CLObject
25 {
26 protected:
27 
28     static CLProgram[cl_program] used;
29 
30     ///
31     this( cl_program id )
32     {
33         enforce( id !is null, new CLException( "can't create program with null id" ) );
34         enforce( id !in used, new CLException( "can't create existing program" ) );
35         this.id = id;
36         used[id] = this;
37 
38         updateInfo();
39     }
40 
41     void updateInfo()
42     {
43         _context = reqContext;
44         updateSource();
45         updateDevices();
46         updateKernels();
47     }
48 
49     void updateDevices()
50     {
51         uint ndev;
52         checkCall!clGetProgramInfo( id, CL_PROGRAM_NUM_DEVICES,
53                 uint.sizeof, &ndev, null );
54 
55         auto dev_ids = new cl_device_id[](ndev);
56         size_t dev_ids_bytes;
57 
58         checkCall!clGetProgramInfo( id, CL_PROGRAM_DEVICES,
59                 ndev * cl_device_id.sizeof,
60                 dev_ids.ptr, &dev_ids_bytes );
61 
62         _devices = dev_ids.map!(a=>CLDevice.getFromID(a)).array;
63     }
64 
65     void updateSource()
66     {
67         size_t len;
68         checkCall!clGetProgramInfo( id, CL_PROGRAM_SOURCE, 0, null, &len );
69         auto src = new char[]( len );
70         checkCall!clGetProgramInfo( id, CL_PROGRAM_SOURCE, len, src.ptr, &len );
71         _source = src.tr("\0","\n").idup;
72     }
73 
74     void updateKernels()
75     {
76         foreach( name, kernel; kernels )
77             kernel.destroy;
78 
79         //kernels.clear;
80         kernels = null;
81 
82         // clGetProgramInfo crashes on OSX when querying CL_PROGRAM_KERNEL_NAMES
83         // before a program is built. Here's a workaround for that.
84         version( OSX )
85         {
86             immutable hasValidInfo = any!( (a) => a.status == BuildStatus.SUCCESS )( buildInfo() );
87             if( !hasValidInfo ) return;
88         }
89 
90         // by standart clGetProgramInfo returns
91         // CL_INVALID_PROGRAM_EXECUTABLE if param_name is
92         // CL_PROGRAM_NUM_KERNELS or CL_PROGRAM_KERNEL_NAMES
93         // and a successful program executable has not been
94         // built for at least one device in the list of devices
95         // associated with program.
96         try if( kernel_names.length == 0 ) return;
97         catch( CLCallException e )
98         {
99             if( e.error == CLError.INVALID_PROGRAM_EXECUTABLE ) return;
100             else throw e;
101         }
102 
103         auto knlist = kernel_names.split(";");
104 
105         foreach( name; knlist )
106             kernels[name] = newChild!CLKernel( this, name );
107 
108         kernels.rehash;
109     }
110 
111     CLDevice[] _devices;
112     CLContext _context;
113     string _source;
114     CLKernel[string] kernels;
115 
116 package:
117     ///
118     cl_program id;
119 
120 public:
121 
122     static CLProgram getFromID( cl_program id )
123     {
124         if( id is null ) return null;
125         if( id in used ) return used[id];
126         return new CLProgram(id);
127     }
128 
129     @property
130     {
131         CLDevice[] devices() { return _devices; }
132         CLContext context() { return _context; }
133         string source() { return _source; }
134     }
135 
136     /// get kernel by name
137     CLKernel opIndex( string name ) { return kernels[name]; }
138 
139     /// get kernels names
140     string[] kernelsNames() @property { return kernels.keys; }
141 
142     ///
143     package static CLProgram createWithSource( CLContext context, string src )
144     {
145         auto buf = cast(char*)src.toStringz;
146         auto id = checkCode!clCreateProgramWithSource( context.id, 1,
147                      &buf, [src.length].ptr );
148 
149         return CLProgram.getFromID( id );
150     }
151 
152     ///
153     BuildInfo[] build( CLDevice[] devs, CLBuildOption[] options=[] )
154     {
155         try checkCall!clBuildProgram( id,
156                 cast(uint)devs.length,
157                 getIDsPtr(devs),
158                 getOptionsStringz(options),
159                 null, null /+ callback and userdata for callback +/ );
160         catch( CLCallException e )
161             throw new CLBuildException( e.error, buildInfo(), e.file, e.line );
162 
163         updateInfo();
164 
165         return buildInfo();
166     }
167 
168     /// use devices from context
169     BuildInfo[] build( CLBuildOption[] options=[] )
170     { return build( context.devices, options ); }
171 
172     ///
173     enum BuildStatus
174     {
175         NONE        = CL_BUILD_NONE,       /// `CL_BUILD_NONE`
176         ERROR       = CL_BUILD_ERROR,      /// `CL_BUILD_ERROR`
177         SUCCESS     = CL_BUILD_SUCCESS,    /// `CL_BUILD_SUCCESS`
178         IN_PROGRESS = CL_BUILD_IN_PROGRESS /// `CL_BUILD_IN_PROGRESS`
179     }
180 
181     ///
182     static struct BuildInfo
183     {
184         ///
185         CLDevice device;
186         ///
187         BuildStatus status;
188         ///
189         string log;
190     }
191 
192     ///
193     BuildInfo[] buildInfo()
194     {
195         return devices
196             .map!(a=>BuildInfo(a,buildStatus(a),buildLog(a)))
197             .array;
198     }
199 
200     static private enum info_list =
201     [
202         "uint reference_count:refcount",
203         "cl_context:CLContext context:reqContext",
204         //"uint num_devices",
205         //"cl_device_id[] devices",
206         //"size_t[] binary_sizes",
207         //"void*[] binaries",
208         //"size_t num_kernels",
209         "string kernel_names",
210     ];
211 
212     mixin( infoMixin( "program", info_list ) );
213 
214 protected:
215 
216     override void selfDestroy()
217     {
218         used.remove(id);
219         checkCall!clReleaseProgram(id);
220     }
221 
222     ///
223     auto getOptionsStringz( CLBuildOption[] options )
224     {
225         if( options.length == 0 ) return null;
226         return options.map!(a=>a.toString).array.join(" ").toStringz;
227     }
228 
229     ///
230     BuildStatus buildStatus( CLDevice device )
231     {
232         cl_build_status val;
233         size_t len;
234         checkCall!clGetProgramBuildInfo( id, device.id,
235                 CL_PROGRAM_BUILD_STATUS, cl_build_status.sizeof, &val, &len );
236         return cast(BuildStatus)val;
237     }
238 
239     ///
240     string buildLog( CLDevice device )
241     {
242         size_t len;
243         checkCall!clGetProgramBuildInfo( id, device.id,
244                 CL_PROGRAM_BUILD_LOG, 0, null, &len );
245         if( len == 0 ) return null;
246         auto val = new char[](len);
247         checkCall!clGetProgramBuildInfo( id, device.id,
248                 CL_PROGRAM_BUILD_LOG, val.length, val.ptr, &len );
249         return val.idup;
250     }
251 }
252 
253 ///
254 interface CLBuildOption
255 {
256     ///
257     string toString();
258 
259     static
260     {
261         ///
262         CLBuildOption define( string name, string val=null )
263         {
264             return new class CLBuildOption
265             {
266                 override string toString()
267                 { return format( "-D %s%s", name, (val?"="~val:"") ); }
268             };
269         }
270 
271         ///
272         CLBuildOption include( string d )
273         {
274             return new class CLBuildOption
275             { override string toString() { return format( "-I %s", d ); } };
276         }
277 
278         ///
279         @property CLBuildOption inhibitAllWarningMessages()
280         {
281             return new class CLBuildOption
282             { override string toString() { return "-w"; } };
283         }
284 
285         ///
286         @property CLBuildOption makeAllWarningsIntoErrors()
287         {
288             return new class CLBuildOption
289             { override string toString() { return "-Werror"; } };
290         }
291 
292         private
293         {
294             /++ generate static functions to return simple options
295                 + Rules:
296                 +   to camel case, first small
297                 + Example:
298                 + ---
299                 +   single-precision-constant ->
300                 +   static @property CLBuildOption singlePrecisionConstant()
301                 + ---
302                 +
303                 + List:
304                 + ---
305                 +  single-precision-constant
306                 +  denorms-are-zero
307                 +  opt-disable
308                 +  strict-aliasing
309                 +  mad-enable
310                 +  no-signed-zeros
311                 +  unsafe-math-optimizations
312                 +  finite-math-only
313                 +  fast-relaxed-math
314                 + ---
315                 +/
316             enum string[] simple_build_options =
317             [
318                 "single-precision-constant",
319                 "denorms-are-zero",
320                 "opt-disable",
321                 "strict-aliasing",
322                 "mad-enable",
323                 "no-signed-zeros",
324                 "unsafe-math-optimizations",
325                 "finite-math-only",
326                 "fast-relaxed-math"
327             ];
328 
329             private string simpleBuildOptionsListDefineString( in string[] list )
330             { return map!(a=>simpleBuildOptionDefineString(a))(list).array.join("\n"); }
331 
332             string simpleBuildOptionDefineString( string opt )
333             {
334                 return format(`
335             static @property CLBuildOption %s()
336             {
337                 return new class CLBuildOption
338                 { override string toString() { return "-cl-%s"; } };
339             }`, toCamelCaseBySep(opt,"-",false), opt );
340             }
341         }
342 
343         mixin( simpleBuildOptionsListDefineString( simple_build_options ) );
344     }
345 }