GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 86.2% 551 / 3 / 642
Functions: 92.2% 47 / 0 / 51
Branches: 48.7% 583 / 12 / 1210

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