GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 85.1% 537 / 3 / 634
Functions: 90.0% 45 / 0 / 50
Branches: 47.8% 567 / 12 / 1198

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