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 that performs entity
10// linking across multiple TU summaries using the EntityLinker framework.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h"
15#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
16#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
17#include "clang/ScalableStaticAnalysis/Core/Support/ErrorBuilder.h"
18#include "clang/ScalableStaticAnalysis/SSAFForceLinker.h" // IWYU pragma: keep
19#include "clang/ScalableStaticAnalysis/Tool/Utils.h"
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/ADT/SmallVector.h"
22#include "llvm/Support/CommandLine.h"
23#include "llvm/Support/FormatVariadic.h"
24#include "llvm/Support/InitLLVM.h"
25#include "llvm/Support/Path.h"
26#include "llvm/Support/Timer.h"
27#include "llvm/Support/WithColor.h"
28#include "llvm/Support/raw_ostream.h"
29#include <memory>
30#include <string>
31
32using namespace llvm;
33using namespace clang::ssaf;
34
35namespace path = llvm::sys::path;
36
37namespace {
38
39//===----------------------------------------------------------------------===//
40// Command-Line Options
41//===----------------------------------------------------------------------===//
42
43cl::OptionCategory SsafLinkerCategory("clang-ssaf-linker options");
44
45cl::list<std::string> InputPaths(cl::Positional, cl::desc("<input files>"),
46 cl::OneOrMore, cl::cat(SsafLinkerCategory));
47
48cl::opt<std::string> OutputPath("o", cl::desc("Output file path"),
49 cl::value_desc("path"), cl::Required,
50 cl::cat(SsafLinkerCategory));
51
52cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
53 cl::init(Val: false), cl::cat(SsafLinkerCategory));
54
55cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(Val: false),
56 cl::cat(SsafLinkerCategory));
57
58//===----------------------------------------------------------------------===//
59// Error Messages
60//===----------------------------------------------------------------------===//
61
62namespace LocalErrorMessages {
63
64constexpr const char *LinkingSummary = "Linking summary '{0}'";
65
66} // namespace LocalErrorMessages
67
68//===----------------------------------------------------------------------===//
69// Diagnostic Utilities
70//===----------------------------------------------------------------------===//
71
72constexpr unsigned IndentationWidth = 2;
73
74template <typename... Ts>
75void info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
76 if (Verbose) {
77 llvm::WithColor::note()
78 << std::string(IndentationLevel * IndentationWidth, ' ') << "- "
79 << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
80 }
81}
82
83//===----------------------------------------------------------------------===//
84// Data Structures
85//===----------------------------------------------------------------------===//
86
87struct LinkerInput {
88 std::vector<FormatFile> InputFiles;
89 FormatFile OutputFile;
90 std::string LinkUnitName;
91};
92
93//===----------------------------------------------------------------------===//
94// Pipeline
95//===----------------------------------------------------------------------===//
96
97LinkerInput validate(llvm::TimerGroup &TG) {
98 llvm::Timer TValidate("validate", "Validate Input", TG);
99 LinkerInput LI;
100
101 {
102 llvm::TimeRegion _(Time ? &TValidate : nullptr);
103
104 LI.OutputFile = FormatFile::fromOutputPath(Path: OutputPath);
105 LI.LinkUnitName = path::stem(path: LI.OutputFile.Path).str();
106 }
107
108 info(IndentationLevel: 2, Fmt: "Validated output summary path '{0}'.", Args&: LI.OutputFile.Path);
109
110 {
111 llvm::TimeRegion _(Time ? &TValidate : nullptr);
112 for (const auto &InputPath : InputPaths) {
113 LI.InputFiles.push_back(x: FormatFile::fromInputPath(Path: InputPath));
114 }
115 }
116
117 info(IndentationLevel: 2, Fmt: "Validated {0} input summary paths.", Args: LI.InputFiles.size());
118
119 return LI;
120}
121
122void link(const LinkerInput &LI, llvm::TimerGroup &TG) {
123 info(IndentationLevel: 2, Fmt: "Constructing linker.");
124
125 // TODO: The linker currently uses a hardcoded target triple. Architecture
126 // tracking in the linker will be handled properly in a separate PR.
127 EntityLinker EL(llvm::Triple("arm64-apple-macosx"),
128 NestedBuildNamespace(BuildNamespace(
129 BuildNamespaceKind::LinkUnit, LI.LinkUnitName)));
130
131 llvm::Timer TRead("read", "Read Summaries", TG);
132 llvm::Timer TLink("link", "Link Summaries", TG);
133 llvm::Timer TWrite("write", "Write Summary", TG);
134
135 info(IndentationLevel: 2, Fmt: "Linking summaries.");
136
137 for (auto [Index, InputFile] : llvm::enumerate(First: LI.InputFiles)) {
138 std::unique_ptr<TUSummaryEncoding> Summary;
139
140 {
141 info(IndentationLevel: 3, Fmt: "[{0}/{1}] Reading '{2}'.", Args: (Index + 1), Args: LI.InputFiles.size(),
142 Args: InputFile.Path);
143
144 llvm::TimeRegion _(Time ? &TRead : nullptr);
145
146 auto ExpectedSummaryEncoding =
147 InputFile.Format->readTUSummaryEncoding(Path: InputFile.Path);
148 if (!ExpectedSummaryEncoding) {
149 fail(Err: ExpectedSummaryEncoding.takeError());
150 }
151
152 Summary = std::make_unique<TUSummaryEncoding>(
153 args: std::move(*ExpectedSummaryEncoding));
154 }
155
156 {
157 info(IndentationLevel: 3, Fmt: "[{0}/{1}] Linking '{2}'.", Args: (Index + 1), Args: LI.InputFiles.size(),
158 Args: InputFile.Path);
159
160 llvm::TimeRegion _(Time ? &TLink : nullptr);
161
162 if (auto Err = EL.link(Summary: std::move(Summary))) {
163 fail(Err: ErrorBuilder::wrap(E: std::move(Err))
164 .context(Fmt: LocalErrorMessages::LinkingSummary, ArgVals: InputFile.Path)
165 .build());
166 }
167 }
168 }
169
170 {
171 info(IndentationLevel: 2, Fmt: "Writing output summary to '{0}'.", Args: LI.OutputFile.Path);
172
173 llvm::TimeRegion _(Time ? &TWrite : nullptr);
174
175 auto Output = std::move(EL).takeOutput();
176 if (auto Err = LI.OutputFile.Format->writeLUSummaryEncoding(
177 SummaryEncoding: Output, Path: LI.OutputFile.Path)) {
178 fail(Err: std::move(Err));
179 }
180 }
181}
182
183} // namespace
184
185//===----------------------------------------------------------------------===//
186// Driver
187//===----------------------------------------------------------------------===//
188
189int main(int argc, const char **argv) {
190 llvm::StringRef ToolHeading = "SSAF Linker";
191
192 InitLLVM X(argc, argv);
193 initTool(argc, argv, Version: "0.1", Category&: SsafLinkerCategory, ToolHeading);
194
195 llvm::TimerGroup LinkerTimers(getToolName(), ToolHeading);
196 LinkerInput LI;
197
198 {
199 info(IndentationLevel: 0, Fmt: "Linking started.");
200
201 {
202 info(IndentationLevel: 1, Fmt: "Validating input.");
203 LI = validate(TG&: LinkerTimers);
204 }
205
206 {
207 info(IndentationLevel: 1, Fmt: "Linking input.");
208 link(LI, TG&: LinkerTimers);
209 }
210
211 info(IndentationLevel: 0, Fmt: "Linking finished.");
212 }
213
214 return 0;
215}
216