1 | //===--- Offloading.h - 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 | // This file contains the binary format used for budingling device metadata with |
10 | // an associated device image. The data can then be stored inside a host object |
11 | // file to create a fat binary and read by the linker. This is intended to be a |
12 | // thin wrapper around the image itself. If this format becomes sufficiently |
13 | // complex it should be moved to a standard binary format like msgpack or ELF. |
14 | // |
15 | //===----------------------------------------------------------------------===// |
16 | |
17 | #ifndef LLVM_OBJECT_OFFLOADBINARY_H |
18 | #define LLVM_OBJECT_OFFLOADBINARY_H |
19 | |
20 | #include "llvm/ADT/MapVector.h" |
21 | #include "llvm/ADT/SmallString.h" |
22 | #include "llvm/ADT/StringRef.h" |
23 | #include "llvm/Object/Binary.h" |
24 | #include "llvm/Support/Error.h" |
25 | #include "llvm/Support/MemoryBuffer.h" |
26 | #include <memory> |
27 | |
28 | namespace llvm { |
29 | |
30 | namespace object { |
31 | |
32 | /// The producer of the associated offloading image. |
33 | enum OffloadKind : uint16_t { |
34 | OFK_None = 0, |
35 | OFK_OpenMP, |
36 | OFK_Cuda, |
37 | OFK_HIP, |
38 | OFK_LAST, |
39 | }; |
40 | |
41 | /// The type of contents the offloading image contains. |
42 | enum ImageKind : uint16_t { |
43 | IMG_None = 0, |
44 | IMG_Object, |
45 | IMG_Bitcode, |
46 | IMG_Cubin, |
47 | IMG_Fatbinary, |
48 | IMG_PTX, |
49 | IMG_LAST, |
50 | }; |
51 | |
52 | /// A simple binary serialization of an offloading file. We use this format to |
53 | /// embed the offloading image into the host executable so it can be extracted |
54 | /// and used by the linker. |
55 | /// |
56 | /// Many of these could be stored in the same section by the time the linker |
57 | /// sees it so we mark this information with a header. The version is used to |
58 | /// detect ABI stability and the size is used to find other offloading entries |
59 | /// that may exist in the same section. All offsets are given as absolute byte |
60 | /// offsets from the beginning of the file. |
61 | class OffloadBinary : public Binary { |
62 | public: |
63 | using string_iterator = MapVector<StringRef, StringRef>::const_iterator; |
64 | using string_iterator_range = iterator_range<string_iterator>; |
65 | |
66 | /// The current version of the binary used for backwards compatibility. |
67 | static const uint32_t Version = 1; |
68 | |
69 | /// The offloading metadata that will be serialized to a memory buffer. |
70 | struct OffloadingImage { |
71 | ImageKind TheImageKind; |
72 | OffloadKind TheOffloadKind; |
73 | uint32_t Flags; |
74 | MapVector<StringRef, StringRef> StringData; |
75 | std::unique_ptr<MemoryBuffer> Image; |
76 | }; |
77 | |
78 | /// Attempt to parse the offloading binary stored in \p Data. |
79 | static Expected<std::unique_ptr<OffloadBinary>> create(MemoryBufferRef); |
80 | |
81 | /// Serialize the contents of \p File to a binary buffer to be read later. |
82 | static SmallString<0> write(const OffloadingImage &); |
83 | |
84 | static uint64_t getAlignment() { return 8; } |
85 | |
86 | ImageKind getImageKind() const { return TheEntry->TheImageKind; } |
87 | OffloadKind getOffloadKind() const { return TheEntry->TheOffloadKind; } |
88 | uint32_t getVersion() const { return TheHeader->Version; } |
89 | uint32_t getFlags() const { return TheEntry->Flags; } |
90 | uint64_t getSize() const { return TheHeader->Size; } |
91 | |
92 | StringRef getTriple() const { return getString(Key: "triple" ); } |
93 | StringRef getArch() const { return getString(Key: "arch" ); } |
94 | StringRef getImage() const { |
95 | return StringRef(&Buffer[TheEntry->ImageOffset], TheEntry->ImageSize); |
96 | } |
97 | |
98 | // Iterator over all the key and value pairs in the binary. |
99 | string_iterator_range strings() const { |
100 | return string_iterator_range(StringData.begin(), StringData.end()); |
101 | } |
102 | |
103 | StringRef getString(StringRef Key) const { return StringData.lookup(Key); } |
104 | |
105 | static bool classof(const Binary *V) { return V->isOffloadFile(); } |
106 | |
107 | struct { |
108 | uint8_t [4] = {0x10, 0xFF, 0x10, 0xAD}; // 0x10FF10AD magic bytes. |
109 | uint32_t = OffloadBinary::Version; // Version identifier. |
110 | uint64_t ; // Size in bytes of this entire binary. |
111 | uint64_t ; // Offset of the metadata entry in bytes. |
112 | uint64_t ; // Size of the metadata entry in bytes. |
113 | }; |
114 | |
115 | struct Entry { |
116 | ImageKind TheImageKind; // The kind of the image stored. |
117 | OffloadKind TheOffloadKind; // The producer of this image. |
118 | uint32_t Flags; // Additional flags associated with the image. |
119 | uint64_t StringOffset; // Offset in bytes to the string map. |
120 | uint64_t NumStrings; // Number of entries in the string map. |
121 | uint64_t ImageOffset; // Offset in bytes of the actual binary image. |
122 | uint64_t ImageSize; // Size in bytes of the binary image. |
123 | }; |
124 | |
125 | struct StringEntry { |
126 | uint64_t KeyOffset; |
127 | uint64_t ValueOffset; |
128 | }; |
129 | |
130 | private: |
131 | (MemoryBufferRef Source, const Header *, |
132 | const Entry *TheEntry) |
133 | : Binary(Binary::ID_Offload, Source), Buffer(Source.getBufferStart()), |
134 | TheHeader(TheHeader), TheEntry(TheEntry) { |
135 | const StringEntry *StringMapBegin = |
136 | reinterpret_cast<const StringEntry *>(&Buffer[TheEntry->StringOffset]); |
137 | for (uint64_t I = 0, E = TheEntry->NumStrings; I != E; ++I) { |
138 | StringRef Key = &Buffer[StringMapBegin[I].KeyOffset]; |
139 | StringData[Key] = &Buffer[StringMapBegin[I].ValueOffset]; |
140 | } |
141 | } |
142 | |
143 | OffloadBinary(const OffloadBinary &Other) = delete; |
144 | |
145 | /// Map from keys to offsets in the binary. |
146 | MapVector<StringRef, StringRef> StringData; |
147 | /// Raw pointer to the MemoryBufferRef for convenience. |
148 | const char *Buffer; |
149 | /// Location of the header within the binary. |
150 | const Header *; |
151 | /// Location of the metadata entries within the binary. |
152 | const Entry *TheEntry; |
153 | }; |
154 | |
155 | /// A class to contain the binary information for a single OffloadBinary that |
156 | /// owns its memory. |
157 | class OffloadFile : public OwningBinary<OffloadBinary> { |
158 | public: |
159 | using TargetID = std::pair<StringRef, StringRef>; |
160 | |
161 | OffloadFile(std::unique_ptr<OffloadBinary> Binary, |
162 | std::unique_ptr<MemoryBuffer> Buffer) |
163 | : OwningBinary<OffloadBinary>(std::move(Binary), std::move(Buffer)) {} |
164 | |
165 | /// Make a deep copy of this offloading file. |
166 | OffloadFile copy() const { |
167 | std::unique_ptr<MemoryBuffer> Buffer = MemoryBuffer::getMemBufferCopy( |
168 | InputData: getBinary()->getMemoryBufferRef().getBuffer()); |
169 | |
170 | // This parsing should never fail because it has already been parsed. |
171 | auto NewBinaryOrErr = OffloadBinary::create(*Buffer); |
172 | assert(NewBinaryOrErr && "Failed to parse a copy of the binary?" ); |
173 | if (!NewBinaryOrErr) |
174 | llvm::consumeError(Err: NewBinaryOrErr.takeError()); |
175 | return OffloadFile(std::move(*NewBinaryOrErr), std::move(Buffer)); |
176 | } |
177 | |
178 | /// We use the Triple and Architecture pair to group linker inputs together. |
179 | /// This conversion function lets us use these inputs in a hash-map. |
180 | operator TargetID() const { |
181 | return std::make_pair(x: getBinary()->getTriple(), y: getBinary()->getArch()); |
182 | } |
183 | }; |
184 | |
185 | /// Extracts embedded device offloading code from a memory \p Buffer to a list |
186 | /// of \p Binaries. |
187 | Error (MemoryBufferRef Buffer, |
188 | SmallVectorImpl<OffloadFile> &Binaries); |
189 | |
190 | /// Convert a string \p Name to an image kind. |
191 | ImageKind getImageKind(StringRef Name); |
192 | |
193 | /// Convert an image kind to its string representation. |
194 | StringRef getImageKindName(ImageKind Name); |
195 | |
196 | /// Convert a string \p Name to an offload kind. |
197 | OffloadKind getOffloadKind(StringRef Name); |
198 | |
199 | /// Convert an offload kind to its string representation. |
200 | StringRef getOffloadKindName(OffloadKind Name); |
201 | |
202 | /// If the target is AMD we check the target IDs for mutual compatibility. A |
203 | /// target id is a string conforming to the folowing BNF syntax: |
204 | /// |
205 | /// target-id ::= '<arch> ( : <feature> ( '+' | '-' ) )*' |
206 | /// |
207 | /// The features 'xnack' and 'sramecc' are currently supported. These can be in |
208 | /// the state of on, off, and any when unspecified. A target marked as any can |
209 | /// bind with either on or off. This is used to link mutually compatible |
210 | /// architectures together. Returns false in the case of an exact match. |
211 | bool areTargetsCompatible(const OffloadFile::TargetID &LHS, |
212 | const OffloadFile::TargetID &RHS); |
213 | |
214 | } // namespace object |
215 | |
216 | } // namespace llvm |
217 | #endif |
218 | |