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