/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/tools/clang-offload-wrapper/ClangOffloadWrapper.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===-- clang-offload-wrapper/ClangOffloadWrapper.cpp -----------*- C++ -*-===// |
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 offload wrapper tool. It takes offload target binaries |
11 | | /// as input and creates wrapper bitcode file containing target binaries |
12 | | /// packaged as data. Wrapper bitcode also includes initialization code which |
13 | | /// registers target binaries in offloading runtime at program startup. |
14 | | /// |
15 | | //===----------------------------------------------------------------------===// |
16 | | |
17 | | #include "clang/Basic/Version.h" |
18 | | #include "llvm/ADT/ArrayRef.h" |
19 | | #include "llvm/ADT/Triple.h" |
20 | | #include "llvm/BinaryFormat/ELF.h" |
21 | | #include "llvm/Bitcode/BitcodeWriter.h" |
22 | | #include "llvm/IR/Constants.h" |
23 | | #include "llvm/IR/GlobalVariable.h" |
24 | | #include "llvm/IR/IRBuilder.h" |
25 | | #include "llvm/IR/LLVMContext.h" |
26 | | #include "llvm/IR/Module.h" |
27 | | #include "llvm/Object/ELFObjectFile.h" |
28 | | #include "llvm/Object/ObjectFile.h" |
29 | | #include "llvm/Support/CommandLine.h" |
30 | | #include "llvm/Support/EndianStream.h" |
31 | | #include "llvm/Support/Errc.h" |
32 | | #include "llvm/Support/Error.h" |
33 | | #include "llvm/Support/ErrorOr.h" |
34 | | #include "llvm/Support/FileSystem.h" |
35 | | #include "llvm/Support/MemoryBuffer.h" |
36 | | #include "llvm/Support/Path.h" |
37 | | #include "llvm/Support/Program.h" |
38 | | #include "llvm/Support/Signals.h" |
39 | | #include "llvm/Support/ToolOutputFile.h" |
40 | | #include "llvm/Support/VCSRevision.h" |
41 | | #include "llvm/Support/WithColor.h" |
42 | | #include "llvm/Support/raw_ostream.h" |
43 | | #include "llvm/Transforms/Utils/ModuleUtils.h" |
44 | | #include <cassert> |
45 | | #include <cstdint> |
46 | | |
47 | 4 | #define OPENMP_OFFLOAD_IMAGE_VERSION "1.0" |
48 | | |
49 | | using namespace llvm; |
50 | | using namespace llvm::object; |
51 | | |
52 | | static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden); |
53 | | |
54 | | // Mark all our options with this category, everything else (except for -version |
55 | | // and -help) will be hidden. |
56 | | static cl::OptionCategory |
57 | | ClangOffloadWrapperCategory("clang-offload-wrapper options"); |
58 | | |
59 | | static cl::opt<std::string> Output("o", cl::Required, |
60 | | cl::desc("Output filename"), |
61 | | cl::value_desc("filename"), |
62 | | cl::cat(ClangOffloadWrapperCategory)); |
63 | | |
64 | | static cl::list<std::string> Inputs(cl::Positional, cl::OneOrMore, |
65 | | cl::desc("<input files>"), |
66 | | cl::cat(ClangOffloadWrapperCategory)); |
67 | | |
68 | | static cl::opt<std::string> |
69 | | Target("target", cl::Required, |
70 | | cl::desc("Target triple for the output module"), |
71 | | cl::value_desc("triple"), cl::cat(ClangOffloadWrapperCategory)); |
72 | | |
73 | | static cl::opt<bool> SaveTemps( |
74 | | "save-temps", |
75 | | cl::desc("Save temporary files that may be produced by the tool. " |
76 | | "This option forces print-out of the temporary files' names."), |
77 | | cl::Hidden); |
78 | | |
79 | | static cl::opt<bool> AddOpenMPOffloadNotes( |
80 | | "add-omp-offload-notes", |
81 | | cl::desc("Add LLVMOMPOFFLOAD ELF notes to ELF device images."), cl::Hidden); |
82 | | |
83 | | namespace { |
84 | | |
85 | | class BinaryWrapper { |
86 | | LLVMContext C; |
87 | | Module M; |
88 | | |
89 | | StructType *EntryTy = nullptr; |
90 | | StructType *ImageTy = nullptr; |
91 | | StructType *DescTy = nullptr; |
92 | | |
93 | | std::string ToolName; |
94 | | std::string ObjcopyPath; |
95 | | // Temporary file names that may be created during adding notes |
96 | | // to ELF offload images. Use -save-temps to keep them and also |
97 | | // see their names. A temporary file's name includes the name |
98 | | // of the original input ELF image, so you can easily match |
99 | | // them, if you have multiple inputs. |
100 | | std::vector<std::string> TempFiles; |
101 | | |
102 | | private: |
103 | 15 | IntegerType *getSizeTTy() { |
104 | 15 | switch (M.getDataLayout().getPointerTypeSize(Type::getInt8PtrTy(C))) { |
105 | 0 | case 4u: |
106 | 0 | return Type::getInt32Ty(C); |
107 | 15 | case 8u: |
108 | 15 | return Type::getInt64Ty(C); |
109 | 15 | } |
110 | 0 | llvm_unreachable("unsupported pointer type size"); |
111 | 0 | } |
112 | | |
113 | | // struct __tgt_offload_entry { |
114 | | // void *addr; |
115 | | // char *name; |
116 | | // size_t size; |
117 | | // int32_t flags; |
118 | | // int32_t reserved; |
119 | | // }; |
120 | 35 | StructType *getEntryTy() { |
121 | 35 | if (!EntryTy) |
122 | 5 | EntryTy = StructType::create("__tgt_offload_entry", Type::getInt8PtrTy(C), |
123 | 5 | Type::getInt8PtrTy(C), getSizeTTy(), |
124 | 5 | Type::getInt32Ty(C), Type::getInt32Ty(C)); |
125 | 35 | return EntryTy; |
126 | 35 | } |
127 | | |
128 | 20 | PointerType *getEntryPtrTy() { return PointerType::getUnqual(getEntryTy()); } |
129 | | |
130 | | // struct __tgt_device_image { |
131 | | // void *ImageStart; |
132 | | // void *ImageEnd; |
133 | | // __tgt_offload_entry *EntriesBegin; |
134 | | // __tgt_offload_entry *EntriesEnd; |
135 | | // }; |
136 | 15 | StructType *getDeviceImageTy() { |
137 | 15 | if (!ImageTy) |
138 | 5 | ImageTy = StructType::create("__tgt_device_image", Type::getInt8PtrTy(C), |
139 | 5 | Type::getInt8PtrTy(C), getEntryPtrTy(), |
140 | 5 | getEntryPtrTy()); |
141 | 15 | return ImageTy; |
142 | 15 | } |
143 | | |
144 | 5 | PointerType *getDeviceImagePtrTy() { |
145 | 5 | return PointerType::getUnqual(getDeviceImageTy()); |
146 | 5 | } |
147 | | |
148 | | // struct __tgt_bin_desc { |
149 | | // int32_t NumDeviceImages; |
150 | | // __tgt_device_image *DeviceImages; |
151 | | // __tgt_offload_entry *HostEntriesBegin; |
152 | | // __tgt_offload_entry *HostEntriesEnd; |
153 | | // }; |
154 | 15 | StructType *getBinDescTy() { |
155 | 15 | if (!DescTy) |
156 | 5 | DescTy = StructType::create("__tgt_bin_desc", Type::getInt32Ty(C), |
157 | 5 | getDeviceImagePtrTy(), getEntryPtrTy(), |
158 | 5 | getEntryPtrTy()); |
159 | 15 | return DescTy; |
160 | 15 | } |
161 | | |
162 | 10 | PointerType *getBinDescPtrTy() { |
163 | 10 | return PointerType::getUnqual(getBinDescTy()); |
164 | 10 | } |
165 | | |
166 | | /// Creates binary descriptor for the given device images. Binary descriptor |
167 | | /// is an object that is passed to the offloading runtime at program startup |
168 | | /// and it describes all device images available in the executable or shared |
169 | | /// library. It is defined as follows |
170 | | /// |
171 | | /// __attribute__((visibility("hidden"))) |
172 | | /// extern __tgt_offload_entry *__start_omp_offloading_entries; |
173 | | /// __attribute__((visibility("hidden"))) |
174 | | /// extern __tgt_offload_entry *__stop_omp_offloading_entries; |
175 | | /// |
176 | | /// static const char Image0[] = { <Bufs.front() contents> }; |
177 | | /// ... |
178 | | /// static const char ImageN[] = { <Bufs.back() contents> }; |
179 | | /// |
180 | | /// static const __tgt_device_image Images[] = { |
181 | | /// { |
182 | | /// Image0, /*ImageStart*/ |
183 | | /// Image0 + sizeof(Image0), /*ImageEnd*/ |
184 | | /// __start_omp_offloading_entries, /*EntriesBegin*/ |
185 | | /// __stop_omp_offloading_entries /*EntriesEnd*/ |
186 | | /// }, |
187 | | /// ... |
188 | | /// { |
189 | | /// ImageN, /*ImageStart*/ |
190 | | /// ImageN + sizeof(ImageN), /*ImageEnd*/ |
191 | | /// __start_omp_offloading_entries, /*EntriesBegin*/ |
192 | | /// __stop_omp_offloading_entries /*EntriesEnd*/ |
193 | | /// } |
194 | | /// }; |
195 | | /// |
196 | | /// static const __tgt_bin_desc BinDesc = { |
197 | | /// sizeof(Images) / sizeof(Images[0]), /*NumDeviceImages*/ |
198 | | /// Images, /*DeviceImages*/ |
199 | | /// __start_omp_offloading_entries, /*HostEntriesBegin*/ |
200 | | /// __stop_omp_offloading_entries /*HostEntriesEnd*/ |
201 | | /// }; |
202 | | /// |
203 | | /// Global variable that represents BinDesc is returned. |
204 | 5 | GlobalVariable *createBinDesc(ArrayRef<ArrayRef<char>> Bufs) { |
205 | | // Create external begin/end symbols for the offload entries table. |
206 | 5 | auto *EntriesB = new GlobalVariable( |
207 | 5 | M, getEntryTy(), /*isConstant*/ true, GlobalValue::ExternalLinkage, |
208 | 5 | /*Initializer*/ nullptr, "__start_omp_offloading_entries"); |
209 | 5 | EntriesB->setVisibility(GlobalValue::HiddenVisibility); |
210 | 5 | auto *EntriesE = new GlobalVariable( |
211 | 5 | M, getEntryTy(), /*isConstant*/ true, GlobalValue::ExternalLinkage, |
212 | 5 | /*Initializer*/ nullptr, "__stop_omp_offloading_entries"); |
213 | 5 | EntriesE->setVisibility(GlobalValue::HiddenVisibility); |
214 | | |
215 | | // We assume that external begin/end symbols that we have created above will |
216 | | // be defined by the linker. But linker will do that only if linker inputs |
217 | | // have section with "omp_offloading_entries" name which is not guaranteed. |
218 | | // So, we just create dummy zero sized object in the offload entries section |
219 | | // to force linker to define those symbols. |
220 | 5 | auto *DummyInit = |
221 | 5 | ConstantAggregateZero::get(ArrayType::get(getEntryTy(), 0u)); |
222 | 5 | auto *DummyEntry = new GlobalVariable( |
223 | 5 | M, DummyInit->getType(), true, GlobalVariable::ExternalLinkage, |
224 | 5 | DummyInit, "__dummy.omp_offloading.entry"); |
225 | 5 | DummyEntry->setSection("omp_offloading_entries"); |
226 | 5 | DummyEntry->setVisibility(GlobalValue::HiddenVisibility); |
227 | | |
228 | 5 | auto *Zero = ConstantInt::get(getSizeTTy(), 0u); |
229 | 5 | Constant *ZeroZero[] = {Zero, Zero}; |
230 | | |
231 | | // Create initializer for the images array. |
232 | 5 | SmallVector<Constant *, 4u> ImagesInits; |
233 | 5 | ImagesInits.reserve(Bufs.size()); |
234 | 5 | for (ArrayRef<char> Buf : Bufs) { |
235 | 5 | auto *Data = ConstantDataArray::get(C, Buf); |
236 | 5 | auto *Image = new GlobalVariable(M, Data->getType(), /*isConstant*/ true, |
237 | 5 | GlobalVariable::InternalLinkage, Data, |
238 | 5 | ".omp_offloading.device_image"); |
239 | 5 | Image->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); |
240 | | |
241 | 5 | auto *Size = ConstantInt::get(getSizeTTy(), Buf.size()); |
242 | 5 | Constant *ZeroSize[] = {Zero, Size}; |
243 | | |
244 | 5 | auto *ImageB = ConstantExpr::getGetElementPtr(Image->getValueType(), |
245 | 5 | Image, ZeroZero); |
246 | 5 | auto *ImageE = ConstantExpr::getGetElementPtr(Image->getValueType(), |
247 | 5 | Image, ZeroSize); |
248 | | |
249 | 5 | ImagesInits.push_back(ConstantStruct::get(getDeviceImageTy(), ImageB, |
250 | 5 | ImageE, EntriesB, EntriesE)); |
251 | 5 | } |
252 | | |
253 | | // Then create images array. |
254 | 5 | auto *ImagesData = ConstantArray::get( |
255 | 5 | ArrayType::get(getDeviceImageTy(), ImagesInits.size()), ImagesInits); |
256 | | |
257 | 5 | auto *Images = |
258 | 5 | new GlobalVariable(M, ImagesData->getType(), /*isConstant*/ true, |
259 | 5 | GlobalValue::InternalLinkage, ImagesData, |
260 | 5 | ".omp_offloading.device_images"); |
261 | 5 | Images->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); |
262 | | |
263 | 5 | auto *ImagesB = ConstantExpr::getGetElementPtr(Images->getValueType(), |
264 | 5 | Images, ZeroZero); |
265 | | |
266 | | // And finally create the binary descriptor object. |
267 | 5 | auto *DescInit = ConstantStruct::get( |
268 | 5 | getBinDescTy(), |
269 | 5 | ConstantInt::get(Type::getInt32Ty(C), ImagesInits.size()), ImagesB, |
270 | 5 | EntriesB, EntriesE); |
271 | | |
272 | 5 | return new GlobalVariable(M, DescInit->getType(), /*isConstant*/ true, |
273 | 5 | GlobalValue::InternalLinkage, DescInit, |
274 | 5 | ".omp_offloading.descriptor"); |
275 | 5 | } |
276 | | |
277 | 5 | void createRegisterFunction(GlobalVariable *BinDesc) { |
278 | 5 | auto *FuncTy = FunctionType::get(Type::getVoidTy(C), /*isVarArg*/ false); |
279 | 5 | auto *Func = Function::Create(FuncTy, GlobalValue::InternalLinkage, |
280 | 5 | ".omp_offloading.descriptor_reg", &M); |
281 | 5 | Func->setSection(".text.startup"); |
282 | | |
283 | | // Get __tgt_register_lib function declaration. |
284 | 5 | auto *RegFuncTy = FunctionType::get(Type::getVoidTy(C), getBinDescPtrTy(), |
285 | 5 | /*isVarArg*/ false); |
286 | 5 | FunctionCallee RegFuncC = |
287 | 5 | M.getOrInsertFunction("__tgt_register_lib", RegFuncTy); |
288 | | |
289 | | // Construct function body |
290 | 5 | IRBuilder<> Builder(BasicBlock::Create(C, "entry", Func)); |
291 | 5 | Builder.CreateCall(RegFuncC, BinDesc); |
292 | 5 | Builder.CreateRetVoid(); |
293 | | |
294 | | // Add this function to constructors. |
295 | | // Set priority to 1 so that __tgt_register_lib is executed AFTER |
296 | | // __tgt_register_requires (we want to know what requirements have been |
297 | | // asked for before we load a libomptarget plugin so that by the time the |
298 | | // plugin is loaded it can report how many devices there are which can |
299 | | // satisfy these requirements). |
300 | 5 | appendToGlobalCtors(M, Func, /*Priority*/ 1); |
301 | 5 | } |
302 | | |
303 | 5 | void createUnregisterFunction(GlobalVariable *BinDesc) { |
304 | 5 | auto *FuncTy = FunctionType::get(Type::getVoidTy(C), /*isVarArg*/ false); |
305 | 5 | auto *Func = Function::Create(FuncTy, GlobalValue::InternalLinkage, |
306 | 5 | ".omp_offloading.descriptor_unreg", &M); |
307 | 5 | Func->setSection(".text.startup"); |
308 | | |
309 | | // Get __tgt_unregister_lib function declaration. |
310 | 5 | auto *UnRegFuncTy = FunctionType::get(Type::getVoidTy(C), getBinDescPtrTy(), |
311 | 5 | /*isVarArg*/ false); |
312 | 5 | FunctionCallee UnRegFuncC = |
313 | 5 | M.getOrInsertFunction("__tgt_unregister_lib", UnRegFuncTy); |
314 | | |
315 | | // Construct function body |
316 | 5 | IRBuilder<> Builder(BasicBlock::Create(C, "entry", Func)); |
317 | 5 | Builder.CreateCall(UnRegFuncC, BinDesc); |
318 | 5 | Builder.CreateRetVoid(); |
319 | | |
320 | | // Add this function to global destructors. |
321 | | // Match priority of __tgt_register_lib |
322 | 5 | appendToGlobalDtors(M, Func, /*Priority*/ 1); |
323 | 5 | } |
324 | | |
325 | | public: |
326 | | BinaryWrapper(StringRef Target, StringRef ToolName) |
327 | 5 | : M("offload.wrapper.object", C), ToolName(ToolName) { |
328 | 5 | M.setTargetTriple(Target); |
329 | | // Look for llvm-objcopy in the same directory, from which |
330 | | // clang-offload-wrapper is invoked. This helps OpenMP offload |
331 | | // LIT tests. |
332 | | |
333 | | // This just needs to be some symbol in the binary; C++ doesn't |
334 | | // allow taking the address of ::main however. |
335 | 5 | void *P = (void *)(intptr_t)&Help; |
336 | 5 | std::string COWPath = sys::fs::getMainExecutable(ToolName.str().c_str(), P); |
337 | 5 | if (!COWPath.empty()) { |
338 | 5 | auto COWDir = sys::path::parent_path(COWPath); |
339 | 5 | ErrorOr<std::string> ObjcopyPathOrErr = |
340 | 5 | sys::findProgramByName("llvm-objcopy", {COWDir}); |
341 | 5 | if (ObjcopyPathOrErr) { |
342 | 5 | ObjcopyPath = *ObjcopyPathOrErr; |
343 | 5 | return; |
344 | 5 | } |
345 | | |
346 | | // Otherwise, look through PATH environment. |
347 | 5 | } |
348 | | |
349 | 0 | ErrorOr<std::string> ObjcopyPathOrErr = |
350 | 0 | sys::findProgramByName("llvm-objcopy"); |
351 | 0 | if (!ObjcopyPathOrErr) { |
352 | 0 | WithColor::warning(errs(), ToolName) |
353 | 0 | << "cannot find llvm-objcopy[.exe] in PATH; ELF notes cannot be " |
354 | 0 | "added.\n"; |
355 | 0 | return; |
356 | 0 | } |
357 | | |
358 | 0 | ObjcopyPath = *ObjcopyPathOrErr; |
359 | 0 | } |
360 | | |
361 | 5 | ~BinaryWrapper() { |
362 | 5 | if (TempFiles.empty()) |
363 | 1 | return; |
364 | | |
365 | 4 | StringRef ToolNameRef(ToolName); |
366 | 4 | auto warningOS = [ToolNameRef]() -> raw_ostream & { |
367 | 0 | return WithColor::warning(errs(), ToolNameRef); |
368 | 0 | }; |
369 | | |
370 | 8 | for (auto &F : TempFiles) { |
371 | 8 | if (SaveTemps) { |
372 | 0 | warningOS() << "keeping temporary file " << F << "\n"; |
373 | 0 | continue; |
374 | 0 | } |
375 | | |
376 | 8 | auto EC = sys::fs::remove(F, false); |
377 | 8 | if (EC) |
378 | 0 | warningOS() << "cannot remove temporary file " << F << ": " |
379 | 0 | << EC.message().c_str() << "\n"; |
380 | 8 | } |
381 | 4 | } |
382 | | |
383 | 5 | const Module &wrapBinaries(ArrayRef<ArrayRef<char>> Binaries) { |
384 | 5 | GlobalVariable *Desc = createBinDesc(Binaries); |
385 | 5 | assert(Desc && "no binary descriptor"); |
386 | 0 | createRegisterFunction(Desc); |
387 | 5 | createUnregisterFunction(Desc); |
388 | 5 | return M; |
389 | 5 | } |
390 | | |
391 | | std::unique_ptr<MemoryBuffer> addELFNotes(std::unique_ptr<MemoryBuffer> Buf, |
392 | 5 | StringRef OriginalFileName) { |
393 | | // Cannot add notes, if llvm-objcopy is not available. |
394 | | // |
395 | | // I did not find a clean way to add a new notes section into an existing |
396 | | // ELF file. llvm-objcopy seems to recreate a new ELF from scratch, |
397 | | // and we just try to use llvm-objcopy here. |
398 | 5 | if (ObjcopyPath.empty()) |
399 | 0 | return Buf; |
400 | | |
401 | 5 | StringRef ToolNameRef(ToolName); |
402 | | |
403 | | // Helpers to emit warnings. |
404 | 5 | auto warningOS = [ToolNameRef]() -> raw_ostream & { |
405 | 1 | return WithColor::warning(errs(), ToolNameRef); |
406 | 1 | }; |
407 | 5 | auto handleErrorAsWarning = [&warningOS](Error E) { |
408 | 0 | logAllUnhandledErrors(std::move(E), warningOS()); |
409 | 0 | }; |
410 | | |
411 | 5 | Expected<std::unique_ptr<ObjectFile>> BinOrErr = |
412 | 5 | ObjectFile::createELFObjectFile(Buf->getMemBufferRef(), |
413 | 5 | /*InitContent=*/false); |
414 | 5 | if (Error E = BinOrErr.takeError()) { |
415 | 1 | consumeError(std::move(E)); |
416 | | // This warning is questionable, but let it be here, |
417 | | // assuming that most OpenMP offload models use ELF offload images. |
418 | 1 | warningOS() << OriginalFileName |
419 | 1 | << " is not an ELF image, so notes cannot be added to it.\n"; |
420 | 1 | return Buf; |
421 | 1 | } |
422 | | |
423 | | // If we fail to add the note section, we just pass through the original |
424 | | // ELF image for wrapping. At some point we should enforce the note section |
425 | | // and start emitting errors vs warnings. |
426 | 4 | support::endianness Endianness; |
427 | 4 | if (isa<ELF64LEObjectFile>(BinOrErr->get()) || |
428 | 4 | isa<ELF32LEObjectFile>(BinOrErr->get())3 ) { |
429 | 2 | Endianness = support::little; |
430 | 2 | } else if (isa<ELF64BEObjectFile>(BinOrErr->get()) || |
431 | 2 | isa<ELF32BEObjectFile>(BinOrErr->get())1 ) { |
432 | 2 | Endianness = support::big; |
433 | 2 | } else { |
434 | 0 | warningOS() << OriginalFileName |
435 | 0 | << " is an ELF image of unrecognized format.\n"; |
436 | 0 | return Buf; |
437 | 0 | } |
438 | | |
439 | | // Create temporary file for the data of a new SHT_NOTE section. |
440 | | // We fill it in with data and then pass to llvm-objcopy invocation |
441 | | // for reading. |
442 | 4 | Twine NotesFileModel = OriginalFileName + Twine(".elfnotes.%%%%%%%.tmp"); |
443 | 4 | Expected<sys::fs::TempFile> NotesTemp = |
444 | 4 | sys::fs::TempFile::create(NotesFileModel); |
445 | 4 | if (Error E = NotesTemp.takeError()) { |
446 | 0 | handleErrorAsWarning(createFileError(NotesFileModel, std::move(E))); |
447 | 0 | return Buf; |
448 | 0 | } |
449 | 4 | TempFiles.push_back(NotesTemp->TmpName); |
450 | | |
451 | | // Create temporary file for the updated ELF image. |
452 | | // This is an empty file that we pass to llvm-objcopy invocation |
453 | | // for writing. |
454 | 4 | Twine ELFFileModel = OriginalFileName + Twine(".elfwithnotes.%%%%%%%.tmp"); |
455 | 4 | Expected<sys::fs::TempFile> ELFTemp = |
456 | 4 | sys::fs::TempFile::create(ELFFileModel); |
457 | 4 | if (Error E = ELFTemp.takeError()) { |
458 | 0 | handleErrorAsWarning(createFileError(ELFFileModel, std::move(E))); |
459 | 0 | return Buf; |
460 | 0 | } |
461 | 4 | TempFiles.push_back(ELFTemp->TmpName); |
462 | | |
463 | | // Keep the new ELF image file to reserve the name for the future |
464 | | // llvm-objcopy invocation. |
465 | 4 | std::string ELFTmpFileName = ELFTemp->TmpName; |
466 | 4 | if (Error E = ELFTemp->keep(ELFTmpFileName)) { |
467 | 0 | handleErrorAsWarning(createFileError(ELFTmpFileName, std::move(E))); |
468 | 0 | return Buf; |
469 | 0 | } |
470 | | |
471 | | // Write notes to the *elfnotes*.tmp file. |
472 | 4 | raw_fd_ostream NotesOS(NotesTemp->FD, false); |
473 | | |
474 | 4 | struct NoteTy { |
475 | | // Note name is a null-terminated "LLVMOMPOFFLOAD". |
476 | 4 | std::string Name; |
477 | | // Note type defined in llvm/include/llvm/BinaryFormat/ELF.h. |
478 | 4 | uint32_t Type = 0; |
479 | | // Each note has type-specific associated data. |
480 | 4 | std::string Desc; |
481 | | |
482 | 4 | NoteTy(std::string &&Name, uint32_t Type, std::string &&Desc) |
483 | 12 | : Name(std::move(Name)), Type(Type), Desc(std::move(Desc)) {} |
484 | 4 | }; |
485 | | |
486 | | // So far we emit just three notes. |
487 | 4 | SmallVector<NoteTy, 3> Notes; |
488 | | // Version of the offload image identifying the structure of the ELF image. |
489 | | // Version 1.0 does not have any specific requirements. |
490 | | // We may come up with some structure that has to be honored by all |
491 | | // offload implementations in future (e.g. to let libomptarget |
492 | | // get some information from the offload image). |
493 | 4 | Notes.emplace_back("LLVMOMPOFFLOAD", ELF::NT_LLVM_OPENMP_OFFLOAD_VERSION, |
494 | 4 | OPENMP_OFFLOAD_IMAGE_VERSION); |
495 | | // This is a producer identification string. We are LLVM! |
496 | 4 | Notes.emplace_back("LLVMOMPOFFLOAD", ELF::NT_LLVM_OPENMP_OFFLOAD_PRODUCER, |
497 | 4 | "LLVM"); |
498 | | // This is a producer version. Use the same format that is used |
499 | | // by clang to report the LLVM version. |
500 | 4 | Notes.emplace_back("LLVMOMPOFFLOAD", |
501 | 4 | ELF::NT_LLVM_OPENMP_OFFLOAD_PRODUCER_VERSION, |
502 | 4 | LLVM_VERSION_STRING |
503 | 4 | #ifdef LLVM_REVISION |
504 | 4 | " " LLVM_REVISION |
505 | 4 | #endif |
506 | 4 | ); |
507 | | |
508 | | // Return the amount of padding required for a blob of N bytes |
509 | | // to be aligned to Alignment bytes. |
510 | 44 | auto getPadAmount = [](uint32_t N, uint32_t Alignment) -> uint32_t { |
511 | 44 | uint32_t Mod = (N % Alignment); |
512 | 44 | if (Mod == 0) |
513 | 4 | return 0; |
514 | 40 | return Alignment - Mod; |
515 | 44 | }; |
516 | 24 | auto emitPadding = [&getPadAmount](raw_ostream &OS, uint32_t Size) { |
517 | 44 | for (uint32_t I = 0; I < getPadAmount(Size, 4); ++I20 ) |
518 | 20 | OS << '\0'; |
519 | 24 | }; |
520 | | |
521 | | // Put notes into the file. |
522 | 12 | for (auto &N : Notes) { |
523 | 12 | assert(!N.Name.empty() && "We should not create notes with empty names."); |
524 | | // Name must be null-terminated. |
525 | 12 | if (N.Name.back() != '\0') |
526 | 12 | N.Name += '\0'; |
527 | 12 | uint32_t NameSz = N.Name.size(); |
528 | 12 | uint32_t DescSz = N.Desc.size(); |
529 | | // A note starts with three 4-byte values: |
530 | | // NameSz |
531 | | // DescSz |
532 | | // Type |
533 | | // These three fields are endian-sensitive. |
534 | 12 | support::endian::write<uint32_t>(NotesOS, NameSz, Endianness); |
535 | 12 | support::endian::write<uint32_t>(NotesOS, DescSz, Endianness); |
536 | 12 | support::endian::write<uint32_t>(NotesOS, N.Type, Endianness); |
537 | | // Next, we have a null-terminated Name padded to a 4-byte boundary. |
538 | 12 | NotesOS << N.Name; |
539 | 12 | emitPadding(NotesOS, NameSz); |
540 | 12 | if (DescSz == 0) |
541 | 0 | continue; |
542 | | // Finally, we have a descriptor, which is an arbitrary flow of bytes. |
543 | 12 | NotesOS << N.Desc; |
544 | 12 | emitPadding(NotesOS, DescSz); |
545 | 12 | } |
546 | 4 | NotesOS.flush(); |
547 | | |
548 | | // Keep the notes file. |
549 | 4 | std::string NotesTmpFileName = NotesTemp->TmpName; |
550 | 4 | if (Error E = NotesTemp->keep(NotesTmpFileName)) { |
551 | 0 | handleErrorAsWarning(createFileError(NotesTmpFileName, std::move(E))); |
552 | 0 | return Buf; |
553 | 0 | } |
554 | | |
555 | | // Run llvm-objcopy like this: |
556 | | // llvm-objcopy --add-section=.note.openmp=<notes-tmp-file-name> \ |
557 | | // <orig-file-name> <elf-tmp-file-name> |
558 | | // |
559 | | // This will add a SHT_NOTE section on top of the original ELF. |
560 | 4 | std::vector<StringRef> Args; |
561 | 4 | Args.push_back(ObjcopyPath); |
562 | 4 | std::string Option("--add-section=.note.openmp=" + NotesTmpFileName); |
563 | 4 | Args.push_back(Option); |
564 | 4 | Args.push_back(OriginalFileName); |
565 | 4 | Args.push_back(ELFTmpFileName); |
566 | 4 | bool ExecutionFailed = false; |
567 | 4 | std::string ErrMsg; |
568 | 4 | (void)sys::ExecuteAndWait(ObjcopyPath, Args, |
569 | 4 | /*Env=*/llvm::None, /*Redirects=*/{}, |
570 | 4 | /*SecondsToWait=*/0, |
571 | 4 | /*MemoryLimit=*/0, &ErrMsg, &ExecutionFailed); |
572 | | |
573 | 4 | if (ExecutionFailed) { |
574 | 0 | warningOS() << ErrMsg << "\n"; |
575 | 0 | return Buf; |
576 | 0 | } |
577 | | |
578 | | // Substitute the original ELF with new one. |
579 | 4 | ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = |
580 | 4 | MemoryBuffer::getFile(ELFTmpFileName); |
581 | 4 | if (!BufOrErr) { |
582 | 0 | handleErrorAsWarning( |
583 | 0 | createFileError(ELFTmpFileName, BufOrErr.getError())); |
584 | 0 | return Buf; |
585 | 0 | } |
586 | | |
587 | 4 | return std::move(*BufOrErr); |
588 | 4 | } |
589 | | }; |
590 | | |
591 | | } // anonymous namespace |
592 | | |
593 | 6 | int main(int argc, const char **argv) { |
594 | 6 | sys::PrintStackTraceOnErrorSignal(argv[0]); |
595 | | |
596 | 6 | cl::HideUnrelatedOptions(ClangOffloadWrapperCategory); |
597 | 6 | cl::SetVersionPrinter([](raw_ostream &OS) { |
598 | 0 | OS << clang::getClangToolFullVersion("clang-offload-wrapper") << '\n'; |
599 | 0 | }); |
600 | 6 | cl::ParseCommandLineOptions( |
601 | 6 | argc, argv, |
602 | 6 | "A tool to create a wrapper bitcode for offload target binaries. Takes " |
603 | 6 | "offload\ntarget binaries as input and produces bitcode file containing " |
604 | 6 | "target binaries packaged\nas data and initialization code which " |
605 | 6 | "registers target binaries in offload runtime.\n"); |
606 | | |
607 | 6 | if (Help) { |
608 | 0 | cl::PrintHelpMessage(); |
609 | 0 | return 0; |
610 | 0 | } |
611 | | |
612 | 6 | auto reportError = [argv](Error E) { |
613 | 0 | logAllUnhandledErrors(std::move(E), WithColor::error(errs(), argv[0])); |
614 | 0 | }; |
615 | | |
616 | 6 | if (Triple(Target).getArch() == Triple::UnknownArch) { |
617 | 0 | reportError(createStringError( |
618 | 0 | errc::invalid_argument, "'" + Target + "': unsupported target triple")); |
619 | 0 | return 1; |
620 | 0 | } |
621 | | |
622 | 6 | BinaryWrapper Wrapper(Target, argv[0]); |
623 | | |
624 | | // Read device binaries. |
625 | 6 | SmallVector<std::unique_ptr<MemoryBuffer>, 4u> Buffers; |
626 | 6 | SmallVector<ArrayRef<char>, 4u> Images; |
627 | 6 | Buffers.reserve(Inputs.size()); |
628 | 6 | Images.reserve(Inputs.size()); |
629 | 6 | for (const std::string &File : Inputs) { |
630 | 5 | ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = |
631 | 5 | MemoryBuffer::getFileOrSTDIN(File); |
632 | 5 | if (!BufOrErr) { |
633 | 0 | reportError(createFileError(File, BufOrErr.getError())); |
634 | 0 | return 1; |
635 | 0 | } |
636 | 5 | std::unique_ptr<MemoryBuffer> Buffer(std::move(*BufOrErr)); |
637 | 5 | if (File != "-" && AddOpenMPOffloadNotes) { |
638 | | // Adding ELF notes for STDIN is not supported yet. |
639 | 5 | Buffer = Wrapper.addELFNotes(std::move(Buffer), File); |
640 | 5 | } |
641 | 5 | const std::unique_ptr<MemoryBuffer> &Buf = |
642 | 5 | Buffers.emplace_back(std::move(Buffer)); |
643 | 5 | Images.emplace_back(Buf->getBufferStart(), Buf->getBufferSize()); |
644 | 5 | } |
645 | | |
646 | | // Create the output file to write the resulting bitcode to. |
647 | 6 | std::error_code EC; |
648 | 6 | ToolOutputFile Out(Output, EC, sys::fs::OF_None); |
649 | 6 | if (EC) { |
650 | 0 | reportError(createFileError(Output, EC)); |
651 | 0 | return 1; |
652 | 0 | } |
653 | | |
654 | | // Create a wrapper for device binaries and write its bitcode to the file. |
655 | 6 | WriteBitcodeToFile( |
656 | 6 | Wrapper.wrapBinaries(makeArrayRef(Images.data(), Images.size())), |
657 | 6 | Out.os()); |
658 | 6 | if (Out.os().has_error()) { |
659 | 0 | reportError(createFileError(Output, Out.os().error())); |
660 | 0 | return 1; |
661 | 0 | } |
662 | | |
663 | | // Success. |
664 | 6 | Out.keep(); |
665 | 6 | return 0; |
666 | 6 | } |