1//===- LinkCLI.cpp --------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Implements the default (no subcommand) linking action. Inputs are read one
10// at a time and folded into the link unit as they are read, so the first
11// input that cannot be accepted is the one reported.
12//
13// The target triple is fixed from the first input (or from --target-triple)
14// before the linker is constructed. Validating every later input against it
15// happens here rather than in EntityLinker: choosing which inputs belong to
16// a target is a command line concern, and EntityLinker treats a mismatch as
17// a fatal precondition violation.
18//
19//===----------------------------------------------------------------------===//
20
21#include "LinkCLI.h"
22
23#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h"
24#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
25#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
26#include "clang/ScalableStaticAnalysis/Core/Support/ErrorBuilder.h"
27#include "clang/ScalableStaticAnalysis/Core/Support/FormatProviders.h"
28#include "llvm/ADT/Sequence.h"
29#include "llvm/Support/Path.h"
30#include <cassert>
31#include <memory>
32#include <utility>
33#include <variant>
34
35using namespace llvm;
36using namespace clang::ssaf;
37
38namespace path = llvm::sys::path;
39
40namespace {
41
42//===----------------------------------------------------------------------===//
43// Error Messages
44//===----------------------------------------------------------------------===//
45
46constexpr const char *ReadingArtifact = "Reading artifact '{0}'";
47
48constexpr const char *LinkingArtifact = "Linking artifact '{0}'";
49
50constexpr const char *NoInputs =
51 "no input artifacts: at least one input is required";
52
53constexpr const char *NoMembersToInferFrom =
54 "cannot infer target triple from '{0}': multi-arch static library has no "
55 "members; pass --target-triple";
56
57constexpr const char *AmbiguousMembersToInferFrom =
58 "cannot infer target triple from '{0}': multi-arch static library has {1} "
59 "members; pass --target-triple to select one";
60
61constexpr const char *UnsupportedSharedInput =
62 "'{0}' is a {1}: linking against shared libraries is not yet supported";
63
64constexpr const char *LinkUnitSummaryName = "link unit summary";
65constexpr const char *MultiArchSharedLibraryName = "multi-arch shared library";
66
67//===----------------------------------------------------------------------===//
68// ArtifactEncoding Helpers
69//===----------------------------------------------------------------------===//
70
71/// Returns the human readable kind of an artifact the linker cannot consume.
72///
73/// Only the shared-library family reaches this: every linkable alternative is
74/// handled before it is called. The static_assert makes a new alternative a
75/// compile error here rather than an unhandled case at runtime.
76llvm::StringRef unsupportedInputKindName(const ArtifactEncoding &E) {
77 static_assert(std::variant_size_v<ArtifactEncoding> == 5,
78 "unsupportedInputKindName must cover every ArtifactEncoding "
79 "alternative the linker cannot consume");
80
81 if (std::holds_alternative<LUSummaryEncoding>(v: E)) {
82 return LinkUnitSummaryName;
83 }
84
85 assert(
86 std::holds_alternative<MultiArchSharedLibrary>(E) &&
87 "linkable ArtifactEncoding alternatives must be handled by the caller");
88 return MultiArchSharedLibraryName;
89}
90
91} // namespace
92
93namespace clang::ssaf {
94
95void LinkCLI::run(llvm::TimerGroup &TG, llvm::ArrayRef<std::string> InputPaths,
96 llvm::StringRef OutputPath, llvm::StringRef TargetTriple,
97 bool Verbose, bool Time) {
98 this->InputPaths = InputPaths;
99 this->OutputPath = OutputPath;
100 this->TargetTriple = TargetTriple;
101 this->Verbose = Verbose;
102 this->Time = Time;
103
104 llvm::Timer TValidate("validate", "Validate Input", TG);
105 llvm::Timer TRead("read", "Read Artifacts", TG);
106 llvm::Timer TLink("link", "Link Artifacts", TG);
107 llvm::Timer TWrite("write", "Write Link Unit Summary", TG);
108
109 // Nesting depth for indenting verbose notes.
110 const unsigned Level = 0;
111
112 info(Verbose, Level, Fmt: "Linking started.");
113
114 validate(Level: Level + 1, TValidate);
115
116 LUSummaryEncoding Output = link(Level: Level + 1, TRead, TLink);
117
118 write(Output, Level: Level + 1, TWrite);
119
120 info(Verbose, Level, Fmt: "Linking finished.");
121
122 // A second run() should start from a clean slate.
123 InputFiles.clear();
124 ExplicitTriple.reset();
125}
126
127void LinkCLI::validate(unsigned Level, llvm::Timer &TValidate) {
128 info(Verbose, Level, Fmt: "Validating input.");
129
130 llvm::TimeRegion _(Time ? &TValidate : nullptr);
131
132 OutputFile = FormatFile::fromOutputPath(Path: OutputPath);
133 LinkUnitName = path::stem(path: OutputFile.Path).str();
134 info(Verbose, Level: Level + 1, Fmt: "Validated output path '{0}'.", Args&: OutputFile.Path);
135
136 if (InputPaths.empty()) {
137 fail(Msg: NoInputs);
138 }
139 for (const auto &InputPath : InputPaths) {
140 InputFiles.push_back(x: FormatFile::fromInputPath(Path: InputPath));
141 }
142 info(Verbose, Level: Level + 1, Fmt: "Validated {0} input artifact path(s).",
143 Args: InputFiles.size());
144
145 if (!TargetTriple.empty()) {
146 ExplicitTriple = parseTargetTripleOrFail(FlagName: "--target-triple", Value: TargetTriple);
147 }
148}
149
150LUSummaryEncoding LinkCLI::link(unsigned Level, llvm::Timer &TRead,
151 llvm::Timer &TLink) {
152 info(Verbose, Level, Fmt: "Creating link unit.");
153
154 const unsigned InputLevel = Level + 1;
155 info(Verbose, Level: InputLevel, Fmt: "Linking artifacts.");
156
157 // The target triple comes from the first input, so it has to be read before
158 // the linker can be constructed.
159 constexpr size_t FirstIndex = 0;
160 ArtifactEncoding First =
161 readInput(Input: InputFiles[FirstIndex], Index: FirstIndex, Level: InputLevel + 1, TRead);
162
163 llvm::Triple LinkUnitTriple =
164 resolveTargetTriple(First, SourceFile: InputFiles[FirstIndex].Path, Level: InputLevel + 1);
165
166 NestedBuildNamespace LUNamespace(
167 BuildNamespace(BuildNamespaceKind::LinkUnit, LinkUnitName));
168 EntityLinker EL(LinkUnitTriple, LUNamespace);
169
170 linkInput(EL, Encoding: std::move(First), SourceFile: InputFiles[FirstIndex].Path, Index: FirstIndex,
171 Level: InputLevel + 1, TLink);
172 for (size_t Index : llvm::seq<size_t>(Begin: FirstIndex + 1, End: InputFiles.size())) {
173 linkInput(EL, Encoding: readInput(Input: InputFiles[Index], Index, Level: InputLevel + 1, TRead),
174 SourceFile: InputFiles[Index].Path, Index, Level: InputLevel + 1, TLink);
175 }
176
177 info(Verbose, Level: InputLevel, Fmt: "Linked {0} translation unit(s).",
178 Args: EL.getLinkedTUCount());
179 info(Verbose, Level: InputLevel, Fmt: "Target namespace: '{0}'.", Args&: LUNamespace);
180
181 return std::move(EL).takeOutput();
182}
183
184ArtifactEncoding LinkCLI::readInput(const FormatFile &Input, size_t Index,
185 unsigned Level, llvm::Timer &TRead) {
186 info(Verbose, Level, Fmt: "[{0}/{1}] Reading '{2}'.", Args: Index + 1, Args: InputFiles.size(),
187 Args: Input.Path);
188
189 llvm::TimeRegion _(Time ? &TRead : nullptr);
190
191 auto ExpectedEncoding = Input.Format->readArtifactEncoding(Path: Input.Path);
192 if (!ExpectedEncoding) {
193 fail(Err: ErrorBuilder::wrap(E: ExpectedEncoding.takeError())
194 .context(Fmt: ReadingArtifact, ArgVals: Input.Path)
195 .build());
196 }
197 return std::move(*ExpectedEncoding);
198}
199
200llvm::Triple LinkCLI::resolveTargetTriple(const ArtifactEncoding &First,
201 llvm::StringRef SourceFile,
202 unsigned Level) {
203 if (ExplicitTriple) {
204 info(Verbose, Level, Fmt: "Target triple: '{0}' (from --target-triple).",
205 Args&: *ExplicitTriple);
206 return *ExplicitTriple;
207 }
208
209 auto Inferred = [&]() -> llvm::Triple {
210 if (const auto *TU = std::get_if<TUSummaryEncoding>(ptr: &First)) {
211 return TU->getTargetTriple();
212 }
213
214 if (const auto *SL = std::get_if<StaticLibrary>(ptr: &First)) {
215 return SL->TargetTriple;
216 }
217
218 if (const auto *MASL = std::get_if<MultiArchStaticLibrary>(ptr: &First)) {
219 // A single member names the target unambiguously; anything else needs the
220 // architecture to be chosen on the command line.
221 if (MASL->Members.empty()) {
222 fail(Fmt: NoMembersToInferFrom, Args&: SourceFile);
223 }
224 if (MASL->Members.size() > 1) {
225 fail(Fmt: AmbiguousMembersToInferFrom, Args&: SourceFile, Args: MASL->Members.size());
226 }
227 return (*MASL->Members.begin())->TargetTriple;
228 }
229
230 fail(Fmt: UnsupportedSharedInput, Args&: SourceFile, Args: unsupportedInputKindName(E: First));
231 }();
232
233 info(Verbose, Level, Fmt: "Target triple: '{0}' (inferred from '{1}').", Args&: Inferred,
234 Args&: SourceFile);
235
236 return Inferred;
237}
238
239void LinkCLI::linkInput(EntityLinker &EL, ArtifactEncoding Encoding,
240 llvm::StringRef SourceFile, size_t Index,
241 unsigned Level, llvm::Timer &TLink) {
242 auto failOnError = [&](llvm::Error Err) {
243 if (Err) {
244 fail(Err: ErrorBuilder::wrap(E: std::move(Err))
245 .context(Fmt: LinkingArtifact, ArgVals&: SourceFile)
246 .build());
247 }
248 };
249
250 if (auto *TU = std::get_if<TUSummaryEncoding>(ptr: &Encoding)) {
251 info(Verbose, Level, Fmt: "[{0}/{1}] Linking '{2}'.", Args: Index + 1,
252 Args: InputFiles.size(), Args&: SourceFile);
253 llvm::TimeRegion _(Time ? &TLink : nullptr);
254
255 failOnError(EL.link(Summary: std::make_unique<TUSummaryEncoding>(args: std::move(*TU))));
256 return;
257 }
258
259 if (auto *SL = std::get_if<StaticLibrary>(ptr: &Encoding)) {
260 info(Verbose, Level,
261 Fmt: "[{0}/{1}] Linking '{2}' (static library, {3} member(s)).", Args: Index + 1,
262 Args: InputFiles.size(), Args&: SourceFile, Args: SL->Members.size());
263 llvm::TimeRegion _(Time ? &TLink : nullptr);
264
265 failOnError(EL.link(Library: std::make_unique<StaticLibrary>(args: std::move(*SL))));
266 return;
267 }
268
269 if (auto *MASL = std::get_if<MultiArchStaticLibrary>(ptr: &Encoding)) {
270 info(Verbose, Level,
271 Fmt: "[{0}/{1}] Linking '{2}' (multi-arch static library, {3} member(s)).",
272 Args: Index + 1, Args: InputFiles.size(), Args&: SourceFile, Args: MASL->Members.size());
273 llvm::TimeRegion _(Time ? &TLink : nullptr);
274
275 failOnError(
276 EL.link(Library: std::make_unique<MultiArchStaticLibrary>(args: std::move(*MASL))));
277 return;
278 }
279
280 fail(Fmt: UnsupportedSharedInput, Args&: SourceFile, Args: unsupportedInputKindName(E: Encoding));
281}
282
283void LinkCLI::write(const LUSummaryEncoding &Output, unsigned Level,
284 llvm::Timer &TWrite) {
285 info(Verbose, Level, Fmt: "Writing link unit summary to '{0}'.", Args&: OutputFile.Path);
286
287 llvm::TimeRegion _(Time ? &TWrite : nullptr);
288
289 if (auto Err =
290 OutputFile.Format->writeLUSummaryEncoding(SummaryEncoding: Output, Path: OutputFile.Path)) {
291 fail(Err: std::move(Err));
292 }
293}
294
295} // namespace clang::ssaf
296