[clang] [clang][ssaf] Link static libraries and multi-arch static libraries (PR #215349)
Aviral Goel via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 11 14:53:48 PDT 2026
https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/215349
>From f3f200de85080a90b1ed586046168d2e7a6cd4dd Mon Sep 17 00:00:00 2001
From: Aviral Goel <goel.aviral at gmail.com>
Date: Tue, 11 Aug 2026 11:13:42 -0700
Subject: [PATCH] [clang][ssaf] Link static libraries and multi-arch static
libraries
This change introduces support for linking static libraries and multi-arch
static libraries. To implement this, we have added two `link` overloads to the
`EntityLinker`: a static library folds in as a bundle of TU summaries, and a
multi-arch static library contributes only the static library with the matching
target triple. The target triple is supplied through a new optional flag,
`--target-triple`. If unspecified, it is inferred from the first input, if
possible.
---
.../Core/EntityLinker/EntityLinker.h | 54 +++-
.../EntityLinker/MultiArchStaticLibrary.h | 3 +
.../Core/EntityLinker/StaticLibrary.h | 12 +-
.../Core/Support/FormatProviders.h | 8 +
.../clang/ScalableStaticAnalysis/Tool/Utils.h | 15 +
.../Core/EntityLinker/EntityLinker.cpp | 78 +++++
.../lib/ScalableStaticAnalysis/Tool/Utils.cpp | 21 ++
.../ssaf-linker/Inputs/libord-reversed.json | 62 ++++
.../ssaf-linker/Inputs/libtwo-2arch.json | 39 +++
.../Scalable/ssaf-linker/Inputs/tu-linux.json | 11 +
.../ssaf-linker/Inputs/tu-x86_64.json | 11 +
.../Analysis/Scalable/ssaf-linker/help.test | 23 +-
.../Analysis/Scalable/ssaf-linker/io.test | 12 +-
.../Scalable/ssaf-linker/linking-errors.test | 120 ++++++-
.../Scalable/ssaf-linker/linking.test | 123 ++++++++
.../Analysis/Scalable/ssaf-linker/time.test | 6 +-
.../Scalable/ssaf-linker/verbose.test | 53 +++-
clang/tools/clang-ssaf-linker/CMakeLists.txt | 1 +
clang/tools/clang-ssaf-linker/LinkCLI.cpp | 296 ++++++++++++++++++
clang/tools/clang-ssaf-linker/LinkCLI.h | 106 +++++++
clang/tools/clang-ssaf-linker/SSAFLinker.cpp | 130 +-------
21 files changed, 1032 insertions(+), 152 deletions(-)
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/libord-reversed.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/libtwo-2arch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-linux.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-x86_64.json
create mode 100644 clang/tools/clang-ssaf-linker/LinkCLI.cpp
create mode 100644 clang/tools/clang-ssaf-linker/LinkCLI.h
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h
index f07def1a9b344..9a90b941bddfc 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h
@@ -8,6 +8,8 @@
//
// This file defines the EntityLinker class that combines multiple TU summaries
// into a unified LU summary by deduplicating entities and patching summaries.
+// TU summaries may be supplied individually, bundled in a static library, or
+// bundled in one architecture member of a multi-architecture static library.
//
//===----------------------------------------------------------------------===//
@@ -24,17 +26,27 @@
namespace clang::ssaf {
+class LinkCLI;
+class MultiArchStaticLibrary;
+class StaticLibrary;
class TUSummaryEncoding;
class EntityLinker {
+ friend class LinkCLI;
+
LUSummaryEncoding Output;
+
+ // Namespaces of the TU summaries folded in so far, whether supplied directly
+ // or as members of a library. Doubles as the count of linked translation
+ // units, which is the only place that number is known once a library or a
+ // multi-arch member has been expanded.
std::set<BuildNamespace> ProcessedTUNamespaces;
public:
/// Constructs an EntityLinker to link TU summaries into a LU summary.
///
- /// \param TargetTriple The target triple of the link unit. Every linked TU
- /// must report the same triple.
+ /// \param TargetTriple The target triple of the link unit. Every linked
+ /// input must report the same triple.
/// \param LUNamespace The namespace identifying this link unit.
EntityLinker(llvm::Triple TargetTriple, NestedBuildNamespace LUNamespace)
: Output(std::move(TargetTriple), std::move(LUNamespace)) {}
@@ -45,11 +57,34 @@ class EntityLinker {
/// and merges them into a single data store.
///
/// \param Summary The TU summary to link. Ownership is transferred.
- /// \returns Error if the TU namespace has already been linked or if patching
- /// fails, success otherwise. Corrupted summary data (missing linkage
- /// information, duplicate entity IDs, etc.) triggers a fatal error.
+ /// \returns Error if \p Summary reports a different target triple than this
+ /// link unit, if its TU namespace has already been linked, or if
+ /// patching fails; success otherwise. Corrupted summary data
+ /// (missing linkage information, duplicate entity IDs, etc.)
+ /// triggers a fatal error.
llvm::Error link(std::unique_ptr<TUSummaryEncoding> Summary);
+ /// Links every member of a static library into the LU summary.
+ ///
+ /// Members are folded in unconditionally, in an unspecified order, exactly as
+ /// if each had been passed as an individual TU summary.
+ ///
+ /// \param Library The static library to link. Ownership is transferred.
+ /// \returns Error if \p Library reports a different target triple than this
+ /// link unit or if any member fails to link, success otherwise.
+ llvm::Error link(std::unique_ptr<StaticLibrary> Library);
+
+ /// Links the architecture member matching this link unit into the LU summary.
+ ///
+ /// Members for other architectures are discarded.
+ ///
+ /// \param Library The multi-arch static library to link. Ownership is
+ /// transferred.
+ /// \returns Error if \p Library has no member whose target triple equals this
+ /// link unit's, or if the selected member fails to link; success
+ /// otherwise.
+ llvm::Error link(std::unique_ptr<MultiArchStaticLibrary> Library);
+
/// Returns the accumulated LU summary.
///
/// \returns LU summary containing all the deduplicated and patched entity
@@ -57,6 +92,15 @@ class EntityLinker {
LUSummaryEncoding takeOutput() && { return std::move(Output); }
private:
+ /// Checks that an input belongs to this link unit's target.
+ ///
+ /// \param TargetTriple The triple of the input being linked.
+ /// \param InputNamespace The namespace naming that input in the diagnostic.
+ /// \returns Error if \p TargetTriple differs from this link unit's, success
+ /// otherwise.
+ llvm::Error checkTargetTriple(const llvm::Triple &TargetTriple,
+ const BuildNamespace &InputNamespace) const;
+
/// Resolves a TU entity name to an LU entity name and ID.
///
/// \param OldName The entity name in the TU namespace.
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
index f5ddafbb9d577..b900d5458e9b5 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
@@ -23,6 +23,7 @@
namespace clang::ssaf {
+class LinkCLI;
class MultiArchCreateCLI;
/// Represents a multi-architecture static library.
@@ -32,6 +33,8 @@ class MultiArchCreateCLI;
/// architectures; the wrapper's \c Namespace identifies that shared library and
/// every member's namespace must agree on its name.
class MultiArchStaticLibrary {
+ friend class EntityLinker;
+ friend class LinkCLI;
friend class MultiArchCreateCLI;
friend class SerializationFormat;
friend class TestFixture;
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h
index c74fc4b16e0b2..db1477a42e9b5 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h
@@ -23,6 +23,7 @@
namespace clang::ssaf {
+class LinkCLI;
class MultiArchCreateCLI;
class StaticLibraryCreateCLI;
@@ -30,8 +31,8 @@ class StaticLibraryCreateCLI;
///
/// A StaticLibrary bundles member translation units without performing
/// entity resolution, mirroring the role of ar / libtool -static / lib.exe
-/// in native build pipelines. It is consumed by the EntityLinker for
-/// selective inclusion when passed as a command line argument.
+/// in native build pipelines. It is consumed by the EntityLinker when passed
+/// as a command line argument.
///
/// Static libraries are single-architecture: every member's target triple
/// must equal the library's. Multi-architecture static libraries are
@@ -40,8 +41,13 @@ class StaticLibraryCreateCLI;
///
/// Members are stored as encoded TUSummaryEncoding objects: the
/// static-library tool never decodes per-entity payloads, and the linker
-/// consumes them as-is during its selective inclusion pass.
+/// consumes them as-is while folding them into its link unit.
+///
+/// TODO: The linker currently folds in every member. Restrict inclusion to
+/// the members a link unit actually references, as native linkers do.
class StaticLibrary {
+ friend class EntityLinker;
+ friend class LinkCLI;
friend class MultiArchCreateCLI;
friend class MultiArchStaticLibrary;
friend class SerializationFormat;
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Support/FormatProviders.h b/clang/include/clang/ScalableStaticAnalysis/Core/Support/FormatProviders.h
index e4a7b54033924..e671a7c2d088a 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Support/FormatProviders.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Support/FormatProviders.h
@@ -22,6 +22,7 @@
#include "clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisName.h"
#include "llvm/Support/FormatProviders.h"
#include "llvm/Support/raw_ostream.h"
+#include "llvm/TargetParser/Triple.h"
namespace llvm {
@@ -88,6 +89,13 @@ template <> struct format_provider<clang::ssaf::AnalysisName> {
}
};
+template <> struct format_provider<llvm::Triple> {
+ static void format(const llvm::Triple &Val, raw_ostream &OS,
+ StringRef Style) {
+ OS << llvm::Triple::normalize(Val.str());
+ }
+};
+
} // namespace llvm
#endif // LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_SUPPORT_FORMATPROVIDERS_H
diff --git a/clang/include/clang/ScalableStaticAnalysis/Tool/Utils.h b/clang/include/clang/ScalableStaticAnalysis/Tool/Utils.h
index 8ceb539a055de..968c97a552852 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Tool/Utils.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Tool/Utils.h
@@ -23,6 +23,7 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/WithColor.h"
+#include "llvm/TargetParser/Triple.h"
#include <string>
namespace clang::ssaf {
@@ -76,6 +77,20 @@ void loadPlugins(llvm::ArrayRef<std::string> Paths);
void initTool(int argc, const char **argv, llvm::StringRef Version,
llvm::cl::OptionCategory &Category, llvm::StringRef ToolHeading);
+//===----------------------------------------------------------------------===//
+// Target Triples
+//===----------------------------------------------------------------------===//
+
+/// Parses and validates a target triple supplied on the command line.
+///
+///
+/// \param FlagName The option supplying \p Value, named in the diagnostic.
+/// \param Value The triple as spelled by the user. Must not be empty.
+/// \returns The parsed triple. Calls fail() and exits if the architecture is
+/// unrecognized.
+llvm::Triple parseTargetTripleOrFail(llvm::StringRef FlagName,
+ llvm::StringRef Value);
+
//===----------------------------------------------------------------------===//
// Data Structures
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.cpp b/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.cpp
index 462978932a53d..b703efd8324fb 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.cpp
@@ -8,11 +8,15 @@
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/EntitySummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
#include "clang/ScalableStaticAnalysis/Core/Model/EntityLinkage.h"
#include "clang/ScalableStaticAnalysis/Core/Model/EntityName.h"
#include "clang/ScalableStaticAnalysis/Core/Support/ErrorBuilder.h"
#include "clang/ScalableStaticAnalysis/Core/Support/FormatProviders.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
#include <cassert>
using namespace clang::ssaf;
@@ -44,6 +48,16 @@ static constexpr const char *FailedToInsertEntityIntoOutputSummary =
static constexpr const char *DuplicateTUNamespace =
"failed to link TU summary: duplicate {0}";
+static constexpr const char *LinkingStaticLibraryMember =
+ "failed to link member {0} of static library {1}";
+
+static constexpr const char *MismatchedTargetTriple =
+ "target triple '{0}' of {1} does not match link unit target triple '{2}'";
+
+static constexpr const char *NoMemberForTargetTriple =
+ "multi-arch static library {0} has no member for target triple '{1}' "
+ "(available: {2})";
+
} // namespace ErrorMessages
static NestedBuildNamespace
@@ -180,7 +194,25 @@ EntityLinker::patch(const std::vector<EntitySummaryEncoding *> &PatchTargets,
return llvm::Error::success();
}
+llvm::Error
+EntityLinker::checkTargetTriple(const llvm::Triple &TargetTriple,
+ const BuildNamespace &InputNamespace) const {
+ if (TargetTriple != Output.TargetTriple) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::MismatchedTargetTriple,
+ TargetTriple, InputNamespace,
+ Output.TargetTriple)
+ .build();
+ }
+ return llvm::Error::success();
+}
+
llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
+ if (auto Err =
+ checkTargetTriple(Summary->TargetTriple, Summary->TUNamespace)) {
+ return Err;
+ }
+
auto [_, Inserted] = ProcessedTUNamespaces.insert(Summary->TUNamespace);
if (!Inserted) {
return ErrorBuilder::create(std::errc::invalid_argument,
@@ -195,3 +227,49 @@ llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
auto PatchTargets = merge(SummaryRef, EntityResolutionTable);
return patch(PatchTargets, EntityResolutionTable);
}
+
+llvm::Error EntityLinker::link(std::unique_ptr<StaticLibrary> Library) {
+ if (auto Err = checkTargetTriple(Library->TargetTriple, Library->Namespace)) {
+ return Err;
+ }
+
+ while (!Library->Members.empty()) {
+ auto Node = Library->Members.extract(Library->Members.begin());
+ const BuildNamespace MemberNamespace = Node.value()->TUNamespace;
+
+ if (auto Err = link(std::move(Node.value()))) {
+ return ErrorBuilder::wrap(std::move(Err))
+ .context(ErrorMessages::LinkingStaticLibraryMember, MemberNamespace,
+ Library->Namespace)
+ .build();
+ }
+ }
+
+ return llvm::Error::success();
+}
+
+llvm::Error
+EntityLinker::link(std::unique_ptr<MultiArchStaticLibrary> Library) {
+ auto MatchingMember = llvm::find_if(
+ Library->Members, [this](const std::unique_ptr<StaticLibrary> &Member) {
+ return Member->TargetTriple == Output.TargetTriple;
+ });
+
+ if (MatchingMember == Library->Members.end()) {
+ auto TargetTriples = llvm::map_range(
+ Library->Members, [](const std::unique_ptr<StaticLibrary> &Member) {
+ return llvm::Triple::normalize(Member->TargetTriple.str());
+ });
+ std::string Available = Library->Members.empty()
+ ? std::string("none")
+ : llvm::join(TargetTriples, ", ");
+
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::NoMemberForTargetTriple,
+ Library->Namespace, Output.TargetTriple,
+ Available)
+ .build();
+ }
+
+ return link(std::move(Library->Members.extract(MatchingMember).value()));
+}
diff --git a/clang/lib/ScalableStaticAnalysis/Tool/Utils.cpp b/clang/lib/ScalableStaticAnalysis/Tool/Utils.cpp
index 6a740f57741d7..03139f378186f 100644
--- a/clang/lib/ScalableStaticAnalysis/Tool/Utils.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Tool/Utils.cpp
@@ -56,6 +56,9 @@ constexpr const char *FileAlreadyExists = "File already exists";
constexpr const char *FailedToLoadPlugin = "failed to load plugin '{0}': {1}";
+constexpr const char *InvalidTargetTriple =
+ "invalid {0} '{1}': unrecognized architecture";
+
} // namespace ErrorMessages
llvm::StringRef ToolName;
@@ -148,6 +151,24 @@ void clang::ssaf::loadPlugins(llvm::ArrayRef<std::string> Paths) {
}
}
+llvm::Triple clang::ssaf::parseTargetTripleOrFail(llvm::StringRef FlagName,
+ llvm::StringRef Value) {
+ assert(!Value.empty() &&
+ "parseTargetTripleOrFail: triple value cannot be empty");
+
+ // Normalize so the components are moved to their proper places.
+ llvm::Triple T(llvm::Triple::normalize(Value));
+
+ // Only the architecture is validated. Validating vendor or OS rejects real
+ // targets like x86_64-unknown-linux-gnu. A misspelled vendor or OS is instead
+ // caught as a triple mismatch during linking or library creation.
+ if (T.getArch() == llvm::Triple::UnknownArch) {
+ fail(ErrorMessages::InvalidTargetTriple, FlagName, Value);
+ }
+
+ return T;
+}
+
void clang::ssaf::initTool(int argc, const char **argv, llvm::StringRef Version,
llvm::cl::OptionCategory &Category,
llvm::StringRef ToolHeading) {
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libord-reversed.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libord-reversed.json
new file mode 100644
index 0000000000000..0356200cb9aa5
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libord-reversed.json
@@ -0,0 +1,62 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at only_in_b#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "Internal"
+ }
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "tu-b.cpp"
+ },
+ "type": "TUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [
+ {
+ "id": 0,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at only_in_a#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "Internal"
+ }
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "tu-a.cpp"
+ },
+ "type": "TUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libord"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libtwo-2arch.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libtwo-2arch.json
new file mode 100644
index 0000000000000..c52597151dae3
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libtwo-2arch.json
@@ -0,0 +1,39 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libtwo"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "target_triple": "x86_64-apple-macosx",
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "x86.cpp"
+ },
+ "type": "TUSummary"
+ }
+ ],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libtwo"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libtwo"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-linux.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-linux.json
new file mode 100644
index 0000000000000..9bf5495c9e2f1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-linux.json
@@ -0,0 +1,11 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "linux.cpp"
+ },
+ "id_table": [],
+ "linkage_table": [],
+ "data": [],
+ "target_triple": "x86_64-unknown-linux-gnu",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-x86_64.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-x86_64.json
new file mode 100644
index 0000000000000..a482c204f77c7
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/tu-x86_64.json
@@ -0,0 +1,11 @@
+{
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "x86.cpp"
+ },
+ "id_table": [],
+ "linkage_table": [],
+ "data": [],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "TUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/help.test b/clang/test/Analysis/Scalable/ssaf-linker/help.test
index 7b0c21f066016..45d8e11a15e30 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/help.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/help.test
@@ -15,14 +15,15 @@
// CHECK-NEXT: Type "clang-ssaf-linker{{(\.exe)?}} <subcommand> --help" to get more help on a specific subcommand
// CHECK-EMPTY:
// CHECK-NEXT: OPTIONS:
-// CHECK-NEXT: -h - Alias for --help
-// CHECK-NEXT: --help - Display available options (--help-hidden for more)
-// CHECK-NEXT: --help-hidden - Display all available options
-// CHECK-NEXT: --help-list - Display list of available options (--help-list-hidden for more)
-// CHECK-NEXT: --help-list-hidden - Display list of all available options
-// CHECK-NEXT: -o <path> - Output file path
-// CHECK-NEXT: --print-all-options - Print all option values after command line parsing
-// CHECK-NEXT: --print-options - Print non-default options after command line parsing
-// CHECK-NEXT: --time - Enable timing
-// CHECK-NEXT: --verbose - Enable verbose output
-// CHECK-NEXT: --version - Display the version of this program
+// CHECK-NEXT: -h - Alias for --help
+// CHECK-NEXT: --help - Display available options (--help-hidden for more)
+// CHECK-NEXT: --help-hidden - Display all available options
+// CHECK-NEXT: --help-list - Display list of available options (--help-list-hidden for more)
+// CHECK-NEXT: --help-list-hidden - Display list of all available options
+// CHECK-NEXT: -o <path> - Output file path
+// CHECK-NEXT: --print-all-options - Print all option values after command line parsing
+// CHECK-NEXT: --print-options - Print non-default options after command line parsing
+// CHECK-NEXT: --target-triple=<triple> - Target triple of the link unit (defaults to the first input's; required when the first input is a multi-arch static library with several members)
+// CHECK-NEXT: --time - Enable timing
+// CHECK-NEXT: --verbose - Enable verbose output
+// CHECK-NEXT: --version - Display the version of this program
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/io.test b/clang/test/Analysis/Scalable/ssaf-linker/io.test
index 304f97db656f6..b2b17c9f094ea 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/io.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/io.test
@@ -6,15 +6,23 @@
// Malformed JSON input.
// RUN: not clang-ssaf-linker %S/Inputs/tu-malformed.json -o %t/out.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=BAD-JSON
-// BAD-JSON: clang-ssaf-linker: error: reading TUSummary from file '{{.*}}tu-malformed.json'
+// BAD-JSON: clang-ssaf-linker: error: Reading artifact '{{.*}}tu-malformed.json'
+// BAD-JSON-NEXT: reading ArtifactEncoding from file '{{.*}}tu-malformed.json'
// BAD-JSON-NEXT: {{.*}}: Invalid JSON value{{.*}}
// Missing required fields in otherwise valid JSON.
// RUN: not clang-ssaf-linker %S/Inputs/tu-missing-fields.json -o %t/out.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=MISSING-FIELDS
-// MISSING-FIELDS: clang-ssaf-linker: error: reading TUSummary from file '{{.*}}tu-missing-fields.json'
+// MISSING-FIELDS: clang-ssaf-linker: error: Reading artifact '{{.*}}tu-missing-fields.json'
+// MISSING-FIELDS-NEXT: reading ArtifactEncoding from file '{{.*}}tu-missing-fields.json'
// MISSING-FIELDS-NEXT: failed to read IdTable from field 'id_table': expected JSON array
+// An unrecognized artifact kind names every kind the linker can read.
+// RUN: not clang-ssaf-linker %S/Inputs/bad-artifact.json -o %t/out.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-ARTIFACT
+// BAD-ARTIFACT: clang-ssaf-linker: error: Reading artifact '{{.*}}bad-artifact.json'
+// BAD-ARTIFACT: unknown value 'BogusKind' for field 'type'
+
// Output file already exists.
// RUN: touch %t/out.json
// RUN: not clang-ssaf-linker %S/Inputs/tu-empty.json -o %t/out.json 2>&1 \
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
index 5bda8128ac0e1..e03c7799b2519 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linking-errors.test
@@ -5,24 +5,132 @@
// Linking the same TU namespace twice produces an error.
// RUN: not clang-ssaf-linker %S/Inputs/tu-empty.json %S/Inputs/tu-empty.json -o %t/lu.json 2>&1 \
-// RUN: | FileCheck %s --match-full-lines --check-prefix=DUP-NS
-// DUP-NS: clang-ssaf-linker: error: Linking summary '{{.*}}tu-empty.json'
-// DUP-NS-NEXT: failed to link TU summary: duplicate BuildNamespace(CompilationUnit, empty.cpp)
+// RUN: | FileCheck %s --match-full-lines --check-prefix=DUP-NS-TU
+// DUP-NS-TU: clang-ssaf-linker: error: Linking artifact '{{.*}}tu-empty.json'
+// DUP-NS-TU-NEXT: failed to link TU summary: duplicate BuildNamespace(CompilationUnit, empty.cpp)
+
+// A static library member colliding with an already linked TU summary names the
+// member and the library it came from, on top of the input file context.
+// RUN: clang-ssaf-linker static-library create %S/Inputs/tu-1.json %S/Inputs/tu-2.json --namespace libtwo -o %t/libtwo.json
+// RUN: not clang-ssaf-linker %S/Inputs/tu-1.json %t/libtwo.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=DUP-MEMBER
+// DUP-MEMBER: clang-ssaf-linker: error: Linking artifact '{{.*}}libtwo.json'
+// DUP-MEMBER-NEXT: failed to link member BuildNamespace(CompilationUnit, tu1.cpp) of static library BuildNamespace(StaticLibrary, libtwo)
+// DUP-MEMBER-NEXT: failed to link TU summary: duplicate BuildNamespace(CompilationUnit, tu1.cpp)
// Entity ID object in summary data blob with '@' key alongside extra keys is a fatal error.
// RUN: not clang-ssaf-linker %S/Inputs/tu-invalid-entity-id-multikey.json -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=INVALID-ID-MULTIKEY
-// INVALID-ID-MULTIKEY: clang-ssaf-linker: error: Linking summary '{{.*}}tu-invalid-entity-id-multikey.json'
+// INVALID-ID-MULTIKEY: clang-ssaf-linker: error: Linking artifact '{{.*}}tu-invalid-entity-id-multikey.json'
// INVALID-ID-MULTIKEY-NEXT: failed to read EntityId: expected JSON object with a single '@' key mapped to a number (unsigned 64-bit integer)
// Entity ID object in summary data blob with a non-uint64 '@' value is a fatal error.
// RUN: not clang-ssaf-linker %S/Inputs/tu-invalid-entity-id-value.json -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=INVALID-ID-VALUE
-// INVALID-ID-VALUE: clang-ssaf-linker: error: Linking summary '{{.*}}tu-invalid-entity-id-value.json'
+// INVALID-ID-VALUE: clang-ssaf-linker: error: Linking artifact '{{.*}}tu-invalid-entity-id-value.json'
// INVALID-ID-VALUE-NEXT: failed to read EntityId: expected JSON object with a single '@' key mapped to a number (unsigned 64-bit integer)
// Entity ID reference in summary data blob pointing to an ID absent from the resolution table
// RUN: not clang-ssaf-linker %S/Inputs/tu-invalid-entity-id-ref.json -o %t/lu.json 2>&1 \
// RUN: | FileCheck %s --match-full-lines --check-prefix=INVALID-ID-REF
-// INVALID-ID-REF: clang-ssaf-linker: error: Linking summary '{{.*}}tu-invalid-entity-id-ref.json'
+// INVALID-ID-REF: clang-ssaf-linker: error: Linking artifact '{{.*}}tu-invalid-entity-id-ref.json'
// INVALID-ID-REF-NEXT: failed to patch EntityId: 'EntityId(99)' not found in entity resolution table
+
+// ============================================================================
+// Target triple mismatches
+//
+// The link unit's triple comes from the first input (or --target-triple), and
+// every later input must agree with it. EntityLinker reports the disagreement
+// and the command line adds the input's path as context.
+// ============================================================================
+
+// A TU summary disagreeing with a triple inferred from a preceding TU summary,
+// with no --target-triple involved at all.
+// RUN: not clang-ssaf-linker %S/Inputs/tu-1.json %S/Inputs/tu-x86_64.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=TRIPLE-INFERRED-TU
+// TRIPLE-INFERRED-TU: target triple 'x86_64-apple-macosx' of BuildNamespace(CompilationUnit, x86.cpp) does not match link unit target triple 'arm64-apple-macosx'
+
+// A static library disagreeing with a preceding input.
+// RUN: not clang-ssaf-linker %S/Inputs/tu-1.json %S/Inputs/lib-x86_64.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=TRIPLE-MISMATCH-LIB
+// TRIPLE-MISMATCH-LIB: clang-ssaf-linker: error: Linking artifact '{{.*}}lib-x86_64.json'
+// TRIPLE-MISMATCH-LIB-NEXT: target triple 'x86_64-apple-macosx' of BuildNamespace(StaticLibrary, libmulti) does not match link unit target triple 'arm64-apple-macosx'
+
+// A TU summary disagreeing with a triple inferred from a preceding library,
+// rather than from a preceding TU summary or --target-triple.
+// RUN: not clang-ssaf-linker %S/Inputs/lib-x86_64.json %S/Inputs/tu-1.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=TRIPLE-FROM-LIB
+// TRIPLE-FROM-LIB: target triple 'arm64-apple-macosx' of BuildNamespace(CompilationUnit, tu1.cpp) does not match link unit target triple 'x86_64-apple-macosx'
+
+// A multi-arch static library carrying no member for the link unit's triple
+// reports the members it does carry.
+// RUN: not clang-ssaf-linker --target-triple arm64-apple-ios %S/Inputs/libtwo-2arch.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MEMBER-MISSING
+// MEMBER-MISSING: clang-ssaf-linker: error: Linking artifact '{{.*}}libtwo-2arch.json'
+// MEMBER-MISSING-NEXT: multi-arch static library BuildNamespace(MultiArchStaticLibrary, libtwo) has no member for target triple 'arm64-apple-ios' (available: arm64-apple-macosx, x86_64-apple-macosx)
+
+// A bundle with no members at all still reports which triple was wanted.
+// RUN: not clang-ssaf-linker --target-triple arm64-apple-macosx %S/Inputs/libmulti-empty-wrapper.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=EMPTY-BUNDLE
+// EMPTY-BUNDLE: multi-arch static library BuildNamespace(MultiArchStaticLibrary, libmulti) has no member for target triple 'arm64-apple-macosx' (available: none)
+
+// ============================================================================
+// Target triple inference failures
+// ============================================================================
+
+// More than one member in the first input: the architecture has to be chosen.
+// RUN: not clang-ssaf-linker %S/Inputs/libtwo-2arch.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=AMBIGUOUS-MEMBERS
+// AMBIGUOUS-MEMBERS: cannot infer target triple from '{{.*}}libtwo-2arch.json': multi-arch static library has 2 members; pass --target-triple to select one
+
+// No members in the first input: there is nothing to infer from.
+// RUN: not clang-ssaf-linker %S/Inputs/libmulti-empty-wrapper.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=INFER-NO-MEMBERS
+// INFER-NO-MEMBERS: cannot infer target triple from '{{.*}}libmulti-empty-wrapper.json': multi-arch static library has no members; pass --target-triple
+
+// ============================================================================
+// Shared-library inputs are not linkable yet
+// ============================================================================
+
+// RUN: not clang-ssaf-linker %S/Inputs/lu-arm64.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=LU-FIRST
+// LU-FIRST: '{{.*}}lu-arm64.json' is a link unit summary: linking against shared libraries is not yet supported
+
+// RUN: not clang-ssaf-linker %S/Inputs/libfoo-2arch.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MULTI-ARCH-SHARED-INPUT
+// MULTI-ARCH-SHARED-INPUT: '{{.*}}libfoo-2arch.json' is a multi-arch shared library: linking against shared libraries is not yet supported
+
+// Rejected wherever they appear, not just in first position.
+// RUN: not clang-ssaf-linker %S/Inputs/tu-1.json %S/Inputs/lu-arm64.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=LU-SECOND
+// LU-SECOND: '{{.*}}lu-arm64.json' is a link unit summary: linking against shared libraries is not yet supported
+
+// ============================================================================
+// Malformed --target-triple
+//
+// Only the architecture is validated; a vendor or OS spelled oddly is accepted
+// and simply means "unspecified", so linking.test covers those as acceptances
+// rather than rejections.
+// ============================================================================
+
+// RUN: not clang-ssaf-linker --target-triple bogus %S/Inputs/tu-1.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-TRIPLE-ARCH
+// BAD-TRIPLE-ARCH: clang-ssaf-linker: error: invalid --target-triple 'bogus': unrecognized architecture
+
+// ============================================================================
+// Error ordering: inputs are folded in as they are read, so the first input
+// that cannot be accepted is the one reported. These two runs share the same
+// three inputs in different orders and must report different errors.
+// ============================================================================
+
+// The triple mismatch at input 2 is reported; input 3's duplicate namespace is
+// never reached.
+// RUN: not clang-ssaf-linker %S/Inputs/tu-1.json %S/Inputs/lib-x86_64.json %S/Inputs/tu-1.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=ORDER-TRIPLE-FIRST --implicit-check-not="duplicate BuildNamespace"
+// ORDER-TRIPLE-FIRST: target triple 'x86_64-apple-macosx' of BuildNamespace(StaticLibrary, libmulti) does not match link unit target triple 'arm64-apple-macosx'
+
+// The duplicate namespace at input 2 is reported; input 3's triple mismatch is
+// never reached.
+// RUN: not clang-ssaf-linker %S/Inputs/tu-1.json %S/Inputs/tu-1.json %S/Inputs/lib-x86_64.json -o %t/lu.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=ORDER-DUP-FIRST --implicit-check-not="does not match link unit target triple"
+// ORDER-DUP-FIRST: failed to link TU summary: duplicate BuildNamespace(CompilationUnit, tu1.cpp)
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/linking.test b/clang/test/Analysis/Scalable/ssaf-linker/linking.test
index a6bd0c8df22c8..8708c4c64f003 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/linking.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/linking.test
@@ -40,3 +40,126 @@
// RUN: cd %t && clang-ssaf-linker %S/Inputs/tu-1.json -o lu-1.json
// RUN: diff %S/Outputs/lu-1.json %t/lu-1.json
// RUN: rm %t/lu-1.json
+
+// ============================================================================
+// Static libraries are folded in as bundles of TU summaries
+//
+// The library below bundles tu-1 and tu-2, so linking it is equivalent to
+// passing those two TU summaries on the command line. Members are folded in TU
+// namespace order, which here matches the command line order above.
+// ============================================================================
+
+// Both libraries are derived from tu-1 and tu-2 rather than stored as fixtures:
+// static-library-create.test and multi-arch-create.test already pin what these
+// two commands produce, so a checked-in copy would duplicate them.
+// RUN: clang-ssaf-linker static-library create %S/Inputs/tu-1.json %S/Inputs/tu-2.json --namespace libtwo -o %t/libtwo.json
+// RUN: clang-ssaf-linker multi-arch create %t/libtwo.json -o %t/libtwo-1arch.json
+
+// RUN: clang-ssaf-linker %t/libtwo.json -o %t/lu-1+2.json
+// RUN: diff %S/Outputs/lu-1+2.json %t/lu-1+2.json
+// RUN: rm %t/lu-1+2.json
+
+// A static library with no members contributes nothing, exactly as an empty TU
+// summary does.
+// RUN: clang-ssaf-linker %S/Inputs/lib-arm64.json -o %t/lu-empty.json
+// RUN: diff %S/Outputs/lu-empty.json %t/lu-empty.json
+// RUN: rm %t/lu-empty.json
+
+// Members are folded in TU namespace order, not in the order they happen to
+// appear in the library file. Inputs/libord-reversed.json lists tu-b.cpp before
+// tu-a.cpp, yet tu-a.cpp's entity is assigned the first entity ID.
+// RUN: clang-ssaf-linker %S/Inputs/libord-reversed.json -o %t/lu-ord.json
+// RUN: FileCheck %s --check-prefix=FOLD-ORDER --input-file=%t/lu-ord.json
+// FOLD-ORDER: "id_table": [
+// FOLD-ORDER: "id": 0,
+// FOLD-ORDER: "name": "tu-a.cpp"
+// FOLD-ORDER: "id": 1,
+// FOLD-ORDER: "name": "tu-b.cpp"
+// RUN: rm %t/lu-ord.json
+
+// Libraries and bare TU summaries mix freely in one invocation.
+// RUN: clang-ssaf-linker %S/Inputs/tu-empty.json %t/libtwo.json -o %t/lu-1+2.json
+// RUN: diff %S/Outputs/lu-1+2.json %t/lu-1+2.json
+// RUN: rm %t/lu-1+2.json
+
+// ============================================================================
+// Multi-arch static libraries contribute the member matching the link unit
+// ============================================================================
+
+// A single-member bundle names its target unambiguously, so no --target-triple
+// is needed and the result matches linking the member directly.
+// RUN: clang-ssaf-linker %t/libtwo-1arch.json -o %t/lu-1+2.json
+// RUN: diff %S/Outputs/lu-1+2.json %t/lu-1+2.json
+// RUN: rm %t/lu-1+2.json
+
+// A bundle in a later position is resolved against the triple the first input
+// already fixed, rather than contributing one of its own.
+// RUN: clang-ssaf-linker %S/Inputs/tu-empty.json %t/libtwo-1arch.json -o %t/lu-1+2.json
+// RUN: diff %S/Outputs/lu-1+2.json %t/lu-1+2.json
+// RUN: rm %t/lu-1+2.json
+
+// Only the selected member is folded in. Inputs/libtwo-2arch.json pairs an
+// empty arm64 member with an x86_64 member that has a member, so selecting arm64
+// must produce an empty link unit -- folding the wrong member, or both, would
+// pull x86.cpp in.
+// RUN: clang-ssaf-linker --target-triple arm64-apple-macosx %S/Inputs/libtwo-2arch.json -o %t/lu-empty.json
+// RUN: diff %S/Outputs/lu-empty.json %t/lu-empty.json
+// RUN: rm %t/lu-empty.json
+
+// Members are matched on the triple's canonical components, not its spelling:
+// "aarch64" selects the "arm64" member. The link unit records the spelling that
+// was requested rather than the member's own.
+// RUN: clang-ssaf-linker --target-triple aarch64-apple-macosx %S/Inputs/libtwo-2arch.json -o %t/lu-aarch64.json
+// RUN: FileCheck %s --check-prefix=ALIAS-MEMBER --input-file=%t/lu-aarch64.json
+// ALIAS-MEMBER: "target_triple": "aarch64-apple-macosx",
+// RUN: rm %t/lu-aarch64.json
+
+// An OS version does not distinguish a member either, and the link unit again
+// records the requested spelling.
+// RUN: clang-ssaf-linker --target-triple arm64-apple-macosx15.0 %S/Inputs/libtwo-2arch.json -o %t/lu-versioned.json
+// RUN: FileCheck %s --check-prefix=VERSIONED-MEMBER --input-file=%t/lu-versioned.json
+// VERSIONED-MEMBER: "target_triple": "arm64-apple-macosx15.0",
+// RUN: rm %t/lu-versioned.json
+
+// An explicit --target-triple agreeing with the inputs is accepted and does not
+// change the result.
+// RUN: clang-ssaf-linker --target-triple arm64-apple-macosx %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/lu-1+2.json
+// RUN: diff %S/Outputs/lu-1+2.json %t/lu-1+2.json
+// RUN: rm %t/lu-1+2.json
+
+// ============================================================================
+// Target triples are not restricted to Apple platforms
+//
+// "unknown" and "none" are the conventional spellings for an unspecified
+// vendor or OS, so a triple that uses them is well formed and takes part in
+// triple resolution like any other.
+// ============================================================================
+
+// Inference needs no flag off-Apple either: the link unit adopts the input's
+// own triple and records it.
+// RUN: clang-ssaf-linker %S/Inputs/tu-linux.json -o %t/lu-linux.json
+// RUN: FileCheck %s --check-prefix=LINUX-INFERRED --input-file=%t/lu-linux.json
+// LINUX-INFERRED: "target_triple": "x86_64-unknown-linux-gnu",
+// RUN: rm %t/lu-linux.json
+
+// RUN: not clang-ssaf-linker --target-triple wasm32-unknown-unknown %S/Inputs/libtwo-2arch.json -o %t/lu-wasm.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=WASM-TRIPLE
+// WASM-TRIPLE: has no member for target triple 'wasm32-unknown-unknown' (available: arm64-apple-macosx, x86_64-apple-macosx)
+
+// A component that belongs in a later field is not mistaken for an
+// unrecognized OS: "elf" here names an object format, and the triple is
+// reported in the normalized spelling the link unit would record.
+// RUN: not clang-ssaf-linker --target-triple riscv64-unknown-elf %S/Inputs/lib-arm64.json -o %t/lu-riscv.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=RISCV-TRIPLE
+// RISCV-TRIPLE: does not match link unit target triple 'riscv64-unknown-unknown-elf'
+
+// A component dropped off the end of the triple is absent rather than
+// malformed: arm64-apple names an architecture and a vendor but no OS.
+// RUN: not clang-ssaf-linker --target-triple arm64-apple %S/Inputs/lib-arm64.json -o %t/lu-no-os.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-OS-TRIPLE
+// NO-OS-TRIPLE: does not match link unit target triple 'arm64-apple'
+
+// "none" is likewise a conventional spelling for an unspecified component.
+// RUN: not clang-ssaf-linker --target-triple armv7-none-eabi %S/Inputs/lib-arm64.json -o %t/lu-eabi.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=EABI-TRIPLE
+// EABI-TRIPLE: does not match link unit target triple 'armv7-unknown-none-eabi'
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/time.test b/clang/test/Analysis/Scalable/ssaf-linker/time.test
index 3e5809692f049..665198da4c4b6 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/time.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/time.test
@@ -10,8 +10,8 @@
// CHECK-NEXT: ===-------------------------------------------------------------------------===
// CHECK-NEXT: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
// CHECK: {{.*}}---Wall Time---{{.*}}
-// CHECK-DAG: {{.*}}Write Summary
-// CHECK-DAG: {{.*}}Read Summaries
-// CHECK-DAG: {{.*}}Link Summaries
+// CHECK-DAG: {{.*}}Write Link Unit Summary
+// CHECK-DAG: {{.*}}Read Artifacts
+// CHECK-DAG: {{.*}}Link Artifacts
// CHECK-DAG: {{.*}}Validate Input
// CHECK: {{.*}}Total
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/verbose.test b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
index d731748ad7547..57578792746da 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/verbose.test
@@ -7,14 +7,55 @@
// RUN: | FileCheck %s --match-full-lines
// CHECK: note: - Linking started.
// CHECK-NEXT: note: - Validating input.
-// CHECK-NEXT: note: - Validated output summary path '{{.*}}lu-1+2.json'.
-// CHECK-NEXT: note: - Validated 2 input summary paths.
-// CHECK-NEXT: note: - Linking input.
-// CHECK-NEXT: note: - Constructing linker.
-// CHECK-NEXT: note: - Linking summaries.
+// CHECK-NEXT: note: - Validated output path '{{.*}}lu-1+2.json'.
+// CHECK-NEXT: note: - Validated 2 input artifact path(s).
+// CHECK-NEXT: note: - Creating link unit.
+// CHECK-NEXT: note: - Linking artifacts.
// CHECK-NEXT: note: - [1/2] Reading '{{.*}}tu-1.json'.
+// CHECK-NEXT: note: - Target triple: 'arm64-apple-macosx' (inferred from '{{.*}}tu-1.json').
// CHECK-NEXT: note: - [1/2] Linking '{{.*}}tu-1.json'.
// CHECK-NEXT: note: - [2/2] Reading '{{.*}}tu-2.json'.
// CHECK-NEXT: note: - [2/2] Linking '{{.*}}tu-2.json'.
-// CHECK-NEXT: note: - Writing output summary to '{{.*}}lu-1+2.json'.
+// CHECK-NEXT: note: - Linked 2 translation unit(s).
+// CHECK-NEXT: note: - Target namespace: 'NestedBuildNamespace([BuildNamespace(LinkUnit, lu-1+2)])'.
+// CHECK-NEXT: note: - Writing link unit summary to '{{.*}}lu-1+2.json'.
// CHECK-NEXT: note: - Linking finished.
+
+// A library input reports how many members it contributes, and the closing
+// count is of translation units folded in, not of inputs on the command line.
+// Only the lines that differ from the run above are checked.
+
+// RUN: clang-ssaf-linker static-library create %S/Inputs/tu-1.json %S/Inputs/tu-2.json --namespace libtwo -o %t/libtwo.json
+// RUN: clang-ssaf-linker --verbose %t/libtwo.json %S/Inputs/lib-arm64.json -o %t/lu-libs.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=LIBS
+// LIBS: note: - [1/2] Linking '{{.*}}libtwo.json' (static library, 2 member(s)).
+// LIBS-NEXT: note: - [2/2] Reading '{{.*}}lib-arm64.json'.
+// LIBS-NEXT: note: - [2/2] Linking '{{.*}}lib-arm64.json' (static library, 0 member(s)).
+// LIBS-NEXT: note: - Linked 2 translation unit(s).
+
+// A multi-arch input reports how many members it carries.
+
+// RUN: clang-ssaf-linker multi-arch create %t/libtwo.json -o %t/libtwo-1arch.json
+// RUN: clang-ssaf-linker --verbose %t/libtwo-1arch.json -o %t/lu-member.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=MEMBER
+// MEMBER: note: - [1/1] Reading '{{.*}}libtwo-1arch.json'.
+// MEMBER-NEXT: note: - Target triple: 'arm64-apple-macosx' (inferred from '{{.*}}libtwo-1arch.json').
+// MEMBER-NEXT: note: - [1/1] Linking '{{.*}}libtwo-1arch.json' (multi-arch static library, 1 member(s)).
+// MEMBER-NEXT: note: - Linked 2 translation unit(s).
+
+// An explicit --target-triple is reported as such rather than as inferred.
+// Which member it selects is asserted by content in linking.test, not here; the
+// selected member of Inputs/libtwo-2arch.json is empty, hence no linked units.
+
+// RUN: clang-ssaf-linker --verbose --target-triple arm64-apple-macosx %S/Inputs/libtwo-2arch.json -o %t/lu-explicit.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=EXPLICIT
+// EXPLICIT: note: - Target triple: 'arm64-apple-macosx' (from --target-triple).
+// EXPLICIT-NEXT: note: - [1/1] Linking '{{.*}}libtwo-2arch.json' (multi-arch static library, 2 member(s)).
+// EXPLICIT-NEXT: note: - Linked 0 translation unit(s).
+
+// A run that cannot fix a target triple reports no triple and links nothing.
+
+// RUN: not clang-ssaf-linker --verbose %S/Inputs/libtwo-2arch.json -o %t/lu-none.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-TRIPLE --implicit-check-not="Target triple"
+// NO-TRIPLE: note: - [1/1] Reading '{{.*}}libtwo-2arch.json'.
+// NO-TRIPLE-NEXT: error: cannot infer target triple from '{{.*}}libtwo-2arch.json': multi-arch static library has 2 members; pass --target-triple to select one
diff --git a/clang/tools/clang-ssaf-linker/CMakeLists.txt b/clang/tools/clang-ssaf-linker/CMakeLists.txt
index c51c1f25ff9cc..4527a9a27ee24 100644
--- a/clang/tools/clang-ssaf-linker/CMakeLists.txt
+++ b/clang/tools/clang-ssaf-linker/CMakeLists.txt
@@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_tool(clang-ssaf-linker
+ LinkCLI.cpp
MultiArchCreateCLI.cpp
StaticLibraryCreateCLI.cpp
SSAFLinker.cpp
diff --git a/clang/tools/clang-ssaf-linker/LinkCLI.cpp b/clang/tools/clang-ssaf-linker/LinkCLI.cpp
new file mode 100644
index 0000000000000..1394aca11c49e
--- /dev/null
+++ b/clang/tools/clang-ssaf-linker/LinkCLI.cpp
@@ -0,0 +1,296 @@
+//===- LinkCLI.cpp --------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Implements the default (no subcommand) linking action. Inputs are read one
+// at a time and folded into the link unit as they are read, so the first
+// input that cannot be accepted is the one reported.
+//
+// The target triple is fixed from the first input (or from --target-triple)
+// before the linker is constructed. Validating every later input against it
+// happens here rather than in EntityLinker: choosing which inputs belong to
+// a target is a command line concern, and EntityLinker treats a mismatch as
+// a fatal precondition violation.
+//
+//===----------------------------------------------------------------------===//
+
+#include "LinkCLI.h"
+
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
+#include "clang/ScalableStaticAnalysis/Core/Support/ErrorBuilder.h"
+#include "clang/ScalableStaticAnalysis/Core/Support/FormatProviders.h"
+#include "llvm/ADT/Sequence.h"
+#include "llvm/Support/Path.h"
+#include <cassert>
+#include <memory>
+#include <utility>
+#include <variant>
+
+using namespace llvm;
+using namespace clang::ssaf;
+
+namespace path = llvm::sys::path;
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// Error Messages
+//===----------------------------------------------------------------------===//
+
+constexpr const char *ReadingArtifact = "Reading artifact '{0}'";
+
+constexpr const char *LinkingArtifact = "Linking artifact '{0}'";
+
+constexpr const char *NoInputs =
+ "no input artifacts: at least one input is required";
+
+constexpr const char *NoMembersToInferFrom =
+ "cannot infer target triple from '{0}': multi-arch static library has no "
+ "members; pass --target-triple";
+
+constexpr const char *AmbiguousMembersToInferFrom =
+ "cannot infer target triple from '{0}': multi-arch static library has {1} "
+ "members; pass --target-triple to select one";
+
+constexpr const char *UnsupportedSharedInput =
+ "'{0}' is a {1}: linking against shared libraries is not yet supported";
+
+constexpr const char *LinkUnitSummaryName = "link unit summary";
+constexpr const char *MultiArchSharedLibraryName = "multi-arch shared library";
+
+//===----------------------------------------------------------------------===//
+// ArtifactEncoding Helpers
+//===----------------------------------------------------------------------===//
+
+/// Returns the human readable kind of an artifact the linker cannot consume.
+///
+/// Only the shared-library family reaches this: every linkable alternative is
+/// handled before it is called. The static_assert makes a new alternative a
+/// compile error here rather than an unhandled case at runtime.
+llvm::StringRef unsupportedInputKindName(const ArtifactEncoding &E) {
+ static_assert(std::variant_size_v<ArtifactEncoding> == 5,
+ "unsupportedInputKindName must cover every ArtifactEncoding "
+ "alternative the linker cannot consume");
+
+ if (std::holds_alternative<LUSummaryEncoding>(E)) {
+ return LinkUnitSummaryName;
+ }
+
+ assert(
+ std::holds_alternative<MultiArchSharedLibrary>(E) &&
+ "linkable ArtifactEncoding alternatives must be handled by the caller");
+ return MultiArchSharedLibraryName;
+}
+
+} // namespace
+
+namespace clang::ssaf {
+
+void LinkCLI::run(llvm::TimerGroup &TG, llvm::ArrayRef<std::string> InputPaths,
+ llvm::StringRef OutputPath, llvm::StringRef TargetTriple,
+ bool Verbose, bool Time) {
+ this->InputPaths = InputPaths;
+ this->OutputPath = OutputPath;
+ this->TargetTriple = TargetTriple;
+ this->Verbose = Verbose;
+ this->Time = Time;
+
+ llvm::Timer TValidate("validate", "Validate Input", TG);
+ llvm::Timer TRead("read", "Read Artifacts", TG);
+ llvm::Timer TLink("link", "Link Artifacts", TG);
+ llvm::Timer TWrite("write", "Write Link Unit Summary", TG);
+
+ // Nesting depth for indenting verbose notes.
+ const unsigned Level = 0;
+
+ info(Verbose, Level, "Linking started.");
+
+ validate(Level + 1, TValidate);
+
+ LUSummaryEncoding Output = link(Level + 1, TRead, TLink);
+
+ write(Output, Level + 1, TWrite);
+
+ info(Verbose, Level, "Linking finished.");
+
+ // A second run() should start from a clean slate.
+ InputFiles.clear();
+ ExplicitTriple.reset();
+ ResolvedTriple.reset();
+}
+
+void LinkCLI::validate(unsigned Level, llvm::Timer &TValidate) {
+ info(Verbose, Level, "Validating input.");
+
+ llvm::TimeRegion _(Time ? &TValidate : nullptr);
+
+ OutputFile = FormatFile::fromOutputPath(OutputPath);
+ LinkUnitName = path::stem(OutputFile.Path).str();
+ info(Verbose, Level + 1, "Validated output path '{0}'.", OutputFile.Path);
+
+ if (InputPaths.empty()) {
+ fail(NoInputs);
+ }
+ for (const auto &InputPath : InputPaths) {
+ InputFiles.push_back(FormatFile::fromInputPath(InputPath));
+ }
+ info(Verbose, Level + 1, "Validated {0} input artifact path(s).",
+ InputFiles.size());
+
+ if (!TargetTriple.empty()) {
+ ExplicitTriple = parseTargetTripleOrFail("--target-triple", TargetTriple);
+ }
+}
+
+LUSummaryEncoding LinkCLI::link(unsigned Level, llvm::Timer &TRead,
+ llvm::Timer &TLink) {
+ info(Verbose, Level, "Creating link unit.");
+
+ const unsigned InputLevel = Level + 1;
+ info(Verbose, InputLevel, "Linking artifacts.");
+
+ // The target triple comes from the first input, so it has to be read before
+ // the linker can be constructed.
+ constexpr size_t FirstIndex = 0;
+ ArtifactEncoding First = readInput(FirstIndex, InputLevel + 1, TRead);
+
+ ResolvedTriple = resolveTargetTriple(First, InputLevel + 1);
+
+ NestedBuildNamespace LUNamespace(
+ BuildNamespace(BuildNamespaceKind::LinkUnit, LinkUnitName));
+ EntityLinker EL(*ResolvedTriple, LUNamespace);
+
+ linkInput(EL, std::move(First), FirstIndex, InputLevel + 1, TLink);
+ for (size_t Index : llvm::seq<size_t>(FirstIndex + 1, InputFiles.size())) {
+ linkInput(EL, readInput(Index, InputLevel + 1, TRead), Index,
+ InputLevel + 1, TLink);
+ }
+
+ info(Verbose, InputLevel, "Linked {0} translation unit(s).",
+ EL.ProcessedTUNamespaces.size());
+ info(Verbose, InputLevel, "Target namespace: '{0}'.", LUNamespace);
+
+ return std::move(EL).takeOutput();
+}
+
+ArtifactEncoding LinkCLI::readInput(size_t Index, unsigned Level,
+ llvm::Timer &TRead) {
+ const FormatFile &InputFile = InputFiles[Index];
+ info(Verbose, Level, "[{0}/{1}] Reading '{2}'.", Index + 1, InputFiles.size(),
+ InputFile.Path);
+
+ llvm::TimeRegion _(Time ? &TRead : nullptr);
+
+ auto ExpectedEncoding =
+ InputFile.Format->readArtifactEncoding(InputFile.Path);
+ if (!ExpectedEncoding) {
+ fail(ErrorBuilder::wrap(ExpectedEncoding.takeError())
+ .context(ReadingArtifact, InputFile.Path)
+ .build());
+ }
+ return std::move(*ExpectedEncoding);
+}
+
+llvm::Triple LinkCLI::resolveTargetTriple(const ArtifactEncoding &First,
+ unsigned Level) {
+ if (ExplicitTriple) {
+ info(Verbose, Level, "Target triple: '{0}' (from --target-triple).",
+ *ExplicitTriple);
+ return *ExplicitTriple;
+ }
+
+ llvm::StringRef SourceFile = InputFiles[0].Path;
+
+ auto Inferred = [&]() -> llvm::Triple {
+ if (const auto *TU = std::get_if<TUSummaryEncoding>(&First)) {
+ return TU->getTargetTriple();
+ }
+
+ if (const auto *SL = std::get_if<StaticLibrary>(&First)) {
+ return SL->TargetTriple;
+ }
+
+ if (const auto *MASL = std::get_if<MultiArchStaticLibrary>(&First)) {
+ // A single member names the target unambiguously; anything else needs the
+ // architecture to be chosen on the command line.
+ if (MASL->Members.empty()) {
+ fail(NoMembersToInferFrom, SourceFile);
+ }
+ if (MASL->Members.size() > 1) {
+ fail(AmbiguousMembersToInferFrom, SourceFile, MASL->Members.size());
+ }
+ return (*MASL->Members.begin())->TargetTriple;
+ }
+
+ fail(UnsupportedSharedInput, SourceFile, unsupportedInputKindName(First));
+ }();
+
+ info(Verbose, Level, "Target triple: '{0}' (inferred from '{1}').", Inferred,
+ SourceFile);
+
+ return Inferred;
+}
+
+void LinkCLI::linkInput(EntityLinker &EL, ArtifactEncoding Encoding,
+ size_t Index, unsigned Level, llvm::Timer &TLink) {
+ llvm::StringRef SourceFile = InputFiles[Index].Path;
+
+ auto foldIn = [&](llvm::Error Err) {
+ if (Err) {
+ fail(ErrorBuilder::wrap(std::move(Err))
+ .context(LinkingArtifact, SourceFile)
+ .build());
+ }
+ };
+
+ if (auto *TU = std::get_if<TUSummaryEncoding>(&Encoding)) {
+ info(Verbose, Level, "[{0}/{1}] Linking '{2}'.", Index + 1,
+ InputFiles.size(), SourceFile);
+ llvm::TimeRegion _(Time ? &TLink : nullptr);
+
+ foldIn(EL.link(std::make_unique<TUSummaryEncoding>(std::move(*TU))));
+ return;
+ }
+
+ if (auto *SL = std::get_if<StaticLibrary>(&Encoding)) {
+ info(Verbose, Level,
+ "[{0}/{1}] Linking '{2}' (static library, {3} member(s)).", Index + 1,
+ InputFiles.size(), SourceFile, SL->Members.size());
+ llvm::TimeRegion _(Time ? &TLink : nullptr);
+
+ foldIn(EL.link(std::make_unique<StaticLibrary>(std::move(*SL))));
+ return;
+ }
+
+ if (auto *MASL = std::get_if<MultiArchStaticLibrary>(&Encoding)) {
+ info(Verbose, Level,
+ "[{0}/{1}] Linking '{2}' (multi-arch static library, {3} member(s)).",
+ Index + 1, InputFiles.size(), SourceFile, MASL->Members.size());
+ llvm::TimeRegion _(Time ? &TLink : nullptr);
+
+ foldIn(EL.link(std::make_unique<MultiArchStaticLibrary>(std::move(*MASL))));
+ return;
+ }
+
+ fail(UnsupportedSharedInput, SourceFile, unsupportedInputKindName(Encoding));
+}
+
+void LinkCLI::write(const LUSummaryEncoding &Output, unsigned Level,
+ llvm::Timer &TWrite) {
+ info(Verbose, Level, "Writing link unit summary to '{0}'.", OutputFile.Path);
+
+ llvm::TimeRegion _(Time ? &TWrite : nullptr);
+
+ if (auto Err =
+ OutputFile.Format->writeLUSummaryEncoding(Output, OutputFile.Path)) {
+ fail(std::move(Err));
+ }
+}
+
+} // namespace clang::ssaf
diff --git a/clang/tools/clang-ssaf-linker/LinkCLI.h b/clang/tools/clang-ssaf-linker/LinkCLI.h
new file mode 100644
index 0000000000000..678306c0ac4a7
--- /dev/null
+++ b/clang/tools/clang-ssaf-linker/LinkCLI.h
@@ -0,0 +1,106 @@
+//===- LinkCLI.h ------------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Declares the CLI action class for the default (no subcommand) action of
+// `clang-ssaf-linker`: linking TU summaries, static libraries, and members of
+// multi-arch static libraries into one LU summary. There is no `link` verb or
+// subcommand; the class name only mirrors its sibling action classes.
+//
+// The class is intentionally independent of the tool's cl::opt globals.
+// Every input it needs is passed to run(), so the class can be reused or
+// unit-tested outside the driver.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_LINKCLI_H
+#define LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_LINKCLI_H
+
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h"
+#include "clang/ScalableStaticAnalysis/Tool/Utils.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Timer.h"
+#include "llvm/TargetParser/Triple.h"
+#include <cstddef>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace clang::ssaf {
+
+/// Runs the default linking action for `clang-ssaf-linker`.
+class LinkCLI {
+public:
+ /// Orchestrates validation, linking, and serialization of the LU summary.
+ /// Non-recoverable errors call fail() from Tool/Utils.h and terminate the
+ /// process.
+ void run(llvm::TimerGroup &TG, llvm::ArrayRef<std::string> InputPaths,
+ llvm::StringRef OutputPath, llvm::StringRef TargetTriple,
+ bool Verbose, bool Time);
+
+private:
+ /// Validates the output path and every input path, derives the link unit
+ /// name, and validates TargetTriple if it is set.
+ void validate(unsigned Level, llvm::Timer &TValidate);
+
+ /// Reads the inputs and folds each into one link unit, in command line
+ /// order.
+ ///
+ /// \returns The accumulated LU summary.
+ LUSummaryEncoding link(unsigned Level, llvm::Timer &TRead,
+ llvm::Timer &TLink);
+
+ /// Reads the artifact at \p Index.
+ ArtifactEncoding readInput(size_t Index, unsigned Level, llvm::Timer &TRead);
+
+ /// Determines the link unit's target triple.
+ ///
+ /// An explicit --target-triple wins. Otherwise the triple is inferred from
+ /// \p First: its own for a TU summary or a static library, and its sole
+ /// member's for a single-member multi-arch static library. Any other shape
+ /// cannot be inferred from and requires --target-triple.
+ llvm::Triple resolveTargetTriple(const ArtifactEncoding &First,
+ unsigned Level);
+
+ /// Folds one input into \p EL, reporting whatever EntityLinker rejects --
+ /// including an input that does not belong to the resolved target -- with the
+ /// input's path as context.
+ void linkInput(EntityLinker &EL, ArtifactEncoding Encoding, size_t Index,
+ unsigned Level, llvm::Timer &TLink);
+
+ /// Serializes the LU summary to the validated output path.
+ void write(const LUSummaryEncoding &Output, unsigned Level,
+ llvm::Timer &TWrite);
+
+ // Arguments captured by run() before dispatching to linking methods.
+ // InputPaths, OutputPath, and TargetTriple are non-owning: they alias the
+ // driver's cl::opt storage, which outlives the call.
+ llvm::ArrayRef<std::string> InputPaths;
+ llvm::StringRef OutputPath;
+ llvm::StringRef TargetTriple;
+ bool Verbose = false;
+ bool Time = false;
+
+ // State populated during validate() and consumed by later phases.
+ FormatFile OutputFile;
+ std::vector<FormatFile> InputFiles;
+ std::string LinkUnitName;
+
+ // Set by validate() only when TargetTriple was passed and valid.
+ std::optional<llvm::Triple> ExplicitTriple;
+
+ // The link unit's target triple, fixed by resolveTargetTriple() before the
+ // first input is folded in. Every later input must match it.
+ std::optional<llvm::Triple> ResolvedTriple;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_LINKCLI_H
diff --git a/clang/tools/clang-ssaf-linker/SSAFLinker.cpp b/clang/tools/clang-ssaf-linker/SSAFLinker.cpp
index 9d72e9f338328..79fc34e4deb2d 100644
--- a/clang/tools/clang-ssaf-linker/SSAFLinker.cpp
+++ b/clang/tools/clang-ssaf-linker/SSAFLinker.cpp
@@ -7,7 +7,8 @@
//===----------------------------------------------------------------------===//
//
// This file implements the SSAF entity linker tool. Its default behavior is to
-// link N TU summaries into one LU summary via the EntityLinker framework. It
+// link N inputs -- TU summaries, static libraries, and members of multi-arch
+// static libraries -- into one LU summary via the EntityLinker framework. It
// also provides the `static-library` subcommand for bundling TU summaries into
// a StaticLibrary, and the `multi-arch` subcommand for bundling StaticLibrary
// and SharedLibrary members (or existing multi-arch bundles) into
@@ -15,32 +16,21 @@
//
//===----------------------------------------------------------------------===//
+#include "LinkCLI.h"
#include "MultiArchCreateCLI.h"
#include "StaticLibraryCreateCLI.h"
-#include "clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h"
-#include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h"
-#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
-#include "clang/ScalableStaticAnalysis/Core/Support/ErrorBuilder.h"
#include "clang/ScalableStaticAnalysis/SSAFForceLinker.h" // IWYU pragma: keep
#include "clang/ScalableStaticAnalysis/Tool/Utils.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/CommandLine.h"
-#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/InitLLVM.h"
-#include "llvm/Support/Path.h"
#include "llvm/Support/Timer.h"
-#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"
-#include <memory>
#include <string>
using namespace llvm;
using namespace clang::ssaf;
-namespace path = llvm::sys::path;
-
namespace {
//===----------------------------------------------------------------------===//
@@ -66,6 +56,14 @@ cl::opt<std::string> OutputPath("o", cl::desc("Output file path"),
cl::value_desc("path"), cl::Required,
cl::cat(SsafLinkerCategory));
+cl::opt<std::string> TargetTriple(
+ "target-triple",
+ cl::desc(
+ "Target triple of the link unit (defaults to the first input's; "
+ "required when the first input is a multi-arch static library with "
+ "several members)"),
+ cl::value_desc("triple"), cl::cat(SsafLinkerCategory));
+
// --verbose and --time apply to every subcommand.
cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
cl::init(false), cl::cat(SsafLinkerCategory),
@@ -152,8 +150,6 @@ constexpr const char *MultiArchCreateVerb = "create";
namespace LocalErrorMessages {
-constexpr const char *LinkingSummary = "Linking summary '{0}'";
-
constexpr const char *UnknownStaticLibraryVerb =
"unknown static-library verb '{0}': expected 'create'";
@@ -163,110 +159,12 @@ constexpr const char *UnknownMultiArchVerb =
} // namespace LocalErrorMessages
//===----------------------------------------------------------------------===//
-// link action
+// default (no subcommand) link action
//===----------------------------------------------------------------------===//
-struct LinkerInput {
- std::vector<FormatFile> InputFiles;
- FormatFile OutputFile;
- std::string LinkUnitName;
-};
-
-LinkerInput validateLinkInput(llvm::TimerGroup &TG) {
- llvm::Timer TValidate("validate", "Validate Input", TG);
- LinkerInput LI;
-
- {
- llvm::TimeRegion _(Time ? &TValidate : nullptr);
-
- LI.OutputFile = FormatFile::fromOutputPath(OutputPath);
- LI.LinkUnitName = path::stem(LI.OutputFile.Path).str();
- }
-
- info(Verbose, 2, "Validated output summary path '{0}'.", LI.OutputFile.Path);
-
- {
- llvm::TimeRegion _(Time ? &TValidate : nullptr);
- for (const auto &InputPath : InputPaths) {
- LI.InputFiles.push_back(FormatFile::fromInputPath(InputPath));
- }
- }
-
- info(Verbose, 2, "Validated {0} input summary paths.", LI.InputFiles.size());
-
- return LI;
-}
-
void runLink(llvm::TimerGroup &TG) {
- info(Verbose, 0, "Linking started.");
-
- LinkerInput LI;
- {
- info(Verbose, 1, "Validating input.");
- LI = validateLinkInput(TG);
- }
-
- info(Verbose, 1, "Linking input.");
- info(Verbose, 2, "Constructing linker.");
-
- // TODO: The linker currently uses a hardcoded target triple. Architecture
- // tracking in the linker will be handled properly in a separate PR.
- EntityLinker EL(llvm::Triple("arm64-apple-macosx"),
- NestedBuildNamespace(BuildNamespace(
- BuildNamespaceKind::LinkUnit, LI.LinkUnitName)));
-
- llvm::Timer TRead("read", "Read Summaries", TG);
- llvm::Timer TLink("link", "Link Summaries", TG);
- llvm::Timer TWrite("write", "Write Summary", TG);
-
- info(Verbose, 2, "Linking summaries.");
-
- for (auto [Index, InputFile] : llvm::enumerate(LI.InputFiles)) {
- std::unique_ptr<TUSummaryEncoding> Summary;
-
- {
- info(Verbose, 3, "[{0}/{1}] Reading '{2}'.", (Index + 1),
- LI.InputFiles.size(), InputFile.Path);
-
- llvm::TimeRegion _(Time ? &TRead : nullptr);
-
- auto ExpectedSummaryEncoding =
- InputFile.Format->readTUSummaryEncoding(InputFile.Path);
- if (!ExpectedSummaryEncoding) {
- fail(ExpectedSummaryEncoding.takeError());
- }
-
- Summary = std::make_unique<TUSummaryEncoding>(
- std::move(*ExpectedSummaryEncoding));
- }
-
- {
- info(Verbose, 3, "[{0}/{1}] Linking '{2}'.", (Index + 1),
- LI.InputFiles.size(), InputFile.Path);
-
- llvm::TimeRegion _(Time ? &TLink : nullptr);
-
- if (auto Err = EL.link(std::move(Summary))) {
- fail(ErrorBuilder::wrap(std::move(Err))
- .context(LocalErrorMessages::LinkingSummary, InputFile.Path)
- .build());
- }
- }
- }
-
- {
- info(Verbose, 2, "Writing output summary to '{0}'.", LI.OutputFile.Path);
-
- llvm::TimeRegion _(Time ? &TWrite : nullptr);
-
- auto Output = std::move(EL).takeOutput();
- if (auto Err = LI.OutputFile.Format->writeLUSummaryEncoding(
- Output, LI.OutputFile.Path)) {
- fail(std::move(Err));
- }
- }
-
- info(Verbose, 0, "Linking finished.");
+ LinkCLI LC;
+ LC.run(TG, InputPaths, OutputPath, TargetTriple, Verbose, Time);
}
//===----------------------------------------------------------------------===//
More information about the cfe-commits
mailing list