1//===- OffloadBinary.cpp - Utilities for handling offloading code ---------===//
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/ArrayRef.h"
12#include "llvm/ADT/StringExtras.h"
13#include "llvm/ADT/StringSwitch.h"
14#include "llvm/BinaryFormat/Magic.h"
15#include "llvm/IR/Constants.h"
16#include "llvm/IR/Module.h"
17#include "llvm/IRReader/IRReader.h"
18#include "llvm/MC/StringTableBuilder.h"
19#include "llvm/Object/Archive.h"
20#include "llvm/Object/Binary.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/Compression.h"
27#include "llvm/Support/Error.h"
28#include "llvm/Support/SourceMgr.h"
29#include "llvm/Support/raw_ostream.h"
30#include "llvm/TargetParser/AMDGPUTargetParser.h"
31
32#include <cassert>
33#include <cstddef>
34#include <memory>
35
36using namespace llvm;
37using namespace llvm::object;
38
39namespace {
40
41/// Attempts to extract all the embedded device images contained inside the
42/// buffer \p Contents. The buffer is expected to contain a valid offloading
43/// binary format.
44Error extractOffloadFiles(MemoryBufferRef Contents,
45 SmallVectorImpl<OffloadFile> &Binaries) {
46 uint64_t Offset = 0;
47 // There could be multiple offloading binaries stored at this section.
48 while (Offset < Contents.getBufferSize()) {
49 std::unique_ptr<MemoryBuffer> Buffer =
50 MemoryBuffer::getMemBuffer(InputData: Contents.getBuffer().drop_front(N: Offset), BufferName: "",
51 /*RequiresNullTerminator*/ false);
52 if (!isAddrAligned(Lhs: Align(OffloadBinary::getAlignment()),
53 Addr: Buffer->getBufferStart()))
54 Buffer = MemoryBuffer::getMemBufferCopy(InputData: Buffer->getBuffer(),
55 BufferName: Buffer->getBufferIdentifier());
56
57 auto HeaderOrErr = OffloadBinary::extractHeader(Buf: *Buffer);
58 if (!HeaderOrErr)
59 return HeaderOrErr.takeError();
60 const OffloadBinary::Header *Header = *HeaderOrErr;
61
62 MemoryBufferRef Slice(Buffer->getBuffer().take_front(N: Header->Size),
63 Contents.getBufferIdentifier());
64 auto BinariesOrErr = OffloadBinary::create(Buf: Slice);
65 if (!BinariesOrErr)
66 return BinariesOrErr.takeError();
67
68 for (auto &Binary : *BinariesOrErr)
69 Binaries.emplace_back(Args: std::move(Binary));
70
71 Offset = alignTo(Value: Offset + Header->Size, Align: OffloadBinary::getAlignment());
72 }
73
74 return Error::success();
75}
76
77// Extract offloading binaries from an Object file \p Obj.
78Error extractFromObject(const ObjectFile &Obj,
79 SmallVectorImpl<OffloadFile> &Binaries) {
80 assert((Obj.isELF() || Obj.isCOFF()) && "Invalid file type");
81
82 for (SectionRef Sec : Obj.sections()) {
83 // ELF files contain a section with the LLVM_OFFLOADING type.
84 if (Obj.isELF() &&
85 static_cast<ELFSectionRef>(Sec).getType() != ELF::SHT_LLVM_OFFLOADING)
86 continue;
87
88 // COFF has no section types so we rely on the name of the section.
89 if (Obj.isCOFF()) {
90 Expected<StringRef> NameOrErr = Sec.getName();
91 if (!NameOrErr)
92 return NameOrErr.takeError();
93
94 if (!NameOrErr->starts_with(Prefix: ".llvm.offloading"))
95 continue;
96 }
97
98 Expected<StringRef> Buffer = Sec.getContents();
99 if (!Buffer)
100 return Buffer.takeError();
101
102 MemoryBufferRef Contents(*Buffer, Obj.getFileName());
103 if (Error Err = extractOffloadFiles(Contents, Binaries))
104 return Err;
105 }
106
107 return Error::success();
108}
109
110Error extractFromBitcode(MemoryBufferRef Buffer,
111 SmallVectorImpl<OffloadFile> &Binaries) {
112 LLVMContext Context;
113 SMDiagnostic Err;
114 std::unique_ptr<Module> M = getLazyIRModule(
115 Buffer: MemoryBuffer::getMemBuffer(Ref: Buffer, /*RequiresNullTerminator=*/false), Err,
116 Context);
117 if (!M)
118 return createStringError(EC: inconvertibleErrorCode(),
119 S: "Failed to create module");
120
121 // Extract offloading data from globals referenced by the
122 // `llvm.embedded.object` metadata with the `.llvm.offloading` section.
123 auto *MD = M->getNamedMetadata(Name: "llvm.embedded.objects");
124 if (!MD)
125 return Error::success();
126
127 for (const MDNode *Op : MD->operands()) {
128 if (Op->getNumOperands() < 2)
129 continue;
130
131 MDString *SectionID = dyn_cast<MDString>(Val: Op->getOperand(I: 1));
132 if (!SectionID || SectionID->getString() != ".llvm.offloading")
133 continue;
134
135 GlobalVariable *GV =
136 mdconst::dyn_extract_or_null<GlobalVariable>(MD: Op->getOperand(I: 0));
137 if (!GV)
138 continue;
139
140 auto *CDS = dyn_cast<ConstantDataSequential>(Val: GV->getInitializer());
141 if (!CDS)
142 continue;
143
144 MemoryBufferRef Contents(CDS->getAsString(), M->getName());
145 if (Error Err = extractOffloadFiles(Contents, Binaries))
146 return Err;
147 }
148
149 return Error::success();
150}
151
152Error extractFromArchive(const Archive &Library,
153 SmallVectorImpl<OffloadFile> &Binaries) {
154 // Try to extract device code from each file stored in the static archive.
155 Error Err = Error::success();
156 for (auto Child : Library.children(Err)) {
157 auto ChildBufferOrErr = Child.getMemoryBufferRef();
158 if (!ChildBufferOrErr)
159 return ChildBufferOrErr.takeError();
160 std::unique_ptr<MemoryBuffer> ChildBuffer =
161 MemoryBuffer::getMemBuffer(Ref: *ChildBufferOrErr, RequiresNullTerminator: false);
162
163 // Check if the buffer has the required alignment.
164 if (!isAddrAligned(Lhs: Align(OffloadBinary::getAlignment()),
165 Addr: ChildBuffer->getBufferStart()))
166 ChildBuffer = MemoryBuffer::getMemBufferCopy(
167 InputData: ChildBufferOrErr->getBuffer(),
168 BufferName: ChildBufferOrErr->getBufferIdentifier());
169
170 if (Error Err = extractOffloadBinaries(Buffer: *ChildBuffer, Binaries))
171 return Err;
172 }
173
174 if (Err)
175 return Err;
176 return Error::success();
177}
178
179bool isCompressed(const OffloadBinary::Header &Header) {
180 return Header.Version >= 3 && Header.InflatedSize != 0;
181}
182
183Expected<std::unique_ptr<MemoryBuffer>>
184decompressOffloadBinary(MemoryBufferRef Buf) {
185 const auto *Header =
186 reinterpret_cast<const OffloadBinary::Header *>(Buf.getBufferStart());
187 if (Header->EntriesOffset != sizeof(OffloadBinary::Header) ||
188 Header->EntriesOffset > Header->Size ||
189 Header->InflatedSize < Header->EntriesOffset)
190 return errorCodeToError(EC: object_error::unexpected_eof);
191
192 // Get the compressed binary blob after the header.
193 StringRef Compressed = Buf.getBuffer()
194 .take_front(N: Header->Size)
195 .drop_front(N: Header->EntriesOffset);
196 uint64_t BodySize = Header->InflatedSize - Header->EntriesOffset;
197
198 SmallVector<uint8_t, 0> Body;
199 if (Error Err = compression::decompress(Input: arrayRefFromStringRef(Input: Compressed),
200 Output&: Body, UncompressedSize: BodySize))
201 return std::move(Err);
202
203 // Restore the old header data for the newly uncompressed blob.
204 OffloadBinary::Header Restored = *Header;
205 Restored.Size = Restored.InflatedSize;
206 Restored.InflatedSize = 0;
207 if (Restored.EntriesOffset + Body.size() != Restored.Size)
208 return errorCodeToError(EC: object_error::parse_failed);
209
210 SmallString<0> Out;
211 Out.reserve(N: Restored.Size);
212 Out.append(RHS: StringRef(reinterpret_cast<const char *>(&Restored),
213 sizeof(OffloadBinary::Header)));
214 Out.append(RHS: toStringRef(Input: Body));
215 return MemoryBuffer::getMemBufferCopy(InputData: Out, BufferName: Buf.getBufferIdentifier());
216}
217
218} // namespace
219
220Expected<const OffloadBinary::Header *>
221OffloadBinary::extractHeader(MemoryBufferRef Buf) {
222 if (Buf.getBufferSize() < sizeof(Header))
223 return errorCodeToError(EC: object_error::parse_failed);
224
225 // Check for 0x10FF1OAD magic bytes.
226 if (identify_magic(magic: Buf.getBuffer()) != file_magic::offload_binary)
227 return errorCodeToError(EC: object_error::parse_failed);
228
229 // Make sure that the data has sufficient alignment.
230 if (!isAddrAligned(Lhs: Align(getAlignment()), Addr: Buf.getBufferStart()))
231 return errorCodeToError(EC: object_error::parse_failed);
232
233 const char *Start = Buf.getBufferStart();
234 const Header *TheHeader = reinterpret_cast<const Header *>(Start);
235 if (TheHeader->Version == 0 || TheHeader->Version > OffloadBinary::Version)
236 return errorCodeToError(EC: object_error::parse_failed);
237
238 if (TheHeader->Size > Buf.getBufferSize() || TheHeader->Size < sizeof(Header))
239 return errorCodeToError(EC: object_error::unexpected_eof);
240
241 if (isCompressed(Header: *TheHeader))
242 return TheHeader;
243
244 if (TheHeader->Size < sizeof(Entry))
245 return errorCodeToError(EC: object_error::unexpected_eof);
246
247 uint64_t EntriesCount =
248 (TheHeader->Version == 1) ? 1 : TheHeader->EntriesCount;
249 uint64_t EntriesSize = sizeof(Entry) * EntriesCount;
250 if (TheHeader->EntriesOffset > TheHeader->Size - EntriesSize ||
251 // v1/v2 headers are 32 bytes; sizeof(Header) grew in v3.
252 EntriesSize > TheHeader->Size - offsetof(Header, InflatedSize))
253 return errorCodeToError(EC: object_error::unexpected_eof);
254
255 return TheHeader;
256}
257
258Expected<SmallVector<std::unique_ptr<OffloadBinary>>>
259OffloadBinary::create(MemoryBufferRef Buf, std::optional<uint64_t> Index) {
260 auto HeaderOrErr = extractHeader(Buf);
261 if (!HeaderOrErr)
262 return HeaderOrErr.takeError();
263 const Header *OnDisk = *HeaderOrErr;
264
265 // The binary data may be a compressed image.
266 std::shared_ptr<MemoryBuffer> Binary;
267 if (isCompressed(Header: *OnDisk)) {
268 auto DecompressedOrErr = decompressOffloadBinary(Buf);
269 if (!DecompressedOrErr)
270 return DecompressedOrErr.takeError();
271 Binary = std::shared_ptr<MemoryBuffer>(std::move(*DecompressedOrErr));
272 } else {
273 Binary = std::shared_ptr<MemoryBuffer>(MemoryBuffer::getMemBufferCopy(
274 InputData: Buf.getBuffer().take_front(N: OnDisk->Size), BufferName: Buf.getBufferIdentifier()));
275 }
276
277 // Owned is now an uncompressed OffloadBinary, parse it as before.
278 MemoryBufferRef Owned = *Binary;
279 HeaderOrErr = extractHeader(Buf: Owned);
280 if (!HeaderOrErr)
281 return HeaderOrErr.takeError();
282 const Header *TheHeader = *HeaderOrErr;
283
284 const char *Start = Owned.getBufferStart();
285 const Entry *Entries =
286 reinterpret_cast<const Entry *>(&Start[TheHeader->EntriesOffset]);
287
288 auto validateEntry = [&](const Entry *TheEntry) -> Error {
289 const uint64_t BufSize = Owned.getBufferSize();
290 if (TheEntry->ImageOffset > BufSize ||
291 TheEntry->ImageSize > BufSize - TheEntry->ImageOffset)
292 return errorCodeToError(EC: object_error::unexpected_eof);
293
294 const size_t StringEntrySize =
295 TheHeader->Version == 1 ? sizeof(StringEntryV1) : sizeof(StringEntry);
296 if (TheEntry->StringOffset > BufSize ||
297 TheEntry->NumStrings >
298 (BufSize - TheEntry->StringOffset) / StringEntrySize)
299 return errorCodeToError(EC: object_error::unexpected_eof);
300 return Error::success();
301 };
302
303 SmallVector<std::unique_ptr<OffloadBinary>> Binaries;
304 if (TheHeader->Version > 1 && Index.has_value()) {
305 if (*Index >= TheHeader->EntriesCount)
306 return errorCodeToError(EC: object_error::parse_failed);
307 const Entry *TheEntry = &Entries[*Index];
308 if (auto Err = validateEntry(TheEntry))
309 return std::move(Err);
310
311 Binaries.emplace_back(
312 Args: new OffloadBinary(Binary, TheHeader, TheEntry, *Index));
313 return std::move(Binaries);
314 }
315
316 uint64_t EntriesCount = TheHeader->Version == 1 ? 1 : TheHeader->EntriesCount;
317 for (uint64_t I = 0; I < EntriesCount; ++I) {
318 const Entry *TheEntry = &Entries[I];
319 if (auto Err = validateEntry(TheEntry))
320 return std::move(Err);
321
322 Binaries.emplace_back(Args: new OffloadBinary(Binary, TheHeader, TheEntry, I));
323 }
324
325 return std::move(Binaries);
326}
327
328SmallString<0> OffloadBinary::write(ArrayRef<OffloadingImage> OffloadingData) {
329 uint64_t EntriesCount = OffloadingData.size();
330 assert(EntriesCount > 0 && "At least one offloading image is required");
331
332 // Create a null-terminated string table with all the used strings.
333 // Also calculate total size of images.
334 StringTableBuilder StrTab(StringTableBuilder::ELF);
335 uint64_t TotalStringEntries = 0;
336 uint64_t TotalImagesSize = 0;
337 for (const OffloadingImage &Img : OffloadingData) {
338 for (auto &KeyAndValue : Img.StringData) {
339 StrTab.add(S: KeyAndValue.first);
340 StrTab.add(S: KeyAndValue.second);
341 }
342 TotalStringEntries += Img.StringData.size();
343 TotalImagesSize += Img.Image->getBufferSize();
344 }
345 StrTab.finalize();
346
347 uint64_t StringEntrySize = sizeof(StringEntry) * TotalStringEntries;
348 uint64_t EntriesSize = sizeof(Entry) * EntriesCount;
349 uint64_t StrTabOffset = sizeof(Header) + EntriesSize + StringEntrySize;
350
351 // Make sure the image we're wrapping around is aligned as well.
352 uint64_t BinaryDataSize =
353 alignTo(Value: StrTabOffset + StrTab.getSize(), Align: getAlignment());
354
355 // Create the header and fill in the offsets. The entries will be directly
356 // placed after the header in memory. Align the size to the alignment of the
357 // header so this can be placed contiguously in a single section.
358 Header TheHeader{};
359 TheHeader.Size = alignTo(Value: BinaryDataSize + TotalImagesSize, Align: getAlignment());
360 TheHeader.EntriesOffset = sizeof(Header);
361 TheHeader.EntriesCount = EntriesCount;
362
363 SmallString<0> Data;
364 Data.reserve(N: TheHeader.Size);
365 raw_svector_ostream OS(Data);
366 OS << StringRef(reinterpret_cast<char *>(&TheHeader), sizeof(Header));
367
368 // Create the entries using the string table offsets. The string table will be
369 // placed directly after the set of entries in memory, and all the images are
370 // after that.
371 uint64_t StringEntryOffset = sizeof(Header) + EntriesSize;
372 uint64_t ImageOffset = BinaryDataSize;
373 for (const OffloadingImage &Img : OffloadingData) {
374 Entry TheEntry;
375
376 TheEntry.TheImageKind = Img.TheImageKind;
377 TheEntry.TheOffloadKind = Img.TheOffloadKind;
378 TheEntry.Flags = Img.Flags;
379
380 TheEntry.StringOffset = StringEntryOffset;
381 StringEntryOffset += sizeof(StringEntry) * Img.StringData.size();
382 TheEntry.NumStrings = Img.StringData.size();
383
384 TheEntry.ImageOffset = ImageOffset;
385 ImageOffset += Img.Image->getBufferSize();
386 TheEntry.ImageSize = Img.Image->getBufferSize();
387
388 OS << StringRef(reinterpret_cast<char *>(&TheEntry), sizeof(Entry));
389 }
390
391 // Create the string map entries.
392 for (const OffloadingImage &Img : OffloadingData) {
393 for (auto &KeyAndValue : Img.StringData) {
394 StringEntry Map{.KeyOffset: StrTabOffset + StrTab.getOffset(S: KeyAndValue.first),
395 .ValueOffset: StrTabOffset + StrTab.getOffset(S: KeyAndValue.second),
396 .ValueSize: KeyAndValue.second.size()};
397 OS << StringRef(reinterpret_cast<char *>(&Map), sizeof(StringEntry));
398 }
399 }
400
401 StrTab.write(OS);
402 // Add padding to required image alignment.
403 OS.write_zeros(NumZeros: BinaryDataSize - OS.tell());
404
405 for (const OffloadingImage &Img : OffloadingData)
406 OS << Img.Image->getBuffer();
407
408 // Add final padding to required alignment.
409 assert(TheHeader.Size >= OS.tell() && "Too much data written?");
410 OS.write_zeros(NumZeros: TheHeader.Size - OS.tell());
411 assert(TheHeader.Size == OS.tell() && "Size mismatch");
412
413 return Data;
414}
415
416Expected<SmallString<0>>
417OffloadBinary::write(ArrayRef<OffloadingImage> OffloadingData,
418 compression::Params Compress) {
419 if (const char *Reason = compression::getReasonIfUnsupported(F: Compress.format))
420 return createStringError(Fmt: Reason);
421
422 // Write the complete offloading binary as normal.
423 SmallString<0> Uncompressed = write(OffloadingData);
424 OffloadBinary::Header Header =
425 *reinterpret_cast<const OffloadBinary::Header *>(Uncompressed.data());
426
427 // Compress the entries after the header with the requested configuration.
428 StringRef Body = StringRef(Uncompressed).drop_front(N: Header.EntriesOffset);
429 SmallVector<uint8_t, 0> CompressedBuffer;
430 compression::compress(P: Compress, Input: arrayRefFromStringRef(Input: Body),
431 Output&: CompressedBuffer);
432
433 // Reset the header sizes and create the newly compressed binary.
434 Header.InflatedSize = Uncompressed.size();
435 Header.Size = Header.EntriesOffset + CompressedBuffer.size();
436
437 SmallString<0> Data;
438 Data.reserve(N: alignTo(Value: Header.Size, Align: getAlignment()));
439 raw_svector_ostream OS(Data);
440 OS << StringRef(reinterpret_cast<const char *>(&Header),
441 Header.EntriesOffset);
442 OS << toStringRef(Input: CompressedBuffer);
443 assert(Header.Size == OS.tell() && "Size mismatch");
444 OS.write_zeros(NumZeros: alignTo(Value: Header.Size, Align: getAlignment()) - Header.Size);
445 return Data;
446}
447
448Error object::extractOffloadBinaries(MemoryBufferRef Buffer,
449 SmallVectorImpl<OffloadFile> &Binaries) {
450 file_magic Type = identify_magic(magic: Buffer.getBuffer());
451 switch (Type) {
452 case file_magic::bitcode:
453 return extractFromBitcode(Buffer, Binaries);
454 case file_magic::elf_relocatable:
455 case file_magic::elf_executable:
456 case file_magic::elf_shared_object:
457 case file_magic::coff_object: {
458 Expected<std::unique_ptr<ObjectFile>> ObjFile =
459 ObjectFile::createObjectFile(Object: Buffer, Type);
460 if (!ObjFile)
461 return ObjFile.takeError();
462 return extractFromObject(Obj: *ObjFile->get(), Binaries);
463 }
464 case file_magic::archive: {
465 Expected<std::unique_ptr<llvm::object::Archive>> LibFile =
466 object::Archive::create(Source: Buffer);
467 if (!LibFile)
468 return LibFile.takeError();
469 return extractFromArchive(Library: *LibFile->get(), Binaries);
470 }
471 case file_magic::offload_binary:
472 return extractOffloadFiles(Contents: Buffer, Binaries);
473 default:
474 return Error::success();
475 }
476}
477
478OffloadKind object::getOffloadKind(StringRef Name) {
479 return llvm::StringSwitch<OffloadKind>(Name)
480 .Case(S: "openmp", Value: OFK_OpenMP)
481 .Case(S: "cuda", Value: OFK_Cuda)
482 .Case(S: "hip", Value: OFK_HIP)
483 .Case(S: "sycl", Value: OFK_SYCL)
484 .Default(Value: OFK_None);
485}
486
487StringRef object::getOffloadKindName(OffloadKind Kind) {
488 switch (Kind) {
489 case OFK_OpenMP:
490 return "openmp";
491 case OFK_Cuda:
492 return "cuda";
493 case OFK_HIP:
494 return "hip";
495 case OFK_SYCL:
496 return "sycl";
497 default:
498 return "none";
499 }
500}
501
502ImageKind object::getImageKind(StringRef Name) {
503 return llvm::StringSwitch<ImageKind>(Name)
504 .Case(S: "o", Value: IMG_Object)
505 .Case(S: "bc", Value: IMG_Bitcode)
506 .Case(S: "cubin", Value: IMG_Cubin)
507 .Case(S: "fatbin", Value: IMG_Fatbinary)
508 .Case(S: "s", Value: IMG_PTX)
509 .Case(S: "spv", Value: IMG_SPIRV)
510 .Default(Value: IMG_None);
511}
512
513StringRef object::getImageKindName(ImageKind Kind) {
514 switch (Kind) {
515 case IMG_Object:
516 return "o";
517 case IMG_Bitcode:
518 return "bc";
519 case IMG_Cubin:
520 return "cubin";
521 case IMG_Fatbinary:
522 return "fatbin";
523 case IMG_PTX:
524 return "s";
525 case IMG_SPIRV:
526 return "spv";
527 default:
528 return "";
529 }
530}
531
532bool object::areTargetsEquivalent(const OffloadFile::TargetID &LHS,
533 const OffloadFile::TargetID &RHS) {
534 llvm::Triple LHSTT(LHS.first);
535 llvm::Triple RHSTT(RHS.first);
536
537 // Check for logical AMDGPU target-id equivalence.
538 if (LHSTT.isAMDGPU()) {
539 AMDGPU::TargetID LHSID(LHSTT, LHS.second);
540 AMDGPU::TargetID RHSID(RHSTT, RHS.second);
541 return LHSID.isEquivalent(Other: RHSID);
542 }
543
544 // For other targets the triples must be compatible and the arch must match.
545 return LHSTT.isCompatibleWith(Other: RHSTT) && LHS.second == RHS.second;
546}
547
548bool object::areTargetsCompatible(const OffloadFile::TargetID &Provided,
549 const OffloadFile::TargetID &Requested) {
550 llvm::Triple ProvidedTT(Provided.first);
551 llvm::Triple RequestedTT(Requested.first);
552
553 // The AMDGPU target requires target-id aware checks (base processor plus
554 // xnack/sramecc features).
555 if (ProvidedTT.isAMDGPU()) {
556 AMDGPU::TargetID ProvidedID(ProvidedTT, Provided.second);
557 AMDGPU::TargetID RequestedID(RequestedTT, Requested.second);
558 return ProvidedID.providesFor(Other: RequestedID);
559 }
560
561 // For other targets the triples must be compatible.
562 if (!ProvidedTT.isCompatibleWith(Other: RequestedTT))
563 return false;
564
565 // If the architecture is "generic" we assume it is always compatible.
566 if (Provided.second == "generic" || Requested.second == "generic")
567 return true;
568
569 return Provided.second == Requested.second;
570}
571