GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 86.6% 596 / 3 / 691
Functions: 93.0% 53 / 0 / 57
Branches: 49.4% 644 / 12 / 1316

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