[clang] [llvm] [SYCL] Add SYCL property set registry class (PR #136697)
Justin Cai via llvm-commits
llvm-commits at lists.llvm.org
Tue Apr 22 06:37:09 PDT 2025
https://github.com/jzc created https://github.com/llvm/llvm-project/pull/136697
None
>From f5388e43a3ace6429ad911aebe2a3599858abf8f Mon Sep 17 00:00:00 2001
From: "Cai, Justin" <justin.cai at intel.com>
Date: Tue, 22 Apr 2025 06:34:45 -0700
Subject: [PATCH] [SYCL] Add SYCL property set registry class
---
.../clang-sycl-linker/ClangSYCLLinker.cpp | 14 ++-
.../llvm/Frontend/Offloading/Utility.h | 102 ++++++++++++++++++
llvm/lib/Frontend/Offloading/Utility.cpp | 102 ++++++++++++++++++
llvm/unittests/Frontend/CMakeLists.txt | 1 +
.../Frontend/PropertySetRegistryTest.cpp | 39 +++++++
5 files changed, 253 insertions(+), 5 deletions(-)
create mode 100644 llvm/unittests/Frontend/PropertySetRegistryTest.cpp
diff --git a/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp b/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
index 61ec4dbc1489d..e7f7654d377a1 100644
--- a/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
+++ b/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
@@ -20,6 +20,7 @@
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/CodeGen/CommandFlags.h"
+#include "llvm/Frontend/Offloading/Utility.h"
#include "llvm/IR/DiagnosticPrinter.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IRReader/IRReader.h"
@@ -54,6 +55,7 @@
using namespace llvm;
using namespace llvm::opt;
using namespace llvm::object;
+using namespace llvm::offloading::sycl;
/// Save intermediary results.
static bool SaveTemps = false;
@@ -355,18 +357,18 @@ Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
// result in multiple bitcode codes.
// The following lines are placeholders to represent multiple files and will
// be refactored once SYCL post link support is available.
- SmallVector<std::string> SplitModules;
- SplitModules.emplace_back(*LinkedFile);
+ SmallVector<std::pair<std::string, PropertySetRegistry>> SplitModules;
+ SplitModules.emplace_back(*LinkedFile, PropertySetRegistry{});
// SPIR-V code generation step.
for (size_t I = 0, E = SplitModules.size(); I != E; ++I) {
auto Stem = OutputFile.rsplit('.').first;
std::string SPVFile(Stem);
SPVFile.append("_" + utostr(I) + ".spv");
- auto Err = runSPIRVCodeGen(SplitModules[I], Args, SPVFile, C);
+ auto Err = runSPIRVCodeGen(SplitModules[I].first, Args, SPVFile, C);
if (Err)
return Err;
- SplitModules[I] = SPVFile;
+ SplitModules[I].first = SPVFile;
}
// Write the final output into file.
@@ -376,7 +378,7 @@ Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
llvm::raw_fd_ostream FS(FD, /*shouldClose=*/true);
for (size_t I = 0, E = SplitModules.size(); I != E; ++I) {
- auto File = SplitModules[I];
+ const auto &[File, Properties] = SplitModules[I];
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileOrErr =
llvm::MemoryBuffer::getFileOrSTDIN(File);
if (std::error_code EC = FileOrErr.getError()) {
@@ -385,6 +387,7 @@ Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
else
return createFileError(File, EC);
}
+
OffloadingImage TheImage{};
TheImage.TheImageKind = IMG_Object;
TheImage.TheOffloadKind = OFK_SYCL;
@@ -392,6 +395,7 @@ Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
Args.MakeArgString(Args.getLastArgValue(OPT_triple_EQ));
TheImage.StringData["arch"] =
Args.MakeArgString(Args.getLastArgValue(OPT_arch_EQ));
+ TheImage.StringData["sycl_properties"] = Properties.writeJSON();
TheImage.Image = std::move(*FileOrErr);
llvm::SmallString<0> Buffer = OffloadBinary::write(TheImage);
diff --git a/llvm/include/llvm/Frontend/Offloading/Utility.h b/llvm/include/llvm/Frontend/Offloading/Utility.h
index 7b717a4733b79..c33a94a6cdf29 100644
--- a/llvm/include/llvm/Frontend/Offloading/Utility.h
+++ b/llvm/include/llvm/Frontend/Offloading/Utility.h
@@ -11,6 +11,7 @@
#include <cstdint>
#include <memory>
+#include <variant>
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
@@ -159,6 +160,107 @@ namespace intel {
/// the Intel runtime offload plugin.
Error containerizeOpenMPSPIRVImage(std::unique_ptr<MemoryBuffer> &Binary);
} // namespace intel
+
+namespace sycl {
+class PropertySetRegistry;
+
+// A property value. It can be either a 32-bit unsigned integer or a byte array.
+class PropertyValue {
+public:
+ using ByteArrayTy = SmallVector<char, 8>;
+
+ PropertyValue() = default;
+ PropertyValue(uint32_t Val) : Value(Val) {}
+ PropertyValue(StringRef Data)
+ : Value(ByteArrayTy(Data.begin(), Data.end())) {}
+
+ template <typename C, typename T = typename C::value_type>
+ PropertyValue(const C &Data)
+ : PropertyValue({reinterpret_cast<const char *>(Data.data()),
+ Data.size() * sizeof(T)}) {}
+
+ uint32_t asUint32() const {
+ assert(getType() == PV_UInt32 && "must be UINT32 value");
+ return std::get<uint32_t>(Value);
+ }
+
+ StringRef asByteArray() const {
+ assert(getType() == PV_ByteArray && "must be BYTE_ARRAY value");
+ const auto &ByteArrayRef = std::get<ByteArrayTy>(Value);
+ return {ByteArrayRef.data(), ByteArrayRef.size()};
+ }
+
+ // Note: each enumeration value corresponds one-to-one to the
+ // index of the std::variant.
+ enum Type { PV_None = 0, PV_UInt32 = 1, PV_ByteArray = 2 };
+ Type getType() const { return static_cast<Type>(Value.index()); }
+
+ bool operator==(const PropertyValue &Rhs) const { return Value == Rhs.Value; }
+
+private:
+ std::variant<std::monostate, std::uint32_t, ByteArrayTy> Value = {};
+};
+
+/// A property set is a collection of PropertyValues, each identified by a name.
+/// A property set registry is a collection of property sets, each identified by
+/// a category name. The registry allows for the addition, removal, and
+/// retrieval of property sets and their properties.
+class PropertySetRegistry {
+ using PropertyMapTy = StringMap<unsigned>;
+ /// A property set. Preserves insertion order when iterating elements.
+ using PropertySet = MapVector<SmallString<16>, PropertyValue, PropertyMapTy>;
+ using MapTy = MapVector<SmallString<16>, PropertySet, PropertyMapTy>;
+
+public:
+ /// Function for bulk addition of an entire property set in the given
+ /// \p Category .
+ template <typename MapTy> void add(StringRef Category, const MapTy &Props) {
+ assert(PropSetMap.find(Category) == PropSetMap.end() &&
+ "category already added");
+ auto &PropSet = PropSetMap[Category];
+
+ for (const auto &Prop : Props)
+ PropSet.insert_or_assign(Prop.first, PropertyValue(Prop.second));
+ }
+
+ /// Adds the given \p PropVal with the given \p PropName into the given \p
+ /// Category .
+ template <typename T>
+ void add(StringRef Category, StringRef PropName, const T &PropVal) {
+ auto &PropSet = PropSetMap[Category];
+ PropSet.insert({PropName, PropertyValue(PropVal)});
+ }
+
+ void remove(StringRef Category, StringRef PropName) {
+ auto PropertySetIt = PropSetMap.find(Category);
+ if (PropertySetIt == PropSetMap.end())
+ return;
+ auto &PropertySet = PropertySetIt->second;
+ auto PropIt = PropertySet.find(PropName);
+ if (PropIt == PropertySet.end())
+ return;
+ PropertySet.erase(PropIt);
+ }
+
+ static Expected<PropertySetRegistry> readJSON(MemoryBufferRef Buf);
+
+ /// Dumps the property set registry to the given \p Out stream.
+ void writeJSON(raw_ostream &Out) const;
+ SmallString<0> writeJSON() const;
+
+ MapTy::const_iterator begin() const { return PropSetMap.begin(); }
+ MapTy::const_iterator end() const { return PropSetMap.end(); }
+
+ /// Retrieves a property set with given \p Name .
+ PropertySet &operator[](StringRef Name) { return PropSetMap[Name]; }
+ /// Constant access to the underlying map.
+ const MapTy &getPropSets() const { return PropSetMap; }
+
+private:
+ MapTy PropSetMap;
+};
+
+} // namespace sycl
} // namespace offloading
} // namespace llvm
diff --git a/llvm/lib/Frontend/Offloading/Utility.cpp b/llvm/lib/Frontend/Offloading/Utility.cpp
index 5dcc16d23004c..972c9381e40ec 100644
--- a/llvm/lib/Frontend/Offloading/Utility.cpp
+++ b/llvm/lib/Frontend/Offloading/Utility.cpp
@@ -459,3 +459,105 @@ Error offloading::intel::containerizeOpenMPSPIRVImage(
Img = MemoryBuffer::getMemBufferCopy(YamlFile);
return Error::success();
}
+
+namespace llvm::offloading::sycl {
+
+void PropertySetRegistry::writeJSON(raw_ostream &Out) const {
+ json::OStream J(Out);
+ J.object([&] {
+ for (const auto &PropSet : PropSetMap) {
+ // Note: we do not output the property sets as objects,
+ // but as arrays of [name, value] arrays because we have to
+ // preserve the order of insertion of the properties
+ // in the property set (note: this currently only used by the
+ // spec constant metadata). Even if the key-value pairs
+ // of an object are serialized in the order they were inserted,
+ // the order is not guaranteed to be preserved when
+ // deserializing because json::Object uses a DenseMap to store
+ // its key-value pairs.
+ J.attributeArray(PropSet.first, [&] {
+ for (const auto &Props : PropSet.second) {
+ J.array([&] {
+ J.value(Props.first);
+ switch (Props.second.getType()) {
+ case PropertyValue::PV_UInt32:
+ J.value(Props.second.asUint32());
+ break;
+ case PropertyValue::PV_ByteArray: {
+ StringRef ByteArrayRef = Props.second.asByteArray();
+ J.value(json::Array(ByteArrayRef.bytes()));
+ break;
+ }
+ default:
+ llvm_unreachable(("unsupported property type: " +
+ utostr(Props.second.getType()))
+ .c_str());
+ }
+ });
+ }
+ });
+ }
+ });
+}
+
+SmallString<0> PropertySetRegistry::writeJSON() const {
+ SmallString<0> Str;
+ raw_svector_ostream OS(Str);
+ writeJSON(OS);
+ return Str;
+}
+
+Expected<PropertySetRegistry>
+PropertySetRegistry::readJSON(MemoryBufferRef Buf) {
+ PropertySetRegistry Res;
+ Expected<json::Value> V = json::parse(Buf.getBuffer());
+ if (!V)
+ return V.takeError();
+ const json::Object *O = V->getAsObject();
+ if (!O)
+ return createStringError("expected JSON object");
+ for (const auto &[CategoryName, Value] : *O) {
+ const json::Array *PropsArray = Value.getAsArray();
+ if (!PropsArray)
+ return createStringError("expected JSON array for properties");
+ PropertySet &PropSet = Res.PropSetMap[StringRef(CategoryName)];
+ for (const json::Value &PropPair : *PropsArray) {
+ const json::Array *PropArray = PropPair.getAsArray();
+ if (!PropArray || PropArray->size() != 2)
+ return createStringError(
+ "expected property as [PropertyName, PropertyValue] pair");
+
+ const json::Value &PropNameVal = (*PropArray)[0];
+ const json::Value &PropValueVal = (*PropArray)[1];
+
+ std::optional<StringRef> PropName = PropNameVal.getAsString();
+ if (!PropName)
+ return createStringError("expected property name as string");
+
+ PropertyValue Prop;
+ if (std::optional<uint64_t> Val = PropValueVal.getAsUINT64()) {
+ Prop = PropertyValue(static_cast<uint32_t>(*Val));
+ } else if (const json::Array *Val = PropValueVal.getAsArray()) {
+ SmallVector<unsigned char, 8> Vec;
+ for (const json::Value &V : *Val) {
+ std::optional<uint64_t> Byte = V.getAsUINT64();
+ if (!Byte)
+ return createStringError("invalid byte array value");
+ if (*Byte > std::numeric_limits<unsigned char>::max())
+ return createStringError("byte array value out of range");
+ Vec.push_back(static_cast<unsigned char>(*Byte));
+ }
+ Prop = PropertyValue(Vec);
+ } else {
+ return createStringError("unsupported property type");
+ }
+
+ auto [It, Inserted] = PropSet.insert({*PropName, Prop});
+ if (!Inserted)
+ return createStringError("duplicate property name");
+ }
+ }
+ return Res;
+}
+
+} // namespace llvm::offloading::sycl
diff --git a/llvm/unittests/Frontend/CMakeLists.txt b/llvm/unittests/Frontend/CMakeLists.txt
index 85e113816e3bc..c836d8678e0b2 100644
--- a/llvm/unittests/Frontend/CMakeLists.txt
+++ b/llvm/unittests/Frontend/CMakeLists.txt
@@ -16,6 +16,7 @@ add_llvm_unittest(LLVMFrontendTests
OpenMPParsingTest.cpp
OpenMPCompositionTest.cpp
OpenMPDecompositionTest.cpp
+ PropertySetRegistryTest.cpp
DEPENDS
acc_gen
diff --git a/llvm/unittests/Frontend/PropertySetRegistryTest.cpp b/llvm/unittests/Frontend/PropertySetRegistryTest.cpp
new file mode 100644
index 0000000000000..ded6fccced550
--- /dev/null
+++ b/llvm/unittests/Frontend/PropertySetRegistryTest.cpp
@@ -0,0 +1,39 @@
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Frontend/Offloading/Utility.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "gtest/gtest.h"
+
+using namespace llvm::offloading::sycl;
+using namespace llvm;
+
+void checkEquality(const PropertySetRegistry &PSR1,
+ const PropertySetRegistry &PSR2) {
+ ASSERT_EQ(PSR1.getPropSets().size(), PSR2.getPropSets().size());
+ for (const auto &[Category1, PropSet1] : PSR1.getPropSets()) {
+ auto It = PSR2.getPropSets().find(Category1);
+ ASSERT_TRUE(It != PSR2.getPropSets().end());
+ const auto &[Category2, PropSet2] = *It;
+ ASSERT_EQ(PropSet1.size(), PropSet2.size());
+ for (auto It1 = PropSet1.begin(), It2 = PropSet2.begin(),
+ E = PropSet1.end();
+ It1 != E; ++It1, ++It2) {
+ const auto &[PropName1, PropValue1] = *It1;
+ const auto &[PropName2, PropValue2] = *It2;
+ ASSERT_EQ(PropName1, PropName2);
+ ASSERT_EQ(PropValue1, PropValue2);
+ }
+ }
+}
+
+TEST(PropertySetRegistryTest, PropertySetRegistry) {
+ PropertySetRegistry PSR;
+ PSR.add("Category1", "Prop1", 42);
+ PSR.add("Category1", "Prop2", "Hello");
+ SmallVector<int, 3> arr = {4, 16, 32};
+ PSR.add("Category2", "A", arr);
+ auto Serialized = PSR.writeJSON();
+ auto PSR2 = PropertySetRegistry::readJSON({Serialized, ""});
+ if (auto Err = PSR2.takeError())
+ FAIL();
+ checkEquality(PSR, *PSR2);
+}
More information about the llvm-commits
mailing list