GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 87.2% 502 / 3 / 579
Functions: 93.3% 42 / 0 / 45
Branches: 49.1% 522 / 12 / 1076

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