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 }