GCC Code Coverage Report


src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
904 / 1007
89.8%
Branches:
383 / 472, 26 excl
81.1%
Functions:
76 / 79
96.2%

source-file.spice
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 // Std imports
4 import "std/text/print";
5 import "std/text/stringstream";
6 import "std/text/format";
7 import "std/type/any";
8 import "std/type/type-conversion";
9 import "std/data/pair";
10 import "std/data/map";
11 import "std/data/unordered-map";
12 import "std/data/unordered-set";
13 import "std/data/vector";
14 import "std/io/file";
15 import "std/io/filepath";
16 import "std/text/json/json-value";
17 import "std/text/json/json-serializer";
18 import "std/time/timer";
19 import "std/os/thread-pool";
20 import "std/bindings/llvm/llvm" as llvm;
21
22 // Own imports
23 import "@/exception/fatal-error";
24 import "@/driver";
25 import "@/global/global-resource-manager";
26 import "@/global/runtime-module-manager";
27 import "@/ast/ast-nodes";
28 import "@/exception/compiler-error";
29 import "@/exception/error-manager";
30 import "@/exception/semantic-error";
31 import "@/model/function" as model;
32 import "@/util/compiler-warning";
33 import "@/util/system-util";
34 import "@/util/compile-progress";
35 import "@/symboltablebuilder/symbol-table-entry";
36 import "@/symboltablebuilder/scope";
37 import "@/symboltablebuilder/qual-type";
38 import "@/symboltablebuilder/super-type";
39 import "@/model/struct";
40 import "@/symboltablebuilder/type" as symboltable; // Aliased, because Type clashes with llvm::Type
41 import "@/lexer/lexer";
42 import "@/parser/parser";
43 import "@/importcollector/import-collector";
44 import "@/symboltablebuilder/symbol-table-builder";
45 import "@/typechecker/type-checker";
46 import "@/typechecker/post-type-checking-verifier";
47 import "@/linter/lint-finding";
48 import "@/linter/lint-pass";
49 import "@/irgenerator/ir-generator";
50 import "@/iroptimizer/ir-optimizer";
51 import "@/linker/bitcode-linker";
52 import "@/objectemitter/abstract-object-emitter";
53 import "@/util/concurrency";
54 import "@/objectemitter/llvm-object-emitter";
55 import "@/objectemitter/tpde-object-emitter";
56 import "@/typechecker/function-manager";
57 import "@/typechecker/struct-manager";
58 import "@/typechecker/interface-manager";
59 import "@/typechecker/union-manager";
60 import "@/visualizer/ast-visualizer";
61 import "@/visualizer/dependency-graph-visualizer";
62 import "@/util/test-util";
63
64 type CompileStageType enum {
65 NONE,
66 LEXER_AND_PARSER,
67 AST_VISUALIZER,
68 IMPORT_COLLECTOR,
69 SYMBOL_TABLE_BUILDER,
70 TYPE_CHECKER_PRE,
71 TYPE_CHECKER_POST,
72 DEP_GRAPH_VISUALIZER,
73 IR_GENERATOR,
74 IR_OPTIMIZER,
75 OBJECT_EMITTER,
76 FINISHED
77 }
78
79 type CompileStageIOType enum {
80 IO_CODE,
81 IO_AST,
82 IO_IR,
83 IO_OBJECT_FILE
84 }
85
86 3971 type TimerOutput struct {
87 3971 unsigned long lexerAndParser = 0l
88 3971 unsigned long astVisualizer = 0l
89 3971 unsigned long importCollector = 0l
90 3971 unsigned long symbolTableBuilder = 0l
91 3971 unsigned long typeCheckerPre = 0l
92 3971 unsigned long typeCheckerPost = 0l
93 3971 unsigned long depGraphVisualizer = 0l
94 3971 unsigned long irGenerator = 0l
95 3971 unsigned long irOptimizer = 0l
96 3971 unsigned long objectEmitter = 0l
97 }
98
99 /**
100 * Collects the output of the compiler for debugging
101 */
102 7493 public type CompilerOutput struct {
103 String astString
104 String symbolTableString
105 String depGraphString
106 String irString
107 String irOptString
108 String asmString
109 String typesString
110 String cacheStats
111 public Vector<CompilerWarning> warnings
112 public Vector<LintFinding> lintFindings
113 TimerOutput times
114 }
115
116 public type NameRegistryEntry struct {
117 public unsigned long typeId // Set for structs, interfaces and enums
118 public SymbolTableEntry* targetEntry
119 public Scope* targetScope
120 public SymbolTableEntry* importEntry = nil<SymbolTableEntry*>
121 }
122
123 /**
124 * Get the code generation opt level for the given opt level
125 *
126 * @param optLevel Opt level
127 * @return Code generation opt level
128 */
129 3971 f<llvm::LLVMCodeGenOptLevel> getCodeGenOptLevel(OptLevel optLevel) {
130
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 87 times.
✓ Branch 2 taken 3874 times.
✓ Branch 3 taken 9 times.
3971 switch optLevel {
131 3874 case OptLevel::O0: { return llvm::LLVMCodeGenOptLevel::None; }
132 1 case OptLevel::O1: { return llvm::LLVMCodeGenOptLevel::Less; }
133 9 case OptLevel::O3: { return llvm::LLVMCodeGenOptLevel::Aggressive; }
134 87 default: { return llvm::LLVMCodeGenOptLevel::Default; } // O2, Os, Oz
135 }
136 ✗ }
137
138 /**
139 * Represents a single source file
140 */
141 3522 public type SourceFile struct {
142 // Public fields
143 public String name
144 public String fileName
145 public FilePath filePath
146 public String fileDir
147 3971 public bool isStdFile = false
148 3971 public bool isMainFile = true
149 3971 public bool alwaysKeepSymbolsOnNameCollision = false
150 3971 public bool ignoreWarnings = false
151 3971 public bool explicitErrorHandling = false
152 3971 public bool errorReturnTracing = false
153 3971 public bool reVisitRequested = true
154 3971 public CompileStageType previousStage = CompileStageType::NONE
155 public CompilerOutput compilerOutput
156 public SourceFile* parent
157 3971 public ASTEntryNode* ast = nil<ASTEntryNode*>
158 3971 public Scope* globalScope = nil<Scope*> // Owned by the scope pool of the resource manager
159 public llvm::LLVMContext context
160 public llvm::IRBuilder builder
161 public llvm::TargetMachine targetMachine
162 public llvm::Module llvmModule
163 public FilePath objectFilePath
164 public String objectFileStem // Name of the object file without extension, unique within the compilation
165 public Map<String, SourceFile*> dependencies // Has to be an ordered map to keep the compilation order deterministic
166 public Vector<const SourceFile*> dependants
167 public Map<String, NameRegistryEntry> exportedNameRegistry
168 // Unqualified names that were dropped from the name registry, because multiple imports expose them. Maps each name to the
169 // import entries exposing it, so that a failing lookup can point the user to the colliding imports
170 public Map<String, Vector<const SymbolTableEntry*>> ambiguousNameRegistry
171 public Vector<const model::Function*> testFunctions
172 public GlobalResourceManager& resourceManager // Public, because Scope and SymbolTable need access
173 // Private fields
174 CliOptions& cliOptions
175 UnorderedMap<long, llvm::Type> typeToLLVMTypeMapping // Maps the address of each type to its LLVM type
176 // Number of name registry entries per target entry address, to answer isStructKnownByNameRegistry without scanning the registry
177 UnorderedMap<long, unsigned long> nameRegistryTargetEntryCounts
178 3971 public unsigned int importedRuntimeModules = 0 // Public, because the RuntimeModuleManager needs access
179 3971 unsigned short totalTypeCheckerRuns = 0s
180 // Cycle-safety guards: the pipeline drivers recurse over a dependency graph that may contain cycles (circular
181 // imports). These flags prevent re-entering a file that is already being processed in the same stage.
182 3971 bool registriesMerged = false
183 3971 bool typeCheckerPreRunning = false
184 3971 bool typeCheckerPostRunning = false
185 3971 bool backEndStarted = false
186 3971 bool warningsCollected = false
187 3971 bool lintFindingsCollected = false
188 }
189
190 3971 public p SourceFile.ctor(GlobalResourceManager &resourceManager, SourceFile* parent, const String& name, const FilePath& filePath, bool isStdFile) {
191 // Copy data
192 3971 this.name = name;
193
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3971 times.
3971 this.filePath = filePath;
194 3971 this.isStdFile = isStdFile;
195 3971 this.parent = parent;
196
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3967 times.
3971 this.builder = llvm::IRBuilder(resourceManager.cliOptions.useLTO ? resourceManager.ltoContext : this.context);
197 3971 this.resourceManager = resourceManager;
198 3971 this.cliOptions = resourceManager.cliOptions;
199
200 // Deduce fileName and fileDir
201 3971 const FilePath path = FilePath(filePath);
202 3971 this.fileName = path.getFileName();
203 3971 this.fileDir = path.getParentDir();
204 3971 this.objectFileStem = resourceManager.reserveObjectFileStem(path);
205
206 // Discard value names if not required
207 3971 this.context.setDiscardValueNames(!this.cliOptions.namesForIRValues);
208
209 // Search after selected target
210 3971 string error = nil<string>;
211 3971 const llvm::Target target = llvm::getTargetFromTriple(this.cliOptions.targetTriple, &error);
212
1/2
✓ Branch 0 taken 3971 times.
✗ Branch 1 not taken.
3971 if error != nil<string> {
213 ✗ const String messageSuffix = "Selected target was not found: " + String(error);
214 ✗ const CompilerError compilerError = CompilerError(CompilerErrorType::TARGET_NOT_AVAILABLE, messageSuffix.getRaw());
215 ✗ abortWithError(compilerError.getMessage());
216 ✗ }
217
218 // Create target machine
219 3971 const string cpuName = resourceManager.cpuName;
220 3971 const string features = resourceManager.cpuFeatures;
221 3971 const llvm::Triple& targetTriple = this.cliOptions.targetTriple;
222 3971 const llvm::LLVMRelocMode relocMode = llvm::LLVMRelocMode::PIC;
223 // The LLVM C API cannot change the codegen opt level of a target machine, so it is set right away here.
224 3971 const llvm::LLVMCodeGenOptLevel codeGenOptLevel = getCodeGenOptLevel(this.cliOptions.optLevel);
225 3971 this.targetMachine = target.createTargetMachine(targetTriple, cpuName, features, codeGenOptLevel, relocMode, llvm::LLVMCodeModel::Default);
226 // Emit the assembly code with comments. This only affects the assembly code, not the object files
227 3971 this.targetMachine.setAsmVerbosity(true);
228 3971 }
229
230 5037 public p SourceFile.runParser() {
231 // Check if this stage has already been done
232
2/2
✓ Branch 0 taken 1086 times.
✓ Branch 1 taken 3951 times.
5037 if this.previousStage >= CompileStageType::LEXER_AND_PARSER {
233 1086 return;
234 }
235
236
237 3951 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.lexerAndParser);
238 3951 timer.start();
239
240 // The source file must exist. Raise the same error as the lexer stage does when it cannot be read.
241
2/2
✓ Branch 0 taken 3950 times.
✓ Branch 1 taken 1 time.
3951 if !this.filePath.exists() {
242 1 StringStream msg;
243 1 msg << "Source file at path '" << this.filePath.toString() << "' does not exist.";
244 1 const String messageSuffix = msg.str();
245 1 const CompilerError error = CompilerError(CompilerErrorType::SOURCE_FILE_NOT_FOUND, messageSuffix.getRaw());
246 1 abortWithError(error.getMessage());
247 1 }
248
249 // Parse the source file
250 3951 Lexer lexer = Lexer(this.filePath);
251 3951 Parser parser = Parser(this.resourceManager, this, lexer);
252 3951 this.ast = parser.parse();
253 3951 assert this.ast != nil<ASTEntryNode*>;
254
255 // Create the global scope
256 3951 this.globalScope = this.resourceManager.createScope(nil<Scope*>, this, ScopeType::GLOBAL, &this.ast.node.codeLoc);
257
258 3951 this.previousStage = CompileStageType::LEXER_AND_PARSER;
259 3951 timer.stop();
260 3951 this.printStatusMessage("Parser", CompileStageIOType::IO_CODE, CompileStageIOType::IO_AST, this.compilerOutput.times.lexerAndParser);
261 3951 }
262
263 5037 public p SourceFile.runASTVisualizer() {
264 // Only execute if enabled
265
4/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 5030 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 5011 times.
5037 if !this.cliOptions.dump.dumpAST && !this.cliOptions.testMode {
266 26 return;
267 }
268 // Check if this stage has already been done
269
2/2
✓ Branch 0 taken 1079 times.
✓ Branch 1 taken 3932 times.
5011 if this.previousStage >= CompileStageType::AST_VISUALIZER {
270 1079 return;
271 }
272
273 3932 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.astVisualizer);
274 3932 timer.start();
275
276 // Generate dot code for this source file
277 3932 StringStream dotCode;
278 3932 this.visualizerPreamble(dotCode);
279 3932 ASTVisualizer astVisualizer = ASTVisualizer();
280 3932 Any astDotCode = astVisualizer.visit(this.ast);
281 3932 dotCode << " " << astDotCode.get<String>() << "}";
282
283 // Dump the serialized AST string and the SVG file
284
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3932 times.
3932 this.compilerOutput.astString = dotCode.str();
285
286
2/2
✓ Branch 0 taken 3925 times.
✓ Branch 1 taken 7 times.
3932 if this.cliOptions.dump.dumpAST {
287 7 this.visualizerOutput("AST", this.compilerOutput.astString);
288 7 }
289
290 3932 this.previousStage = CompileStageType::AST_VISUALIZER;
291 3932 timer.stop();
292 3932 this.printStatusMessage("AST Visualizer", CompileStageIOType::IO_AST, CompileStageIOType::IO_AST, this.compilerOutput.times.astVisualizer);
293 3932 }
294
295 5029 public p SourceFile.runImportCollector() {
296 // Skip if this stage has already been done
297
2/2
✓ Branch 0 taken 1086 times.
✓ Branch 1 taken 3943 times.
5029 if this.previousStage >= CompileStageType::IMPORT_COLLECTOR {
298 1086 return;
299 }
300
301 3943 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.importCollector);
302 3943 timer.start();
303
304 // Collect the imports for this source file
305 3943 ImportCollector importCollector = ImportCollector(this.resourceManager, this);
306 3943 importCollector.visit(this.ast);
307 3943 this.checkForHardError();
308
309 3943 this.previousStage = CompileStageType::IMPORT_COLLECTOR;
310
311 // Run first part of pipeline for the imported source file
312
2/2
✓ Branch 0 taken 2508 times.
✓ Branch 1 taken 3943 times.
6451 foreach dyn dependency : this.dependencies {
313 2508 SourceFile* sourceFile = dependency.getSecond();
314 2508 sourceFile.runFrontEnd();
315 2508 }
316
317 3943 timer.stop();
318 3943 this.printStatusMessage("Import Collector", CompileStageIOType::IO_AST, CompileStageIOType::IO_AST, this.compilerOutput.times.importCollector);
319 3943 }
320
321 5029 public p SourceFile.runSymbolTableBuilder() {
322 // Skip if this stage has already been done
323
2/2
✓ Branch 0 taken 1086 times.
✓ Branch 1 taken 3943 times.
5029 if this.previousStage >= CompileStageType::SYMBOL_TABLE_BUILDER {
324 1086 return;
325 }
326
327 3943 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.symbolTableBuilder);
328 3943 timer.start();
329
330 // Build the symbol table. The dependencies' exported name registries are merged in afterwards, in a separate pass
331 // (mergeNameRegistriesRecursive), once every reachable file has built its own registry. This deferral is what makes
332 // circular imports work: with a cycle, a dependency's registry is not fully populated yet at this point.
333 3943 SymbolTableBuilder symbolTableBuilder = SymbolTableBuilder(this.resourceManager, this);
334 3943 symbolTableBuilder.visit(this.ast);
335 3943 this.checkForHardError();
336
337 3943 this.previousStage = CompileStageType::SYMBOL_TABLE_BUILDER;
338 3943 this.resourceManager.compileProgress.finishFrontEnd();
339 3943 timer.stop();
340 3943 this.printStatusMessage("Symbol Table Builder", CompileStageIOType::IO_AST, CompileStageIOType::IO_AST, this.compilerOutput.times.symbolTableBuilder);
341 3943 }
342
343 5000 public p SourceFile.runTypeCheckerPre() { // Public for the RuntimeModuleManager
344 // The typeCheckerPreRunning guard breaks the recursion on a circular import: a cyclic back-edge returns immediately
345 // instead of recursing forever.
346
4/4
✓ Branch 0 taken 1072 times.
✓ Branch 1 taken 3928 times.
✓ Branch 2 taken 1084 times.
✓ Branch 3 taken 3916 times.
5000 if this.previousStage >= CompileStageType::TYPE_CHECKER_PRE || this.typeCheckerPreRunning {
347 1084 return;
348 }
349 3916 this.typeCheckerPreRunning = true;
350
351 // Type-check all dependencies first
352
2/2
✓ Branch 0 taken 2501 times.
✓ Branch 1 taken 3916 times.
6417 foreach dyn dependency : this.dependencies {
353 2501 SourceFile* sourceFile = dependency.getSecond();
354 2501 sourceFile.runTypeCheckerPre();
355 2501 }
356
357 3916 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.typeCheckerPre);
358 3916 timer.start();
359
360 // Then type-check the current file
361 3916 TypeChecker typeChecker = TypeChecker(this.resourceManager, this, TypeCheckerMode::TC_MODE_PRE);
362 3916 typeChecker.visit(this.ast);
363 3916 this.checkForHardError();
364
365 3916 this.previousStage = CompileStageType::TYPE_CHECKER_PRE;
366 3916 this.resourceManager.compileProgress.finishTypeCheckerPre();
367 3916 this.typeCheckerPreRunning = false;
368 3916 timer.stop();
369 3916 this.printStatusMessage("Type Checker Pre", CompileStageIOType::IO_AST, CompileStageIOType::IO_AST, this.compilerOutput.times.typeCheckerPre);
370 3916 }
371
372 12150 p SourceFile.runTypeCheckerPost() {
373 // Re-entrancy guard: within an import cycle, a dependency's post-run recurses back into this file's post-run. The
374 // in-flight fixpoint loop below already revisits this file, so the nested call must be a no-op to avoid unbounded
375 // mutual recursion. Convergence is driven by the reVisitRequested flags propagating across the cycle.
376
2/2
✓ Branch 0 taken 395 times.
✓ Branch 1 taken 11755 times.
12150 if this.typeCheckerPostRunning {
377 395 return;
378 }
379
380 // Nothing to do if this file already finished the post stage and nobody asked for another visit since. This is the
381 // common case, because every visit of a file runs the post stage of all its dependencies again.
382
4/4
✓ Branch 0 taken 5942 times.
✓ Branch 1 taken 5813 times.
✓ Branch 2 taken 5813 times.
✓ Branch 3 taken 5942 times.
11755 if !this.reVisitRequested && this.previousStage >= CompileStageType::TYPE_CHECKER_POST {
383 5813 return;
384 }
385
386 // Skip if not all dependants finished type checking yet
387
2/2
✓ Branch 0 taken 2246 times.
✓ Branch 1 taken 3696 times.
5942 if !this.haveAllDependantsBeenTypeChecked() {
388 2246 return;
389 }
390
391 3696 this.typeCheckerPostRunning = true;
392
393 3696 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.typeCheckerPost);
394 3696 timer.start();
395
396 // Start type-checking loop. The type-checker can request a re-execution. The max number of type-checker runs is limited
397 3696 TypeChecker typeChecker = TypeChecker(this.resourceManager, this, TypeCheckerMode::TC_MODE_POST);
398 3696 unsigned short typeCheckerRuns = 0s;
399
2/2
✓ Branch 0 taken 6384 times.
✓ Branch 1 taken 3696 times.
10080 while this.reVisitRequested {
400 6384 typeCheckerRuns++;
401 6384 this.totalTypeCheckerRuns++;
402 6384 this.reVisitRequested = false;
403
404 // Type-check the current file first. Multiple times, if requested
405 6384 timer.resume();
406 6384 typeChecker.visit(this.ast);
407 6384 this.checkForHardError();
408 6384 timer.pause();
409
410 // Then type-check all dependencies
411
2/2
✓ Branch 0 taken 11382 times.
✓ Branch 1 taken 6384 times.
17766 foreach dyn dependency : this.dependencies {
412 11382 SourceFile* sourceFile = dependency.getSecond();
413 11382 sourceFile.runTypeCheckerPost();
414 11382 }
415 6384 }
416
417 3696 this.typeCheckerPostRunning = false;
418
419 // Measure the remaining work of this stage as well. The time spent in the dependencies above is not part of it
420 3696 timer.resume();
421 3696 this.checkForSoftErrors();
422
423 // Check if all dyn variables were type-inferred successfully
424 3696 this.globalScope.ensureSuccessfulTypeInference();
425
426 // In debug builds, verify that the TypeChecker fully annotated the AST
427 if __get_build_var<bool>("spice.is_debug") {
428 3696 this.runPostTypeCheckingVerifier();
429 }
430
431 // A revisit request can run the post stage again later on, but it only counts as progress the first time
432
2/2
✓ Branch 0 taken 161 times.
✓ Branch 1 taken 3535 times.
3696 if this.previousStage < CompileStageType::TYPE_CHECKER_POST {
433 3535 this.resourceManager.compileProgress.finishTypeCheckerPost();
434 3535 }
435 3696 this.previousStage = CompileStageType::TYPE_CHECKER_POST;
436 3696 timer.stop();
437 3696 this.printStatusMessage("Type Checker Post", CompileStageIOType::IO_AST, CompileStageIOType::IO_AST, this.compilerOutput.times.typeCheckerPost, typeCheckerRuns);
438
439 // Save the JSON version in the compiler output
440
4/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 3680 times.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 3677 times.
3696 if this.cliOptions.dump.dumpSymbolTable || this.cliOptions.testMode {
441 3677 JsonValue* symbolTableJSON = this.globalScope.getSymbolTableJSON();
442 3677 JsonSerializer serializer = JsonSerializer(/*pretty=*/true);
443 3677 this.compilerOutput.symbolTableString = serializer.serialize(symbolTableJSON);
444 3677 deleteJsonValue(symbolTableJSON);
445 3677 }
446
447 // Dump symbol table
448
2/2
✓ Branch 0 taken 3680 times.
✓ Branch 1 taken 16 times.
3696 if this.cliOptions.dump.dumpSymbolTable {
449 16 this.dumpOutput(this.compilerOutput.symbolTableString, String("Symbol Table"), String("symbol-table.json"));
450 16 }
451 3696 }
452
453 3696 p SourceFile.runPostTypeCheckingVerifier() {
454 3696 PostTypeCheckingVerifier verifier = PostTypeCheckingVerifier(this.resourceManager, this);
455 3696 verifier.verify(this.ast);
456 3696 }
457
458 763 public p SourceFile.runDependencyGraphVisualizer() {
459 // Only execute if enabled
460
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 761 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 760 times.
763 if !this.cliOptions.dump.dumpDependencyGraph && !this.cliOptions.testMode { return; }
461
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 760 times.
760 if this.previousStage >= CompileStageType::DEP_GRAPH_VISUALIZER { return; }
462
463 760 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.depGraphVisualizer);
464 760 timer.start();
465
466 // Generate dot code for the full dependency graph
467 760 StringStream dotCode;
468 760 this.visualizerPreamble(dotCode);
469 760 DependencyGraphVisualizer depGraphVisualizer = DependencyGraphVisualizer(this.resourceManager, this);
470 760 depGraphVisualizer.getDependencyGraph(dotCode);
471 760 dotCode << "}";
472
473
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 760 times.
760 this.compilerOutput.depGraphString = dotCode.str();
474
475
2/2
✓ Branch 0 taken 758 times.
✓ Branch 1 taken 2 times.
760 if this.cliOptions.dump.dumpDependencyGraph {
476 2 this.visualizerOutput("Dependency Graph", this.compilerOutput.depGraphString);
477 2 }
478
479 760 this.previousStage = CompileStageType::DEP_GRAPH_VISUALIZER;
480 760 timer.stop();
481 760 this.printStatusMessage("Dependency Graph Visualizer", CompileStageIOType::IO_AST, CompileStageIOType::IO_AST, this.compilerOutput.times.depGraphVisualizer);
482 760 }
483
484 3499 public p SourceFile.runIRGenerator() {
485 // Skip if this stage has already been done
486
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3499 times.
3499 if this.previousStage >= CompileStageType::IR_GENERATOR {
487 ✗ return;
488 }
489
490 3499 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.irGenerator);
491 3499 timer.start();
492 3499 this.resourceManager.compileProgress.startBackEndStage(BackEndStage::IR_GENERATOR);
493
494 // Create the LLVM module for this source file
495
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3495 times.
3499 llvm::LLVMContext& llvmContext = this.cliOptions.useLTO ? this.resourceManager.ltoContext : this.context;
496 3499 this.llvmModule = llvm::Module(this.fileName.getRaw(), llvmContext);
497
498 // Generate this source file
499 3499 IRGenerator irGenerator = IRGenerator(this.resourceManager, this);
500 3499 irGenerator.visit(this.ast);
501
502 // Save the ir string in the compiler output
503
3/4
✓ Branch 0 taken 573 times.
✓ Branch 1 taken 2926 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3499 times.
3499 if this.cliOptions.dump.dumpIR || this.cliOptions.testMode {
504 3499 this.compilerOutput.irString = getIRString(&this.llvmModule, this.cliOptions);
505 3499 }
506
507 // Dump unoptimized IR code
508
2/2
✓ Branch 0 taken 2926 times.
✓ Branch 1 taken 573 times.
3499 if this.cliOptions.dump.dumpIR {
509 573 this.dumpOutput(this.compilerOutput.irString, String("Unoptimized IR Code"), String("ir-code.ll"));
510 573 }
511
512 3499 this.previousStage = CompileStageType::IR_GENERATOR;
513 3499 this.resourceManager.compileProgress.finishBackEndStage(BackEndStage::IR_GENERATOR);
514 3499 timer.stop();
515 3499 this.printStatusMessage("IR Generator", CompileStageIOType::IO_AST, CompileStageIOType::IO_IR, this.compilerOutput.times.irGenerator);
516 3499 }
517
518 3495 public p SourceFile.runDefaultIROptimizer() {
519 3495 assert !this.cliOptions.useLTO;
520
521 // Skip if this stage has already been done
522
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 3495 times.
✓ Branch 2 taken 3495 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3495 times.
3495 if this.previousStage > CompileStageType::IR_OPTIMIZER || (this.previousStage == CompileStageType::IR_OPTIMIZER && !this.cliOptions.testMode) {
523 ✗ return;
524 }
525
526 3495 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.irOptimizer);
527 3495 timer.start();
528 3495 this.resourceManager.compileProgress.startBackEndStage(BackEndStage::IR_OPTIMIZER);
529
530 // Optimize this source file
531 3495 IROptimizer irOptimizer = IROptimizer(this.resourceManager, this);
532 3495 irOptimizer.prepare();
533 3495 irOptimizer.optimizeDefault();
534
535 // Save the optimized ir string in the compiler output
536
3/4
✓ Branch 0 taken 571 times.
✓ Branch 1 taken 2924 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3495 times.
3495 if this.cliOptions.dump.dumpIR || this.cliOptions.testMode {
537 3495 this.compilerOutput.irOptString = getIRString(&this.llvmModule, this.cliOptions);
538 3495 }
539
540 // Dump optimized IR code
541
2/2
✓ Branch 0 taken 2924 times.
✓ Branch 1 taken 571 times.
3495 if this.cliOptions.dump.dumpIR {
542 571 const String optLevelStr = toString(this.cliOptions.optLevel);
543 571 this.dumpOutput(this.compilerOutput.irOptString, String("Optimized IR Code"), "ir-code-O" + optLevelStr + ".ll");
544 571 }
545
546 3495 this.previousStage = CompileStageType::IR_OPTIMIZER;
547 3495 this.resourceManager.compileProgress.finishBackEndStage(BackEndStage::IR_OPTIMIZER);
548 3495 timer.stop();
549 3495 this.printStatusMessage("IR Optimizer", CompileStageIOType::IO_IR, CompileStageIOType::IO_IR, this.compilerOutput.times.irOptimizer);
550 3495 }
551
552 4 public p SourceFile.runPreLinkIROptimizer() {
553 4 assert this.cliOptions.useLTO;
554
555 // Skip if this stage has already been done
556
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if this.previousStage >= CompileStageType::IR_OPTIMIZER {
557 ✗ return;
558 }
559
560 4 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.irOptimizer);
561 4 timer.start();
562 4 this.resourceManager.compileProgress.startBackEndStage(BackEndStage::IR_OPTIMIZER);
563
564 // Optimize this source file
565 4 IROptimizer irOptimizer = IROptimizer(this.resourceManager, this);
566 4 irOptimizer.prepare();
567 4 irOptimizer.optimizePreLink();
568
569 // Save the optimized ir string in the compiler output
570
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if this.cliOptions.dump.dumpIR || this.cliOptions.testMode {
571 4 this.compilerOutput.irOptString = getIRString(&this.llvmModule, this.cliOptions);
572 4 }
573
574 // Dump optimized IR code
575
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if this.cliOptions.dump.dumpIR {
576 2 this.dumpOutput(this.compilerOutput.irOptString, String("Optimized IR Code (pre-link)"), String("ir-code-lto-pre-link.ll"));
577 2 }
578
579 4 this.resourceManager.compileProgress.finishBackEndStage(BackEndStage::IR_OPTIMIZER);
580 4 timer.pause();
581 4 }
582
583 4 public p SourceFile.runBitcodeLinker() {
584 4 assert this.cliOptions.useLTO;
585
586 // Skip if this is not the main source file
587
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if !this.isMainFile {
588 2 return;
589 }
590
591 // Skip if this stage has already been done
592
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if this.previousStage >= CompileStageType::IR_OPTIMIZER {
593 ✗ return;
594 }
595
596 2 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.irOptimizer);
597 2 timer.resume();
598 // The bitcode linker and the post-link optimizer form one stage for the whole program
599 2 this.resourceManager.compileProgress.startBackEndStage(BackEndStage::LTO_IR_OPTIMIZER);
600
601 // Link all source files together
602 2 BitcodeLinker linker = BitcodeLinker(this.resourceManager);
603 2 linker.link();
604
605 2 timer.pause();
606 2 }
607
608 4 public p SourceFile.runPostLinkIROptimizer() {
609 4 assert this.cliOptions.useLTO;
610
611 // Skip if this is not the main source file
612
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if !this.isMainFile {
613 2 return;
614 }
615
616 // Skip if this stage has already been done
617
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if this.previousStage >= CompileStageType::IR_OPTIMIZER {
618 ✗ return;
619 }
620
621 2 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.irOptimizer);
622 2 timer.resume();
623
624 // Optimize LTO module
625 2 IROptimizer irOptimizer = IROptimizer(this.resourceManager, this);
626 2 irOptimizer.prepare();
627 2 irOptimizer.optimizePostLink();
628
629 // Save the optimized ir string in the compiler output
630
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if this.cliOptions.dump.dumpIR || this.cliOptions.testMode {
631 2 this.compilerOutput.irOptString = getIRString(&this.resourceManager.ltoModule, this.cliOptions);
632 2 }
633
634 // Dump optimized IR code
635
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if this.cliOptions.dump.dumpIR {
636 1 this.dumpOutput(this.compilerOutput.irOptString, String("Optimized IR Code (post-Link)"), String("ir-code-lto-post-link.ll"));
637 1 }
638
639 2 this.previousStage = CompileStageType::IR_OPTIMIZER;
640 2 this.resourceManager.compileProgress.finishBackEndStage(BackEndStage::LTO_IR_OPTIMIZER);
641 2 timer.stop();
642 2 this.printStatusMessage("IR Optimizer", CompileStageIOType::IO_IR, CompileStageIOType::IO_IR, this.compilerOutput.times.irOptimizer);
643 2 }
644
645 3499 public p SourceFile.runObjectEmitter() {
646 // Skip if this stage has already been done
647
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3499 times.
3499 if this.previousStage >= CompileStageType::OBJECT_EMITTER {
648 ✗ return;
649 }
650
651 // Skip if LTO is enabled and this is not the main source file
652
4/4
✓ Branch 0 taken 3495 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3497 times.
3499 if this.cliOptions.useLTO && !this.isMainFile {
653 2 return;
654 }
655
656 3497 Timer timer = Timer(TimerMode::MILLIS, &this.compilerOutput.times.objectEmitter);
657 3497 timer.start();
658 // With LTO, the main source file emits the object file for the whole program
659
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3495 times.
3497 const BackEndStage progressStage = this.cliOptions.useLTO ? BackEndStage::LTO_OBJECT_EMITTER : BackEndStage::OBJECT_EMITTER;
660 3497 this.resourceManager.compileProgress.startBackEndStage(progressStage);
661
662 // The code generation opt level matches the opt level, the IR was generated and optimized with. The LLVM C API cannot
663 // change it afterwards, so the target machine is created with it in the constructor.
664
665 // Deduce an object file path
666 3497 this.objectFilePath = this.getObjectFilePath();
667
668 // Pick a concrete emitter based on the selected backend. The driver only accepts the TPDE backend, if the compiler is backed
669 // by TPDE
670 3497 LLVMObjectEmitter llvmObjectEmitter = LLVMObjectEmitter(this.resourceManager, this);
671
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3495 times.
3497 TPDEObjectEmitter tpdeObjectEmitter = TPDEObjectEmitter(this.cliOptions.useLTO ? &this.resourceManager.ltoModule : &this.llvmModule);
672 3497 IAbstractObjectEmitter* objectEmitter = &llvmObjectEmitter;
673
2/2
✓ Branch 0 taken 3495 times.
✓ Branch 1 taken 2 times.
3497 if this.cliOptions.backend == Backend::TPDE {
674 2 objectEmitter = &tpdeObjectEmitter;
675 2 }
676
677 // Emit object for this source file
678 3497 objectEmitter.emit(this.objectFilePath);
679
680 // Save assembly string in the compiler output (TPDE emits a placeholder note)
681
5/6
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 3385 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3385 times.
✓ Branch 4 taken 112 times.
✓ Branch 5 taken 3385 times.
3497 if this.cliOptions.isNativeTarget && (this.cliOptions.dump.dumpAssembly || this.cliOptions.testMode) {
682 3385 objectEmitter.getASMString(this.compilerOutput.asmString);
683 3385 }
684
685 // Dump assembly code
686
1/2
✓ Branch 0 taken 3497 times.
✗ Branch 1 not taken.
3497 if this.cliOptions.dump.dumpAssembly {
687 ✗ this.dumpOutput(this.compilerOutput.asmString, String("Assembly code"), String("assembly-code.s"));
688 ✗ }
689
690 // The object file is registered with the linker in concludeCompilation and not here, because this stage may run on a
691 // worker thread of the parallel back end and the linker input order has to stay deterministic.
692
693 3497 this.previousStage = CompileStageType::OBJECT_EMITTER;
694 3497 this.resourceManager.compileProgress.finishBackEndStage(progressStage);
695 3497 timer.stop();
696 3497 this.printStatusMessage("Object Emitter", CompileStageIOType::IO_IR, CompileStageIOType::IO_OBJECT_FILE, this.compilerOutput.times.objectEmitter);
697 3497 }
698
699 3499 public p SourceFile.concludeCompilation() {
700 // Skip if this stage has already been done
701
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3499 times.
3499 if this.previousStage >= CompileStageType::FINISHED {
702 ✗ return;
703 }
704
705 // Add the emitted object file to the linker objects. This happens here and not in runObjectEmitter, because
706 // concludeCompilation is always driven serially and in dependency order, while the object emitter may run on a worker
707 // thread of the parallel back end.
708
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3497 times.
3499 if !this.objectFilePath.isEmpty() {
709 3497 this.resourceManager.linker.addEmittedObjectFile(this.objectFilePath);
710 3497 }
711
712 // Save type registry as string in the compiler output
713
6/6
✓ Branch 0 taken 2739 times.
✓ Branch 1 taken 760 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 754 times.
✓ Branch 4 taken 2739 times.
✓ Branch 5 taken 760 times.
3499 if this.isMainFile && (this.cliOptions.dump.dumpTypes || this.cliOptions.testMode) {
714 760 this.compilerOutput.typesString = this.resourceManager.typeRegistry.dump();
715 760 }
716
717 // Dump type registry
718
4/4
✓ Branch 0 taken 2739 times.
✓ Branch 1 taken 760 times.
✓ Branch 2 taken 3493 times.
✓ Branch 3 taken 6 times.
3499 if this.isMainFile && this.cliOptions.dump.dumpTypes {
719 6 this.dumpOutput(this.compilerOutput.typesString, String("Type Registry"), String("type-registry.out"));
720 6 }
721
722 // Save cache statistics as string in the compiler output
723
6/6
✓ Branch 0 taken 2739 times.
✓ Branch 1 taken 760 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 759 times.
✓ Branch 4 taken 2739 times.
✓ Branch 5 taken 760 times.
3499 if this.isMainFile && (this.cliOptions.dump.dumpCacheStats || this.cliOptions.testMode) {
724 760 this.dumpCacheStats();
725 760 }
726
727 // Dump lookup cache statistics
728
4/4
✓ Branch 0 taken 2739 times.
✓ Branch 1 taken 760 times.
✓ Branch 2 taken 3498 times.
✓ Branch 3 taken 1 time.
3499 if this.isMainFile && this.cliOptions.dump.dumpCacheStats {
729 1 this.dumpOutput(this.compilerOutput.cacheStats, String("Cache Statistics"), String("cache-stats.out"));
730 1 }
731
732
1/2
✓ Branch 0 taken 3499 times.
✗ Branch 1 not taken.
3499 if this.cliOptions.printDebugOutput {
733 ✗ println("Finished compiling " + this.fileName);
734 ✗ }
735
736 3499 this.previousStage = CompileStageType::FINISHED;
737 3499 }
738
739 5007 public p SourceFile.runFrontEnd() {
740 // The front end of the main source file covers the front ends of all its (transitive) dependencies. A circular import
741 // re-enters the front end of the main source file, so only measure the outermost call, which starts with its parser
742
2/2
✓ Branch 0 taken 3954 times.
✓ Branch 1 taken 1053 times.
5007 const bool measureWallTime = this.isMainFile && this.previousStage == CompileStageType::NONE;
743
2/2
✓ Branch 0 taken 3955 times.
✓ Branch 1 taken 1052 times.
5007 if measureWallTime {
744 1052 this.resourceManager.frontEndTimer.start();
745 1052 }
746
747 5007 this.runParser();
748
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5007 times.
5007 if this.resourceManager.abortCompilation { return; }
749 5007 this.runASTVisualizer();
750
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5007 times.
5007 if this.resourceManager.abortCompilation { return; }
751 5007 this.runImportCollector();
752
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5007 times.
5007 if this.resourceManager.abortCompilation { return; }
753 5007 this.runSymbolTableBuilder();
754
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5007 times.
5007 if this.resourceManager.abortCompilation { return; }
755
756
2/2
✓ Branch 0 taken 3998 times.
✓ Branch 1 taken 1009 times.
5007 if measureWallTime {
757 1009 this.resourceManager.frontEndTimer.stop();
758 1009 }
759 5007 }
760
761 1255 public p SourceFile.runMiddleEnd() {
762
2/2
✓ Branch 0 taken 246 times.
✓ Branch 1 taken 1009 times.
1255 if this.isMainFile {
763 1009 this.resourceManager.middleEndTimer.start();
764 1009 }
765
766 // Merge the exported name registries of all (transitive) dependencies into the respective importing files. This is
767 // the deferred tail of the front-end: it must run after every reachable file has built its own registry, which is
768 // why it cannot live inside the per-file front-end recursion (a circular import would otherwise merge a dependency
769 // whose registry is not populated yet). runMiddleEnd is the first stage that is only ever invoked at the top level.
770 1255 this.mergeNameRegistriesRecursive();
771
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 763 times.
1255 if this.resourceManager.abortCompilation { return; }
772 // From here on, struct manifestations may be substantiated, and each of them gets its compiler-generated default
773 // members decided right at that point (see TypeChecker.createImplicitDefaultMembers). Once the middle end is done,
774 // every manifestation exists and was decided on, so the back end must not create any more of them.
775 763 DefaultMemberCreationSection _;
776 // We need two runs here due to generics.
777 // The first run to determine all concrete function/struct/interface substantiations
778 763 this.runTypeCheckerPre(); // Visit the dependency tree from bottom to top
779
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 763 times.
763 if this.resourceManager.abortCompilation { return; }
780 // The second run to ensure, also generic scopes are type-checked properly
781 763 this.runTypeCheckerPost(); // Visit the dependency tree from top to bottom in topological order
782
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 763 times.
763 if this.resourceManager.abortCompilation { return; }
783 // The per-file convergence loop inside runTypeCheckerPost is scoped to its own call stack: a cross-file revisit
784 // request that lands on a file whose loop already unwound (e.g. a recursive generic dtor chain that closes back
785 // through a runtime module which was itself gated behind another, not-yet-checked importer) is otherwise dropped,
786 // leaving a fully-substantiated manifestation whose body was never type-checked. Sweep every source file in the
787 // program for a straggling revisit request and drive it to convergence directly, repeating until none are left.
788 763 bool anySourceFileRevisitPending = true;
789
2/2
✓ Branch 0 taken 768 times.
✓ Branch 1 taken 763 times.
1531 while anySourceFileRevisitPending {
790 768 anySourceFileRevisitPending = false;
791 // Snapshot the current files before driving any of them: runTypeCheckerPost() below can itself trigger a
792 // freshly-discovered runtime import (SourceFile.requestRuntimeModule -> GlobalResourceManager.createSourceFile),
793 // which inserts into resourceManager.sourceFiles, so a stable list of pointers is collected first. It is taken in
794 // creation order, so that the revisits happen in a deterministic order, independent of the hash function of the map.
795 768 Vector<SourceFile*> sourceFilesSnapshot;
796
2/2
✓ Branch 0 taken 3574 times.
✓ Branch 1 taken 768 times.
4342 foreach SourceFile* createdSourceFile : this.resourceManager.sourceFilesInCreationOrder {
797 3574 sourceFilesSnapshot.pushBack(createdSourceFile);
798 3574 }
799
2/2
✓ Branch 0 taken 3574 times.
✓ Branch 1 taken 768 times.
4342 foreach SourceFile* sourceFile : sourceFilesSnapshot {
800
2/2
✓ Branch 0 taken 3572 times.
✓ Branch 1 taken 2 times.
3574 if sourceFile.reVisitRequested {
801 2 sourceFile.runTypeCheckerPost();
802 2 anySourceFileRevisitPending = true;
803 2 }
804 3574 }
805 // A source file created mid-sweep (see above) still needs its own pass; it starts out with reVisitRequested
806 // true, so re-looping picks it up via the snapshot taken on the next iteration.
807
1/2
✓ Branch 0 taken 768 times.
✗ Branch 1 not taken.
768 if this.resourceManager.sourceFiles.getSize() > sourceFilesSnapshot.getSize() {
808 ✗ anySourceFileRevisitPending = true;
809 ✗ }
810 768 }
811
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 763 times.
763 if this.resourceManager.abortCompilation { return; }
812 // Check that no two definitions of the program are exported under the same linker symbol name
813 763 this.checkForExportedSymbolCollisions();
814
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 763 times.
763 if this.resourceManager.abortCompilation { return; }
815 // Visualize dependency graph
816 763 this.runDependencyGraphVisualizer();
817
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 763 times.
763 if this.resourceManager.abortCompilation { return; }
818
819
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 763 times.
763 if this.isMainFile {
820 763 this.resourceManager.middleEndTimer.stop();
821 763 }
822 763 }
823
824 6533 p SourceFile.collectBackEndSourceFiles(Vector<SourceFile*>& backEndSourceFiles) {
825 // Guard against collecting a file that already went through the back end. Circular imports form a cycle in the
826 // dependency graph, so the deps-first recursion below would otherwise loop forever.
827
2/2
✓ Branch 0 taken 3034 times.
✓ Branch 1 taken 3499 times.
6533 if this.backEndStarted {
828 3034 return;
829 }
830 3499 this.backEndStarted = true;
831
832 // Collect all dependencies first, so that they end up in front of this file in the resulting list
833
2/2
✓ Branch 0 taken 5773 times.
✓ Branch 1 taken 3499 times.
9272 foreach dyn dependency : this.dependencies {
834 5773 SourceFile* sourceFile = dependency.getSecond();
835 5773 sourceFile.collectBackEndSourceFiles(backEndSourceFiles);
836 5773 }
837
838 3499 backEndSourceFiles.pushBack(this);
839 3499 }
840
841 3499 p SourceFile.runBackEndForThisFile() {
842 3499 this.runIRGenerator();
843
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3499 times.
3499 if this.resourceManager.abortCompilation { return; }
844
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3495 times.
3499 if this.cliOptions.useLTO {
845 4 this.runPreLinkIROptimizer();
846
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if this.resourceManager.abortCompilation { return; }
847 4 this.runBitcodeLinker();
848
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if this.resourceManager.abortCompilation { return; }
849 4 this.runPostLinkIROptimizer();
850
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if this.resourceManager.abortCompilation { return; }
851 4 } else {
852 3495 this.runDefaultIROptimizer();
853
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3495 times.
3495 if this.resourceManager.abortCompilation { return; }
854 3495 }
855 3499 this.runObjectEmitter();
856 3499 this.releaseBackEndResources();
857 3499 }
858
859 /**
860 * Release the LLVM resources of this source file, as soon as its object file was emitted. Releasing them early bounds the peak memory usage to the resources of the source files, whose back end
861 * runs at the moment. Nothing uses them afterwards, because the back end of each source file only works on its own resources.
862 * With LTO, the modules are linked into the LTO module and the main source file emits the object file, so they are kept.
863 */
864 3499 p SourceFile.releaseBackEndResources() {
865
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3495 times.
3499 if this.cliOptions.useLTO {
866 4 return;
867 }
868 // The LLVM types live in the context, so forget them before. The builder and the module have to be disposed before the
869 // context as well
870 3495 this.typeToLLVMTypeMapping.clear();
871 3495 this.builder.dispose();
872 3495 this.llvmModule.dispose();
873 3495 this.targetMachine.dispose();
874 3495 this.context.dispose();
875 3495 }
876
877 760 public p SourceFile.runBackEnd() {
878
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 760 times.
760 if this.isMainFile {
879 760 this.resourceManager.backEndTimer.start();
880 760 }
881
882 // Flatten the dependency graph into the order the back end used to recurse in: every file comes after all of its
883 // dependencies, and files that already ran their back end are skipped.
884 760 Vector<SourceFile*> backEndSourceFiles;
885 760 this.collectBackEndSourceFiles(backEndSourceFiles);
886
887 // Nothing to do if this file and all of its dependencies already went through the back end
888
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 760 times.
760 if backEndSourceFiles.isEmpty() {
889 ✗ return;
890 }
891
892 // Unlike the front end and the middle end, the back end has no cross-file data dependencies: every source file owns
893 // its own LLVMContext, IRBuilder, TargetMachine and llvm::Module, and references to symbols of other files are emitted
894 // as declarations into the local module. So the per-file pipelines can simply be spread over a worker pool.
895 // Exceptions, in which the back end stays serial:
896 // - LTO, because all source files share the LTO context and module of the GlobalResourceManager
897 // - dump modes, because they write to the console/output dir and their ordering is part of the user-visible output
898
3/4
✓ Branch 0 taken 527 times.
✓ Branch 1 taken 233 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 527 times.
760 const bool dumpRequested = this.cliOptions.dump.dumpIR || this.cliOptions.dump.dumpAssembly || this.cliOptions.dump.dumpObjectFiles;
899 760 const unsigned long compileJobCount = this.resourceManager.getCompileJobCount();
900
2/2
✓ Branch 0 taken 366 times.
✓ Branch 1 taken 394 times.
760 const unsigned long jobCount = compileJobCount < backEndSourceFiles.getSize() ? compileJobCount : backEndSourceFiles.getSize();
901
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 759 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
760 const bool runParallel = jobCount > 1ul && !this.cliOptions.useLTO && !dumpRequested;
902
903
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 759 times.
760 if runParallel {
904 1 ThreadPool threadPool = ThreadPool(cast<unsigned short>(jobCount));
905
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 1 time.
17 foreach SourceFile* sourceFile : backEndSourceFiles {
906 32 threadPool.enqueue(p() [[async]] {
907 16 sourceFile.runBackEndForThisFile();
908 16 });
909 16 }
910 // Enter the parallel section before the worker threads start and leave it only after all of them were joined. A
911 // failing source file aborts the compilation on its worker thread (see abortWithError), which ends the process.
912 1 ParallelSection _;
913 1 threadPool.start();
914 1 threadPool.join();
915 1 } else {
916
2/2
✓ Branch 0 taken 3483 times.
✓ Branch 1 taken 759 times.
4242 foreach SourceFile* sourceFile : backEndSourceFiles {
917 3483 sourceFile.runBackEndForThisFile();
918
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3483 times.
3483 if this.resourceManager.abortCompilation { return; }
919 3483 }
920 759 }
921
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 760 times.
760 if this.resourceManager.abortCompilation { return; }
922
923 // Conclude the compilation of all source files. This registers the emitted object files with the linker, which has to
924 // happen serially and in a fixed order to stay deterministic.
925
2/2
✓ Branch 0 taken 3499 times.
✓ Branch 1 taken 760 times.
4259 foreach SourceFile* sourceFile : backEndSourceFiles {
926 3499 sourceFile.concludeCompilation();
927 3499 }
928
929
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 760 times.
760 if this.isMainFile {
930 760 this.resourceManager.backEndTimer.stop();
931
1/2
✓ Branch 0 taken 760 times.
✗ Branch 1 not taken.
760 if this.cliOptions.printDebugOutput {
932 ✗ this.dumpCompilationStats();
933 ✗ }
934 760 }
935 760 }
936
937 6033 public p SourceFile.addDependency(SourceFile* sourceFile, const String& dependencyName) {
938 // Circular imports are explicitly supported, so cycles are not rejected here. Source files are deduplicated by path
939 // in GlobalResourceManager::createSourceFile, so a cyclic import resolves to the same SourceFile instance and the
940 // pipeline drivers guard against re-entering a file that is already in progress.
941
942 // Add the dependency. Do not demote the compilation root (parent == nullptr) to a non-main file.
943
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 6032 times.
6033 if sourceFile.parent != nil<SourceFile*> {
944 6032 sourceFile.isMainFile = false;
945 6032 }
946
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6033 times.
6033 if !this.dependencies.contains(dependencyName) {
947 6033 this.dependencies.upsert(dependencyName, sourceFile);
948 6033 }
949
950 // Add the dependant
951 6033 sourceFile.dependants.pushBack(this);
952 6033 }
953
954 // Shortcuts used by the compiler passes
955 35729 public f<Scope*> SourceFile.getGlobalScope() {
956 35729 return this.globalScope;
957 }
958
959 4056 public f<const FilePath&> SourceFile.getFilePath() {
960 4056 return this.filePath;
961 }
962
963 3942 public f<bool> SourceFile.isMainSourceFile() {
964 3942 return this.isMainFile;
965 }
966
967 // True if the exported name registry already maps the name to the given symbol table entry
968 45761 public f<bool> SourceFile.hasNameRegistryTarget(const String& symbolName, const SymbolTableEntry* target) {
969 45761 const NameRegistryEntry* existing = this.getNameRegistryEntry(symbolName);
970
2/2
✓ Branch 0 taken 35258 times.
✓ Branch 1 taken 10503 times.
45761 return existing != nil<NameRegistryEntry*> && existing.targetEntry == target;
971 }
972
973 1491 public p SourceFile.setAlwaysKeepSymbolsOnNameCollision(bool value) { this.alwaysKeepSymbolsOnNameCollision = value; }
974 4 public p SourceFile.setIgnoreWarnings(bool value) { this.ignoreWarnings = value; }
975 4 public p SourceFile.setExplicitErrorHandling(bool value) { this.explicitErrorHandling = value; }
976 5 public p SourceFile.setErrorReturnTracing(bool value) { this.errorReturnTracing = value; }
977
978 430538 public f<bool> SourceFile.imports(const SourceFile* sourceFile) {
979
2/2
✓ Branch 0 taken 1117582 times.
✓ Branch 1 taken 389351 times.
1506933 foreach dyn dependency : this.dependencies {
980
2/2
✓ Branch 0 taken 41187 times.
✓ Branch 1 taken 1076395 times.
1117582 if dependency.getSecond() == sourceFile {
981 41187 return true;
982 }
983 1076395 }
984 389351 return false;
985 }
986
987 21530 #[ignoreUnusedReturnValue]
988 21530 public f<SourceFile*> SourceFile.requestRuntimeModule(RuntimeModule runtimeModule) {
989 // Check if the module was already imported
990
2/2
✓ Branch 0 taken 18008 times.
✓ Branch 1 taken 3522 times.
21530 if this.isRuntimeModuleAvailable(runtimeModule) {
991 18008 return this.resourceManager.runtimeModuleManager.getModule(runtimeModule);
992 }
993 3522 return this.resourceManager.runtimeModuleManager.requestModule(this, runtimeModule);
994 }
995
996 22924 public f<bool> SourceFile.isRuntimeModuleAvailable(RuntimeModule runtimeModule) {
997 22924 return (this.importedRuntimeModules & runtimeModule) != 0;
998 }
999
1000 166845 public p SourceFile.addNameRegistryEntry(const String& symbolName, unsigned long typeId, SymbolTableEntry* entry, Scope* scope, bool keepNewOnCollision, SymbolTableEntry* importEntry) {
1001
4/4
✓ Branch 0 taken 129973 times.
✓ Branch 1 taken 36872 times.
✓ Branch 2 taken 166561 times.
✓ Branch 3 taken 284 times.
166845 if keepNewOnCollision || !this.exportedNameRegistry.contains(symbolName) { // Overwrite potential existing entry
1002
2/2
✓ Branch 0 taken 149798 times.
✓ Branch 1 taken 16763 times.
166561 if this.exportedNameRegistry.contains(symbolName) {
1003 16763 const SymbolTableEntry* oldTargetEntry = this.exportedNameRegistry.get(symbolName).targetEntry;
1004 16763 this.untrackNameRegistryTargetEntry(oldTargetEntry);
1005 16763 this.exportedNameRegistry.remove(symbolName);
1006 16763 }
1007 166561 this.exportedNameRegistry.upsert(symbolName, NameRegistryEntry{typeId, entry, scope, importEntry});
1008 166561 this.trackNameRegistryTargetEntry(entry);
1009
4/4
✓ Branch 0 taken 36588 times.
✓ Branch 1 taken 129973 times.
✓ Branch 2 taken 166429 times.
✓ Branch 3 taken 132 times.
166561 if keepNewOnCollision && this.ambiguousNameRegistry.contains(symbolName) {
1010 132 this.ambiguousNameRegistry.remove(symbolName);
1011 132 }
1012 166561 } else { // Name collision => we must remove the existing entry. Remember the colliding imports to report the ambiguity later
1013
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 255 times.
284 if !this.ambiguousNameRegistry.contains(symbolName) {
1014 255 this.ambiguousNameRegistry.upsert(symbolName, Vector<const SymbolTableEntry*>());
1015 255 }
1016 284 Vector<const SymbolTableEntry*>& collidingImports = this.ambiguousNameRegistry.get(symbolName);
1017 284 collidingImports.pushBack(this.exportedNameRegistry.get(symbolName).importEntry);
1018 284 collidingImports.pushBack(importEntry);
1019 284 const SymbolTableEntry* oldTargetEntry = this.exportedNameRegistry.get(symbolName).targetEntry;
1020 284 this.untrackNameRegistryTargetEntry(oldTargetEntry);
1021 284 this.exportedNameRegistry.remove(symbolName);
1022 284 }
1023 166845 }
1024
1025 662101 public f<const NameRegistryEntry*> SourceFile.getNameRegistryEntry(const String& symbolName) {
1026
2/2
✓ Branch 0 taken 297474 times.
✓ Branch 1 taken 364627 times.
662101 if !this.exportedNameRegistry.contains(symbolName) {
1027 297474 return nil<NameRegistryEntry*>;
1028 }
1029
1030 // Resolve registry entry for the given name
1031 364627 const NameRegistryEntry* entry = &this.exportedNameRegistry.get(symbolName);
1032
1033 // Mark the import entry as used
1034
2/2
✓ Branch 0 taken 344030 times.
✓ Branch 1 taken 20597 times.
364627 if entry.importEntry != nil<SymbolTableEntry*> {
1035 20597 entry.importEntry.used = true;
1036 20597 }
1037
1038 364627 return entry;
1039 }
1040
1041 19 public f<bool> SourceFile.isAmbiguousName(const String& symbolName) {
1042 19 return this.ambiguousNameRegistry.contains(symbolName);
1043 }
1044
1045 /**
1046 * Check if the name registry of this source file knows the struct manifestation with the given body scope
1047 *
1048 * @param bodyScope Body scope of the struct manifestation
1049 * @param structName Name of the struct
1050 * @return Known or not
1051 */
1052 95999 public f<bool> SourceFile.isStructKnownByNameRegistry(Scope* bodyScope, const String& structName) {
1053
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 95999 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 95999 times.
95999 if bodyScope == nil<Scope*> || bodyScope.parent == nil<Scope*> {
1054 ✗ return false;
1055 }
1056
1057 // All manifestations of a struct live in the scope the struct is defined in, next to the struct's symbol table entry.
1058 // The name registry entry of a struct points to this symbol table entry, so we can look it up instead of scanning the
1059 // whole name registry
1060 95999 const SymbolTableEntry* structEntry = bodyScope.parent.lookupStrict(structName);
1061
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 95999 times.
✓ Branch 2 taken 7126 times.
✓ Branch 3 taken 88873 times.
95999 if structEntry == nil<const SymbolTableEntry*> || !this.nameRegistryTargetEntryCounts.contains(getEntryAddress(structEntry)) {
1062 7126 return false;
1063 }
1064 88873 const QualType& structType = structEntry.getQualType();
1065
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88873 times.
88873 if !structType.isBase(SuperType::TY_STRUCT) {
1066 ✗ return false;
1067 }
1068
1069 // Check if the body scope belongs to one of the manifestations of the struct
1070 88873 ASTNode* declNode = structEntry.declNode;
1071
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88873 times.
88873 if declNode.kind != ASTNodeKind::STRUCT_DEF {
1072 ✗ return false;
1073 }
1074 88873 const ASTStructDefNode* structDefNode = downcast<ASTStructDefNode>(declNode);
1075
2/2
✓ Branch 0 taken 141276 times.
✓ Branch 1 taken 6 times.
141282 foreach Struct* manifestation : structDefNode.structManifestations {
1076
2/2
✓ Branch 0 taken 88867 times.
✓ Branch 1 taken 52409 times.
141276 if manifestation.scope == bodyScope {
1077 88867 return true;
1078 }
1079 52409 }
1080 6 return false;
1081 }
1082
1083 /**
1084 * Count a new name registry entry pointing to the given target entry
1085 *
1086 * @param targetEntry Target entry of the name registry entry
1087 */
1088 268126 p SourceFile.trackNameRegistryTargetEntry(const SymbolTableEntry* targetEntry) {
1089
1/2
✓ Branch 0 taken 268126 times.
✗ Branch 1 not taken.
268126 if targetEntry == nil<const SymbolTableEntry*> {
1090 ✗ return;
1091 }
1092 268126 const long entryAddress = getEntryAddress(targetEntry);
1093
2/2
✓ Branch 0 taken 102931 times.
✓ Branch 1 taken 165195 times.
268126 if this.nameRegistryTargetEntryCounts.contains(entryAddress) {
1094 102931 unsigned long& count = this.nameRegistryTargetEntryCounts.get(entryAddress);
1095 102931 count++;
1096 102931 } else {
1097 165195 this.nameRegistryTargetEntryCounts.upsert(entryAddress, 1ul);
1098 165195 }
1099 268126 }
1100
1101 /**
1102 * Uncount a removed name registry entry pointing to the given target entry
1103 *
1104 * @param targetEntry Target entry of the name registry entry
1105 */
1106 17047 p SourceFile.untrackNameRegistryTargetEntry(const SymbolTableEntry* targetEntry) {
1107
1/2
✓ Branch 0 taken 17047 times.
✗ Branch 1 not taken.
17047 if targetEntry == nil<const SymbolTableEntry*> {
1108 ✗ return;
1109 }
1110 17047 const long entryAddress = getEntryAddress(targetEntry);
1111 17047 assert this.nameRegistryTargetEntryCounts.contains(entryAddress);
1112 17047 unsigned long& count = this.nameRegistryTargetEntryCounts.get(entryAddress);
1113 17047 count--;
1114
2/2
✓ Branch 0 taken 5817 times.
✓ Branch 1 taken 11230 times.
17047 if count == 0ul {
1115 11230 this.nameRegistryTargetEntryCounts.remove(entryAddress);
1116 11230 }
1117 17047 }
1118
1119 /**
1120 * Get the address of the given symbol table entry, to use it as a map key
1121 *
1122 * @param entry Symbol table entry
1123 * @return Address of the entry
1124 */
1125 381172 f<long> getEntryAddress(const SymbolTableEntry* entry) {
1126 unsafe {
1127 381172 return cast<long>(entry);
1128 }
1129 }
1130
1131 /**
1132 * Build an error message for a name that is not available, because multiple imports expose it. The message lists the
1133 * colliding imports and suggests how to qualify the name.
1134 *
1135 * @param symbolName Ambiguous name
1136 * @return Error message
1137 */
1138 5 public f<String> SourceFile.getAmbiguousNameMessage(const String& symbolName) {
1139 5 assert this.isAmbiguousName(symbolName);
1140
1141 // Collect the distinct imports that expose the name. Runtime imports have no import entry and are skipped
1142 5 Vector<ASTImportDefNode*> importNodes;
1143 5 const Vector<const SymbolTableEntry*>& importEntries = this.ambiguousNameRegistry.get(symbolName);
1144
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5 times.
17 for unsigned long i = 0l; i < importEntries.getSize(); i++ {
1145 12 const SymbolTableEntry* importEntry = importEntries.get(i);
1146
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if importEntry == nil<const SymbolTableEntry*> {
1147 ✗ continue;
1148 }
1149 12 const ASTImportDefNode* importNode = downcast<ASTImportDefNode>(importEntry.declNode);
1150 12 bool alreadyCollected = false;
1151
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 12 times.
22 for unsigned long j = 0l; j < importNodes.getSize(); j++ {
1152
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if importNodes.get(j) == importNode {
1153 ✗ alreadyCollected = true;
1154 ✗ }
1155 10 }
1156
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if !alreadyCollected {
1157 12 importNodes.pushBack(importNode);
1158 12 }
1159 12 }
1160
1161 5 String msg = String("'") + symbolName + "' is exposed by ";
1162
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if importNodes.getSize() < 2l {
1163 ✗ msg += "multiple imports";
1164 ✗ } else {
1165
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
5 if importNodes.getSize() == 2l {
1166 4 msg += "both ";
1167 4 }
1168
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5 times.
17 for unsigned long i = 0l; i < importNodes.getSize(); i++ {
1169
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 7 times.
12 if i > 0l {
1170 7 msg += i == importNodes.getSize() - 1l ? " and " : ", ";
1171 7 }
1172 12 msg += String("'") + importNodes.get(i).importPath + "'";
1173 12 }
1174 5 }
1175
1176 // Suggest qualifying the name with the first import name, that can be written in source code
1177
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 time.
7 for unsigned long i = 0l; i < importNodes.getSize(); i++ {
1178 6 String& importName = importNodes.get(i).importName;
1179
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if this.isQualifiableImportName(importName) {
1180 4 msg += String(". Please qualify it, e.g. '") + importName + SCOPE_ACCESS_TOKEN + symbolName + "'";
1181 8 return msg;
1182 4 }
1183 2 }
1184 1 msg += String(". Please import one of them with an alias and qualify it, e.g. 'alias") + SCOPE_ACCESS_TOKEN + symbolName + "'";
1185 2 return msg;
1186 5 }
1187
1188 /**
1189 * Check if the given import name can be used to qualify a name in source code, i.e. if it is a valid identifier.
1190 *
1191 * @param importName Import name
1192 * @return Qualifiable or not
1193 */
1194 6 f<bool> SourceFile.isQualifiableImportName(String& importName) {
1195
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if importName.isEmpty() {
1196 ✗ return false;
1197 }
1198
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 4 times.
25 for unsigned long i = 0l; i < importName.getLength(); i++ {
1199 21 const char c = importName[i];
1200
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 15 times.
21 const bool isLowerLetter = c >= 'a' && c <= 'z';
1201
8/10
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 4 times.
✓ Branch 9 taken 2 times.
21 const bool isIdentifierChar = isLowerLetter || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
1202
7/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 19 times.
21 if !isIdentifierChar || (i == 0l && !isLowerLetter && c != '_') {
1203 2 return false;
1204 }
1205 19 }
1206 4 return true;
1207 }
1208
1209 /**
1210 * Abort the compilation if a hard error was raised. Hard errors are recorded (see ErrorManager), so each stage checks for them
1211 * afterwards.
1212 */
1213 18183 public p SourceFile.checkForHardError() {
1214 18183 const ErrorManager& errorManager = this.resourceManager.errorManager;
1215
2/2
✓ Branch 0 taken 18099 times.
✓ Branch 1 taken 84 times.
18183 if errorManager.hasHardError {
1216 84 abortWithError(errorManager.hardErrorMessage);
1217 84 }
1218 18183 }
1219
1220 /**
1221 * Check that no two definitions of the program are exported under the same linker symbol name. Non-public symbols get
1222 * internal linkage and therefore can never collide. Public ones get external linkage under a name that does not contain
1223 * the source file, so e.g. two public functions with the same signature in different source files would break the linking.
1224 * Every collision is reported as soft error.
1225 */
1226 766 p SourceFile.checkForExportedSymbolCollisions() {
1227 766 UnorderedMap<String, ASTNode*> exportedSymbols;
1228 // Spice cannot hash pointers, so the visited files are identified by their file path, which is unique per source file
1229 766 UnorderedSet<String> visitedSourceFiles;
1230 766 this.registerExportedSymbols(exportedSymbols, visitedSourceFiles);
1231 766 this.checkForSoftErrors();
1232 766 }
1233
1234 /**
1235 * Register the exported symbols of this source file and all its (transitive) dependencies in the given registry. This
1236 * covers the same source files as collectBackEndSourceFiles, so exactly the ones that end up in the linked program.
1237 *
1238 * @param exportedSymbols Registry, that maps each linker symbol name to the AST node of its definition
1239 * @param visitedSourceFiles File paths of the source files, that were already registered
1240 */
1241 6582 p SourceFile.registerExportedSymbols(UnorderedMap<String, ASTNode*>& exportedSymbols, UnorderedSet<String>& visitedSourceFiles) {
1242 // Guard against circular imports
1243 6582 const String filePathStr = String(this.filePath.toString());
1244
2/2
✓ Branch 0 taken 3058 times.
✓ Branch 1 taken 3524 times.
6582 if visitedSourceFiles.contains(filePathStr) {
1245 3058 return;
1246 }
1247 3524 visitedSourceFiles.insert(filePathStr);
1248
1249 // Register the symbols of all dependencies first, so that a collision is reported at the definition in the importing file
1250
2/2
✓ Branch 0 taken 5816 times.
✓ Branch 1 taken 3524 times.
9340 foreach dyn dependency : this.dependencies {
1251 5816 SourceFile* sourceFile = dependency.getSecond();
1252 5816 sourceFile.registerExportedSymbols(exportedSymbols, visitedSourceFiles);
1253 5816 }
1254
1255
2/2
✓ Branch 0 taken 60632 times.
✓ Branch 1 taken 3524 times.
64156 foreach IVisitable* topLevelDef : this.ast.topLevelDefs {
1256 60632 ASTNode* node = getASTNode(topLevelDef);
1257
2/2
✓ Branch 0 taken 4208 times.
✓ Branch 1 taken 56424 times.
60632 if node.kind == ASTNodeKind::GLOBAL_VAR_DEF {
1258 // Public globals are exported under their plain name
1259 4208 const ASTGlobalVarDefNode* globalVarDef = downcast<ASTGlobalVarDefNode>(node);
1260 4208 const QualType& globalVarType = globalVarDef.entry.getQualType();
1261
2/2
✓ Branch 0 taken 2151 times.
✓ Branch 1 taken 2057 times.
4208 if globalVarType.isPublic() {
1262 2151 this.registerExportedSymbol(exportedSymbols, globalVarDef.varName, globalVarDef.varName, node);
1263 2151 }
1264
4/4
✓ Branch 0 taken 29433 times.
✓ Branch 1 taken 26991 times.
✓ Branch 2 taken 12612 times.
✓ Branch 3 taken 43812 times.
60632 } else if node.kind == ASTNodeKind::FCT_DEF || node.kind == ASTNodeKind::PROC_DEF {
1265 43812 ASTFctDefBaseNode* fctDef = nil<ASTFctDefBaseNode*>;
1266
2/2
✓ Branch 0 taken 26991 times.
✓ Branch 1 taken 16821 times.
43812 if node.kind == ASTNodeKind::FCT_DEF {
1267 26991 ASTFctDefNode* fctDefNode = downcastMut<ASTFctDefNode>(node);
1268 26991 fctDef = &fctDefNode.node;
1269 26991 } else {
1270 16821 ASTProcDefNode* procDefNode = downcastMut<ASTProcDefNode>(node);
1271 16821 fctDef = &procDefNode.node;
1272 16821 }
1273 // Each emitted manifestation of an exported function is exported under its mangled name
1274
2/2
✓ Branch 0 taken 58473 times.
✓ Branch 1 taken 43812 times.
102285 foreach const model::Function* manifestation : fctDef.manifestations {
1275
4/4
✓ Branch 0 taken 15112 times.
✓ Branch 1 taken 43361 times.
✓ Branch 2 taken 20319 times.
✓ Branch 3 taken 38154 times.
58473 if manifestation.isFullySubstantiated() && isExportedFunction(manifestation, this.cliOptions.generateTestMain) {
1276 38154 this.registerExportedSymbol(exportedSymbols, manifestation.getMangledName(), manifestation.getSignature(), node);
1277 38154 }
1278 58473 }
1279 56424 }
1280 60632 }
1281 6582 }
1282
1283 /**
1284 * Register a single exported symbol and report a soft error if another definition is already exported under its name
1285 *
1286 * @param exportedSymbols Registry, that maps each linker symbol name to the AST node of its definition
1287 * @param linkerName Name of the symbol for the linker
1288 * @param displayName Name of the symbol for the error message
1289 * @param declNode Definition of the symbol
1290 */
1291 40305 p SourceFile.registerExportedSymbol(UnorderedMap<String, ASTNode*>& exportedSymbols, const String& linkerName, const String& displayName, ASTNode* declNode) {
1292
2/2
✓ Branch 0 taken 40300 times.
✓ Branch 1 taken 5 times.
40305 if !exportedSymbols.contains(linkerName) {
1293 40300 exportedSymbols.upsert(linkerName, declNode);
1294 40300 return;
1295 }
1296 5 const ASTNode* existingDeclNode = exportedSymbols.get(linkerName);
1297
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if existingDeclNode == declNode {
1298 ✗ return;
1299 }
1300 5 const String message = "Exported symbol '" + displayName + "' collides with the exported symbol defined at " + existingDeclNode.codeLoc.toPrettyString() + ", because both have the linker name '" + linkerName + "'";
1301 5 this.resourceManager.errorManager.addSoftError(declNode, SemanticErrorType::EXPORTED_SYMBOL_COLLISION, message);
1302 5 }
1303
1304 4641 public p SourceFile.checkForSoftErrors() {
1305 // Check if there are any soft errors and if so, print them
1306
2/2
✓ Branch 0 taken 4460 times.
✓ Branch 1 taken 181 times.
4641 if !this.resourceManager.errorManager.softErrors.isEmpty() {
1307 181 String errorMessage = String("There are unresolved errors. Please fix them and recompile.");
1308
2/2
✓ Branch 0 taken 262 times.
✓ Branch 1 taken 181 times.
443 foreach const SoftError& error : this.resourceManager.errorManager.softErrors {
1309 262 errorMessage += "\n\n";
1310 262 errorMessage += error.message;
1311 262 }
1312 181 const CompilerError compilerError = CompilerError(CompilerErrorType::UNRESOLVED_SOFT_ERRORS, errorMessage.getRaw());
1313 181 abortWithError(compilerError.getMessage());
1314 181 }
1315 4641 }
1316
1317 152 public f<bool> SourceFile.isLibraryOutput() {
1318
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 151 times.
152 return this.cliOptions.outputContainer == OutputContainer::STATIC_LIBRARY || this.cliOptions.outputContainer == OutputContainer::SHARED_LIBRARY;
1319 }
1320
1321 /**
1322 * Get the path of the object file, that the object emitter emits for this source file. With the object file output container,
1323 * the object of the main source file is the output file, so it goes to the output path.
1324 *
1325 * @return Object file path
1326 */
1327 3501 public f<FilePath> SourceFile.getObjectFilePath() {
1328
4/4
✓ Branch 0 taken 2739 times.
✓ Branch 1 taken 762 times.
✓ Branch 2 taken 299 times.
✓ Branch 3 taken 3202 times.
3501 if this.isMainFile && this.cliOptions.outputContainer == OutputContainer::OBJECT_FILE {
1329 299 return this.cliOptions.outputPath;
1330 }
1331 3202 const string objectFileExt = getOutputFileExtension(this.cliOptions, OutputContainer::OBJECT_FILE);
1332 6404 return this.cliOptions.outputDir / (this.objectFileStem + "." + objectFileExt);
1333 3202 }
1334
1335 857 public p SourceFile.collectAndPrintWarnings() {
1336 // Skip if already visited. This keeps circular imports from recursing infinitely, since the dependency graph may contain
1337 // cycles.
1338
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 837 times.
857 if this.warningsCollected {
1339 20 return;
1340 }
1341 837 this.warningsCollected = true;
1342 // Print warnings for all dependencies
1343
2/2
✓ Branch 0 taken 756 times.
✓ Branch 1 taken 837 times.
1593 foreach dyn dependency : this.dependencies {
1344 756 SourceFile* sourceFile = dependency.getSecond();
1345
2/2
✓ Branch 0 taken 659 times.
✓ Branch 1 taken 97 times.
756 if !sourceFile.isStdFile {
1346 97 sourceFile.collectAndPrintWarnings();
1347 97 }
1348 756 }
1349 // Collect warnings for this file
1350
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 837 times.
837 if !this.ignoreWarnings {
1351 837 this.globalScope.collectWarnings(this.compilerOutput.warnings);
1352 837 }
1353 // Print warnings for this file. Generic code is checked once per manifestation, so the same warning may have been added
1354 // multiple times. Print each of them only once
1355 837 UnorderedSet<String> printedWarnings;
1356
2/2
✓ Branch 0 taken 253 times.
✓ Branch 1 taken 837 times.
1090 foreach dyn warning : this.compilerOutput.warnings {
1357
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 242 times.
253 if !printedWarnings.contains(warning.warningMessage) {
1358 242 printedWarnings.insert(warning.warningMessage);
1359 242 warning.print();
1360 242 }
1361 253 }
1362 837 }
1363
1364 3 public p SourceFile.collectAndPrintLintFindings() {
1365 // Skip if already visited. This keeps circular imports from recursing infinitely, since the dependency graph may contain
1366 // cycles.
1367
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if this.lintFindingsCollected {
1368 ✗ return;
1369 }
1370 3 this.lintFindingsCollected = true;
1371 // Collect lint findings for all dependencies
1372
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 foreach dyn dependency : this.dependencies {
1373 1 SourceFile* sourceFile = dependency.getSecond();
1374
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1 if !sourceFile.isStdFile {
1375 ✗ sourceFile.collectAndPrintLintFindings();
1376 ✗ }
1377 1 }
1378 // Collect lint findings for this file
1379
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if !this.ignoreWarnings {
1380 3 LintPass lintPass = LintPass(this.resourceManager, this);
1381 3 this.compilerOutput.lintFindings = lintPass.lint(this.ast);
1382 3 }
1383 // Print lint findings for this file
1384
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
11 foreach dyn finding : this.compilerOutput.lintFindings {
1385 8 finding.print();
1386 8 }
1387 3 }
1388
1389 ✗ public f<const SourceFile*> SourceFile.getRootSourceFile() {
1390
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ return this.isMainFile ? this : this.parent.getRootSourceFile();
1391 }
1392
1393 /**
1394 * Get the LLVM context, the LLVM entities of this source file live in
1395 *
1396 * @return LLVM context
1397 */
1398 40613 public f<llvm::LLVMContext&> SourceFile.getLLVMContext() {
1399
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 40605 times.
40613 return this.cliOptions.useLTO ? this.resourceManager.ltoContext : this.context;
1400 }
1401
1402 /**
1403 * Get the LLVM type of the given type. The LLVM types are cached, so that each type gets lowered only once (e.g. named struct
1404 * types would otherwise be created multiple times).
1405 *
1406 * @param rawType Type
1407 * @return LLVM type
1408 */
1409 1704919 public f<llvm::Type> SourceFile.getLLVMType(const symboltable::Type* rawType) {
1410 // Check if the type is already in the mapping
1411 1704919 long typeAddress = 0l;
1412 unsafe {
1413 1704919 typeAddress = cast<long>(rawType);
1414 }
1415
2/2
✓ Branch 0 taken 1667829 times.
✓ Branch 1 taken 37090 times.
1704919 if this.typeToLLVMTypeMapping.contains(typeAddress) {
1416 1667829 return this.typeToLLVMTypeMapping.get(typeAddress);
1417 }
1418
1419 // If not, generate the LLVM type
1420 37090 const llvm::Type llvmType = rawType.toLLVMType(this);
1421 37090 this.typeToLLVMTypeMapping.upsert(typeAddress, llvmType);
1422 37090 return llvmType;
1423 }
1424
1425 /**
1426 * Check if this source file is the given runtime module itself
1427 *
1428 * @param runtimeModule Runtime module
1429 * @return Is the runtime module or not
1430 */
1431 45267 public f<bool> SourceFile.isRT(RuntimeModule runtimeModule) {
1432 45267 const String topLevelName = String(getIdentifyingTopLevelName(runtimeModule));
1433
2/2
✓ Branch 0 taken 5849 times.
✓ Branch 1 taken 39418 times.
45267 if !this.exportedNameRegistry.contains(topLevelName) {
1434 5849 return false;
1435 }
1436 39418 const NameRegistryEntry& entry = this.exportedNameRegistry.get(topLevelName);
1437 78836 return entry.targetEntry.scope == this.globalScope;
1438 45267 }
1439
1440 // Check if this source file is the string / memory / RTTI / result runtime module itself
1441 4728 public inline f<bool> SourceFile.isStringRT() { return this.isRT(RuntimeModule::STRING_RT); }
1442 3951 public inline f<bool> SourceFile.isMemoryRT() { return this.isRT(RuntimeModule::MEMORY_RT); }
1443 2489 public inline f<bool> SourceFile.isRttiRT() { return this.isRT(RuntimeModule::RTTI_RT); }
1444 5 public inline f<bool> SourceFile.isResultRT() { return this.isRT(RuntimeModule::RESULT_RT); }
1445
1446 6253 f<bool> SourceFile.haveAllDependantsBeenTypeChecked() {
1447
2/2
✓ Branch 0 taken 9686 times.
✓ Branch 1 taken 4007 times.
13693 foreach const SourceFile* dependant : this.dependants {
1448
2/2
✓ Branch 0 taken 7430 times.
✓ Branch 1 taken 2256 times.
9686 if dependant.totalTypeCheckerRuns != 0s {
1449 7430 continue;
1450 }
1451 // Ignore dependants that are part of the same import cycle (i.e. this file transitively depends on them). They
1452 // cannot be type-checked before us either, so waiting on them would deadlock the whole cycle. Searching the import
1453 // graph is expensive, so this is only checked for dependants that were not type-checked yet.
1454
2/2
✓ Branch 0 taken 2246 times.
✓ Branch 1 taken 10 times.
2256 if !this.dependsOn(dependant) {
1455 2246 return false;
1456 }
1457 10 }
1458 4007 return true;
1459 }
1460
1461 6024 public p SourceFile.mergeNameRegistries(const SourceFile& importedSourceFile, const String& importName) { // Public for the RuntimeModuleManager
1462 // Retrieve import entry
1463 6024 SymbolTableEntry* importEntry = this.globalScope.lookupStrict(importName);
1464 6024 assert importEntry != nil<SymbolTableEntry*> || importName.startsWith("__"); // Runtime imports start with two underscores
1465
1466
2/2
✓ Branch 0 taken 193809 times.
✓ Branch 1 taken 6024 times.
199833 foreach const Pair<const String&, NameRegistryEntry&> item : importedSourceFile.exportedNameRegistry {
1467 193809 const String& originalName = item.getFirst();
1468 193809 const NameRegistryEntry& entry = item.getSecond();
1469 // Skip if we introduce a transitive dependency
1470
2/2
✓ Branch 0 taken 92244 times.
✓ Branch 1 taken 101565 times.
193809 if entry.targetScope.sourceFile.globalScope != importedSourceFile.globalScope {
1471 92244 continue;
1472 }
1473 // Add the fully qualified name
1474 101565 String newName = importName;
1475 101565 newName += SCOPE_ACCESS_TOKEN;
1476 101565 newName += originalName;
1477
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 101565 times.
101565 if !this.exportedNameRegistry.contains(newName) {
1478 101565 this.exportedNameRegistry.upsert(newName, NameRegistryEntry{entry.typeId, entry.targetEntry, entry.targetScope, importEntry});
1479 101565 this.trackNameRegistryTargetEntry(entry.targetEntry);
1480 101565 }
1481 // Add the shortened name, considering the name collision. A symbol defined in the importing file itself always
1482 // shadows imported symbols of the same name. Since this merge runs after the file built its own registry (so that
1483 // circular imports work), we must explicitly avoid letting an import overwrite or erase such an own symbol.
1484
2/2
✓ Branch 0 taken 8098 times.
✓ Branch 1 taken 93467 times.
101565 const NameRegistryEntry* existing = this.exportedNameRegistry.contains(originalName) ? &this.exportedNameRegistry.get(originalName) : nil<NameRegistryEntry*>;
1485
2/2
✓ Branch 0 taken 93467 times.
✓ Branch 1 taken 8098 times.
101565 const bool existingIsOwn = existing != nil<NameRegistryEntry*> && existing.targetScope.sourceFile.globalScope == this.globalScope;
1486
2/2
✓ Branch 0 taken 2281 times.
✓ Branch 1 taken 99284 times.
101565 if !existingIsOwn {
1487 99284 const bool keepOnCollision = importedSourceFile.alwaysKeepSymbolsOnNameCollision;
1488 99284 this.addNameRegistryEntry(originalName, entry.typeId, entry.targetEntry, entry.targetScope, keepOnCollision, importEntry);
1489 99284 }
1490 101565 }
1491 6024 }
1492
1493 /**
1494 * Recursively merge the exported name registries of all (transitive) dependencies into the respective importing source
1495 * files. Each file merges its direct dependencies' registries exactly once (guarded by registriesMerged), so this is
1496 * safe to call on overlapping subgraphs and on graphs that contain cycles (circular imports).
1497 *
1498 * Must only be called once every reachable file has built its own exported name registry (i.e. after the front-end).
1499 */
1500 5001 public p SourceFile.mergeNameRegistriesRecursive() {
1501
2/2
✓ Branch 0 taken 1084 times.
✓ Branch 1 taken 3917 times.
5001 if this.registriesMerged {
1502 1084 return;
1503 }
1504 3917 this.registriesMerged = true;
1505
1506 // Merge the direct dependencies' registries into this file. Their own registries are fully built by now, so the
1507 // order in which the reachable files are visited does not matter (even across import cycles).
1508
2/2
✓ Branch 0 taken 2502 times.
✓ Branch 1 taken 3917 times.
6419 foreach dyn dependency : this.dependencies {
1509 2502 const String& importName = dependency.getFirst();
1510 2502 const SourceFile* sourceFile = dependency.getSecond();
1511 2502 this.mergeNameRegistries(*sourceFile, importName);
1512 2502 }
1513
1514 // Recurse into the dependencies to cover the rest of the reachable graph
1515
2/2
✓ Branch 0 taken 2502 times.
✓ Branch 1 taken 3917 times.
6419 foreach dyn dependency : this.dependencies {
1516 2502 SourceFile* sourceFile = dependency.getSecond();
1517 2502 sourceFile.mergeNameRegistriesRecursive();
1518 2502 }
1519 3917 }
1520
1521 /**
1522 * Check whether this source file transitively depends on (imports) the given other source file.
1523 * Used to detect strongly connected components (import cycles) in the dependency graph.
1524 *
1525 * @param other Potential (transitive) dependency
1526 * @return true if this file reaches the other file by following dependency edges
1527 */
1528 2256 f<bool> SourceFile.dependsOn(const SourceFile* other) {
1529 // Spice cannot hash pointers, so the visited files are identified by their file path, which is unique per source file
1530 2256 UnorderedSet<String> visited;
1531 2256 Vector<const SourceFile*> worklist;
1532 2256 worklist.pushBack(this);
1533 2256 visited.insert(String(this.filePath.toString()));
1534
2/2
✓ Branch 0 taken 2246 times.
✓ Branch 1 taken 4912 times.
7158 while !worklist.isEmpty() {
1535 4912 const SourceFile* current = worklist.back();
1536 4912 worklist.popBack();
1537
2/2
✓ Branch 0 taken 3331 times.
✓ Branch 1 taken 4902 times.
8233 foreach dyn dep : current.dependencies {
1538 3331 const SourceFile* dependency = dep.getSecond();
1539
2/2
✓ Branch 0 taken 3321 times.
✓ Branch 1 taken 10 times.
3331 if dependency == other {
1540 10 return true;
1541 }
1542 3321 const String dependencyPath = String(dependency.filePath.toString());
1543
2/2
✓ Branch 0 taken 664 times.
✓ Branch 1 taken 2657 times.
3321 if !visited.contains(dependencyPath) {
1544 2657 visited.insert(dependencyPath);
1545 2657 worklist.pushBack(dependency);
1546 2657 }
1547 3321 }
1548 4902 }
1549 2246 return false;
1550 2256 }
1551
1552 760 p SourceFile.dumpCacheStats() {
1553 760 StringStream cacheStats;
1554 760 cacheStats << dumpFunctionLookupCacheStatistics() << "\n";
1555 760 cacheStats << dumpStructLookupCacheStatistics() << "\n";
1556 760 cacheStats << dumpInterfaceLookupCacheStatistics() << "\n";
1557 760 cacheStats << dumpUnionLookupCacheStatistics() << "\n";
1558
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 760 times.
760 this.compilerOutput.cacheStats = cacheStats.str();
1559 760 }
1560
1561 ✗ p SourceFile.dumpCompilationStats() {
1562 ✗ const unsigned long sourceFileCount = this.resourceManager.sourceFiles.getSize();
1563 ✗ const unsigned long totalLineCount = this.resourceManager.getTotalLineCount();
1564 ✗ const unsigned long totalTypeCount = this.resourceManager.typeRegistry.getTypeCount();
1565 ✗ const unsigned long allocatedBytes = this.resourceManager.astNodeAlloc.getTotalAllocatedSize();
1566 ✗ const unsigned long allocationCount = this.resourceManager.astNodeAlloc.getAllocationCount();
1567 ✗ const unsigned long frontEndDuration = this.resourceManager.frontEndTimer.getDurationInMillis();
1568 ✗ const unsigned long middleEndDuration = this.resourceManager.middleEndTimer.getDurationInMillis();
1569 ✗ const unsigned long backEndDuration = this.resourceManager.backEndTimer.getDurationInMillis();
1570 ✗ const unsigned long totalDuration = frontEndDuration + middleEndDuration + backEndDuration;
1571 ✗ print("\nSuccessfully compiled " + toString(sourceFileCount) + " source file(s)");
1572 ✗ print(" or " + toString(totalLineCount) + " lines in total.\n");
1573 ✗ print("Total number of blocks allocated via BlockAllocator: " + formatStorageSize(allocatedBytes));
1574 ✗ print(" in " + toString(allocationCount) + " allocations.\n");
1575 ✗ print("Total number of types: " + toString(totalTypeCount) + "\n");
1576 ✗ print("Total compile time: " + toString(totalDuration) + " ms (frontend: " + toString(frontEndDuration));
1577 ✗ print(" ms, middle end: " + toString(middleEndDuration) + " ms, backend: " + toString(backEndDuration) + " ms)\n");
1578 ✗ }
1579
1580 1170 p SourceFile.dumpOutput(const String& content, const String& caption, const String& fileSuffix) {
1581
1/2
✓ Branch 0 taken 1170 times.
✗ Branch 1 not taken.
1170 if this.cliOptions.dump.dumpToFiles {
1582 // Dump to file
1583 1170 const String dumpFileName = this.filePath.getBaseName() + "-" + fileSuffix;
1584 1170 FilePath dumpFilePath = this.cliOptions.outputDir / dumpFileName;
1585 1170 dumpFilePath.makeNative();
1586 1170 const Result<bool> writeResult = writeFile(dumpFilePath.toString(), content.getRaw());
1587
1/2
✓ Branch 0 taken 1170 times.
✗ Branch 1 not taken.
1170 if !writeResult.isOk() {
1588 ✗ const String messageSuffix = "Failed to open file: " + dumpFilePath.toString();
1589 ✗ const CompilerError error = CompilerError(CompilerErrorType::IO_ERROR, messageSuffix.getRaw());
1590 ✗ abortWithError(error.getMessage());
1591 ✗ }
1592 1170 } else {
1593 // Dump to console
1594 ✗ print("\n" + caption + ":\n" + content);
1595 ✗ }
1596
1597 // If the abort after dump is requested, set the abort compilation flag
1598
1/2
✓ Branch 0 taken 1170 times.
✗ Branch 1 not taken.
1170 if this.cliOptions.dump.abortAfterDump {
1599 // If this is an IR dump whilst having optimization enabled, we may not abort when dumping unoptimized IR,
1600 // because we also have to dump the optimized IR
1601
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if this.cliOptions.dump.dumpIR && fileSuffix == "ir-code.ll" {
1602 ✗ this.resourceManager.abortCompilation = this.cliOptions.optLevel == OptLevel::O0;
1603 ✗ } else {
1604 ✗ this.resourceManager.abortCompilation = true;
1605 ✗ }
1606 ✗ }
1607 1170 }
1608
1609 4692 p SourceFile.visualizerPreamble(StringStream& output) {
1610
2/2
✓ Branch 0 taken 1795 times.
✓ Branch 1 taken 2897 times.
4692 if this.isMainFile {
1611 1795 output << "digraph {\n rankdir=\"TB\";\n";
1612 1795 } else {
1613 2897 output << "subgraph {\n";
1614 2897 }
1615 4692 output << " label=\"" << this.filePath.toGenericString() << "\";\n";
1616 4692 }
1617
1618 9 p SourceFile.visualizerOutput(const string outputNameRaw, const String& output) {
1619
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if this.cliOptions.dump.dumpToFiles {
1620 // The dump file names are lower case
1621 ✗ const String outputName = toLower(String(outputNameRaw));
1622
1623 // Check if graphviz is installed
1624
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if !isGraphvizInstalled() {
1625 ✗ const CompilerError error = CompilerError(CompilerErrorType::IO_ERROR, "Please check if you have installed Graphviz and added it to the PATH variable");
1626 ✗ abortWithError(error.getMessage());
1627 ✗ }
1628
1629 // Write to a dot file
1630 ✗ this.dumpOutput(output, outputName, outputName + ".dot");
1631
1632 // Generate SVG. This only works if the dot code was dumped into a file
1633 ✗ print("\nGenerating SVG file ... ");
1634 ✗ const String dotFileName = this.filePath.getBaseName() + "-" + outputName + ".dot";
1635 ✗ FilePath dotFilePath = this.cliOptions.outputDir / dotFileName;
1636 ✗ FilePath svgFilePath = dotFilePath;
1637 ✗ svgFilePath.replaceExtension("svg");
1638 ✗ dotFilePath.makeNative();
1639 ✗ svgFilePath.makeNative();
1640 ✗ Vector<String> dotArgs;
1641 ✗ dotArgs.pushBack(String("-T"));
1642 ✗ dotArgs.pushBack(String("svg"));
1643 ✗ dotArgs.pushBack(String("-o"));
1644 ✗ dotArgs.pushBack(String(svgFilePath.toString()));
1645 ✗ dotArgs.pushBack(String(dotFilePath.toString()));
1646 ✗ const ExecResult _ = exec(String("dot"), dotArgs);
1647 ✗ print("done.\nSVG file can be found at: \"" + svgFilePath.toString() + "\"\n");
1648 ✗ } else {
1649 // Dump to console
1650 9 print("\nSerialized " + String(outputNameRaw) + ":\n\n" + output + "\n");
1651 9 }
1652
1653 // If the abort after dump is requested, set the abort compilation flag
1654
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if this.cliOptions.dump.abortAfterDump {
1655 ✗ this.resourceManager.abortCompilation = true;
1656 ✗ }
1657 9 }
1658
1659 34586 p SourceFile.printStatusMessage(const string stage, const CompileStageIOType& in, const CompileStageIOType& out, unsigned long stageRuntime, unsigned short stageRuns = 0s) {
1660
2/4
✓ Branch 0 taken 30890 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3696 times.
✗ Branch 3 not taken.
34586 if this.cliOptions.printDebugOutput {
1661 ✗ const string[5] compilerStageIoTypeName = ["Code", "CST", "AST", "IR", "Obj"];
1662 // Build output string
1663 ✗ StringStream outputStr;
1664 ✗ outputStr << "[" << stage << "] for " << this.fileName << ": ";
1665 ✗ outputStr << compilerStageIoTypeName[cast<int>(in)] << " --> " << compilerStageIoTypeName[cast<int>(out)];
1666 ✗ outputStr << " (" << toString(stageRuntime) << " ms";
1667
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ if stageRuns > 0s {
1668 ✗ outputStr << "; " << toString(stageRuns) << " run(s)";
1669 ✗ }
1670 ✗ outputStr << ")\n";
1671 // Print
1672 ✗ print(outputStr.str());
1673 ✗ }
1674 34586 }
1675
1676 // ─── tests ────────────────────────────────────────────────────────────────────
1677
1678 1 #[test]
1679 f<bool> testGetObjectFilePath() {
1680 1 const FilePath testDir = makeUniqueTestDir("spice-object-path-test");
1681 1 const FilePath mainPath = testDir / "main.spice";
1682 1 const FilePath utilsPath = testDir / "utils.spice";
1683 1 writeTestFile(mainPath, "");
1684 1 writeTestFile(utilsPath, "");
1685
1686 1 string objectFileExt;
1687 1 FilePath execMainObjectPath;
1688 1 FilePath execUtilsObjectPath;
1689 1 FilePath objMainObjectPath;
1690 1 FilePath objUtilsObjectPath;
1691 {
1692 // Source files look up an LLVM target, so use the native one
1693 1 CliOptions cliOptions;
1694 1 cliOptions.targetTriple = llvm::normalizeTargetTriple(llvm::getDefaultTargetTriple());
1695 1 cliOptions.isNativeTarget = true;
1696
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 cliOptions.outputDir = testDir;
1697 1 cliOptions.outputPath = testDir / "custom-name.o";
1698 1 objectFileExt = getOutputFileExtension(cliOptions, OutputContainer::OBJECT_FILE);
1699 1 GlobalResourceManager resourceManager = GlobalResourceManager(cliOptions);
1700 1 SourceFile* mainFile = resourceManager.createSourceFile(nil<SourceFile*>, String("main"), mainPath, false);
1701 1 SourceFile* utils = resourceManager.createSourceFile(mainFile, String("utils"), utilsPath, false);
1702 1 utils.isMainFile = false;
1703
1704 // When linking, every source file emits its object file into the output dir
1705 1 cliOptions.outputContainer = OutputContainer::EXECUTABLE;
1706 1 execMainObjectPath = mainFile.getObjectFilePath();
1707 1 execUtilsObjectPath = utils.getObjectFilePath();
1708 // With the object file output container, the object of the main source file is the output file
1709 1 cliOptions.outputContainer = OutputContainer::OBJECT_FILE;
1710 1 objMainObjectPath = mainFile.getObjectFilePath();
1711 1 objUtilsObjectPath = utils.getObjectFilePath();
1712 1 }
1713
1714 // Clean up before asserting, so that a failure does not leave the files behind
1715 1 removeTestDir(testDir);
1716 1 FilePath expectedMainObjectPath = testDir / "main.spice";
1717 1 expectedMainObjectPath.replaceExtension(objectFileExt);
1718 1 FilePath expectedUtilsObjectPath = testDir / "utils.spice";
1719 1 expectedUtilsObjectPath.replaceExtension(objectFileExt);
1720 1 assert execMainObjectPath == expectedMainObjectPath;
1721 1 assert execUtilsObjectPath == expectedUtilsObjectPath;
1722 1 assert objMainObjectPath == testDir / "custom-name.o";
1723 1 assert objUtilsObjectPath == expectedUtilsObjectPath;
1724 1 return true;
1725 1 }
1726