Skip to content

Commit b0d98dc

Browse files
authored
[SYCL] Propagate kernel parameter optimization info to SYCL runtime. (#2303)
- Implement Base64 encoder/decoder to represent arbitrary data in property files - Introduce BYTE_ARRAY property type in PropertySetIO - Implement support for BYTE_ARRAY properties in clang offload wrapper - Use the above tools to propagate kernel parameter optimization info from IR to SYCL RT: * The post-link tool - uses a new analysis pass (SPIRKernelParamOptInfo) to extract kernel parameter optimization info from IR produced by the DeadArgumentElimination pass - uses the PropertySetIO mechanism to encode this information into the output property file * Then the clang offload wrapper picks up the property file and injects it into the device binary descriptor accessed by the SYCL runtime when the app runs. Signed-off-by: Konstantin S Bobrovsky <[email protected]>
1 parent 97b893a commit b0d98dc

File tree

14 files changed

+770
-50
lines changed

14 files changed

+770
-50
lines changed

clang/test/Driver/clang-offload-wrapper-exe.cpp

Lines changed: 103 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@
44
// works, and that the tool generates data properly accessible at runtime.
55

66
// --- Prepare test data
7-
// RUN: echo -e -n 'device binary image\n' > %t.bin
7+
// - create the first binary image
8+
// RUN: echo -e -n 'device binary image1\n' > %t.bin
89
// RUN: echo -e -n '[Category1]\nint_prop1=1|10\n[Category2]\nint_prop2=1|20\n' > %t.props
910
// RUN: echo -e -n 'kernel1\nkernel2\n' > %t.sym
1011
// RUN: echo -e -n 'Manifest file - arbitrary data generated by the toolchain\n' > %t.mnf
12+
13+
// - create the second binary image with byte array property values
14+
// RUN: echo -e -n 'device binary image2\n' > %t_1.bin
15+
// RUN: echo -e -n '[Category3]\n' > %t_1.props
16+
// RUN: echo -e -n 'kernel1=2|IAAAAAAAAAQA\n' >> %t_1.props
17+
// RUN: echo -e -n 'kernel2=2|oAAAAAAAAAw///3/wB\n' >> %t_1.props
18+
19+
// - create the batch file input for the wrapper
1120
// RUN: echo '[Code|Properties|Symbols|Manifest]' > %t.batch
1221
// RUN: echo %t.bin"|"%t.props"|"%t.sym"|"%t.mnf >> %t.batch
22+
// RUN: echo %t_1.bin"|"%t_1.props"|""|" >> %t.batch
1323
// --- Generate "gold" output
1424
// RUN: cat %t.bin %t.mnf %t.props %t.sym > %t.all
1525
// --- Create the wrapper object
@@ -22,6 +32,8 @@
2232
// RUN: %t.batch.exe > %t.batch.exe.out
2333
// RUN: diff -b %t.batch.exe.out %t.all
2434

35+
#include <assert.h>
36+
#include <cstring>
2537
#include <iostream>
2638
#include <string>
2739

@@ -105,31 +117,97 @@ static int getInt(void *Addr) {
105117
return Ptr[0] | (Ptr[1] << 8) | (Ptr[2] << 16) | (Ptr[3] << 24);
106118
}
107119

108-
int main(int argc, char **argv) {
109-
ASSERT(BinDesc->NumDeviceBinaries == 1, "BinDesc->NumDeviceBinaries");
110-
ASSERT(BinDesc->Version == 1, "BinDesc->Version");
120+
using byte = unsigned char;
121+
122+
static void printProp(const pi_device_binary_property &Prop) {
123+
std::cerr << "Property " << Prop->Name << " {\n";
124+
std::cerr << " Type: " << Prop->Type << "\n";
125+
if (Prop->Type != 1)
126+
std::cerr << " Size = " << Prop->ValSize << "\n";
127+
128+
std::cerr << " Value = ";
129+
if (Prop->Type == 1)
130+
std::cerr << getInt(&Prop->ValSize);
131+
else {
132+
std::cerr << " {\n ";
111133

112-
for (int I = 0; I < BinDesc->NumDeviceBinaries; ++I) {
113-
pi_device_binary Bin = &BinDesc->DeviceBinaries[I];
114-
ASSERT(Bin->Kind == 4, "Bin->Kind");
115-
ASSERT(Bin->Format == 1, "Bin->Format");
116-
117-
// dump code
118-
std::cout << getString(Bin->BinaryStart, Bin->BinaryEnd);
119-
// dump manifest
120-
std::cout << std::string(Bin->ManifestStart, Bin->ManifestEnd - Bin->ManifestStart);
121-
// dump properties
122-
for (pi_device_binary_property_set PropSet = Bin->PropertySetsBegin; PropSet != Bin->PropertySetsEnd; ++PropSet) {
123-
std::cout << "[" << PropSet->Name << "]"
124-
<< "\n";
125-
126-
for (pi_device_binary_property Prop = PropSet->PropertiesBegin; Prop != PropSet->PropertiesEnd; ++Prop)
127-
// values fitting into 64 bits are written into the 'ValAddr' field:
128-
std::cout << Prop->Name << "=" << Prop->Type << "|" << getInt(&Prop->ValSize) << "\n";
134+
byte *Ptr = (byte *)Prop->ValAddr;
135+
136+
for (auto I = 0; I < Prop->ValSize && I < 100; ++I) {
137+
std::cerr << " 0x" << std::hex << (unsigned int)Ptr[I];
138+
std::cerr << std::dec;
139+
}
140+
std::cerr << "\n }";
141+
}
142+
std::cerr << "\n";
143+
std::cerr << "}\n";
144+
}
145+
146+
static int dumpBinary0() {
147+
pi_device_binary Bin = &BinDesc->DeviceBinaries[0];
148+
ASSERT(Bin->Kind == 4, "Bin->Kind");
149+
ASSERT(Bin->Format == 1, "Bin->Format");
150+
151+
// dump code
152+
std::cout << getString(Bin->BinaryStart, Bin->BinaryEnd);
153+
// dump manifest
154+
std::cout << std::string(Bin->ManifestStart, Bin->ManifestEnd - Bin->ManifestStart);
155+
// dump properties
156+
for (pi_device_binary_property_set PropSet = Bin->PropertySetsBegin; PropSet != Bin->PropertySetsEnd; ++PropSet) {
157+
std::cout << "[" << PropSet->Name << "]"
158+
<< "\n";
159+
160+
for (pi_device_binary_property Prop = PropSet->PropertiesBegin; Prop != PropSet->PropertiesEnd; ++Prop) {
161+
ASSERT(Prop->Type == 1, "Prop->Type");
162+
std::cout << Prop->Name << "=" << Prop->Type << "|" << getInt(&Prop->ValSize) << "\n";
129163
}
130-
// dump symbols
131-
for (_pi_offload_entry Entry = Bin->EntriesBegin; Entry != Bin->EntriesEnd; ++Entry)
132-
std::cout << Entry->name << "\n";
133164
}
165+
// dump symbols
166+
for (_pi_offload_entry Entry = Bin->EntriesBegin; Entry != Bin->EntriesEnd; ++Entry)
167+
std::cout << Entry->name << "\n";
134168
return 0;
135-
}
169+
}
170+
171+
// Clang offload wrapper does Base64 decoding on byte array property values, so
172+
// they can't be dumped as is and compared to the original. Instead, this
173+
// testcase checks that the byte array in the property value is equal to the
174+
// pre-decoded byte array.
175+
static int checkBinary1() {
176+
// Decoded from "IAAAAAAAAAQA":
177+
const byte Arr0[] = {8, 0, 0, 0, 0, 0, 0, 0, 0x1};
178+
// Decoded from "oAAAAAAAAAw///3/wB":
179+
const byte Arr1[] = {40, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0x7F, 0xFF, 0x70};
180+
181+
struct {
182+
const byte *Ptr;
183+
const size_t Size;
184+
} GoldArrays[] = {
185+
{Arr0, sizeof(Arr0)},
186+
{Arr1, sizeof(Arr1)}};
187+
pi_device_binary Bin = &BinDesc->DeviceBinaries[1];
188+
ASSERT(Bin->Kind == 4, "Bin->Kind");
189+
ASSERT(Bin->Format == 1, "Bin->Format");
190+
191+
for (pi_device_binary_property_set PropSet = Bin->PropertySetsBegin; PropSet != Bin->PropertySetsEnd; ++PropSet) {
192+
int Cnt = 0;
193+
194+
for (pi_device_binary_property Prop = PropSet->PropertiesBegin; Prop != PropSet->PropertiesEnd; ++Prop, ++Cnt) {
195+
ASSERT(Prop->Type == 2, "Prop->Type"); // must be a byte array
196+
char *Ptr = reinterpret_cast<char *>(Prop->ValAddr);
197+
int Cmp = std::memcmp(Prop->ValAddr, GoldArrays[Cnt].Ptr, GoldArrays[Cnt].Size);
198+
ASSERT(Cmp == 0, "byte array property");
199+
}
200+
}
201+
return 0;
202+
}
203+
204+
int main(int argc, char **argv) {
205+
ASSERT(BinDesc->NumDeviceBinaries == 2, "BinDesc->NumDeviceBinaries");
206+
ASSERT(BinDesc->Version == 1, "BinDesc->Version");
207+
208+
if (dumpBinary0() != 0)
209+
return 1;
210+
if (checkBinary1() != 0)
211+
return 1;
212+
return 0;
213+
}

clang/tools/clang-offload-wrapper/ClangOffloadWrapper.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,15 @@ class BinaryWrapper {
610610
return std::make_pair(ImageB, ImageE);
611611
}
612612

613+
// Adds given data buffer as constant byte array and returns a constant
614+
// pointer to it. The pointer type does not carry size information.
615+
Constant *addRawDataToModule(ArrayRef<char> Data, const Twine &Name) {
616+
auto *Var = addGlobalArrayVariable(Name, Data);
617+
auto *DataPtr = ConstantExpr::getGetElementPtr(Var->getValueType(), Var,
618+
getSizetConstPair(0u, 0u));
619+
return DataPtr;
620+
}
621+
613622
// Creates all necessary data objects for the given image and returns a pair
614623
// of pointers that point to the beginning and end of the global variable that
615624
// contains the image data.
@@ -718,6 +727,14 @@ class BinaryWrapper {
718727
ConstantInt::get(Type::getInt64Ty(C), Prop.second.asUint32());
719728
break;
720729
}
730+
case llvm::util::PropertyValue::BYTE_ARRAY: {
731+
const char *Ptr =
732+
reinterpret_cast<const char *>(Prop.second.asRawByteArray());
733+
uint64_t Size = Prop.second.getRawByteArraySize();
734+
PropValSize = ConstantInt::get(Type::getInt64Ty(C), Size);
735+
PropValAddr = addRawDataToModule(ArrayRef<char>(Ptr, Size), "prop_val");
736+
break;
737+
}
721738
default:
722739
llvm_unreachable_internal("unsupported property");
723740
}

llvm/include/llvm/Support/Base64.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
#ifndef LLVM_SUPPORT_BASE64_H
1414
#define LLVM_SUPPORT_BASE64_H
1515

16+
#include "llvm/Support/Error.h"
17+
#include "llvm/Support/raw_ostream.h"
18+
19+
#include <memory>
1620
#include <string>
1721

1822
namespace llvm {
@@ -51,6 +55,33 @@ template <class InputBytes> std::string encodeBase64(InputBytes const &Bytes) {
5155
return Buffer;
5256
}
5357

58+
// General-purpose Base64 encoder/decoder.
59+
// TODO update WinCOFFObjectWriter.cpp to use this library.
60+
class Base64 {
61+
public:
62+
using byte = uint8_t;
63+
64+
// Get the size of the encoded byte sequence of given size.
65+
static size_t getEncodedSize(size_t SrcSize);
66+
67+
// Encode a byte sequence of given size into an output stream.
68+
// Returns the number of bytes in the encoded result.
69+
static size_t encode(const byte *Src, raw_ostream &Out, size_t SrcSize);
70+
71+
// Get the size of the encoded byte sequence of given size.
72+
static size_t getDecodedSize(size_t SrcSize);
73+
74+
// Decode a sequence of given size into a pre-allocated memory.
75+
// Returns the number of bytes in the decoded result or 0 in case of error.
76+
static Expected<size_t> decode(const char *Src, byte *Dst, size_t SrcSize);
77+
78+
// Allocate minimum required amount of memory and decode a sequence of given
79+
// size into it.
80+
// Returns the decoded result. The size can be obtained via getDecodedSize.
81+
static Expected<std::unique_ptr<byte>> decode(const char *Src,
82+
size_t SrcSize);
83+
};
84+
5485
} // end namespace llvm
5586

56-
#endif
87+
#endif // LLVM_SUPPORT_BASE64_H

llvm/include/llvm/Support/PropertySetIO.h

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ namespace util {
5151
// container.
5252
class PropertyValue {
5353
public:
54+
// Type of the size of the value. Value size gets serialized along with the
55+
// value data in some cases for later reading at runtime, so size_t is not
56+
// suitable as its size varies.
57+
using SizeTy = uint64_t;
58+
using byte = uint8_t;
59+
5460
// Defines supported property types
55-
enum Type { first = 0, NONE = first, UINT32, last = UINT32 };
61+
enum Type { first = 0, NONE = first, UINT32, BYTE_ARRAY, last = BYTE_ARRAY };
5662

5763
// Translates C++ type to the corresponding type tag.
5864
template <typename T> static Type getTypeTag();
@@ -64,53 +70,103 @@ class PropertyValue {
6470
return static_cast<Type>(T);
6571
}
6672

73+
~PropertyValue() {
74+
if ((getType() == BYTE_ARRAY) && Val.ByteArrayVal)
75+
delete[] Val.ByteArrayVal;
76+
}
77+
6778
PropertyValue() = default;
6879
PropertyValue(Type T) : Ty(T) {}
6980

7081
PropertyValue(uint32_t Val) : Ty(UINT32), Val({Val}) {}
71-
PropertyValue(const PropertyValue &P) = default;
72-
PropertyValue(PropertyValue &&P) = default;
82+
PropertyValue(const byte *Data, SizeTy DataBitSize);
83+
PropertyValue(const PropertyValue &P);
84+
PropertyValue(PropertyValue &&P);
7385

74-
PropertyValue &operator=(PropertyValue &&P) = default;
86+
PropertyValue &operator=(PropertyValue &&P);
7587

76-
PropertyValue &operator=(const PropertyValue &P) = default;
88+
PropertyValue &operator=(const PropertyValue &P);
7789

7890
// get property value as unsigned 32-bit integer
7991
uint32_t asUint32() const {
80-
assert(Ty == UINT32);
92+
if (Ty != UINT32)
93+
llvm_unreachable("must be UINT32 value");
8194
return Val.UInt32Val;
8295
}
8396

97+
// Get raw data size in bits.
98+
SizeTy getByteArraySizeInBits() const {
99+
if (Ty != BYTE_ARRAY)
100+
llvm_unreachable("must be BYTE_ARRAY value");
101+
SizeTy Res = 0;
102+
103+
for (auto I = 0; I < sizeof(SizeTy); ++I)
104+
Res |= (SizeTy)Val.ByteArrayVal[I] << (8 * I);
105+
return Res;
106+
}
107+
108+
// Get byte array data size in bytes.
109+
SizeTy getByteArraySize() const {
110+
SizeTy SizeInBits = getByteArraySizeInBits();
111+
constexpr unsigned int MASK = 0x7;
112+
return ((SizeInBits + MASK) & ~MASK) / 8;
113+
}
114+
115+
// Get byte array data size in bytes, including the leading bytes encoding the
116+
// size.
117+
SizeTy getRawByteArraySize() const {
118+
return getByteArraySize() + sizeof(SizeTy);
119+
}
120+
121+
// Get byte array data including the leading bytes encoding the size.
122+
const byte *asRawByteArray() const {
123+
if (Ty != BYTE_ARRAY)
124+
llvm_unreachable("must be BYTE_ARRAY value");
125+
return Val.ByteArrayVal;
126+
}
127+
128+
// Get byte array data excluding the leading bytes encoding the size.
129+
const byte *asByteArray() const {
130+
if (Ty != BYTE_ARRAY)
131+
llvm_unreachable("must be BYTE_ARRAY value");
132+
return Val.ByteArrayVal + sizeof(SizeTy);
133+
}
134+
84135
bool isValid() const { return getType() != NONE; }
85136

86137
// set property value; the 'T' type must be convertible to a property type tag
87138
template <typename T> void set(T V) {
88-
assert(getTypeTag<T>() == Ty);
139+
if (getTypeTag<T>() != Ty)
140+
llvm_unreachable("invalid type tag for this operation");
89141
getValueRef<T>() = V;
90142
}
91143

92144
Type getType() const { return Ty; }
93145

94-
size_t size() const {
146+
SizeTy size() const {
95147
switch (Ty) {
96148
case UINT32:
97149
return sizeof(Val.UInt32Val);
150+
case BYTE_ARRAY:
151+
return getRawByteArraySize();
98152
default:
99153
llvm_unreachable_internal("unsupported property type");
100154
}
101155
}
102156

103157
private:
104158
template <typename T> T &getValueRef();
159+
void copy(const PropertyValue &P);
105160

106161
Type Ty = NONE;
162+
// TODO: replace this union with std::variant when uplifting to C++17
107163
union {
108164
uint32_t UInt32Val;
165+
// Holds first sizeof(size_t) bytes of size followed by actual raw data.
166+
byte *ByteArrayVal;
109167
} Val;
110168
};
111169

112-
std::ostream &operator<<(std::ostream &Out, const PropertyValue &V);
113-
114170
// A property set. Preserves insertion order when iterating elements.
115171
using PropertySet = MapVector<StringRef, PropertyValue>;
116172

@@ -124,6 +180,7 @@ class PropertySetRegistry {
124180
static constexpr char SYCL_SPECIALIZATION_CONSTANTS[] =
125181
"SYCL/specialization constants";
126182
static constexpr char SYCL_DEVICELIB_REQ_MASK[] = "SYCL/devicelib req mask";
183+
static constexpr char SYCL_KERNEL_PARAM_OPT_INFO[] = "SYCL/kernel param opt";
127184

128185
// Function for bulk addition of an entire property set under given category
129186
// (property set name).

0 commit comments

Comments
 (0)