Skip to content

Commit 94d2158

Browse files
authored
Add clang-offload-deps tool for generating dependence files for offload targets (#2872)
This tool is intended to be used by the clang driver for offload linking with static offload libraries. It takes linked host image as input and produces bitcode files (one per offload target) containing references to symbols that must be defined in the target images. Each dependence bitcode file then is expected to be compiled to an object by the driver using appropriate target toolchain, and dependence object added to the target linker as input together with the other inputs. References to the symbols in dependence object should ensure that target linker pulls in necessary symbol definitions from the input static libraries. Signed-off-by: Sergey Dmitriev <[email protected]>
1 parent e05a19c commit 94d2158

File tree

7 files changed

+329
-0
lines changed

7 files changed

+329
-0
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ llvm/tools/sycl-post-link/ @kbobrovs @AlexeySachkov
103103
# Clang offload tools
104104
clang/tools/clang-offload-bundler/ @kbobrovs @sndmitriev
105105
clang/tools/clang-offload-wrapper/ @sndmitriev @kbobrovs
106+
clang/tools/clang-offload-deps/ @sndmitriev
106107

107108
# Explicit SIMD
108109
SYCLLowerIR/ @kbobrovs

clang/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ list(APPEND CLANG_TEST_DEPS
6565
clang-format
6666
clang-tblgen
6767
clang-offload-bundler
68+
clang-offload-deps
6869
clang-import-test
6970
clang-rename
7071
clang-refactor
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// REQUIRES: x86-registered-target
2+
3+
//
4+
// Check help message.
5+
//
6+
// RUN: clang-offload-deps --help | FileCheck %s --check-prefix CHECK-HELP
7+
// CHECK-HELP: {{.*}}OVERVIEW: A tool for creating dependence bitcode files for offload targets. Takes
8+
// CHECK-HELP-NEXT: {{.*}}host image as input and produces bitcode files, one per offload target, with
9+
// CHECK-HELP-NEXT: {{.*}}references to symbols that must be defined in target images.
10+
// CHECK-HELP: {{.*}}USAGE: clang-offload-deps [options] <input file>
11+
// CHECK-HELP: {{.*}} --outputs=<string> - [<output file>,...]
12+
// CHECK-HELP: {{.*}} --targets=<string> - [<offload kind>-<target triple>,...]
13+
14+
//
15+
// Create source image for reading dependencies from.
16+
//
17+
// RUN: %clang -target %itanium_abi_triple -c %s -o %t.host
18+
// RUN: %clang -target x86_64-pc-linux-gnu -c %s -o %t.x86_64
19+
// RUN: %clang -target spir64 -emit-llvm -c %s -o %t.spir64
20+
// RUN: clang-offload-bundler -type=o -targets=host-%itanium_abi_triple,openmp-x86_64-pc-linux-gnu,sycl-spir64 -inputs=%t.host,%t.x86_64,%t.spir64 -outputs=%t.fat
21+
22+
//
23+
// Generate dependencies for targets and check contents of the output bitcode files.
24+
//
25+
// RUN: clang-offload-deps -targets=openmp-x86_64-pc-linux-gnu,sycl-spir64 -outputs=%t.deps.x86_64,%t.deps.spir64 %t.fat
26+
// RUN: llvm-dis -o - %t.deps.x86_64 | FileCheck %s --check-prefixes=CHECK-DEPS-X86_64
27+
// RUN: llvm-dis -o - %t.deps.spir64 | FileCheck %s --check-prefixes=CHECK-DEPS-SPIR64
28+
29+
// CHECK-DEPS-X86_64: target triple = "x86_64-pc-linux-gnu"
30+
// CHECK-DEPS-X86_64: @bar = external global i8*
31+
// CHECK-DEPS-X86_64: @foo = external global i8*
32+
// CHECK-DEPS-X86_64: @offload.symbols = hidden local_unnamed_addr global [2 x i8*] [i8* bitcast (i8** @bar to i8*), i8* bitcast (i8** @foo to i8*)]
33+
34+
// CHECK-DEPS-SPIR64: target triple = "spir64"
35+
// CHECK-DEPS-SPIR64: @bar = external global i8*
36+
// CHECK-DEPS-SPIR64: @foo = external global i8*
37+
// CHECK-DEPS-SPIR64: @llvm.used = appending global [2 x i8*] [i8* bitcast (i8** @bar to i8*), i8* bitcast (i8** @foo to i8*)], section "llvm.metadata"
38+
39+
//
40+
// Check that input with no .tgtsym section is handled correctly.
41+
//
42+
// RUN: clang-offload-deps -targets=openmp-x86_64-pc-linux-gnu,sycl-spir64 -outputs=%t.empty.x86_64,%t.empty.spir64 %t.host
43+
// RUN: llvm-dis -o - %t.empty.x86_64 | FileCheck %s --check-prefixes=CHECK-EMPTY-X86_64
44+
// RUN: llvm-dis -o - %t.empty.spir64 | FileCheck %s --check-prefixes=CHECK-EMPTY-SPIR64
45+
46+
// CHECK-EMPTY-X86_64: target triple = "x86_64-pc-linux-gnu"
47+
// CHECK-EMPTY-X86_64-NOT: @offload.symbols
48+
49+
// CHECK-EMPTY-SPIR64: target triple = "spir64"
50+
// CHECK-EMPTY-SPIR64-NOT: @llvm.used
51+
52+
void foo(void) {}
53+
void bar(void) {}

clang/tools/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_clang_subdirectory(clang-format-vs)
99
add_clang_subdirectory(clang-fuzzer)
1010
add_clang_subdirectory(clang-import-test)
1111
add_clang_subdirectory(clang-offload-bundler)
12+
add_clang_subdirectory(clang-offload-deps)
1213
add_clang_subdirectory(clang-offload-wrapper)
1314
add_clang_subdirectory(clang-scan-deps)
1415

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
set(LLVM_LINK_COMPONENTS BitWriter Core Object Support)
2+
3+
add_clang_tool(clang-offload-deps
4+
ClangOffloadDeps.cpp
5+
6+
DEPENDS
7+
intrinsics_gen
8+
)
9+
10+
set(CLANG_OFFLOAD_DEPS_LIB_DEPS
11+
clangBasic
12+
)
13+
14+
add_dependencies(clang clang-offload-deps)
15+
16+
clang_target_link_libraries(clang-offload-deps
17+
PRIVATE
18+
${CLANG_OFFLOAD_DEPS_LIB_DEPS}
19+
)
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
//===----------- clang-offload-deps/ClangOffloadDeps.cpp ------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// Implementation of the clang-offload-deps tool. This tool is intended to be
11+
/// used by the clang driver for offload linking with static offload libraries.
12+
/// It takes linked host image as input and produces bitcode files, one per
13+
/// offload target, containing references to symbols that must be defined in the
14+
/// target images. Dependence bitcode file is then expected to be compiled to an
15+
/// object by the driver using the appropriate offload target toolchain. This
16+
/// dependence object is added to the target linker as input together with the
17+
/// other inputs. References to the symbols in dependence object should ensure
18+
/// that target linker pulls in necessary symbol definitions from the input
19+
/// static libraries.
20+
///
21+
//===----------------------------------------------------------------------===//
22+
23+
#include "clang/Basic/Version.h"
24+
#include "llvm/ADT/ArrayRef.h"
25+
#include "llvm/ADT/Triple.h"
26+
#include "llvm/Bitcode/BitcodeWriter.h"
27+
#include "llvm/IR/GlobalVariable.h"
28+
#include "llvm/IR/LLVMContext.h"
29+
#include "llvm/IR/Module.h"
30+
#ifndef NDEBUG
31+
#include "llvm/IR/Verifier.h"
32+
#endif // NDEBUG
33+
#include "llvm/Object/ObjectFile.h"
34+
#include "llvm/Support/CommandLine.h"
35+
#include "llvm/Support/Errc.h"
36+
#include "llvm/Support/ErrorOr.h"
37+
#include "llvm/Support/Signals.h"
38+
#include "llvm/Support/ToolOutputFile.h"
39+
#include "llvm/Support/WithColor.h"
40+
#include "llvm/Support/raw_ostream.h"
41+
42+
#define SYMBOLS_SECTION_NAME ".tgtsym"
43+
44+
using namespace llvm;
45+
using namespace llvm::object;
46+
47+
static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
48+
49+
// Mark all our options with this category, everything else (except for -version
50+
// and -help) will be hidden.
51+
static cl::OptionCategory
52+
ClangOffloadDepsCategory("clang-offload-deps options");
53+
54+
static cl::list<std::string> Outputs("outputs", cl::CommaSeparated,
55+
cl::OneOrMore,
56+
cl::desc("[<output file>,...]"),
57+
cl::cat(ClangOffloadDepsCategory));
58+
static cl::list<std::string>
59+
Targets("targets", cl::CommaSeparated, cl::OneOrMore,
60+
cl::desc("[<offload kind>-<target triple>,...]"),
61+
cl::cat(ClangOffloadDepsCategory));
62+
63+
static cl::opt<std::string> Input(cl::Positional, cl::Required,
64+
cl::desc("<input file>"),
65+
cl::cat(ClangOffloadDepsCategory));
66+
67+
/// Path to the current binary.
68+
static std::string ToolPath;
69+
70+
static void reportError(Error E) {
71+
logAllUnhandledErrors(std::move(E), WithColor::error(errs(), ToolPath));
72+
}
73+
74+
int main(int argc, const char **argv) {
75+
sys::PrintStackTraceOnErrorSignal(argv[0]);
76+
ToolPath = sys::fs::getMainExecutable(argv[0], &ToolPath);
77+
78+
cl::HideUnrelatedOptions(ClangOffloadDepsCategory);
79+
cl::SetVersionPrinter([](raw_ostream &OS) {
80+
OS << clang::getClangToolFullVersion("clang-offload-deps") << '\n';
81+
});
82+
cl::ParseCommandLineOptions(
83+
argc, argv,
84+
"A tool for creating dependence bitcode files for offload targets. "
85+
"Takes\nhost image as input and produces bitcode files, one per offload "
86+
"target, with\nreferences to symbols that must be defined in target "
87+
"images.\n");
88+
89+
if (Help) {
90+
cl::PrintHelpMessage();
91+
return 0;
92+
}
93+
94+
// The number of output files and targets should match.
95+
if (Targets.size() != Outputs.size()) {
96+
reportError(
97+
createStringError(errc::invalid_argument,
98+
"number of output files and targets should match"));
99+
return 1;
100+
}
101+
102+
// Verify that given targets are valid. Each target string is expected to have
103+
// the following format
104+
// <kind>-<triple>
105+
// where <kind> is host, openmp, hip, sycl or fpga,
106+
// and <triple> is an offload target triple.
107+
SmallVector<StringRef, 8u> Triples(Targets.size());
108+
for (unsigned I = 0; I < Targets.size(); ++I) {
109+
StringRef Kind;
110+
std::tie(Kind, Triples[I]) = StringRef(Targets[I]).split('-');
111+
112+
bool KindIsValid = StringSwitch<bool>(Kind)
113+
.Case("host", true)
114+
.Case("openmp", true)
115+
.Case("hip", true)
116+
.Case("sycl", true)
117+
.Case("fpga", true)
118+
.Default(false);
119+
120+
bool TripleIsValid = Triple(Triples[I]).getArch() != Triple::UnknownArch;
121+
122+
if (!KindIsValid || !TripleIsValid) {
123+
SmallVector<char, 128u> Buf;
124+
raw_svector_ostream Msg(Buf);
125+
Msg << "invalid target '" << Targets[I] << "'";
126+
if (!KindIsValid)
127+
Msg << ", unknown offloading kind '" << Kind << "'";
128+
if (!TripleIsValid)
129+
Msg << ", unknown target triple '" << Triples[I] << "'";
130+
reportError(createStringError(errc::invalid_argument, Msg.str()));
131+
return 1;
132+
}
133+
}
134+
135+
// Read input file. It should have one of the supported object file formats.
136+
Expected<OwningBinary<ObjectFile>> ObjectOrErr =
137+
ObjectFile::createObjectFile(Input);
138+
if (!ObjectOrErr) {
139+
reportError(ObjectOrErr.takeError());
140+
return 1;
141+
}
142+
143+
// Then try to find a section in the input binary which contains offload
144+
// symbol names and parse section contents.
145+
DenseMap<StringRef, SmallDenseSet<StringRef>> Target2Symbols;
146+
for (SectionRef Section : ObjectOrErr->getBinary()->sections()) {
147+
// Look for the .tgtsym section in the binary.
148+
Expected<StringRef> NameOrErr = Section.getName();
149+
if (!NameOrErr) {
150+
reportError(NameOrErr.takeError());
151+
return 1;
152+
}
153+
if (*NameOrErr != SYMBOLS_SECTION_NAME)
154+
continue;
155+
156+
// This is the section we are looking for, read symbol names from it.
157+
Expected<StringRef> DataOrErr = Section.getContents();
158+
if (!DataOrErr) {
159+
reportError(DataOrErr.takeError());
160+
return 1;
161+
}
162+
163+
// Symbol names are prefixed by a target, and prefixed names are separated
164+
// by '\0' characters from each other. Find the names matching our list of
165+
// offload targets and insert them into the map.
166+
for (StringRef Symbol = DataOrErr.get(); !Symbol.empty();) {
167+
unsigned Len = strlen(Symbol.data());
168+
169+
for (const std::string &Target : Targets) {
170+
std::string Prefix = Target + ".";
171+
if (Symbol.startswith(Prefix))
172+
Target2Symbols[Target].insert(
173+
Symbol.substr(Prefix.size(), Len - Prefix.size()));
174+
}
175+
176+
Symbol = Symbol.drop_front(Len + 1u);
177+
}
178+
179+
// Binary should not have more than one .tgtsym section.
180+
break;
181+
}
182+
183+
LLVMContext Context;
184+
Type *Int8PtrTy = Type::getInt8PtrTy(Context);
185+
186+
// Create bitcode file with the symbol names for each target and write it to
187+
// the output file.
188+
SmallVector<std::unique_ptr<ToolOutputFile>, 8u> Files;
189+
Files.reserve(Outputs.size());
190+
for (unsigned I = 0; I < Outputs.size(); ++I) {
191+
StringRef FileName = Outputs[I];
192+
193+
Module Mod{"offload-deps", Context};
194+
Mod.setTargetTriple(Triples[I]);
195+
196+
SmallVector<Constant *, 8u> Used;
197+
Used.reserve(Target2Symbols[Targets[I]].size());
198+
for (StringRef Symbol : Target2Symbols[Targets[I]])
199+
Used.push_back(ConstantExpr::getPointerBitCastOrAddrSpaceCast(
200+
Mod.getOrInsertGlobal(Symbol, Int8PtrTy), Int8PtrTy));
201+
202+
if (!Used.empty()) {
203+
ArrayType *ArrayTy = ArrayType::get(Int8PtrTy, Used.size());
204+
205+
// SPIRV linking is done on LLVM IR inputs, so we can use special
206+
// global variable llvm.used to represent a reference to a symbol. But for
207+
// other targets we have to create a real reference since llvm.used may
208+
// not be representable in the object file.
209+
if (Triple(Triples[I]).isSPIR()) {
210+
auto *GV = new GlobalVariable(
211+
Mod, ArrayTy, false, GlobalValue::AppendingLinkage,
212+
ConstantArray::get(ArrayTy, Used), "llvm.used");
213+
GV->setSection("llvm.metadata");
214+
} else {
215+
auto *GV = new GlobalVariable(
216+
Mod, ArrayTy, false, GlobalValue::ExternalLinkage,
217+
ConstantArray::get(ArrayTy, Used), "offload.symbols");
218+
GV->setUnnamedAddr(GlobalValue::UnnamedAddr::Local);
219+
GV->setVisibility(GlobalValue::HiddenVisibility);
220+
}
221+
}
222+
223+
#ifndef NDEBUG
224+
if (verifyModule(Mod, &errs())) {
225+
reportError(createStringError(inconvertibleErrorCode(),
226+
"module verification error"));
227+
return 1;
228+
}
229+
#endif // NDEBUG
230+
231+
// Open output file.
232+
std::error_code EC;
233+
const auto &File = Files.emplace_back(
234+
std::make_unique<ToolOutputFile>(FileName, EC, sys::fs::OF_None));
235+
if (EC) {
236+
reportError(createFileError(FileName, EC));
237+
return 1;
238+
}
239+
240+
// Write deps module to the output.
241+
WriteBitcodeToFile(Mod, File->os());
242+
if (File->os().has_error()) {
243+
reportError(createFileError(FileName, File->os().error()));
244+
return 1;
245+
}
246+
}
247+
248+
// Everything is done, keep the output files.
249+
for (const auto &File : Files)
250+
File->keep();
251+
252+
return 0;
253+
}

sycl/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ add_custom_target( sycl-toolchain
284284
clang
285285
clang-offload-wrapper
286286
clang-offload-bundler
287+
clang-offload-deps
287288
file-table-tform
288289
llc
289290
llvm-ar

0 commit comments

Comments
 (0)