1 //===-- clang-offload-packager/ClangOffloadPackager.cpp - file bundler ---===//
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 // This tool takes several device object files and bundles them into a single
10 // binary image using a custom binary format. This is intended to be used to
11 // embed many device files into an application to create a fat binary.
12 //
13 //===---------------------------------------------------------------------===//
14 
15 #include "clang/Basic/Version.h"
16 
17 #include "llvm/BinaryFormat/Magic.h"
18 #include "llvm/Object/OffloadBinary.h"
19 #include "llvm/Support/CommandLine.h"
20 #include "llvm/Support/FileOutputBuffer.h"
21 #include "llvm/Support/FileSystem.h"
22 #include "llvm/Support/MemoryBuffer.h"
23 #include "llvm/Support/Path.h"
24 #include "llvm/Support/Signals.h"
25 #include "llvm/Support/StringSaver.h"
26 #include "llvm/Support/WithColor.h"
27 
28 using namespace llvm;
29 using namespace llvm::object;
30 
31 static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
32 
33 static cl::OptionCategory
34     ClangOffloadPackagerCategory("clang-offload-packager options");
35 
36 static cl::opt<std::string> OutputFile("o", cl::desc("Write output to <file>."),
37                                        cl::value_desc("file"),
38                                        cl::cat(ClangOffloadPackagerCategory));
39 
40 static cl::opt<std::string> InputFile(cl::Positional,
41                                       cl::desc("Extract from <file>."),
42                                       cl::value_desc("file"),
43                                       cl::cat(ClangOffloadPackagerCategory));
44 
45 static cl::list<std::string>
46     DeviceImages("image",
47                  cl::desc("List of key and value arguments. Required keywords "
48                           "are 'file' and 'triple'."),
49                  cl::value_desc("<key>=<value>,..."),
50                  cl::cat(ClangOffloadPackagerCategory));
51 
52 static void PrintVersion(raw_ostream &OS) {
53   OS << clang::getClangToolFullVersion("clang-offload-packager") << '\n';
54 }
55 
56 // Get a map containing all the arguments for the image. Repeated arguments will
57 // be placed in a comma separated list.
58 static DenseMap<StringRef, StringRef> getImageArguments(StringRef Image,
59                                                         StringSaver &Saver) {
60   DenseMap<StringRef, StringRef> Args;
61   for (StringRef Arg : llvm::split(Image, ",")) {
62     auto [Key, Value] = Arg.split("=");
63     if (Args.count(Key))
64       Args[Key] = Saver.save(Args[Key] + "," + Value);
65     else
66       Args[Key] = Value;
67   }
68 
69   return Args;
70 }
71 
72 static Error bundleImages() {
73   SmallVector<char, 1024> BinaryData;
74   raw_svector_ostream OS(BinaryData);
75   for (StringRef Image : DeviceImages) {
76     BumpPtrAllocator Alloc;
77     StringSaver Saver(Alloc);
78     DenseMap<StringRef, StringRef> Args = getImageArguments(Image, Saver);
79 
80     if (!Args.count("triple") || !Args.count("file"))
81       return createStringError(
82           inconvertibleErrorCode(),
83           "'file' and 'triple' are required image arguments");
84 
85     OffloadBinary::OffloadingImage ImageBinary{};
86     std::unique_ptr<llvm::MemoryBuffer> DeviceImage;
87     for (const auto &[Key, Value] : Args) {
88       if (Key == "file") {
89         llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> ObjectOrErr =
90             llvm::MemoryBuffer::getFileOrSTDIN(Value);
91         if (std::error_code EC = ObjectOrErr.getError())
92           return errorCodeToError(EC);
93 
94         // Clang uses the '.o' suffix for LTO bitcode.
95         if (identify_magic((*ObjectOrErr)->getBuffer()) == file_magic::bitcode)
96           ImageBinary.TheImageKind = object::IMG_Bitcode;
97         else
98           ImageBinary.TheImageKind =
99               getImageKind(sys::path::extension(Value).drop_front());
100         ImageBinary.Image = std::move(*ObjectOrErr);
101       } else if (Key == "kind") {
102         ImageBinary.TheOffloadKind = getOffloadKind(Value);
103       } else {
104         ImageBinary.StringData[Key] = Value;
105       }
106     }
107     std::unique_ptr<MemoryBuffer> Buffer = OffloadBinary::write(ImageBinary);
108     if (Buffer->getBufferSize() % OffloadBinary::getAlignment() != 0)
109       return createStringError(inconvertibleErrorCode(),
110                                "Offload binary has invalid size alignment");
111     OS << Buffer->getBuffer();
112   }
113 
114   Expected<std::unique_ptr<FileOutputBuffer>> OutputOrErr =
115       FileOutputBuffer::create(OutputFile, BinaryData.size());
116   if (!OutputOrErr)
117     return OutputOrErr.takeError();
118   std::unique_ptr<FileOutputBuffer> Output = std::move(*OutputOrErr);
119   std::copy(BinaryData.begin(), BinaryData.end(), Output->getBufferStart());
120   if (Error E = Output->commit())
121     return E;
122   return Error::success();
123 }
124 
125 static Error unbundleImages() {
126   ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
127       MemoryBuffer::getFileOrSTDIN(InputFile);
128   if (std::error_code EC = BufferOrErr.getError())
129     return createFileError(InputFile, EC);
130   std::unique_ptr<MemoryBuffer> Buffer = std::move(*BufferOrErr);
131 
132   // This data can be misaligned if extracted from an archive.
133   if (!isAddrAligned(Align(OffloadBinary::getAlignment()),
134                      Buffer->getBufferStart()))
135     Buffer = MemoryBuffer::getMemBufferCopy(Buffer->getBuffer(),
136                                             Buffer->getBufferIdentifier());
137 
138   SmallVector<OffloadFile> Binaries;
139   if (Error Err = extractOffloadBinaries(*Buffer, Binaries))
140     return Err;
141 
142   // Try to extract each device image specified by the user from the input file.
143   for (StringRef Image : DeviceImages) {
144     BumpPtrAllocator Alloc;
145     StringSaver Saver(Alloc);
146     auto Args = getImageArguments(Image, Saver);
147 
148     for (uint64_t I = 0, E = Binaries.size(); I != E; ++I) {
149       const auto *Binary = Binaries[I].getBinary();
150       // We handle the 'file' and 'kind' identifiers differently.
151       bool Match = llvm::all_of(Args, [&](auto &Arg) {
152         const auto [Key, Value] = Arg;
153         if (Key == "file")
154           return true;
155         if (Key == "kind")
156           return Binary->getOffloadKind() == getOffloadKind(Value);
157         return Binary->getString(Key) == Value;
158       });
159       if (!Match)
160         continue;
161 
162       // If the user did not provide a filename derive one from the input and
163       // image.
164       StringRef Filename =
165           !Args.count("file")
166               ? Saver.save(sys::path::stem(InputFile) + "-" +
167                            Binary->getTriple() + "-" + Binary->getArch() + "." +
168                            std::to_string(I) + "." +
169                            getImageKindName(Binary->getImageKind()))
170               : Args["file"];
171 
172       Expected<std::unique_ptr<FileOutputBuffer>> OutputOrErr =
173           FileOutputBuffer::create(Filename, Binary->getImage().size());
174       if (!OutputOrErr)
175         return OutputOrErr.takeError();
176       std::unique_ptr<FileOutputBuffer> Output = std::move(*OutputOrErr);
177       llvm::copy(Binary->getImage(), Output->getBufferStart());
178       if (Error E = Output->commit())
179         return E;
180     }
181   }
182 
183   return Error::success();
184 }
185 
186 int main(int argc, const char **argv) {
187   sys::PrintStackTraceOnErrorSignal(argv[0]);
188   cl::HideUnrelatedOptions(ClangOffloadPackagerCategory);
189   cl::SetVersionPrinter(PrintVersion);
190   cl::ParseCommandLineOptions(
191       argc, argv,
192       "A utility for bundling several object files into a single binary.\n"
193       "The output binary can then be embedded into the host section table\n"
194       "to create a fatbinary containing offloading code.\n");
195 
196   if (Help) {
197     cl::PrintHelpMessage();
198     return EXIT_SUCCESS;
199   }
200 
201   auto reportError = [argv](Error E) {
202     logAllUnhandledErrors(std::move(E), WithColor::error(errs(), argv[0]));
203     return EXIT_FAILURE;
204   };
205 
206   if (!InputFile.empty() && !OutputFile.empty())
207     return reportError(
208         createStringError(inconvertibleErrorCode(),
209                           "Packaging to an output file and extracting from an "
210                           "input file are mutually exclusive."));
211 
212   if (!OutputFile.empty()) {
213     if (Error Err = bundleImages())
214       return reportError(std::move(Err));
215   } else if (!InputFile.empty()) {
216     if (Error Err = unbundleImages())
217       return reportError(std::move(Err));
218   }
219 
220   return EXIT_SUCCESS;
221 }
222