GCC Code Coverage Report


src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
356 / 359
99.2%
Branches:
96 / 100, 152 excl
96.0%
Functions:
35 / 36
97.2%

util/compile-progress.spice
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 // Std imports
4 import "std/io/progress-bar";
5 import "std/os/mutex";
6 import "std/os/thread";
7 import "std/data/vector";
8 import "std/time/delay";
9
10 // Own imports
11 import "@/driver";
12
13 // Link external functions
14 ext f<int> fprintf(byte* /*stream*/, string /*format*/, ...);
15
16 // Labels of the compile phases. They are padded to the same width, so that the bar does not move when the phase changes
17 public const string COMPILE_PHASE_FRONT_END = "Parsing ";
18 public const string COMPILE_PHASE_MIDDLE_END = "Type checking ";
19 public const string COMPILE_PHASE_BACK_END = "Generating code";
20 public const string COMPILE_PHASE_LINKING = "Linking ";
21
22 // Progress steps per source file and stage, weighted by the time the stage takes for an average source file (one step is
23 // roughly 2 ms). The back end steps are measured for a serial back end and divided by the speedup of the parallel back end.
24 const unsigned long STEPS_FRONT_END = 2ul;
25 const unsigned long STEPS_TYPE_CHECKER_PRE = 2ul;
26 const unsigned long STEPS_TYPE_CHECKER_POST = 10ul;
27 const unsigned long STEPS_IR_GENERATOR = 5ul;
28 const unsigned long STEPS_IR_OPTIMIZER = 21ul; // Also the pre-link optimizer with LTO
29 const unsigned long STEPS_OBJECT_EMITTER_O0 = 5ul;
30 const unsigned long STEPS_OBJECT_EMITTER_OPTIMIZED = 19ul;
31 const unsigned long STEPS_LTO_IR_OPTIMIZER = 30ul; // Bitcode linker and post-link optimizer for the whole program
32 const unsigned long STEPS_LTO_OBJECT_EMITTER = 30ul; // Object emitter for the whole program
33 const unsigned long STEPS_LINKING = 2ul;
34 // The parallel back end does not scale with the number of jobs, because a few source files (e.g. with many generic
35 // manifestations) take much longer than the others. This is about the speedup, it reaches with many jobs.
36 const unsigned long MAX_BACK_END_SPEEDUP = 6ul;
37
38 // Interval, in which the heartbeat thread refreshes the bar while a long-running stage reports no progress
39 const int HEARTBEAT_INTERVAL_MILLIS = 50;
40
41 /**
42 * Back end stages, that report their progress
43 */
44 public type BackEndStage enum {
45 IR_GENERATOR,
46 IR_OPTIMIZER, // The default optimizer, or the pre-link optimizer with LTO
47 LTO_IR_OPTIMIZER, // Bitcode linker and post-link optimizer for the whole program
48 OBJECT_EMITTER,
49 LTO_OBJECT_EMITTER // Object emitter for the whole program
50 }
51
52 /**
53 * Shows the progress of a compilation as progress bar with ETA on the console. It is disabled by default and only enabled
54 * for an actual compilation, which prints nothing else to the console while it runs (see compileProject).
55 *
56 * The progress is counted in steps, which the source files report when they finish a stage. Every source file adds the
57 * steps of all its stages to the total when it is created, so the total grows while the front end discovers the imports.
58 * The source files report from the worker threads of the parallel back end as well, which is why all accesses are locked.
59 *
60 * Some stages run only once for the whole program (the LTO stages and linking) and report no progress, until they are
61 * done. In the meantime, a heartbeat thread keeps the elapsed time and the ETA on the console up to date.
62 */
63 ✗ public type CompileProgress struct {
64 ProgressBar bar
65 Mutex mutex
66 1064 bool enabled = false
67 // Steps per source file
68 1064 unsigned long stepsIrGenerator = 0ul
69 1064 unsigned long stepsIrOptimizer = 0ul
70 1064 unsigned long stepsObjectEmitter = 0ul
71 1064 unsigned long stepsLtoIrOptimizer = 0ul
72 1064 unsigned long stepsLtoObjectEmitter = 0ul
73 1064 unsigned long stepsLinking = 0ul
74 1064 unsigned long sourceFileCount = 0ul
75 1064 bool heartbeatRunning = false
76 Vector<Thread> heartbeatThreads // Holds the heartbeat thread while it runs
77 }
78
79 // Active compile progress. Compile errors abort the compilation from places that have no access to the resource manager
80 // (e.g. the lexer and the parser), and they have to remove the bar from the console first.
81 CompileProgress* ACTIVE_COMPILE_PROGRESS;
82
83 1064 public p CompileProgress.ctor() {
84 1064 this.bar = ProgressBar(0ul, COMPILE_PHASE_FRONT_END);
85 1064 this.bar.setShowCounter(false); // The steps are no meaningful unit for the user
86 1064 }
87
88 775 public p CompileProgress.dtor() {
89 775 this.stopHeartbeat();
90 775 this.clear();
91
2/2
✓ Branch 0 taken 767 times.
✓ Branch 1 taken 8 times.
775 if ACTIVE_COMPILE_PROGRESS == this {
92 8 ACTIVE_COMPILE_PROGRESS = nil<CompileProgress*>;
93 8 }
94 775 }
95
96 /**
97 * Enable the progress bar and draw it for the first time
98 *
99 * @param runBackEnd Whether the back end runs
100 * @param link Whether the object files are linked
101 * @param optimized Whether the opt level is above O0
102 * @param useLTO Whether LTO is enabled
103 * @param jobCount Number of source files, that the back end compiles in parallel
104 */
105 8 public p CompileProgress.enable(bool runBackEnd, bool link, bool optimized, bool useLTO, unsigned long jobCount) {
106
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if runBackEnd {
107 // Divide the serial steps by the speedup of the parallel back end. LTO always runs serially
108
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 unsigned long speedup = useLTO ? 1ul : jobCount;
109
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
6 if speedup > MAX_BACK_END_SPEEDUP { speedup = MAX_BACK_END_SPEEDUP; }
110
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
6 if speedup == 0ul { speedup = 1ul; }
111 6 this.stepsIrGenerator = divideRoundingUp(STEPS_IR_GENERATOR, speedup);
112
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
6 this.stepsIrOptimizer = optimized ? divideRoundingUp(STEPS_IR_OPTIMIZER, speedup) : 0ul;
113
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if useLTO {
114 2 this.stepsLtoIrOptimizer = STEPS_LTO_IR_OPTIMIZER;
115 2 this.stepsLtoObjectEmitter = STEPS_LTO_OBJECT_EMITTER;
116 2 } else {
117
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 const unsigned long serialStepsObjectEmitter = optimized ? STEPS_OBJECT_EMITTER_OPTIMIZED : STEPS_OBJECT_EMITTER_O0;
118 4 this.stepsObjectEmitter = divideRoundingUp(serialStepsObjectEmitter, speedup);
119 4 }
120 6 }
121
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
8 if link {
122 3 this.stepsLinking = STEPS_LINKING;
123 3 }
124
125 8 this.enabled = true;
126 8 ACTIVE_COMPILE_PROGRESS = this;
127 // The bar is drawn for the first time with the first source file, because a bar without any steps counts as complete
128 8 }
129
130 /**
131 * Account for a newly created source file
132 */
133 3978 public p CompileProgress.addSourceFile() {
134
2/2
✓ Branch 0 taken 3972 times.
✓ Branch 1 taken 6 times.
3978 if !this.enabled { return; }
135 6 LockGuard _ = LockGuard(this.mutex);
136 6 unsigned long stepsPerFile = STEPS_FRONT_END + STEPS_TYPE_CHECKER_PRE + STEPS_TYPE_CHECKER_POST + this.stepsLinking;
137 6 stepsPerFile += this.stepsIrGenerator + this.stepsIrOptimizer + this.stepsObjectEmitter;
138 6 stepsPerFile += this.stepsLtoIrOptimizer + this.stepsLtoObjectEmitter;
139 6 this.sourceFileCount++;
140 6 this.bar.setTotal(this.bar.getTotal() + stepsPerFile);
141
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if this.sourceFileCount == 1ul {
142 3 this.bar.start();
143 3 }
144 6 }
145
146 /**
147 * Report that a source file finished its front end
148 */
149 3928 public p CompileProgress.finishFrontEnd() {
150 3928 this.advance(STEPS_FRONT_END);
151 3928 }
152
153 /**
154 * Report that a source file finished the pre-run of the type checker
155 */
156 3895 public p CompileProgress.finishTypeCheckerPre() {
157 3895 this.advance(STEPS_TYPE_CHECKER_PRE);
158 3895 }
159
160 /**
161 * Report that a source file finished the post-run of the type checker
162 */
163 3540 public p CompileProgress.finishTypeCheckerPost() {
164 3540 this.advance(STEPS_TYPE_CHECKER_POST);
165 3540 }
166
167 /**
168 * Report that a source file enters a back end stage. Stages for the whole program get a heartbeat until they finish.
169 *
170 * @param stage Back end stage
171 */
172 10509 public p CompileProgress.startBackEndStage(BackEndStage stage) {
173
2/2
✓ Branch 0 taken 10498 times.
✓ Branch 1 taken 11 times.
10509 if !this.enabled { return; }
174
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
11 if isWholeProgramStage(stage) {
175 2 this.startHeartbeat();
176 2 }
177 11 }
178
179 /**
180 * Report that a source file finished a back end stage
181 *
182 * @param stage Back end stage
183 */
184 10513 public p CompileProgress.finishBackEndStage(BackEndStage stage) {
185
2/2
✓ Branch 0 taken 10498 times.
✓ Branch 1 taken 15 times.
10513 if !this.enabled { return; }
186
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2 times.
15 if isWholeProgramStage(stage) {
187 2 this.stopHeartbeat();
188 2 }
189 15 LockGuard _ = LockGuard(this.mutex);
190 15 this.bar.tick(this.getBackEndStageSteps(stage));
191 15 }
192
193 /**
194 * Switch to the linking phase and keep the bar up to date on a heartbeat thread, until finishLinking is called
195 */
196 464 public p CompileProgress.startLinking() {
197
2/2
✓ Branch 0 taken 463 times.
✓ Branch 1 taken 1 time.
464 if !this.enabled { return; }
198 1 this.setPhase(COMPILE_PHASE_LINKING);
199 1 this.startHeartbeat();
200 1 }
201
202 /**
203 * Stop the heartbeat thread and complete the steps of the linking
204 */
205 464 public p CompileProgress.finishLinking() {
206 464 this.stopHeartbeat();
207 464 this.advance(this.stepsLinking * this.sourceFileCount);
208 464 }
209
210 /**
211 * Switch to the next compile phase
212 *
213 * @param phase Label of the phase (one of the COMPILE_PHASE_* constants)
214 */
215 1770 public p CompileProgress.setPhase(string phase) {
216
2/2
✓ Branch 0 taken 1769 times.
✓ Branch 1 taken 1 time.
1770 if !this.enabled { return; }
217 1 LockGuard _ = LockGuard(this.mutex);
218 1 this.bar.setLabel(phase);
219 1 this.bar.redraw();
220 1 }
221
222 /**
223 * Remove the bar from the console for good, e.g. to print the warnings or because the compilation ends
224 */
225 1539 public p CompileProgress.clear() {
226
2/2
✓ Branch 0 taken 1531 times.
✓ Branch 1 taken 8 times.
1539 if !this.enabled { return; }
227 8 LockGuard _ = LockGuard(this.mutex);
228 8 this.bar.clear();
229 8 this.enabled = false;
230 8 }
231
232 /**
233 * Print output to stderr without removing the bar for good. The bar is cleared first and drawn again with the next update
234 *
235 * @param output Output to print
236 */
237 1 public p CompileProgress.printAboveBar(const String& output) {
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if !this.enabled { return; }
239 1 LockGuard _ = LockGuard(this.mutex);
240 1 this.bar.clear();
241 1 printToStdErr(output);
242 1 }
243
244 /**
245 * Advance the progress by the given number of steps
246 *
247 * @param steps Number of steps
248 */
249 11827 p CompileProgress.advance(unsigned long steps) {
250
2/2
✓ Branch 0 taken 11811 times.
✓ Branch 1 taken 16 times.
11827 if !this.enabled { return; }
251 16 LockGuard _ = LockGuard(this.mutex);
252 16 this.bar.tick(steps);
253 16 }
254
255 /**
256 * Retrieve the number of steps, a source file completes with the given back end stage
257 *
258 * @param stage Back end stage
259 * @return Number of steps
260 */
261 15 f<unsigned long> CompileProgress.getBackEndStageSteps(BackEndStage stage) {
262
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
15 if stage == BackEndStage::IR_GENERATOR { return this.stepsIrGenerator; }
263
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 if stage == BackEndStage::IR_OPTIMIZER { return this.stepsIrOptimizer; }
264
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if stage == BackEndStage::OBJECT_EMITTER { return this.stepsObjectEmitter; }
265 // The stages for the whole program complete the steps of all source files at once
266
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if stage == BackEndStage::LTO_IR_OPTIMIZER { return this.stepsLtoIrOptimizer * this.sourceFileCount; }
267 1 return this.stepsLtoObjectEmitter * this.sourceFileCount;
268 }
269
270 /**
271 * Start a heartbeat thread, that keeps the bar up to date while a stage reports no progress
272 */
273 3 p CompileProgress.startHeartbeat() {
274 3 this.mutex.acquire();
275 3 this.heartbeatRunning = true;
276 3 this.mutex.release();
277 3 CompileProgress* progress = this;
278 6 this.heartbeatThreads.pushBack(Thread(p() [[async]] {
279 3 progress.runHeartbeat();
280 3 }));
281 3 Thread& heartbeatThread = this.heartbeatThreads.back();
282 3 heartbeatThread.run();
283 3 }
284
285 /**
286 * Routine of the heartbeat thread. Refreshes the bar, until the heartbeat is stopped or the bar is removed.
287 */
288 3 p CompileProgress.runHeartbeat() {
289 3 bool running = true;
290
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 while running {
291 3 delay(HEARTBEAT_INTERVAL_MILLIS);
292 3 this.mutex.acquire();
293
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 running = this.enabled && this.heartbeatRunning;
294
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if running {
295 ✗ this.bar.update(this.bar.getCurrent()); // Throttled by the bar itself
296 ✗ }
297 3 this.mutex.release();
298 3 }
299 3 }
300
301 /**
302 * Stop the heartbeat thread, if it runs. The caller must not hold the mutex.
303 */
304 1241 p CompileProgress.stopHeartbeat() {
305
2/2
✓ Branch 0 taken 1238 times.
✓ Branch 1 taken 3 times.
1241 if this.heartbeatThreads.isEmpty() { return; }
306 3 this.mutex.acquire();
307 3 this.heartbeatRunning = false;
308 3 this.mutex.release();
309
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 foreach Thread& thread : this.heartbeatThreads {
310 3 thread.join();
311 3 }
312 3 this.heartbeatThreads.clear();
313 3 }
314
315 /**
316 * Check whether the given back end stage runs once for the whole program
317 *
318 * @param stage Back end stage
319 * @return Whole program stage or not
320 */
321 31 f<bool> isWholeProgramStage(BackEndStage stage) {
322
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 28 times.
31 return stage == BackEndStage::LTO_IR_OPTIMIZER || stage == BackEndStage::LTO_OBJECT_EMITTER;
323 }
324
325 /**
326 * Print the given output to stderr
327 *
328 * @param output Output to print
329 */
330 1 p printToStdErr(const String& output) {
331 1 const int _ = fprintf(__stderr(), "%s", output.getRaw());
332 1 }
333
334 /**
335 * Divide two numbers, rounding up. Keeps a stage, that takes any time, from ending up with zero steps
336 *
337 * @param dividend Dividend
338 * @param divisor Divisor
339 * @return Quotient, rounded up
340 */
341 20 f<unsigned long> divideRoundingUp(unsigned long dividend, unsigned long divisor) {
342 20 return (dividend + divisor - 1ul) / divisor;
343 }
344
345 /**
346 * Check whether the compilation progress should be shown as progress bar. This is only the case if stdout is a terminal
347 * and the compiler prints nothing else to it while compiling, i.e. neither debug output nor dumps to the console. The AST
348 * and dependency graph dumps print their SVG generation status even when dumping to files, so they always hide the bar. The
349 * --quiet option suppresses it explicitly.
350 *
351 * @param cliOptions Command line options
352 * @return Show progress or not
353 */
354 1058 public f<bool> shouldShowCompileProgress(const CliOptions& cliOptions) {
355
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1057 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1056 times.
1058 if cliOptions.quiet || cliOptions.printDebugOutput { return false; }
356 1056 const DumpSettings& dump = cliOptions.dump;
357
13/14
✓ Branch 0 taken 1048 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1040 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 1038 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 1037 times.
✓ Branch 7 taken 1 time.
✓ Branch 8 taken 1034 times.
✓ Branch 9 taken 3 times.
✓ Branch 10 taken 799 times.
✓ Branch 11 taken 235 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 799 times.
1056 const bool anyDump = dump.dumpAST || dump.dumpSymbolTable || dump.dumpTypes || dump.dumpCacheStats || dump.dumpDependencyGraph || dump.dumpIR || dump.dumpAssembly || dump.dumpObjectFiles;
358
4/4
✓ Branch 0 taken 799 times.
✓ Branch 1 taken 257 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 1046 times.
1056 if anyDump && !dump.dumpToFiles { return false; }
359
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1045 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1044 times.
1046 if dump.dumpAST || dump.dumpDependencyGraph { return false; }
360 1044 return isStdoutTerminal();
361 }
362
363 /**
364 * Check whether the progress bar of the active compilation is currently shown. If so, nothing else may write to the
365 * console, e.g. external tools have to get their output captured.
366 *
367 * @return Shown or not
368 */
369 468 public f<bool> isCompileProgressActive() {
370
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 465 times.
468 if ACTIVE_COMPILE_PROGRESS == nil<CompileProgress*> { return false; }
371 3 return ACTIVE_COMPILE_PROGRESS.enabled;
372 }
373
374 /**
375 * Print output to stderr above the progress bar of the active compilation, e.g. the warnings of an external tool, whose
376 * output was captured, because the bar is shown
377 *
378 * @param output Output to print
379 */
380 2 public p printAboveActiveCompileProgress(const String& output) {
381 2 CompileProgress* compileProgress = ACTIVE_COMPILE_PROGRESS;
382
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if compileProgress == nil<CompileProgress*> { return; }
383 1 compileProgress.printAboveBar(output);
384 1 }
385
386 /**
387 * Remove the progress bar of the active compilation from the console, if there is one. Called before a compile error aborts
388 * the compilation, so that the error message does not end up behind the bar.
389 */
390 300 public p clearActiveCompileProgress() {
391 300 CompileProgress* compileProgress = ACTIVE_COMPILE_PROGRESS;
392
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 299 times.
300 if compileProgress == nil<CompileProgress*> { return; }
393 1 compileProgress.clear();
394 1 }
395
396 // ─── tests ────────────────────────────────────────────────────────────────────
397
398 1 #[test]
399 f<bool> testDivideRoundingUp() {
400 1 assert divideRoundingUp(0ul, 3ul) == 0ul;
401 1 assert divideRoundingUp(5ul, 1ul) == 5ul;
402 1 assert divideRoundingUp(5ul, 6ul) == 1ul; // A stage, that takes any time, never ends up with zero steps
403 1 assert divideRoundingUp(6ul, 6ul) == 1ul;
404 1 assert divideRoundingUp(21ul, 6ul) == 4ul;
405 1 return true;
406 }
407
408 1 #[test]
409 f<bool> testIsWholeProgramStage() {
410 1 assert !isWholeProgramStage(BackEndStage::IR_GENERATOR);
411 1 assert !isWholeProgramStage(BackEndStage::IR_OPTIMIZER);
412 1 assert !isWholeProgramStage(BackEndStage::OBJECT_EMITTER);
413 1 assert isWholeProgramStage(BackEndStage::LTO_IR_OPTIMIZER);
414 1 assert isWholeProgramStage(BackEndStage::LTO_OBJECT_EMITTER);
415 1 return true;
416 }
417
418 1 #[test]
419 f<bool> testEnableComputesStepsPerSourceFile() {
420 { // Neither back end nor linking
421 1 CompileProgress progress;
422 1 progress.enable(false, false, true, false, 4ul);
423 1 assert progress.enabled;
424 1 assert progress.stepsIrGenerator == 0ul;
425 1 assert progress.stepsIrOptimizer == 0ul;
426 1 assert progress.stepsObjectEmitter == 0ul;
427 1 assert progress.stepsLtoIrOptimizer == 0ul;
428 1 assert progress.stepsLtoObjectEmitter == 0ul;
429 1 assert progress.stepsLinking == 0ul;
430 1 }
431 { // O0 skips the optimizer, the parallel back end divides the serial steps by the job count
432 1 CompileProgress progress;
433 1 progress.enable(true, true, false, false, 4ul);
434 1 assert progress.stepsIrGenerator == 2ul;
435 1 assert progress.stepsIrOptimizer == 0ul;
436 1 assert progress.stepsObjectEmitter == 2ul;
437 1 assert progress.stepsLtoIrOptimizer == 0ul;
438 1 assert progress.stepsLtoObjectEmitter == 0ul;
439 1 assert progress.stepsLinking == STEPS_LINKING;
440 1 }
441 { // The speedup is capped for many jobs
442 1 CompileProgress progress;
443 1 progress.enable(true, false, true, false, 32ul);
444 1 assert progress.stepsIrGenerator == 1ul;
445 1 assert progress.stepsIrOptimizer == 4ul;
446 1 assert progress.stepsObjectEmitter == 4ul;
447 1 assert progress.stepsLinking == 0ul;
448 1 }
449 { // No jobs count as one job
450 1 CompileProgress progress;
451 1 progress.enable(true, false, true, false, 0ul);
452 1 assert progress.stepsIrGenerator == STEPS_IR_GENERATOR;
453 1 assert progress.stepsIrOptimizer == STEPS_IR_OPTIMIZER;
454 1 assert progress.stepsObjectEmitter == STEPS_OBJECT_EMITTER_OPTIMIZED;
455 1 }
456 { // LTO runs serially and replaces the object emitter per source file with the stages for the whole program
457 1 CompileProgress progress;
458 1 progress.enable(true, true, true, true, 8ul);
459 1 assert progress.stepsIrGenerator == STEPS_IR_GENERATOR;
460 1 assert progress.stepsIrOptimizer == STEPS_IR_OPTIMIZER;
461 1 assert progress.stepsObjectEmitter == 0ul;
462 1 assert progress.stepsLtoIrOptimizer == STEPS_LTO_IR_OPTIMIZER;
463 1 assert progress.stepsLtoObjectEmitter == STEPS_LTO_OBJECT_EMITTER;
464 1 assert progress.stepsLinking == STEPS_LINKING;
465 1 }
466 1 return true;
467 }
468
469 1 #[test]
470 f<bool> testDisabledProgressIgnoresReports() {
471 1 CompileProgress progress;
472 1 assert !progress.enabled;
473 1 progress.addSourceFile();
474 1 progress.finishFrontEnd();
475 1 progress.startBackEndStage(BackEndStage::LTO_IR_OPTIMIZER);
476 1 progress.finishBackEndStage(BackEndStage::LTO_IR_OPTIMIZER);
477 1 progress.startLinking();
478 1 progress.finishLinking();
479 1 assert progress.sourceFileCount == 0ul;
480 1 assert progress.bar.getTotal() == 0ul;
481 1 assert progress.bar.getCurrent() == 0ul;
482 1 assert progress.heartbeatThreads.isEmpty();
483 1 assert !isCompileProgressActive();
484 1 return true;
485 1 }
486
487 1 #[test]
488 f<bool> testProgressCompletesWithoutLTO() {
489 1 CompileProgress progress;
490 1 progress.enable(true, true, true, false, 1ul);
491 // Every source file adds the steps of all its stages to the total
492 1 const unsigned long stepsPerFile = 2ul + 2ul + 10ul + 5ul + 21ul + 19ul + 2ul;
493
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 for unsigned long i = 0ul; i < 3ul; i++ {
494 3 progress.addSourceFile();
495 3 }
496 1 assert progress.sourceFileCount == 3ul;
497 1 assert progress.bar.getTotal() == 3ul * stepsPerFile;
498 1 assert progress.bar.getCurrent() == 0ul;
499
500
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 for unsigned long i = 0ul; i < 3ul; i++ {
501 3 progress.finishFrontEnd();
502 3 progress.finishTypeCheckerPre();
503 3 progress.finishTypeCheckerPost();
504 3 }
505 1 assert progress.bar.getCurrent() == 3ul * 14ul;
506
507
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 time.
4 for unsigned long i = 0ul; i < 3ul; i++ {
508 3 progress.startBackEndStage(BackEndStage::IR_GENERATOR);
509 3 progress.finishBackEndStage(BackEndStage::IR_GENERATOR);
510 3 progress.startBackEndStage(BackEndStage::IR_OPTIMIZER);
511 3 progress.finishBackEndStage(BackEndStage::IR_OPTIMIZER);
512 3 progress.startBackEndStage(BackEndStage::OBJECT_EMITTER);
513 3 progress.finishBackEndStage(BackEndStage::OBJECT_EMITTER);
514 3 assert progress.heartbeatThreads.isEmpty(); // Stages per source file need no heartbeat
515 3 }
516 1 assert progress.bar.getCurrent() == 3ul * (stepsPerFile - STEPS_LINKING);
517
518 // Linking runs on a heartbeat and completes the steps of all source files
519 1 progress.startLinking();
520 1 const String line = progress.bar.format(0l);
521 1 assert line.startsWith(COMPILE_PHASE_LINKING);
522 1 assert progress.heartbeatThreads.getSize() == 1ul;
523 1 progress.finishLinking();
524 1 assert progress.heartbeatThreads.isEmpty();
525 1 assert !progress.heartbeatRunning;
526 1 assert progress.bar.getCurrent() == progress.bar.getTotal();
527 1 return true;
528 1 }
529
530 1 #[test]
531 f<bool> testProgressCompletesWithLTO() {
532 1 CompileProgress progress;
533 1 progress.enable(true, false, true, true, 4ul);
534 1 progress.addSourceFile();
535 1 progress.addSourceFile();
536 1 const unsigned long stepsPerFile = 2ul + 2ul + 10ul + 5ul + 21ul + 30ul + 30ul;
537 1 assert progress.bar.getTotal() == 2ul * stepsPerFile;
538
539 // Front end, type checker and the stages per source file
540
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 for unsigned long i = 0ul; i < 2ul; i++ {
541 2 progress.finishFrontEnd();
542 2 progress.finishTypeCheckerPre();
543 2 progress.finishTypeCheckerPost();
544 2 progress.finishBackEndStage(BackEndStage::IR_GENERATOR);
545 2 progress.finishBackEndStage(BackEndStage::IR_OPTIMIZER);
546 2 }
547 1 assert progress.bar.getCurrent() == 2ul * (14ul + 5ul + 21ul);
548
549 // The stages for the whole program run on a heartbeat and complete the steps of all source files at once
550 1 progress.startBackEndStage(BackEndStage::LTO_IR_OPTIMIZER);
551 1 assert progress.heartbeatRunning;
552 1 assert progress.heartbeatThreads.getSize() == 1ul;
553 1 progress.finishBackEndStage(BackEndStage::LTO_IR_OPTIMIZER);
554 1 assert !progress.heartbeatRunning;
555 1 assert progress.heartbeatThreads.isEmpty();
556 1 assert progress.bar.getCurrent() == 2ul * (14ul + 5ul + 21ul + 30ul);
557
558 1 progress.startBackEndStage(BackEndStage::LTO_OBJECT_EMITTER);
559 1 progress.finishBackEndStage(BackEndStage::LTO_OBJECT_EMITTER);
560 1 assert progress.heartbeatThreads.isEmpty();
561 1 assert progress.bar.getCurrent() == progress.bar.getTotal();
562 1 return true;
563 1 }
564
565 1 #[test]
566 f<bool> testActiveCompileProgress() {
567 {
568 1 CompileProgress progress;
569 1 assert !isCompileProgressActive(); // Not registered before it is enabled
570 1 progress.enable(false, false, false, false, 1ul);
571 1 progress.addSourceFile();
572 1 assert isCompileProgressActive();
573 1 printAboveActiveCompileProgress(String()); // Keeps the bar enabled
574 1 assert isCompileProgressActive();
575
576 // Clearing the active progress removes the bar for good
577 1 clearActiveCompileProgress();
578 1 assert !progress.enabled;
579 1 assert !isCompileProgressActive();
580 1 progress.finishFrontEnd();
581 1 assert progress.bar.getCurrent() == 0ul;
582 1 }
583 // The destructor unregisters the progress
584 1 assert ACTIVE_COMPILE_PROGRESS == nil<CompileProgress*>;
585 1 assert !isCompileProgressActive();
586 1 clearActiveCompileProgress(); // No-op without an active progress
587 1 printAboveActiveCompileProgress(String());
588 1 return true;
589 1 }
590
591 1 #[test]
592 f<bool> testShouldShowCompileProgressRespectsOptions() {
593 { // Quiet
594 1 CliOptions cliOptions;
595 1 cliOptions.quiet = true;
596 1 assert !shouldShowCompileProgress(cliOptions);
597 1 }
598 { // Debug output
599 1 CliOptions cliOptions;
600 1 cliOptions.printDebugOutput = true;
601 1 assert !shouldShowCompileProgress(cliOptions);
602 1 }
603 { // Dump to the console
604 1 CliOptions cliOptions;
605 1 cliOptions.dump.dumpIR = true;
606 1 assert !shouldShowCompileProgress(cliOptions);
607 1 }
608 { // The AST and dependency graph dumps print their SVG generation status even when dumping to files
609 1 CliOptions cliOptions;
610 1 cliOptions.dump.dumpAST = true;
611 1 cliOptions.dump.dumpToFiles = true;
612 1 assert !shouldShowCompileProgress(cliOptions);
613 1 cliOptions.dump.dumpAST = false;
614 1 cliOptions.dump.dumpDependencyGraph = true;
615 1 assert !shouldShowCompileProgress(cliOptions);
616 1 }
617 { // Other dumps to files depend on the terminal only
618 1 CliOptions cliOptions;
619 1 cliOptions.dump.dumpIR = true;
620 1 cliOptions.dump.dumpToFiles = true;
621 1 assert shouldShowCompileProgress(cliOptions) == isStdoutTerminal();
622 1 }
623 1 return true;
624 }
625