OffloadBinary.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. //===- Offloading.cpp - Utilities for handling offloading code -*- 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. #include "llvm/Object/OffloadBinary.h"
  9. #include "llvm/ADT/StringSwitch.h"
  10. #include "llvm/BinaryFormat/Magic.h"
  11. #include "llvm/IR/Constants.h"
  12. #include "llvm/IR/Module.h"
  13. #include "llvm/IRReader/IRReader.h"
  14. #include "llvm/MC/StringTableBuilder.h"
  15. #include "llvm/Object/Archive.h"
  16. #include "llvm/Object/ArchiveWriter.h"
  17. #include "llvm/Object/Binary.h"
  18. #include "llvm/Object/COFF.h"
  19. #include "llvm/Object/ELFObjectFile.h"
  20. #include "llvm/Object/Error.h"
  21. #include "llvm/Object/IRObjectFile.h"
  22. #include "llvm/Object/ObjectFile.h"
  23. #include "llvm/Support/Alignment.h"
  24. #include "llvm/Support/FileOutputBuffer.h"
  25. #include "llvm/Support/SourceMgr.h"
  26. using namespace llvm;
  27. using namespace llvm::object;
  28. namespace {
  29. /// Attempts to extract all the embedded device images contained inside the
  30. /// buffer \p Contents. The buffer is expected to contain a valid offloading
  31. /// binary format.
  32. Error extractOffloadFiles(MemoryBufferRef Contents,
  33. SmallVectorImpl<OffloadFile> &Binaries) {
  34. uint64_t Offset = 0;
  35. // There could be multiple offloading binaries stored at this section.
  36. while (Offset < Contents.getBuffer().size()) {
  37. std::unique_ptr<MemoryBuffer> Buffer =
  38. MemoryBuffer::getMemBuffer(Contents.getBuffer().drop_front(Offset), "",
  39. /*RequiresNullTerminator*/ false);
  40. if (!isAddrAligned(Align(OffloadBinary::getAlignment()),
  41. Buffer->getBufferStart()))
  42. Buffer = MemoryBuffer::getMemBufferCopy(Buffer->getBuffer(),
  43. Buffer->getBufferIdentifier());
  44. auto BinaryOrErr = OffloadBinary::create(*Buffer);
  45. if (!BinaryOrErr)
  46. return BinaryOrErr.takeError();
  47. OffloadBinary &Binary = **BinaryOrErr;
  48. // Create a new owned binary with a copy of the original memory.
  49. std::unique_ptr<MemoryBuffer> BufferCopy = MemoryBuffer::getMemBufferCopy(
  50. Binary.getData().take_front(Binary.getSize()),
  51. Contents.getBufferIdentifier());
  52. auto NewBinaryOrErr = OffloadBinary::create(*BufferCopy);
  53. if (!NewBinaryOrErr)
  54. return NewBinaryOrErr.takeError();
  55. Binaries.emplace_back(std::move(*NewBinaryOrErr), std::move(BufferCopy));
  56. Offset += Binary.getSize();
  57. }
  58. return Error::success();
  59. }
  60. // Extract offloading binaries from an Object file \p Obj.
  61. Error extractFromObject(const ObjectFile &Obj,
  62. SmallVectorImpl<OffloadFile> &Binaries) {
  63. assert((Obj.isELF() || Obj.isCOFF()) && "Invalid file type");
  64. for (SectionRef Sec : Obj.sections()) {
  65. // ELF files contain a section with the LLVM_OFFLOADING type.
  66. if (Obj.isELF() &&
  67. static_cast<ELFSectionRef>(Sec).getType() != ELF::SHT_LLVM_OFFLOADING)
  68. continue;
  69. // COFF has no section types so we rely on the name of the section.
  70. if (Obj.isCOFF()) {
  71. Expected<StringRef> NameOrErr = Sec.getName();
  72. if (!NameOrErr)
  73. return NameOrErr.takeError();
  74. if (!NameOrErr->equals(".llvm.offloading"))
  75. continue;
  76. }
  77. Expected<StringRef> Buffer = Sec.getContents();
  78. if (!Buffer)
  79. return Buffer.takeError();
  80. MemoryBufferRef Contents(*Buffer, Obj.getFileName());
  81. if (Error Err = extractOffloadFiles(Contents, Binaries))
  82. return Err;
  83. }
  84. return Error::success();
  85. }
  86. Error extractFromBitcode(MemoryBufferRef Buffer,
  87. SmallVectorImpl<OffloadFile> &Binaries) {
  88. LLVMContext Context;
  89. SMDiagnostic Err;
  90. std::unique_ptr<Module> M = getLazyIRModule(
  91. MemoryBuffer::getMemBuffer(Buffer, /*RequiresNullTerminator=*/false), Err,
  92. Context);
  93. if (!M)
  94. return createStringError(inconvertibleErrorCode(),
  95. "Failed to create module");
  96. // Extract offloading data from globals referenced by the
  97. // `llvm.embedded.object` metadata with the `.llvm.offloading` section.
  98. auto *MD = M->getNamedMetadata("llvm.embedded.objects");
  99. if (!MD)
  100. return Error::success();
  101. for (const MDNode *Op : MD->operands()) {
  102. if (Op->getNumOperands() < 2)
  103. continue;
  104. MDString *SectionID = dyn_cast<MDString>(Op->getOperand(1));
  105. if (!SectionID || SectionID->getString() != ".llvm.offloading")
  106. continue;
  107. GlobalVariable *GV =
  108. mdconst::dyn_extract_or_null<GlobalVariable>(Op->getOperand(0));
  109. if (!GV)
  110. continue;
  111. auto *CDS = dyn_cast<ConstantDataSequential>(GV->getInitializer());
  112. if (!CDS)
  113. continue;
  114. MemoryBufferRef Contents(CDS->getAsString(), M->getName());
  115. if (Error Err = extractOffloadFiles(Contents, Binaries))
  116. return Err;
  117. }
  118. return Error::success();
  119. }
  120. Error extractFromArchive(const Archive &Library,
  121. SmallVectorImpl<OffloadFile> &Binaries) {
  122. // Try to extract device code from each file stored in the static archive.
  123. Error Err = Error::success();
  124. for (auto Child : Library.children(Err)) {
  125. auto ChildBufferOrErr = Child.getMemoryBufferRef();
  126. if (!ChildBufferOrErr)
  127. return ChildBufferOrErr.takeError();
  128. std::unique_ptr<MemoryBuffer> ChildBuffer =
  129. MemoryBuffer::getMemBuffer(*ChildBufferOrErr, false);
  130. // Check if the buffer has the required alignment.
  131. if (!isAddrAligned(Align(OffloadBinary::getAlignment()),
  132. ChildBuffer->getBufferStart()))
  133. ChildBuffer = MemoryBuffer::getMemBufferCopy(
  134. ChildBufferOrErr->getBuffer(),
  135. ChildBufferOrErr->getBufferIdentifier());
  136. if (Error Err = extractOffloadBinaries(*ChildBuffer, Binaries))
  137. return Err;
  138. }
  139. if (Err)
  140. return Err;
  141. return Error::success();
  142. }
  143. } // namespace
  144. Expected<std::unique_ptr<OffloadBinary>>
  145. OffloadBinary::create(MemoryBufferRef Buf) {
  146. if (Buf.getBufferSize() < sizeof(Header) + sizeof(Entry))
  147. return errorCodeToError(object_error::parse_failed);
  148. // Check for 0x10FF1OAD magic bytes.
  149. if (identify_magic(Buf.getBuffer()) != file_magic::offload_binary)
  150. return errorCodeToError(object_error::parse_failed);
  151. // Make sure that the data has sufficient alignment.
  152. if (!isAddrAligned(Align(getAlignment()), Buf.getBufferStart()))
  153. return errorCodeToError(object_error::parse_failed);
  154. const char *Start = Buf.getBufferStart();
  155. const Header *TheHeader = reinterpret_cast<const Header *>(Start);
  156. if (TheHeader->Version != OffloadBinary::Version)
  157. return errorCodeToError(object_error::parse_failed);
  158. if (TheHeader->Size > Buf.getBufferSize() ||
  159. TheHeader->EntryOffset > TheHeader->Size - sizeof(Entry) ||
  160. TheHeader->EntrySize > TheHeader->Size - sizeof(Header))
  161. return errorCodeToError(object_error::unexpected_eof);
  162. const Entry *TheEntry =
  163. reinterpret_cast<const Entry *>(&Start[TheHeader->EntryOffset]);
  164. if (TheEntry->ImageOffset > Buf.getBufferSize() ||
  165. TheEntry->StringOffset > Buf.getBufferSize())
  166. return errorCodeToError(object_error::unexpected_eof);
  167. return std::unique_ptr<OffloadBinary>(
  168. new OffloadBinary(Buf, TheHeader, TheEntry));
  169. }
  170. std::unique_ptr<MemoryBuffer>
  171. OffloadBinary::write(const OffloadingImage &OffloadingData) {
  172. // Create a null-terminated string table with all the used strings.
  173. StringTableBuilder StrTab(StringTableBuilder::ELF);
  174. for (auto &KeyAndValue : OffloadingData.StringData) {
  175. StrTab.add(KeyAndValue.getKey());
  176. StrTab.add(KeyAndValue.getValue());
  177. }
  178. StrTab.finalize();
  179. uint64_t StringEntrySize =
  180. sizeof(StringEntry) * OffloadingData.StringData.size();
  181. // Make sure the image we're wrapping around is aligned as well.
  182. uint64_t BinaryDataSize = alignTo(sizeof(Header) + sizeof(Entry) +
  183. StringEntrySize + StrTab.getSize(),
  184. getAlignment());
  185. // Create the header and fill in the offsets. The entry will be directly
  186. // placed after the header in memory. Align the size to the alignment of the
  187. // header so this can be placed contiguously in a single section.
  188. Header TheHeader;
  189. TheHeader.Size = alignTo(
  190. BinaryDataSize + OffloadingData.Image->getBufferSize(), getAlignment());
  191. TheHeader.EntryOffset = sizeof(Header);
  192. TheHeader.EntrySize = sizeof(Entry);
  193. // Create the entry using the string table offsets. The string table will be
  194. // placed directly after the entry in memory, and the image after that.
  195. Entry TheEntry;
  196. TheEntry.TheImageKind = OffloadingData.TheImageKind;
  197. TheEntry.TheOffloadKind = OffloadingData.TheOffloadKind;
  198. TheEntry.Flags = OffloadingData.Flags;
  199. TheEntry.StringOffset = sizeof(Header) + sizeof(Entry);
  200. TheEntry.NumStrings = OffloadingData.StringData.size();
  201. TheEntry.ImageOffset = BinaryDataSize;
  202. TheEntry.ImageSize = OffloadingData.Image->getBufferSize();
  203. SmallVector<char> Data;
  204. Data.reserve(TheHeader.Size);
  205. raw_svector_ostream OS(Data);
  206. OS << StringRef(reinterpret_cast<char *>(&TheHeader), sizeof(Header));
  207. OS << StringRef(reinterpret_cast<char *>(&TheEntry), sizeof(Entry));
  208. for (auto &KeyAndValue : OffloadingData.StringData) {
  209. uint64_t Offset = sizeof(Header) + sizeof(Entry) + StringEntrySize;
  210. StringEntry Map{Offset + StrTab.getOffset(KeyAndValue.getKey()),
  211. Offset + StrTab.getOffset(KeyAndValue.getValue())};
  212. OS << StringRef(reinterpret_cast<char *>(&Map), sizeof(StringEntry));
  213. }
  214. StrTab.write(OS);
  215. // Add padding to required image alignment.
  216. OS.write_zeros(TheEntry.ImageOffset - OS.tell());
  217. OS << OffloadingData.Image->getBuffer();
  218. // Add final padding to required alignment.
  219. assert(TheHeader.Size >= OS.tell() && "Too much data written?");
  220. OS.write_zeros(TheHeader.Size - OS.tell());
  221. assert(TheHeader.Size == OS.tell() && "Size mismatch");
  222. return MemoryBuffer::getMemBufferCopy(OS.str());
  223. }
  224. Error object::extractOffloadBinaries(MemoryBufferRef Buffer,
  225. SmallVectorImpl<OffloadFile> &Binaries) {
  226. file_magic Type = identify_magic(Buffer.getBuffer());
  227. switch (Type) {
  228. case file_magic::bitcode:
  229. return extractFromBitcode(Buffer, Binaries);
  230. case file_magic::elf_relocatable:
  231. case file_magic::elf_executable:
  232. case file_magic::elf_shared_object:
  233. case file_magic::coff_object: {
  234. Expected<std::unique_ptr<ObjectFile>> ObjFile =
  235. ObjectFile::createObjectFile(Buffer, Type);
  236. if (!ObjFile)
  237. return ObjFile.takeError();
  238. return extractFromObject(*ObjFile->get(), Binaries);
  239. }
  240. case file_magic::archive: {
  241. Expected<std::unique_ptr<llvm::object::Archive>> LibFile =
  242. object::Archive::create(Buffer);
  243. if (!LibFile)
  244. return LibFile.takeError();
  245. return extractFromArchive(*LibFile->get(), Binaries);
  246. }
  247. case file_magic::offload_binary:
  248. return extractOffloadFiles(Buffer, Binaries);
  249. default:
  250. return Error::success();
  251. }
  252. }
  253. OffloadKind object::getOffloadKind(StringRef Name) {
  254. return llvm::StringSwitch<OffloadKind>(Name)
  255. .Case("openmp", OFK_OpenMP)
  256. .Case("cuda", OFK_Cuda)
  257. .Case("hip", OFK_HIP)
  258. .Default(OFK_None);
  259. }
  260. StringRef object::getOffloadKindName(OffloadKind Kind) {
  261. switch (Kind) {
  262. case OFK_OpenMP:
  263. return "openmp";
  264. case OFK_Cuda:
  265. return "cuda";
  266. case OFK_HIP:
  267. return "hip";
  268. default:
  269. return "none";
  270. }
  271. }
  272. ImageKind object::getImageKind(StringRef Name) {
  273. return llvm::StringSwitch<ImageKind>(Name)
  274. .Case("o", IMG_Object)
  275. .Case("bc", IMG_Bitcode)
  276. .Case("cubin", IMG_Cubin)
  277. .Case("fatbin", IMG_Fatbinary)
  278. .Case("s", IMG_PTX)
  279. .Default(IMG_None);
  280. }
  281. StringRef object::getImageKindName(ImageKind Kind) {
  282. switch (Kind) {
  283. case IMG_Object:
  284. return "o";
  285. case IMG_Bitcode:
  286. return "bc";
  287. case IMG_Cubin:
  288. return "cubin";
  289. case IMG_Fatbinary:
  290. return "fatbin";
  291. case IMG_PTX:
  292. return "s";
  293. default:
  294. return "";
  295. }
  296. }