1//===- SSAFLinker.cpp - SSAF Linker ---------------------------------------===//
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// This file implements the SSAF entity linker tool. Its default behavior
10// is to link N TU summaries into one LU summary via the EntityLinker
11// framework. It also provides the `static-library` subcommand for
12// bundling TU summaries into a StaticLibrary.
13//
14//===----------------------------------------------------------------------===//
15
16#include "StaticLibraryCreateCLI.h"
17
18#include "clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h"
19#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
20#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
21#include "clang/ScalableStaticAnalysis/Core/Support/ErrorBuilder.h"
22#include "clang/ScalableStaticAnalysis/SSAFForceLinker.h" // IWYU pragma: keep
23#include "clang/ScalableStaticAnalysis/Tool/Utils.h"
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/ADT/SmallVector.h"
26#include "llvm/Support/CommandLine.h"
27#include "llvm/Support/FormatVariadic.h"
28#include "llvm/Support/InitLLVM.h"
29#include "llvm/Support/Path.h"
30#include "llvm/Support/Timer.h"
31#include "llvm/Support/WithColor.h"
32#include "llvm/Support/raw_ostream.h"
33#include <memory>
34#include <string>
35
36using namespace llvm;
37using namespace clang::ssaf;
38
39namespace path = llvm::sys::path;
40
41namespace {
42
43//===----------------------------------------------------------------------===//
44// Command-Line Options
45//===----------------------------------------------------------------------===//
46
47cl::OptionCategory SsafLinkerCategory("clang-ssaf-linker options");
48
49// The `static-library` subcommand groups all StaticLibrary operations.
50cl::SubCommand StaticLibraryCmd("static-library",
51 "Operations on StaticLibraries");
52
53// Top-level (default) `link` action positionals.
54cl::list<std::string> InputPaths(cl::Positional, cl::desc("<input files>"),
55 cl::OneOrMore, cl::cat(SsafLinkerCategory));
56
57cl::opt<std::string> OutputPath("o", cl::desc("Output file path"),
58 cl::value_desc("path"), cl::Required,
59 cl::cat(SsafLinkerCategory));
60
61// --verbose and --time apply to every subcommand.
62cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
63 cl::init(Val: false), cl::cat(SsafLinkerCategory),
64 cl::sub(cl::SubCommand::getTopLevel()),
65 cl::sub(StaticLibraryCmd));
66
67cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(Val: false),
68 cl::cat(SsafLinkerCategory),
69 cl::sub(cl::SubCommand::getTopLevel()),
70 cl::sub(StaticLibraryCmd));
71
72// The `static-library` subcommand's verb positional. Declared BEFORE
73// StaticLibraryInputs so cl-lib binds argv[0] under the subcommand to the
74// verb rather than to the greedy input list.
75cl::opt<std::string> StaticLibraryVerb(cl::Positional, cl::Required,
76 cl::sub(StaticLibraryCmd),
77 cl::desc("<verb>"),
78 cl::value_desc("create"),
79 cl::cat(SsafLinkerCategory));
80
81// The `static-library` subcommand's action-specific positional input
82// list. Currently consumed by `static-library create`; if future verbs
83// need different input shapes they'll declare their own positionals.
84cl::list<std::string> StaticLibraryInputs(cl::Positional,
85 cl::sub(StaticLibraryCmd),
86 cl::desc("<TU summary files>"),
87 cl::cat(SsafLinkerCategory));
88
89cl::opt<std::string> StaticLibraryOutput("o", cl::Required,
90 cl::sub(StaticLibraryCmd),
91 cl::desc("Output file path"),
92 cl::value_desc("path"),
93 cl::cat(SsafLinkerCategory));
94
95cl::opt<std::string> StaticLibraryNamespace(
96 "namespace", cl::sub(StaticLibraryCmd),
97 cl::desc("Namespace name for the StaticLibrary (defaults to output "
98 "file stem)"),
99 cl::value_desc("name"), cl::cat(SsafLinkerCategory));
100
101cl::opt<std::string> StaticLibraryTriple(
102 "target-triple", cl::sub(StaticLibraryCmd),
103 cl::desc("Target triple (defaults to inputs' triple; must match all "
104 "inputs when set)"),
105 cl::value_desc("triple"), cl::cat(SsafLinkerCategory));
106
107//===----------------------------------------------------------------------===//
108// StaticLibrary Verbs
109//===----------------------------------------------------------------------===//
110
111// Verb strings for the `static-library` subcommand. Kept in sync with
112// UnknownStaticLibraryVerb below.
113constexpr const char *StaticLibraryCreateVerb = "create";
114
115//===----------------------------------------------------------------------===//
116// Error Messages
117//===----------------------------------------------------------------------===//
118
119namespace LocalErrorMessages {
120
121constexpr const char *LinkingSummary = "Linking summary '{0}'";
122
123constexpr const char *UnknownStaticLibraryVerb =
124 "unknown static-library verb '{0}': expected 'create'";
125
126} // namespace LocalErrorMessages
127
128//===----------------------------------------------------------------------===//
129// Diagnostic Utilities
130//===----------------------------------------------------------------------===//
131
132constexpr unsigned IndentationWidth = 2;
133
134template <typename... Ts>
135void info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
136 if (Verbose) {
137 llvm::WithColor::note()
138 << std::string(IndentationLevel * IndentationWidth, ' ') << "- "
139 << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
140 }
141}
142
143//===----------------------------------------------------------------------===//
144// link action
145//===----------------------------------------------------------------------===//
146
147struct LinkerInput {
148 std::vector<FormatFile> InputFiles;
149 FormatFile OutputFile;
150 std::string LinkUnitName;
151};
152
153LinkerInput validateLinkInput(llvm::TimerGroup &TG) {
154 llvm::Timer TValidate("validate", "Validate Input", TG);
155 LinkerInput LI;
156
157 {
158 llvm::TimeRegion _(Time ? &TValidate : nullptr);
159
160 LI.OutputFile = FormatFile::fromOutputPath(Path: OutputPath);
161 LI.LinkUnitName = path::stem(path: LI.OutputFile.Path).str();
162 }
163
164 info(IndentationLevel: 2, Fmt: "Validated output summary path '{0}'.", Args&: LI.OutputFile.Path);
165
166 {
167 llvm::TimeRegion _(Time ? &TValidate : nullptr);
168 for (const auto &InputPath : InputPaths) {
169 LI.InputFiles.push_back(x: FormatFile::fromInputPath(Path: InputPath));
170 }
171 }
172
173 info(IndentationLevel: 2, Fmt: "Validated {0} input summary paths.", Args: LI.InputFiles.size());
174
175 return LI;
176}
177
178void runLink(llvm::TimerGroup &TG) {
179 info(IndentationLevel: 0, Fmt: "Linking started.");
180
181 LinkerInput LI;
182 {
183 info(IndentationLevel: 1, Fmt: "Validating input.");
184 LI = validateLinkInput(TG);
185 }
186
187 info(IndentationLevel: 1, Fmt: "Linking input.");
188 info(IndentationLevel: 2, Fmt: "Constructing linker.");
189
190 // TODO: The linker currently uses a hardcoded target triple. Architecture
191 // tracking in the linker will be handled properly in a separate PR.
192 EntityLinker EL(llvm::Triple("arm64-apple-macosx"),
193 NestedBuildNamespace(BuildNamespace(
194 BuildNamespaceKind::LinkUnit, LI.LinkUnitName)));
195
196 llvm::Timer TRead("read", "Read Summaries", TG);
197 llvm::Timer TLink("link", "Link Summaries", TG);
198 llvm::Timer TWrite("write", "Write Summary", TG);
199
200 info(IndentationLevel: 2, Fmt: "Linking summaries.");
201
202 for (auto [Index, InputFile] : llvm::enumerate(First&: LI.InputFiles)) {
203 std::unique_ptr<TUSummaryEncoding> Summary;
204
205 {
206 info(IndentationLevel: 3, Fmt: "[{0}/{1}] Reading '{2}'.", Args: (Index + 1), Args: LI.InputFiles.size(),
207 Args&: InputFile.Path);
208
209 llvm::TimeRegion _(Time ? &TRead : nullptr);
210
211 auto ExpectedSummaryEncoding =
212 InputFile.Format->readTUSummaryEncoding(Path: InputFile.Path);
213 if (!ExpectedSummaryEncoding) {
214 fail(Err: ExpectedSummaryEncoding.takeError());
215 }
216
217 Summary = std::make_unique<TUSummaryEncoding>(
218 args: std::move(*ExpectedSummaryEncoding));
219 }
220
221 {
222 info(IndentationLevel: 3, Fmt: "[{0}/{1}] Linking '{2}'.", Args: (Index + 1), Args: LI.InputFiles.size(),
223 Args&: InputFile.Path);
224
225 llvm::TimeRegion _(Time ? &TLink : nullptr);
226
227 if (auto Err = EL.link(Summary: std::move(Summary))) {
228 fail(Err: ErrorBuilder::wrap(E: std::move(Err))
229 .context(Fmt: LocalErrorMessages::LinkingSummary, ArgVals&: InputFile.Path)
230 .build());
231 }
232 }
233 }
234
235 {
236 info(IndentationLevel: 2, Fmt: "Writing output summary to '{0}'.", Args&: LI.OutputFile.Path);
237
238 llvm::TimeRegion _(Time ? &TWrite : nullptr);
239
240 auto Output = std::move(EL).takeOutput();
241 if (auto Err = LI.OutputFile.Format->writeLUSummaryEncoding(
242 SummaryEncoding: Output, Path: LI.OutputFile.Path)) {
243 fail(Err: std::move(Err));
244 }
245 }
246
247 info(IndentationLevel: 0, Fmt: "Linking finished.");
248}
249
250//===----------------------------------------------------------------------===//
251// static-library subcommand dispatch
252//===----------------------------------------------------------------------===//
253
254void runStaticLibrary(llvm::TimerGroup &TG) {
255 if (StaticLibraryVerb == StaticLibraryCreateVerb) {
256 StaticLibraryCreateCLI::Config Cfg;
257 Cfg.InputPaths = StaticLibraryInputs;
258 Cfg.OutputPath = StaticLibraryOutput;
259 Cfg.Namespace = StaticLibraryNamespace;
260 Cfg.TargetTriple = StaticLibraryTriple;
261 Cfg.Verbose = Verbose;
262 Cfg.Time = Time;
263
264 StaticLibraryCreateCLI SLC;
265 SLC.run(TG, Cfg);
266 return;
267 }
268 fail(Fmt: LocalErrorMessages::UnknownStaticLibraryVerb,
269 Args&: StaticLibraryVerb.getValue());
270}
271
272} // namespace
273
274//===----------------------------------------------------------------------===//
275// Driver
276//===----------------------------------------------------------------------===//
277
278int main(int argc, const char **argv) {
279 llvm::StringRef ToolHeading = "SSAF Linker";
280
281 InitLLVM X(argc, argv);
282 initTool(argc, argv, Version: "0.1", Category&: SsafLinkerCategory, ToolHeading);
283
284 llvm::TimerGroup Timers(getToolName(), ToolHeading);
285
286 if (StaticLibraryCmd) {
287 runStaticLibrary(TG&: Timers);
288 } else {
289 // Default (no subcommand): run the linker pipeline.
290 runLink(TG&: Timers);
291 }
292
293 return 0;
294}
295