GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 65.2% 73 / 22 / 134
Functions: 90.0% 9 / 0 / 10
Branches: 35.8% 73 / 94 / 298

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