GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 86.8% 532 / 3 / 616
Functions: 91.7% 44 / 0 / 48
Branches: 49.1% 557 / 12 / 1146

src/SourceFile.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "SourceFile.h"
4
5 #include <algorithm>
6 #include <queue>
7 #include <unordered_set>
8
9 #include <ast/ASTBuilder.h>
10 #include <driver/Driver.h>
11 #include <exception/AntlrThrowingErrorListener.h>
12 #include <exception/CompilerError.h>
13 #include <global/GlobalResourceManager.h>
14 #include <global/TypeRegistry.h>
15 #include <importcollector/ImportCollector.h>
16 #include <irgenerator/IRGenerator.h>
17 #include <iroptimizer/IROptimizer.h>
18 #include <linker/BitcodeLinker.h>
19 #include <objectemitter/LLVMObjectEmitter.h>
20 #ifdef SPICE_ENABLE_TPDE
21 #include <objectemitter/TPDEObjectEmitter.h>
22 #endif
23 #include <symboltablebuilder/SymbolTable.h>
24 #include <symboltablebuilder/SymbolTableBuilder.h>
25 #include <typechecker/FunctionManager.h>
26 #include <typechecker/InterfaceManager.h>
27 #include <typechecker/MacroDefs.h>
28 #include <typechecker/PostTypeCheckingVerifier.h>
29 #include <typechecker/StructManager.h>
30 #include <typechecker/TypeChecker.h>
31 #include <util/CompilerWarning.h>
32 #include <util/Concurrency.h>
33 #include <util/FileUtil.h>
34 #include <util/SystemUtil.h>
35 #include <util/ThreadPool.h>
36 #include <util/Timer.h>
37 #include <visualizer/ASTVisualizer.h>
38 #include <visualizer/CSTVisualizer.h>
39 #include <visualizer/DependencyGraphVisualizer.h>
40
41 #include <llvm/IR/Module.h>
42 #include <llvm/MC/TargetRegistry.h>
43
44 namespace spice::compiler {
45
46 2476 SourceFile::SourceFile(GlobalResourceManager &resourceManager, SourceFile *parent, std::string name,
47 const std::filesystem::path &filePath, bool stdFile)
48
1/2
✓ Branch 6 → 7 taken 2476 times.
✗ Branch 6 → 132 not taken.
7428 : name(std::move(name)), filePath(filePath), isStdFile(stdFile), parent(parent),
49
3/4
✓ Branch 18 → 19 taken 2 times.
✓ Branch 18 → 20 taken 2474 times.
✓ Branch 21 → 22 taken 2476 times.
✗ Branch 21 → 64 not taken.
2476 builder(resourceManager.cliOptions.useLTO ? resourceManager.ltoContext : context), resourceManager(resourceManager),
50
1/2
✓ Branch 16 → 17 taken 2476 times.
✗ Branch 16 → 112 not taken.
7428 cliOptions(resourceManager.cliOptions) {
51 // Deduce fileName and fileDir
52
3/6
✓ Branch 29 → 30 taken 2476 times.
✗ Branch 29 → 69 not taken.
✓ Branch 30 → 31 taken 2476 times.
✗ Branch 30 → 67 not taken.
✓ Branch 31 → 32 taken 2476 times.
✗ Branch 31 → 65 not taken.
2476 fileName = std::filesystem::path(filePath).filename().string();
53
3/6
✓ Branch 36 → 37 taken 2476 times.
✗ Branch 36 → 76 not taken.
✓ Branch 37 → 38 taken 2476 times.
✗ Branch 37 → 74 not taken.
✓ Branch 38 → 39 taken 2476 times.
✗ Branch 38 → 72 not taken.
2476 fileDir = std::filesystem::path(filePath).parent_path().string();
54
55 // Discard value names if not required
56
1/2
✓ Branch 43 → 44 taken 2476 times.
✗ Branch 43 → 93 not taken.
2476 context.setDiscardValueNames(!cliOptions.namesForIRValues);
57
58 // Search after the selected target
59 2476 std::string error;
60
1/2
✓ Branch 45 → 46 taken 2476 times.
✗ Branch 45 → 91 not taken.
2476 const llvm::Target *target = llvm::TargetRegistry::lookupTarget(cliOptions.targetTriple, error);
61
1/2
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 52 taken 2476 times.
2476 if (!target)
62 throw CompilerError(TARGET_NOT_AVAILABLE, "Selected target was not found: " + error); // GCOV_EXCL_LINE
63
64 // Create the target machine
65
1/2
✓ Branch 52 → 53 taken 2476 times.
✗ Branch 52 → 91 not taken.
2476 llvm::TargetOptions opt;
66 2476 opt.MCOptions.AsmVerbose = true;
67 2476 opt.MCOptions.PreserveAsmComments = true;
68 2476 const std::string &cpuName = resourceManager.cpuName;
69 2476 const std::string &features = resourceManager.cpuFeatures;
70 2476 const llvm::Triple &targetTriple = cliOptions.targetTriple;
71 2476 constexpr llvm::Reloc::Model relocModel = llvm::Reloc::PIC_;
72
1/2
✓ Branch 57 → 58 taken 2476 times.
✗ Branch 57 → 85 not taken.
2476 llvm::TargetMachine *targetMachineRaw = target->createTargetMachine(targetTriple, cpuName, features, opt, relocModel);
73 2476 targetMachine = std::unique_ptr<llvm::TargetMachine>(targetMachineRaw);
74 2476 }
75
76 3755 void SourceFile::runLexer() {
77
2/2
✓ Branch 2 → 3 taken 573 times.
✓ Branch 2 → 4 taken 3182 times.
3755 if (isMainFile)
78
1/2
✓ Branch 3 → 4 taken 573 times.
✗ Branch 3 → 83 not taken.
573 resourceManager.totalTimer.start();
79
80 // Check if this stage has already been done
81
2/2
✓ Branch 4 → 5 taken 1284 times.
✓ Branch 4 → 6 taken 2471 times.
3755 if (previousStage >= LEXER)
82 1284 return;
83
84
1/2
✓ Branch 6 → 7 taken 2471 times.
✗ Branch 6 → 83 not taken.
2471 Timer timer(&compilerOutput.times.lexer);
85
1/2
✓ Branch 7 → 8 taken 2471 times.
✗ Branch 7 → 83 not taken.
2471 timer.start();
86
87 // Read from the input source file
88
1/2
✓ Branch 8 → 9 taken 2471 times.
✗ Branch 8 → 83 not taken.
2471 std::ifstream fileInputStream(filePath);
89
3/4
✓ Branch 9 → 10 taken 2471 times.
✗ Branch 9 → 81 not taken.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 20 taken 2470 times.
2471 if (!fileInputStream)
90
4/8
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 59 not taken.
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 57 not taken.
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 55 not taken.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 52 not taken.
1 throw CompilerError(SOURCE_FILE_NOT_FOUND, "Source file at path '" + filePath.string() + "' does not exist.");
91
92 // Tokenize input
93
1/2
✓ Branch 20 → 21 taken 2470 times.
✗ Branch 20 → 64 not taken.
2470 antlrCtx.inputStream = std::make_unique<antlr4::ANTLRInputStream>(fileInputStream);
94
1/2
✓ Branch 24 → 25 taken 2470 times.
✗ Branch 24 → 65 not taken.
2470 antlrCtx.lexer = std::make_unique<SpiceLexer>(antlrCtx.inputStream.get());
95
1/2
✓ Branch 28 → 29 taken 2470 times.
✗ Branch 28 → 81 not taken.
2470 antlrCtx.lexer->removeErrorListeners();
96
1/2
✓ Branch 29 → 30 taken 2470 times.
✗ Branch 29 → 67 not taken.
2470 antlrCtx.lexerErrorHandler = std::make_unique<AntlrThrowingErrorListener>(ThrowingErrorListenerMode::LEXER, this);
97
1/2
✓ Branch 34 → 35 taken 2470 times.
✗ Branch 34 → 81 not taken.
2470 antlrCtx.lexer->addErrorListener(antlrCtx.lexerErrorHandler.get());
98
1/2
✓ Branch 36 → 37 taken 2470 times.
✗ Branch 36 → 70 not taken.
2470 antlrCtx.tokenStream = std::make_unique<antlr4::CommonTokenStream>(antlrCtx.lexer.get());
99
100 // Pre-compute a local cache key so the field is populated for cycle-aware fallbacks.
101 // The final key (which folds in transitive dependency cache keys) is computed at the end
102 // of runImportCollector, once every dependency's cache key has been finalized.
103
3/4
✓ Branch 41 → 42 taken 2469 times.
✓ Branch 41 → 74 taken 1 time.
✓ Branch 42 → 43 taken 2469 times.
✗ Branch 42 → 72 not taken.
2471 cacheKey = resourceManager.cacheManager.computeCacheKey(antlrCtx.tokenStream->getText());
104
105 2469 previousStage = LEXER;
106
1/2
✓ Branch 47 → 48 taken 2469 times.
✗ Branch 47 → 81 not taken.
2469 timer.stop();
107
1/2
✓ Branch 48 → 49 taken 2469 times.
✗ Branch 48 → 79 not taken.
2469 printStatusMessage("Lexer", IO_CODE, IO_TOKENS, compilerOutput.times.lexer);
108 2471 }
109
110 3753 void SourceFile::runParser() {
111 // Skip if restored from the cache or this stage has already been done
112
3/4
✓ Branch 2 → 3 taken 3753 times.
✗ Branch 2 → 4 not taken.
✓ Branch 3 → 4 taken 1284 times.
✓ Branch 3 → 5 taken 2469 times.
3753 if (restoredFromCache || previousStage >= PARSER)
113 1284 return;
114
115
1/2
✓ Branch 5 → 6 taken 2469 times.
✗ Branch 5 → 32 not taken.
2469 Timer timer(&compilerOutput.times.parser);
116
1/2
✓ Branch 6 → 7 taken 2469 times.
✗ Branch 6 → 32 not taken.
2469 timer.start();
117
118 // Parse input
119
1/2
✓ Branch 8 → 9 taken 2469 times.
✗ Branch 8 → 25 not taken.
2469 antlrCtx.parser = std::make_unique<SpiceParser>(antlrCtx.tokenStream.get()); // Check for syntax errors
120
1/2
✓ Branch 12 → 13 taken 2469 times.
✗ Branch 12 → 32 not taken.
2469 antlrCtx.parser->removeErrorListeners();
121
1/2
✓ Branch 13 → 14 taken 2469 times.
✗ Branch 13 → 27 not taken.
2469 antlrCtx.parserErrorHandler = std::make_unique<AntlrThrowingErrorListener>(ThrowingErrorListenerMode::PARSER, this);
122
1/2
✓ Branch 18 → 19 taken 2469 times.
✗ Branch 18 → 32 not taken.
2469 antlrCtx.parser->addErrorListener(antlrCtx.parserErrorHandler.get());
123
1/2
✓ Branch 20 → 21 taken 2469 times.
✗ Branch 20 → 32 not taken.
2469 antlrCtx.parser->removeParseListeners();
124
125 2469 previousStage = PARSER;
126
1/2
✓ Branch 21 → 22 taken 2469 times.
✗ Branch 21 → 32 not taken.
2469 timer.stop();
127
1/2
✓ Branch 22 → 23 taken 2469 times.
✗ Branch 22 → 30 not taken.
2469 printStatusMessage("Parser", IO_TOKENS, IO_CST, compilerOutput.times.parser);
128 }
129
130 3192 void SourceFile::runCSTVisualizer() {
131 // Only execute if enabled
132
4/6
✓ Branch 2 → 3 taken 3192 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 3192 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 3188 times.
3192 if (restoredFromCache || (!cliOptions.dump.dumpCST && !cliOptions.testMode))
133 1288 return;
134 // Check if this stage has already been done
135
2/2
✓ Branch 6 → 7 taken 1284 times.
✓ Branch 6 → 8 taken 1904 times.
3188 if (previousStage >= CST_VISUALIZER)
136 1284 return;
137
138
1/2
✓ Branch 8 → 9 taken 1904 times.
✗ Branch 8 → 66 not taken.
1904 Timer timer(&compilerOutput.times.cstVisualizer);
139
1/2
✓ Branch 9 → 10 taken 1904 times.
✗ Branch 9 → 66 not taken.
1904 timer.start();
140
141 // Generate dot code for this source file
142
1/2
✓ Branch 10 → 11 taken 1904 times.
✗ Branch 10 → 66 not taken.
1904 std::stringstream dotCode;
143
1/2
✓ Branch 11 → 12 taken 1904 times.
✗ Branch 11 → 64 not taken.
1904 visualizerPreamble(dotCode);
144
1/2
✓ Branch 14 → 15 taken 1904 times.
✗ Branch 14 → 64 not taken.
1904 CSTVisualizer cstVisualizer(resourceManager, this, antlrCtx.lexer.get(), antlrCtx.parser.get());
145
6/12
✓ Branch 15 → 16 taken 1904 times.
✗ Branch 15 → 62 not taken.
✓ Branch 17 → 18 taken 1904 times.
✗ Branch 17 → 51 not taken.
✓ Branch 18 → 19 taken 1904 times.
✗ Branch 18 → 51 not taken.
✓ Branch 19 → 20 taken 1904 times.
✗ Branch 19 → 49 not taken.
✓ Branch 20 → 21 taken 1904 times.
✗ Branch 20 → 47 not taken.
✓ Branch 21 → 22 taken 1904 times.
✗ Branch 21 → 47 not taken.
1904 dotCode << " " << std::any_cast<std::string>(cstVisualizer.visit(antlrCtx.parser->entry())) << "}";
146
1/2
✓ Branch 25 → 26 taken 1904 times.
✗ Branch 25 → 62 not taken.
1904 antlrCtx.parser->reset();
147
148 // Dump the serialized CST string and the SVG file
149
2/4
✓ Branch 26 → 27 taken 1904 times.
✗ Branch 26 → 28 not taken.
✓ Branch 27 → 28 taken 1904 times.
✗ Branch 27 → 32 not taken.
1904 if (cliOptions.dump.dumpCST || cliOptions.testMode)
150
1/2
✓ Branch 28 → 29 taken 1904 times.
✗ Branch 28 → 53 not taken.
1904 compilerOutput.cstString = dotCode.str();
151
152
1/2
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 40 taken 1904 times.
1904 if (cliOptions.dump.dumpCST)
153 visualizerOutput("CST", compilerOutput.cstString);
154
155 1904 previousStage = CST_VISUALIZER;
156
1/2
✓ Branch 40 → 41 taken 1904 times.
✗ Branch 40 → 62 not taken.
1904 timer.stop();
157
1/2
✓ Branch 41 → 42 taken 1904 times.
✗ Branch 41 → 60 not taken.
1904 printStatusMessage("CST Visualizer", IO_CST, IO_CST, compilerOutput.times.cstVisualizer);
158 1904 }
159
160 3753 void SourceFile::runASTBuilder() {
161 // Skip if restored from the cache or this stage has already been done
162
3/4
✓ Branch 2 → 3 taken 3753 times.
✗ Branch 2 → 4 not taken.
✓ Branch 3 → 4 taken 1284 times.
✓ Branch 3 → 5 taken 2469 times.
3753 if (restoredFromCache || previousStage >= AST_BUILDER)
163 1284 return;
164
165
1/2
✓ Branch 5 → 6 taken 2469 times.
✗ Branch 5 → 36 not taken.
2469 Timer timer(&compilerOutput.times.astBuilder);
166
1/2
✓ Branch 6 → 7 taken 2469 times.
✗ Branch 6 → 36 not taken.
2469 timer.start();
167
168 // Build AST for this source file
169
1/2
✓ Branch 8 → 9 taken 2469 times.
✗ Branch 8 → 36 not taken.
2469 ASTBuilder astBuilder(resourceManager, this, antlrCtx.inputStream.get());
170
5/6
✓ Branch 10 → 11 taken 2467 times.
✓ Branch 10 → 26 taken 2 times.
✓ Branch 11 → 12 taken 2461 times.
✓ Branch 11 → 26 taken 6 times.
✓ Branch 12 → 13 taken 2461 times.
✗ Branch 12 → 24 not taken.
2469 ast = std::any_cast<EntryNode *>(astBuilder.visit(antlrCtx.parser->entry()));
171
1/2
✓ Branch 15 → 16 taken 2461 times.
✗ Branch 15 → 34 not taken.
2461 antlrCtx.parser->reset();
172
173 // Create global scope
174
1/2
✓ Branch 16 → 17 taken 2461 times.
✗ Branch 16 → 27 not taken.
2461 globalScope = std::make_unique<Scope>(nullptr, this, ScopeType::GLOBAL, &ast->codeLoc);
175
176 2461 previousStage = AST_BUILDER;
177
1/2
✓ Branch 19 → 20 taken 2461 times.
✗ Branch 19 → 34 not taken.
2461 timer.stop();
178
1/2
✓ Branch 20 → 21 taken 2461 times.
✗ Branch 20 → 32 not taken.
2461 printStatusMessage("AST Builder", IO_CST, IO_AST, compilerOutput.times.astBuilder);
179 2469 }
180
181 3192 void SourceFile::runASTVisualizer() {
182 // Only execute if enabled
183
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3192 times.
3192 if (restoredFromCache)
184 1288 return;
185
3/4
✓ Branch 4 → 5 taken 3192 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 4 times.
✓ Branch 5 → 7 taken 3188 times.
3192 if (!cliOptions.dump.dumpAST && !cliOptions.testMode)
186 4 return;
187 // Check if this stage has already been done
188
2/2
✓ Branch 7 → 8 taken 1284 times.
✓ Branch 7 → 9 taken 1904 times.
3188 if (previousStage >= AST_VISUALIZER)
189 1284 return;
190
191
1/2
✓ Branch 9 → 10 taken 1904 times.
✗ Branch 9 → 58 not taken.
1904 Timer timer(&compilerOutput.times.astVisualizer);
192
1/2
✓ Branch 10 → 11 taken 1904 times.
✗ Branch 10 → 58 not taken.
1904 timer.start();
193
194 // Generate dot code for this source file
195
1/2
✓ Branch 11 → 12 taken 1904 times.
✗ Branch 11 → 58 not taken.
1904 std::stringstream dotCode;
196
1/2
✓ Branch 12 → 13 taken 1904 times.
✗ Branch 12 → 56 not taken.
1904 visualizerPreamble(dotCode);
197
1/2
✓ Branch 13 → 14 taken 1904 times.
✗ Branch 13 → 56 not taken.
1904 ASTVisualizer astVisualizer(resourceManager, this);
198
5/10
✓ Branch 14 → 15 taken 1904 times.
✗ Branch 14 → 54 not taken.
✓ Branch 15 → 16 taken 1904 times.
✗ Branch 15 → 43 not taken.
✓ Branch 16 → 17 taken 1904 times.
✗ Branch 16 → 41 not taken.
✓ Branch 17 → 18 taken 1904 times.
✗ Branch 17 → 39 not taken.
✓ Branch 18 → 19 taken 1904 times.
✗ Branch 18 → 39 not taken.
1904 dotCode << " " << std::any_cast<std::string>(astVisualizer.visit(ast)) << "}";
199
200 // Dump the serialized AST string and the SVG file
201
1/2
✓ Branch 21 → 22 taken 1904 times.
✗ Branch 21 → 45 not taken.
1904 compilerOutput.astString = dotCode.str();
202
203
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 32 taken 1904 times.
1904 if (cliOptions.dump.dumpAST)
204 visualizerOutput("AST", compilerOutput.astString);
205
206 1904 previousStage = AST_VISUALIZER;
207
1/2
✓ Branch 32 → 33 taken 1904 times.
✗ Branch 32 → 54 not taken.
1904 timer.stop();
208
1/2
✓ Branch 33 → 34 taken 1904 times.
✗ Branch 33 → 52 not taken.
1904 printStatusMessage("AST Visualizer", IO_AST, IO_AST, compilerOutput.times.astVisualizer);
209 1904 }
210
211 3745 void SourceFile::runImportCollector() { // NOLINT(misc-no-recursion)
212 // Skip if restored from the cache or this stage has already been done
213
3/4
✓ Branch 2 → 3 taken 3745 times.
✗ Branch 2 → 4 not taken.
✓ Branch 3 → 4 taken 1284 times.
✓ Branch 3 → 5 taken 2461 times.
3745 if (restoredFromCache || previousStage >= IMPORT_COLLECTOR)
214 1284 return;
215
216
1/2
✓ Branch 5 → 6 taken 2461 times.
✗ Branch 5 → 85 not taken.
2461 Timer timer(&compilerOutput.times.importCollector);
217
1/2
✓ Branch 6 → 7 taken 2461 times.
✗ Branch 6 → 85 not taken.
2461 timer.start();
218
219 // Collect the imports for this source file
220
1/2
✓ Branch 7 → 8 taken 2461 times.
✗ Branch 7 → 85 not taken.
2461 ImportCollector importCollector(resourceManager, this);
221
2/2
✓ Branch 8 → 9 taken 2457 times.
✓ Branch 8 → 67 taken 4 times.
2461 importCollector.visit(ast);
222
223 2457 previousStage = IMPORT_COLLECTOR;
224
225 // Run first part of pipeline for the imported source file
226
5/8
✓ Branch 10 → 11 taken 2457 times.
✗ Branch 10 → 68 not taken.
✓ Branch 11 → 12 taken 2457 times.
✗ Branch 11 → 68 not taken.
✓ Branch 12 → 13 taken 2457 times.
✗ Branch 12 → 68 not taken.
✓ Branch 18 → 14 taken 2513 times.
✓ Branch 18 → 19 taken 2457 times.
4970 for (SourceFile *sourceFile : dependencies | std::views::values)
227
1/2
✓ Branch 15 → 16 taken 2513 times.
✗ Branch 15 → 68 not taken.
2513 sourceFile->runFrontEnd();
228
229 // Now that every transitive dependency has its final cache key, fold them into our own
230 // cache key. This way any change to a dependency invalidates the cache entry of every
231 // dependent (and transitively of the dependents' dependents), avoiding stale object files.
232 2457 std::vector<std::string> transitiveDepCacheKeys;
233 2457 std::unordered_set<std::string> visited;
234
1/2
✓ Branch 20 → 21 taken 2457 times.
✗ Branch 20 → 79 not taken.
2457 std::queue<const SourceFile *> worklist;
235
5/8
✓ Branch 21 → 22 taken 2457 times.
✗ Branch 21 → 69 not taken.
✓ Branch 22 → 23 taken 2457 times.
✗ Branch 22 → 69 not taken.
✓ Branch 23 → 24 taken 2457 times.
✗ Branch 23 → 69 not taken.
✓ Branch 29 → 25 taken 2513 times.
✓ Branch 29 → 30 taken 2457 times.
4970 for (const SourceFile *dep : dependencies | std::views::values)
236
1/2
✓ Branch 26 → 27 taken 2513 times.
✗ Branch 26 → 69 not taken.
2513 worklist.push(dep);
237
2/2
✓ Branch 49 → 31 taken 12208 times.
✓ Branch 49 → 50 taken 2457 times.
14665 while (!worklist.empty()) {
238 12208 const SourceFile *dep = worklist.front();
239 12208 worklist.pop();
240
3/4
✓ Branch 33 → 34 taken 12208 times.
✗ Branch 33 → 77 not taken.
✓ Branch 34 → 35 taken 6481 times.
✓ Branch 34 → 36 taken 5727 times.
12208 if (!visited.insert(dep->cacheKey).second)
241 6481 continue;
242
1/2
✓ Branch 36 → 37 taken 5727 times.
✗ Branch 36 → 77 not taken.
5727 transitiveDepCacheKeys.push_back(dep->cacheKey);
243
5/8
✓ Branch 37 → 38 taken 5727 times.
✗ Branch 37 → 70 not taken.
✓ Branch 38 → 39 taken 5727 times.
✗ Branch 38 → 70 not taken.
✓ Branch 39 → 40 taken 5727 times.
✗ Branch 39 → 70 not taken.
✓ Branch 45 → 41 taken 9695 times.
✓ Branch 45 → 46 taken 5727 times.
15422 for (const SourceFile *transitive : dep->dependencies | std::views::values)
244
1/2
✓ Branch 42 → 43 taken 9695 times.
✗ Branch 42 → 70 not taken.
9695 worklist.push(transitive);
245 }
246
2/4
✓ Branch 51 → 52 taken 2457 times.
✗ Branch 51 → 73 not taken.
✓ Branch 52 → 53 taken 2457 times.
✗ Branch 52 → 71 not taken.
2457 cacheKey = resourceManager.cacheManager.computeCacheKey(antlrCtx.tokenStream->getText(), transitiveDepCacheKeys);
247
248 // Try to load from the cache. Deferred from runLexer so that dep cache keys can participate.
249
2/2
✓ Branch 56 → 57 taken 4 times.
✓ Branch 56 → 59 taken 2453 times.
2457 if (!cliOptions.ignoreCache)
250
1/2
✓ Branch 57 → 58 taken 4 times.
✗ Branch 57 → 77 not taken.
4 restoredFromCache = resourceManager.cacheManager.lookupSourceFile(this);
251
252
1/2
✓ Branch 59 → 60 taken 2457 times.
✗ Branch 59 → 77 not taken.
2457 timer.stop();
253
1/2
✓ Branch 60 → 61 taken 2457 times.
✗ Branch 60 → 75 not taken.
2457 printStatusMessage("Import Collector", IO_AST, IO_AST, compilerOutput.times.importCollector);
254 2461 }
255
256 3741 void SourceFile::runSymbolTableBuilder() {
257 // Skip if this stage has already been done. Unlike the later stages, this one must still run even if the file was
258 // restored from the cache: it's the only pass that populates exportedNameRegistry, and a dependant that isn't itself
259 // a cache hit needs that registry to resolve the symbols it imports from this file.
260
2/2
✓ Branch 2 → 3 taken 1284 times.
✓ Branch 2 → 4 taken 2457 times.
3741 if (previousStage >= SYMBOL_TABLE_BUILDER)
261 1284 return;
262
263
1/2
✓ Branch 4 → 5 taken 2457 times.
✗ Branch 4 → 19 not taken.
2457 Timer timer(&compilerOutput.times.symbolTableBuilder);
264
1/2
✓ Branch 5 → 6 taken 2457 times.
✗ Branch 5 → 19 not taken.
2457 timer.start();
265
266 // Build symbol table of the current file. The dependencies' exported name registries are merged in afterwards, in a
267 // separate pass (mergeNameRegistriesRecursive), once every reachable file has built its own registry. This deferral
268 // is what makes circular imports work: with a cycle, a dependency's registry is not fully populated yet at this point.
269
1/2
✓ Branch 6 → 7 taken 2457 times.
✗ Branch 6 → 19 not taken.
2457 SymbolTableBuilder symbolTableBuilder(resourceManager, this);
270
2/2
✓ Branch 7 → 8 taken 2438 times.
✓ Branch 7 → 14 taken 19 times.
2457 symbolTableBuilder.visit(ast);
271
272 2438 previousStage = SYMBOL_TABLE_BUILDER;
273
1/2
✓ Branch 9 → 10 taken 2438 times.
✗ Branch 9 → 17 not taken.
2438 timer.stop();
274
1/2
✓ Branch 10 → 11 taken 2438 times.
✗ Branch 10 → 15 not taken.
2438 printStatusMessage("Symbol Table Builder", IO_AST, IO_AST, compilerOutput.times.symbolTableBuilder);
275 2457 }
276
277 3721 void SourceFile::runTypeCheckerPre() { // NOLINT(misc-no-recursion)
278 // Skip if this stage has already been done. Unlike the later (codegen) stages, this one must still run even if the
279 // file was restored from the cache: it's what populates the FunctionManager/StructManager manifestations that a
280 // dependant which isn't itself a cache hit needs for overload resolution and generic substantiation.
281 // The typeCheckerPreRunning guard breaks the recursion on a circular import: a cyclic back-edge returns immediately
282 // instead of recursing forever. The file is still pre-checked once the in-progress invocation reaches it, and any
283 // cross-file references left unresolved (because a cycle peer was not pre-checked yet) are fixed up by the post run.
284
4/4
✓ Branch 2 → 3 taken 2462 times.
✓ Branch 2 → 4 taken 1259 times.
✓ Branch 3 → 4 taken 25 times.
✓ Branch 3 → 5 taken 2437 times.
3721 if (previousStage >= TYPE_CHECKER_PRE || typeCheckerPreRunning)
285 1284 return;
286 2437 typeCheckerPreRunning = true;
287
288 // Type-check all dependencies first
289
5/8
✓ Branch 5 → 6 taken 2437 times.
✗ Branch 5 → 24 not taken.
✓ Branch 6 → 7 taken 2437 times.
✗ Branch 6 → 24 not taken.
✓ Branch 7 → 8 taken 2437 times.
✗ Branch 7 → 24 not taken.
✓ Branch 13 → 9 taken 2512 times.
✓ Branch 13 → 14 taken 2436 times.
4948 for (SourceFile *sourceFile : dependencies | std::views::values)
290
2/2
✓ Branch 10 → 11 taken 2511 times.
✓ Branch 10 → 24 taken 1 time.
2512 sourceFile->runTypeCheckerPre();
291
292
1/2
✓ Branch 14 → 15 taken 2436 times.
✗ Branch 14 → 30 not taken.
2436 Timer timer(&compilerOutput.times.typeCheckerPre);
293
1/2
✓ Branch 15 → 16 taken 2436 times.
✗ Branch 15 → 30 not taken.
2436 timer.start();
294
295 // Then type-check the current file
296
1/2
✓ Branch 16 → 17 taken 2436 times.
✗ Branch 16 → 30 not taken.
2436 TypeChecker typeChecker(resourceManager, this, TC_MODE_PRE);
297
2/2
✓ Branch 17 → 18 taken 2421 times.
✓ Branch 17 → 25 taken 15 times.
2436 typeChecker.visit(ast);
298
299 2421 previousStage = TYPE_CHECKER_PRE;
300 2421 typeCheckerPreRunning = false;
301
1/2
✓ Branch 19 → 20 taken 2421 times.
✗ Branch 19 → 28 not taken.
2421 timer.stop();
302
1/2
✓ Branch 20 → 21 taken 2421 times.
✗ Branch 20 → 26 not taken.
2421 printStatusMessage("Type Checker Pre", IO_AST, IO_AST, compilerOutput.times.typeCheckerPre);
303 2436 }
304
305 10257 void SourceFile::runTypeCheckerPost() { // NOLINT(misc-no-recursion)
306 // Re-entrancy guard: within an import cycle, a dependency's post-run recurses back into this file's post-run. The
307 // in-flight fixpoint loop below already revisits this file, so the nested call must be a no-op to avoid unbounded
308 // mutual recursion. Convergence is driven by the reVisitRequested flags propagating across the cycle.
309
2/2
✓ Branch 2 → 3 taken 26 times.
✓ Branch 2 → 4 taken 10231 times.
10257 if (typeCheckerPostRunning)
310 3364 return;
311
312 // Skip if not all dependants finished type checking yet. This still has to run for files restored from the cache,
313 // for the same reason as runTypeCheckerPre (see comment there).
314
3/4
✓ Branch 4 → 5 taken 10231 times.
✗ Branch 4 → 80 not taken.
✓ Branch 5 → 6 taken 3338 times.
✓ Branch 5 → 7 taken 6893 times.
10231 if (!haveAllDependantsBeenTypeChecked())
315 3338 return;
316
317 6893 typeCheckerPostRunning = true;
318
319
1/2
✓ Branch 7 → 8 taken 6893 times.
✗ Branch 7 → 80 not taken.
6893 Timer timer(&compilerOutput.times.typeCheckerPost);
320
1/2
✓ Branch 8 → 9 taken 6893 times.
✗ Branch 8 → 80 not taken.
6893 timer.start();
321
322 // Start type-checking loop. The type-checker can request a re-execution. The max number of type-checker runs is limited
323
1/2
✓ Branch 9 → 10 taken 6893 times.
✗ Branch 9 → 80 not taken.
6893 TypeChecker typeChecker(resourceManager, this, TC_MODE_POST);
324 6893 unsigned short typeCheckerRuns = 0;
325
2/2
✓ Branch 25 → 11 taken 3713 times.
✓ Branch 25 → 26 taken 6825 times.
10538 while (reVisitRequested) {
326 3713 typeCheckerRuns++;
327 3713 totalTypeCheckerRuns++;
328 3713 reVisitRequested = false;
329
330 // Type-check the current file first. Multiple times, if requested
331 3713 timer.resume();
332
2/2
✓ Branch 12 → 13 taken 3681 times.
✓ Branch 12 → 58 taken 32 times.
3713 typeChecker.visit(ast);
333
1/2
✓ Branch 14 → 15 taken 3681 times.
✗ Branch 14 → 78 not taken.
3681 timer.pause();
334
335 // Then type-check all dependencies
336
5/8
✓ Branch 15 → 16 taken 3681 times.
✗ Branch 15 → 59 not taken.
✓ Branch 16 → 17 taken 3681 times.
✗ Branch 16 → 59 not taken.
✓ Branch 17 → 18 taken 3681 times.
✗ Branch 17 → 59 not taken.
✓ Branch 23 → 19 taken 9704 times.
✓ Branch 23 → 24 taken 3645 times.
13349 for (SourceFile *sourceFile : dependencies | std::views::values)
337
2/2
✓ Branch 20 → 21 taken 9668 times.
✓ Branch 20 → 59 taken 36 times.
9704 sourceFile->runTypeCheckerPost();
338 }
339
340 6825 typeCheckerPostRunning = false;
341
342
2/2
✓ Branch 26 → 27 taken 6711 times.
✓ Branch 26 → 78 taken 114 times.
6825 checkForSoftErrors();
343
344 // Check if all dyn variables were type-inferred successfully
345
2/2
✓ Branch 28 → 29 taken 6710 times.
✓ Branch 28 → 78 taken 1 time.
6711 globalScope->ensureSuccessfulTypeInference();
346
347 #ifndef NDEBUG
348 // In debug builds, verify that the TypeChecker fully annotated the AST
349
1/2
✓ Branch 29 → 30 taken 6710 times.
✗ Branch 29 → 78 not taken.
6710 runPostTypeCheckingVerifier();
350 #endif
351
352 6710 previousStage = TYPE_CHECKER_POST;
353
1/2
✓ Branch 30 → 31 taken 6710 times.
✗ Branch 30 → 78 not taken.
6710 timer.stop();
354
1/2
✓ Branch 31 → 32 taken 6710 times.
✗ Branch 31 → 60 not taken.
6710 printStatusMessage("Type Checker Post", IO_AST, IO_AST, compilerOutput.times.typeCheckerPost, typeCheckerRuns);
355
356 // Save the JSON version in the compiler output
357
3/4
✓ Branch 32 → 33 taken 6710 times.
✗ Branch 32 → 34 not taken.
✓ Branch 33 → 34 taken 6706 times.
✓ Branch 33 → 41 taken 4 times.
6710 if (cliOptions.dump.dumpSymbolTable || cliOptions.testMode)
358
2/4
✓ Branch 35 → 36 taken 6706 times.
✗ Branch 35 → 64 not taken.
✓ Branch 36 → 37 taken 6706 times.
✗ Branch 36 → 62 not taken.
6706 compilerOutput.symbolTableString = globalScope->getSymbolTableJSON().dump(/*indent=*/2);
359
360 // Dump symbol table
361
1/2
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 54 taken 6710 times.
6710 if (cliOptions.dump.dumpSymbolTable)
362 dumpOutput(compilerOutput.symbolTableString, "Symbol Table", "symbol-table.json");
363 6893 }
364
365 6710 void SourceFile::runPostTypeCheckingVerifier() {
366
1/2
✓ Branch 2 → 3 taken 6710 times.
✗ Branch 2 → 8 not taken.
6710 PostTypeCheckingVerifier verifier(resourceManager, this);
367
1/2
✓ Branch 3 → 4 taken 6710 times.
✗ Branch 3 → 6 not taken.
6710 verifier.verify(ast);
368 6710 }
369
370 379 void SourceFile::runDependencyGraphVisualizer() {
371 // Only execute if enabled
372
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 379 times.
379 if (restoredFromCache)
373 4 return;
374
3/4
✓ Branch 4 → 5 taken 379 times.
✗ Branch 4 → 7 not taken.
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 7 taken 377 times.
379 if (!cliOptions.dump.dumpDependencyGraph && !cliOptions.testMode)
375 2 return;
376 // Check if this stage has already been done
377
2/2
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 9 taken 375 times.
377 if (previousStage >= DEP_GRAPH_VISUALIZER)
378 2 return;
379
380
1/2
✓ Branch 9 → 10 taken 375 times.
✗ Branch 9 → 47 not taken.
375 Timer timer(&compilerOutput.times.depGraphVisualizer);
381
1/2
✓ Branch 10 → 11 taken 375 times.
✗ Branch 10 → 47 not taken.
375 timer.start();
382
383 // Generate dot code for this source file
384
1/2
✓ Branch 11 → 12 taken 375 times.
✗ Branch 11 → 47 not taken.
375 std::stringstream dotCode;
385
1/2
✓ Branch 12 → 13 taken 375 times.
✗ Branch 12 → 45 not taken.
375 visualizerPreamble(dotCode);
386
1/2
✓ Branch 13 → 14 taken 375 times.
✗ Branch 13 → 45 not taken.
375 DependencyGraphVisualizer depGraphVisualizer(resourceManager, this);
387
1/2
✓ Branch 14 → 15 taken 375 times.
✗ Branch 14 → 43 not taken.
375 depGraphVisualizer.getDependencyGraph(dotCode);
388
1/2
✓ Branch 15 → 16 taken 375 times.
✗ Branch 15 → 43 not taken.
375 dotCode << "}";
389
390 // Dump the serialized AST string and the SVG file
391
1/2
✓ Branch 16 → 17 taken 375 times.
✗ Branch 16 → 34 not taken.
375 compilerOutput.depGraphString = dotCode.str();
392
393
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 27 taken 375 times.
375 if (cliOptions.dump.dumpDependencyGraph)
394 visualizerOutput("Dependency Graph", compilerOutput.depGraphString);
395
396 375 previousStage = DEP_GRAPH_VISUALIZER;
397
1/2
✓ Branch 27 → 28 taken 375 times.
✗ Branch 27 → 43 not taken.
375 timer.stop();
398
1/2
✓ Branch 28 → 29 taken 375 times.
✗ Branch 28 → 41 not taken.
375 printStatusMessage("AST Visualizer", IO_AST, IO_AST, compilerOutput.times.depGraphVisualizer);
399 375 }
400
401 2251 void SourceFile::runIRGenerator() {
402 // Skip if restored from the cache or this stage has already been done
403
4/4
✓ Branch 2 → 3 taken 2250 times.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 2249 times.
2251 if (restoredFromCache || previousStage >= IR_GENERATOR)
404 2 return;
405
406
1/2
✓ Branch 5 → 6 taken 2249 times.
✗ Branch 5 → 60 not taken.
2249 Timer timer(&compilerOutput.times.irGenerator);
407
1/2
✓ Branch 6 → 7 taken 2249 times.
✗ Branch 6 → 60 not taken.
2249 timer.start();
408
409 // Create the LLVM module for this source file
410
2/2
✓ Branch 7 → 8 taken 4 times.
✓ Branch 7 → 9 taken 2245 times.
2249 llvm::LLVMContext &llvmContext = cliOptions.useLTO ? resourceManager.ltoContext : context;
411
1/2
✓ Branch 10 → 11 taken 2249 times.
✗ Branch 10 → 41 not taken.
2249 llvmModule = std::make_unique<llvm::Module>(fileName, llvmContext);
412
413 // Generate this source file
414
1/2
✓ Branch 13 → 14 taken 2249 times.
✗ Branch 13 → 60 not taken.
2249 IRGenerator irGenerator(resourceManager, this);
415
1/2
✓ Branch 14 → 15 taken 2249 times.
✗ Branch 14 → 42 not taken.
2249 irGenerator.visit(ast);
416
417 // Save the ir string in the compiler output
418
3/4
✓ Branch 16 → 17 taken 2249 times.
✗ Branch 16 → 18 not taken.
✓ Branch 17 → 18 taken 2246 times.
✓ Branch 17 → 23 taken 3 times.
2249 if (cliOptions.dump.dumpIR || cliOptions.testMode)
419
1/2
✓ Branch 19 → 20 taken 2246 times.
✗ Branch 19 → 43 not taken.
2246 compilerOutput.irString = IRGenerator::getIRString(llvmModule.get(), cliOptions);
420
421 // Dump unoptimized IR code
422
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 36 taken 2249 times.
2249 if (cliOptions.dump.dumpIR)
423 dumpOutput(compilerOutput.irString, "Unoptimized IR Code", "ir-code.ll");
424
425 2249 previousStage = IR_GENERATOR;
426
1/2
✓ Branch 36 → 37 taken 2249 times.
✗ Branch 36 → 58 not taken.
2249 timer.stop();
427
1/2
✓ Branch 37 → 38 taken 2249 times.
✗ Branch 37 → 56 not taken.
2249 printStatusMessage("IR Generator", IO_AST, IO_IR, compilerOutput.times.irGenerator);
428 2249 }
429
430 2041 void SourceFile::runDefaultIROptimizer() {
431
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2041 times.
2041 assert(!cliOptions.useLTO);
432
433 // Skip if restored from the cache or this stage has already been done
434
4/8
✓ Branch 4 → 5 taken 2040 times.
✓ Branch 4 → 8 taken 1 time.
✓ Branch 5 → 6 taken 2040 times.
✗ Branch 5 → 8 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 2040 times.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
2041 if (restoredFromCache || previousStage > IR_OPTIMIZER || (previousStage == IR_OPTIMIZER && !cliOptions.testMode))
435 1 return;
436
437
1/2
✓ Branch 9 → 10 taken 2040 times.
✗ Branch 9 → 60 not taken.
2040 Timer timer(&compilerOutput.times.irOptimizer);
438
1/2
✓ Branch 10 → 11 taken 2040 times.
✗ Branch 10 → 60 not taken.
2040 timer.start();
439
440 // Optimize this source file
441
1/2
✓ Branch 11 → 12 taken 2040 times.
✗ Branch 11 → 60 not taken.
2040 IROptimizer irOptimizer(resourceManager, this);
442
1/2
✓ Branch 12 → 13 taken 2040 times.
✗ Branch 12 → 58 not taken.
2040 irOptimizer.prepare();
443
1/2
✓ Branch 13 → 14 taken 2040 times.
✗ Branch 13 → 58 not taken.
2040 irOptimizer.optimizeDefault();
444
445 // Save the optimized ir string in the compiler output
446
3/4
✓ Branch 14 → 15 taken 2040 times.
✗ Branch 14 → 16 not taken.
✓ Branch 15 → 16 taken 2037 times.
✓ Branch 15 → 21 taken 3 times.
2040 if (cliOptions.dump.dumpIR || cliOptions.testMode)
447
1/2
✓ Branch 17 → 18 taken 2037 times.
✗ Branch 17 → 40 not taken.
2037 compilerOutput.irOptString = IRGenerator::getIRString(llvmModule.get(), cliOptions);
448
449 // Dump optimized IR code
450
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 35 taken 2040 times.
2040 if (cliOptions.dump.dumpIR)
451 dumpOutput(compilerOutput.irOptString, "Optimized IR Code",
452 "ir-code-O" + std::to_string(static_cast<uint8_t>(cliOptions.optLevel)) + ".ll");
453
454 2040 previousStage = IR_OPTIMIZER;
455
1/2
✓ Branch 35 → 36 taken 2040 times.
✗ Branch 35 → 58 not taken.
2040 timer.stop();
456
1/2
✓ Branch 36 → 37 taken 2040 times.
✗ Branch 36 → 56 not taken.
2040 printStatusMessage("IR Optimizer", IO_IR, IO_IR, compilerOutput.times.irOptimizer);
457 2040 }
458
459 3 void SourceFile::runPreLinkIROptimizer() {
460
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3 times.
3 assert(cliOptions.useLTO);
461
462 // Skip if restored from the cache or this stage has already been done
463
2/4
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 3 times.
3 if (restoredFromCache || previousStage >= IR_OPTIMIZER)
464 return;
465
466
1/2
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 51 not taken.
3 Timer timer(&compilerOutput.times.irOptimizer);
467
1/2
✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 51 not taken.
3 timer.start();
468
469 // Optimize this source file
470
1/2
✓ Branch 9 → 10 taken 3 times.
✗ Branch 9 → 51 not taken.
3 IROptimizer irOptimizer(resourceManager, this);
471
1/2
✓ Branch 10 → 11 taken 3 times.
✗ Branch 10 → 49 not taken.
3 irOptimizer.prepare();
472
1/2
✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 49 not taken.
3 irOptimizer.optimizePreLink();
473
474 // Save the optimized ir string in the compiler output
475
2/4
✓ Branch 12 → 13 taken 3 times.
✗ Branch 12 → 14 not taken.
✓ Branch 13 → 14 taken 3 times.
✗ Branch 13 → 19 not taken.
3 if (cliOptions.dump.dumpIR || cliOptions.testMode)
476
1/2
✓ Branch 15 → 16 taken 3 times.
✗ Branch 15 → 36 not taken.
3 compilerOutput.irOptString = IRGenerator::getIRString(llvmModule.get(), cliOptions);
477
478 // Dump optimized IR code
479
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 32 taken 3 times.
3 if (cliOptions.dump.dumpIR)
480 dumpOutput(compilerOutput.irOptString, "Optimized IR Code (pre-link)", "ir-code-lto-pre-link.ll");
481
482
1/2
✓ Branch 32 → 33 taken 3 times.
✗ Branch 32 → 49 not taken.
3 timer.pause();
483 3 }
484
485 2 void SourceFile::runBitcodeLinker() {
486
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2 times.
2 assert(cliOptions.useLTO);
487
488 // Skip if this is not the main source file
489
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 1 time.
2 if (!isMainFile)
490 1 return;
491
492 // Skip if restored from the cache or this stage has already been done
493
2/4
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 if (restoredFromCache || previousStage >= IR_OPTIMIZER)
494 return;
495
496
1/2
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 20 not taken.
1 Timer timer(&compilerOutput.times.irOptimizer);
497 1 timer.resume();
498
499 // Link all source files together
500
1/2
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 20 not taken.
1 BitcodeLinker linker(resourceManager);
501
1/2
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 18 not taken.
1 linker.link();
502
503
1/2
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 18 not taken.
1 timer.pause();
504 1 }
505
506 2 void SourceFile::runPostLinkIROptimizer() {
507
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2 times.
2 assert(cliOptions.useLTO);
508
509 // Skip if this is not the main source file
510
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 1 time.
2 if (!isMainFile)
511 1 return;
512
513 // Skip if restored from the cache or this stage has already been done
514
2/4
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 if (restoredFromCache || previousStage >= IR_OPTIMIZER)
515 return;
516
517
1/2
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 57 not taken.
1 Timer timer(&compilerOutput.times.irOptimizer);
518 1 timer.resume();
519
520 // Optimize LTO module
521
1/2
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 57 not taken.
1 IROptimizer irOptimizer(resourceManager, this);
522
1/2
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 55 not taken.
1 irOptimizer.prepare();
523
1/2
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 55 not taken.
1 irOptimizer.optimizePostLink();
524
525 // Save the optimized ir string in the compiler output
526
2/4
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 16 not taken.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 21 not taken.
1 if (cliOptions.dump.dumpIR || cliOptions.testMode) {
527 1 llvm::Module *module = resourceManager.ltoModule.get();
528
1/2
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 40 not taken.
1 compilerOutput.irOptString = IRGenerator::getIRString(module, cliOptions);
529 }
530
531 // Dump optimized IR code
532
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 34 taken 1 time.
1 if (cliOptions.dump.dumpIR)
533 dumpOutput(compilerOutput.irOptString, "Optimized IR Code (post-Link)", "ir-code-lto-post-link.ll");
534
535 1 previousStage = IR_OPTIMIZER;
536
1/2
✓ Branch 34 → 35 taken 1 time.
✗ Branch 34 → 55 not taken.
1 timer.stop();
537
1/2
✓ Branch 35 → 36 taken 1 time.
✗ Branch 35 → 53 not taken.
1 printStatusMessage("IR Optimizer", IO_IR, IO_IR, compilerOutput.times.irOptimizer);
538 1 }
539
540 2166 void SourceFile::runObjectEmitter() {
541 // Skip if restored from the cache or this stage has already been done
542
4/4
✓ Branch 2 → 3 taken 2165 times.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 2164 times.
2166 if (restoredFromCache || previousStage >= OBJECT_EMITTER)
543 3 return;
544
545 // Skip if LTO is enabled and this is not the main source file
546
4/4
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 8 taken 2162 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 1 time.
2164 if (cliOptions.useLTO && !isMainFile)
547 1 return;
548
549
1/2
✓ Branch 8 → 9 taken 2163 times.
✗ Branch 8 → 82 not taken.
2163 Timer timer(&compilerOutput.times.objectEmitter);
550
1/2
✓ Branch 9 → 10 taken 2163 times.
✗ Branch 9 → 82 not taken.
2163 timer.start();
551
552 // Deduce an object file path
553
2/4
✓ Branch 10 → 11 taken 2163 times.
✗ Branch 10 → 58 not taken.
✓ Branch 11 → 12 taken 2163 times.
✗ Branch 11 → 56 not taken.
2163 objectFilePath = cliOptions.outputDir / filePath.filename();
554
2/4
✓ Branch 15 → 16 taken 2163 times.
✗ Branch 15 → 62 not taken.
✓ Branch 16 → 17 taken 2163 times.
✗ Branch 16 → 60 not taken.
2163 objectFilePath.replace_extension("o");
555
556 // Pick a concrete emitter based on the selected backend. The TPDE emitter is compiled into a
557 // sibling library (spice_tpde) that keeps its -fno-rtti requirement out of spicecore; the
558 // AbstractObjectEmitter base gives us a single interface both branches produce.
559 2163 std::unique_ptr<AbstractObjectEmitter> objectEmitter;
560 #ifdef SPICE_ENABLE_TPDE
561
2/2
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 26 taken 2162 times.
2163 if (cliOptions.backend == Backend::TPDE) {
562
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 1 time.
1 llvm::Module &module = cliOptions.useLTO ? *resourceManager.ltoModule : *llvmModule;
563
1/2
✓ Branch 22 → 23 taken 1 time.
✗ Branch 22 → 63 not taken.
1 objectEmitter = std::make_unique<TPDEObjectEmitter>(module);
564 } else {
565
1/2
✓ Branch 26 → 27 taken 2162 times.
✗ Branch 26 → 64 not taken.
2162 objectEmitter = std::make_unique<LLVMObjectEmitter>(resourceManager, this);
566 }
567 #else
568 objectEmitter = std::make_unique<LLVMObjectEmitter>(resourceManager, this);
569 #endif
570
571 // Emit object for this source file
572
1/2
✓ Branch 31 → 32 taken 2163 times.
✗ Branch 31 → 80 not taken.
2163 objectEmitter->emit(objectFilePath);
573
574 // Save assembly string in the compiler output (TPDE emits a placeholder note)
575
5/6
✓ Branch 32 → 33 taken 2119 times.
✓ Branch 32 → 37 taken 44 times.
✓ Branch 33 → 34 taken 2119 times.
✗ Branch 33 → 35 not taken.
✓ Branch 34 → 35 taken 2116 times.
✓ Branch 34 → 37 taken 3 times.
2163 if (cliOptions.isNativeTarget && (cliOptions.dump.dumpAssembly || cliOptions.testMode))
576
1/2
✓ Branch 36 → 37 taken 2116 times.
✗ Branch 36 → 80 not taken.
2116 objectEmitter->getASMString(compilerOutput.asmString);
577
578 // Dump assembly code
579
1/2
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 50 taken 2163 times.
2163 if (cliOptions.dump.dumpAssembly)
580 dumpOutput(compilerOutput.asmString, "Assembly code", "assembly-code.s");
581
582 // The object file is registered with the linker in concludeCompilation and not here, because this stage may run on a
583 // worker thread of the parallel back end and the linker input order has to stay deterministic.
584
585 2163 previousStage = OBJECT_EMITTER;
586
1/2
✓ Branch 50 → 51 taken 2163 times.
✗ Branch 50 → 80 not taken.
2163 timer.stop();
587
1/2
✓ Branch 51 → 52 taken 2163 times.
✗ Branch 51 → 78 not taken.
2163 printStatusMessage("Object Emitter", IO_IR, IO_OBJECT_FILE, compilerOutput.times.objectEmitter);
588 2163 }
589
590 2166 void SourceFile::concludeCompilation() {
591 // Handle cache-restored files: register all cached objects with linker
592
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 51 taken 2165 times.
2166 if (restoredFromCache) {
593
2/2
✓ Branch 17 → 5 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
3 for (const auto &cachedObjectFilePath : cachedObjectFilePaths)
594
1/2
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 103 not taken.
1 resourceManager.linker.addFileToLinkage(cachedObjectFilePath);
595
1/2
✗ Branch 32 → 20 not taken.
✓ Branch 32 → 33 taken 1 time.
2 for (const auto &flag : sourceLinkerFlags)
596 resourceManager.linker.addLinkerFlag(flag);
597
1/2
✗ Branch 49 → 35 not taken.
✓ Branch 49 → 50 taken 1 time.
2 for (const auto &path : sourceAdditionalSourcePaths)
598 resourceManager.linker.addAdditionalSourcePath(path);
599 1 return;
600 }
601
602
2/2
✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 2164 times.
2165 if (previousStage >= FINISHED)
603 1 return;
604
605 // Add the emitted object file to the linker objects. This happens here and not in runObjectEmitter, because
606 // concludeCompilation is always driven serially and in dependency order, while the object emitter may run on a worker
607 // thread of the parallel back end.
608
2/2
✓ Branch 54 → 55 taken 2163 times.
✓ Branch 54 → 56 taken 1 time.
2164 if (!objectFilePath.empty())
609 2163 resourceManager.linker.addFileToLinkage(objectFilePath);
610
611 // Cache the source file
612
2/2
✓ Branch 56 → 57 taken 3 times.
✓ Branch 56 → 58 taken 2161 times.
2164 if (!cliOptions.ignoreCache)
613 3 resourceManager.cacheManager.cacheSourceFile(this);
614
615 // Save type registry as string in the compiler output
616
5/6
✓ Branch 58 → 59 taken 329 times.
✓ Branch 58 → 65 taken 1835 times.
✓ Branch 59 → 60 taken 329 times.
✗ Branch 59 → 61 not taken.
✓ Branch 60 → 61 taken 327 times.
✓ Branch 60 → 65 taken 2 times.
2164 if (isMainFile && (cliOptions.dump.dumpTypes || cliOptions.testMode))
617
1/2
✓ Branch 61 → 62 taken 327 times.
✗ Branch 61 → 109 not taken.
327 compilerOutput.typesString = TypeRegistry::dump();
618
619 // Dump type registry
620
3/4
✓ Branch 65 → 66 taken 329 times.
✓ Branch 65 → 79 taken 1835 times.
✗ Branch 66 → 67 not taken.
✓ Branch 66 → 79 taken 329 times.
2164 if (isMainFile && cliOptions.dump.dumpTypes)
621 dumpOutput(compilerOutput.typesString, "Type Registry", "type-registry.out");
622
623 // Save cache statistics as string in the compiler output
624
5/6
✓ Branch 79 → 80 taken 329 times.
✓ Branch 79 → 83 taken 1835 times.
✓ Branch 80 → 81 taken 329 times.
✗ Branch 80 → 82 not taken.
✓ Branch 81 → 82 taken 327 times.
✓ Branch 81 → 83 taken 2 times.
2164 if (isMainFile && (cliOptions.dump.dumpCacheStats || cliOptions.testMode))
625 327 dumpCacheStats();
626
627 // Dump lookup cache statistics
628
3/4
✓ Branch 83 → 84 taken 329 times.
✓ Branch 83 → 97 taken 1835 times.
✗ Branch 84 → 85 not taken.
✓ Branch 84 → 97 taken 329 times.
2164 if (isMainFile && cliOptions.dump.dumpCacheStats)
629 dumpOutput(compilerOutput.cacheStats, "Cache Statistics", "cache-stats.out");
630
631
1/2
✗ Branch 97 → 98 not taken.
✓ Branch 97 → 101 taken 2164 times.
2164 if (cliOptions.printDebugOutput)
632 std::cout << "Finished compiling " << fileName << std::endl;
633
634 2164 previousStage = FINISHED;
635 }
636
637 3185 void SourceFile::runFrontEnd() { // NOLINT(misc-no-recursion)
638 3185 runLexer();
639
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 3185 times.
3185 CHECK_ABORT_FLAG_V()
640 3185 runParser();
641
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 3185 times.
3185 CHECK_ABORT_FLAG_V()
642 3185 runCSTVisualizer();
643
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 3185 times.
3185 CHECK_ABORT_FLAG_V()
644 3185 runASTBuilder();
645
1/2
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 3185 times.
3185 CHECK_ABORT_FLAG_V()
646 3185 runASTVisualizer();
647
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 3185 times.
3185 CHECK_ABORT_FLAG_V()
648 3185 runImportCollector();
649
1/2
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 3185 times.
3185 CHECK_ABORT_FLAG_V()
650 3185 runSymbolTableBuilder();
651
1/2
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 3185 times.
3185 CHECK_ABORT_FLAG_V()
652 }
653
654 539 void SourceFile::runMiddleEnd() {
655 // Merge the exported name registries of all (transitive) dependencies into the respective importing files. This is
656 // the deferred tail of the front-end: it must run after every reachable file has built its own registry, which is
657 // why it cannot live inside the per-file front-end recursion (a circular import would otherwise merge a dependency
658 // whose registry is not populated yet). runMiddleEnd is the first stage that is only ever invoked at the top level.
659 539 mergeNameRegistriesRecursive();
660
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 539 times.
539 CHECK_ABORT_FLAG_V()
661 // We need two runs here due to generics.
662 // The first run to determine all concrete function/struct/interface substantiations
663 539 runTypeCheckerPre(); // Visit the dependency tree from bottom to top
664
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 524 times.
524 CHECK_ABORT_FLAG_V()
665 // The second run to ensure, also generic scopes are type-checked properly
666 524 runTypeCheckerPost(); // Visit the dependency tree from top to bottom in topological order
667
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 377 times.
377 CHECK_ABORT_FLAG_V()
668 // The per-file convergence loop inside runTypeCheckerPost is scoped to its own call stack: a cross-file revisit
669 // request that lands on a file whose loop already unwound (e.g. a recursive generic dtor chain that closes back
670 // through a runtime module which was itself gated behind another, not-yet-checked importer) is otherwise dropped,
671 // leaving a fully-substantiated manifestation whose body was never type-checked. Sweep every source file in the
672 // program for a straggling revisit request and drive it to convergence directly, repeating until none are left.
673 bool anySourceFileRevisitPending;
674
2/2
✓ Branch 48 → 14 taken 10 times.
✓ Branch 48 → 49 taken 377 times.
387 do {
675 387 anySourceFileRevisitPending = false;
676 // Snapshot the current files before driving any of them: runTypeCheckerPost() below can itself trigger a
677 // freshly-discovered runtime import (SourceFile::requestRuntimeModule -> GlobalResourceManager::createSourceFile),
678 // which inserts into resourceManager.sourceFiles - iterating that map while it is being mutated is undefined
679 // behavior, so a stable list of raw pointers is collected first.
680 387 std::vector<SourceFile *> sourceFilesSnapshot;
681
1/2
✓ Branch 15 → 16 taken 387 times.
✗ Branch 15 → 60 not taken.
387 sourceFilesSnapshot.reserve(resourceManager.sourceFiles.size());
682
5/8
✓ Branch 16 → 17 taken 387 times.
✗ Branch 16 → 58 not taken.
✓ Branch 17 → 18 taken 387 times.
✗ Branch 17 → 58 not taken.
✓ Branch 18 → 19 taken 387 times.
✗ Branch 18 → 58 not taken.
✓ Branch 25 → 20 taken 2570 times.
✓ Branch 25 → 26 taken 387 times.
2957 for (const std::unique_ptr<SourceFile> &sourceFile : resourceManager.sourceFiles | std::views::values)
683
1/2
✓ Branch 22 → 23 taken 2570 times.
✗ Branch 22 → 57 not taken.
2570 sourceFilesSnapshot.push_back(sourceFile.get());
684
2/2
✓ Branch 42 → 28 taken 2570 times.
✓ Branch 42 → 43 taken 387 times.
3344 for (SourceFile *sourceFile : sourceFilesSnapshot) {
685
2/2
✓ Branch 30 → 31 taken 29 times.
✓ Branch 30 → 33 taken 2541 times.
2570 if (sourceFile->reVisitRequested) {
686
1/2
✓ Branch 31 → 32 taken 29 times.
✗ Branch 31 → 59 not taken.
29 sourceFile->runTypeCheckerPost();
687 29 anySourceFileRevisitPending = true;
688 }
689 }
690 // A source file created mid-sweep (see above) still needs its own pass; it starts out with reVisitRequested
691 // true, so re-looping picks it up via the snapshot taken on the next iteration.
692
1/2
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 387 times.
387 if (resourceManager.sourceFiles.size() > sourceFilesSnapshot.size())
693 anySourceFileRevisitPending = true;
694 387 } while (anySourceFileRevisitPending);
695
1/2
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 52 taken 377 times.
377 CHECK_ABORT_FLAG_V()
696 // Visualize dependency graph
697 377 runDependencyGraphVisualizer();
698
1/2
✗ Branch 54 → 55 not taken.
✓ Branch 54 → 56 taken 377 times.
377 CHECK_ABORT_FLAG_V()
699 }
700
701 4721 void SourceFile::collectBackEndSourceFiles(std::vector<SourceFile *> &backEndSourceFiles) { // NOLINT(misc-no-recursion)
702 // Guard against collecting a file that already went through the back end. Circular imports form a cycle in the
703 // dependency graph, so the deps-first recursion below would otherwise loop forever.
704
2/2
✓ Branch 2 → 3 taken 2882 times.
✓ Branch 2 → 4 taken 1839 times.
4721 if (backEndStarted)
705 2882 return;
706 1839 backEndStarted = true;
707
708 // Collect all dependencies first, so that they end up in front of this file in the resulting list
709
5/8
✓ Branch 4 → 5 taken 1839 times.
✗ Branch 4 → 16 not taken.
✓ Branch 5 → 6 taken 1839 times.
✗ Branch 5 → 16 not taken.
✓ Branch 6 → 7 taken 1839 times.
✗ Branch 6 → 16 not taken.
✓ Branch 12 → 8 taken 4334 times.
✓ Branch 12 → 13 taken 1839 times.
6173 for (SourceFile *sourceFile : dependencies | std::views::values)
710
1/2
✓ Branch 9 → 10 taken 4334 times.
✗ Branch 9 → 16 not taken.
4334 sourceFile->collectBackEndSourceFiles(backEndSourceFiles);
711
712
1/2
✓ Branch 13 → 14 taken 1839 times.
✗ Branch 13 → 17 not taken.
1839 backEndSourceFiles.push_back(this);
713 }
714
715 1839 void SourceFile::runBackEndForThisFile() {
716 1839 runIRGenerator();
717
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1839 times.
1839 CHECK_ABORT_FLAG_V()
718
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 19 taken 1838 times.
1839 if (cliOptions.useLTO) {
719 1 runPreLinkIROptimizer();
720
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
1 CHECK_ABORT_FLAG_V()
721 1 runBitcodeLinker();
722
1/2
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 1 time.
1 CHECK_ABORT_FLAG_V()
723 1 runPostLinkIROptimizer();
724
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 23 taken 1 time.
1 CHECK_ABORT_FLAG_V()
725 } else {
726 1838 runDefaultIROptimizer();
727
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 1838 times.
1838 CHECK_ABORT_FLAG_V()
728 }
729 1839 runObjectEmitter();
730 }
731
732 387 void SourceFile::runBackEnd() {
733 // Flatten the dependency graph into the order the back end used to recurse in: every file comes after all of its
734 // dependencies, and files that already ran their back end are skipped.
735 387 std::vector<SourceFile *> backEndSourceFiles;
736
1/2
✓ Branch 2 → 3 taken 387 times.
✗ Branch 2 → 102 not taken.
387 collectBackEndSourceFiles(backEndSourceFiles);
737
738 // Nothing to do if this file and all of its dependencies already went through the back end
739
2/2
✓ Branch 4 → 5 taken 52 times.
✓ Branch 4 → 6 taken 335 times.
387 if (backEndSourceFiles.empty())
740 52 return;
741
742 // Unlike the front end and the middle end, the back end has no cross-file data dependencies: every source file owns
743 // its own LLVMContext, IRBuilder, TargetMachine and llvm::Module, and references to symbols of other files are emitted
744 // as declarations into the local module. So the per-file pipelines can simply be spread over a worker pool.
745 // Exceptions, in which the back end stays serial:
746 // - LTO, because all source files share the LTO context and module of the GlobalResourceManager
747 // - dump modes, because they write to the console/output dir and their ordering is part of the user-visible output
748
3/6
✓ Branch 6 → 7 taken 335 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 335 times.
✗ Branch 7 → 9 not taken.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 335 times.
335 const bool dumpRequested = cliOptions.dump.dumpIR || cliOptions.dump.dumpAssembly || cliOptions.dump.dumpObjectFiles;
749
1/2
✓ Branch 12 → 13 taken 335 times.
✗ Branch 12 → 90 not taken.
335 const size_t jobCount = std::min(resourceManager.getCompileJobCount(), backEndSourceFiles.size());
750
1/6
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 18 taken 335 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 18 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
335 const bool runParallel = jobCount > 1 && !cliOptions.useLTO && !dumpRequested;
751
752
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 42 taken 335 times.
335 if (runParallel) {
753 ThreadPool &threadPool = resourceManager.getThreadPool(jobCount);
754 const ParallelSection parallelSection;
755 for (SourceFile *sourceFile : backEndSourceFiles)
756 threadPool.submit([sourceFile] { sourceFile->runBackEndForThisFile(); });
757 threadPool.waitForAll(); // Re-throws the exception of the first failing source file, if there was one
758 } else {
759
2/2
✓ Branch 59 → 44 taken 1839 times.
✓ Branch 59 → 60 taken 335 times.
2509 for (SourceFile *sourceFile : backEndSourceFiles) {
760
1/2
✓ Branch 46 → 47 taken 1839 times.
✗ Branch 46 → 100 not taken.
1839 sourceFile->runBackEndForThisFile();
761
1/2
✗ Branch 48 → 49 not taken.
✓ Branch 48 → 50 taken 1839 times.
1839 CHECK_ABORT_FLAG_V()
762 }
763 }
764
1/2
✗ Branch 62 → 63 not taken.
✓ Branch 62 → 64 taken 335 times.
335 CHECK_ABORT_FLAG_V()
765
766 // Conclude the compilation of all source files. This registers the emitted object files with the linker and writes the
767 // compilation cache, both of which have to happen serially and in a fixed order to stay deterministic.
768
2/2
✓ Branch 78 → 66 taken 1839 times.
✓ Branch 78 → 79 taken 335 times.
2509 for (SourceFile *sourceFile : backEndSourceFiles)
769
1/2
✓ Branch 68 → 69 taken 1839 times.
✗ Branch 68 → 101 not taken.
1839 sourceFile->concludeCompilation();
770
771
2/2
✓ Branch 79 → 80 taken 2 times.
✓ Branch 79 → 83 taken 333 times.
335 if (isMainFile) {
772
1/2
✓ Branch 80 → 81 taken 2 times.
✗ Branch 80 → 102 not taken.
2 resourceManager.totalTimer.stop();
773
1/2
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 83 taken 2 times.
2 if (cliOptions.printDebugOutput)
774 dumpCompilationStats();
775 }
776
2/2
✓ Branch 85 → 86 taken 335 times.
✓ Branch 85 → 88 taken 52 times.
387 }
777
778 4794 void SourceFile::addDependency(SourceFile *sourceFile, const std::string &dependencyName) {
779 // Circular imports are explicitly supported, so cycles are not rejected here. Source files are deduplicated by path
780 // in GlobalResourceManager::createSourceFile, so a cyclic import resolves to the same SourceFile instance and the
781 // pipeline drivers guard against re-entering a file that is already in progress.
782
783 // Add the dependency. Do not demote the compilation root (parent == nullptr) to a non-main file: with a circular
784 // import the root can be imported by one of its own transitive dependencies, yet it must remain the main file (the
785 // isMainFile flag drives getRootSourceFile, object emission, timing, etc.).
786
2/2
✓ Branch 2 → 3 taken 4793 times.
✓ Branch 2 → 4 taken 1 time.
4794 if (sourceFile->parent != nullptr)
787 4793 sourceFile->isMainFile = false;
788 4794 dependencies.emplace(dependencyName, sourceFile);
789
790 // Add the dependant
791
1/2
✓ Branch 5 → 6 taken 4794 times.
✗ Branch 5 → 7 not taken.
4794 sourceFile->dependants.push_back(this);
792 4794 }
793
794 300693 bool SourceFile::imports(const SourceFile *sourceFile) const {
795 1429068 return std::ranges::any_of(dependencies, [=](const auto &dependency) { return dependency.second == sourceFile; });
796 }
797
798 13184 SourceFile *SourceFile::requestRuntimeModule(RuntimeModule runtimeModule) {
799 // Check if the module was already imported
800
2/2
✓ Branch 3 → 4 taken 10905 times.
✓ Branch 3 → 6 taken 2279 times.
13184 if (isRuntimeModuleAvailable(runtimeModule))
801 10905 return resourceManager.runtimeModuleManager.getModule(runtimeModule);
802 2279 return resourceManager.runtimeModuleManager.requestModule(this, runtimeModule);
803 }
804
805 15359 bool SourceFile::isRuntimeModuleAvailable(RuntimeModule runtimeModule) const { return importedRuntimeModules & runtimeModule; }
806
807 167163 void SourceFile::addNameRegistryEntry(const std::string &symbolName, uint64_t typeId, SymbolTableEntry *entry, Scope *scope,
808 bool keepNewOnCollision, SymbolTableEntry *importEntry) {
809
6/6
✓ Branch 2 → 3 taken 83219 times.
✓ Branch 2 → 5 taken 83944 times.
✓ Branch 4 → 5 taken 82757 times.
✓ Branch 4 → 6 taken 462 times.
✓ Branch 7 → 8 taken 166701 times.
✓ Branch 7 → 15 taken 462 times.
167163 if (keepNewOnCollision || !exportedNameRegistry.contains(symbolName)) // Overwrite potential existing entry
810 333402 exportedNameRegistry[symbolName] = {symbolName, typeId, entry, scope, importEntry};
811 else // Name collision => we must remove the existing entry
812 462 exportedNameRegistry.erase(symbolName);
813
3/8
✓ Branch 8 → 9 taken 166701 times.
✗ Branch 8 → 22 not taken.
✓ Branch 9 → 10 taken 166701 times.
✗ Branch 9 → 17 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 166701 times.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 21 not taken.
500565 }
814
815 988480 const NameRegistryEntry *SourceFile::getNameRegistryEntry(const std::string &symbolName) const {
816
1/2
✓ Branch 2 → 3 taken 988480 times.
✗ Branch 2 → 13 not taken.
988480 const auto it = exportedNameRegistry.find(symbolName);
817
2/2
✓ Branch 5 → 6 taken 457233 times.
✓ Branch 5 → 7 taken 531247 times.
988480 if (it == exportedNameRegistry.end())
818 457233 return nullptr;
819
820 // Resolve registry entry for the given name
821 531247 const NameRegistryEntry *entry = &it->second;
822
823 // Mark the import entry as used
824
2/2
✓ Branch 8 → 9 taken 67862 times.
✓ Branch 8 → 10 taken 463385 times.
531247 if (entry->importEntry != nullptr)
825 67862 entry->importEntry->used = true;
826
827 531247 return entry;
828 }
829
830 1118832 llvm::Type *SourceFile::getLLVMType(const Type *type) {
831 // Check if the type is already in the mapping
832
1/2
✓ Branch 2 → 3 taken 1118832 times.
✗ Branch 2 → 13 not taken.
1118832 const auto it = typeToLLVMTypeMapping.find(type);
833
2/2
✓ Branch 5 → 6 taken 1086185 times.
✓ Branch 5 → 8 taken 32647 times.
1118832 if (it != typeToLLVMTypeMapping.end())
834 1086185 return it->second;
835
836 // If not, generate the LLVM type
837
1/2
✓ Branch 8 → 9 taken 32647 times.
✗ Branch 8 → 13 not taken.
32647 llvm::Type *llvmType = type->toLLVMType(this);
838
1/2
✓ Branch 9 → 10 taken 32647 times.
✗ Branch 9 → 13 not taken.
32647 typeToLLVMTypeMapping[type] = llvmType;
839 32647 return llvmType;
840 }
841
842 6827 void SourceFile::checkForSoftErrors() const {
843 // Check if there are any soft errors and if so, print them
844
2/2
✓ Branch 3 → 4 taken 116 times.
✓ Branch 3 → 27 taken 6711 times.
6827 if (!resourceManager.errorManager.softErrors.empty()) {
845
1/2
✓ Branch 4 → 5 taken 116 times.
✗ Branch 4 → 37 not taken.
116 std::stringstream errorStream;
846
1/2
✓ Branch 5 → 6 taken 116 times.
✗ Branch 5 → 35 not taken.
116 errorStream << "There are unresolved errors. Please fix them and recompile.";
847
2/2
✓ Branch 21 → 8 taken 185 times.
✓ Branch 21 → 22 taken 116 times.
417 for (const auto &[codeLoc, message] : resourceManager.errorManager.softErrors)
848
2/4
✓ Branch 10 → 11 taken 185 times.
✗ Branch 10 → 28 not taken.
✓ Branch 11 → 12 taken 185 times.
✗ Branch 11 → 28 not taken.
185 errorStream << "\n\n" << message;
849
2/4
✓ Branch 23 → 24 taken 116 times.
✗ Branch 23 → 32 not taken.
✓ Branch 24 → 25 taken 116 times.
✗ Branch 24 → 29 not taken.
116 throw CompilerError(UNRESOLVED_SOFT_ERRORS, errorStream.str());
850 116 }
851 6711 }
852
853 394 void SourceFile::collectAndPrintWarnings() { // NOLINT(misc-no-recursion)
854 // Skip if restored from cache (no scope tree available), or if already visited. The latter guard keeps circular
855 // imports from recursing infinitely, since the dependency graph may contain cycles.
856
3/4
✓ Branch 2 → 3 taken 394 times.
✗ Branch 2 → 4 not taken.
✓ Branch 3 → 4 taken 15 times.
✓ Branch 3 → 5 taken 379 times.
394 if (restoredFromCache || warningsCollected)
857 15 return;
858 379 warningsCollected = true;
859 // Print warnings for all dependencies
860
5/8
✓ Branch 5 → 6 taken 379 times.
✗ Branch 5 → 35 not taken.
✓ Branch 6 → 7 taken 379 times.
✗ Branch 6 → 35 not taken.
✓ Branch 7 → 8 taken 379 times.
✗ Branch 7 → 35 not taken.
✓ Branch 14 → 9 taken 329 times.
✓ Branch 14 → 15 taken 379 times.
708 for (SourceFile *sourceFile : dependencies | std::views::values)
861
2/2
✓ Branch 10 → 11 taken 50 times.
✓ Branch 10 → 12 taken 279 times.
329 if (!sourceFile->isStdFile)
862
1/2
✓ Branch 11 → 12 taken 50 times.
✗ Branch 11 → 35 not taken.
50 sourceFile->collectAndPrintWarnings();
863 // Collect warnings for this file
864
1/2
✓ Branch 15 → 16 taken 379 times.
✗ Branch 15 → 18 not taken.
379 if (!ignoreWarnings)
865 379 globalScope->collectWarnings(compilerOutput.warnings);
866 // Print warnings for this file
867
2/2
✓ Branch 32 → 20 taken 194 times.
✓ Branch 32 → 33 taken 379 times.
952 for (const CompilerWarning &warning : compilerOutput.warnings)
868
1/2
✓ Branch 22 → 23 taken 194 times.
✗ Branch 22 → 36 not taken.
194 warning.print();
869 }
870
871 14856 const SourceFile *SourceFile::getRootSourceFile() const { // NOLINT(misc-no-recursion)
872
2/2
✓ Branch 2 → 3 taken 4883 times.
✓ Branch 2 → 4 taken 9973 times.
14856 return isMainFile ? this : parent->getRootSourceFile();
873 }
874
875 29823 bool SourceFile::isRT(RuntimeModule runtimeModule) const {
876
2/4
✓ Branch 2 → 3 taken 29823 times.
✗ Branch 2 → 38 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 29823 times.
29823 assert(IDENTIFYING_TOP_LEVEL_NAMES.contains(runtimeModule));
877
1/2
✓ Branch 5 → 6 taken 29823 times.
✗ Branch 5 → 38 not taken.
29823 const char *topLevelName = IDENTIFYING_TOP_LEVEL_NAMES.at(runtimeModule);
878
2/4
✓ Branch 8 → 9 taken 29823 times.
✗ Branch 8 → 28 not taken.
✓ Branch 9 → 10 taken 29823 times.
✗ Branch 9 → 26 not taken.
59646 const auto it = exportedNameRegistry.find(topLevelName);
879
2/2
✓ Branch 14 → 15 taken 3992 times.
✓ Branch 14 → 16 taken 25831 times.
29823 if (it == exportedNameRegistry.end())
880 3992 return false;
881
2/4
✓ Branch 18 → 19 taken 25831 times.
✗ Branch 18 → 34 not taken.
✓ Branch 19 → 20 taken 25831 times.
✗ Branch 19 → 32 not taken.
77493 return exportedNameRegistry.at(topLevelName).targetEntry->scope == globalScope.get();
882 }
883
884 10231 bool SourceFile::haveAllDependantsBeenTypeChecked() const {
885 10231 return std::ranges::all_of(dependants, [this](const SourceFile *dependant) {
886 // Ignore dependants that are part of the same import cycle (i.e. this file transitively depends on them). They
887 // cannot be type-checked before us either, so waiting on them would deadlock the whole cycle. Such a strongly
888 // connected component is type-checked as a unit and converges through the reVisitRequested fixpoint loop instead.
889
2/2
✓ Branch 3 → 4 taken 158 times.
✓ Branch 3 → 5 taken 33051 times.
33209 if (dependsOn(dependant))
890 158 return true;
891 33051 return dependant->totalTypeCheckerRuns >= 1;
892 10231 });
893 }
894
895 /**
896 * Check whether this source file transitively depends on (imports) the given other source file.
897 * Used to detect strongly connected components (import cycles) in the dependency graph.
898 *
899 * @param other Potential (transitive) dependency
900 * @return true if this file reaches the other file by following dependency edges
901 */
902 33209 bool SourceFile::dependsOn(const SourceFile *other) const {
903 33209 std::unordered_set<const SourceFile *> visited;
904
1/2
✓ Branch 3 → 4 taken 33209 times.
✗ Branch 3 → 35 not taken.
33209 std::queue<const SourceFile *> worklist;
905
1/2
✓ Branch 4 → 5 taken 33209 times.
✗ Branch 4 → 30 not taken.
33209 worklist.push(this);
906
1/2
✓ Branch 5 → 6 taken 33209 times.
✗ Branch 5 → 31 not taken.
33209 visited.insert(this);
907
2/2
✓ Branch 24 → 7 taken 172632 times.
✓ Branch 24 → 25 taken 33051 times.
205683 while (!worklist.empty()) {
908 172632 const SourceFile *current = worklist.front();
909 172632 worklist.pop();
910
5/8
✓ Branch 9 → 10 taken 172632 times.
✗ Branch 9 → 32 not taken.
✓ Branch 10 → 11 taken 172632 times.
✗ Branch 10 → 32 not taken.
✓ Branch 11 → 12 taken 172632 times.
✗ Branch 11 → 32 not taken.
✓ Branch 21 → 13 taken 282550 times.
✓ Branch 21 → 22 taken 172474 times.
455024 for (const SourceFile *dependency : current->dependencies | std::views::values) {
911
2/2
✓ Branch 14 → 15 taken 158 times.
✓ Branch 14 → 16 taken 282392 times.
282550 if (dependency == other)
912 158 return true;
913
3/4
✓ Branch 16 → 17 taken 282392 times.
✗ Branch 16 → 32 not taken.
✓ Branch 17 → 18 taken 139656 times.
✓ Branch 17 → 19 taken 142736 times.
282392 if (visited.insert(dependency).second)
914
1/2
✓ Branch 18 → 19 taken 139656 times.
✗ Branch 18 → 32 not taken.
139656 worklist.push(dependency);
915 }
916 }
917 33051 return false;
918 33209 }
919
920 /**
921 * Acquire all publicly visible symbols from the imported source file and put them in the name registry of the current one.
922 * But only do that for the symbols that are actually defined in the imported source file. Do not allow transitive dependencies.
923 * Here, we also register privately visible symbols to know that the symbol exist. The error handling regarding the visibility
924 * is issued later in the pipeline.
925 *
926 * @param importedSourceFile Imported source file
927 * @param importName First fragment of all fully qualified symbol names from that import
928 */
929 4791 void SourceFile::mergeNameRegistries(const SourceFile &importedSourceFile, const std::string &importName) {
930 // Retrieve import entry
931 4791 SymbolTableEntry *importEntry = globalScope->lookupStrict(importName);
932
3/4
✓ Branch 6 → 7 taken 2279 times.
✓ Branch 6 → 10 taken 2512 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 2279 times.
4791 assert(importEntry != nullptr || importName.starts_with("__")); // Runtime imports start with two underscores
933
934
2/2
✓ Branch 42 → 12 taken 277201 times.
✓ Branch 42 → 43 taken 4791 times.
281992 for (const auto &[originalName, entry] : importedSourceFile.exportedNameRegistry) {
935 // Skip if we introduce a transitive dependency
936
2/2
✓ Branch 16 → 17 taken 164049 times.
✓ Branch 16 → 18 taken 113152 times.
277201 if (entry.targetScope->sourceFile->globalScope != importedSourceFile.globalScope)
937 164049 continue;
938 // Add the fully qualified name
939
1/2
✓ Branch 18 → 19 taken 113152 times.
✗ Branch 18 → 52 not taken.
113152 std::string newName = importName;
940
1/2
✓ Branch 19 → 20 taken 113152 times.
✗ Branch 19 → 50 not taken.
113152 newName += SCOPE_ACCESS_TOKEN;
941
1/2
✓ Branch 20 → 21 taken 113152 times.
✗ Branch 20 → 50 not taken.
113152 newName += originalName;
942 226304 exportedNameRegistry.emplace(newName,
943
3/8
✓ Branch 21 → 22 taken 113152 times.
✗ Branch 21 → 49 not taken.
✓ Branch 22 → 23 taken 113152 times.
✗ Branch 22 → 44 not taken.
✗ Branch 24 → 25 not taken.
✓ Branch 24 → 26 taken 113152 times.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
113152 NameRegistryEntry{newName, entry.typeId, entry.targetEntry, entry.targetScope, importEntry});
944 // Add the shortened name, considering the name collision. A symbol defined in the importing file itself always
945 // shadows imported symbols of the same name. Since this merge runs after the file built its own registry (so that
946 // circular imports work), we must explicitly avoid letting an import overwrite or erase such an own symbol - the old
947 // ordering achieved this implicitly by registering own symbols last with keep-on-collision.
948
1/2
✓ Branch 26 → 27 taken 113152 times.
✗ Branch 26 → 50 not taken.
113152 const auto existing = exportedNameRegistry.find(originalName);
949 const bool existingIsOwn =
950
4/4
✓ Branch 29 → 30 taken 3041 times.
✓ Branch 29 → 34 taken 110111 times.
✓ Branch 32 → 33 taken 909 times.
✓ Branch 32 → 34 taken 2132 times.
113152 existing != exportedNameRegistry.end() && existing->second.targetScope->sourceFile->globalScope == globalScope;
951
2/2
✓ Branch 35 → 36 taken 112243 times.
✓ Branch 35 → 37 taken 909 times.
113152 if (!existingIsOwn) {
952 112243 const bool keepOnCollision = importedSourceFile.alwaysKeepSymbolsOnNameCollision;
953
1/2
✓ Branch 36 → 37 taken 112243 times.
✗ Branch 36 → 50 not taken.
112243 addNameRegistryEntry(originalName, entry.typeId, entry.targetEntry, entry.targetScope, keepOnCollision, importEntry);
954 }
955 113152 }
956 4791 }
957
958 /**
959 * Recursively merge the exported name registries of all (transitive) dependencies into the respective importing source
960 * files. Each file merges its direct dependencies' registries exactly once (guarded by registriesMerged), so this is
961 * safe to call on overlapping subgraphs and on graphs that contain cycles (circular imports).
962 *
963 * Must only be called once every reachable file has built its own exported name registry (i.e. after the front-end).
964 */
965 3721 void SourceFile::mergeNameRegistriesRecursive() { // NOLINT(misc-no-recursion)
966
2/2
✓ Branch 2 → 3 taken 1284 times.
✓ Branch 2 → 4 taken 2437 times.
3721 if (registriesMerged)
967 1284 return;
968 2437 registriesMerged = true;
969
970 // Merge the direct dependencies' registries into this file. Their own registries are fully built by now, so the
971 // order in which the reachable files are visited does not matter (even across import cycles).
972
2/2
✓ Branch 12 → 6 taken 2512 times.
✓ Branch 12 → 13 taken 2437 times.
4949 for (const auto &[importName, dependency] : dependencies)
973
1/2
✓ Branch 9 → 10 taken 2512 times.
✗ Branch 9 → 24 not taken.
2512 mergeNameRegistries(*dependency, importName);
974
975 // Recurse into the dependencies to cover the rest of the reachable graph
976
5/8
✓ Branch 13 → 14 taken 2437 times.
✗ Branch 13 → 25 not taken.
✓ Branch 14 → 15 taken 2437 times.
✗ Branch 14 → 25 not taken.
✓ Branch 15 → 16 taken 2437 times.
✗ Branch 15 → 25 not taken.
✓ Branch 21 → 17 taken 2512 times.
✓ Branch 21 → 22 taken 2437 times.
4949 for (SourceFile *dependency : dependencies | std::views::values)
977
1/2
✓ Branch 18 → 19 taken 2512 times.
✗ Branch 18 → 25 not taken.
2512 dependency->mergeNameRegistriesRecursive();
978 }
979
980 327 void SourceFile::dumpCacheStats() {
981
1/2
✓ Branch 2 → 3 taken 327 times.
✗ Branch 2 → 32 not taken.
327 std::stringstream cacheStats;
982
3/6
✓ Branch 3 → 4 taken 327 times.
✗ Branch 3 → 22 not taken.
✓ Branch 4 → 5 taken 327 times.
✗ Branch 4 → 20 not taken.
✓ Branch 5 → 6 taken 327 times.
✗ Branch 5 → 20 not taken.
327 cacheStats << FunctionManager::dumpLookupCacheStatistics() << std::endl;
983
3/6
✓ Branch 7 → 8 taken 327 times.
✗ Branch 7 → 25 not taken.
✓ Branch 8 → 9 taken 327 times.
✗ Branch 8 → 23 not taken.
✓ Branch 9 → 10 taken 327 times.
✗ Branch 9 → 23 not taken.
327 cacheStats << StructManager::dumpLookupCacheStatistics() << std::endl;
984
3/6
✓ Branch 11 → 12 taken 327 times.
✗ Branch 11 → 28 not taken.
✓ Branch 12 → 13 taken 327 times.
✗ Branch 12 → 26 not taken.
✓ Branch 13 → 14 taken 327 times.
✗ Branch 13 → 26 not taken.
327 cacheStats << InterfaceManager::dumpLookupCacheStatistics() << std::endl;
985
1/2
✓ Branch 15 → 16 taken 327 times.
✗ Branch 15 → 29 not taken.
327 compilerOutput.cacheStats = cacheStats.str();
986 327 }
987
988 void SourceFile::dumpCompilationStats() const {
989 const size_t sourceFileCount = resourceManager.sourceFiles.size();
990 const size_t totalLineCount = resourceManager.getTotalLineCount();
991 const size_t totalTypeCount = TypeRegistry::getTypeCount();
992 const size_t allocatedBytes = resourceManager.astNodeAlloc.getTotalAllocatedSize();
993 const size_t allocationCount = resourceManager.astNodeAlloc.getAllocationCount();
994 const size_t totalDuration = resourceManager.totalTimer.getDurationMilliseconds();
995 std::cout << "\nSuccessfully compiled " << std::to_string(sourceFileCount) << " source file(s)";
996 std::cout << " or " << std::to_string(totalLineCount) << " lines in total.\n";
997 std::cout << "Total number of blocks allocated via BlockAllocator: " << CommonUtil::formatBytes(allocatedBytes);
998 std::cout << " in " << std::to_string(allocationCount) << " allocations.\n";
999 #ifndef NDEBUG
1000 resourceManager.astNodeAlloc.printAllocatedClassStatistic();
1001 #endif
1002 std::cout << "Total number of types: " << std::to_string(totalTypeCount) << "\n";
1003 std::cout << "Total compile time: " << std::to_string(totalDuration) << " ms\n";
1004 }
1005
1006 void SourceFile::dumpOutput(const std::string &content, const std::string &caption, const std::string &fileSuffix) const {
1007 if (cliOptions.dump.dumpToFiles) {
1008 // Dump to file
1009 const std::string dumpFileName = filePath.stem().string() + "-" + fileSuffix;
1010 std::filesystem::path dumpFilePath = cliOptions.outputDir / dumpFileName;
1011 dumpFilePath.make_preferred();
1012 FileUtil::writeToFile(dumpFilePath, content);
1013 } else {
1014 // Dump to console
1015 std::cout << "\n" << caption << ":\n" << content;
1016 }
1017
1018 // If the abort after dump is requested, set the abort compilation flag
1019 if (cliOptions.dump.abortAfterDump) {
1020 // If this is an IR dump whilst having optimization enabled, we may not abort when dumping unoptimized IR,
1021 // because we also have to dump the optimized IR
1022 if (cliOptions.dump.dumpIR && fileSuffix == "ir-code.ll") {
1023 resourceManager.abortCompilation = cliOptions.optLevel == OptLevel::O0;
1024 } else {
1025 resourceManager.abortCompilation = true;
1026 }
1027 }
1028 }
1029
1030 4183 void SourceFile::visualizerPreamble(std::stringstream &output) const {
1031
2/2
✓ Branch 2 → 3 taken 389 times.
✓ Branch 2 → 4 taken 3794 times.
4183 if (isMainFile)
1032 389 output << "digraph {\n rankdir=\"TB\";\n";
1033 else
1034 3794 output << "subgraph {\n";
1035
3/6
✓ Branch 6 → 7 taken 4183 times.
✗ Branch 6 → 13 not taken.
✓ Branch 7 → 8 taken 4183 times.
✗ Branch 7 → 11 not taken.
✓ Branch 8 → 9 taken 4183 times.
✗ Branch 8 → 11 not taken.
4183 output << " label=\"" << filePath.generic_string() << "\";\n";
1036 4183 }
1037
1038 void SourceFile::visualizerOutput(std::string outputName, const std::string &output) const {
1039 if (cliOptions.dump.dumpToFiles) {
1040 // Check if graphviz is installed
1041 // GCOV_EXCL_START
1042 if (!SystemUtil::isGraphvizInstalled())
1043 throw CompilerError(IO_ERROR, "Please check if you have installed Graphviz and added it to the PATH variable");
1044 // GCOV_EXCL_STOP
1045
1046 // Write to a dot file
1047 std::ranges::transform(outputName, outputName.begin(), ::tolower);
1048 dumpOutput(output, outputName, outputName + ".dot");
1049
1050 // Generate SVG. This only works if the dot code was dumped into a file
1051 std::cout << "\nGenerating SVG file ... ";
1052 const std::string dotFileName = filePath.stem().string() + "-" + outputName + ".dot";
1053 std::filesystem::path dotFilePath = cliOptions.outputDir / dotFileName;
1054 std::filesystem::path svgFilePath = dotFilePath;
1055 svgFilePath.replace_extension("svg");
1056 dotFilePath.make_preferred();
1057 svgFilePath.make_preferred();
1058 SystemUtil::exec("dot -T svg -o" + svgFilePath.string() + " " + dotFilePath.string());
1059 std::cout << "done.\nSVG file can be found at: " << svgFilePath << "\n";
1060 } else {
1061 // Dump to console
1062 std::cout << "\nSerialized " << outputName << ":\n\n" << output << "\n";
1063 }
1064
1065 // If the abort after dump is requested, set the abort compilation flag
1066 if (cliOptions.dump.abortAfterDump)
1067 resourceManager.abortCompilation = true;
1068 }
1069
1070 32061 void SourceFile::printStatusMessage(const char *stage, const CompileStageIOType &in, const CompileStageIOType &out,
1071 uint64_t stageRuntime, unsigned short stageRuns) const {
1072
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 30 taken 32061 times.
32061 if (cliOptions.printDebugOutput) {
1073 static constexpr const char *const compilerStageIoTypeName[6] = {"Code", "Tokens", "CST", "AST", "IR", "Obj"};
1074 // Build output string
1075 std::stringstream outputStr;
1076 outputStr << "[" << stage << "] for " << fileName << ": ";
1077 outputStr << compilerStageIoTypeName[in] << " --> " << compilerStageIoTypeName[out];
1078 outputStr << " (" << std::to_string(stageRuntime) << " ms";
1079 if (stageRuns > 0)
1080 outputStr << "; " << std::to_string(stageRuns) << " run(s)";
1081 outputStr << ")\n";
1082 // Print
1083 std::cout << outputStr.str();
1084 }
1085 32061 }
1086
1087 } // namespace spice::compiler
1088