1//===- MultiArchCreateCLI.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 `multi-arch create` CLI action. The run() function picks the
10// family from the first input and hands off to either createStaticLibrary() or
11// createSharedLibrary().
12//
13//===----------------------------------------------------------------------===//
14
15#include "MultiArchCreateCLI.h"
16
17#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
18#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
19#include "clang/ScalableStaticAnalysis/Core/Support/ErrorBuilder.h"
20#include "clang/ScalableStaticAnalysis/Core/Support/FormatProviders.h"
21#include "llvm/ADT/ArrayRef.h"
22#include "llvm/ADT/StringRef.h"
23#include "llvm/Support/Timer.h"
24#include "llvm/TargetParser/Triple.h"
25#include <cassert>
26#include <memory>
27#include <string>
28#include <utility>
29#include <variant>
30
31using namespace llvm;
32using namespace clang::ssaf;
33
34namespace {
35
36//===----------------------------------------------------------------------===//
37// Error Messages
38//===----------------------------------------------------------------------===//
39
40constexpr const char *ReadingArtifact = "Reading artifact '{0}'";
41
42constexpr const char *NoInputs =
43 "no input artifacts: at least one input is required";
44
45constexpr const char *InvalidInputKind =
46 "'{0}' is a raw TU summary, not a valid input to multi-arch create: run "
47 "static-library create or an entity-linking step first";
48
49constexpr const char *MixedFamily =
50 "input '{0}' is a {1} artifact, but a preceding input established this "
51 "bundle as {2}";
52
53constexpr const char *NamespaceMismatch =
54 "namespace {0} from '{1}' does not match expected namespace {2}";
55
56constexpr const char *NoCandidateMembers =
57 "no candidate members could be derived from the given inputs: at least "
58 "one member is required";
59
60constexpr const char *DuplicateTriple =
61 "duplicate architecture slice '{0}' contributed by both '{1}' and '{2}'";
62
63constexpr const char *StaticFamilyName = "static-library";
64constexpr const char *SharedFamilyName = "shared-library";
65
66//===----------------------------------------------------------------------===//
67// ArtifactEncoding Helpers
68//===----------------------------------------------------------------------===//
69
70bool isStaticFamily(const ArtifactEncoding &E) {
71 return std::holds_alternative<StaticLibrary>(v: E) ||
72 std::holds_alternative<MultiArchStaticLibrary>(v: E);
73}
74
75bool isSharedFamily(const ArtifactEncoding &E) {
76 return std::holds_alternative<LUSummaryEncoding>(v: E) ||
77 std::holds_alternative<MultiArchSharedLibrary>(v: E);
78}
79
80bool isTUSummaryEncoding(const ArtifactEncoding &E) {
81 return std::holds_alternative<TUSummaryEncoding>(v: E);
82}
83
84} // namespace
85
86namespace clang::ssaf {
87
88void MultiArchCreateCLI::run(llvm::TimerGroup &TG,
89 llvm::ArrayRef<std::string> InputPaths,
90 llvm::StringRef OutputPath, bool Verbose,
91 bool Time) {
92 this->InputPaths = InputPaths;
93 this->OutputPath = OutputPath;
94 this->Verbose = Verbose;
95 this->Time = Time;
96
97 llvm::Timer TValidate("validate", "Validate Input", TG);
98 llvm::Timer TRead("read", "Read Artifacts", TG);
99 llvm::Timer TBundle("bundle", "Bundle Input", TG);
100 llvm::Timer TWrite("write", "Write Multi-Arch Bundle", TG);
101
102 // Nesting depth for indenting verbose notes.
103 const unsigned Level = 0;
104
105 info(Verbose, Level, Fmt: "Bundling started.");
106
107 validate(Level: Level + 1, TValidate);
108
109 ArtifactEncoding Result = create(Level: Level + 1, TRead, TBundle);
110
111 write(Bundle: Result, Level: Level + 1, TWrite);
112
113 info(Verbose, Level, Fmt: "Bundling finished.");
114
115 // Second run() should start from a clean slate.
116 InputFiles.clear();
117 SourceByMember.clear();
118}
119
120void MultiArchCreateCLI::validate(unsigned Level, llvm::Timer &TValidate) {
121 info(Verbose, Level, Fmt: "Validating input.");
122
123 llvm::TimeRegion _(Time ? &TValidate : nullptr);
124
125 OutputFile = FormatFile::fromOutputPath(Path: OutputPath);
126 info(Verbose, Level: Level + 1, Fmt: "Validated output path '{0}'.", Args&: OutputFile.Path);
127
128 if (InputPaths.empty()) {
129 fail(Msg: NoInputs);
130 }
131
132 for (const auto &InputPath : InputPaths) {
133 InputFiles.push_back(x: FormatFile::fromInputPath(Path: InputPath));
134 }
135
136 info(Verbose, Level: Level + 1, Fmt: "Validated {0} input artifact path(s).",
137 Args: InputFiles.size());
138}
139
140ArtifactEncoding MultiArchCreateCLI::create(unsigned Level, llvm::Timer &TRead,
141 llvm::Timer &TBundle) {
142 info(Verbose, Level, Fmt: "Creating bundle.");
143
144 const unsigned MemberLevel = Level + 1;
145 info(Verbose, Level: MemberLevel, Fmt: "Bundling members.");
146
147 size_t Index = 0;
148 ArtifactEncoding First = readInput(Index, Level: MemberLevel + 1, TRead);
149
150 if (isStaticFamily(E: First)) {
151 return createStaticLibrary(First: std::move(First), Level: MemberLevel, TRead, TBundle);
152 }
153
154 if (isSharedFamily(E: First)) {
155 return createSharedLibrary(First: std::move(First), Level: MemberLevel, TRead, TBundle);
156 }
157
158 fail(Fmt: InvalidInputKind, Args&: InputFiles[Index].Path);
159}
160
161ArtifactEncoding MultiArchCreateCLI::readInput(size_t Index, unsigned Level,
162 llvm::Timer &TRead) {
163 const FormatFile &InputFile = InputFiles[Index];
164 info(Verbose, Level, Fmt: "[{0}/{1}] Reading '{2}'.", Args: Index + 1, Args: InputFiles.size(),
165 Args: InputFile.Path);
166
167 llvm::TimeRegion _(Time ? &TRead : nullptr);
168 auto ExpectedEncoding =
169 InputFile.Format->readArtifactEncoding(Path: InputFile.Path);
170 if (!ExpectedEncoding) {
171 fail(Err: ErrorBuilder::wrap(E: ExpectedEncoding.takeError())
172 .context(Fmt: ReadingArtifact, ArgVals: InputFile.Path)
173 .build());
174 }
175 return std::move(*ExpectedEncoding);
176}
177
178const BuildNamespace &
179MultiArchCreateCLI::staticFamilyNamespace(const ArtifactEncoding &E) {
180 assert(isStaticFamily(E) && "not a static-family artifact");
181 if (const auto *SL = std::get_if<StaticLibrary>(ptr: &E)) {
182 return SL->Namespace;
183 }
184 return std::get<MultiArchStaticLibrary>(v: E).Namespace;
185}
186
187const NestedBuildNamespace &
188MultiArchCreateCLI::sharedFamilyNamespace(const ArtifactEncoding &E) {
189 assert(isSharedFamily(E) && "not a shared-family artifact");
190 if (const auto *LU = std::get_if<LUSummaryEncoding>(ptr: &E)) {
191 return LU->LUNamespace;
192 }
193 return std::get<MultiArchSharedLibrary>(v: E).Namespace;
194}
195
196ArtifactEncoding MultiArchCreateCLI::createStaticLibrary(ArtifactEncoding First,
197 unsigned Level,
198 llvm::Timer &TRead,
199 llvm::Timer &TBundle) {
200 MultiArchStaticLibrary Bundle(staticFamilyNamespace(E: First).withKind(
201 Kind: BuildNamespaceKind::MultiArchStaticLibrary));
202
203 size_t Index = 0;
204
205 addStaticInput(Bundle, Encoding: std::move(First), Index, Level: Level + 1, TBundle);
206 for (Index = 1; Index < InputFiles.size(); ++Index) {
207 addStaticInput(Bundle, Encoding: readInput(Index, Level: Level + 1, TRead), Index, Level: Level + 1,
208 TBundle);
209 }
210
211 if (Bundle.Members.empty()) {
212 fail(Msg: NoCandidateMembers);
213 }
214
215 info(Verbose, Level, Fmt: "Bundled {0} member(s).", Args: Bundle.Members.size());
216 info(Verbose, Level, Fmt: "Target namespace: '{0}'.", Args&: Bundle.Namespace);
217
218 return ArtifactEncoding(std::move(Bundle));
219}
220
221ArtifactEncoding MultiArchCreateCLI::createSharedLibrary(ArtifactEncoding First,
222 unsigned Level,
223 llvm::Timer &TRead,
224 llvm::Timer &TBundle) {
225 MultiArchSharedLibrary Bundle(sharedFamilyNamespace(E: First));
226
227 size_t Index = 0;
228
229 addSharedInput(Bundle, Encoding: std::move(First), Index, Level: Level + 1, TBundle);
230 for (Index = 1; Index < InputFiles.size(); ++Index) {
231 addSharedInput(Bundle, Encoding: readInput(Index, Level: Level + 1, TRead), Index, Level: Level + 1,
232 TBundle);
233 }
234
235 if (Bundle.Members.empty()) {
236 fail(Msg: NoCandidateMembers);
237 }
238
239 info(Verbose, Level, Fmt: "Bundled {0} member(s).", Args: Bundle.Members.size());
240 info(Verbose, Level, Fmt: "Target namespace: '{0}'.", Args&: Bundle.Namespace);
241
242 return ArtifactEncoding(std::move(Bundle));
243}
244
245void MultiArchCreateCLI::addStaticInput(MultiArchStaticLibrary &Bundle,
246 ArtifactEncoding Encoding, size_t Index,
247 unsigned Level, llvm::Timer &TBundle) {
248 llvm::StringRef SourceFile = InputFiles[Index].Path;
249 info(Verbose, Level, Fmt: "[{0}/{1}] Bundling '{2}'.", Args: Index + 1,
250 Args: InputFiles.size(), Args&: SourceFile);
251 llvm::TimeRegion _(Time ? &TBundle : nullptr);
252
253 if (auto *SL = std::get_if<StaticLibrary>(ptr: &Encoding)) {
254 BuildNamespace Expected =
255 Bundle.Namespace.withKind(Kind: BuildNamespaceKind::StaticLibrary);
256 if (SL->Namespace != Expected) {
257 fail(Fmt: NamespaceMismatch, Args&: SL->Namespace, Args&: SourceFile, Args&: Expected);
258 }
259 addStaticMember(Bundle, Member: std::make_unique<StaticLibrary>(args: std::move(*SL)),
260 SourceFile);
261 return;
262 }
263
264 if (auto *MASL = std::get_if<MultiArchStaticLibrary>(ptr: &Encoding)) {
265 if (MASL->Namespace != Bundle.Namespace) {
266 fail(Fmt: NamespaceMismatch, Args&: MASL->Namespace, Args&: SourceFile, Args&: Bundle.Namespace);
267 }
268
269 while (!MASL->Members.empty()) {
270 auto Node = MASL->Members.extract(pos: MASL->Members.begin());
271 addStaticMember(Bundle, Member: std::move(Node.value()), SourceFile);
272 }
273 return;
274 }
275
276 if (isTUSummaryEncoding(E: Encoding)) {
277 fail(Fmt: InvalidInputKind, Args&: SourceFile);
278 }
279
280 fail(Fmt: MixedFamily, Args&: SourceFile, Args: SharedFamilyName, Args: StaticFamilyName);
281}
282
283void MultiArchCreateCLI::addSharedInput(MultiArchSharedLibrary &Bundle,
284 ArtifactEncoding Encoding, size_t Index,
285 unsigned Level, llvm::Timer &TBundle) {
286 llvm::StringRef SourceFile = InputFiles[Index].Path;
287 info(Verbose, Level, Fmt: "[{0}/{1}] Bundling '{2}'.", Args: Index + 1,
288 Args: InputFiles.size(), Args&: SourceFile);
289 llvm::TimeRegion _(Time ? &TBundle : nullptr);
290
291 if (auto *LU = std::get_if<LUSummaryEncoding>(ptr: &Encoding)) {
292 if (LU->LUNamespace != Bundle.Namespace) {
293 fail(Fmt: NamespaceMismatch, Args&: LU->LUNamespace, Args&: SourceFile, Args&: Bundle.Namespace);
294 }
295 addSharedMember(Bundle, Member: std::make_unique<LUSummaryEncoding>(args: std::move(*LU)),
296 SourceFile);
297 return;
298 }
299
300 if (auto *MASharedL = std::get_if<MultiArchSharedLibrary>(ptr: &Encoding)) {
301 if (MASharedL->Namespace != Bundle.Namespace) {
302 fail(Fmt: NamespaceMismatch, Args&: MASharedL->Namespace, Args&: SourceFile,
303 Args&: Bundle.Namespace);
304 }
305 while (!MASharedL->Members.empty()) {
306 auto Node = MASharedL->Members.extract(pos: MASharedL->Members.begin());
307 addSharedMember(Bundle, Member: std::move(Node.value()), SourceFile);
308 }
309 return;
310 }
311
312 if (isTUSummaryEncoding(E: Encoding)) {
313 fail(Fmt: InvalidInputKind, Args&: SourceFile);
314 }
315
316 fail(Fmt: MixedFamily, Args&: SourceFile, Args: StaticFamilyName, Args: SharedFamilyName);
317}
318
319void MultiArchCreateCLI::addStaticMember(MultiArchStaticLibrary &Bundle,
320 std::unique_ptr<StaticLibrary> Member,
321 llvm::StringRef SourceFile) {
322 auto [It, Inserted] = Bundle.Members.insert(x: std::move(Member));
323 if (!Inserted) {
324 fail(Fmt: DuplicateTriple, Args: llvm::Triple::normalize(Str: (*It)->TargetTriple.str()),
325 Args: SourceByMember.lookup(Val: It->get()), Args&: SourceFile);
326 }
327 SourceByMember[It->get()] = SourceFile;
328}
329
330void MultiArchCreateCLI::addSharedMember(
331 MultiArchSharedLibrary &Bundle, std::unique_ptr<LUSummaryEncoding> Member,
332 llvm::StringRef SourceFile) {
333 auto [It, Inserted] = Bundle.Members.insert(x: std::move(Member));
334 if (!Inserted) {
335 fail(Fmt: DuplicateTriple, Args: llvm::Triple::normalize(Str: (*It)->TargetTriple.str()),
336 Args: SourceByMember.lookup(Val: It->get()), Args&: SourceFile);
337 }
338 SourceByMember[It->get()] = SourceFile;
339}
340
341void MultiArchCreateCLI::write(const ArtifactEncoding &Bundle, unsigned Level,
342 llvm::Timer &TWrite) {
343 info(Verbose, Level, Fmt: "Writing bundle to '{0}'.", Args&: OutputFile.Path);
344
345 llvm::TimeRegion _(Time ? &TWrite : nullptr);
346
347 if (auto Err =
348 OutputFile.Format->writeArtifactEncoding(E: Bundle, Path: OutputFile.Path)) {
349 fail(Err: std::move(Err));
350 }
351}
352
353} // namespace clang::ssaf
354