GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 63.2% 67 / 22 / 128
Functions: 90.0% 9 / 0 / 10
Branches: 34.2% 67 / 94 / 290

src/linker/ExternalLinkerInterface.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "ExternalLinkerInterface.h"
4
5 #include <iostream>
6
7 #include <driver/Driver.h>
8 #include <exception/CompilerError.h>
9 #include <exception/LinkerError.h>
10 #include <util/GlobalDefinitions.h>
11 #include <util/SystemUtil.h>
12 #include <util/Timer.h>
13
14 namespace spice::compiler {
15
16 468 ExternalLinkerInterface::ExternalLinkerInterface(const CliOptions &cliOptions)
17 468 : outputPath(cliOptions.outputPath), cliOptions(cliOptions) {}
18
19 230 void ExternalLinkerInterface::prepare() {
20 // Static linking
21
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 10 taken 230 times.
230 if (cliOptions.outputContainer == OutputContainer::SHARED_LIBRARY) {
22 addLinkerFlag("-shared");
23
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 18 taken 230 times.
230 } else if (cliOptions.staticLinking) {
24 addLinkerFlag("-static");
25 }
26
27 // The following flags only make sense if we want to emit an executable
28
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 230 times.
230 if (cliOptions.outputContainer != OutputContainer::EXECUTABLE)
29 return;
30
31 // Stripping symbols
32
5/6
✓ Branch 20 → 21 taken 226 times.
✓ Branch 20 → 24 taken 4 times.
✓ Branch 22 → 23 taken 226 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 226 times.
✓ Branch 25 → 33 taken 4 times.
230 if (!cliOptions.instrumentation.generateDebugInfo && !cliOptions.targetTriple.isOSDarwin())
33
2/4
✓ Branch 28 → 29 taken 226 times.
✗ Branch 28 → 112 not taken.
✓ Branch 29 → 30 taken 226 times.
✗ Branch 29 → 110 not taken.
452 addLinkerFlag("-Wl,-s");
34
35 // Sanitizers
36
3/6
✓ Branch 33 → 34 taken 226 times.
✓ Branch 33 → 35 taken 2 times.
✓ Branch 33 → 42 taken 2 times.
✗ Branch 33 → 49 not taken.
✗ Branch 33 → 63 not taken.
✗ Branch 33 → 76 not taken.
230 switch (cliOptions.instrumentation.sanitizer) {
37 226 case Sanitizer::NONE:
38 226 break;
39 2 case Sanitizer::ADDRESS:
40
2/4
✓ Branch 37 → 38 taken 2 times.
✗ Branch 37 → 118 not taken.
✓ Branch 38 → 39 taken 2 times.
✗ Branch 38 → 116 not taken.
2 addLinkerFlag("-lasan");
41 2 break;
42 2 case Sanitizer::THREAD:
43
2/4
✓ Branch 44 → 45 taken 2 times.
✗ Branch 44 → 124 not taken.
✓ Branch 45 → 46 taken 2 times.
✗ Branch 45 → 122 not taken.
2 addLinkerFlag("-ltsan");
44 2 break;
45 case Sanitizer::MEMORY:
46 addLinkerFlag("-L$(clang -print-resource-dir)/lib/x86_64-unknown-linux-gnu");
47 addLinkerFlag("-lclang_rt.msan");
48 requestLibMathLinkage();
49 break;
50 case Sanitizer::TYPE:
51 addLinkerFlag("-L$(clang -print-resource-dir)/lib/x86_64-unknown-linux-gnu");
52 addLinkerFlag("-lclang_rt.tysan");
53 break;
54 }
55
56 // Web Assembly
57
1/2
✗ Branch 77 → 78 not taken.
✓ Branch 77 → 97 taken 230 times.
230 if (cliOptions.targetTriple.isWasm()) {
58 addLinkerFlag("-nostdlib");
59 addLinkerFlag("-Wl,--no-entry");
60 addLinkerFlag("-Wl,--export-all");
61 }
62 }
63
64 230 void ExternalLinkerInterface::run() const {
65
1/4
✓ Branch 2 → 3 taken 230 times.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 6 not taken.
✗ Branch 2 → 7 not taken.
230 switch (cliOptions.outputContainer) {
66 230 case OutputContainer::EXECUTABLE:
67 case OutputContainer::SHARED_LIBRARY:
68 230 link();
69 230 break;
70 case OutputContainer::STATIC_LIBRARY:
71 archive();
72 break;
73 case OutputContainer::OBJECT_FILE:
74 // No linking necessary
75 break;
76 default:
77 assert_fail("Unknown output container");
78 }
79 230 }
80
81 /**
82 * Cleanup intermediary object files
83 */
84 230 void ExternalLinkerInterface::cleanup() const {
85 // Cleanup intermediary object files
86
1/2
✓ Branch 2 → 3 taken 230 times.
✗ Branch 2 → 33 not taken.
230 const char *objFileExt = SystemUtil::getOutputFileExtension(cliOptions, cliOptions.outputContainer);
87
2/4
✓ Branch 3 → 4 taken 230 times.
✗ Branch 3 → 27 not taken.
✓ Branch 4 → 5 taken 230 times.
✗ Branch 4 → 27 not taken.
230 if (cliOptions.outputContainer != OutputContainer::OBJECT_FILE && !cliOptions.dump.dumpToFiles)
88
2/2
✓ Branch 25 → 7 taken 937 times.
✓ Branch 25 → 26 taken 230 times.
1397 for (const std::filesystem::path &path : linkedFiles)
89
3/6
✓ Branch 9 → 10 taken 937 times.
✗ Branch 9 → 31 not taken.
✓ Branch 10 → 11 taken 937 times.
✗ Branch 10 → 28 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 937 times.
937 if (path.extension() == objFileExt)
90 std::filesystem::remove(path);
91 230 }
92
93 /**
94 * Link the object files to an executable
95 */
96 230 void ExternalLinkerInterface::link() const {
97
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 230 times.
230 assert(!outputPath.empty());
98
99 // Build the linker command
100
1/2
✓ Branch 5 → 6 taken 230 times.
✗ Branch 5 → 137 not taken.
230 std::stringstream commandBuilder;
101
1/2
✓ Branch 6 → 7 taken 230 times.
✗ Branch 6 → 135 not taken.
230 const auto [linkerInvokerName, linkerInvokerPath] = SystemUtil::findLinkerInvoker();
102
1/2
✓ Branch 7 → 8 taken 230 times.
✗ Branch 7 → 133 not taken.
230 commandBuilder << linkerInvokerPath;
103
1/2
✓ Branch 8 → 9 taken 230 times.
✗ Branch 8 → 133 not taken.
230 const auto [linkerName, linkerPath] = SystemUtil::findLinker(cliOptions);
104 230 const bool isGccInvoker = std::string_view(linkerInvokerName) == "gcc";
105 // GCC 16 dropped '-fuse-ld=ld'; skip when using GCC with the default BFD linker
106
2/6
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 17 taken 230 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✓ Branch 19 → 20 taken 230 times.
✗ Branch 19 → 22 not taken.
230 if (!isGccInvoker || std::string_view(linkerName) != "ld")
107
2/4
✓ Branch 20 → 21 taken 230 times.
✗ Branch 20 → 131 not taken.
✓ Branch 21 → 22 taken 230 times.
✗ Branch 21 → 131 not taken.
230 commandBuilder << " -fuse-ld=" << linkerPath;
108 // '--target=' is clang-only; GCC uses target-specific toolchain prefixes instead
109
1/2
✓ Branch 22 → 23 taken 230 times.
✗ Branch 22 → 26 not taken.
230 if (!isGccInvoker)
110
2/4
✓ Branch 23 → 24 taken 230 times.
✗ Branch 23 → 131 not taken.
✓ Branch 25 → 26 taken 230 times.
✗ Branch 25 → 131 not taken.
230 commandBuilder << " --target=" << cliOptions.targetTriple.str();
111 // Append linker flags
112
2/2
✓ Branch 41 → 28 taken 488 times.
✓ Branch 41 → 42 taken 230 times.
948 for (const std::string &linkerFlag : linkerFlags)
113
2/4
✓ Branch 30 → 31 taken 488 times.
✗ Branch 30 → 110 not taken.
✓ Branch 31 → 32 taken 488 times.
✗ Branch 31 → 110 not taken.
488 commandBuilder << " " << linkerFlag;
114
2/2
✓ Branch 42 → 43 taken 1 time.
✓ Branch 42 → 44 taken 229 times.
230 if (linkLibMath)
115
1/2
✓ Branch 43 → 44 taken 1 time.
✗ Branch 43 → 131 not taken.
1 commandBuilder << " -lm";
116 // Append output path
117
3/6
✓ Branch 44 → 45 taken 230 times.
✗ Branch 44 → 131 not taken.
✓ Branch 45 → 46 taken 230 times.
✗ Branch 45 → 113 not taken.
✓ Branch 46 → 47 taken 230 times.
✗ Branch 46 → 111 not taken.
230 commandBuilder << " -o " << outputPath.string();
118 // Append object files
119
2/2
✓ Branch 65 → 50 taken 937 times.
✓ Branch 65 → 66 taken 230 times.
1397 for (const std::filesystem::path &objectFilePath : linkedFiles)
120
3/6
✓ Branch 52 → 53 taken 937 times.
✗ Branch 52 → 117 not taken.
✓ Branch 53 → 54 taken 937 times.
✗ Branch 53 → 116 not taken.
✓ Branch 54 → 55 taken 937 times.
✗ Branch 54 → 114 not taken.
937 commandBuilder << " " << objectFilePath.string();
121
1/2
✓ Branch 66 → 67 taken 230 times.
✗ Branch 66 → 131 not taken.
230 const std::string command = commandBuilder.str();
122
123 // Print status message
124
1/2
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 81 taken 230 times.
230 if (cliOptions.printDebugOutput) {
125 std::cout << "\nLinking with: " << linkerInvokerName << " (invoker) / " << linkerName << " (linker)"; // GCOV_EXCL_LINE
126 std::cout << "\nLinker command: " << command; // GCOV_EXCL_LINE
127 std::cout << "\nEmitting executable to path: " << outputPath.string() << "\n"; // GCOV_EXCL_LINE
128 }
129
130 // Call the linker
131
1/2
✓ Branch 81 → 82 taken 230 times.
✗ Branch 81 → 129 not taken.
230 Timer timer;
132
1/2
✓ Branch 82 → 83 taken 230 times.
✗ Branch 82 → 129 not taken.
230 timer.start();
133
1/2
✓ Branch 83 → 84 taken 230 times.
✗ Branch 83 → 129 not taken.
230 const auto [output, exitCode] = SystemUtil::exec(command);
134
1/2
✓ Branch 84 → 85 taken 230 times.
✗ Branch 84 → 127 not taken.
230 timer.stop();
135
136 // Check for linker error
137 if (exitCode != 0) { // GCOV_EXCL_LINE
138 const std::string errorMessage = "Linker exited with non-zero exit code\nLinker command: " + command; // GCOV_EXCL_LINE
139 throw LinkerError(LINKER_ERROR, errorMessage); // GCOV_EXCL_LINE
140 } // GCOV_EXCL_LINE
141
142 // Print linker result if appropriate
143 if (cliOptions.printDebugOutput && !output.empty()) // GCOV_EXCL_LINE
144 std::cout << "Linking result: " << output << "\n\n"; // GCOV_EXCL_LINE
145
146 // Print link time
147 if (cliOptions.printDebugOutput) // GCOV_EXCL_LINE
148 std::cout << "Total link time: " << timer.getDurationMilliseconds() << " ms\n\n"; // GCOV_EXCL_LINE
149 230 }
150
151 /**
152 * Archive the object files to a static library
153 */
154 void ExternalLinkerInterface::archive() const {
155 assert(!outputPath.empty());
156
157 // Build the archiver command
158 std::stringstream commandBuilder;
159 const auto [archiverName, archiverPath] = SystemUtil::findArchiver();
160 commandBuilder << archiverPath;
161 commandBuilder << " rcs "; // r = insert files into archive; c = create archive if not existing, s = create archive index
162 commandBuilder << outputPath.string();
163 for (const std::filesystem::path &path : linkedFiles)
164 commandBuilder << " " << path.string();
165 const std::string command = commandBuilder.str();
166
167 // Print status message
168 if (cliOptions.printDebugOutput) {
169 std::cout << "\nArchiving with: " << archiverName; // GCOV_EXCL_LINE
170 std::cout << "\nArchiver command: " << command; // GCOV_EXCL_LINE
171 std::cout << "\nEmitting static library to path: " << outputPath.string() << "\n"; // GCOV_EXCL_LINE
172 }
173
174 // Call the archiver
175 Timer timer;
176 timer.start();
177 const auto [output, exitCode] = SystemUtil::exec(command);
178 timer.stop();
179
180 // Check for linker error
181 if (exitCode != 0) { // GCOV_EXCL_LINE
182 const std::string errorMessage = "Archiver exited with non-zero exit code\nArchiver command: " + command; // GCOV_EXCL_LINE
183 throw LinkerError(LINKER_ERROR, errorMessage); // GCOV_EXCL_LINE
184 } // GCOV_EXCL_LINE
185
186 // Print linker result if appropriate
187 if (cliOptions.printDebugOutput && !output.empty()) // GCOV_EXCL_LINE
188 std::cout << "Archiving result: " << output << "\n\n"; // GCOV_EXCL_LINE
189
190 // Print link time
191 if (cliOptions.printDebugOutput) // GCOV_EXCL_LINE
192 std::cout << "Total archive time: " << timer.getDurationMilliseconds() << " ms\n\n"; // GCOV_EXCL_LINE
193 }
194
195 /**
196 * Add another object file to be linked when calling 'link()'
197 *
198 * @param path Path to the object file
199 */
200 1014 void ExternalLinkerInterface::addFileToLinkage(const std::filesystem::path &path) { linkedFiles.push_back(path); }
201
202 /**
203 * Add another linker flag for the call to the linker executable
204 *
205 * @param flag Linker flag
206 */
207 491 void ExternalLinkerInterface::addLinkerFlag(const std::string &flag) { linkerFlags.push_back(flag); }
208
209 /**
210 * Add another source file to compile and link in (C or C++)
211 *
212 * @param additionalSource Additional source file
213 */
214 3 void ExternalLinkerInterface::addAdditionalSourcePath(std::filesystem::path additionalSource) {
215 // Check if the file exists
216
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 12 taken 2 times.
3 if (!exists(additionalSource)) {
217
3/6
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 22 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 20 not taken.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 18 not taken.
1 const std::string msg = "The additional source file '" + additionalSource.string() + "' does not exist";
218
1/2
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 24 not taken.
1 throw CompilerError(IO_ERROR, msg);
219 1 }
220
221 // Add the file to the linker
222
1/2
✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 30 not taken.
2 additionalSource = canonical(additionalSource);
223 2 additionalSource.make_preferred();
224 2 addFileToLinkage(additionalSource);
225 2 }
226
227 /**
228 * Link against libmath a.k.a. -lm
229 */
230 2 void ExternalLinkerInterface::requestLibMathLinkage() { linkLibMath = true; }
231
232 } // namespace spice::compiler
233