Skip to content

Commit 185a23e

Browse files
jzcasudarsa
andauthored
[SYCL] Add property set types and JSON representation (#147321)
This PR adds the `PropertySet` type, along with a pair of functions used to serialize and deserialize into a JSON representation. A property set is a key-value map, with values being one of 2 types - uint32 or byte array. A property set registry is a collection of property sets, indexed by a "category" name. In SYCL offloading, property sets will be used to communicate metadata about device images needed by the SYCL runtime. For example, there is a property set which has a byte array containing the numeric ID, offset, and size of each SYCL2020 spec constant. Another example is a property set describing the optional kernel features used in the module: does it use fp64? fp16? atomic64? This metadata will be computed by `clang-sycl-linker` and the JSON representation will be inserted in the string table of each output `OffloadBinary`. This JSON will be consumed the SYCL offload wrapper and will be lowered to the binary form SYCL runtime expects. For example, consider this SYCL program that calls a kernel that uses fp64: ```c++ #include <sycl/sycl.hpp> using namespace sycl; class MyKernel; int main() { queue q; auto *p = malloc_shared<double>(1, q); *p = .1; q.single_task<MyKernel>([=]{ *p *= 2; }).wait(); std::cout << *p << "\n"; free(p, q); } ``` The device code for this program would have the kernel marked with `!sycl_used_aspects`: ``` define spir_kernel void @_ZTS8MyKernel([...]) !sycl_used_aspects !n { [...] } !n = {i32 6} ``` `clang-sycl-linker` would recognize this metadata and then would output the following JSON in the `OffloadBinary`'s key-value map: ``` { "SYCL/device requirements": { // aspects contains a list of sycl::aspect values used // by the module; in this case just the value 6 encoded // as a 4-byte little-endian integer "aspects": "BjAwMA==" } } ``` The SYCL offload wrapper would lower those property sets to something like this: ```c++ struct _sycl_device_binary_property_set_struct { char *CategoryName; _sycl_device_binary_property *PropertiesBegin; _sycl_device_binary_property *PropertiesEnd; }; struct _sycl_device_binary_property_struct { char *PropertyName; void *ValAddr; uint64_t ValSize; }; // _sycl_device_binary_property_struct device_requirements[] = { /* PropertyName */ "aspects", /* ValAddr */ [pointer to the bytes 0x06 0x00 0x00 0x00], /* ValSize */ 4, }; _sycl_device_binary_property_set_struct properties[] = { /* CategoryName */ "SYCL/device requirements", /* PropertiesBegin */ device_requirements, /* PropertiesEnd */ std::end(device_requirments), } ``` --------- Co-authored-by: Arvind Sudarsanam <[email protected]>
1 parent cc3932b commit 185a23e

File tree

5 files changed

+211
-0
lines changed

5 files changed

+211
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
///===- llvm/Frontend/Offloading/PropertySet.h ----------------------------===//
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+
/// \file This file defines PropertySetRegistry and PropertyValue types and
9+
/// provides helper functions to translate PropertySetRegistry from/to JSON.
10+
//===----------------------------------------------------------------------===//
11+
12+
#include "llvm/ADT/SmallVector.h"
13+
#include "llvm/Support/Error.h"
14+
15+
#include <map>
16+
#include <variant>
17+
18+
namespace llvm {
19+
class raw_ostream;
20+
class MemoryBufferRef;
21+
22+
namespace offloading {
23+
24+
using ByteArray = SmallVector<unsigned char, 0>;
25+
using PropertyValue = std::variant<uint32_t, ByteArray>;
26+
using PropertySet = std::map<std::string, PropertyValue>;
27+
using PropertySetRegistry = std::map<std::string, PropertySet>;
28+
29+
void writePropertiesToJSON(const PropertySetRegistry &P, raw_ostream &O);
30+
Expected<PropertySetRegistry> readPropertiesFromJSON(MemoryBufferRef Buf);
31+
32+
} // namespace offloading
33+
} // namespace llvm

llvm/lib/Frontend/Offloading/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
add_llvm_component_library(LLVMFrontendOffloading
22
Utility.cpp
33
OffloadWrapper.cpp
4+
PropertySet.cpp
45

56
ADDITIONAL_HEADER_DIRS
67
${LLVM_MAIN_INCLUDE_DIR}/llvm/Frontend
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
///===- llvm/Frontend/Offloading/PropertySet.cpp --------------------------===//
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/Frontend/Offloading/PropertySet.h"
10+
#include "llvm/Support/Base64.h"
11+
#include "llvm/Support/JSON.h"
12+
#include "llvm/Support/MemoryBufferRef.h"
13+
14+
using namespace llvm;
15+
using namespace llvm::offloading;
16+
17+
void llvm::offloading::writePropertiesToJSON(
18+
const PropertySetRegistry &PSRegistry, raw_ostream &Out) {
19+
json::OStream J(Out);
20+
J.object([&] {
21+
for (const auto &[CategoryName, PropSet] : PSRegistry) {
22+
J.attributeObject(CategoryName, [&] {
23+
for (const auto &[PropName, PropVal] : PropSet) {
24+
switch (PropVal.index()) {
25+
case 0:
26+
J.attribute(PropName, std::get<uint32_t>(PropVal));
27+
break;
28+
case 1:
29+
J.attribute(PropName, encodeBase64(std::get<ByteArray>(PropVal)));
30+
break;
31+
default:
32+
llvm_unreachable("unsupported property type");
33+
}
34+
}
35+
});
36+
}
37+
});
38+
}
39+
40+
// note: createStringError has an overload that takes a format string,
41+
// but it uses llvm::format instead of llvm::formatv, which does
42+
// not work with json::Value. This is a helper function to use
43+
// llvm::formatv with createStringError.
44+
template <typename... Ts> auto createStringErrorV(Ts &&...Args) {
45+
return createStringError(formatv(std::forward<Ts>(Args)...));
46+
}
47+
48+
Expected<PropertyValue>
49+
readPropertyValueFromJSON(const json::Value &PropValueVal) {
50+
if (std::optional<uint64_t> Val = PropValueVal.getAsUINT64())
51+
return PropertyValue(static_cast<uint32_t>(*Val));
52+
53+
if (std::optional<StringRef> Val = PropValueVal.getAsString()) {
54+
std::vector<char> Decoded;
55+
if (Error E = decodeBase64(*Val, Decoded))
56+
return createStringErrorV("unable to base64 decode the string {0}: {1}",
57+
Val, toString(std::move(E)));
58+
return PropertyValue(ByteArray(Decoded.begin(), Decoded.end()));
59+
}
60+
61+
return createStringErrorV("expected a uint64 or a string, got {0}",
62+
PropValueVal);
63+
}
64+
65+
Expected<PropertySetRegistry>
66+
llvm::offloading::readPropertiesFromJSON(MemoryBufferRef Buf) {
67+
PropertySetRegistry Res;
68+
Expected<json::Value> V = json::parse(Buf.getBuffer());
69+
if (Error E = V.takeError())
70+
return E;
71+
72+
const json::Object *O = V->getAsObject();
73+
if (!O)
74+
return createStringErrorV(
75+
"error while deserializing property set registry: "
76+
"expected JSON object, got {0}",
77+
*V);
78+
79+
for (const auto &[CategoryName, Value] : *O) {
80+
const json::Object *PropSetVal = Value.getAsObject();
81+
if (!PropSetVal)
82+
return createStringErrorV("error while deserializing property set {0}: "
83+
"expected JSON array, got {1}",
84+
CategoryName.str(), Value);
85+
86+
PropertySet &PropSet = Res[CategoryName.str()];
87+
for (const auto &[PropName, PropValueVal] : *PropSetVal) {
88+
Expected<PropertyValue> Prop = readPropertyValueFromJSON(PropValueVal);
89+
if (Error E = Prop.takeError())
90+
return createStringErrorV(
91+
"error while deserializing property {0} in property set {1}: {2}",
92+
PropName.str(), CategoryName.str(), toString(std::move(E)));
93+
94+
auto [It, Inserted] =
95+
PropSet.try_emplace(PropName.str(), std::move(*Prop));
96+
assert(Inserted && "Property already exists in PropertySet");
97+
}
98+
}
99+
return Res;
100+
}

llvm/unittests/Frontend/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ add_llvm_unittest(LLVMFrontendTests
2222
OpenMPDecompositionTest.cpp
2323
OpenMPDirectiveNameTest.cpp
2424
OpenMPDirectiveNameParserTest.cpp
25+
PropertySetRegistryTest.cpp
2526

2627
DEPENDS
2728
acc_gen
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//===- llvm/unittest/Frontend/PropertySetRegistry.cpp ---------------------===//
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/ADT/SmallVector.h"
10+
#include "llvm/Frontend/Offloading/PropertySet.h"
11+
#include "llvm/Support/MemoryBuffer.h"
12+
#include "gtest/gtest.h"
13+
14+
using namespace llvm::offloading;
15+
using namespace llvm;
16+
17+
void checkSerialization(const PropertySetRegistry &PSR) {
18+
SmallString<0> Serialized;
19+
raw_svector_ostream OS(Serialized);
20+
writePropertiesToJSON(PSR, OS);
21+
auto PSR2 = readPropertiesFromJSON({Serialized, ""});
22+
ASSERT_EQ("", toString(PSR2.takeError()));
23+
EXPECT_EQ(PSR, *PSR2);
24+
}
25+
26+
TEST(PropertySetRegistryTest, PropertySetRegistry) {
27+
PropertySetRegistry PSR;
28+
checkSerialization(PSR);
29+
30+
PSR["Category1"]["Prop1"] = 42U;
31+
PSR["Category1"]["Prop2"] = ByteArray(StringRef("Hello").bytes());
32+
PSR["Category2"]["A"] = ByteArray{0, 4, 16, 32, 255};
33+
checkSerialization(PSR);
34+
35+
PSR = PropertySetRegistry();
36+
PSR["ABC"]["empty_array"] = ByteArray();
37+
PSR["ABC"]["max_val"] = std::numeric_limits<uint32_t>::max();
38+
checkSerialization(PSR);
39+
}
40+
41+
TEST(PropertySetRegistryTest, IllFormedJSON) {
42+
SmallString<0> Input;
43+
44+
// Invalid json
45+
Input = "{ invalid }";
46+
auto Res = readPropertiesFromJSON({Input, ""});
47+
EXPECT_NE("", toString(Res.takeError()));
48+
49+
Input = "";
50+
Res = readPropertiesFromJSON({Input, ""});
51+
EXPECT_NE("", toString(Res.takeError()));
52+
53+
// Not a JSON object
54+
Input = "[1, 2, 3]";
55+
Res = readPropertiesFromJSON({Input, ""});
56+
EXPECT_NE("", toString(Res.takeError()));
57+
58+
// Property set not an object
59+
Input = R"({ "Category": 42 })";
60+
Res = readPropertiesFromJSON({Input, ""});
61+
EXPECT_NE("", toString(Res.takeError()));
62+
63+
// Property value has non string/non-integer type
64+
Input = R"({ "Category": { "Prop": [1, 2, 3] } })";
65+
Res = readPropertiesFromJSON({Input, ""});
66+
EXPECT_NE("", toString(Res.takeError()));
67+
68+
// Property value is an invalid base64 string
69+
Input = R"({ "Category": { "Prop": ";" } })";
70+
Res = readPropertiesFromJSON({Input, ""});
71+
EXPECT_NE("", toString(Res.takeError()));
72+
73+
Input = R"({ "Category": { "Prop": "!@#$" } })";
74+
Res = readPropertiesFromJSON({Input, ""});
75+
EXPECT_NE("", toString(Res.takeError()));
76+
}

0 commit comments

Comments
 (0)