|
| 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 | +} |
0 commit comments