GLS.CUDA.Compiler.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. //
  2. // The graphics engine GLScene https://github.com/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 GLScene.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. else
  193. {$IFDEF USE_LOGGING}
  194. LogError('Source File Not Found');
  195. {$ENDIF}
  196. exit(false);
  197. end;
  198. CodeSource := TStringList.Create;
  199. CodeSource.LoadFromFile(FSourceCodeFile);
  200. Result := false;
  201. FConsoleContent := '';
  202. if FileExists(FNVCCPath + 'nvcc.exe') and
  203. FileExists(FCppCompilerPath + 'cl.exe') and Assigned(FProduct) then
  204. begin
  205. tepmPath := GetEnvironmentVariable('TEMP');
  206. tepmPath := IncludeTrailingPathDelimiter(tepmPath);
  207. tempFile := tepmPath + 'temp';
  208. CodeSource.SaveToFile(tempFile + '.cu');
  209. commands := '"' + tempFile + '.cu" ';
  210. commands := commands + '-arch ';
  211. case FVirtualArch of
  212. compute_10:
  213. commands := commands + 'compute_10 ';
  214. compute_11:
  215. commands := commands + 'compute_11 ';
  216. compute_12:
  217. commands := commands + 'compute_12 ';
  218. compute_13:
  219. commands := commands + 'compute_13 ';
  220. compute_20:
  221. commands := commands + 'compute_20 ';
  222. end;
  223. commands := commands + '-code ';
  224. for rArch in FRealArch do
  225. commands := commands + cSM[rArch] + ', ';
  226. commands[Length(commands)-1] := ' ';
  227. commands := commands + '-ccbin ';
  228. pathfile := Copy(FCppCompilerPath, 1, Length(FCppCompilerPath) - 1);
  229. commands := commands + '"' + pathfile + '" ';
  230. commands := commands + '-Xcompiler "/EHsc /W3 /nologo /O2 /Zi /MT " ';
  231. commands := commands + '-maxrregcount=' + IntToStr(FMaxRegisterCount) + ' ';
  232. commands := commands + '-m32 ';
  233. case FOutputCodeType of
  234. codePtx:
  235. begin
  236. commands := commands + '--ptx ';
  237. tempFileExt := 'ptx';
  238. end;
  239. codeCubin:
  240. begin
  241. commands := commands + '--cubin ';
  242. tempFileExt := 'cubin';
  243. end;
  244. codeGpu:
  245. begin
  246. commands := commands + '--gpu ';
  247. tempFileExt := 'gpu';
  248. end;
  249. end;
  250. commands := commands + '-o "' + tempFile + '.' + tempFileExt + '" ';
  251. commands := commands + #00;
  252. nvcc := FNVCCPath + 'nvcc.exe ';
  253. with Security do
  254. begin
  255. nlength := SizeOf(TSecurityAttributes);
  256. binherithandle := true;
  257. lpsecuritydescriptor := nil;
  258. end;
  259. if CreatePipe(ReadPipe, WritePipe, @Security, 0) then
  260. begin
  261. // Redirect In- and Output through STARTUPINFO structure
  262. Buffer := AllocMem(ReadBufferSize + 1);
  263. FillChar(start, SizeOf(start), #0);
  264. start.cb := SizeOf(start);
  265. start.hStdOutput := WritePipe;
  266. start.hStdInput := ReadPipe;
  267. start.hStdError := WritePipe;
  268. start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
  269. start.wShowWindow := SW_HIDE;
  270. // Creates a Console Child Process with redirected input and output
  271. if CreateProcess(nil, PChar(nvcc+commands), @Security, @Security, true,
  272. CREATE_NO_WINDOW or NORMAL_PRIORITY_CLASS, nil, nil, start,
  273. ProcessInfo) then
  274. begin
  275. n := 0;
  276. TotalBytesRead := 0;
  277. repeat
  278. // Increase counter to prevent an endless loop if the process is dead
  279. Inc(n, 1);
  280. // wait for end of child process
  281. Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100);
  282. Application.ProcessMessages;
  283. // it is important to read from time to time the output information
  284. // so that the pipe is not blocked by an overflow. New information
  285. // can be written from the console app to the pipe only if there is
  286. // enough buffer space.
  287. if not PeekNamedPipe(ReadPipe, @Buffer[TotalBytesRead],
  288. ReadBufferSize, @BytesRead, @TotalBytesAvail,
  289. @BytesLeftThisMessage) then
  290. break
  291. else if BytesRead > 0 then
  292. ReadFile(ReadPipe, Buffer[TotalBytesRead], BytesRead,
  293. BytesRead, nil);
  294. TotalBytesRead := TotalBytesRead + BytesRead;
  295. until (Apprunning <> WAIT_TIMEOUT) or (n > 150);
  296. Buffer[TotalBytesRead] := #00;
  297. OemToCharA(Buffer, Buffer);
  298. end
  299. else
  300. begin
  301. if csDesigning in ComponentState then
  302. MessageDlg('Fail Run NVCC', TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0)
  303. else
  304. {$IFDEF USE_LOGGING}
  305. LogError('Fail Run NVCC');
  306. {$ENDIF}
  307. end;
  308. pathfile := tempFile + '.' + tempFileExt;
  309. if FileExists(pathfile) then
  310. begin
  311. FProduct.LoadFromFile(pathfile);
  312. FModuleInfo.ParseModule(CodeSource, FProduct);
  313. if csDesigning in ComponentState then
  314. FProduct.OnChange(Self);
  315. DeleteFile(pathfile);
  316. Result := true;
  317. FConsoleContent := string(StrPas(Buffer));
  318. msg := Format('Success Compilation', [FConsoleContent]);
  319. if csDesigning in ComponentState then
  320. MessageDlg(msg, TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], 0)
  321. else
  322. {$IFDEF USE_LOGGING}
  323. LogInfo(msg);
  324. {$ENDIF}
  325. end
  326. else
  327. begin
  328. msg := Format('Fail Compilation', [StrPas(Buffer)]);
  329. if csDesigning in ComponentState then
  330. MessageDlg(msg, TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0)
  331. else
  332. {$IFDEF USE_LOGGING}
  333. LogError(msg);
  334. {$ENDIF}
  335. end;
  336. FreeMem(Buffer);
  337. CloseHandle(ProcessInfo.hProcess);
  338. CloseHandle(ProcessInfo.hThread);
  339. CloseHandle(ReadPipe);
  340. CloseHandle(WritePipe);
  341. end
  342. else
  343. begin
  344. if csDesigning in ComponentState then
  345. MessageDlg('Fail Create Pipe', TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0)
  346. else
  347. {$IFDEF USE_LOGGING}
  348. GLSLogger.LogError(strFailCreatePipe);
  349. {$ENDIF}
  350. end;
  351. pathfile := tempFile + '.cu';
  352. DeleteFile(pathfile);
  353. end;
  354. CodeSource.Free;
  355. end;
  356. procedure TGLCUDACompiler.SetCppCompilerPath(const AValue: string);
  357. begin
  358. if FileExists(AValue + 'cl.exe') then
  359. FCppCompilerPath := AValue;
  360. end;
  361. procedure TGLCUDACompiler.setMaxRegisterCount(Value: Integer);
  362. begin
  363. if Value <> FMaxRegisterCount then
  364. begin
  365. Value := 4 * (Value div 4);
  366. if Value < 4 then
  367. Value := 4;
  368. if Value > 128 then
  369. Value := 128;
  370. FMaxRegisterCount := Value;
  371. end;
  372. end;
  373. procedure TGLCUDACompiler.SetNVCCPath(const AValue: string);
  374. begin
  375. if FileExists(AValue + 'nvcc.exe') then
  376. FNVCCPath := AValue;
  377. end;
  378. procedure TGLCUDACompiler.setOutputCodeType(const Value
  379. : TGLCUDACompilerOutput);
  380. begin
  381. if Value = codeUndefined then
  382. exit;
  383. FOutputCodeType := Value;
  384. end;
  385. procedure TGLCUDACompiler.SetRealArch(AValue: TGLCUDARealArchs);
  386. begin
  387. if AValue = [] then
  388. AValue := [sm_10];
  389. FRealArch := AValue;
  390. end;
  391. procedure TGLCUDACompiler.SetSourceCodeFile(const AFileName: string);
  392. begin
  393. if FileStreamExists(AFileName) then
  394. FSourceCodeFile := AFileName;
  395. end;
  396. function TGLCUDACompiler.StoreProjectModule: Boolean;
  397. begin
  398. Result := FProjectModule <> 'none';
  399. end;
  400. end.