https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/208799
>From 95c81aab0cfd6dc81d9434b01f8fad44a6806104 Mon Sep 17 00:00:00 2001 From: Aviral Goel <[email protected]> Date: Fri, 10 Jul 2026 11:20:08 -0700 Subject: [PATCH] [clang][ssaf] Add static-library create subcommand This change adds `clang-ssaf-linker static-library create` for bundling TU summaries into a StaticLibrary archive. The linker has been refactored to dispatch on `cl::SubCommand`; invocations without a subcommand still run the existing linker pipeline unchanged for backward compatibility. --- .../Core/EntityLinker/StaticLibrary.h | 9 +- .../Core/EntityLinker/TUSummaryEncoding.h | 6 + .../Outputs/lib-explicit-triple.json | 21 + .../Outputs/lib-namespace-override.json | 21 + .../ssaf-linker/Outputs/libempty.json | 21 + .../Scalable/ssaf-linker/Outputs/libtwo.json | 640 ++++++++++++++++++ .../Analysis/Scalable/ssaf-linker/help.test | 10 +- .../ssaf-linker/static-library-create.test | 98 +++ .../ssaf-linker/static-library-time.test | 19 + .../ssaf-linker/static-library-verbose.test | 23 + clang/tools/clang-ssaf-linker/CMakeLists.txt | 1 + clang/tools/clang-ssaf-linker/LinkerCommon.h | 47 ++ clang/tools/clang-ssaf-linker/SSAFLinker.cpp | 143 ++-- .../StaticLibraryCreateCLI.cpp | 214 ++++++ .../StaticLibraryCreateCLI.h | 77 +++ 15 files changed, 1297 insertions(+), 53 deletions(-) create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/lib-explicit-triple.json create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/lib-namespace-override.json create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/libempty.json create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/Outputs/libtwo.json create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/static-library-create.test create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/static-library-time.test create mode 100644 clang/test/Analysis/Scalable/ssaf-linker/static-library-verbose.test create mode 100644 clang/tools/clang-ssaf-linker/LinkerCommon.h create mode 100644 clang/tools/clang-ssaf-linker/StaticLibraryCreateCLI.cpp create mode 100644 clang/tools/clang-ssaf-linker/StaticLibraryCreateCLI.h diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h index 4ed34c9ec68cf..7358a73c32da1 100644 --- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h +++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h @@ -23,6 +23,8 @@ namespace clang::ssaf { +class StaticLibraryCreateCLI; + /// Represents a static library of translation unit summary encodings. /// /// A StaticLibrary bundles member translation units without performing @@ -35,12 +37,13 @@ namespace clang::ssaf { /// expressed as a fat wrapper around per-architecture StaticLibrary /// instances rather than as a single mixed-architecture library. /// -/// Members are stored as encoded TUSummaryEncoding objects: the archiver -/// tool never decodes per-entity payloads, and the linker consumes them -/// as-is during its selective inclusion pass. +/// Members are stored as encoded TUSummaryEncoding objects: the +/// static-library tool never decodes per-entity payloads, and the linker +/// consumes them as-is during its selective inclusion pass. class StaticLibrary { friend class MultiArchStaticLibrary; friend class SerializationFormat; + friend class StaticLibraryCreateCLI; friend class TestFixture; /// Orders members by their TUNamespace. As a nested struct of diff --git a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h index 2ece195ed1fd0..4a5ebc0d486ff 100644 --- a/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h +++ b/clang/include/clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h @@ -26,6 +26,11 @@ namespace clang::ssaf { +// Forward declared so it can be friended. Defined in +// clang/tools/clang-ssaf-linker/. Needs access to TUNamespace to report +// the offending member in a duplicate-member error during assembly. +class StaticLibraryCreateCLI; + /// Represents a translation unit summary in its serialized encoding. /// /// TUSummaryEncoding holds entity summary data in a format-specific encoding @@ -36,6 +41,7 @@ class TUSummaryEncoding { friend class EntityLinker; friend class SerializationFormat; friend class StaticLibrary; + friend class StaticLibraryCreateCLI; friend class TestFixture; // Target triple of the translation unit. diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lib-explicit-triple.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lib-explicit-triple.json new file mode 100644 index 0000000000000..57be3f594b8cb --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lib-explicit-triple.json @@ -0,0 +1,21 @@ +{ + "members": [ + { + "data": [], + "id_table": [], + "linkage_table": [], + "target_triple": "arm64-apple-macosx", + "tu_namespace": { + "kind": "CompilationUnit", + "name": "empty.cpp" + }, + "type": "TUSummary" + } + ], + "namespace": { + "kind": "StaticLibrary", + "name": "lib-explicit-triple" + }, + "target_triple": "arm64-apple-macosx", + "type": "StaticLibrary" +} diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lib-namespace-override.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lib-namespace-override.json new file mode 100644 index 0000000000000..0f8e9a8f26b47 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/lib-namespace-override.json @@ -0,0 +1,21 @@ +{ + "members": [ + { + "data": [], + "id_table": [], + "linkage_table": [], + "target_triple": "arm64-apple-macosx", + "tu_namespace": { + "kind": "CompilationUnit", + "name": "empty.cpp" + }, + "type": "TUSummary" + } + ], + "namespace": { + "kind": "StaticLibrary", + "name": "my-static-lib" + }, + "target_triple": "arm64-apple-macosx", + "type": "StaticLibrary" +} diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libempty.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libempty.json new file mode 100644 index 0000000000000..ccd2e650980b7 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libempty.json @@ -0,0 +1,21 @@ +{ + "members": [ + { + "data": [], + "id_table": [], + "linkage_table": [], + "target_triple": "arm64-apple-macosx", + "tu_namespace": { + "kind": "CompilationUnit", + "name": "empty.cpp" + }, + "type": "TUSummary" + } + ], + "namespace": { + "kind": "StaticLibrary", + "name": "libempty" + }, + "target_triple": "arm64-apple-macosx", + "type": "StaticLibrary" +} diff --git a/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libtwo.json b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libtwo.json new file mode 100644 index 0000000000000..7c4d80720ec57 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-linker/Outputs/libtwo.json @@ -0,0 +1,640 @@ +{ + "members": [ + { + "data": [ + { + "summary_data": [ + { + "entity_id": 0, + "entity_summary": { + "call_count": 3, + "callees": [ + { + "@": 1 + }, + { + "@": 2 + }, + { + "@": 3 + } + ] + } + }, + { + "entity_id": 1, + "entity_summary": { + "call_count": 2, + "callees": [ + { + "@": 0 + }, + { + "@": 4 + } + ] + } + }, + { + "entity_id": 2, + "entity_summary": { + "call_count": 1, + "callees": [ + { + "@": 5 + } + ] + } + }, + { + "entity_id": 3, + "entity_summary": { + "call_count": 2, + "callees": [ + { + "@": 0 + }, + { + "@": 1 + } + ] + } + }, + { + "entity_id": 4, + "entity_summary": { + "call_count": 1, + "callees": [ + { + "@": 3 + } + ] + } + }, + { + "entity_id": 5, + "entity_summary": { + "call_count": 3, + "callees": [ + { + "@": 0 + }, + { + "@": 3 + }, + { + "@": 4 + } + ] + } + } + ], + "summary_name": "Analysis1" + }, + { + "summary_data": [ + { + "entity_id": 0, + "entity_summary": { + "direct": { + "@": 3 + }, + "indirect": [ + { + "entity": { + "@": 1 + }, + "level": 1 + }, + { + "entity": { + "@": 4 + }, + "level": 2 + } + ] + } + }, + { + "entity_id": 1, + "entity_summary": { + "direct": { + "@": 0 + }, + "indirect": [ + { + "entity": { + "@": 2 + }, + "level": 1 + }, + { + "entity": { + "@": 5 + }, + "level": 2 + } + ] + } + }, + { + "entity_id": 2, + "entity_summary": { + "direct": { + "@": 1 + }, + "indirect": [ + { + "entity": { + "@": 3 + }, + "level": 1 + } + ] + } + }, + { + "entity_id": 3, + "entity_summary": { + "direct": { + "@": 4 + }, + "indirect": [ + { + "entity": { + "@": 0 + }, + "level": 1 + }, + { + "entity": { + "@": 2 + }, + "level": 2 + } + ] + } + }, + { + "entity_id": 4, + "entity_summary": { + "direct": { + "@": 5 + }, + "indirect": [ + { + "entity": { + "@": 1 + }, + "level": 1 + } + ] + } + }, + { + "entity_id": 5, + "entity_summary": { + "direct": { + "@": 2 + }, + "indirect": [ + { + "entity": { + "@": 0 + }, + "level": 1 + }, + { + "entity": { + "@": 3 + }, + "level": 2 + }, + { + "entity": { + "@": 4 + }, + "level": 3 + } + ] + } + } + ], + "summary_name": "Analysis2" + } + ], + "id_table": [ + { + "id": 0, + "name": { + "suffix": "", + "usr": "c:@F@shared_ext#" + } + }, + { + "id": 1, + "name": { + "suffix": "", + "usr": "c:@F@shared_int#" + } + }, + { + "id": 2, + "name": { + "suffix": "", + "usr": "c:@F@shared_none#" + } + }, + { + "id": 3, + "name": { + "suffix": "", + "usr": "c:@F@unique_ext_tu1#" + } + }, + { + "id": 4, + "name": { + "suffix": "", + "usr": "c:@F@unique_int_tu1#" + } + }, + { + "id": 5, + "name": { + "suffix": "", + "usr": "c:@F@unique_none_tu1#" + } + } + ], + "linkage_table": [ + { + "id": 0, + "linkage": { + "type": "External" + } + }, + { + "id": 1, + "linkage": { + "type": "Internal" + } + }, + { + "id": 2, + "linkage": { + "type": "None" + } + }, + { + "id": 3, + "linkage": { + "type": "External" + } + }, + { + "id": 4, + "linkage": { + "type": "Internal" + } + }, + { + "id": 5, + "linkage": { + "type": "None" + } + } + ], + "target_triple": "arm64-apple-macosx", + "tu_namespace": { + "kind": "CompilationUnit", + "name": "tu1.cpp" + }, + "type": "TUSummary" + }, + { + "data": [ + { + "summary_data": [ + { + "entity_id": 0, + "entity_summary": { + "call_count": 3, + "callees": [ + { + "@": 1 + }, + { + "@": 2 + }, + { + "@": 5 + } + ] + } + }, + { + "entity_id": 1, + "entity_summary": { + "call_count": 2, + "callees": [ + { + "@": 0 + }, + { + "@": 3 + } + ] + } + }, + { + "entity_id": 2, + "entity_summary": { + "call_count": 1, + "callees": [ + { + "@": 4 + } + ] + } + }, + { + "entity_id": 3, + "entity_summary": { + "call_count": 2, + "callees": [ + { + "@": 0 + }, + { + "@": 2 + } + ] + } + }, + { + "entity_id": 4, + "entity_summary": { + "call_count": 1, + "callees": [ + { + "@": 3 + } + ] + } + }, + { + "entity_id": 5, + "entity_summary": { + "call_count": 3, + "callees": [ + { + "@": 1 + }, + { + "@": 2 + }, + { + "@": 3 + } + ] + } + } + ], + "summary_name": "Analysis1" + }, + { + "summary_data": [ + { + "entity_id": 0, + "entity_summary": { + "direct": { + "@": 3 + }, + "indirect": [ + { + "entity": { + "@": 2 + }, + "level": 1 + }, + { + "entity": { + "@": 5 + }, + "level": 2 + } + ] + } + }, + { + "entity_id": 1, + "entity_summary": { + "direct": { + "@": 0 + }, + "indirect": [ + { + "entity": { + "@": 4 + }, + "level": 1 + } + ] + } + }, + { + "entity_id": 2, + "entity_summary": { + "direct": { + "@": 5 + }, + "indirect": [ + { + "entity": { + "@": 0 + }, + "level": 1 + }, + { + "entity": { + "@": 3 + }, + "level": 2 + } + ] + } + }, + { + "entity_id": 3, + "entity_summary": { + "direct": { + "@": 1 + }, + "indirect": [ + { + "entity": { + "@": 2 + }, + "level": 1 + }, + { + "entity": { + "@": 0 + }, + "level": 2 + } + ] + } + }, + { + "entity_id": 4, + "entity_summary": { + "direct": { + "@": 2 + }, + "indirect": [ + { + "entity": { + "@": 5 + }, + "level": 1 + }, + { + "entity": { + "@": 3 + }, + "level": 2 + } + ] + } + }, + { + "entity_id": 5, + "entity_summary": { + "direct": { + "@": 4 + }, + "indirect": [ + { + "entity": { + "@": 1 + }, + "level": 1 + }, + { + "entity": { + "@": 2 + }, + "level": 2 + }, + { + "entity": { + "@": 0 + }, + "level": 3 + } + ] + } + } + ], + "summary_name": "Analysis2" + } + ], + "id_table": [ + { + "id": 0, + "name": { + "suffix": "", + "usr": "c:@F@shared_ext#" + } + }, + { + "id": 1, + "name": { + "suffix": "", + "usr": "c:@F@shared_int#" + } + }, + { + "id": 2, + "name": { + "suffix": "", + "usr": "c:@F@shared_none#" + } + }, + { + "id": 3, + "name": { + "suffix": "", + "usr": "c:@F@unique_ext_tu2#" + } + }, + { + "id": 4, + "name": { + "suffix": "", + "usr": "c:@F@unique_int_tu2#" + } + }, + { + "id": 5, + "name": { + "suffix": "", + "usr": "c:@F@unique_none_tu2#" + } + } + ], + "linkage_table": [ + { + "id": 0, + "linkage": { + "type": "External" + } + }, + { + "id": 1, + "linkage": { + "type": "Internal" + } + }, + { + "id": 2, + "linkage": { + "type": "None" + } + }, + { + "id": 3, + "linkage": { + "type": "External" + } + }, + { + "id": 4, + "linkage": { + "type": "Internal" + } + }, + { + "id": 5, + "linkage": { + "type": "None" + } + } + ], + "target_triple": "arm64-apple-macosx", + "tu_namespace": { + "kind": "CompilationUnit", + "name": "tu2.cpp" + }, + "type": "TUSummary" + } + ], + "namespace": { + "kind": "StaticLibrary", + "name": "libtwo" + }, + "target_triple": "arm64-apple-macosx", + "type": "StaticLibrary" +} diff --git a/clang/test/Analysis/Scalable/ssaf-linker/help.test b/clang/test/Analysis/Scalable/ssaf-linker/help.test index cb071a7b22d7b..6f897f4ca9228 100644 --- a/clang/test/Analysis/Scalable/ssaf-linker/help.test +++ b/clang/test/Analysis/Scalable/ssaf-linker/help.test @@ -5,7 +5,13 @@ // CHECK: OVERVIEW: SSAF Linker // CHECK-EMPTY: -// CHECK-NEXT: USAGE: clang-ssaf-linker{{(\.exe)?}} [options] <input files> +// CHECK-NEXT: USAGE: clang-ssaf-linker{{(\.exe)?}} [subcommand] [options] <input files> +// CHECK-EMPTY: +// CHECK-NEXT: SUBCOMMANDS: +// CHECK-EMPTY: +// 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 // CHECK-EMPTY: // CHECK-NEXT: OPTIONS: // CHECK-NEXT: -h - Alias for --help @@ -18,4 +24,4 @@ // CHECK-NEXT: --print-options - Print non-default options after command line parsing // CHECK-NEXT: --time - Enable timing // CHECK-NEXT: --verbose - Enable verbose output -// CHECK-NEXT: --version - Display the version of this program \ No newline at end of file +// CHECK-NEXT: --version - Display the version of this program diff --git a/clang/test/Analysis/Scalable/ssaf-linker/static-library-create.test b/clang/test/Analysis/Scalable/ssaf-linker/static-library-create.test new file mode 100644 index 0000000000000..112d05ea50428 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-linker/static-library-create.test @@ -0,0 +1,98 @@ +// Tests for `clang-ssaf-linker static-library create`. +// +// These tests exercise the CLI shape (subcommand dispatch, verb positional, +// flag surface) and the happy / error paths of StaticLibrary construction. + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// ============================================================================ +// Happy path: single TU input +// ============================================================================ + +// The single-input case produces a StaticLibrary whose namespace name is +// derived from the output file stem (`libempty`), whose target triple is +// inferred from the input, and whose single member is the input TU. +// RUN: clang-ssaf-linker static-library create %S/Inputs/tu-empty.json -o %t/libempty.json +// RUN: diff %S/Outputs/libempty.json %t/libempty.json + +// ============================================================================ +// Happy path: multiple TU inputs +// ============================================================================ + +// Multiple inputs are bundled together; every member's target triple must +// match, and every member's TUNamespace must be unique. +// RUN: clang-ssaf-linker static-library create %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/libtwo.json +// RUN: diff %S/Outputs/libtwo.json %t/libtwo.json + +// ============================================================================ +// Explicit --namespace overrides the output-stem default +// ============================================================================ + +// RUN: clang-ssaf-linker static-library create %S/Inputs/tu-empty.json --namespace my-static-lib -o %t/lib-namespace-override.json +// RUN: diff %S/Outputs/lib-namespace-override.json %t/lib-namespace-override.json + +// ============================================================================ +// Explicit --target-triple matches inputs +// ============================================================================ + +// When explicit and matching, the flag is accepted and the produced +// StaticLibrary is identical to the inferred-triple case. +// RUN: clang-ssaf-linker static-library create %S/Inputs/tu-empty.json --target-triple arm64-apple-macosx -o %t/lib-explicit-triple.json +// RUN: diff %S/Outputs/lib-explicit-triple.json %t/lib-explicit-triple.json + +// ============================================================================ +// Explicit --target-triple disagrees with input +// ============================================================================ + +// RUN: not clang-ssaf-linker static-library create %S/Inputs/tu-empty.json --target-triple x86_64-apple-macosx -o %t/lib-mismatch.json 2>&1 \ +// RUN: | FileCheck %s --check-prefix=EXPLICIT-MISMATCH +// EXPLICIT-MISMATCH: target triple 'arm64-apple-macosx' from TU summary '{{.*}}tu-empty.json' does not match expected triple 'x86_64-apple-macosx' + +// ============================================================================ +// Duplicate TU namespaces across inputs +// ============================================================================ + +// Passing the same TU summary twice trips duplicate-member detection. +// RUN: not clang-ssaf-linker static-library create %S/Inputs/tu-empty.json %S/Inputs/tu-empty.json -o %t/lib-dup.json 2>&1 \ +// RUN: | FileCheck %s --check-prefix=DUP-MEMBER +// DUP-MEMBER: duplicate TU summary member with namespace BuildNamespace(CompilationUnit, empty.cpp) + +// ============================================================================ +// Empty input list +// ============================================================================ + +// The subcommand requires at least one positional input after the verb. +// RUN: not clang-ssaf-linker static-library create -o %t/lib-empty-inputs.json 2>&1 \ +// RUN: | FileCheck %s --check-prefix=NO-INPUTS +// NO-INPUTS: no input TU summaries: at least one input is required + +// ============================================================================ +// Unknown verb +// ============================================================================ + +// RUN: not clang-ssaf-linker static-library extract %S/Inputs/tu-empty.json -o %t/lib-unknown.json 2>&1 \ +// RUN: | FileCheck %s --check-prefix=UNKNOWN-VERB +// UNKNOWN-VERB: unknown static-library verb 'extract': expected 'create' + +// ============================================================================ +// Round-trip: produced StaticLibrary parses cleanly with clang-ssaf-format +// ============================================================================ + +// The linker's output must be a valid StaticLibrary artifact that +// clang-ssaf-format can read back and write out byte-identical (its own +// reader-writer round-trip). This ties the linker to the same on-disk +// invariants the format tool enforces. +// RUN: clang-ssaf-linker static-library create %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/librt.json +// RUN: clang-ssaf-format --type static-library %t/librt.json -o %t/librt-roundtrip.json +// RUN: diff %t/librt.json %t/librt-roundtrip.json + +// ============================================================================ +// Backward-compat: no subcommand still runs `link` +// ============================================================================ + +// Existing invocations that don't name a subcommand fall back to the linker +// pipeline unchanged. +// RUN: mkdir -p %t/default-cmd +// RUN: clang-ssaf-linker %S/Inputs/tu-empty.json -o %t/default-cmd/lu-empty.json +// RUN: diff %S/Outputs/lu-empty.json %t/default-cmd/lu-empty.json diff --git a/clang/test/Analysis/Scalable/ssaf-linker/static-library-time.test b/clang/test/Analysis/Scalable/ssaf-linker/static-library-time.test new file mode 100644 index 0000000000000..fb0e7f0695a2b --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-linker/static-library-time.test @@ -0,0 +1,19 @@ +// Test the --time flag on the `static-library create` subcommand. + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// RUN: clang-ssaf-linker static-library create --time \ +// RUN: %S/Inputs/tu-1.json %S/Inputs/tu-2.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 Summaries +// CHECK-DAG: {{.*}}Assemble StaticLibrary +// CHECK-DAG: {{.*}}Write StaticLibrary +// CHECK: {{.*}}Total diff --git a/clang/test/Analysis/Scalable/ssaf-linker/static-library-verbose.test b/clang/test/Analysis/Scalable/ssaf-linker/static-library-verbose.test new file mode 100644 index 0000000000000..42b4a1cdb7365 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-linker/static-library-verbose.test @@ -0,0 +1,23 @@ +// Test the --verbose flag on the `static-library create` subcommand. + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// RUN: clang-ssaf-linker static-library create --verbose \ +// RUN: %S/Inputs/tu-1.json %S/Inputs/tu-2.json -o %t/libv.json 2>&1 \ +// RUN: | FileCheck %s --match-full-lines + +// CHECK: note: - Creating StaticLibrary started. +// CHECK-NEXT: note: - Validating input. +// CHECK-NEXT: note: - Validated output path '{{.*}}libv.json'. +// CHECK-NEXT: note: - Validated 2 input summary paths. +// CHECK-NEXT: note: - Namespace name: 'libv'. +// CHECK-NEXT: note: - Bundling StaticLibrary objects. +// CHECK-NEXT: note: - Bundling members. +// CHECK-NEXT: note: - [1/2] Reading '{{.*}}tu-1.json'. +// CHECK-NEXT: note: - [1/2] Assembling '{{.*}}tu-1.json'. +// CHECK-NEXT: note: - [2/2] Reading '{{.*}}tu-2.json'. +// CHECK-NEXT: note: - [2/2] Assembling '{{.*}}tu-2.json'. +// CHECK-NEXT: note: - Target triple: 'arm64-apple-macosx'. +// CHECK-NEXT: note: - Writing StaticLibrary to '{{.*}}libv.json'. +// CHECK-NEXT: note: - Creating StaticLibrary finished. diff --git a/clang/tools/clang-ssaf-linker/CMakeLists.txt b/clang/tools/clang-ssaf-linker/CMakeLists.txt index a4d371d908373..c315a0b0faf32 100644 --- a/clang/tools/clang-ssaf-linker/CMakeLists.txt +++ b/clang/tools/clang-ssaf-linker/CMakeLists.txt @@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS add_clang_tool(clang-ssaf-linker SSAFLinker.cpp + StaticLibraryCreateCLI.cpp ) clang_target_link_libraries(clang-ssaf-linker diff --git a/clang/tools/clang-ssaf-linker/LinkerCommon.h b/clang/tools/clang-ssaf-linker/LinkerCommon.h new file mode 100644 index 0000000000000..12993c71047df --- /dev/null +++ b/clang/tools/clang-ssaf-linker/LinkerCommon.h @@ -0,0 +1,47 @@ +//===- LinkerCommon.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 +// +//===----------------------------------------------------------------------===// +// +// Shared cl::opt globals and diagnostic helpers used by every +// clang-ssaf-linker action's translation unit. The globals themselves +// are defined in SSAFLinker.cpp; this header only extern-declares them +// so per-action TUs can reference them without spelling out the +// boilerplate. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_LINKERCOMMON_H +#define LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_LINKERCOMMON_H + +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/WithColor.h" +#include "llvm/Support/raw_ostream.h" +#include <string> +#include <utility> + +// Shared cl::opt globals, defined in SSAFLinker.cpp. +extern llvm::cl::OptionCategory SsafLinkerCategory; +extern llvm::cl::SubCommand StaticLibraryCmd; +extern llvm::cl::opt<bool> Verbose; +extern llvm::cl::opt<bool> Time; + +// Indent width, in spaces, per level of nesting in verbose output. +inline constexpr unsigned IndentationWidth = 2; + +/// Prints one indented note to stderr when `--verbose` is set. No-op +/// otherwise. +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"; + } +} + +#endif // LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_LINKERCOMMON_H diff --git a/clang/tools/clang-ssaf-linker/SSAFLinker.cpp b/clang/tools/clang-ssaf-linker/SSAFLinker.cpp index a8bf6c97a37b8..9afb2ccbf235e 100644 --- a/clang/tools/clang-ssaf-linker/SSAFLinker.cpp +++ b/clang/tools/clang-ssaf-linker/SSAFLinker.cpp @@ -6,11 +6,18 @@ // //===----------------------------------------------------------------------===// // -// This file implements the SSAF entity linker tool that performs entity -// linking across multiple TU summaries using the EntityLinker framework. +// 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 (SSAF's analogue of +// `ar` / `libtool -static`) without performing entity resolution; the +// create action's logic lives in StaticLibraryCreateCLI. // //===----------------------------------------------------------------------===// +#include "LinkerCommon.h" +#include "StaticLibraryCreateCLI.h" + #include "clang/ScalableStaticAnalysis/Core/EntityLinker/EntityLinker.h" #include "clang/ScalableStaticAnalysis/Core/EntityLinker/TUSummaryEncoding.h" #include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h" @@ -19,13 +26,9 @@ #include "clang/ScalableStaticAnalysis/Tool/Utils.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Path.h" #include "llvm/Support/Timer.h" -#include "llvm/Support/WithColor.h" -#include "llvm/Support/raw_ostream.h" #include <memory> #include <string> @@ -34,14 +37,28 @@ using namespace clang::ssaf; namespace path = llvm::sys::path; -namespace { - //===----------------------------------------------------------------------===// // Command-Line Options +// +// These globals are visible to StaticLibraryCreateCLI.cpp via `extern` +// declarations. Any addition or rename must be mirrored there. //===----------------------------------------------------------------------===// cl::OptionCategory SsafLinkerCategory("clang-ssaf-linker options"); +// The `static-library` subcommand groups all StaticLibrary operations. +// The first positional argument selects the verb (currently only +// `create`) and the driver dispatches on it. Declared before the +// options that reference it so those options can attach to its scope. + +cl::SubCommand StaticLibraryCmd("static-library", + "Operations on StaticLibraries"); + +namespace { + +// The `link` action is the default when no subcommand is named. It +// parses against these top-level options. + cl::list<std::string> InputPaths(cl::Positional, cl::desc("<input files>"), cl::OneOrMore, cl::cat(SsafLinkerCategory)); @@ -49,11 +66,43 @@ cl::opt<std::string> OutputPath("o", cl::desc("Output file path"), cl::value_desc("path"), cl::Required, cl::cat(SsafLinkerCategory)); +} // namespace + +// --verbose and --time apply to every subcommand: they're one conceptual +// switch each for the whole tool. Attaching a single cl::opt to both the +// top-level scope and every named subcommand lets a single global back +// both `clang-ssaf-linker --verbose ...` and +// `clang-ssaf-linker static-library create --verbose ...`. + cl::opt<bool> Verbose("verbose", cl::desc("Enable verbose output"), - cl::init(false), cl::cat(SsafLinkerCategory)); + cl::init(false), cl::cat(SsafLinkerCategory), + cl::sub(cl::SubCommand::getTopLevel()), + cl::sub(StaticLibraryCmd)); cl::opt<bool> Time("time", cl::desc("Enable timing"), cl::init(false), - cl::cat(SsafLinkerCategory)); + cl::cat(SsafLinkerCategory), + cl::sub(cl::SubCommand::getTopLevel()), + cl::sub(StaticLibraryCmd)); + +namespace { + +// The verb positional under the `static-library` subcommand. Selecting +// which action to run happens here; the actions themselves live in their +// own translation units and read their own cl::opt globals. + +cl::opt<std::string> StaticLibraryVerb(cl::Positional, cl::Required, + cl::sub(StaticLibraryCmd), + cl::desc("<verb>"), + cl::value_desc("create"), + cl::cat(SsafLinkerCategory)); + +//===----------------------------------------------------------------------===// +// StaticLibrary Verbs +//===----------------------------------------------------------------------===// + +// Verb strings for the `static-library` subcommand. Kept in sync with +// UnknownStaticLibraryVerb below. +constexpr const char *StaticLibraryCreateVerb = "create"; //===----------------------------------------------------------------------===// // Error Messages @@ -63,25 +112,13 @@ namespace LocalErrorMessages { constexpr const char *LinkingSummary = "Linking summary '{0}'"; -} // namespace LocalErrorMessages - -//===----------------------------------------------------------------------===// -// Diagnostic Utilities -//===----------------------------------------------------------------------===// - -constexpr unsigned IndentationWidth = 2; +constexpr const char *UnknownStaticLibraryVerb = + "unknown static-library 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 //===----------------------------------------------------------------------===// -// Data Structures +// link action //===----------------------------------------------------------------------===// struct LinkerInput { @@ -90,11 +127,7 @@ struct LinkerInput { std::string LinkUnitName; }; -//===----------------------------------------------------------------------===// -// Pipeline -//===----------------------------------------------------------------------===// - -LinkerInput validate(llvm::TimerGroup &TG) { +LinkerInput validateLinkInput(llvm::TimerGroup &TG) { llvm::Timer TValidate("validate", "Validate Input", TG); LinkerInput LI; @@ -119,7 +152,16 @@ LinkerInput validate(llvm::TimerGroup &TG) { return LI; } -void link(const LinkerInput &LI, llvm::TimerGroup &TG) { +void runLink(llvm::TimerGroup &TG) { + info(0, "Linking started."); + + LinkerInput LI; + { + info(1, "Validating input."); + LI = validateLinkInput(TG); + } + + info(1, "Linking input."); info(2, "Constructing linker."); // TODO: The linker currently uses a hardcoded target triple. Architecture @@ -178,6 +220,22 @@ void link(const LinkerInput &LI, llvm::TimerGroup &TG) { fail(std::move(Err)); } } + + info(0, "Linking finished."); +} + +//===----------------------------------------------------------------------===// +// static-library subcommand dispatch +//===----------------------------------------------------------------------===// + +void runStaticLibrary(llvm::TimerGroup &TG) { + if (StaticLibraryVerb == StaticLibraryCreateVerb) { + StaticLibraryCreateCLI SLC; + SLC.run(TG); + return; + } + fail(LocalErrorMessages::UnknownStaticLibraryVerb, + StaticLibraryVerb.getValue()); } } // namespace @@ -192,23 +250,12 @@ int main(int argc, const char **argv) { InitLLVM X(argc, argv); initTool(argc, argv, "0.1", SsafLinkerCategory, ToolHeading); - llvm::TimerGroup LinkerTimers(getToolName(), ToolHeading); - LinkerInput LI; - - { - info(0, "Linking started."); - - { - info(1, "Validating input."); - LI = validate(LinkerTimers); - } - - { - info(1, "Linking input."); - link(LI, LinkerTimers); - } + llvm::TimerGroup Timers(getToolName(), ToolHeading); - info(0, "Linking finished."); + if (StaticLibraryCmd) { + runStaticLibrary(Timers); + } else { + runLink(Timers); } return 0; diff --git a/clang/tools/clang-ssaf-linker/StaticLibraryCreateCLI.cpp b/clang/tools/clang-ssaf-linker/StaticLibraryCreateCLI.cpp new file mode 100644 index 0000000000000..86b4e15863760 --- /dev/null +++ b/clang/tools/clang-ssaf-linker/StaticLibraryCreateCLI.cpp @@ -0,0 +1,214 @@ +//===- StaticLibraryCreateCLI.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 `static-library create` CLI action. Owns the +// subcommand's cl::opt globals and splits the work into an ordered +// set of phase methods (validate, bundle, write). The bundle phase +// streams inputs one at a time — read then insert — mirroring the +// `link` action's per-input read+link loop. +// +//===----------------------------------------------------------------------===// + +#include "StaticLibraryCreateCLI.h" + +#include "LinkerCommon.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/STLExtras.h" +#include "llvm/Support/Path.h" +#include <memory> + +using namespace llvm; +using namespace clang::ssaf; + +namespace path = llvm::sys::path; + +//===----------------------------------------------------------------------===// +// Command-Line Options +// +// The subcommand and the verb positional live in SSAFLinker.cpp because +// they drive dispatch; every other option is owned here since it is +// consumed only by this action. Shared globals come from LinkerCommon.h. +//===----------------------------------------------------------------------===// + +namespace { + +cl::list<std::string> StaticLibraryInputs(cl::Positional, + cl::sub(StaticLibraryCmd), + cl::desc("<TU summary files>"), + cl::cat(SsafLinkerCategory)); + +cl::opt<std::string> StaticLibraryOutput("o", cl::Required, + cl::sub(StaticLibraryCmd), + cl::desc("Output file path"), + cl::value_desc("path"), + cl::cat(SsafLinkerCategory)); + +cl::opt<std::string> StaticLibraryNamespace( + "namespace", cl::sub(StaticLibraryCmd), + cl::desc("Namespace name for the StaticLibrary (defaults to output " + "file stem)"), + cl::value_desc("name"), cl::cat(SsafLinkerCategory)); + +cl::opt<std::string> StaticLibraryTriple( + "target-triple", cl::sub(StaticLibraryCmd), + cl::desc("Target triple (defaults to inputs' triple; must match all " + "inputs when set)"), + cl::value_desc("triple"), cl::cat(SsafLinkerCategory)); + +//===----------------------------------------------------------------------===// +// Error Messages +//===----------------------------------------------------------------------===// + +constexpr const char *ReadingSummary = "Reading TU summary '{0}'"; + +constexpr const char *TargetTripleMismatch = + "target triple '{0}' from TU summary '{1}' does not match expected " + "triple '{2}'"; + +constexpr const char *NoInputs = + "no input TU summaries: at least one input is required"; + +constexpr const char *DuplicateMember = + "duplicate TU summary member with namespace {0}"; + +//===----------------------------------------------------------------------===// +// Helpers +//===----------------------------------------------------------------------===// + +BuildNamespace makeStaticLibraryNamespace(llvm::StringRef Name) { + return BuildNamespace(BuildNamespaceKind::StaticLibrary, Name); +} + +} // namespace + +//===----------------------------------------------------------------------===// +// StaticLibraryCreateCLI +//===----------------------------------------------------------------------===// + +void StaticLibraryCreateCLI::run(llvm::TimerGroup &TG) { + info(0, "Creating StaticLibrary started."); + + { + info(1, "Validating input."); + validate(TG); + } + + info(1, "Bundling StaticLibrary objects."); + StaticLibrary Result = bundle(TG); + write(TG, Result); + + info(0, "Creating StaticLibrary finished."); +} + +void StaticLibraryCreateCLI::validate(llvm::TimerGroup &TG) { + llvm::Timer TValidate("validate", "Validate Input", TG); + llvm::TimeRegion _(Time ? &TValidate : nullptr); + + OutputFile = FormatFile::fromOutputPath(StaticLibraryOutput); + info(2, "Validated output path '{0}'.", OutputFile.Path); + + if (StaticLibraryInputs.empty()) { + fail(NoInputs); + } + for (const auto &InputPath : StaticLibraryInputs) { + InputFiles.push_back(FormatFile::fromInputPath(InputPath)); + } + info(2, "Validated {0} input summary paths.", InputFiles.size()); + + NamespaceName = StaticLibraryNamespace.empty() + ? path::stem(OutputFile.Path).str() + : StaticLibraryNamespace; + info(2, "Namespace name: '{0}'.", NamespaceName); + + if (!StaticLibraryTriple.empty()) { + ResolvedTriple = llvm::Triple(StaticLibraryTriple); + } +} + +StaticLibrary StaticLibraryCreateCLI::bundle(llvm::TimerGroup &TG) { + llvm::Timer TRead("read", "Read Summaries", TG); + llvm::Timer TAssemble("assemble", "Assemble StaticLibrary", TG); + + // If the target triple came from --target-triple, we can build the + // StaticLibrary upfront; otherwise its triple is the first member's + // and construction has to wait until we've read that member. + std::optional<StaticLibrary> Result; + if (ResolvedTriple) { + Result.emplace(*ResolvedTriple, makeStaticLibraryNamespace(NamespaceName)); + } + + info(2, "Bundling members."); + + for (auto [Index, InputFile] : llvm::enumerate(InputFiles)) { + std::unique_ptr<TUSummaryEncoding> Member; + + { + info(3, "[{0}/{1}] Reading '{2}'.", (Index + 1), InputFiles.size(), + InputFile.Path); + llvm::TimeRegion _(Time ? &TRead : nullptr); + + auto ExpectedSummary = + InputFile.Format->readTUSummaryEncoding(InputFile.Path); + if (!ExpectedSummary) { + fail(ErrorBuilder::wrap(ExpectedSummary.takeError()) + .context(ReadingSummary, InputFile.Path) + .build()); + } + Member = std::make_unique<TUSummaryEncoding>(std::move(*ExpectedSummary)); + } + + // Resolve (or verify) the target triple and, if this is the first + // input under triple inference, construct the StaticLibrary now. + const llvm::Triple &MemberTriple = Member->getTargetTriple(); + if (!Result) { + ResolvedTriple = MemberTriple; + Result.emplace(*ResolvedTriple, + makeStaticLibraryNamespace(NamespaceName)); + } else if (MemberTriple != Result->TargetTriple) { + fail(TargetTripleMismatch, llvm::Triple::normalize(MemberTriple.str()), + InputFile.Path, llvm::Triple::normalize(Result->TargetTriple.str())); + } + + { + info(3, "[{0}/{1}] Assembling '{2}'.", (Index + 1), InputFiles.size(), + InputFile.Path); + llvm::TimeRegion _(Time ? &TAssemble : nullptr); + + // StaticLibraryCreateCLI is a friend of StaticLibrary and + // TUSummaryEncoding: we can insert into the private members set + // and copy the member's private TUNamespace for the error + // message, all without exposing mutating API on those types. + auto MemberNamespace = Member->TUNamespace; + auto [It, Inserted] = Result->Members.insert(std::move(Member)); + if (!Inserted) { + fail(DuplicateMember, MemberNamespace); + } + } + } + + info(2, "Target triple: '{0}'.", + llvm::Triple::normalize(ResolvedTriple->str())); + + return std::move(*Result); +} + +void StaticLibraryCreateCLI::write(llvm::TimerGroup &TG, + const StaticLibrary &Result) { + info(2, "Writing StaticLibrary to '{0}'.", OutputFile.Path); + + llvm::Timer TWrite("write", "Write StaticLibrary", TG); + llvm::TimeRegion _(Time ? &TWrite : nullptr); + + if (auto Err = + OutputFile.Format->writeStaticLibrary(Result, OutputFile.Path)) { + fail(std::move(Err)); + } +} diff --git a/clang/tools/clang-ssaf-linker/StaticLibraryCreateCLI.h b/clang/tools/clang-ssaf-linker/StaticLibraryCreateCLI.h new file mode 100644 index 0000000000000..ddd9df87cb2bd --- /dev/null +++ b/clang/tools/clang-ssaf-linker/StaticLibraryCreateCLI.h @@ -0,0 +1,77 @@ +//===- StaticLibraryCreateCLI.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 static-library +// create`: validates inputs and output, streams TU summaries into a +// StaticLibrary one at a time (reading and inserting each in turn), +// and serializes the result. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_STATICLIBRARYCREATECLI_H +#define LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_STATICLIBRARYCREATECLI_H + +#include "clang/ScalableStaticAnalysis/Core/EntityLinker/StaticLibrary.h" +#include "clang/ScalableStaticAnalysis/Tool/Utils.h" +#include "llvm/Support/Timer.h" +#include "llvm/TargetParser/Triple.h" +#include <optional> +#include <string> +#include <vector> + +namespace clang::ssaf { + +/// Runs the `static-library create` action for `clang-ssaf-linker`. +/// +/// The pipeline is split into phases so each is readable on its own. +/// Non-recoverable errors call fail() from Tool/Utils.h and terminate +/// the process. +/// +/// The class is a friend of the StaticLibrary and TUSummaryEncoding +/// model types so it can assemble a StaticLibrary in place (private +/// member-set insertion plus reading each encoding's private +/// TUNamespace for duplicate-member error reporting) without exposing +/// mutating API on those types. +class StaticLibraryCreateCLI { +public: + /// Orchestrator: validate → bundle → write. + void run(llvm::TimerGroup &TG); + +private: + /// Validates the output path and every input path, and resolves the + /// namespace name (defaulting to the output file stem when + /// `--namespace` is unset) and the explicit target triple from + /// `--target-triple` (if any). + void validate(llvm::TimerGroup &TG); + + /// Reads each validated input file and inserts it into a StaticLibrary + /// one at a time, mirroring the `link` action's per-input + /// read+link streaming pattern. Resolves the target triple from + /// `--target-triple` if set, else from the first input; every + /// subsequent input must match. Triple mismatches include the + /// offending input file's path in the error. + StaticLibrary bundle(llvm::TimerGroup &TG); + + /// Serializes the StaticLibrary to the validated output path. + void write(llvm::TimerGroup &TG, const StaticLibrary &Result); + + // State populated during validate() and consumed by later phases. + FormatFile OutputFile; + std::vector<FormatFile> InputFiles; + std::string NamespaceName; + + // Set by validate() only when `--target-triple` was passed. bundle() + // uses it to construct the StaticLibrary up front and to verify every + // input's triple; when unset, bundle() populates it from the first + // input and every subsequent input must match. + std::optional<llvm::Triple> ResolvedTriple; +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_TOOLS_CLANG_SSAF_LINKER_STATICLIBRARYCREATECLI_H _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
