GLS.CUDA.Compiler.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. //
  2. // The graphics engine GLScene
  3. //
  4. unit GLS.CUDA.Compiler;
  5. (*
  6. Component allows to compile the CUDA-source (*.cu) file in design- and runtime.
  7. To work requires the presence of CUDA Toolkit 4.X and MS Visual Studio C++.
  8. *)
  9. interface
  10. {$I Stage.Defines.inc}
  11. uses
  12. Winapi.Windows,
  13. Winapi.ShellAPI,
  14. Winapi.TlHelp32,
  15. System.UITypes,
  16. System.SysUtils,
  17. System.Classes,
  18. Vcl.Forms,
  19. // VCL.Dialogs,
  20. GLS.ApplicationFileIO,
  21. CUDA.Parser;
  22. type
  23. TGLCUDACompilerOutput = (codeUndefined, codePtx, codeCubin, codeGpu);
  24. (*
  25. compute_10 Basic features
  26. compute_11 + atomic memory operations on global memory
  27. compute_12 + atomic memory operations on shared memory
  28. + vote instructions
  29. compute_13 + double precision floating point support
  30. Compute_20 + FERMI support
  31. *)
  32. TGLCUDAVirtArch = (compute_10, compute_11, compute_12, compute_13, compute_20);
  33. (*
  34. sm_10 ISA_1 Basic features
  35. sm_11 + atomic memory operations on global memory
  36. sm_12 + atomic memory operations on shared memory
  37. + vote instructions
  38. sm_13 + double precision floating point support
  39. sm_20 + FERMI support.
  40. sm_21 + Unknown
  41. *)
  42. TGLCUDARealArch = (sm_10, sm_11, sm_12, sm_13, sm_20, sm_21);
  43. TGLCUDARealArchs = set of TGLCUDARealArch;
  44. TGLCUDACompiler = class(TComponent)
  45. private
  46. FNVCCPath: string;
  47. FCppCompilerPath: string;
  48. FProduct: TStringList;
  49. FProjectModule: string;
  50. FSourceCodeFile: string;
  51. FConsoleContent: string;
  52. FOutputCodeType: TGLCUDACompilerOutput;
  53. FVirtualArch: TGLCUDAVirtArch;
  54. FRealArch: TGLCUDARealArchs;
  55. FMaxRegisterCount: Integer;
  56. FModuleInfo: TCUDAModuleInfo;
  57. procedure SetMaxRegisterCount(Value: Integer);
  58. procedure SetOutputCodeType(const Value: TGLCUDACompilerOutput);
  59. function StoreProjectModule: Boolean;
  60. procedure SetRealArch(AValue: TGLCUDARealArchs);
  61. procedure SetNVCCPath(const AValue: string);
  62. procedure SetCppCompilerPath(const AValue: string);
  63. protected
  64. procedure Loaded; override;
  65. public
  66. constructor Create(AOwner: TComponent); override;
  67. destructor Destroy; override;
  68. procedure Assign(Source: TPersistent); override;
  69. procedure SetSourceCodeFile(const AFileName: string);
  70. function Compile: Boolean;
  71. // Product of compilation.
  72. property Product: TStringList read FProduct write FProduct;
  73. property ModuleInfo: TCUDAModuleInfo read FModuleInfo;
  74. property ConsoleContent: string read FConsoleContent;
  75. published
  76. // NVidia CUDA Compiler.
  77. property NVCCPath: string read FNVCCPath write SetNVCCPath;
  78. (* Microsoft Visual Studio Compiler.
  79. Pascal compiler is still not done. *)
  80. property CppCompilerPath: string read FCppCompilerPath
  81. write SetCppCompilerPath;
  82. // Full file name of source code file.
  83. property SourceCodeFile: string read FSourceCodeFile;
  84. (* Disign-time only property.
  85. Make choose of one of the Project module as CUDA kernel source *)
  86. property ProjectModule: string read FProjectModule write FProjectModule
  87. stored StoreProjectModule;
  88. (* Output code type for module kernel
  89. - Ptx - Parallel Thread Execution
  90. - Cubin - CUDA Binary *)
  91. property OutputCodeType: TGLCUDACompilerOutput read FOutputCodeType
  92. write setOutputCodeType default codePtx;
  93. (* In the CUDA naming scheme,
  94. GPUs are named sm_xy,
  95. where x denotes the GPU generation number,
  96. and y the version in that generation. *)
  97. property RealArchitecture: TGLCUDARealArchs read FRealArch
  98. write SetRealArch default [sm_13];
  99. // Virtual architecture.
  100. property VirtualArchitecture: TGLCUDAVirtArch read FVirtualArch
  101. write FVirtualArch default compute_13;
  102. // Maximum registers that kernel can use.
  103. property MaxRegisterCount: Integer read FMaxRegisterCount
  104. write SetMaxRegisterCount default 32;
  105. end;
  106. TFindCuFileFunc = function(var AModuleName: string): Boolean;
  107. var
  108. vFindCuFileFunc: TFindCuFileFunc;
  109. implementation //------------------------------------------------------------
  110. // ------------------
  111. // ------------------ TGLCUDACompiler ------------------
  112. // ------------------
  113. constructor TGLCUDACompiler.Create(AOwner: TComponent);
  114. var
  115. path: string;
  116. begin
  117. inherited Create(AOwner);
  118. FOutputCodeType := codePtx;
  119. FVirtualArch := compute_13;
  120. FRealArch := [sm_13];
  121. FMaxRegisterCount := 32;
  122. FNVCCPath := '';
  123. path := GetEnvironmentVariable('CUDA_BIN_PATH');
  124. if Length(path) > 0 then
  125. begin
  126. path := IncludeTrailingPathDelimiter(path);
  127. if FileExists(path + 'nvcc.exe') then
  128. FNVCCPath := path;
  129. end;
  130. path := 'C:\Program Files\Microsoft Visual Studio 2019\VC\bin\'; // check path for current VC
  131. if FileExists(path + 'cl.exe') then
  132. FCppCompilerPath := path
  133. else
  134. FCppCompilerPath := '';
  135. FProjectModule := 'none';
  136. FModuleInfo := TCUDAModuleInfo.Create;
  137. end;
  138. destructor TGLCUDACompiler.Destroy;
  139. begin
  140. FModuleInfo.Destroy;
  141. inherited;
  142. end;
  143. procedure TGLCUDACompiler.Loaded;
  144. var
  145. LStr: string;
  146. begin
  147. inherited;
  148. if (FProjectModule <> 'none') and Assigned(vFindCuFileFunc) then
  149. begin
  150. LStr := FProjectModule;
  151. if vFindCuFileFunc(LStr) then
  152. FSourceCodeFile := LStr
  153. else
  154. FSourceCodeFile := '';
  155. end;
  156. end;
  157. procedure TGLCUDACompiler.Assign(Source: TPersistent);
  158. var
  159. compiler: TGLCUDACompiler;
  160. begin
  161. if Source is TGLCUDACompiler then
  162. begin
  163. compiler := TGLCUDACompiler(Source);
  164. FSourceCodeFile := compiler.FSourceCodeFile;
  165. FOutputCodeType := compiler.FOutputCodeType;
  166. FVirtualArch := compiler.FVirtualArch;
  167. end;
  168. inherited Assign(Source);
  169. end;
  170. function TGLCUDACompiler.Compile: Boolean;
  171. const
  172. ReadBufferSize = 1048576; // 1 MB Buffer
  173. cSM: array[TGLCUDARealArch] of string =
  174. ('sm_10', 'sm_11', 'sm_12', 'sm_13', 'sm_20', 'sm_21');
  175. var
  176. tepmPath, tempFile, tempFileExt: string;
  177. commands, nvcc, pathfile, msg: string;
  178. rArch: TGLCUDARealArch;
  179. CodeSource: TStringList;
  180. Security: TSecurityAttributes;
  181. ReadPipe, WritePipe: THandle;
  182. start: TStartUpInfo;
  183. ProcessInfo: TProcessInformation;
  184. Buffer: PAnsiChar;
  185. TotalBytesRead, BytesRead: DWORD;
  186. Apprunning, n, BytesLeftThisMessage, TotalBytesAvail: Integer;
  187. begin
  188. if not FileExists(FSourceCodeFile) then
  189. begin
  190. if csDesigning in ComponentState then
  191. // MessageDlg('Source File Not Found', TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0)
  192. Write('Source File Not Found')
  193. else
  194. {$IFDEF USE_LOGGING}
  195. LogError('Source File Not Found');
  196. {$ENDIF}
  197. exit(false);
  198. end;
  199. CodeSource := TStringList.Create;
  200. CodeSource.LoadFromFile(FSourceCodeFile);
  201. Result := false;
  202. FConsoleContent := '';
  203. if FileExists(FNVCCPath + 'nvcc.exe') and
  204. FileExists(FCppCompilerPath + 'cl.exe') and Assigned(FProduct) then
  205. begin
  206. tepmPath := GetEnvironmentVariable('TEMP');
  207. tepmPath := IncludeTrailingPathDelimiter(tepmPath);
  208. tempFile := tepmPath + 'temp';
  209. CodeSource.SaveToFile(tempFile + '.cu');
  210. commands := '"' + tempFile + '.cu" ';
  211. commands := commands + '-arch ';
  212. case FVirtualArch of
  213. compute_10:
  214. commands := commands + 'compute_10 ';
  215. compute_11:
  216. commands := commands + 'compute_11 ';
  217. compute_12:
  218. commands := commands + 'compute_12 ';
  219. compute_13:
  220. commands := commands + 'compute_13 ';
  221. compute_20:
  222. commands := commands + 'compute_20 ';
  223. end;
  224. commands := commands + '-code ';
  225. for rArch in FRealArch do
  226. commands := commands + cSM[rArch] + ', ';
  227. commands[Length(commands)-1] := ' ';
  228. commands := commands + '-ccbin ';
  229. pathfile := Copy(FCppCompilerPath, 1, Length(FCppCompilerPath) - 1);
  230. commands := commands + '"' + pathfile + '" ';
  231. commands := commands + '-Xcompiler "/EHsc /W3 /nologo /O2 /Zi /MT " ';
  232. commands := commands + '-maxrregcount=' + IntToStr(FMaxRegisterCount) + ' ';
  233. commands := commands + '-m32 ';
  234. case FOutputCodeType of
  235. codePtx:
  236. begin
  237. commands := commands + '--ptx ';
  238. tempFileExt := 'ptx';
  239. end;
  240. codeCubin:
  241. begin
  242. commands := commands + '--cubin ';
  243. tempFileExt := 'cubin';
  244. end;
  245. codeGpu:
  246. begin
  247. commands := commands + '--gpu ';
  248. tempFileExt := 'gpu';
  249. end;
  250. end;
  251. commands := commands + '-o "' + tempFile + '.' + tempFileExt + '" ';
  252. commands := commands + #00;
  253. nvcc := FNVCCPath + 'nvcc.exe ';
  254. with Security do
  255. begin
  256. nlength := SizeOf(TSecurityAttributes);
  257. binherithandle := true;
  258. lpsecuritydescriptor := nil;
  259. end;
  260. if CreatePipe(ReadPipe, WritePipe, @Security, 0) then
  261. begin
  262. // Redirect In- and Output through STARTUPINFO structure
  263. Buffer := AllocMem(ReadBufferSize + 1);
  264. FillChar(start, SizeOf(start), #0);
  265. start.cb := SizeOf(start);
  266. start.hStdOutput := WritePipe;
  267. start.hStdInput := ReadPipe;
  268. start.hStdError := WritePipe;
  269. start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
  270. start.wShowWindow := SW_HIDE;
  271. // Creates a Console Child Process with redirected input and output
  272. if CreateProcess(nil, PChar(nvcc+commands), @Security, @Security, true,
  273. CREATE_NO_WINDOW or NORMAL_PRIORITY_CLASS, nil, nil, start,
  274. ProcessInfo) then
  275. begin
  276. n := 0;
  277. TotalBytesRead := 0;
  278. repeat
  279. // Increase counter to prevent an endless loop if the process is dead
  280. Inc(n, 1);
  281. // wait for end of child process
  282. Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100);
  283. Application.ProcessMessages;
  284. // it is important to read from time to time the output information
  285. // so that the pipe is not blocked by an overflow. New information
  286. // can be written from the console app to the pipe only if there is
  287. // enough buffer space.
  288. if not PeekNamedPipe(ReadPipe, @Buffer[TotalBytesRead],
  289. ReadBufferSize, @BytesRead, @TotalBytesAvail,
  290. @BytesLeftThisMessage) then
  291. break
  292. else if BytesRead > 0 then
  293. ReadFile(ReadPipe, Buffer[TotalBytesRead], BytesRead,
  294. BytesRead, nil);
  295. TotalBytesRead := TotalBytesRead + BytesRead;
  296. until (Apprunning <> WAIT_TIMEOUT) or (n > 150);
  297. Buffer[TotalBytesRead] := #00;
  298. OemToCharA(Buffer, Buffer);
  299. end
  300. else
  301. begin
  302. if csDesigning in ComponentState then
  303. // MessageDlg('Fail Run NVCC', TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0)
  304. Write('Fail Run NVCC')
  305. else
  306. {$IFDEF USE_LOGGING}
  307. LogError('Fail Run NVCC');
  308. {$ENDIF}
  309. end;
  310. pathfile := tempFile + '.' + tempFileExt;
  311. if FileExists(pathfile) then
  312. begin
  313. FProduct.LoadFromFile(pathfile);
  314. FModuleInfo.ParseModule(CodeSource, FProduct);
  315. if csDesigning in ComponentState then
  316. FProduct.OnChange(Self);
  317. DeleteFile(pathfile);
  318. Result := true;
  319. FConsoleContent := string(StrPas(Buffer));
  320. msg := Format('Success Compilation', [FConsoleContent]);
  321. if csDesigning in ComponentState then
  322. // MessageDlg(msg, TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], 0)
  323. Write(msg)
  324. else
  325. {$IFDEF USE_LOGGING}
  326. LogInfo(msg);
  327. {$ENDIF}
  328. end
  329. else
  330. begin
  331. msg := Format('Fail Compilation', [StrPas(Buffer)]);
  332. if csDesigning in ComponentState then
  333. //MessageDlg(msg, TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0)
  334. Write(msg)
  335. else
  336. {$IFDEF USE_LOGGING}
  337. LogError(msg);
  338. {$ENDIF}
  339. end;
  340. FreeMem(Buffer);
  341. CloseHandle(ProcessInfo.hProcess);
  342. CloseHandle(ProcessInfo.hThread);
  343. CloseHandle(ReadPipe);
  344. CloseHandle(WritePipe);
  345. end
  346. else
  347. begin
  348. if csDesigning in ComponentState then
  349. //MessageDlg('Fail Create Pipe', TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0)
  350. Write(msg)
  351. else
  352. {$IFDEF USE_LOGGING}
  353. GLSLogger.LogError(strFailCreatePipe);
  354. {$ENDIF}
  355. end;
  356. pathfile := tempFile + '.cu';
  357. DeleteFile(pathfile);
  358. end;
  359. CodeSource.Free;
  360. end;
  361. procedure TGLCUDACompiler.SetCppCompilerPath(const AValue: string);
  362. begin
  363. if FileExists(AValue + 'cl.exe') then
  364. FCppCompilerPath := AValue;
  365. end;
  366. procedure TGLCUDACompiler.setMaxRegisterCount(Value: Integer);
  367. begin
  368. if Value <> FMaxRegisterCount then
  369. begin
  370. Value := 4 * (Value div 4);
  371. if Value < 4 then
  372. Value := 4;
  373. if Value > 128 then
  374. Value := 128;
  375. FMaxRegisterCount := Value;
  376. end;
  377. end;
  378. procedure TGLCUDACompiler.SetNVCCPath(const AValue: string);
  379. begin
  380. if FileExists(AValue + 'nvcc.exe') then
  381. FNVCCPath := AValue;
  382. end;
  383. procedure TGLCUDACompiler.setOutputCodeType(const Value
  384. : TGLCUDACompilerOutput);
  385. begin
  386. if Value = codeUndefined then
  387. Exit;
  388. FOutputCodeType := Value;
  389. end;
  390. procedure TGLCUDACompiler.SetRealArch(AValue: TGLCUDARealArchs);
  391. begin
  392. if AValue = [] then
  393. AValue := [sm_10];
  394. FRealArch := AValue;
  395. end;
  396. procedure TGLCUDACompiler.SetSourceCodeFile(const AFileName: string);
  397. begin
  398. if FileStreamExists(AFileName) then
  399. FSourceCodeFile := AFileName;
  400. end;
  401. function TGLCUDACompiler.StoreProjectModule: Boolean;
  402. begin
  403. Result := FProjectModule <> 'none';
  404. end;
  405. end.