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 // by standart clGetProgramInfo returns 83 // CL_INVALID_PROGRAM_EXECUTABLE if param_name is 84 // CL_PROGRAM_NUM_KERNELS or CL_PROGRAM_KERNEL_NAMES 85 // and a successful program executable has not been 86 // built for at least one device in the list of devices 87 // associated with program. 88 try if( kernel_names.length == 0 ) return; 89 catch( CLCallException e ) 90 { 91 if( e.error == CLError.INVALID_PROGRAM_EXECUTABLE ) return; 92 else throw e; 93 } 94 95 auto knlist = kernel_names.split(";"); 96 97 foreach( name; knlist ) 98 kernels[name] = newChild!CLKernel( this, name ); 99 100 kernels.rehash; 101 } 102 103 CLDevice[] _devices; 104 CLContext _context; 105 string _source; 106 CLKernel[string] kernels; 107 108 package: 109 /// 110 cl_program id; 111 112 public: 113 114 static CLProgram getFromID( cl_program id ) 115 { 116 if( id is null ) return null; 117 if( id in used ) return used[id]; 118 return new CLProgram(id); 119 } 120 121 @property 122 { 123 CLDevice[] devices() { return _devices; } 124 CLContext context() { return _context; } 125 string source() { return _source; } 126 } 127 128 /// get kernel by name 129 CLKernel opIndex( string name ) { return kernels[name]; } 130 131 /// get kernels names 132 string[] kernelsNames() @property { return kernels.keys; } 133 134 /// 135 package static CLProgram createWithSource( CLContext context, string src ) 136 { 137 auto buf = cast(char*)src.toStringz; 138 auto id = checkCode!clCreateProgramWithSource( context.id, 1, 139 &buf, [src.length].ptr ); 140 141 return CLProgram.getFromID( id ); 142 } 143 144 /// 145 BuildInfo[] build( CLDevice[] devs, CLBuildOption[] options=[] ) 146 { 147 try checkCall!clBuildProgram( id, 148 cast(uint)devs.length, 149 getIDsPtr(devs), 150 getOptionsStringz(options), 151 null, null /+ callback and userdata for callback +/ ); 152 catch( CLCallException e ) 153 throw new CLBuildException( e.error, buildInfo(), e.file, e.line ); 154 155 updateInfo(); 156 157 return buildInfo(); 158 } 159 160 /// use devices from context 161 BuildInfo[] build( CLBuildOption[] options=[] ) 162 { return build( context.devices, options ); } 163 164 /// 165 enum BuildStatus 166 { 167 NONE = CL_BUILD_NONE, /// `CL_BUILD_NONE` 168 ERROR = CL_BUILD_ERROR, /// `CL_BUILD_ERROR` 169 SUCCESS = CL_BUILD_SUCCESS, /// `CL_BUILD_SUCCESS` 170 IN_PROGRESS = CL_BUILD_IN_PROGRESS /// `CL_BUILD_IN_PROGRESS` 171 } 172 173 /// 174 static struct BuildInfo 175 { 176 /// 177 CLDevice device; 178 /// 179 BuildStatus status; 180 /// 181 string log; 182 } 183 184 /// 185 BuildInfo[] buildInfo() 186 { 187 return devices 188 .map!(a=>BuildInfo(a,buildStatus(a),buildLog(a))) 189 .array; 190 } 191 192 static private enum info_list = 193 [ 194 "uint reference_count:refcount", 195 "cl_context:CLContext context:reqContext", 196 //"uint num_devices", 197 //"cl_device_id[] devices", 198 //"size_t[] binary_sizes", 199 //"void*[] binaries", 200 //"size_t num_kernels", 201 "string kernel_names", 202 ]; 203 204 mixin( infoMixin( "program", info_list ) ); 205 206 protected: 207 208 override void selfDestroy() 209 { 210 used.remove(id); 211 checkCall!clReleaseProgram(id); 212 } 213 214 /// 215 auto getOptionsStringz( CLBuildOption[] options ) 216 { 217 if( options.length == 0 ) return null; 218 return options.map!(a=>a.toString).array.join(" ").toStringz; 219 } 220 221 /// 222 BuildStatus buildStatus( CLDevice device ) 223 { 224 cl_build_status val; 225 size_t len; 226 checkCall!clGetProgramBuildInfo( id, device.id, 227 CL_PROGRAM_BUILD_STATUS, cl_build_status.sizeof, &val, &len ); 228 return cast(BuildStatus)val; 229 } 230 231 /// 232 string buildLog( CLDevice device ) 233 { 234 size_t len; 235 checkCall!clGetProgramBuildInfo( id, device.id, 236 CL_PROGRAM_BUILD_LOG, 0, null, &len ); 237 if( len == 0 ) return null; 238 auto val = new char[](len); 239 checkCall!clGetProgramBuildInfo( id, device.id, 240 CL_PROGRAM_BUILD_LOG, val.length, val.ptr, &len ); 241 return val.idup; 242 } 243 } 244 245 /// 246 interface CLBuildOption 247 { 248 /// 249 string toString(); 250 251 static 252 { 253 /// 254 CLBuildOption define( string name, string val=null ) 255 { 256 return new class CLBuildOption 257 { 258 override string toString() 259 { return format( "-D %s%s", name, (val?"="~val:"") ); } 260 }; 261 } 262 263 /// 264 CLBuildOption include( string d ) 265 { 266 return new class CLBuildOption 267 { override string toString() { return format( "-I %s", d ); } }; 268 } 269 270 /// 271 @property CLBuildOption inhibitAllWarningMessages() 272 { 273 return new class CLBuildOption 274 { override string toString() { return "-w"; } }; 275 } 276 277 /// 278 @property CLBuildOption makeAllWarningsIntoErrors() 279 { 280 return new class CLBuildOption 281 { override string toString() { return "-Werror"; } }; 282 } 283 284 private 285 { 286 /++ generate static functions to return simple options 287 + Rules: 288 + to camel case, first small 289 + Example: 290 + --- 291 + single-precision-constant -> 292 + static @property CLBuildOption singlePrecisionConstant() 293 + --- 294 + 295 + List: 296 + --- 297 + single-precision-constant 298 + denorms-are-zero 299 + opt-disable 300 + strict-aliasing 301 + mad-enable 302 + no-signed-zeros 303 + unsafe-math-optimizations 304 + finite-math-only 305 + fast-relaxed-math 306 + --- 307 +/ 308 enum string[] simple_build_options = 309 [ 310 "single-precision-constant", 311 "denorms-are-zero", 312 "opt-disable", 313 "strict-aliasing", 314 "mad-enable", 315 "no-signed-zeros", 316 "unsafe-math-optimizations", 317 "finite-math-only", 318 "fast-relaxed-math" 319 ]; 320 321 private string simpleBuildOptionsListDefineString( in string[] list ) 322 { return map!(a=>simpleBuildOptionDefineString(a))(list).array.join("\n"); } 323 324 string simpleBuildOptionDefineString( string opt ) 325 { 326 return format(` 327 static @property CLBuildOption %s() 328 { 329 return new class CLBuildOption 330 { override string toString() { return "-cl-%s"; } }; 331 }`, toCamelCaseBySep(opt,"-",false), opt ); 332 } 333 } 334 335 mixin( simpleBuildOptionsListDefineString( simple_build_options ) ); 336 } 337 }