ctask.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. {
  2. Copyright (c) 2024- by Michael Van Canneyt
  3. This unit handles the compiler tasks.
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15. ****************************************************************************
  16. }
  17. unit ctask;
  18. {$mode ObjFPC}
  19. { $DEFINE DEBUG_CTASK}
  20. interface
  21. uses
  22. finput, fmodule, cclasses, globstat;
  23. type
  24. { ttask_list
  25. About state:
  26. Contains scanner/parser position needed for compiling pascal sources,
  27. irrelevant for loading ppu(s).
  28. It is restored before continuing and saved afterwards (if unfinished).
  29. Loading ppu files works recursively and stops when a unit requires (re)compile,
  30. A recompile discards the saved state the adds the module to ctask.
  31. When the recursion steps back, leaving the current unit unfinished the state is saved,
  32. so ctask can continue with another unit.
  33. }
  34. ttask_list = class(tlinkedlistitem)
  35. module : tmodule;
  36. state : tglobalstate;
  37. constructor create(_m : tmodule);
  38. destructor destroy; override;
  39. procedure SaveState;
  40. procedure RestoreState;
  41. procedure DiscardState;
  42. function nexttask : ttask_list; inline;
  43. end;
  44. ttasklinkedlist = class(tlinkedlist)
  45. function firsttask : ttask_list; inline;
  46. end;
  47. { ttask_handler }
  48. ttask_handler = class
  49. private
  50. list : ttasklinkedlist;
  51. hash : TFPHashList;
  52. main : tmodule;
  53. procedure rebuild_hash;
  54. public
  55. constructor create;
  56. destructor destroy; override;
  57. // Find the task for module m
  58. function findtask(m : tmodule) : ttask_list;
  59. // Can we continue processing this module ? If not, firstwaiting contains first module that m is waiting for.
  60. function cancontinue(m : tmodule; checksub : boolean; out firstwaiting: tmodule): boolean;
  61. // Overload of cancontinue, based on task.
  62. function cancontinue(t: ttask_list; out firstwaiting: tmodule): boolean; inline;
  63. // Check modules waiting for t, find highest state and count them
  64. function countwaiting(m : tmodule; out highest_state: tmodulestate; out firsthighestwaiting: tmodule): integer; // EnableCTaskPPU: remove
  65. // Continue processing this module. Return true if the module is done and can be removed.
  66. function continue_task(t : ttask_list): Boolean;
  67. {$IFDEF EnableCTaskPPU}
  68. // Check for a circular dependency and fix it
  69. function check_cycle: boolean;
  70. {$ENDIF}
  71. // process the queue. Note that while processing the queue, elements will be added.
  72. procedure processqueue;
  73. // add a module to the queue. If a module is already in the queue, we do not add it again.
  74. procedure addmodule(m : tmodule);
  75. // write current queue and what is waiting for what
  76. procedure write_queue;
  77. end;
  78. var
  79. task_handler : TTask_handler;
  80. procedure InitTaskHandler;
  81. procedure DoneTaskHandler;
  82. implementation
  83. uses
  84. verbose, fppu, globtype, sysutils,
  85. scanner, parser, pmodules, symbase;
  86. procedure InitTaskHandler;
  87. begin
  88. task_handler:=ttask_handler.create;
  89. end;
  90. procedure DoneTaskHandler;
  91. begin
  92. freeandnil(task_handler);
  93. end;
  94. { ttasklinkedlist }
  95. function ttasklinkedlist.firsttask: ttask_list;
  96. begin
  97. Result:=ttask_list(first);
  98. end;
  99. { ttask_list }
  100. constructor ttask_list.create(_m: tmodule);
  101. begin
  102. inherited create;
  103. module:=_m;
  104. state:=nil;
  105. end;
  106. destructor ttask_list.destroy;
  107. begin
  108. DiscardState;
  109. Inherited;
  110. end;
  111. procedure ttask_list.DiscardState;
  112. begin
  113. FreeAndNil(state);
  114. end;
  115. function ttask_list.nexttask: ttask_list;
  116. begin
  117. Result:=ttask_list(next);
  118. end;
  119. procedure ttask_list.SaveState;
  120. begin
  121. if State=Nil then
  122. State:=tglobalstate.Create
  123. else
  124. State.save;
  125. end;
  126. procedure ttask_list.RestoreState;
  127. begin
  128. if not module.is_reset then
  129. state.restore;
  130. if assigned(current_scanner) and assigned(current_scanner.inputfile) then
  131. if current_scanner.inputfile.closed then
  132. begin
  133. current_scanner.tempopeninputfile;
  134. current_scanner.gettokenpos;
  135. end;
  136. end;
  137. { ttask_handler }
  138. constructor ttask_handler.create;
  139. begin
  140. list:=ttasklinkedlist.Create;
  141. hash:=TFPHashList.Create;
  142. {$IFDEF EnableCTaskPPU}
  143. tmodule.queue_module:=@addmodule;
  144. {$ENDIF}
  145. end;
  146. destructor ttask_handler.destroy;
  147. begin
  148. {$IFDEF EnableCTaskPPU}
  149. tmodule.queue_module:=nil;
  150. {$ENDIF}
  151. hash.free;
  152. hash := nil;
  153. List.Clear;
  154. FreeAndNil(list);
  155. inherited destroy;
  156. end;
  157. function ttask_handler.findtask(m: tmodule): ttask_list;
  158. begin
  159. result:=list.FirstTask;
  160. while result<>nil do
  161. begin
  162. if result.module=m then
  163. exit;
  164. result:=result.nexttask;
  165. end;
  166. {$IFDEF DEBUG_CTASK_VERBOSE}Writeln('No task found for '+m.ToString);{$ENDIF}
  167. end;
  168. function ttask_handler.cancontinue(m: tmodule; checksub : boolean; out firstwaiting: tmodule): boolean;
  169. procedure CheckUsed(out acandidate : tmodule);
  170. var
  171. itm : TLinkedListItem;
  172. iscandidate : boolean;
  173. m2 : tmodule;
  174. begin
  175. acandidate:=nil;
  176. itm:=m.used_units.First;
  177. while assigned(itm) do
  178. begin
  179. iscandidate:=Not (tused_unit(itm).u.state in [ms_processed,ms_compiled]);
  180. if iscandidate then
  181. begin
  182. acandidate:=tused_unit(itm).u;
  183. if cancontinue(acandidate,false,m2) then
  184. break;
  185. end;
  186. itm:=itm.Next;
  187. end;
  188. acandidate:=nil;
  189. end;
  190. var
  191. m2 : tmodule;
  192. begin
  193. firstwaiting:=nil;
  194. // We do not need to consider the program as long as there are units that need to be treated.
  195. if (m.is_initial and not m.is_unit) and (list.count>1) then
  196. exit(False);
  197. {$IFDEF EnableCTaskPPU}
  198. if m.do_reload then
  199. cancontinue:=tppumodule(m).canreload(firstwaiting)
  200. else
  201. {$ENDIF}
  202. begin
  203. case m.state of
  204. ms_unknown : cancontinue:=true;
  205. ms_registered : cancontinue:=true;
  206. {$IFDEF EnableCTaskPPU}
  207. ms_load: cancontinue:=tppumodule(m).ppuloadcancontinue(firstwaiting);
  208. {$ENDIF}
  209. ms_compile : cancontinue:=true;
  210. ms_compiling_wait : cancontinue:=m.usedunitsloaded(true,firstwaiting);
  211. ms_compiling_waitintf : cancontinue:=m.usedunitsloaded(true,firstwaiting);
  212. ms_compiling_waitimpl : cancontinue:=m.usedunitsloaded(false,firstwaiting);
  213. ms_compiling_waitfinish : cancontinue:=m.nowaitingforunits(firstwaiting);
  214. ms_compiled_waitcrc : cancontinue:=m.usedunitsfinalcrc(firstwaiting);
  215. ms_compiled : cancontinue:=true;
  216. ms_processed : cancontinue:=true;
  217. ms_moduleerror : cancontinue:=true;
  218. else
  219. InternalError(2024011802);
  220. end;
  221. end;
  222. // EnableCTaskPPU: remove checksub
  223. if (not cancontinue) and checksub then
  224. begin
  225. checkused(m2);
  226. if m2<>nil then
  227. firstwaiting:=m2;
  228. end;
  229. {$IFDEF DEBUG_CTASK_VERBOSE}
  230. Write('CTASK: ',m.ToString,' state: ',m.state,', can continue: ',Result);
  231. if result then
  232. Writeln
  233. else
  234. begin
  235. Write(' (First waiting: ');
  236. If Assigned(FirstWaiting) then
  237. Writeln(FirstWaiting.ToString,' )')
  238. else
  239. Writeln('<none>)');
  240. end;
  241. {$ENDIF}
  242. end;
  243. function ttask_handler.cancontinue(t : ttask_list; out firstwaiting : tmodule): boolean;
  244. begin
  245. Result:=cancontinue(t.module,true,firstwaiting);
  246. end;
  247. function ttask_handler.countwaiting(m: tmodule; out highest_state: tmodulestate; out
  248. firsthighestwaiting: tmodule): integer;
  249. var
  250. i: Integer;
  251. dep_unit: tdependent_unit;
  252. state: tmodulestate;
  253. waitfor_unit: tmodule;
  254. begin
  255. Result:=0;
  256. highest_state:=ms_registered;
  257. firsthighestwaiting:=nil;
  258. if m.is_initial and not m.is_unit then
  259. // program/library
  260. exit;
  261. if m.waitingunits<>nil then
  262. begin
  263. for i:=0 to m.waitingunits.Count-1 do
  264. begin
  265. waitfor_unit:=tmodule(m.waitingunits[i]);
  266. state:=waitfor_unit.state;
  267. if state in [ms_compiled, ms_processed] then
  268. // not waiting
  269. else if state<highest_state then
  270. // worse
  271. else if state=highest_state then
  272. // same
  273. inc(Result)
  274. else
  275. begin
  276. // better
  277. Result:=1;
  278. highest_state:=state;
  279. firsthighestwaiting:=waitfor_unit;
  280. end;
  281. end;
  282. end;
  283. if m.dependent_units<>nil then
  284. begin
  285. dep_unit:=tdependent_unit(m.dependent_units.First);
  286. while dep_unit<>nil do
  287. begin
  288. state:=dep_unit.u.state;
  289. if state in [ms_compiled, ms_processed] then
  290. // not waiting
  291. else if state<highest_state then
  292. // worse
  293. else if state=highest_state then
  294. // same
  295. inc(Result)
  296. else
  297. begin
  298. // better
  299. Result:=1;
  300. highest_state:=state;
  301. firsthighestwaiting:=dep_unit.u;
  302. end;
  303. dep_unit:=tdependent_unit(dep_unit.Next);
  304. end;
  305. end;
  306. end;
  307. function ttask_handler.continue_task(t : ttask_list) : Boolean;
  308. var
  309. m : tmodule;
  310. orgname : shortstring;
  311. begin
  312. m:=t.module;
  313. orgname:=m.modulename^;
  314. {$IFDEF DEBUG_CTASK}Writeln('CTASK: ',m.ToString,' Continues. State: ',m.state,' do_reload=',m.do_reload);{$ENDIF}
  315. if Assigned(t.state) then
  316. t.RestoreState;
  317. {$IFDEF EnableCTaskPPU}
  318. if m.do_reload then
  319. begin
  320. writeln('ttask_handler.continue ',m.modulename^,' ',m.state,' reloading...');
  321. tppumodule(m).reload;
  322. exit;
  323. end;
  324. writeln('ttask_handler.continue ',m.modulename^,' ',m.state,' continue...');
  325. {$ENDIF}
  326. case m.state of
  327. ms_registered : parser.compile_module(m);
  328. {$IFDEF EnableCTaskPPU}
  329. ms_load: (m as tppumodule).continueloadppu;
  330. {$ENDIF}
  331. ms_compile :
  332. begin
  333. if m=main then
  334. begin
  335. macrosymtablestack.clear;
  336. FreeAndNil(macrosymtablestack);
  337. end;
  338. parser.compile_module(m);
  339. end;
  340. ms_compiled : if (not m.is_initial) or m.is_unit then
  341. (m as tppumodule).post_load_or_compile(m,m.compilecount>1);
  342. ms_compiling_wait : pmodules.proc_program_declarations(m,m.islibrary);
  343. ms_compiling_waitintf : pmodules.parse_unit_interface_declarations(m);
  344. ms_compiling_waitimpl : pmodules.proc_unit_implementation(m);
  345. ms_compiling_waitfinish : pmodules.finish_compile_unit(m);
  346. ms_compiled_waitcrc : pmodules.finish_unit(m);
  347. ms_processed : ;
  348. else
  349. InternalError(2024011801);
  350. end;
  351. {$IFDEF EnableCTaskPPU}
  352. writeln('ttask_handler.continue AFTER ',m.modulename^,' ',m.state,' reload=',m.do_reload);
  353. {$ENDIF}
  354. if (m.is_initial and not m.is_unit) and (list.Count>1) then
  355. // program must wait for all units to finish
  356. else if m.state=ms_compiled then
  357. begin
  358. parsing_done(m);
  359. if m.is_initial and not m.is_unit then
  360. m.state:=ms_processed;
  361. end;
  362. Result:=m.state=ms_processed;
  363. {$IFDEF DEBUG_CTASK}
  364. Write('CTASK: ',m.ToString,' done: ',Result);
  365. if Result then
  366. Writeln
  367. else
  368. Writeln(', state is now: ',m.state);
  369. {$ENDIF}
  370. if not result then
  371. // Not done, save state
  372. t.SaveState;
  373. {
  374. the name can change as a result of processing, e.g. PROGRAM -> TB0406
  375. Normally only for the initial module, but we'll do a generic check.
  376. }
  377. if m.modulename^<>orgname then
  378. rebuild_hash;
  379. end;
  380. {$IFDEF EnableCTaskPPU}
  381. function ttask_handler.check_cycle: boolean;
  382. var
  383. last: ttask_list;
  384. function Search(m: tppumodule): boolean;
  385. var
  386. uu: tused_unit;
  387. pm: tppumodule;
  388. begin
  389. Result:=false;
  390. // mark module as searched
  391. m.cycle_search_stamp:=m.cycle_stamp;
  392. uu:=tused_unit(m.used_units);
  393. while uu<>nil do
  394. begin
  395. pm:=tppumodule(uu.u);
  396. if pm<>nil then
  397. begin
  398. if pm=last.module then
  399. Result:=true
  400. else if pm.cycle_stamp=pm.cycle_search_stamp then
  401. // already searched
  402. else
  403. Result:=Result or Search(pm);
  404. end;
  405. uu:=tused_unit(uu.Next);
  406. end;
  407. if Result then
  408. begin
  409. // cycle detected -> recompile ppu
  410. if m.state=ms_load then
  411. begin
  412. {$IFDEF DEBUG_CTASK}
  413. writeln('PPUALGO check_cycle last=',last.module.modulename^,' ',last.module.state,', RECOMPILE ',m.modulename^,' ',m.state);
  414. {$ENDIF}
  415. m.recompile_cycle;
  416. check_cycle:=true;
  417. end;
  418. end;
  419. end;
  420. var
  421. t: ttask_list;
  422. begin
  423. Result:=false;
  424. // find highest unit_index in queue
  425. t:=list.firsttask;
  426. if t=nil then exit;
  427. last:=nil;
  428. while t<>nil do
  429. begin
  430. if (last=nil) or (last.module.unit_index<t.module.unit_index) then
  431. last:=t;
  432. t:=t.nexttask;
  433. end;
  434. if tppumodule.cycle_stamp=high(dword) then
  435. tppumodule.cycle_stamp:=0
  436. else
  437. inc(tppumodule.cycle_stamp);
  438. Search(tppumodule(last.module));
  439. end;
  440. {$ENDIF}
  441. procedure ttask_handler.rebuild_hash;
  442. var
  443. t : ttask_list;
  444. begin
  445. Hash.Clear;
  446. t:=list.firsttask;
  447. While assigned(t) do
  448. begin
  449. Hash.Add(t.module.modulename^,t);
  450. t:=t.nexttask;
  451. end;
  452. end;
  453. procedure ttask_handler.processqueue;
  454. var
  455. t, besttask: ttask_list;
  456. firstwaiting, bestmod, m, firsthighestwaiting: tmodule;
  457. begin
  458. // Strategy: goal is to write ppus early, so that mem is freed early and in case of an error
  459. // next compile can load ppus instead of compiling again.
  460. repeat
  461. {$IFDEF DEBUG_CTASK}writeln('CTASK: ttask_handler.processqueue: task-count=',list.Count);{$ENDIF}
  462. besttask:=nil;
  463. if list.firsttask=nil then
  464. exit; // completed
  465. // search for any module, that can continue, with furthest state
  466. t:=list.firsttask;
  467. while t<>nil do
  468. begin
  469. m:=t.module;
  470. if (besttask<>nil) and (besttask.module.unit_index>m.unit_index) then
  471. // skip
  472. else if cancontinue(m,false,firstwaiting) then
  473. begin
  474. {$IFDEF DEBUG_CTASK}
  475. Writeln('CTASK: ',m.ToString,' state=',m.state,' unit_index=',m.unit_index);
  476. {$ENDIF}
  477. // prefer highest unit_index to complete strongly connected components first
  478. if (besttask=nil)
  479. or (besttask.module.unit_index<m.unit_index) then
  480. besttask:=t;
  481. end;
  482. t:=t.nexttask;
  483. end;
  484. {$IFDEF EnableCTaskPPU}
  485. if besttask=nil then
  486. if check_cycle then continue;
  487. {$ENDIF}
  488. if besttask=nil then
  489. begin
  490. // no progress possible
  491. write_queue;
  492. InternalError(2026012015);
  493. end;
  494. {$IF defined(DEBUG_CTASK) or defined(Debug_FreeParseMem)}Writeln('CTASK: continuing ',besttask.module.ToString,' state=',besttask.module.statestr,' total-units=',loaded_units.Count,' tasks=',list.Count);{$ENDIF}
  495. if continue_task(besttask) then
  496. begin
  497. {$IFDEF DEBUG_CTASK}Writeln('CTASK: ',besttask.module.ToString,' is finished, removing from task list');{$ENDIF}
  498. hash.Remove(besttask.module);
  499. list.Remove(besttask);
  500. FreeAndNil(besttask);
  501. end;
  502. until false;
  503. end;
  504. procedure ttask_handler.addmodule(m: tmodule);
  505. var
  506. n : TSymStr;
  507. e, t : ttask_list;
  508. begin
  509. n:=m.modulename^;
  510. e:=ttask_list(Hash.Find(n));
  511. if e=nil then
  512. begin
  513. {$IFDEF DEBUG_CTASK}Writeln('CTASK: ',m.ToString,' added to task scheduler. State: ',m.state,' unit_index=',m.unit_index);{$ENDIF}
  514. // Clear reset flag.
  515. // This can happen when during load, reset is done and unit is added to task list.
  516. m.is_reset:=false;
  517. t:=ttask_list.create(m);
  518. list.insert(t);
  519. hash.Add(n,t);
  520. if list.count=1 then
  521. main:=m;
  522. end
  523. else
  524. begin
  525. // We have a task, if it was reset, then clear the state and move the task to the start.
  526. if m.is_reset then
  527. begin
  528. {$IFDEF DEBUG_CTASK}Writeln('CTASK: ',m.ToString,' was reset, resetting flag. State: ',m.state);{$ENDIF}
  529. m.is_reset:=false;
  530. t:=findtask(m);
  531. if assigned(t) then
  532. begin
  533. t.DiscardState;
  534. list.Remove(t);
  535. list.insertbefore(t,list.First);
  536. end;
  537. end;
  538. end;
  539. end;
  540. procedure ttask_handler.write_queue;
  541. var
  542. t: ttask_list;
  543. firstwaiting, m: tmodule;
  544. cc: Boolean;
  545. begin
  546. writeln('ttask_handler.write_queue:');
  547. t:=list.firsttask;
  548. while t<>nil do
  549. begin
  550. cc:=cancontinue(t,firstwaiting);
  551. m:=t.module;
  552. if firstwaiting<>nil then
  553. writeln('queue: ',m.realmodulename^,' ',m.statestr,' cancontinue=',cc,' firstwaiting=',firstwaiting.realmodulename^,' ',firstwaiting.state)
  554. else
  555. writeln('queue: ',m.realmodulename^,' ',m.statestr,' cancontinue=',cc,' firstwaiting=nil');
  556. t:=t.nexttask;
  557. end;
  558. end;
  559. end.