From cb29d2e0db8f9806548d1a93bc0042ab3d75d8f2 Mon Sep 17 00:00:00 2001 From: "Javier G. Sogo" Date: Mon, 27 Mar 2023 16:55:51 +0200 Subject: [PATCH] Implement a `PayloadRegister` as a factory to register and create `Payload`s (#68) This PR implements a `PayloadRegistry` factory as a template specialization of `plugin::registry::PluginRegistry` (see #63). This registry can instantiate `PayloadInfo` items which, in turn, are responsible of instantiate concrete `Payload` implementations (same schema as `TargetSystem`). It also turns the `ZipPayload` into a pluggable `Payload` for this new registry. --------- Co-authored-by: Marius Hillenbrand --- CMakeLists.txt | 7 + include/Payload/Payload.h | 42 +-- include/Payload/PayloadRegistry.h | 35 +++ lib/API/api.cpp | 42 ++- lib/Payload/CMakeLists.txt | 37 ++- lib/Payload/Payload.cpp | 213 +-------------- lib/Payload/Payloads.inc.in | 17 ++ lib/Payload/ZipPayload/CMakeLists.txt | 31 +++ lib/Payload/ZipPayload/Payload.inc | 33 +++ lib/Payload/ZipPayload/ZipPayload.cpp | 245 ++++++++++++++++++ lib/Payload/ZipPayload/ZipPayload.h | 61 +++++ mock_target/MockTarget.cpp | 2 +- test/unittest/CMakeLists.txt | 20 +- test/unittest/Payload/PayloadRegistryTest.cpp | 39 +++ tools/qss-opt/qss-opt.cpp | 12 +- 15 files changed, 568 insertions(+), 268 deletions(-) create mode 100644 include/Payload/PayloadRegistry.h create mode 100644 lib/Payload/Payloads.inc.in create mode 100644 lib/Payload/ZipPayload/CMakeLists.txt create mode 100644 lib/Payload/ZipPayload/Payload.inc create mode 100644 lib/Payload/ZipPayload/ZipPayload.cpp create mode 100644 lib/Payload/ZipPayload/ZipPayload.h create mode 100644 test/unittest/Payload/PayloadRegistryTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d6643175..7aa98f266 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,6 +152,13 @@ if(QSSC_WITH_MOCK_TARGET) ) endif() +set(QSSC_PAYLOAD_PATHS + "" + CACHE STRING + "List of directories containing QSS compiler payloads." + ) + + set(QSSC_RESOURCES_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/lib/qssc) configure_file(include/Config.h.in ${QSSC_BINARY_DIR}/include/Config.h) diff --git a/include/Payload/Payload.h b/include/Payload/Payload.h index 475b1ff39..4beffa57b 100644 --- a/include/Payload/Payload.h +++ b/include/Payload/Payload.h @@ -32,12 +32,21 @@ #include "llvm/Support/raw_ostream.h" namespace qssc::payload { + +struct PayloadConfig { + std::string prefix; + std::string name; +}; + // Payload class will wrap the QSS Payload and interface with the qss-compiler class Payload { +public: + using PluginConfiguration = PayloadConfig; + public: Payload() : prefix(""), name("exp") {} - Payload(std::string prefix, std::string name) - : prefix(std::move(prefix) + "/"), name(std::move(name)) { + explicit Payload(PayloadConfig config) + : prefix(std::move(config.prefix) + "/"), name(std::move(config.name)) { files.clear(); } virtual ~Payload() = default; @@ -76,35 +85,6 @@ class Payload { std::unordered_map files; }; // class Payload -class ZipPayload : public Payload { -public: - ZipPayload() = default; - ZipPayload(std::string prefix, std::string name) - : Payload(std::move(prefix), std::move(name)) {} - virtual ~ZipPayload() = default; - - // write all files to the stream - virtual void write(llvm::raw_ostream &stream) override; - // write all files to the stream - virtual void write(std::ostream &stream) override; - // write all files in plaintext to the stream - virtual void writePlain(std::ostream &stream) override; - virtual void writePlain(llvm::raw_ostream &stream) override; - - // write all files to a zip archive named fName - void writeZip(std::string fName); - // write all files to a zip archive and output it to the stream - void writeZip(std::ostream &stream); - void writeZip(llvm::raw_ostream &stream); - // write all files in plaintext to the dir named dirName - void writePlain(const std::string &dirName = "."); - -private: - // creates a manifest json file - void addManifest(); - -}; // class ZipPayload - } // namespace qssc::payload #endif // PAYLOAD_PAYLOAD_H diff --git a/include/Payload/PayloadRegistry.h b/include/Payload/PayloadRegistry.h new file mode 100644 index 000000000..d9fff4b86 --- /dev/null +++ b/include/Payload/PayloadRegistry.h @@ -0,0 +1,35 @@ +//===- PayloadRegistry.h - Payload Registry ---------------------*- C++ -*-===// +// +// (C) Copyright IBM 2023. +// +// This code is part of Qiskit. +// +// This code is licensed under the Apache License, Version 2.0 with LLVM +// Exceptions. You may obtain a copy of this license in the LICENSE.txt +// file in the root directory of this source tree. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. +// +//===----------------------------------------------------------------------===// +// +// Declaration of the QSSC payload registry system. +// +//===----------------------------------------------------------------------===// +#ifndef PAYLOADREGISTRY_H +#define PAYLOADREGISTRY_H + +#include "Payload.h" + +#include "Plugin/PluginInfo.h" +#include "Plugin/PluginRegistry.h" + +namespace qssc::payload::registry { + +using PayloadInfo = plugin::registry::PluginInfo; +using PayloadRegistry = plugin::registry::PluginRegistry; + +} // namespace qssc::payload::registry + +#endif diff --git a/lib/API/api.cpp b/lib/API/api.cpp index 8bfc2ce80..52e327e4d 100644 --- a/lib/API/api.cpp +++ b/lib/API/api.cpp @@ -20,7 +20,6 @@ #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/Dialect.h" #include "mlir/IR/MLIRContext.h" -#include "mlir/InitAllDialects.h" #include "mlir/InitAllPasses.h" #include "mlir/Parser.h" #include "mlir/Pass/PassManager.h" @@ -28,7 +27,6 @@ #include "mlir/Support/Timing.h" #include "llvm/ADT/Optional.h" -#include "llvm/MC/TargetRegistry.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" @@ -36,23 +34,17 @@ #include "llvm/Support/ToolOutputFile.h" #include "Payload/Payload.h" +#include "Payload/PayloadRegistry.h" #include "QSSC.h" #include "HAL/PassRegistration.h" #include "HAL/TargetSystem.h" #include "HAL/TargetSystemRegistry.h" -#include "Dialect/RegisterDialects.h" - -#include "Dialect/OQ3/IR/OQ3Dialect.h" - #include "Dialect/Pulse/IR/PulseDialect.h" #include "Dialect/Pulse/Transforms/Passes.h" - -#include "Dialect/QUIR/IR/QUIRDialect.h" #include "Dialect/QUIR/Transforms/Passes.h" - -#include "Dialect/QCS/IR/QCSDialect.h" +#include "Dialect/RegisterDialects.h" #include "Frontend/OpenQASM3/OpenQASM3Frontend.h" @@ -135,6 +127,11 @@ static llvm::cl::opt llvm::cl::desc("Print the list of registered targets"), llvm::cl::init(false), llvm::cl::cat(qsscCat)); +static llvm::cl::opt + showPayloads("show-payloads", + llvm::cl::desc("Print the list of registered payloads"), + llvm::cl::init(false), llvm::cl::cat(qsscCat)); + static llvm::cl::opt plaintextPayload("plaintext-payload", llvm::cl::desc("Write the payload in plaintext"), @@ -374,6 +371,17 @@ compile_(int argc, char const **argv, std::string *outputString, return llvm::Error::success(); } + if (showPayloads) { + llvm::outs() << "Registered Payloads:\n"; + for (const auto &payload : + qssc::payload::registry::PayloadRegistry::registeredPlugins()) { + // Constants chosen empirically to align with --help. + // TODO: Select constants more intelligently. + qssc::plugin::registry::printHelpStr(payload.second, 2, 57); + } + return llvm::Error::success(); + } + if (auto err = determineInputType()) return err; @@ -426,12 +434,18 @@ compile_(int argc, char const **argv, std::string *outputString, if (emitAction == Action::GenQEM) { if (outputFilename == "-") { - payload = std::make_unique(); + auto payloadInfo = + qssc::payload::registry::PayloadRegistry::lookupPluginInfo("ZIP"); + payload = std::move( + payloadInfo.getValue()->createPluginInstance(llvm::None).get()); } else { - std::filesystem::path payloadPath(outputFilename.c_str()); - std::string fNamePrefix = payloadPath.stem(); + const std::filesystem::path payloadPath(outputFilename.c_str()); + const std::string fNamePrefix = payloadPath.stem(); + const qssc::payload::PayloadConfig config{fNamePrefix, fNamePrefix}; + auto payloadInfo = + qssc::payload::registry::PayloadRegistry::lookupPluginInfo("ZIP"); payload = - std::make_unique(fNamePrefix, fNamePrefix); + std::move(payloadInfo.getValue()->createPluginInstance(config).get()); } } if (outputString) { diff --git a/lib/Payload/CMakeLists.txt b/lib/Payload/CMakeLists.txt index 6a520d806..208edef96 100644 --- a/lib/Payload/CMakeLists.txt +++ b/lib/Payload/CMakeLists.txt @@ -10,13 +10,34 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -ADD_LIBRARY(QSSCPayload Payload.cpp) -include_directories(${PROJECT_SOURCE_DIR}/include) +add_subdirectory(ZipPayload) -get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) -get_property(conversion_libs GLOBAL PROPERTY MLIR_CONVERSION_LIBS) -set(LIBS - libzip::zip - ) +# Register payloads with build system +foreach(payload_path ${QSSC_PAYLOAD_PATHS}) + message(STATUS "Adding QSS Payload directory: ${payload_path}") + add_subdirectory(${payload_path} ${CMAKE_CURRENT_BINARY_DIR}/Payloads/${payload_path}) +endforeach() -target_link_libraries(QSSCPayload PRIVATE ${LIBS}) +# Write Payloads.inc file for registration +get_property(reg_headers GLOBAL PROPERTY QSSC_PAYLOAD_REGISTRATION_HEADERS) +foreach(REG_HEADER ${reg_headers}) + set(PAYLOAD_REGISTRATION_INCLUDE_SECTION "${PAYLOAD_REGISTRATION_INCLUDE_SECTION}#include \"${REG_HEADER}\"\n") +endforeach() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Payloads.inc.in ${CMAKE_CURRENT_BINARY_DIR}/Payloads.inc) +unset(PAYLOAD_REGISTRATION_INCLUDE_SECTION) + +# Add QSSCHAL lib +get_property(qssc_payloads GLOBAL PROPERTY QSSC_PAYLOADS) +qssc_add_library(QSSCPayload + Payload.cpp + + ADDITIONAL_HEADER_DIRS + ${QSSC_INCLUDE_DIR}/Payload + + LINK_LIBS + ${qssc_payloads} +) + +# Add include directory to pick up generated Payloads.inc +target_include_directories(QSSCPayload PRIVATE ${QSSC_BINARY_DIR}/lib/Payload/) diff --git a/lib/Payload/Payload.cpp b/lib/Payload/Payload.cpp index 5c3d1e6d1..d429a2b4d 100644 --- a/lib/Payload/Payload.cpp +++ b/lib/Payload/Payload.cpp @@ -19,235 +19,40 @@ //===----------------------------------------------------------------------===// #include -#include #include #include -#include #include #include "nlohmann/json.hpp" -#include -#include "Config.h" #include "Payload/Payload.h" +// Inject static initialization headers from payloads. We need to include them +// in a translation unit that is not being optimized (removed) by the compiler. +#include "Payloads.inc" + using namespace qssc::payload; namespace fs = std::filesystem; auto Payload::getFile(const std::string &fName) -> std::string * { - std::lock_guard lock(_mtx); - std::string key = prefix + fName; + const std::lock_guard lock(_mtx); + const std::string key = prefix + fName; files.try_emplace(key); return &files[key]; } auto Payload::getFile(const char *fName) -> std::string * { - std::lock_guard lock(_mtx); - std::string key = prefix + fName; + const std::lock_guard lock(_mtx); + const std::string key = prefix + fName; files.try_emplace(key); return &files[key]; } auto Payload::orderedFileNames() -> std::vector { - std::lock_guard lock(_mtx); + const std::lock_guard lock(_mtx); std::vector ret; for (auto &filePair : files) ret.emplace_back(filePair.first); std::sort(ret.begin(), ret.end()); return ret; } - -// creates a manifest json file and adds it to the file map -void ZipPayload::addManifest() { - std::lock_guard lock(_mtx); - std::string manifest_fname = "manifest/manifest.json"; - nlohmann::json manifest; - manifest["version"] = QSSC_VERSION; - manifest["contents_path"] = prefix; - files[manifest_fname] = manifest.dump() + "\n"; -} - -void ZipPayload::writePlain(const std::string &dirName) { - std::lock_guard lock(_mtx); - for (const auto &filePair : files) { - fs::path fName(dirName); - fName /= filePair.first; - - fs::create_directories(fName.parent_path()); - std::ofstream fStream(fName, std::ofstream::out); - if (fStream.fail() || !fStream.good()) { - llvm::errs() << "Unable to open output file " << fName << "\n"; - continue; - } - fStream << filePair.second; - fStream.close(); - } -} - -void ZipPayload::writePlain(llvm::raw_ostream &stream) { - std::vector orderedNames = orderedFileNames(); - stream << "------------------------------------------\n"; - stream << "Plaintext payload: " << prefix << "\n"; - stream << "------------------------------------------\n"; - stream << "Manifest:\n"; - for (auto &fName : orderedNames) - stream << fName << "\n"; - stream << "------------------------------------------\n"; - for (auto &fName : orderedNames) { - stream << "File: " << fName << "\n"; - stream << files[fName]; - if (*(files[fName].rbegin()) != '\n') - stream << "\n"; - stream << "------------------------------------------\n"; - } -} - -void ZipPayload::writePlain(std::ostream &stream) { - llvm::raw_os_ostream llstream(stream); - writePlain(llstream); -} - -namespace { -void setFilePermissions(zip_int64_t fileIndex, fs::path &fName, - zip_t *new_archive) { - zip_uint8_t opsys; - zip_uint32_t attributes; - zip_file_get_external_attributes(new_archive, fileIndex, 0, &opsys, - &attributes); - if (opsys == ZIP_OPSYS_UNIX) { - zip_uint32_t mask = UINT32_MAX; // all 1s for negative mask - mask ^= (S_IWGRP << 16); // turn off write for the group - mask ^= (S_IWOTH << 16); // turn off write for others - - // apply negative write mask - attributes &= mask; - - // if executable turn on S_IXUSR - if (fName.has_extension() && fName.extension() == ".sh") - attributes |= (S_IXUSR << 16); // turn on execute for user - - // set new attributes - zip_file_set_external_attributes(new_archive, fileIndex, 0, opsys, - attributes); - } -} -} // end anonymous namespace - -void ZipPayload::writeZip(llvm::raw_ostream &stream) { - llvm::outs() << "Writing zip to stream\n"; - // first add the manifest - addManifest(); - - // zip archive stuff - zip_source_t *new_archive_src; - zip_source_t *file_src; - zip_t *new_archive; - zip_error_t error; - - //===---- Initialize archive ----===// - zip_error_init(&error); - - // open a zip source, buffer is allocated internally to libzip - if ((new_archive_src = zip_source_buffer_create(nullptr, 0, 0, &error)) == - nullptr) { - llvm::errs() << "Can't create zip source for new archive: " - << zip_error_strerror(&error) << "\n"; - zip_error_fini(&error); - return; - } - - // make sure the new source buffer stays around after closing the archive - zip_source_keep(new_archive_src); - - // create and open an archive from the new archive source - if ((new_archive = zip_open_from_source(new_archive_src, ZIP_TRUNCATE, - &error)) == nullptr) { - llvm::errs() << "Can't create/open an archive from the new archive source: " - << zip_error_strerror(&error) << "\n"; - zip_source_free(new_archive_src); - zip_error_fini(&error); - return; - } - zip_error_fini(&error); - - llvm::outs() << "Zip buffer created, adding files to archive\n"; - // archive is now allocated and created, need to fill it with files/data - std::vector orderedNames = orderedFileNames(); - for (auto &fName : orderedNames) { - llvm::outs() << "Adding file " << fName << " to archive buffer (" - << files[fName].size() << " bytes)\n"; - - //===---- Add file ----===// - // init the error object - zip_error_init(&error); - - // first create a zip source from the file data - if ((file_src = zip_source_buffer_create(files[fName].c_str(), - files[fName].size(), 0, &error)) == - nullptr) { - llvm::errs() << "Can't create zip source for " << fName << " : " - << zip_error_strerror(&error) << "\n"; - zip_error_fini(&error); - continue; - } - zip_error_fini(&error); - - // now add it to the archive - zip_int64_t fileIndex = -1; - if ((fileIndex = zip_file_add(new_archive, fName.c_str(), file_src, - ZIP_FL_OVERWRITE)) < 0) { - llvm::errs() << "Problem adding file " << fName - << " to archive: " << zip_strerror(new_archive) << "\n"; - continue; - } - - setFilePermissions(fileIndex, fName, new_archive); - } - - //===---- Shutdown archive ----===// - // shutdown the archive, write central directory - if (zip_close(new_archive) < 0) { - llvm::errs() << "Problem closing new zip archive: " - << zip_strerror(new_archive) << "\n"; - return; - } - - //===---- Repen for copying ----===// - // reopen the archive stored in the new_archive_src - zip_source_open(new_archive_src); - // seek to the end of the archive - zip_source_seek(new_archive_src, 0, SEEK_END); - // get the number of bytes - zip_int64_t sz = zip_source_tell(new_archive_src); - llvm::outs() << "Zip buffer is of size " << sz << " bytes\n"; - - // allocate a new buffer to copy the archive into - char *outbuffer = (char *)malloc(sz); - if (!outbuffer) { - llvm::errs() - << "Unable to allocate output buffer for writing zip to stream\n"; - zip_source_close(new_archive_src); - return; - } - - // seek back to the begining of the archive - zip_source_seek(new_archive_src, 0, SEEK_SET); - // copy the entire archive into the output bufffer - zip_source_read(new_archive_src, outbuffer, sz); - // all done - zip_source_close(new_archive_src); - - // output the new archive to the stream - stream.write(outbuffer, sz); - stream.flush(); - free(outbuffer); -} - -void ZipPayload::writeZip(std::ostream &stream) { - llvm::raw_os_ostream llstream(stream); - writeZip(llstream); -} - -void ZipPayload::write(llvm::raw_ostream &stream) { writeZip(stream); } - -void ZipPayload::write(std::ostream &stream) { writeZip(stream); } diff --git a/lib/Payload/Payloads.inc.in b/lib/Payload/Payloads.inc.in new file mode 100644 index 000000000..b69eae431 --- /dev/null +++ b/lib/Payload/Payloads.inc.in @@ -0,0 +1,17 @@ +//===- Payloads.inc - Payload Registry -------------------------*- C++ -*-===// +// +// (C) Copyright IBM 2023. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. +// +//===----------------------------------------------------------------------===// +/// +/// !! This file is generated by CMake configure. !! +/// +/// It includes (quite literally) the registration headers of all QSS targets +/// specified to CMake generate. +/// +//===----------------------------------------------------------------------===// +@PAYLOAD_REGISTRATION_INCLUDE_SECTION@ diff --git a/lib/Payload/ZipPayload/CMakeLists.txt b/lib/Payload/ZipPayload/CMakeLists.txt new file mode 100644 index 000000000..717260d0e --- /dev/null +++ b/lib/Payload/ZipPayload/CMakeLists.txt @@ -0,0 +1,31 @@ +# (C) Copyright IBM 2023. +# +# This code is part of Qiskit. +# +# This code is licensed under the Apache License, Version 2.0 with LLVM +# Exceptions. You may obtain a copy of this license in the LICENSE.txt +# file in the root directory of this source tree. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +set(QSSC_PAYLOAD_PATHS + ${QSSC_PAYLOAD_PATHS} + ${CMAKE_CURRENT_SOURCE_DIR} + ) + +qssc_add_plugin(QSSCPayloadZip QSSC_PAYLOAD_PLUGIN + ZipPayload.cpp + + ADDITIONAL_HEADER_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${QSSC_INCLUDE_DIR}/Payload + + LINK_LIBS + QSSCPayload + libzip::zip + + PLUGIN_REGISTRATION_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/Payload.inc + ) diff --git a/lib/Payload/ZipPayload/Payload.inc b/lib/Payload/ZipPayload/Payload.inc new file mode 100644 index 000000000..677e6ee22 --- /dev/null +++ b/lib/Payload/ZipPayload/Payload.inc @@ -0,0 +1,33 @@ +//===- Payload.inc - Mock payload registration ------------------*- C++ -*-===// +// +// (C) Copyright IBM 2023. +// +// This code is part of Qiskit. +// +// This code is licensed under the Apache License, Version 2.0 with LLVM +// Exceptions. You may obtain a copy of this license in the LICENSE.txt +// file in the root directory of this source tree. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. +// +//===----------------------------------------------------------------------===// +/// +/// This file defines static objects that register payloads +/// with the QSS compiler core. +/// +//===----------------------------------------------------------------------===// + +#ifndef PAYLOAD_PAYLOAD_ZIPPAYLOAD_H +#define PAYLOAD_PAYLOAD_ZIPPAYLOAD_H + +#include "ZipPayload.h" + +namespace qssc::payload { + +[[maybe_unused]] int registrar = init(); + +} // namespace qssc::payload + +#endif // PAYLOAD_PAYLOAD_ZIPPAYLOAD_H diff --git a/lib/Payload/ZipPayload/ZipPayload.cpp b/lib/Payload/ZipPayload/ZipPayload.cpp new file mode 100644 index 000000000..1f0572e28 --- /dev/null +++ b/lib/Payload/ZipPayload/ZipPayload.cpp @@ -0,0 +1,245 @@ +//===- ZipPayload.cpp -------------------------------------------*- C++ -*-===// +// +// (C) Copyright IBM 2023. +// +// This code is part of Qiskit. +// +// This code is licensed under the Apache License, Version 2.0 with LLVM +// Exceptions. You may obtain a copy of this license in the LICENSE.txt +// file in the root directory of this source tree. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. +// +//===----------------------------------------------------------------------===// +/// +/// Implements the ZipPayload class +/// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include + +#include "Config.h" +#include "ZipPayload.h" + +#include "Payload/PayloadRegistry.h" + +using namespace qssc::payload; +namespace fs = std::filesystem; + +int qssc::payload::init() { + const char *name = "ZIP"; + bool registered = registry::PayloadRegistry::registerPlugin( + name, name, "Payload that generates zip file with .qem extension.", + [](llvm::Optional config) + -> llvm::Expected> { + if (config.hasValue()) + return std::make_unique(config.getValue()); + return std::make_unique(); + }); + return registered ? 0 : -1; +} + +// creates a manifest json file and adds it to the file map +void ZipPayload::addManifest() { + std::lock_guard lock(_mtx); + std::string manifest_fname = "manifest/manifest.json"; + nlohmann::json manifest; + manifest["version"] = QSSC_VERSION; + manifest["contents_path"] = prefix; + files[manifest_fname] = manifest.dump() + "\n"; +} + +void ZipPayload::writePlain(const std::string &dirName) { + std::lock_guard lock(_mtx); + for (const auto &filePair : files) { + fs::path fName(dirName); + fName /= filePair.first; + + fs::create_directories(fName.parent_path()); + std::ofstream fStream(fName, std::ofstream::out); + if (fStream.fail() || !fStream.good()) { + llvm::errs() << "Unable to open output file " << fName << "\n"; + continue; + } + fStream << filePair.second; + fStream.close(); + } +} + +void ZipPayload::writePlain(llvm::raw_ostream &stream) { + std::vector orderedNames = orderedFileNames(); + stream << "------------------------------------------\n"; + stream << "Plaintext payload: " << prefix << "\n"; + stream << "------------------------------------------\n"; + stream << "Manifest:\n"; + for (auto &fName : orderedNames) + stream << fName << "\n"; + stream << "------------------------------------------\n"; + for (auto &fName : orderedNames) { + stream << "File: " << fName << "\n"; + stream << files[fName]; + if (*(files[fName].rbegin()) != '\n') + stream << "\n"; + stream << "------------------------------------------\n"; + } +} + +void ZipPayload::writePlain(std::ostream &stream) { + llvm::raw_os_ostream llstream(stream); + writePlain(llstream); +} + +namespace { +void setFilePermissions(zip_int64_t fileIndex, fs::path &fName, + zip_t *new_archive) { + zip_uint8_t opsys; + zip_uint32_t attributes; + zip_file_get_external_attributes(new_archive, fileIndex, 0, &opsys, + &attributes); + if (opsys == ZIP_OPSYS_UNIX) { + zip_uint32_t mask = UINT32_MAX; // all 1s for negative mask + mask ^= (S_IWGRP << 16); // turn off write for the group + mask ^= (S_IWOTH << 16); // turn off write for others + + // apply negative write mask + attributes &= mask; + + // if executable turn on S_IXUSR + if (fName.has_extension() && fName.extension() == ".sh") + attributes |= (S_IXUSR << 16); // turn on execute for user + + // set new attributes + zip_file_set_external_attributes(new_archive, fileIndex, 0, opsys, + attributes); + } +} +} // end anonymous namespace + +void ZipPayload::writeZip(llvm::raw_ostream &stream) { + llvm::outs() << "Writing zip to stream\n"; + // first add the manifest + addManifest(); + + // zip archive stuff + zip_source_t *new_archive_src; + zip_source_t *file_src; + zip_t *new_archive; + zip_error_t error; + + //===---- Initialize archive ----===// + zip_error_init(&error); + + // open a zip source, buffer is allocated internally to libzip + if ((new_archive_src = zip_source_buffer_create(nullptr, 0, 0, &error)) == + nullptr) { + llvm::errs() << "Can't create zip source for new archive: " + << zip_error_strerror(&error) << "\n"; + zip_error_fini(&error); + return; + } + + // make sure the new source buffer stays around after closing the archive + zip_source_keep(new_archive_src); + + // create and open an archive from the new archive source + if ((new_archive = zip_open_from_source(new_archive_src, ZIP_TRUNCATE, + &error)) == nullptr) { + llvm::errs() << "Can't create/open an archive from the new archive source: " + << zip_error_strerror(&error) << "\n"; + zip_source_free(new_archive_src); + zip_error_fini(&error); + return; + } + zip_error_fini(&error); + + llvm::outs() << "Zip buffer created, adding files to archive\n"; + // archive is now allocated and created, need to fill it with files/data + std::vector orderedNames = orderedFileNames(); + for (auto &fName : orderedNames) { + llvm::outs() << "Adding file " << fName << " to archive buffer (" + << files[fName].size() << " bytes)\n"; + + //===---- Add file ----===// + // init the error object + zip_error_init(&error); + + // first create a zip source from the file data + if ((file_src = zip_source_buffer_create(files[fName].c_str(), + files[fName].size(), 0, &error)) == + nullptr) { + llvm::errs() << "Can't create zip source for " << fName << " : " + << zip_error_strerror(&error) << "\n"; + zip_error_fini(&error); + continue; + } + zip_error_fini(&error); + + // now add it to the archive + zip_int64_t fileIndex = -1; + if ((fileIndex = zip_file_add(new_archive, fName.c_str(), file_src, + ZIP_FL_OVERWRITE)) < 0) { + llvm::errs() << "Problem adding file " << fName + << " to archive: " << zip_strerror(new_archive) << "\n"; + continue; + } + + setFilePermissions(fileIndex, fName, new_archive); + } + + //===---- Shutdown archive ----===// + // shutdown the archive, write central directory + if (zip_close(new_archive) < 0) { + llvm::errs() << "Problem closing new zip archive: " + << zip_strerror(new_archive) << "\n"; + return; + } + + //===---- Reopen for copying ----===// + // reopen the archive stored in the new_archive_src + zip_source_open(new_archive_src); + // seek to the end of the archive + zip_source_seek(new_archive_src, 0, SEEK_END); + // get the number of bytes + zip_int64_t sz = zip_source_tell(new_archive_src); + llvm::outs() << "Zip buffer is of size " << sz << " bytes\n"; + + // allocate a new buffer to copy the archive into + char *outbuffer = (char *)malloc(sz); + if (!outbuffer) { + llvm::errs() + << "Unable to allocate output buffer for writing zip to stream\n"; + zip_source_close(new_archive_src); + return; + } + + // seek back to the begining of the archive + zip_source_seek(new_archive_src, 0, SEEK_SET); + // copy the entire archive into the output bufffer + zip_source_read(new_archive_src, outbuffer, sz); + // all done + zip_source_close(new_archive_src); + + // output the new archive to the stream + stream.write(outbuffer, sz); + stream.flush(); + free(outbuffer); +} + +void ZipPayload::writeZip(std::ostream &stream) { + llvm::raw_os_ostream llstream(stream); + writeZip(llstream); +} + +void ZipPayload::write(llvm::raw_ostream &stream) { writeZip(stream); } + +void ZipPayload::write(std::ostream &stream) { writeZip(stream); } diff --git a/lib/Payload/ZipPayload/ZipPayload.h b/lib/Payload/ZipPayload/ZipPayload.h new file mode 100644 index 000000000..2050c672f --- /dev/null +++ b/lib/Payload/ZipPayload/ZipPayload.h @@ -0,0 +1,61 @@ +//===- ZipPayload.h ---------------------------------------------*- C++ -*-===// +// +// (C) Copyright IBM 2023. +// +// This code is part of Qiskit. +// +// This code is licensed under the Apache License, Version 2.0 with LLVM +// Exceptions. You may obtain a copy of this license in the LICENSE.txt +// file in the root directory of this source tree. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. +// +//===----------------------------------------------------------------------===// +/// +/// Declares the ZipPayload class +/// +//===----------------------------------------------------------------------===// + +#ifndef PAYLOAD_ZIPPAYLOAD_H +#define PAYLOAD_ZIPPAYLOAD_H + +#include "Payload/Payload.h" + +namespace qssc::payload { + +// Register the zip payload. +int init(); + +class ZipPayload : public Payload { +public: + ZipPayload() = default; + ZipPayload(PayloadConfig config) : Payload(std::move(config)) {} + virtual ~ZipPayload() = default; + + // write all files to the stream + virtual void write(llvm::raw_ostream &stream) override; + // write all files to the stream + virtual void write(std::ostream &stream) override; + // write all files in plaintext to the stream + virtual void writePlain(std::ostream &stream) override; + virtual void writePlain(llvm::raw_ostream &stream) override; + + // write all files to a zip archive named fName + void writeZip(std::string fName); + // write all files to a zip archive and output it to the stream + void writeZip(std::ostream &stream); + void writeZip(llvm::raw_ostream &stream); + // write all files in plaintext to the dir named dirName + void writePlain(const std::string &dirName = "."); + +private: + // creates a manifest json file + void addManifest(); + +}; // class ZipPayload + +} // namespace qssc::payload + +#endif // PAYLOAD_ZIPPAYLOAD_H diff --git a/mock_target/MockTarget.cpp b/mock_target/MockTarget.cpp index aaca36bfc..424acb4cb 100644 --- a/mock_target/MockTarget.cpp +++ b/mock_target/MockTarget.cpp @@ -66,7 +66,7 @@ static llvm::cl::OptionCategory int qssc::targets::mock::init() { bool registered = registry::TargetSystemRegistry::registerPlugin( - "mock", "Mock system for testing the targetting infrastructure.", + "mock", "Mock system for testing the targeting infrastructure.", [](llvm::Optional configurationPath) -> llvm::Expected> { if (!configurationPath) diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index 85548ffa3..f2b6de379 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -17,18 +17,24 @@ package_add_test_with_libs(unittest-quir-dialect QSSCLib ) +set(TEST_FILES + Payload/PayloadRegistryTest.cpp + ) + if (QSSC_WITH_MOCK_TARGET) - package_add_test_with_libs(unittest-qss-compiler + set(TEST_FILES HAL/TargetSystemRegistryTest.cpp - # add other source files with test cases here - - LIBRARIES - QSSCLib + ${CMAKE_CURRENT_SOURCE_DIR} ) -else() - message(WARNING "Unit testing is enabled, but QSSC_WITH_MOCK_TARGET is false. QSS compiler unit tests will be skipped.") endif () +package_add_test_with_libs(unittest-qss-compiler + ${TEST_FILES} + + LIBRARIES + QSSCLib + ) + add_custom_target(run-qss-compiler-unittests COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS ${QSSC_UNITTESTS} diff --git a/test/unittest/Payload/PayloadRegistryTest.cpp b/test/unittest/Payload/PayloadRegistryTest.cpp new file mode 100644 index 000000000..cc74ab334 --- /dev/null +++ b/test/unittest/Payload/PayloadRegistryTest.cpp @@ -0,0 +1,39 @@ +//===- PayloadRegistryTest.cpp ----------------------------------*- C++ -*-===// +// +// (C) Copyright IBM 2023. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file implements test cases for PayloadRegistry. +/// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +#include "Payload/PayloadRegistry.h" + +namespace { + +TEST(PayloadRegistry, LookupZipPayload) { + // As a compiler developer, I want to register and lookup payloads by name. + + const char *zipName = "ZIP"; + + EXPECT_TRUE(qssc::payload::registry::PayloadRegistry::pluginExists(zipName)); + + auto payloadInfoOpt = + qssc::payload::registry::PayloadRegistry::lookupPluginInfo(zipName); + EXPECT_TRUE(payloadInfoOpt.hasValue()); + + auto *payloadInfo = payloadInfoOpt.getValue(); + + ASSERT_NE(payloadInfo, nullptr); + EXPECT_EQ(payloadInfo->getName(), zipName); +} + +} // anonymous namespace diff --git a/tools/qss-opt/qss-opt.cpp b/tools/qss-opt/qss-opt.cpp index 7a7a92811..5df9d865e 100644 --- a/tools/qss-opt/qss-opt.cpp +++ b/tools/qss-opt/qss-opt.cpp @@ -25,7 +25,6 @@ #include "mlir/IR/MLIRContext.h" #include "mlir/InitAllDialects.h" #include "mlir/InitAllPasses.h" -#include "mlir/Pass/Pass.h" #include "mlir/Pass/PassManager.h" #include "mlir/Support/FileUtilities.h" #include "mlir/Support/MlirOptMain.h" @@ -36,11 +35,11 @@ #include "llvm/Support/SourceMgr.h" #include "llvm/Support/ToolOutputFile.h" -#include "QSSC.h" - #include "HAL/PassRegistration.h" #include "HAL/TargetSystemRegistry.h" +#include "Payload/PayloadRegistry.h" + #include "Dialect/OQ3/IR/OQ3Dialect.h" #include "Dialect/Pulse/IR/PulseDialect.h" #include "Dialect/Pulse/Transforms/Passes.h" @@ -145,6 +144,13 @@ auto main(int argc, char **argv) -> int { os << target.second.getName() << " - " << target.second.getDescription() << "\n"; } + + os << "\nAvailable Payloads:\n"; + for (const auto &payload : + qssc::payload::registry::PayloadRegistry::registeredPlugins()) { + os << payload.second.getName() << " - " << payload.second.getDescription() + << "\n"; + } } // Parse pass names in main to ensure static initialization completed.