[clang] [clang][ssaf] Add multi-arch create subcommand (PR #212847)
Aviral Goel via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 4 16:56:21 PDT 2026
https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/212847
>From 48538cf4cca64090d5435dc43361c14effc0e9dc Mon Sep 17 00:00:00 2001
From: Aviral Goel <goel.aviral at gmail.com>
Date: Fri, 24 Jul 2026 12:15:16 -0700
Subject: [PATCH] [clang][ssaf] Add multi-arch create subcommand
This change adds `clang-ssaf-linker multi-arch create` for bundling
per-architecture artifacts into a unified multi-architecture bundle.
---
.../Core/EntityLinker/LUSummaryEncoding.h | 3 +
.../EntityLinker/MultiArchSharedLibrary.h | 3 +
.../EntityLinker/MultiArchStaticLibrary.h | 3 +
.../Core/EntityLinker/StaticLibrary.h | 2 +
.../Core/Model/BuildNamespace.h | 4 +
.../clang/ScalableStaticAnalysis/Tool/Utils.h | 14 +
.../Core/Model/BuildNamespace.cpp | 4 +
.../ssaf-linker/Inputs/bad-artifact.json | 3 +
.../ssaf-linker/Inputs/lib-aarch64.json | 9 +
.../ssaf-linker/Inputs/lib-arm64.json | 9 +
.../ssaf-linker/Inputs/lib-ios-versioned.json | 9 +
.../Scalable/ssaf-linker/Inputs/lib-ios.json | 9 +
.../ssaf-linker/Inputs/lib-otherns.json | 9 +
.../ssaf-linker/Inputs/lib-x86_64.json | 9 +
.../ssaf-linker/Inputs/libfoo-2arch.json | 37 ++
.../Inputs/libmulti-empty-wrapper.json | 8 +
.../Inputs/libmulti-multiarch-wrongns.json | 8 +
.../Inputs/libshared-empty-wrapper.json | 10 +
.../Inputs/libshared-multiarch-wrongns.json | 10 +
.../Scalable/ssaf-linker/Inputs/lu-arm64.json | 13 +
.../ssaf-linker/Inputs/lu-ios-libfoo.json | 13 +
.../ssaf-linker/Inputs/lu-macosx-libfoo.json | 13 +
.../ssaf-linker/Inputs/lu-otherns.json | 13 +
.../ssaf-linker/Inputs/lu-x86_64.json | 13 +
.../ssaf-linker/Outputs/libfoo-3arch.json | 50 +++
.../ssaf-linker/Outputs/libmulti-1arch.json | 18 +
.../ssaf-linker/Outputs/libmulti-2arch.json | 27 ++
.../ssaf-linker/Outputs/libmulti-3arch.json | 36 ++
.../ssaf-linker/Outputs/libshared-1arch.json | 24 ++
.../ssaf-linker/Outputs/libshared-2arch.json | 37 ++
.../Analysis/Scalable/ssaf-linker/help.test | 1 +
.../ssaf-linker/multi-arch-create.test | 296 ++++++++++++++++
.../Scalable/ssaf-linker/multi-arch-help.test | 22 ++
.../Scalable/ssaf-linker/multi-arch-time.test | 36 ++
.../ssaf-linker/multi-arch-verbose.test | 55 +++
clang/tools/clang-ssaf-linker/CMakeLists.txt | 1 +
.../clang-ssaf-linker/MultiArchCreateCLI.cpp | 335 ++++++++++++++++++
.../clang-ssaf-linker/MultiArchCreateCLI.h | 111 ++++++
clang/tools/clang-ssaf-linker/SSAFLinker.cpp | 107 ++++--
.../BuildNamespaceTest.cpp | 13 +
40 files changed, 1362 insertions(+), 35 deletions(-)
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/bad-artifact.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-aarch64.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-arm64.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-ios-versioned.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-ios.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-otherns.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-x86_64.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/libfoo-2arch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/libmulti-empty-wrapper.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/libmulti-multiarch-wrongns.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/libshared-empty-wrapper.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/libshared-multiarch-wrongns.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-arm64.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-ios-libfoo.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-macosx-libfoo.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-otherns.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-x86_64.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/libfoo-3arch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-1arch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-2arch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-3arch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/libshared-1arch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/libshared-2arch.json
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/multi-arch-create.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/multi-arch-help.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/multi-arch-time.test
create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/multi-arch-verbose.test
create mode 100644 clang/tools/clang-ssaf-linker/MultiArchCreateCLI.cpp
create mode 100644 clang/tools/clang-ssaf-linker/MultiArchCreateCLI.h
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h
index 308196d38a84a..a801f5ba46f2e 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h
@@ -26,6 +26,8 @@
namespace clang::ssaf {
+class MultiArchCreateCLI;
+
/// Represents a link unit summary in its serialized encoding.
///
/// LUSummaryEncoding holds the combined entity summary data from multiple
@@ -33,6 +35,7 @@ namespace clang::ssaf {
/// entity linker and contains deduplicated and patched entity summaries.
class LUSummaryEncoding {
friend class EntityLinker;
+ friend class MultiArchCreateCLI;
friend class MultiArchSharedLibrary;
friend class SerializationFormat;
friend class TestFixture;
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h
index 8c3f4345ee5f8..e4d33297ab8b0 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h
@@ -24,6 +24,8 @@
namespace clang::ssaf {
+class MultiArchCreateCLI;
+
/// Represents a multi-architecture shared library.
///
/// A MultiArchSharedLibrary bundles per-architecture LUSummaryEncoding
@@ -34,6 +36,7 @@ namespace clang::ssaf {
/// namespace identifying that shared library, and every member's
/// \c LUNamespace must equal it exactly.
class MultiArchSharedLibrary {
+ friend class MultiArchCreateCLI;
friend class SerializationFormat;
friend class TestFixture;
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
index 6ff7ed347a791..f5ddafbb9d577 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h
@@ -23,6 +23,8 @@
namespace clang::ssaf {
+class MultiArchCreateCLI;
+
/// Represents a multi-architecture static library.
///
/// A MultiArchStaticLibrary bundles per-architecture StaticLibrary members. All
@@ -30,6 +32,7 @@ namespace clang::ssaf {
/// architectures; the wrapper's \c Namespace identifies that shared library and
/// every member's namespace must agree on its name.
class MultiArchStaticLibrary {
+ 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 7358a73c32da1..c74fc4b16e0b2 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 MultiArchCreateCLI;
class StaticLibraryCreateCLI;
/// Represents a static library of translation unit summary encodings.
@@ -41,6 +42,7 @@ class StaticLibraryCreateCLI;
/// static-library tool never decodes per-entity payloads, and the linker
/// consumes them as-is during its selective inclusion pass.
class StaticLibrary {
+ friend class MultiArchCreateCLI;
friend class MultiArchStaticLibrary;
friend class SerializationFormat;
friend class StaticLibraryCreateCLI;
diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h b/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h
index d503b16bf294c..4e4368a06ed89 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h
@@ -60,6 +60,10 @@ class BuildNamespace {
/// \returns A BuildNamespace with CompilationUnit kind.
static BuildNamespace makeCompilationUnit(llvm::StringRef CompilationId);
+ /// Returns a copy of this namespace with its kind replaced by \p Kind,
+ /// preserving the name.
+ BuildNamespace withKind(BuildNamespaceKind Kind) const;
+
bool operator==(const BuildNamespace &Other) const;
bool operator!=(const BuildNamespace &Other) const;
bool operator<(const BuildNamespace &Other) const;
diff --git a/clang/include/clang/ScalableStaticAnalysis/Tool/Utils.h b/clang/include/clang/ScalableStaticAnalysis/Tool/Utils.h
index f02bb8152cfd1..8ceb539a055de 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Tool/Utils.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Tool/Utils.h
@@ -22,6 +22,7 @@
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/WithColor.h"
#include <string>
namespace clang::ssaf {
@@ -47,6 +48,19 @@ template <typename... Ts>
[[noreturn]] void fail(llvm::Error Err);
+/// Number of spaces per indentation level used by info().
+constexpr unsigned IndentationWidth = 2;
+
+/// Prints an indented note to stderr when Verbose is set.
+template <typename... Ts>
+inline void info(bool Verbose, unsigned Level, const char *Fmt, Ts &&...Args) {
+ if (Verbose) {
+ llvm::WithColor::note()
+ << std::string(Level * IndentationWidth, ' ') << "- "
+ << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
+ }
+}
+
//===----------------------------------------------------------------------===//
// Plugin Loading
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/ScalableStaticAnalysis/Core/Model/BuildNamespace.cpp b/clang/lib/ScalableStaticAnalysis/Core/Model/BuildNamespace.cpp
index f68ed961f71b9..345f375788014 100644
--- a/clang/lib/ScalableStaticAnalysis/Core/Model/BuildNamespace.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Core/Model/BuildNamespace.cpp
@@ -20,6 +20,10 @@ BuildNamespace::makeCompilationUnit(llvm::StringRef CompilationId) {
CompilationId.str()};
}
+BuildNamespace BuildNamespace::withKind(BuildNamespaceKind Kind) const {
+ return BuildNamespace{Kind, Name};
+}
+
bool BuildNamespace::operator==(const BuildNamespace &Other) const {
return asTuple() == Other.asTuple();
}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/bad-artifact.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/bad-artifact.json
new file mode 100644
index 0000000000000..fb5e09c2c59a7
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/bad-artifact.json
@@ -0,0 +1,3 @@
+{
+ "type": "BogusKind"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-aarch64.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-aarch64.json
new file mode 100644
index 0000000000000..c0e7cf25306c8
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-aarch64.json
@@ -0,0 +1,9 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "aarch64-apple-macosx",
+ "type": "StaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-arm64.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-arm64.json
new file mode 100644
index 0000000000000..a14f0724adb8a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-arm64.json
@@ -0,0 +1,9 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-ios-versioned.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-ios-versioned.json
new file mode 100644
index 0000000000000..836fa93a1fa7a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-ios-versioned.json
@@ -0,0 +1,9 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "arm64-apple-ios17.0",
+ "type": "StaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-ios.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-ios.json
new file mode 100644
index 0000000000000..0ae42468f899a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-ios.json
@@ -0,0 +1,9 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "arm64-apple-ios",
+ "type": "StaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-otherns.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-otherns.json
new file mode 100644
index 0000000000000..9f928ad5ebbd0
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-otherns.json
@@ -0,0 +1,9 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "otherlib"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-x86_64.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-x86_64.json
new file mode 100644
index 0000000000000..ac8bb1830f5f1
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lib-x86_64.json
@@ -0,0 +1,9 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libfoo-2arch.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libfoo-2arch.json
new file mode 100644
index 0000000000000..d1de17900eb84
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libfoo-2arch.json
@@ -0,0 +1,37 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libmulti-empty-wrapper.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libmulti-empty-wrapper.json
new file mode 100644
index 0000000000000..f153b0a312cd0
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libmulti-empty-wrapper.json
@@ -0,0 +1,8 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libmulti"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libmulti-multiarch-wrongns.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libmulti-multiarch-wrongns.json
new file mode 100644
index 0000000000000..b75b9872aaf93
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libmulti-multiarch-wrongns.json
@@ -0,0 +1,8 @@
+{
+ "members": [],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "otherlib"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libshared-empty-wrapper.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libshared-empty-wrapper.json
new file mode 100644
index 0000000000000..9acdc0470c71b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libshared-empty-wrapper.json
@@ -0,0 +1,10 @@
+{
+ "members": [],
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libshared"
+ }
+ ],
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libshared-multiarch-wrongns.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libshared-multiarch-wrongns.json
new file mode 100644
index 0000000000000..53ca1cbddee0a
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/libshared-multiarch-wrongns.json
@@ -0,0 +1,10 @@
+{
+ "members": [],
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "othershared"
+ }
+ ],
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-arm64.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-arm64.json
new file mode 100644
index 0000000000000..06d5d82913237
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-arm64.json
@@ -0,0 +1,13 @@
+{
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libshared"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-ios-libfoo.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-ios-libfoo.json
new file mode 100644
index 0000000000000..275d54b0da457
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-ios-libfoo.json
@@ -0,0 +1,13 @@
+{
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-ios",
+ "type": "LUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-macosx-libfoo.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-macosx-libfoo.json
new file mode 100644
index 0000000000000..26a2fdf99fc8b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-macosx-libfoo.json
@@ -0,0 +1,13 @@
+{
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-otherns.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-otherns.json
new file mode 100644
index 0000000000000..e5dc71f9be509
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-otherns.json
@@ -0,0 +1,13 @@
+{
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "othershared"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-x86_64.json b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-x86_64.json
new file mode 100644
index 0000000000000..71f0eff931829
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Inputs/lu-x86_64.json
@@ -0,0 +1,13 @@
+{
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libshared"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libfoo-3arch.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libfoo-3arch.json
new file mode 100644
index 0000000000000..fbc389c33f086
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libfoo-3arch.json
@@ -0,0 +1,50 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-ios",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libfoo"
+ }
+ ],
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-1arch.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-1arch.json
new file mode 100644
index 0000000000000..08d8ab9f6045d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-1arch.json
@@ -0,0 +1,18 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libmulti"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-2arch.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-2arch.json
new file mode 100644
index 0000000000000..498fc1a650cb2
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-2arch.json
@@ -0,0 +1,27 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libmulti"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-3arch.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-3arch.json
new file mode 100644
index 0000000000000..2eac4efea795c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libmulti-3arch.json
@@ -0,0 +1,36 @@
+{
+ "members": [
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "arm64-apple-ios",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "arm64-apple-macosx",
+ "type": "StaticLibrary"
+ },
+ {
+ "members": [],
+ "namespace": {
+ "kind": "StaticLibrary",
+ "name": "libmulti"
+ },
+ "target_triple": "x86_64-apple-macosx",
+ "type": "StaticLibrary"
+ }
+ ],
+ "namespace": {
+ "kind": "MultiArchStaticLibrary",
+ "name": "libmulti"
+ },
+ "type": "MultiArchStaticLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libshared-1arch.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libshared-1arch.json
new file mode 100644
index 0000000000000..bd069da05fda0
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libshared-1arch.json
@@ -0,0 +1,24 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libshared"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libshared"
+ }
+ ],
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libshared-2arch.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libshared-2arch.json
new file mode 100644
index 0000000000000..9d1e293db8287
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libshared-2arch.json
@@ -0,0 +1,37 @@
+{
+ "members": [
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libshared"
+ }
+ ],
+ "target_triple": "arm64-apple-macosx",
+ "type": "LUSummary"
+ },
+ {
+ "data": [],
+ "id_table": [],
+ "linkage_table": [],
+ "lu_namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libshared"
+ }
+ ],
+ "target_triple": "x86_64-apple-macosx",
+ "type": "LUSummary"
+ }
+ ],
+ "namespace": [
+ {
+ "kind": "LinkUnit",
+ "name": "libshared"
+ }
+ ],
+ "type": "MultiArchSharedLibrary"
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/help.test b/clang/test/Analysis/Scalable/ssaf-linker/help.test
index 6f897f4ca9228..e4a696d86b165 100644
--- a/clang/test/Analysis/Scalable/ssaf-linker/help.test
+++ b/clang/test/Analysis/Scalable/ssaf-linker/help.test
@@ -9,6 +9,7 @@
// CHECK-EMPTY:
// CHECK-NEXT: SUBCOMMANDS:
// CHECK-EMPTY:
+// CHECK-NEXT: multi-arch - Operations on multi-architecture StaticLibraries and SharedLibraries
// CHECK-NEXT: static-library - Operations on StaticLibraries
// CHECK-EMPTY:
// CHECK-NEXT: Type "clang-ssaf-linker{{(\.exe)?}} <subcommand> --help" to get more help on a specific subcommand
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-create.test b/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-create.test
new file mode 100644
index 0000000000000..a3cd120249d63
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-create.test
@@ -0,0 +1,296 @@
+// Tests for `clang-ssaf-linker multi-arch create`.
+//
+// These tests exercise the CLI shape (subcommand dispatch, verb positional,
+// flag surface) and the happy / error paths of MultiArchStaticLibrary and
+// MultiArchSharedLibrary construction, mirroring `lipo -create`'s handling
+// of single- and multi-arch inputs.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// ============================================================================
+// Happy path (static family): two bare StaticLibrary inputs; the bundle's
+// identity is inferred from the inputs' own namespace
+// ============================================================================
+
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/lib-x86_64.json -o %t/libmulti-2arch.json
+// RUN: diff %S/Outputs/libmulti-2arch.json %t/libmulti-2arch.json
+
+// ============================================================================
+// Happy path (static family): a single bare StaticLibrary input, producing a
+// one-slice MultiArchStaticLibrary
+// ============================================================================
+
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json -o %t/libmulti-1arch.json
+// RUN: diff %S/Outputs/libmulti-1arch.json %t/libmulti-1arch.json
+
+// ============================================================================
+// Happy path (static family): flatten an existing multi-arch bundle + a bare
+// input of a new triple, re-bundling into a 3-member MultiArchStaticLibrary
+// ============================================================================
+
+// RUN: clang-ssaf-linker multi-arch create %t/libmulti-2arch.json %S/Inputs/lib-ios.json -o %t/libmulti-3arch.json
+// RUN: diff %S/Outputs/libmulti-3arch.json %t/libmulti-3arch.json
+
+// ============================================================================
+// Happy path (static family): three bare inputs in one invocation, producing
+// the same bundle as building it up two at a time
+// ============================================================================
+
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/lib-x86_64.json %S/Inputs/lib-ios.json -o %t/libmulti-3bare.json
+// RUN: diff %S/Outputs/libmulti-3arch.json %t/libmulti-3bare.json
+
+// ============================================================================
+// Happy path: a wrapper that contributes no slices of its own still fixes the
+// bundle's identity, so a later bare input joins the namespace the wrapper
+// named rather than one of its own
+// ============================================================================
+
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/libmulti-empty-wrapper.json %S/Inputs/lib-arm64.json -o %t/libmulti-from-wrapper.json
+// RUN: diff %S/Outputs/libmulti-1arch.json %t/libmulti-from-wrapper.json
+
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/libshared-empty-wrapper.json %S/Inputs/lu-arm64.json -o %t/libshared-from-wrapper.json
+// RUN: diff %S/Outputs/libshared-1arch.json %t/libshared-from-wrapper.json
+
+// An identity established by an empty wrapper is enforced on later inputs just
+// as one established by a member is.
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/libmulti-empty-wrapper.json %S/Inputs/lib-otherns.json -o %t/nsmismatch-from-wrapper.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NS-MISMATCH-FROM-WRAPPER
+// NS-MISMATCH-FROM-WRAPPER: namespace BuildNamespace(StaticLibrary, otherlib) from '{{.*}}lib-otherns.json' does not match expected namespace BuildNamespace(StaticLibrary, libmulti)
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/libshared-empty-wrapper.json %S/Inputs/lu-otherns.json -o %t/nsmismatch-from-shared-wrapper.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NS-MISMATCH-FROM-SHARED-WRAPPER
+// NS-MISMATCH-FROM-SHARED-WRAPPER: namespace NestedBuildNamespace([BuildNamespace(LinkUnit, othershared)]) from '{{.*}}lu-otherns.json' does not match expected namespace NestedBuildNamespace([BuildNamespace(LinkUnit, libshared)])
+
+// ============================================================================
+// Happy path (shared family): two bare LUSummaryEncoding inputs
+// ============================================================================
+
+// Like the static family, a shared-family bundle's identity is inferred from
+// its members' own namespace.
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/lu-arm64.json %S/Inputs/lu-x86_64.json -o %t/libshared-2arch.json
+// RUN: diff %S/Outputs/libshared-2arch.json %t/libshared-2arch.json
+
+// ============================================================================
+// Happy path (shared family): a single bare LUSummaryEncoding input, producing
+// a one-slice MultiArchSharedLibrary
+// ============================================================================
+
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/lu-arm64.json -o %t/libshared-1arch.json
+// RUN: diff %S/Outputs/libshared-1arch.json %t/libshared-1arch.json
+
+// ============================================================================
+// Happy path (shared family): flatten an existing multi-arch bundle + a bare
+// input of a new triple, re-bundling into a 3-member MultiArchSharedLibrary
+// ============================================================================
+
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/libfoo-2arch.json %S/Inputs/lu-ios-libfoo.json -o %t/libfoo-3arch.json
+// RUN: diff %S/Outputs/libfoo-3arch.json %t/libfoo-3arch.json
+
+// ============================================================================
+// Mixed-family rejection
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/lu-arm64.json -o %t/mix1.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MIXED-STATIC-THEN-SHARED
+// MIXED-STATIC-THEN-SHARED: input '{{.*}}lu-arm64.json' is a shared-library artifact, but a preceding input established this bundle as static-library
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lu-arm64.json %S/Inputs/lib-arm64.json -o %t/mix2.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MIXED-SHARED-THEN-STATIC
+// MIXED-SHARED-THEN-STATIC: input '{{.*}}lib-arm64.json' is a static-library artifact, but a preceding input established this bundle as shared-library
+
+// ============================================================================
+// Raw TUSummaryEncoding rejection
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/tu-empty.json -o %t/tu-reject.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=INVALID-KIND
+// INVALID-KIND: '{{.*}}tu-empty.json' is a raw TU summary, not a valid input to multi-arch create: run static-library create or an entity-linking step first
+
+// A TU summary is rejected wherever it appears, not just in first position
+// (the first input having already established the family).
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/tu-empty.json -o %t/tu-reject-static.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=INVALID-KIND-STATIC
+// INVALID-KIND-STATIC: '{{.*}}tu-empty.json' is a raw TU summary, not a valid input to multi-arch create
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lu-arm64.json %S/Inputs/tu-empty.json -o %t/tu-reject-shared.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=INVALID-KIND-SHARED
+// INVALID-KIND-SHARED: '{{.*}}tu-empty.json' is a raw TU summary, not a valid input to multi-arch create
+
+// ============================================================================
+// Malformed artifact: a read/decode failure is reported with the reading
+// context, not swallowed or misclassified
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/bad-artifact.json -o %t/bad.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-ARTIFACT
+// BAD-ARTIFACT: Reading artifact '{{.*}}bad-artifact.json'
+// BAD-ARTIFACT: unknown value 'BogusKind' for field 'type'
+
+// ============================================================================
+// Duplicate architecture slice
+// ============================================================================
+
+// Across two bare inputs (the same file passed twice).
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/lib-arm64.json -o %t/dup-bare.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DUP-BARE
+// DUP-BARE: duplicate architecture slice 'arm64-apple-macosx' contributed by both '{{.*}}lib-arm64.json' and '{{.*}}lib-arm64.json'
+
+// Straddling a flattened multi-arch input and a separate bare input.
+// RUN: not clang-ssaf-linker multi-arch create %t/libmulti-2arch.json %S/Inputs/lib-arm64.json -o %t/dup-flattened.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DUP-FLATTENED
+// DUP-FLATTENED: duplicate architecture slice 'arm64-apple-macosx' contributed by both '{{.*}}libmulti-2arch.json' and '{{.*}}lib-arm64.json'
+
+// Slices collide on the triple's canonical components, not on its spelling:
+// "arm64" and "aarch64" name the same architecture, so the second input is a
+// duplicate even though the two triple strings differ. The diagnostic names
+// the retained slice's spelling and both contributing files.
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/lib-aarch64.json -o %t/dup-alias.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DUP-ALIAS
+// DUP-ALIAS: duplicate architecture slice 'arm64-apple-macosx' contributed by both '{{.*}}lib-arm64.json' and '{{.*}}lib-aarch64.json'
+
+// Likewise, an OS version does not distinguish a slice: arm64-apple-ios and
+// arm64-apple-ios17.0 are the same slice.
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-ios.json %S/Inputs/lib-ios-versioned.json -o %t/dup-osversion.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DUP-OSVERSION
+// DUP-OSVERSION: duplicate architecture slice 'arm64-apple-ios' contributed by both '{{.*}}lib-ios.json' and '{{.*}}lib-ios-versioned.json'
+
+// The same rejection applies to the shared family, bare and flattened.
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lu-arm64.json %S/Inputs/lu-arm64.json -o %t/dup-shared-bare.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DUP-SHARED-BARE
+// DUP-SHARED-BARE: duplicate architecture slice 'arm64-apple-macosx' contributed by both '{{.*}}lu-arm64.json' and '{{.*}}lu-arm64.json'
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/libfoo-2arch.json %S/Inputs/lu-macosx-libfoo.json -o %t/dup-shared-flattened.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DUP-SHARED-FLATTENED
+// DUP-SHARED-FLATTENED: duplicate architecture slice 'arm64-apple-macosx' contributed by both '{{.*}}libfoo-2arch.json' and '{{.*}}lu-macosx-libfoo.json'
+
+// ============================================================================
+// Error ordering: inputs are folded into the bundle as they are read, so the
+// first input that cannot be accepted is the one reported — whichever kind of
+// problem it has. These two runs share the same three inputs in different
+// orders and must report different errors.
+// ============================================================================
+
+// The duplicate at input 2 is reported; input 3's namespace is never reached.
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/lib-arm64.json %S/Inputs/lib-otherns.json -o %t/order-dup-first.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=ORDER-DUP-FIRST --implicit-check-not="does not match expected namespace"
+// ORDER-DUP-FIRST: duplicate architecture slice 'arm64-apple-macosx' contributed by both '{{.*}}lib-arm64.json' and '{{.*}}lib-arm64.json'
+
+// The namespace mismatch at input 2 is reported; input 3's duplicate slice is
+// never reached.
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/lib-otherns.json %S/Inputs/lib-arm64.json -o %t/order-ns-first.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=ORDER-NS-FIRST --implicit-check-not="duplicate architecture slice"
+// ORDER-NS-FIRST: namespace BuildNamespace(StaticLibrary, otherlib) from '{{.*}}lib-otherns.json' does not match expected namespace BuildNamespace(StaticLibrary, libmulti)
+
+// ============================================================================
+// Namespace mismatch
+// ============================================================================
+
+// A bare StaticLibrary member's own namespace disagrees with the target
+// (inferred from the first input).
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/lib-otherns.json -o %t/nsmismatch-bare.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NS-MISMATCH-BARE
+// NS-MISMATCH-BARE: namespace BuildNamespace(StaticLibrary, otherlib) from '{{.*}}lib-otherns.json' does not match expected namespace BuildNamespace(StaticLibrary, libmulti)
+
+// A MultiArchStaticLibrary wrapper's own namespace disagrees with the target
+// (inferred from the first input).
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json %S/Inputs/libmulti-multiarch-wrongns.json -o %t/nsmismatch-wrapper.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NS-MISMATCH-WRAPPER
+// NS-MISMATCH-WRAPPER: namespace BuildNamespace(MultiArchStaticLibrary, otherlib) from '{{.*}}libmulti-multiarch-wrongns.json' does not match expected namespace BuildNamespace(MultiArchStaticLibrary, libmulti)
+
+// A bare LUSummaryEncoding member's own namespace disagrees with the target
+// (inferred from the first input).
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lu-arm64.json %S/Inputs/lu-otherns.json -o %t/nsmismatch-shared.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NS-MISMATCH-SHARED
+// NS-MISMATCH-SHARED: namespace NestedBuildNamespace([BuildNamespace(LinkUnit, othershared)]) from '{{.*}}lu-otherns.json' does not match expected namespace NestedBuildNamespace([BuildNamespace(LinkUnit, libshared)])
+
+// A MultiArchSharedLibrary wrapper's own namespace disagrees with the target
+// (inferred from the first input).
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lu-arm64.json %S/Inputs/libshared-multiarch-wrongns.json -o %t/nsmismatch-shared-wrapper.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NS-MISMATCH-SHARED-WRAPPER
+// NS-MISMATCH-SHARED-WRAPPER: namespace NestedBuildNamespace([BuildNamespace(LinkUnit, othershared)]) from '{{.*}}libshared-multiarch-wrongns.json' does not match expected namespace NestedBuildNamespace([BuildNamespace(LinkUnit, libshared)])
+
+// ============================================================================
+// Zero candidates after flattening (every input is an empty multi-arch bundle)
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/libmulti-empty-wrapper.json -o %t/zerocand.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-CANDIDATES
+// NO-CANDIDATES: no candidate members could be derived from the given inputs: at least one member is required
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/libshared-empty-wrapper.json -o %t/zerocand-shared.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-CANDIDATES-SHARED
+// NO-CANDIDATES-SHARED: no candidate members could be derived from the given inputs: at least one member is required
+
+// ============================================================================
+// Empty input list
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch create -o %t/no-inputs.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-INPUTS
+// NO-INPUTS: no input artifacts: at least one input is required
+
+// ============================================================================
+// Missing output flag
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-OUTPUT
+// NO-OUTPUT: for the -o option: must be specified at least once!
+
+// ============================================================================
+// Unusable input paths
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/tu-badext.txt -o %t/badext.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-EXTENSION
+// BAD-EXTENSION: clang-ssaf-linker: error: failed to validate path '{{.*}}tu-badext.txt': No format registered for extension 'txt'
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/does-not-exist.json -o %t/missing.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MISSING-INPUT
+// MISSING-INPUT: clang-ssaf-linker: error: failed to validate path '{{.*}}does-not-exist.json': Path does not exist
+
+// ============================================================================
+// Output already exists
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch create %S/Inputs/lib-arm64.json -o %t/libmulti-2arch.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=OUTPUT-EXISTS
+// OUTPUT-EXISTS: clang-ssaf-linker: error: failed to validate path '{{.*}}libmulti-2arch.json': File already exists
+
+// ============================================================================
+// Unknown verb
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch extract %S/Inputs/lib-arm64.json -o %t/unknown.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=UNKNOWN-VERB
+// UNKNOWN-VERB: unknown multi-arch verb 'extract': expected 'create'
+
+// ============================================================================
+// Missing verb
+// ============================================================================
+
+// RUN: not clang-ssaf-linker multi-arch -o %t/no-verb.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-VERB
+// NO-VERB: Not enough positional command line arguments specified!
+
+// ============================================================================
+// Order independence: members are keyed by target triple, so the bundle is
+// byte-identical however the inputs are ordered on the command line
+// ============================================================================
+
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/lib-x86_64.json %S/Inputs/lib-arm64.json -o %t/libmulti-reversed.json
+// RUN: diff %S/Outputs/libmulti-2arch.json %t/libmulti-reversed.json
+
+// RUN: clang-ssaf-linker multi-arch create %S/Inputs/lu-x86_64.json %S/Inputs/lu-arm64.json -o %t/libshared-reversed.json
+// RUN: diff %S/Outputs/libshared-2arch.json %t/libshared-reversed.json
+
+// ============================================================================
+// Round-trip: produced bundles parse cleanly with clang-ssaf-format
+// ============================================================================
+
+// RUN: clang-ssaf-format --type multi-arch-static-library %t/libmulti-3arch.json -o %t/libmulti-3arch-roundtrip.json
+// RUN: diff %t/libmulti-3arch.json %t/libmulti-3arch-roundtrip.json
+
+// RUN: clang-ssaf-format --type multi-arch-shared-library %t/libfoo-3arch.json -o %t/libfoo-3arch-roundtrip.json
+// RUN: diff %t/libfoo-3arch.json %t/libfoo-3arch-roundtrip.json
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-help.test b/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-help.test
new file mode 100644
index 0000000000000..85e6e9b87afa8
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-help.test
@@ -0,0 +1,22 @@
+// Test the --help output for the `multi-arch` subcommand.
+
+// RUN: clang-ssaf-linker multi-arch --help-list-hidden \
+// RUN: | FileCheck %s --match-full-lines
+
+// CHECK: OVERVIEW: SSAF Linker
+// CHECK-EMPTY:
+// CHECK-NEXT: SUBCOMMAND 'multi-arch': Operations on multi-architecture StaticLibraries and SharedLibraries
+// CHECK-EMPTY:
+// CHECK-NEXT: USAGE: clang-ssaf-linker{{(\.exe)?}} multi-arch [options] <verb> <static-library or shared-library files>
+// 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
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-time.test b/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-time.test
new file mode 100644
index 0000000000000..31be44026018c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-time.test
@@ -0,0 +1,36 @@
+// Test the --time flag on the `multi-arch create` subcommand.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// RUN: clang-ssaf-linker multi-arch create --time \
+// RUN: %S/Inputs/lib-arm64.json %S/Inputs/lib-x86_64.json -o %t/libt.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines
+
+// CHECK: ===-------------------------------------------------------------------------===
+// CHECK-NEXT: {{[ ]+}}SSAF Linker
+// CHECK-NEXT: ===-------------------------------------------------------------------------===
+// CHECK-NEXT: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
+// CHECK: {{.*}}---Wall Time---{{.*}}
+// CHECK-DAG: {{.*}}Validate Input
+// CHECK-DAG: {{.*}}Read Artifacts
+// CHECK-DAG: {{.*}}Bundle Input
+// CHECK-DAG: {{.*}}Write Multi-Arch Bundle
+// CHECK: {{.*}}Total
+
+// The shared family runs the same phases, so it reports the same timers.
+
+// RUN: clang-ssaf-linker multi-arch create --time \
+// RUN: %S/Inputs/lu-arm64.json %S/Inputs/lu-x86_64.json -o %t/libts.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=SHARED
+
+// SHARED: ===-------------------------------------------------------------------------===
+// SHARED-NEXT: {{[ ]+}}SSAF Linker
+// SHARED-NEXT: ===-------------------------------------------------------------------------===
+// SHARED-NEXT: Total Execution Time: {{[0-9.]+}} seconds ({{[0-9.]+}} wall clock)
+// SHARED: {{.*}}---Wall Time---{{.*}}
+// SHARED-DAG: {{.*}}Validate Input
+// SHARED-DAG: {{.*}}Read Artifacts
+// SHARED-DAG: {{.*}}Bundle Input
+// SHARED-DAG: {{.*}}Write Multi-Arch Bundle
+// SHARED: {{.*}}Total
diff --git a/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-verbose.test b/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-verbose.test
new file mode 100644
index 0000000000000..0f62fe0a1d873
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-linker/multi-arch-verbose.test
@@ -0,0 +1,55 @@
+// Test the --verbose flag on the `multi-arch create` subcommand.
+
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+
+// RUN: clang-ssaf-linker multi-arch create --verbose \
+// RUN: %S/Inputs/lib-arm64.json %S/Inputs/lib-x86_64.json -o %t/libv.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines
+
+// CHECK: note: - Bundling started.
+// CHECK-NEXT: note: - Validating input.
+// CHECK-NEXT: note: - Validated output path '{{.*}}libv.json'.
+// CHECK-NEXT: note: - Validated 2 input artifact path(s).
+// CHECK-NEXT: note: - Creating bundle.
+// CHECK-NEXT: note: - Bundling members.
+// CHECK-NEXT: note: - [1/2] Reading '{{.*}}lib-arm64.json'.
+// CHECK-NEXT: note: - [1/2] Bundling '{{.*}}lib-arm64.json'.
+// CHECK-NEXT: note: - [2/2] Reading '{{.*}}lib-x86_64.json'.
+// CHECK-NEXT: note: - [2/2] Bundling '{{.*}}lib-x86_64.json'.
+// CHECK-NEXT: note: - Bundled 2 member(s).
+// CHECK-NEXT: note: - Target namespace: 'BuildNamespace(MultiArchStaticLibrary, libmulti)'.
+// CHECK-NEXT: note: - Writing bundle to '{{.*}}libv.json'.
+// CHECK-NEXT: note: - Bundling finished.
+
+// The shared family reports a NestedBuildNamespace target, and flattening a
+// multi-arch input contributes more slices than there are inputs.
+
+// RUN: clang-ssaf-linker multi-arch create --verbose \
+// RUN: %S/Inputs/libfoo-2arch.json %S/Inputs/lu-ios-libfoo.json -o %t/libfoov.json 2>&1 \
+// RUN: | FileCheck %s --match-full-lines --check-prefix=SHARED
+
+// SHARED: note: - Bundling started.
+// SHARED-NEXT: note: - Validating input.
+// SHARED-NEXT: note: - Validated output path '{{.*}}libfoov.json'.
+// SHARED-NEXT: note: - Validated 2 input artifact path(s).
+// SHARED-NEXT: note: - Creating bundle.
+// SHARED-NEXT: note: - Bundling members.
+// SHARED-NEXT: note: - [1/2] Reading '{{.*}}libfoo-2arch.json'.
+// SHARED-NEXT: note: - [1/2] Bundling '{{.*}}libfoo-2arch.json'.
+// SHARED-NEXT: note: - [2/2] Reading '{{.*}}lu-ios-libfoo.json'.
+// SHARED-NEXT: note: - [2/2] Bundling '{{.*}}lu-ios-libfoo.json'.
+// SHARED-NEXT: note: - Bundled 3 member(s).
+// SHARED-NEXT: note: - Target namespace: 'NestedBuildNamespace([BuildNamespace(LinkUnit, libfoo)])'.
+// SHARED-NEXT: note: - Writing bundle to '{{.*}}libfoov.json'.
+// SHARED-NEXT: note: - Bundling finished.
+
+// A run that fails before assembling reports neither a target namespace nor a
+// slice count.
+
+// RUN: not clang-ssaf-linker multi-arch create --verbose \
+// RUN: %S/Inputs/libmulti-empty-wrapper.json -o %t/libnone.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-TARGET --implicit-check-not="Target namespace"
+
+// NO-TARGET: note: - [1/1] Bundling '{{.*}}libmulti-empty-wrapper.json'.
+// NO-TARGET-NEXT: error: no candidate members could be derived from the given inputs: at least one member is required
diff --git a/clang/tools/clang-ssaf-linker/CMakeLists.txt b/clang/tools/clang-ssaf-linker/CMakeLists.txt
index 448b8cbb8cf14..c51c1f25ff9cc 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
+ MultiArchCreateCLI.cpp
StaticLibraryCreateCLI.cpp
SSAFLinker.cpp
)
diff --git a/clang/tools/clang-ssaf-linker/MultiArchCreateCLI.cpp b/clang/tools/clang-ssaf-linker/MultiArchCreateCLI.cpp
new file mode 100644
index 0000000000000..cd0ba4faf064b
--- /dev/null
+++ b/clang/tools/clang-ssaf-linker/MultiArchCreateCLI.cpp
@@ -0,0 +1,335 @@
+//===- MultiArchCreateCLI.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 `multi-arch create` CLI action. The run() function picks the
+// family from the first input and hands off to either createStaticLibrary() or
+// createSharedLibrary().
+//
+//===----------------------------------------------------------------------===//
+
+#include "MultiArchCreateCLI.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/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Timer.h"
+#include "llvm/TargetParser/Triple.h"
+#include <cassert>
+#include <memory>
+#include <string>
+#include <utility>
+#include <variant>
+
+using namespace llvm;
+using namespace clang::ssaf;
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// Error Messages
+//===----------------------------------------------------------------------===//
+
+constexpr const char *ReadingArtifact = "Reading artifact '{0}'";
+
+constexpr const char *NoInputs =
+ "no input artifacts: at least one input is required";
+
+constexpr const char *InvalidInputKind =
+ "'{0}' is a raw TU summary, not a valid input to multi-arch create: run "
+ "static-library create or an entity-linking step first";
+
+constexpr const char *MixedFamily =
+ "input '{0}' is a {1} artifact, but a preceding input established this "
+ "bundle as {2}";
+
+constexpr const char *NamespaceMismatch =
+ "namespace {0} from '{1}' does not match expected namespace {2}";
+
+constexpr const char *NoCandidateMembers =
+ "no candidate members could be derived from the given inputs: at least "
+ "one member is required";
+
+constexpr const char *DuplicateTriple =
+ "duplicate architecture slice '{0}' contributed by both '{1}' and '{2}'";
+
+constexpr const char *StaticFamilyName = "static-library";
+constexpr const char *SharedFamilyName = "shared-library";
+
+//===----------------------------------------------------------------------===//
+// ArtifactEncoding Helpers
+//===----------------------------------------------------------------------===//
+
+bool isStaticFamily(const ArtifactEncoding &E) {
+ return std::holds_alternative<StaticLibrary>(E) ||
+ std::holds_alternative<MultiArchStaticLibrary>(E);
+}
+
+bool isSharedFamily(const ArtifactEncoding &E) {
+ return std::holds_alternative<LUSummaryEncoding>(E) ||
+ std::holds_alternative<MultiArchSharedLibrary>(E);
+}
+
+bool isTUSummaryEncoding(const ArtifactEncoding &E) {
+ return std::holds_alternative<TUSummaryEncoding>(E);
+}
+
+} // namespace
+
+namespace clang::ssaf {
+
+void MultiArchCreateCLI::run(llvm::TimerGroup &TG,
+ llvm::ArrayRef<std::string> InputPaths,
+ llvm::StringRef OutputPath, bool Verbose,
+ bool Time) {
+ this->InputPaths = InputPaths;
+ this->OutputPath = OutputPath;
+ this->Verbose = Verbose;
+ this->Time = Time;
+
+ llvm::Timer TValidate("validate", "Validate Input", TG);
+ llvm::Timer TRead("read", "Read Artifacts", TG);
+ llvm::Timer TBundle("bundle", "Bundle Input", TG);
+ llvm::Timer TWrite("write", "Write Multi-Arch Bundle", TG);
+
+ info(Verbose, 0, "Bundling started.");
+
+ info(Verbose, 1, "Validating input.");
+ validate(TValidate);
+
+ info(Verbose, 1, "Creating bundle.");
+ ArtifactEncoding Result = create(TRead, TBundle);
+
+ info(Verbose, 1, "Writing bundle to '{0}'.", OutputFile.Path);
+ write(Result, TWrite);
+
+ info(Verbose, 0, "Bundling finished.");
+
+ // Second run() should start from a clean slate.
+ InputFiles.clear();
+ SourceByMember.clear();
+}
+
+void MultiArchCreateCLI::validate(llvm::Timer &TValidate) {
+ llvm::TimeRegion _(Time ? &TValidate : nullptr);
+
+ OutputFile = FormatFile::fromOutputPath(OutputPath);
+ info(Verbose, 2, "Validated output path '{0}'.", OutputFile.Path);
+
+ if (InputPaths.empty()) {
+ fail(NoInputs);
+ }
+
+ for (const auto &InputPath : InputPaths) {
+ InputFiles.push_back(FormatFile::fromInputPath(InputPath));
+ }
+
+ info(Verbose, 2, "Validated {0} input artifact path(s).", InputFiles.size());
+}
+
+ArtifactEncoding MultiArchCreateCLI::create(llvm::Timer &TRead,
+ llvm::Timer &TBundle) {
+ info(Verbose, 2, "Bundling members.");
+
+ // The first input decides the bundle kind.
+ ArtifactEncoding First = readInput(0, TRead);
+
+ if (isStaticFamily(First)) {
+ return createStaticLibrary(std::move(First), TRead, TBundle);
+ } else if (isSharedFamily(First)) {
+ return createSharedLibrary(std::move(First), TRead, TBundle);
+ } else {
+ fail(InvalidInputKind, InputFiles[0].Path);
+ }
+}
+
+ArtifactEncoding MultiArchCreateCLI::readInput(size_t Index,
+ llvm::Timer &TRead) {
+ const FormatFile &InputFile = InputFiles[Index];
+ info(Verbose, 3, "[{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);
+}
+
+const BuildNamespace &
+MultiArchCreateCLI::staticFamilyNamespace(const ArtifactEncoding &E) {
+ assert(isStaticFamily(E) && "not a static-family artifact");
+ if (const auto *SL = std::get_if<StaticLibrary>(&E)) {
+ return SL->Namespace;
+ }
+ return std::get<MultiArchStaticLibrary>(E).Namespace;
+}
+
+const NestedBuildNamespace &
+MultiArchCreateCLI::sharedFamilyNamespace(const ArtifactEncoding &E) {
+ assert(isSharedFamily(E) && "not a shared-family artifact");
+ if (const auto *LU = std::get_if<LUSummaryEncoding>(&E)) {
+ return LU->LUNamespace;
+ }
+ return std::get<MultiArchSharedLibrary>(E).Namespace;
+}
+
+ArtifactEncoding MultiArchCreateCLI::createStaticLibrary(ArtifactEncoding First,
+ llvm::Timer &TRead,
+ llvm::Timer &TBundle) {
+ MultiArchStaticLibrary Bundle(staticFamilyNamespace(First).withKind(
+ BuildNamespaceKind::MultiArchStaticLibrary));
+
+ addStaticInput(Bundle, std::move(First), 0, TBundle);
+ for (size_t Index = 1; Index < InputFiles.size(); ++Index) {
+ addStaticInput(Bundle, readInput(Index, TRead), Index, TBundle);
+ }
+
+ if (Bundle.Members.empty()) {
+ fail(NoCandidateMembers);
+ }
+
+ info(Verbose, 2, "Bundled {0} member(s).", Bundle.Members.size());
+ info(Verbose, 2, "Target namespace: '{0}'.", Bundle.Namespace);
+
+ return ArtifactEncoding(std::move(Bundle));
+}
+
+ArtifactEncoding MultiArchCreateCLI::createSharedLibrary(ArtifactEncoding First,
+ llvm::Timer &TRead,
+ llvm::Timer &TBundle) {
+ MultiArchSharedLibrary Bundle(sharedFamilyNamespace(First));
+
+ addSharedInput(Bundle, std::move(First), 0, TBundle);
+ for (size_t Index = 1; Index < InputFiles.size(); ++Index) {
+ addSharedInput(Bundle, readInput(Index, TRead), Index, TBundle);
+ }
+
+ if (Bundle.Members.empty()) {
+ fail(NoCandidateMembers);
+ }
+
+ info(Verbose, 2, "Bundled {0} member(s).", Bundle.Members.size());
+ info(Verbose, 2, "Target namespace: '{0}'.", Bundle.Namespace);
+
+ return ArtifactEncoding(std::move(Bundle));
+}
+
+void MultiArchCreateCLI::addStaticInput(MultiArchStaticLibrary &Bundle,
+ ArtifactEncoding Encoding, size_t Index,
+ llvm::Timer &TBundle) {
+ llvm::StringRef SourceFile = InputFiles[Index].Path;
+ info(Verbose, 3, "[{0}/{1}] Bundling '{2}'.", Index + 1, InputFiles.size(),
+ SourceFile);
+ llvm::TimeRegion _(Time ? &TBundle : nullptr);
+
+ if (auto *SL = std::get_if<StaticLibrary>(&Encoding)) {
+ BuildNamespace Expected =
+ Bundle.Namespace.withKind(BuildNamespaceKind::StaticLibrary);
+ if (SL->Namespace != Expected) {
+ fail(NamespaceMismatch, SL->Namespace, SourceFile, Expected);
+ }
+ addStaticMember(Bundle, std::make_unique<StaticLibrary>(std::move(*SL)),
+ SourceFile);
+ return;
+ }
+
+ if (auto *MASL = std::get_if<MultiArchStaticLibrary>(&Encoding)) {
+ if (MASL->Namespace != Bundle.Namespace) {
+ fail(NamespaceMismatch, MASL->Namespace, SourceFile, Bundle.Namespace);
+ }
+
+ while (!MASL->Members.empty()) {
+ auto Node = MASL->Members.extract(MASL->Members.begin());
+ addStaticMember(Bundle, std::move(Node.value()), SourceFile);
+ }
+ return;
+ }
+
+ if (isTUSummaryEncoding(Encoding)) {
+ fail(InvalidInputKind, SourceFile);
+ }
+
+ fail(MixedFamily, SourceFile, SharedFamilyName, StaticFamilyName);
+}
+
+void MultiArchCreateCLI::addSharedInput(MultiArchSharedLibrary &Bundle,
+ ArtifactEncoding Encoding, size_t Index,
+ llvm::Timer &TBundle) {
+ llvm::StringRef SourceFile = InputFiles[Index].Path;
+ info(Verbose, 3, "[{0}/{1}] Bundling '{2}'.", Index + 1, InputFiles.size(),
+ SourceFile);
+ llvm::TimeRegion _(Time ? &TBundle : nullptr);
+
+ if (auto *LU = std::get_if<LUSummaryEncoding>(&Encoding)) {
+ if (LU->LUNamespace != Bundle.Namespace) {
+ fail(NamespaceMismatch, LU->LUNamespace, SourceFile, Bundle.Namespace);
+ }
+ addSharedMember(Bundle, std::make_unique<LUSummaryEncoding>(std::move(*LU)),
+ SourceFile);
+ return;
+ }
+
+ if (auto *MASharedL = std::get_if<MultiArchSharedLibrary>(&Encoding)) {
+ if (MASharedL->Namespace != Bundle.Namespace) {
+ fail(NamespaceMismatch, MASharedL->Namespace, SourceFile,
+ Bundle.Namespace);
+ }
+ while (!MASharedL->Members.empty()) {
+ auto Node = MASharedL->Members.extract(MASharedL->Members.begin());
+ addSharedMember(Bundle, std::move(Node.value()), SourceFile);
+ }
+ return;
+ }
+
+ if (isTUSummaryEncoding(Encoding)) {
+ fail(InvalidInputKind, SourceFile);
+ }
+
+ fail(MixedFamily, SourceFile, StaticFamilyName, SharedFamilyName);
+}
+
+void MultiArchCreateCLI::addStaticMember(MultiArchStaticLibrary &Bundle,
+ std::unique_ptr<StaticLibrary> Member,
+ llvm::StringRef SourceFile) {
+ auto [It, Inserted] = Bundle.Members.insert(std::move(Member));
+ if (!Inserted) {
+ fail(DuplicateTriple, llvm::Triple::normalize((*It)->TargetTriple.str()),
+ SourceByMember.lookup(It->get()), SourceFile);
+ }
+ SourceByMember[It->get()] = SourceFile;
+}
+
+void MultiArchCreateCLI::addSharedMember(
+ MultiArchSharedLibrary &Bundle, std::unique_ptr<LUSummaryEncoding> Member,
+ llvm::StringRef SourceFile) {
+ auto [It, Inserted] = Bundle.Members.insert(std::move(Member));
+ if (!Inserted) {
+ fail(DuplicateTriple, llvm::Triple::normalize((*It)->TargetTriple.str()),
+ SourceByMember.lookup(It->get()), SourceFile);
+ }
+ SourceByMember[It->get()] = SourceFile;
+}
+
+void MultiArchCreateCLI::write(const ArtifactEncoding &Bundle,
+ llvm::Timer &TWrite) {
+ llvm::TimeRegion _(Time ? &TWrite : nullptr);
+
+ if (auto Err =
+ OutputFile.Format->writeArtifactEncoding(Bundle, OutputFile.Path)) {
+ fail(std::move(Err));
+ }
+}
+
+} // namespace clang::ssaf
diff --git a/clang/tools/clang-ssaf-linker/MultiArchCreateCLI.h b/clang/tools/clang-ssaf-linker/MultiArchCreateCLI.h
new file mode 100644
index 0000000000000..f1e811c90e9c7
--- /dev/null
+++ b/clang/tools/clang-ssaf-linker/MultiArchCreateCLI.h
@@ -0,0 +1,111 @@
+//===- MultiArchCreateCLI.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 `clang-ssaf-linker multi-arch create`.
+// Bundles StaticLibrary/MultiArchStaticLibrary inputs into one
+// MultiArchStaticLibrary, or LUSummaryEncoding/MultiArchSharedLibrary inputs
+// into one MultiArchSharedLibrary.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_MULTIARCHCREATECLI_H
+#define LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_MULTIARCHCREATECLI_H
+
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/LUSummaryEncoding.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchSharedLibrary.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/MultiArchStaticLibrary.h"
+#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
+#include "clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h"
+#include "clang/ScalableStaticAnalysis/Tool/Utils.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Timer.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace clang::ssaf {
+
+/// Runs the `multi-arch create` action for `clang-ssaf-linker`.
+class MultiArchCreateCLI {
+public:
+ /// Orchestrates validation, construction, and serialization of the MultiArch
+ /// artifact.
+ void run(llvm::TimerGroup &TG, llvm::ArrayRef<std::string> InputPaths,
+ llvm::StringRef OutputPath, bool Verbose, bool Time);
+
+private:
+ /// Validates the output path and every input path.
+ void validate(llvm::Timer &TValidate);
+
+ /// Constructs the artifact from the inputs.
+ ArtifactEncoding create(llvm::Timer &TRead, llvm::Timer &TBundle);
+
+ /// Reads Artifact from file at \p Index.
+ ArtifactEncoding readInput(size_t Index, llvm::Timer &TRead);
+
+ /// Returns the namespace of the static-family input.
+ static const BuildNamespace &staticFamilyNamespace(const ArtifactEncoding &E);
+
+ /// Returns the namespace of the shared-family input.
+ static const NestedBuildNamespace &
+ sharedFamilyNamespace(const ArtifactEncoding &E);
+
+ /// Constructs a MultiArchStaticLibrary from the inputs.
+ ArtifactEncoding createStaticLibrary(ArtifactEncoding First,
+ llvm::Timer &TRead,
+ llvm::Timer &TBundle);
+
+ /// Constructs a MultiArchSharedLibrary from the inputs.
+ ArtifactEncoding createSharedLibrary(ArtifactEncoding First,
+ llvm::Timer &TRead,
+ llvm::Timer &TBundle);
+
+ /// Adds one input to the \p Bundle.
+ void addStaticInput(MultiArchStaticLibrary &Bundle, ArtifactEncoding Encoding,
+ size_t Index, llvm::Timer &TBundle);
+ void addSharedInput(MultiArchSharedLibrary &Bundle, ArtifactEncoding Encoding,
+ size_t Index, llvm::Timer &TBundle);
+
+ /// Inserts one member, failing on duplicate target triple.
+ void addStaticMember(MultiArchStaticLibrary &Bundle,
+ std::unique_ptr<StaticLibrary> Member,
+ llvm::StringRef SourceFile);
+ void addSharedMember(MultiArchSharedLibrary &Bundle,
+ std::unique_ptr<LUSummaryEncoding> Member,
+ llvm::StringRef SourceFile);
+
+ /// Serializes the artifact to the validated output path.
+ void write(const ArtifactEncoding &Bundle, llvm::Timer &TWrite);
+
+ // Arguments captured by run() before dispatching to bundling methods.
+ // InputPaths and OutputPath are non-owning: they alias the driver's cl::opt
+ // storage, which outlives the call.
+ llvm::ArrayRef<std::string> InputPaths;
+ llvm::StringRef OutputPath;
+ bool Verbose = false;
+ bool Time = false;
+
+ // State populated during validate() and consumed by later phases.
+ FormatFile OutputFile;
+ std::vector<FormatFile> InputFiles;
+
+ /// Maps each inserted member to the input file that contributed it, so a
+ /// duplicate can name both contributors. Keyed on the member's address, not
+ /// its triple spelling: the member sets are keyed by Triple enum components,
+ /// which fold alias spellings ("arm64" / "aarch64") and OS versions, so two
+ /// colliding slices can carry triple strings that differ. The values alias
+ /// InputFiles[I].Path, which outlives the run.
+ llvm::DenseMap<const void *, llvm::StringRef> SourceByMember;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_MULTIARCHCREATECLI_H
diff --git a/clang/tools/clang-ssaf-linker/SSAFLinker.cpp b/clang/tools/clang-ssaf-linker/SSAFLinker.cpp
index 308914bddf270..9d72e9f338328 100644
--- a/clang/tools/clang-ssaf-linker/SSAFLinker.cpp
+++ b/clang/tools/clang-ssaf-linker/SSAFLinker.cpp
@@ -6,13 +6,16 @@
//
//===----------------------------------------------------------------------===//
//
-// 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 also provides the `static-library` subcommand for
-// bundling TU summaries into a StaticLibrary.
+// 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
+// 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
+// MultiArchStaticLibrary or MultiArchSharedLibrary.
//
//===----------------------------------------------------------------------===//
+#include "MultiArchCreateCLI.h"
#include "StaticLibraryCreateCLI.h"
#include "clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h"
@@ -50,6 +53,11 @@ cl::OptionCategory SsafLinkerCategory("clang-ssaf-linker options");
cl::SubCommand StaticLibraryCmd("static-library",
"Operations on StaticLibraries");
+// The `multi-arch` subcommand groups all multi-architecture operations.
+cl::SubCommand MultiArchCmd("multi-arch",
+ "Operations on multi-architecture StaticLibrary "
+ "and SharedLibrary artifacts");
+
// Top-level (default) `link` action positionals.
cl::list<std::string> InputPaths(cl::Positional, cl::desc("<input files>"),
cl::OneOrMore, cl::cat(SsafLinkerCategory));
@@ -62,12 +70,12 @@ cl::opt<std::string> OutputPath("o", cl::desc("Output file path"),
cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"),
cl::init(false), cl::cat(SsafLinkerCategory),
cl::sub(cl::SubCommand::getTopLevel()),
- cl::sub(StaticLibraryCmd));
+ cl::sub(StaticLibraryCmd), cl::sub(MultiArchCmd));
cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false),
cl::cat(SsafLinkerCategory),
cl::sub(cl::SubCommand::getTopLevel()),
- cl::sub(StaticLibraryCmd));
+ cl::sub(StaticLibraryCmd), cl::sub(MultiArchCmd));
// The `static-library` subcommand's verb positional. Declared BEFORE
// StaticLibraryInputs so cl-lib binds argv[0] under the subcommand to the
@@ -75,7 +83,6 @@ cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false),
cl::opt<std::string> StaticLibraryVerb(cl::Positional, cl::Required,
cl::sub(StaticLibraryCmd),
cl::desc("<verb>"),
- cl::value_desc("create"),
cl::cat(SsafLinkerCategory));
// The `static-library` subcommand's action-specific positional input
@@ -104,6 +111,25 @@ cl::opt<std::string> StaticLibraryTriple(
"inputs when set)"),
cl::value_desc("triple"), cl::cat(SsafLinkerCategory));
+// The `multi-arch` subcommand's verb positional. Declared BEFORE
+// MultiArchInputs so cl-lib binds argv[0] under the subcommand to the verb
+// rather than to the greedy input list.
+cl::opt<std::string> MultiArchVerb(cl::Positional, cl::Required,
+ cl::sub(MultiArchCmd), cl::desc("<verb>"),
+ cl::cat(SsafLinkerCategory));
+
+// The `multi-arch` subcommand's action-specific positional input list.
+// Currently consumed by `multi-arch create`.
+cl::list<std::string>
+ MultiArchInputs(cl::Positional, cl::sub(MultiArchCmd),
+ cl::desc("<static-library or shared-library files>"),
+ cl::cat(SsafLinkerCategory));
+
+cl::opt<std::string> MultiArchOutput("o", cl::Required, cl::sub(MultiArchCmd),
+ cl::desc("Output file path"),
+ cl::value_desc("path"),
+ cl::cat(SsafLinkerCategory));
+
//===----------------------------------------------------------------------===//
// StaticLibrary Verbs
//===----------------------------------------------------------------------===//
@@ -112,6 +138,14 @@ cl::opt<std::string> StaticLibraryTriple(
// UnknownStaticLibraryVerb below.
constexpr const char *StaticLibraryCreateVerb = "create";
+//===----------------------------------------------------------------------===//
+// MultiArch Verbs
+//===----------------------------------------------------------------------===//
+
+// Verb strings for the `multi-arch` subcommand. Kept in sync with
+// UnknownMultiArchVerb below.
+constexpr const char *MultiArchCreateVerb = "create";
+
//===----------------------------------------------------------------------===//
// Error Messages
//===----------------------------------------------------------------------===//
@@ -123,22 +157,10 @@ constexpr const char *LinkingSummary = "Linking summary '{0}'";
constexpr const char *UnknownStaticLibraryVerb =
"unknown static-library verb '{0}': expected 'create'";
-} // namespace LocalErrorMessages
-
-//===----------------------------------------------------------------------===//
-// Diagnostic Utilities
-//===----------------------------------------------------------------------===//
-
-constexpr unsigned IndentationWidth = 2;
+constexpr const char *UnknownMultiArchVerb =
+ "unknown multi-arch verb '{0}': expected 'create'";
-template <typename... Ts>
-void info(unsigned IndentationLevel, const char *Fmt, Ts &&...Args) {
- if (Verbose) {
- llvm::WithColor::note()
- << std::string(IndentationLevel * IndentationWidth, ' ') << "- "
- << llvm::formatv(Fmt, std::forward<Ts>(Args)...) << "\n";
- }
-}
+} // namespace LocalErrorMessages
//===----------------------------------------------------------------------===//
// link action
@@ -161,7 +183,7 @@ LinkerInput validateLinkInput(llvm::TimerGroup &TG) {
LI.LinkUnitName = path::stem(LI.OutputFile.Path).str();
}
- info(2, "Validated output summary path '{0}'.", LI.OutputFile.Path);
+ info(Verbose, 2, "Validated output summary path '{0}'.", LI.OutputFile.Path);
{
llvm::TimeRegion _(Time ? &TValidate : nullptr);
@@ -170,22 +192,22 @@ LinkerInput validateLinkInput(llvm::TimerGroup &TG) {
}
}
- info(2, "Validated {0} input summary paths.", LI.InputFiles.size());
+ info(Verbose, 2, "Validated {0} input summary paths.", LI.InputFiles.size());
return LI;
}
void runLink(llvm::TimerGroup &TG) {
- info(0, "Linking started.");
+ info(Verbose, 0, "Linking started.");
LinkerInput LI;
{
- info(1, "Validating input.");
+ info(Verbose, 1, "Validating input.");
LI = validateLinkInput(TG);
}
- info(1, "Linking input.");
- info(2, "Constructing linker.");
+ 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.
@@ -197,14 +219,14 @@ void runLink(llvm::TimerGroup &TG) {
llvm::Timer TLink("link", "Link Summaries", TG);
llvm::Timer TWrite("write", "Write Summary", TG);
- info(2, "Linking summaries.");
+ info(Verbose, 2, "Linking summaries.");
for (auto [Index, InputFile] : llvm::enumerate(LI.InputFiles)) {
std::unique_ptr<TUSummaryEncoding> Summary;
{
- info(3, "[{0}/{1}] Reading '{2}'.", (Index + 1), LI.InputFiles.size(),
- InputFile.Path);
+ info(Verbose, 3, "[{0}/{1}] Reading '{2}'.", (Index + 1),
+ LI.InputFiles.size(), InputFile.Path);
llvm::TimeRegion _(Time ? &TRead : nullptr);
@@ -219,8 +241,8 @@ void runLink(llvm::TimerGroup &TG) {
}
{
- info(3, "[{0}/{1}] Linking '{2}'.", (Index + 1), LI.InputFiles.size(),
- InputFile.Path);
+ info(Verbose, 3, "[{0}/{1}] Linking '{2}'.", (Index + 1),
+ LI.InputFiles.size(), InputFile.Path);
llvm::TimeRegion _(Time ? &TLink : nullptr);
@@ -233,7 +255,7 @@ void runLink(llvm::TimerGroup &TG) {
}
{
- info(2, "Writing output summary to '{0}'.", LI.OutputFile.Path);
+ info(Verbose, 2, "Writing output summary to '{0}'.", LI.OutputFile.Path);
llvm::TimeRegion _(Time ? &TWrite : nullptr);
@@ -244,7 +266,7 @@ void runLink(llvm::TimerGroup &TG) {
}
}
- info(0, "Linking finished.");
+ info(Verbose, 0, "Linking finished.");
}
//===----------------------------------------------------------------------===//
@@ -269,6 +291,19 @@ void runStaticLibrary(llvm::TimerGroup &TG) {
StaticLibraryVerb.getValue());
}
+//===----------------------------------------------------------------------===//
+// multi-arch subcommand dispatch
+//===----------------------------------------------------------------------===//
+
+void runMultiArch(llvm::TimerGroup &TG) {
+ if (MultiArchVerb == MultiArchCreateVerb) {
+ MultiArchCreateCLI MAC;
+ MAC.run(TG, MultiArchInputs, MultiArchOutput, Verbose, Time);
+ return;
+ }
+ fail(LocalErrorMessages::UnknownMultiArchVerb, MultiArchVerb.getValue());
+}
+
} // namespace
//===----------------------------------------------------------------------===//
@@ -285,6 +320,8 @@ int main(int argc, const char **argv) {
if (StaticLibraryCmd) {
runStaticLibrary(Timers);
+ } else if (MultiArchCmd) {
+ runMultiArch(Timers);
} else {
// Default (no subcommand): run the linker pipeline.
runLink(Timers);
diff --git a/clang/unittests/ScalableStaticAnalysis/BuildNamespaceTest.cpp b/clang/unittests/ScalableStaticAnalysis/BuildNamespaceTest.cpp
index 5089796a2b0b8..43d8b5f7e0e43 100644
--- a/clang/unittests/ScalableStaticAnalysis/BuildNamespaceTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/BuildNamespaceTest.cpp
@@ -31,6 +31,19 @@ TEST(BuildNamespaceTest, DifferentKinds) {
EXPECT_NE(CU, LU);
}
+TEST(BuildNamespaceTest, WithKind) {
+ BuildNamespace Bare(BuildNamespaceKind::StaticLibrary, "libmulti");
+ BuildNamespace Wrapper =
+ Bare.withKind(BuildNamespaceKind::MultiArchStaticLibrary);
+
+ // The name is preserved and only the kind changes.
+ EXPECT_EQ(Wrapper, BuildNamespace(BuildNamespaceKind::MultiArchStaticLibrary,
+ "libmulti"));
+ // The original is left unmodified.
+ EXPECT_EQ(Bare,
+ BuildNamespace(BuildNamespaceKind::StaticLibrary, "libmulti"));
+}
+
// NestedBuildNamespace Tests
TEST(NestedBuildNamespaceTest, DefaultConstruction) {
More information about the cfe-commits
mailing list