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 <[email protected]> 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) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
