diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d9640026..a8ca55552 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,7 @@ set(QSSC_RESOURCES_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/resourc set(QSSC_PLUGIN_EXT ${CMAKE_SHARED_LIBRARY_SUFFIX}) option(QSSC_WITH_MOCK_TARGET "Available targets include the built-in mock target" ON) +option(QSSC_WITH_AER_SIMULATOR_TARGET "Available targets include the built-in aer-simulator target" ON) set(QSSC_TARGET_DIRS "" CACHE STRING @@ -155,6 +156,17 @@ if(QSSC_WITH_MOCK_TARGET) ) endif() +if(QSSC_WITH_AER_SIMULATOR_TARGET) + set(QSSC_TARGET_DIRS + ${QSSC_TARGET_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/targets/simulators/aer + ) + set(QSSC_TARGET_TEST_DIRS + ${QSSC_TARGET_TEST_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/targets/simulators/aer/test + ) +endif() + set(QSSC_PAYLOAD_PATHS "" CACHE STRING diff --git a/targets/simulators/aer/AerSimulator.cpp b/targets/simulators/aer/AerSimulator.cpp new file mode 100644 index 000000000..e1432352f --- /dev/null +++ b/targets/simulators/aer/AerSimulator.cpp @@ -0,0 +1,364 @@ +//===- AerSimulator.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. +// +//===----------------------------------------------------------------------===// + +#include "AerSimulator.h" + +#include + +#include "Conversion/QUIRToAer.h" +#include "Conversion/QUIRToLLVM/QUIRToLLVM.h" +#include "Transforms/OutputClassicalRegisters.h" + +#include "Dialect/QUIR/Transforms/Passes.h" +#include "HAL/TargetSystemRegistry.h" +#include "Payload/Payload.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/IR/Module.h" +#include "llvm/MC/SubtargetFeature.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Target/TargetMachine.h" + +#include "mlir/Conversion/Passes.h" +#include "mlir/Dialect/LLVMIR/Transforms/Passes.h" +#include "mlir/ExecutionEngine/OptUtils.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/PassManager.h" +#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h" +#include "mlir/Target/LLVMIR/Export.h" + +#include +#include + +using namespace mlir; +using namespace mlir::quir; + +using namespace qssc::hal; +using namespace qssc::targets::simulators::aer; + +using json = nlohmann::json; + +namespace qssc::targets::simulators::aer { + +int init() { + bool registered = + registry::TargetSystemRegistry::registerPlugin( + "aer-simulator", + "Quantum simulator using qiskit Aer for quantum programs in " + "OpenQASM3/QUIR", + [](llvm::Optional configurationPath) + -> llvm::Expected> { + if (!configurationPath) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Configuration file must be specified.\n"); + + auto config = + std::make_unique(*configurationPath); + return std::make_unique(std::move(config)); + }); + return registered ? 0 : -1; +} + +const char *toStringInAer(SimulationMethod method) { + switch (method) { + case SimulationMethod::STATEVECTOR: + return "statevector"; + case SimulationMethod::DENSITY_MATRIX: + return "density_matrix"; + case SimulationMethod::MPS: + return "matrix_product_state"; + case SimulationMethod::STABILIZER: + return "stabilizer"; + case SimulationMethod::EXTENDED_STABILIZER: + return "extended_stabilizer"; + case SimulationMethod::UNITARY: + return "unitary"; + case SimulationMethod::SUPEROP: + return "superop"; + } + + assert(false && "Invalid simulation method"); + return ""; +} + +const char *toStringInAer(SimulationDevice device) { + switch (device) { + case SimulationDevice::CPU: + return "CPU"; + case SimulationDevice::GPU: + return "GPU"; + case SimulationDevice::THRUST_CPU: + return "ThrustCPU"; + } + + assert(false && "Invalid simulation device"); + return ""; +} + +const char *toStringInAer(SimulationPrecision precision) { + switch (precision) { + case SimulationPrecision::DOUBLE: + return "double"; + } + + assert(false && "Invalid simulation precision"); + return ""; +} + +} // namespace qssc::targets::simulators::aer + +const std::vector AerSimulator::childNames = {}; + +AerSimulatorConfig::AerSimulatorConfig(llvm::StringRef configurationPath) + : SystemConfiguration(), method(SimulationMethod::STATEVECTOR), + device(SimulationDevice::CPU), precision(SimulationPrecision::DOUBLE) { + std::ifstream cfgFile(configurationPath.data()); + if (!cfgFile) { + llvm::errs() << "Failed to open the configuration file: "; + llvm::errs() << configurationPath; + return; + } + + json cfg; + cfgFile >> cfg; + if (cfg.contains("method")) { + const auto cfgMethod = cfg["method"]; + if (cfgMethod == "statevector") + method = SimulationMethod::STATEVECTOR; + else if (cfgMethod == "density_matrix") + method = SimulationMethod::DENSITY_MATRIX; + else if (cfgMethod == "MPS") + method = SimulationMethod::MPS; + else if (cfgMethod == "stabilizer") + method = SimulationMethod::STABILIZER; + else if (cfgMethod == "extended_stabilizer") + method = SimulationMethod::EXTENDED_STABILIZER; + else if (cfgMethod == "unitary") + method = SimulationMethod::UNITARY; + else if (cfgMethod == "superop") + method = SimulationMethod::SUPEROP; + else { + llvm::errs() << "Unsupported Aer simulation method: " << cfgMethod.dump(); + llvm::errs() << ". Use default value.\n"; + } + } + if (cfg.contains("device")) { + const auto cfgDevice = cfg["device"]; + if (cfgDevice == "cpu" || cfgDevice == "CPU") + device = SimulationDevice::CPU; + else if (cfgDevice == "gpu" || cfgDevice == "GPU") + device = SimulationDevice::GPU; + else if (cfgDevice == "thrust_cpu") + device = SimulationDevice::THRUST_CPU; + else { + llvm::errs() << "Unsupported Aer simulation device: " << cfgDevice.dump(); + llvm::errs() << ". Use default value.\n"; + } + } + if (cfg.contains("precision")) { + const auto cfgPrecision = cfg["precision"]; + if (cfgPrecision == "double") + precision = SimulationPrecision::DOUBLE; + else { + llvm::errs() << "Unsupported Aer simulation precision: " + << cfgPrecision.dump(); + llvm::errs() << ". Use default value.\n"; + } + } +} // SimulatorConfig + +AerSimulator::AerSimulator(std::unique_ptr config) + : TargetSystem("AerSimulator", nullptr), + simulatorConfig(std::move(config)) {} // AerSimulator + +llvm::Error AerSimulator::registerTargetPasses() { + mlir::PassRegistration(); + mlir::PassRegistration(); + + return llvm::Error::success(); +} // AerSimulator::registerTargetPasses + +namespace { +void simulatorPipelineBuilder(mlir::OpPassManager &pm) { + pm.addPass(mlir::createCanonicalizerPass()); + pm.addPass(std::make_unique()); + // `OutputCRegsPass` must be applied before `VariableEliminationPass`. + // It inserts classical `oq3` instructions for printing the values + // of classical registers. These instructions will be converted into + // standard ops by `VariableEliminationPass`. + pm.addPass(std::make_unique()); + pm.addPass(std::make_unique(false)); + pm.addPass(std::make_unique()); +} // simulatorPipelineBuilder +} // anonymous namespace + +llvm::Error AerSimulator::registerTargetPipelines() { + mlir::PassPipelineRegistration<> pipeline( + "aer-simulator-conversion", "Run Aer simulator specific conversions", + simulatorPipelineBuilder); + + return llvm::Error::success(); +} // AerSimulator::registerTargetPipelines + +llvm::Error AerSimulator::addPayloadPasses(mlir::PassManager &pm) { + if (payloadPassesFound(pm)) { + // command line specified payload conversion, + // let the user handle exactly what to add + return llvm::Error::success(); + } + + simulatorPipelineBuilder(pm); + + return llvm::Error::success(); +} // AerSimulator::addPayloadPasses + +bool AerSimulator::payloadPassesFound(mlir::PassManager &pm) { + for (auto &pass : pm.getPasses()) + if (pass.getName() == + "qssc::targets::simulator::aer::conversion::QUIRToAerPass") + return true; + return false; +} // AerSimulator::payloadPassesFound + +llvm::Error AerSimulator::addToPayload(mlir::ModuleOp &moduleOp, + qssc::payload::Payload &payload) { + return buildLLVMPayload(moduleOp, payload); +} // AerSimulator::addToPayload + +llvm::Error AerSimulator::buildLLVMPayload(mlir::ModuleOp &moduleOp, + payload::Payload &payload) { + + auto *context = moduleOp.getContext(); + assert(context); + + // Register LLVM dialect and all infrastructure required for translation to + // LLVM IR + mlir::registerLLVMDialectTranslation(*context); + + mlir::PassManager pm(context); + // Apply any generic pass manager command line options and run the pipeline. + mlir::applyPassManagerCLOptions(pm); + mlir::applyDefaultTimingPassManagerCLOptions(pm); + + pm.addPass(mlir::createLowerToLLVMPass()); + pm.addPass(mlir::LLVM::createLegalizeForExportPass()); + if (failed(pm.run(moduleOp))) { + return llvm::make_error( + "Problems converting `Simulator` module to AER!\n", + llvm::inconvertibleErrorCode()); + } + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmParser(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeAllTargetMCs(); + + // Setup the machine properties for the target architecture. + // TODO: In future, it would be better to make this configurable + std::string targetTriple = llvm::sys::getDefaultTargetTriple(); + std::string errorMessage; + const auto *target = + llvm::TargetRegistry::lookupTarget(targetTriple, errorMessage); + if (!target) { + return llvm::make_error( + "Unable to find target: " + errorMessage + "\n", + llvm::inconvertibleErrorCode()); + } + + std::string cpu("generic"); + llvm::SubtargetFeatures features; + std::unique_ptr machine(target->createTargetMachine( + targetTriple, cpu, features.getString(), {}, {})); + auto dataLayout = machine->createDataLayout(); + + if (auto err = quir::translateModuleToLLVMDialect(moduleOp, dataLayout)) + return err; + + // Build LLVM payload + llvm::LLVMContext llvmContext; + std::unique_ptr llvmModule = + mlir::translateModuleToLLVMIR(moduleOp, llvmContext); + if (!llvmModule) { + std::string msg; + llvm::raw_string_ostream os(msg); + os << "Error converting LLVM module to LLVM IR!\n"; + os << moduleOp << "\n"; + return llvm::make_error(msg, + llvm::inconvertibleErrorCode()); + } + + llvmModule->setDataLayout(dataLayout); + llvmModule->setTargetTriple(targetTriple); + + // Optionally run an optimization pipeline over the llvm module. + auto optPipeline = mlir::makeOptimizingTransformer(0, 0, nullptr); + if (auto err = optPipeline(llvmModule.get())) { + std::string msg; + llvm::raw_string_ostream os(msg); + os << "Failed to optimize LLVM IR: " << err << "\n"; + return llvm::make_error(msg, + llvm::inconvertibleErrorCode()); + } + + llvm::SmallString<128> objPath; + int objFd; + if (auto err = llvm::sys::fs::createTemporaryFile("simulatorModule", "o", + objFd, objPath)) { + return llvm::make_error( + "Failed to create temporary object file for simulator module.\n", + llvm::inconvertibleErrorCode()); + } + auto obj = std::make_unique(objPath, objFd); + llvm::legacy::PassManager pass; + if (machine->addPassesToEmitFile(pass, obj->os(), nullptr, + llvm::CodeGenFileType::CGFT_ObjectFile)) { + return llvm::make_error( + "Cannot emit object files with TargetMachine.\n", + llvm::inconvertibleErrorCode()); + } + pass.run(*llvmModule); + obj->os().flush(); + + // TODO: In future, we need to link the generated object file + // with `libaer.{so, dylib}` to create a executable file here. + // An external linker (e.g. ld) may have to be called and also + // we have to specify the path to the linker and the shared library. + + std::ifstream binary(objPath.c_str(), std::ios_base::binary); + if (!binary) { + return llvm::make_error( + "Failed to open generated object file.", + llvm::inconvertibleErrorCode()); + } + + std::string binaryContents{std::istreambuf_iterator(binary), + std::istreambuf_iterator()}; + + payload.getFile("simulator.bin")->assign(std::move(binaryContents)); + + return llvm::Error::success(); +} // AerSimulator::buildLLVMPayload diff --git a/targets/simulators/aer/AerSimulator.h b/targets/simulators/aer/AerSimulator.h new file mode 100644 index 000000000..b4cec9c07 --- /dev/null +++ b/targets/simulators/aer/AerSimulator.h @@ -0,0 +1,101 @@ +//===- AerSimulator.h - Simulator target info -------------------*- 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 declares the classes for the a Simulator target interface +// +//===----------------------------------------------------------------------===// +#ifndef HAL_TARGETS_AER_SIMULATOR_H +#define HAL_TARGETS_AER_SIMULATOR_H + +#include "HAL/SystemConfiguration.h" +#include "HAL/TargetSystem.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#include +#include + +namespace qssc::targets::simulators::aer { + +// Register the Aer simulator target. +int init(); + +// "method" : +enum class SimulationMethod { + STATEVECTOR, // "statevector" + DENSITY_MATRIX, // "density_matrix" + MPS, // "MPS" + STABILIZER, // "stabilizer" + EXTENDED_STABILIZER, // "extended_stabilizer" + UNITARY, // "unitary" + SUPEROP, // "superop" +}; + +// "device" : +enum class SimulationDevice { + CPU, // "cpu" + GPU, // "gpu" + THRUST_CPU, // "thrust_cpu" +}; + +// "precision" : +enum class SimulationPrecision { + DOUBLE, // "double" +}; + +const char *toStringInAer(SimulationMethod method); +const char *toStringInAer(SimulationDevice device); +const char *toStringInAer(SimulationPrecision precision); + +class AerSimulatorConfig : public qssc::hal::SystemConfiguration { +public: + explicit AerSimulatorConfig(llvm::StringRef configurationPath); + + SimulationMethod getMethod() const { return method; } + SimulationDevice getDevice() const { return device; } + SimulationPrecision getPrecision() const { return precision; } + +private: + SimulationMethod method; + SimulationDevice device; + SimulationPrecision precision; +}; // class AerSimulatorConfig + +class AerSimulator : public qssc::hal::TargetSystem { +public: + static constexpr auto name = "aer-simulator"; + static const std::vector childNames; + explicit AerSimulator(std::unique_ptr config); + static llvm::Error registerTargetPasses(); + static llvm::Error registerTargetPipelines(); + llvm::Error addPayloadPasses(mlir::PassManager &pm) override; + auto payloadPassesFound(mlir::PassManager &pm) -> bool; + llvm::Error addToPayload(mlir::ModuleOp &moduleOp, + payload::Payload &payload) override; + auto getConfig() -> AerSimulatorConfig & { return *simulatorConfig; } + +private: + std::unique_ptr simulatorConfig; + +private: + llvm::Error buildLLVMPayload(mlir::ModuleOp &moduleOp, + payload::Payload &payload); +}; // class AerSimulatorSystem + +} // namespace qssc::targets::simulators::aer + +#endif // HAL_TARGETS_AER_SIMULATOR_H diff --git a/targets/simulators/aer/CMakeLists.txt b/targets/simulators/aer/CMakeLists.txt new file mode 100644 index 000000000..f5dda0e1f --- /dev/null +++ b/targets/simulators/aer/CMakeLists.txt @@ -0,0 +1,59 @@ +# (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. + +get_property(qssc_api_libs GLOBAL PROPERTY QSSC_API_LIBS) + +set(llvm_code_gen_libraries + LLVMPowerPCCodeGen + LLVMPowerPCInfo + LLVMPowerPCAsmParser + LLVMPowerPCDesc + LLVMX86CodeGen + LLVMX86Info + LLVMX86AsmParser + LLVMX86Desc + LLVMX86TargetMCA +) + +# Add AArch64 libraries if present in LLVM build. +if ("LLVMAArch64CodeGen" IN_LIST LLVM_AVAILABLE_LIBS) + list(APPEND llvm_code_gen_libraries + LLVMAArch64CodeGen + LLVMAArch64Info + LLVMAArch64AsmParser + LLVMAArch64Desc + ) + message(STATUS "Adding AArch64 libraries to llvm_code_gen_libraries") +endif () + +qssc_add_plugin(QSSCTargetAerSimulator QSSC_TARGET_PLUGIN +Conversion/QUIRToAer.cpp +Conversion/TypeConversion.cpp +Transforms/OutputClassicalRegisters.cpp +AerSimulator.cpp + +ADDITIONAL_HEADER_DIRS +${CMAKE_CURRENT_SOURCE_DIR} +${QSSC_INCLUDE_DIR}/HAL + +LINK_LIBS +${qssc_api_libs} +QSSCHAL +MLIRExecutionEngine +MLIROptLib +MLIRLLVMIR +MLIRLLVMToLLVMIRTranslation +MLIRStandardOpsTransforms +${llvm_code_gen_libraries} +PLUGIN_REGISTRATION_HEADERS +${CMAKE_CURRENT_SOURCE_DIR}/Target.inc +) diff --git a/targets/simulators/aer/Conversion/QUIRToAer.cpp b/targets/simulators/aer/Conversion/QUIRToAer.cpp new file mode 100644 index 000000000..d036311ac --- /dev/null +++ b/targets/simulators/aer/Conversion/QUIRToAer.cpp @@ -0,0 +1,553 @@ +//===- QUIRToAER.cpp - Convert QUIR to AER --------------*- 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 implements passes for converting QUIR to AER +// +//===----------------------------------------------------------------------===// +#include "Conversion/QUIRToAer.h" +#include "Conversion/OQ3ToStandard/OQ3ToStandard.h" +#include "Conversion/QUIRToStandard/VariablesToGlobalMemRefConversion.h" +#include "Conversion/TypeConversion.h" + +#include "Dialect/OQ3/IR/OQ3Ops.h" +#include "Dialect/Pulse/IR/PulseDialect.h" +#include "Dialect/QCS/IR/QCSOps.h" +#include "Dialect/QUIR/IR/QUIRDialect.h" +#include "Dialect/QUIR/IR/QUIROps.h" +#include "Dialect/QUIR/Utils/Utils.h" + +#include "mlir/Dialect/Affine/IR/AffineOps.h" +#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/Dialect/SCF/SCF.h" +#include "mlir/Dialect/StandardOps/IR/Ops.h" +#include "mlir/Dialect/StandardOps/Transforms/FuncConversions.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +using namespace mlir; + +#include + +namespace qssc::targets::simulators::aer::conversion { + +namespace { + +// The wrapper helps a converter access the global state by generating +// `AddressOp` and `LoadOp` automatically. +class AerStateWrapper { +public: + AerStateWrapper() = default; + AerStateWrapper(LLVM::GlobalOp mem) : mem(mem) {} + + Value access(OpBuilder &builder) const { + auto addr = getAddr(builder); + return builder.create(builder.getUnknownLoc(), addr, + /*alignment=*/8); + } + + Value getAddr(OpBuilder &builder) const { + return builder.create(builder.getUnknownLoc(), mem); + } + + LLVM::GlobalOp raw() const { return mem; } + +private: + LLVM::GlobalOp mem; +}; + +using AerFunctionTable = std::map; +// Declare Aer runtime API functions globally. +// The definitions of those functions are given externally by a linker. +AerFunctionTable declareAerFunctions(ModuleOp moduleOp) { + using LLVM::LLVMFunctionType; + + AerFunctionTable aerFuncTable; + OpBuilder builder(moduleOp); + + auto registerFunc = [&](const char *name, LLVMFunctionType ty) { + const auto loc = builder.getUnknownLoc(); + const auto f = builder.create(loc, name, ty); + aerFuncTable.insert({name, f}); + }; + + auto *context = moduleOp->getContext(); + builder.setInsertionPointToStart(moduleOp.getBody()); + // common types + const auto voidType = LLVM::LLVMVoidType::get(context); + const auto i8Type = builder.getI8Type(); + const auto i64Type = builder.getI64Type(); + const auto f64Type = builder.getF64Type(); + const auto aerStateType = LLVM::LLVMPointerType::get(i8Type); + const auto strType = LLVM::LLVMPointerType::get(i8Type); + // @aer_state(...) -> i8* + const auto aerStateFunType = LLVMFunctionType::get(aerStateType, {}, true); + registerFunc("aer_state", aerStateFunType); + // @aer_state_configure(i8* noundef, i8* noundef, i8* noundef) -> void + const auto aerStateConfigureType = + LLVMFunctionType::get(voidType, {strType, strType, strType}); + registerFunc("aer_state_configure", aerStateConfigureType); + // @aer_allocate_qubits(i8* noundef, i64 noundef) -> i64 + const auto aerAllocQubitsType = + LLVMFunctionType::get(i64Type, {aerStateType, i64Type}); + registerFunc("aer_allocate_qubits", aerAllocQubitsType); + // @aer_state_initialize(i8*) -> i8* + const auto aerStateInitType = + LLVMFunctionType::get(aerStateType, {aerStateType}); + registerFunc("aer_state_initialize", aerStateInitType); + // @aer_apply_u3(i8* noundef, i64 noundef, i64 noundef, i64 noundef) -> void + const auto aerApplyU3Type = LLVMFunctionType::get( + voidType, {aerStateType, i64Type, f64Type, f64Type, f64Type}); + registerFunc("aer_apply_u3", aerApplyU3Type); + // @aer_apply_cx(i8* noundef, i64 noundef, i64 noundef) -> void + const auto aerApplyCXType = + LLVMFunctionType::get(voidType, {aerStateType, i64Type, i64Type}); + registerFunc("aer_apply_cx", aerApplyCXType); + // @aer_apply_measure(i8* noundef, i64* noundef, i64 noundef) -> i64 + const auto aerMeasType = LLVMFunctionType::get( + i64Type, {aerStateType, LLVM::LLVMPointerType::get(i64Type), i64Type}); + registerFunc("aer_apply_measure", aerMeasType); + // @aer_state_finalize(i8* noundef) -> void + const auto aerStateFinalizeType = + LLVMFunctionType::get(voidType, aerStateType); + registerFunc("aer_state_finalize", aerStateFinalizeType); + + return aerFuncTable; +} + +// Create an Aer state globally and then wrap the state value. +AerStateWrapper createAerState(MLIRContext *ctx, ModuleOp moduleOp, + AerFunctionTable aerFuncTable) { + const auto i8Type = IntegerType::get(ctx, 8); + + OpBuilder builder(moduleOp); + builder.setInsertionPointToStart(moduleOp.getBody()); + auto aerStateTy = LLVM::LLVMPointerType::get(i8Type); + auto globalState = builder.create( + moduleOp->getLoc(), aerStateTy, /*isConstant=*/false, LLVM::Linkage::Weak, + "aer_state_handler", Attribute{}, + /*alignment=*/8); + auto aerState = AerStateWrapper(globalState); + + auto *mainFunc = mlir::quir::getMainFunction(moduleOp); + auto *mainBody = &mainFunc->getRegion(0).getBlocks().front(); + builder.setInsertionPointToStart(mainBody); + auto addr = aerState.getAddr(builder); + auto call = + builder + .create(builder.getUnknownLoc(), + aerFuncTable.at("aer_state"), ValueRange{}) + .getResult(0); + builder.create(builder.getUnknownLoc(), call, addr); + + return aerState; +} + +void insertAerStateInitialize(ModuleOp moduleOp, AerStateWrapper aerState, + AerFunctionTable aerFuncTable) { + OpBuilder builder(moduleOp); + + // Insert Aer runtime initialization after qubit declarations. + // Assume that the following conditions hold: + // 1. Each qubit declaration has a unique id (e.g., {id = 0 : i32}). + // 2. The last qubit declaration has the biggest id. + std::optional lastQubitDeclOp; + moduleOp.walk([&](quir::DeclareQubitOp declOp) { + if (!lastQubitDeclOp || + lastQubitDeclOp->id().getValue() < declOp.id().getValue()) + lastQubitDeclOp = declOp; + }); + assert(lastQubitDeclOp && "At least one qubit must be declared."); + builder.setInsertionPointAfter(*lastQubitDeclOp); + auto state = aerState.access(builder); + builder.create(lastQubitDeclOp->getLoc(), + aerFuncTable.at("aer_state_initialize"), state); +} + +using ArrayForMeas = mlir::Value; +// Allocate an array for measurements globally. +// @note Aer C API requires an array of measured qubits. This provides a common +// array for the measurements that can avoid a stack allocation for each +// function call of the Aer measurement. +// Note that the size of this array must be large enough to perform all +// the measurements appeared in a given program. +ArrayForMeas prepareArrayForMeas(ModuleOp moduleOp) { + OpBuilder builder(moduleOp); + + const auto i64Type = builder.getI64Type(); + auto mainFunc = mlir::quir::getMainFunction(moduleOp); + builder.setInsertionPointToStart(&mainFunc->getRegion(0).getBlocks().front()); + const int arraySize = 1; // TODO: Support multi-body measurement in future + auto arrSizeOp = builder.create( + builder.getUnknownLoc(), i64Type, + builder.getIntegerAttr(i64Type, arraySize)); + return builder + .create(builder.getUnknownLoc(), + LLVM::LLVMPointerType::get(i64Type), arrSizeOp, + /*alignment=*/8) + .getResult(); +} + +} // namespace + +// Assume qcs.init is called before all quir.declare_qubit operations +struct QCSInitConversionPat : public OpConversionPattern { + explicit QCSInitConversionPat(MLIRContext *ctx, TypeConverter &typeConverter, + AerSimulatorConfig config, + AerStateWrapper aerState, + AerFunctionTable aerFuncTable) + : OpConversionPattern(typeConverter, ctx, /* benefit= */ 1), + config(std::move(config)), aerFuncTable(std::move(aerFuncTable)), + aerState(aerState) {} + + LogicalResult + matchAndRewrite(qcs::SystemInitOp initOp, qcs::SystemInitOp::Adaptor adapter, + ConversionPatternRewriter &rewriter) const override { + + // global string values for aer configuration + std::map globals; + const auto method = config.getMethod(); + const auto device = config.getDevice(); + const auto precision = config.getPrecision(); + const auto *const methodStr = toStringInAer(method); + const auto *const deviceStr = toStringInAer(device); + const auto *const precisionStr = toStringInAer(precision); + const auto config_strs = {"method", methodStr, "device", + deviceStr, "precision", precisionStr}; + for (const auto *config_str : config_strs) { + const auto var_name = std::string("aer_conf_") + config_str; + const auto with_null = config_str + std::string("\0", 1); + globals[config_str] = + LLVM::createGlobalString(initOp->getLoc(), rewriter, var_name, + with_null, LLVM::Linkage::Private); + } + // configure + // aer_state_configure(state, "method", ) + // aer_state_configure(state, "device", ) + // aer_state_configure(state, "precision", ) + auto state = aerState.access(rewriter); + rewriter.create( + initOp->getLoc(), aerFuncTable.at("aer_state_configure"), + ValueRange{state, globals["method"], globals[methodStr]}); + rewriter.create( + initOp->getLoc(), aerFuncTable.at("aer_state_configure"), + ValueRange{state, globals["device"], globals[deviceStr]}); + rewriter.create( + initOp->getLoc(), aerFuncTable.at("aer_state_configure"), + ValueRange{state, globals["precision"], globals[precisionStr]}); + rewriter.eraseOp(initOp); + return success(); + } + +private: + const AerSimulatorConfig config; + const AerFunctionTable aerFuncTable; + AerStateWrapper aerState; +}; + +// Currently the simulator target does not support shot iterations. +struct RemoveQCSShotInitConversionPat + : public OpConversionPattern { + explicit RemoveQCSShotInitConversionPat(MLIRContext *ctx, + TypeConverter &typeConverter) + : OpConversionPattern(typeConverter, ctx, /* benefit= */ 1) {} + + LogicalResult + matchAndRewrite(qcs::ShotInitOp initOp, qcs::ShotInitOp::Adaptor adapter, + ConversionPatternRewriter &rewriter) const override { + rewriter.eraseOp(initOp); + return success(); + } +}; + +struct FinalizeConversionPat + : public OpConversionPattern { + explicit FinalizeConversionPat(MLIRContext *ctx, TypeConverter &typeConverter, + AerStateWrapper aerState, + AerFunctionTable aerFuncTable) + : OpConversionPattern(typeConverter, ctx, /* benefit= */ 1), + aerState(aerState), aerFuncTable(std::move(aerFuncTable)) {} + + LogicalResult + matchAndRewrite(qcs::SystemFinalizeOp finOp, + qcs::SystemFinalizeOp::Adaptor adapter, + ConversionPatternRewriter &rewriter) const override { + PatternRewriter::InsertionGuard insertGuard(rewriter); + rewriter.setInsertionPointAfter(finOp); + rewriter.create(rewriter.getUnknownLoc(), + aerFuncTable.at("aer_state_finalize"), + aerState.access(rewriter)); + rewriter.eraseOp(finOp); + return success(); + } + +private: + AerStateWrapper aerState; + const AerFunctionTable aerFuncTable; +}; + +struct DeclareQubitConversionPat + : public OpConversionPattern { + using Adaptor = quir::DeclareQubitOp::Adaptor; + + explicit DeclareQubitConversionPat(MLIRContext *ctx, + TypeConverter &typeConverter, + AerStateWrapper aerState, + AerFunctionTable aerFuncTable) + : OpConversionPattern(typeConverter, ctx, /*benefit=*/1), + aerState(aerState), aerFuncTable(std::move(aerFuncTable)) {} + + LogicalResult + matchAndRewrite(quir::DeclareQubitOp op, Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + const int width = op.getType().dyn_cast().getWidth(); + assert(width == 1 && "Multi qubit declaration has not supported yet."); + + const auto sizeAttr = rewriter.getIntegerAttr(rewriter.getI64Type(), width); + auto constOp = rewriter.create( + op->getLoc(), rewriter.getI64Type(), sizeAttr); + auto state = aerState.access(rewriter); + auto alloc = rewriter.create( + op->getLoc(), aerFuncTable.at("aer_allocate_qubits"), + ValueRange{state, constOp}); + rewriter.replaceOp(op, alloc.getResults()); + return success(); + } + +private: + AerStateWrapper aerState; + const AerFunctionTable aerFuncTable; +}; + +struct BuiltinUopConversionPat : public OpConversionPattern { + explicit BuiltinUopConversionPat(MLIRContext *ctx, + TypeConverter &typeConverter, + AerStateWrapper aerState, + AerFunctionTable aerFuncTable) + : OpConversionPattern(typeConverter, ctx, /*benefit=*/1), + aerState(aerState), aerFuncTable(std::move(aerFuncTable)) {} + + LogicalResult + matchAndRewrite(quir::Builtin_UOp op, quir::Builtin_UOp::Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto state = aerState.access(rewriter); + std::vector args = {state}; + args.insert(args.end(), adaptor.getOperands().begin(), + adaptor.getOperands().end()); + rewriter.create(op.getLoc(), aerFuncTable.at("aer_apply_u3"), + args); + rewriter.eraseOp(op); + return success(); + } + +private: + AerStateWrapper aerState; + const AerFunctionTable aerFuncTable; +}; + +struct BuiltinCXConversionPat : public OpConversionPattern { + explicit BuiltinCXConversionPat(MLIRContext *ctx, + TypeConverter &typeConverter, + AerStateWrapper aerState, + AerFunctionTable aerFuncTable) + : OpConversionPattern(typeConverter, ctx, /*benefit=*/1), + aerState(aerState), aerFuncTable(std::move(aerFuncTable)) {} + + LogicalResult + matchAndRewrite(quir::BuiltinCXOp op, quir::BuiltinCXOp::Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto state = aerState.access(rewriter); + std::vector args = {state}; + args.insert(args.end(), adaptor.getOperands().begin(), + adaptor.getOperands().end()); + rewriter.create(op->getLoc(), aerFuncTable.at("aer_apply_cx"), + args); + rewriter.eraseOp(op); + return success(); + } + +private: + AerStateWrapper aerState; + const AerFunctionTable aerFuncTable; +}; + +struct MeasureOpConversionPat : public OpConversionPattern { + explicit MeasureOpConversionPat(MLIRContext *ctx, + TypeConverter &typeConverter, + AerStateWrapper aerState, + AerFunctionTable aerFuncTable, + ArrayForMeas arrayForMeas) + : OpConversionPattern(typeConverter, ctx, /*benefit=*/1), + aerState(aerState), aerFuncTable(std::move(aerFuncTable)), + arrayForMeas(arrayForMeas) {} + + LogicalResult + matchAndRewrite(quir::MeasureOp op, quir::MeasureOp::Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + assert(op->getNumOperands() == 1 && + "Multi-body measurement have not been supported yet."); + // How to cast? + auto allocaArrForMeas = + llvm::dyn_cast(arrayForMeas.getDefiningOp()); + const auto i64Type = rewriter.getI64Type(); + const unsigned arrSize = 1; // TODO + const IntegerAttr arraySizeAttr = rewriter.getIntegerAttr(i64Type, arrSize); + const auto qubit = *adaptor.getOperands().begin(); + auto arrSizeOp = rewriter.create(op->getLoc(), i64Type, + arraySizeAttr); + rewriter.create(op->getLoc(), qubit, allocaArrForMeas); + auto state = aerState.access(rewriter); + auto meas = rewriter.create( + op->getLoc(), aerFuncTable.at("aer_apply_measure"), + ValueRange{state, arrayForMeas, arrSizeOp}); + auto casted = rewriter.create( + op->getLoc(), meas.getResult(0), rewriter.getI1Type()); + rewriter.replaceOp(op, casted.getResult()); + + return success(); + } + +private: + AerStateWrapper aerState; + const AerFunctionTable aerFuncTable; + ArrayForMeas arrayForMeas; +}; + +struct ConstConversionPat : public OpConversionPattern { + explicit ConstConversionPat(MLIRContext *ctx, TypeConverter &typeConverter) + : OpConversionPattern(typeConverter, ctx, 1) {} + + LogicalResult + matchAndRewrite(quir::ConstantOp op, quir::ConstantOp::Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + if (auto angleAttr = op.value().dyn_cast()) { + rewriter.setInsertionPointAfter(op); + const auto angle = angleAttr.getValue().convertToDouble(); + const auto fType = rewriter.getF64Type(); + FloatAttr fAttr = rewriter.getFloatAttr(fType, angle); + auto constOp = + rewriter.create(op->getLoc(), fType, fAttr); + rewriter.replaceOp(op, {constOp}); + } else if (op.value().isa()) { + rewriter.eraseOp(op); + } + return success(); + } +}; + +template +struct RemoveConversionPat : public OpConversionPattern { + using Adaptor = typename Op::Adaptor; + + explicit RemoveConversionPat(MLIRContext *ctx, TypeConverter &typeConverter) + : OpConversionPattern(typeConverter, ctx, 1) {} + + LogicalResult + matchAndRewrite(Op op, Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.eraseOp(op); + return success(); + } +}; + +// TDOO: Supporting custom gates is future work. +struct FunctionConversionPat : public OpConversionPattern { + explicit FunctionConversionPat(MLIRContext *ctx, TypeConverter &typeConverter) + : OpConversionPattern(typeConverter, ctx, 1) {} + + LogicalResult + matchAndRewrite(mlir::FuncOp funcOp, mlir::FuncOp::Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Assume: funcOp != mainFunc + if (funcOp.getName() == "main") + return success(); + + rewriter.eraseOp(funcOp); + return success(); + } +}; + +void conversion::QUIRToAERPass::getDependentDialects( + DialectRegistry ®istry) const { + registry.insert(); +} + +void QUIRToAERPass::runOnOperation(AerSimulator &system) { + ModuleOp moduleOp = getOperation(); + const auto simulatorConfig = system.getConfig(); + + // First remove all arguments from synchronization ops + moduleOp->walk([](qcs::SynchronizeOp synchOp) { + synchOp.qubitsMutable().assign(ValueRange({})); + }); + + AerTypeConverter typeConverter; + auto *context = &getContext(); + ConversionTarget target(*context); + + target.addLegalDialect(); + target + .addIllegalDialect(); + target.addDynamicallyLegalOp( + [&](FuncOp op) { return typeConverter.isSignatureLegal(op.getType()); }); + + // Aer initialization + auto aerFuncTable = declareAerFunctions(moduleOp); + auto aerState = createAerState(context, moduleOp, aerFuncTable); + insertAerStateInitialize(moduleOp, aerState, aerFuncTable); + auto arrayForMeas = prepareArrayForMeas(moduleOp); + + RewritePatternSet patterns(context); + populateFunctionOpInterfaceTypeConversionPattern(patterns, + typeConverter); + populateCallOpTypeConversionPattern(patterns, typeConverter); + oq3::populateOQ3ToStandardConversionPatterns(typeConverter, patterns); + patterns.add(context, typeConverter, simulatorConfig, + aerState, aerFuncTable); + patterns.add( + context, typeConverter, aerState, aerFuncTable); + patterns.add(context, typeConverter, aerState, + aerFuncTable, arrayForMeas); + patterns.add< + RemoveQCSShotInitConversionPat, ConstConversionPat, FunctionConversionPat, + RemoveConversionPat, // TODO: Support noise models + RemoveConversionPat, // TODO: Support noise models + RemoveConversionPat>( // TODO: Support custom gates + context, typeConverter); + + // With the target and rewrite patterns defined, we can now attempt the + // conversion. The conversion will signal failure if any of our `illegal` + // operations were not converted successfully. + if (failed(applyPartialConversion(moduleOp, target, std::move(patterns)))) + llvm::outs() << "Failed applyPartialConversion\n"; +} // QUIRToStdPass::runOnOperation() + +llvm::StringRef QUIRToAERPass::getArgument() const { + return "simulator-quir-to-aer"; +} + +llvm::StringRef QUIRToAERPass::getDescription() const { + return "Convert QUIR ops to aer"; +} + +} // namespace qssc::targets::simulators::aer::conversion diff --git a/targets/simulators/aer/Conversion/QUIRToAer.h b/targets/simulators/aer/Conversion/QUIRToAer.h new file mode 100644 index 000000000..d67328004 --- /dev/null +++ b/targets/simulators/aer/Conversion/QUIRToAer.h @@ -0,0 +1,46 @@ +//===- QUIRToAer.h - Convert QUIR to Aer Dialect ----------------*- 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 declares the pass for converting QUIR to Aer +// +//===----------------------------------------------------------------------===// + +#ifndef SIMULATOR_AER_CONVERSION_QUIR_TO_AER_H +#define SIMULATOR_AER_CONVERSION_QUIR_TO_AER_H + +#include "AerSimulator.h" + +#include "HAL/TargetOperationPass.h" + +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/Pass.h" + +namespace qssc::targets::simulators::aer::conversion { +struct QUIRToAERPass + : public mlir::PassWrapper< + QUIRToAERPass, + hal::TargetOperationPass> { + void runOnOperation(AerSimulator &system) override; + void getDependentDialects(mlir::DialectRegistry ®istry) const override; + + QUIRToAERPass() : PassWrapper() {} + + llvm::StringRef getArgument() const override; + llvm::StringRef getDescription() const override; +}; +} // namespace qssc::targets::simulators::aer::conversion + +#endif // SIMULATOR_AER_CONVERSION_QUIR_TO_AER_H diff --git a/targets/simulators/aer/Conversion/TypeConversion.cpp b/targets/simulators/aer/Conversion/TypeConversion.cpp new file mode 100644 index 000000000..8eb5d9f53 --- /dev/null +++ b/targets/simulators/aer/Conversion/TypeConversion.cpp @@ -0,0 +1,110 @@ +//===- TypeConversion.cpp - Convert QUIR types to Std -----------*- 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 implements common utilities for converting QUIR to std +/// +//===----------------------------------------------------------------------===// + +#include "Conversion/TypeConversion.h" +#include "Dialect/OQ3/IR/OQ3Ops.h" +#include "Dialect/QUIR/IR/QUIROps.h" + +namespace qssc::targets::simulators::aer { + +using namespace mlir; + +namespace { +Optional convertCBitType(quir::CBitType t) { + + if (t.getWidth() <= 64) + return IntegerType::get(t.getContext(), t.getWidth()); + + return llvm::None; +} + +Optional legalizeIndexType(mlir::IndexType t) { return t; } +} // anonymous namespace + +AerTypeConverter::AerTypeConverter() { + addConversion(convertQubitType); + addConversion(convertAngleType); + addConversion(convertDurationType); + addSourceMaterialization(qubitSourceMaterialization); + addSourceMaterialization(angleSourceMaterialization); + addSourceMaterialization(durationSourceMaterialization); + + addConversion(convertCBitType); + addConversion(legalizeIndexType); +} + +Optional AerTypeConverter::convertQubitType(Type t) { + if (auto qubitTy = t.dyn_cast()) + return IntegerType::get(t.getContext(), 64); + return llvm::None; +} + +Optional AerTypeConverter::convertAngleType(Type t) { + auto *context = t.getContext(); + if (auto angleType = t.dyn_cast()) { + auto width = angleType.getWidth(); + + if (!width.hasValue()) { + llvm::errs() << "Cannot lower an angle with no width!\n"; + return {}; + } + return Float64Type::get(context); + } + if (auto intType = t.dyn_cast()) { + // MUST return the converted type as itself to mark legal + // for function types in func defs and calls + return intType; + } + return llvm::None; +} + +Optional AerTypeConverter::convertDurationType(Type t) { + if (auto durTy = t.dyn_cast()) + return IntegerType::get(t.getContext(), 64); + return llvm::None; +} + +Optional +AerTypeConverter::qubitSourceMaterialization(OpBuilder &builder, + quir::QubitType qType, + ValueRange values, Location loc) { + for (Value val : values) + return val; + return llvm::None; +} + +Optional AerTypeConverter::angleSourceMaterialization( + OpBuilder &builder, quir::AngleType aType, ValueRange valRange, + Location loc) { + for (Value val : valRange) { + auto castOp = builder.create(loc, aType, val); + return castOp.out(); + } // for val : valRange + return llvm::None; +} // angleSourceMaterialization + +Optional AerTypeConverter::durationSourceMaterialization( + OpBuilder &builder, quir::DurationType dType, ValueRange valRange, + Location loc) { + for (Value val : valRange) + return val; + return llvm::None; +} +} // namespace qssc::targets::simulators::aer diff --git a/targets/simulators/aer/Conversion/TypeConversion.h b/targets/simulators/aer/Conversion/TypeConversion.h new file mode 100644 index 000000000..a4ad479a6 --- /dev/null +++ b/targets/simulators/aer/Conversion/TypeConversion.h @@ -0,0 +1,57 @@ +//===- TypeConversion.h - Convert QUIR types to Std -------------*- 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 declares common utilities for converting QUIR to std +// +//===----------------------------------------------------------------------===// + +#ifndef AERTOSTD_TYPECONVERSION__H +#define AERTOSTD_TYPECONVERSION__H + +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/Transforms/DialectConversion.h" + +#include "Dialect/QUIR/IR/QUIRTypes.h" + +namespace qssc::targets::simulators::aer { + +struct AerTypeConverter : public mlir::TypeConverter { + using mlir::TypeConverter::TypeConverter; + + AerTypeConverter(); + + static mlir::Optional convertQubitType(mlir::Type t); + static mlir::Optional convertAngleType(mlir::Type t); + static mlir::Optional convertDurationType(mlir::Type t); + + static mlir::Optional + qubitSourceMaterialization(mlir::OpBuilder &builder, + mlir::quir::QubitType qType, + mlir::ValueRange valRange, mlir::Location loc); + static mlir::Optional + angleSourceMaterialization(mlir::OpBuilder &builder, + mlir::quir::AngleType aType, + mlir::ValueRange valRange, mlir::Location loc); + static mlir::Optional + durationSourceMaterialization(mlir::OpBuilder &builder, + mlir::quir::DurationType dType, + mlir::ValueRange valRange, mlir::Location loc); + +}; // struct AerTypeConverter + +} // namespace qssc::targets::simulators::aer + +#endif // AERTOSTD_TYPECONVERSION__H diff --git a/targets/simulators/aer/Target.inc b/targets/simulators/aer/Target.inc new file mode 100644 index 000000000..cfd8fe30c --- /dev/null +++ b/targets/simulators/aer/Target.inc @@ -0,0 +1,33 @@ +//===- Target.inc - Simulator target 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 system targets +// with the QSS compiler core. +// +//===----------------------------------------------------------------------===// + +#ifndef HAL_TARGETS_AER_SIMULATOR_TARGET_H +#define HAL_TARGETS_AER_SIMULATOR_TARGET_H + +#include "AerSimulator.h" + +namespace qssc::targets::simulators::aer { + +[[maybe_unused]] int registrar = init(); + +} // namespace qssc::targets::simulators::aer + +#endif // HAL_TARGETS_AER_SIMULATOR_TARGET_H diff --git a/targets/simulators/aer/Transforms/OutputClassicalRegisters.cpp b/targets/simulators/aer/Transforms/OutputClassicalRegisters.cpp new file mode 100644 index 000000000..8a232ad16 --- /dev/null +++ b/targets/simulators/aer/Transforms/OutputClassicalRegisters.cpp @@ -0,0 +1,150 @@ +//===- OutputClassicalRegisters.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. +// +//===----------------------------------------------------------------------===// +// +// This file implements passes to output classical register values +// +//===----------------------------------------------------------------------===// +#include "Transforms/OutputClassicalRegisters.h" + +#include "Dialect/OQ3/IR/OQ3Ops.h" +#include "Dialect/QCS/IR/QCSOps.h" + +#include "mlir/Dialect/Affine/IR/AffineOps.h" +#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +using namespace mlir; + +namespace qssc::targets::simulators::aer::transforms { + +class OutputCRegsPassImpl { +public: + OutputCRegsPassImpl() = default; + + void runOnOperation(MLIRContext *context, ModuleOp moduleOp) { + mlir::TypeConverter typeConverter; + ConversionTarget target(*context); + + prepareConversion(moduleOp); + insertOutputCRegs(moduleOp); + } + +private: + void prepareConversion(ModuleOp moduleOp) { + // Create definition table that maps classical register names -> + // defining operations. + cbitDecls.clear(); + moduleOp->walk([&](oq3::DeclareVariableOp op) { + if (auto ty = op.type().dyn_cast()) + cbitDecls.emplace_back(op.getName().str(), op); + }); + + // Declare the printf function. + // TODO: In future, the result values should be printed with another way. + OpBuilder builder(moduleOp); + builder.setInsertionPointToStart(moduleOp.getBody()); + const auto i32Ty = builder.getI32Type(); + const auto i8PtrTy = LLVM::LLVMPointerType::get(builder.getI8Type()); + const auto printfTy = + LLVM::LLVMFunctionType::get(i32Ty, i8PtrTy, /*isVarArgs=*/true); + printfFuncOp = builder.create(moduleOp->getLoc(), + "printf", printfTy); + } + + // Insert output ops where `qcf.finalize` is called. + // We do not erase `qcf.finalize` because a subsequent pass may use it + // for the translation. + void insertOutputCRegs(ModuleOp moduleOp) { + // Assume that `qcf.finalize` is called only once. + moduleOp->walk([&](qcs::SystemFinalizeOp op) { + OpBuilder builder(op); + + // Define constant strings for printing globally. + if (globalStrs.find("\n") == globalStrs.end()) { + const auto varName = std::string{"str_endline"}; + const auto value = std::string{"\n\0", 2}; + globalStrs["\n"] = LLVM::createGlobalString( + op->getLoc(), builder, varName, value, LLVM::Linkage::Private); + } + if (globalStrs.find("%d") == globalStrs.end()) { + const auto varName = std::string{"str_digit"}; + const auto value = std::string{"%d\0", 3}; + globalStrs["%d"] = LLVM::createGlobalString( + op->getLoc(), builder, varName, value, LLVM::Linkage::Private); + } + + // Print the values of classical registers in the declared order. + for (auto &[name, declOp] : cbitDecls) { + if (globalStrs.find(name) == globalStrs.end()) { + const auto varName = std::string{"str_creg_"} + name; + const auto value = std::string{" "} + name + std::string{" : \0", 4}; + globalStrs[name] = LLVM::createGlobalString( + op->getLoc(), builder, varName, value, LLVM::Linkage::Private); + } + + builder.create(op->getLoc(), printfFuncOp, + globalStrs[name]); + auto cBitTy = declOp.type().dyn_cast(); + const int width = cBitTy.getWidth(); + const auto boolTy = builder.getI1Type(); + auto loaded = + builder.create(op->getLoc(), cBitTy, name); + for (int i = width - 1; i >= 0; --i) { + auto indexAttr = builder.getIndexAttr(i); + auto bit = builder.create(op->getLoc(), boolTy, + loaded, indexAttr); + builder.create(op->getLoc(), printfFuncOp, + ValueRange{globalStrs["%d"], bit}); + } + builder.create(op->getLoc(), printfFuncOp, + globalStrs["\n"]); + } + }); + } + +private: + std::vector> cbitDecls; + std::map globalStrs; + LLVM::LLVMFuncOp printfFuncOp; +}; + +OutputCRegsPass::OutputCRegsPass() : PassWrapper() { + impl = std::make_shared(); +} + +void OutputCRegsPass::runOnOperation(AerSimulator &system) { + auto *context = &getContext(); + auto moduleOp = getOperation(); + impl->runOnOperation(context, moduleOp); +} + +void OutputCRegsPass::getDependentDialects(DialectRegistry ®istry) const { + registry.insert(); +} + +llvm::StringRef OutputCRegsPass::getArgument() const { + return "simulator-output-cregs"; +} + +llvm::StringRef OutputCRegsPass::getDescription() const { + return "Insert output ops for classical registers"; +} + +} // namespace qssc::targets::simulators::aer::transforms diff --git a/targets/simulators/aer/Transforms/OutputClassicalRegisters.h b/targets/simulators/aer/Transforms/OutputClassicalRegisters.h new file mode 100644 index 000000000..d30c5ebf0 --- /dev/null +++ b/targets/simulators/aer/Transforms/OutputClassicalRegisters.h @@ -0,0 +1,52 @@ +//===- OutputClassicalRegisters.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. +// +//===----------------------------------------------------------------------===// +// +// This file declares the pass to output classical register values +// +//===----------------------------------------------------------------------===// + +#ifndef SIMULATOR_TRANSFORMS_OUTPUT_CLASSICAL_REGISTERS_H +#define SIMULATOR_TRANSFORMS_OUTPUT_CLASSICAL_REGISTERS_H + +#include "AerSimulator.h" + +#include "HAL/TargetOperationPass.h" + +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/Pass.h" + +namespace qssc::targets::simulators::aer::transforms { + +class OutputCRegsPassImpl; + +struct OutputCRegsPass + : public mlir::PassWrapper< + OutputCRegsPass, + hal::TargetOperationPass> { + void runOnOperation(AerSimulator &system) override; + void getDependentDialects(mlir::DialectRegistry ®istry) const override; + + OutputCRegsPass(); + + llvm::StringRef getArgument() const override; + llvm::StringRef getDescription() const override; + +private: + std::shared_ptr impl; +}; +} // namespace qssc::targets::simulators::aer::transforms + +#endif // SIMULATOR_TRANSFORMS_OUTPUT_CLASSICAL_REGISTERS_H diff --git a/targets/simulators/aer/test/CMakeLists.txt b/targets/simulators/aer/test/CMakeLists.txt new file mode 100644 index 000000000..9b4520ef2 --- /dev/null +++ b/targets/simulators/aer/test/CMakeLists.txt @@ -0,0 +1,17 @@ +# (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. + +qssc_add_lit_test_suite(check-aer-simulator + "Aer Simulator Static" + "${CMAKE_CURRENT_SOURCE_DIR}" + "${LIT_TEST_EXTRA_ARGS} -v -DTEST_CFG=${CMAKE_CURRENT_SOURCE_DIR}/test.cfg" +) diff --git a/targets/simulators/aer/test/Conversion/aer_configure.mlir b/targets/simulators/aer/test/Conversion/aer_configure.mlir new file mode 100644 index 000000000..dd43d3663 --- /dev/null +++ b/targets/simulators/aer/test/Conversion/aer_configure.mlir @@ -0,0 +1,89 @@ +// RUN: echo "{" > %t +// RUN: echo " \"method\" : \"MPS\"," >> %t +// RUN: echo " \"device\" : \"gpu\"," >> %t +// RUN: echo " \"precision\" : \"double\"" >> %t +// RUN: echo "}" >> %t +// RUN: qss-compiler -X=mlir --emit=mlir --target aer-simulator --config %t %s --simulator-quir-to-aer | FileCheck %s + +// +// This file was generated by: +// $ qss-compiler -X=mlir --emit=mlir --target aer-simulator --config test.cfg bell.qasm \ +// $ --simulator-output-cregs --quir-eliminate-variables +// + +// +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023. +// +// 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. +// + +module { +// CHECK: module { + llvm.mlir.global private constant @str_creg_c1(" c1 : \00") + llvm.mlir.global private constant @str_creg_c0(" c0 : \00") + llvm.mlir.global private constant @str_digit("%d\00") + llvm.mlir.global private constant @str_endline("\0A\00") + llvm.func @printf(!llvm.ptr, ...) -> i32 + func @cx(%arg0: !quir.qubit<1>, %arg1: !quir.qubit<1>) { + return + } + + // CHECK: llvm.mlir.global {{.*}}("double\00") + // CHECK-NEXT: llvm.mlir.global {{.*}}("precision\00") + // CHECK-NEXT: llvm.mlir.global {{.*}}("GPU\00") + // CHECK-NEXT: llvm.mlir.global {{.*}}("device\00") + // CHECK-NEXT: llvm.mlir.global {{.*}}("matrix_product_state\00") + // CHECK-NEXT: llvm.mlir.global {{.*}}("method\00") + + // CHECK: func @main() -> i32 { + + func @main() -> i32 { + %angle = quir.constant #quir.angle<1.57079632679 : !quir.angle<64>> + %angle_0 = quir.constant #quir.angle<0.000000e+00 : !quir.angle<64>> + %angle_1 = quir.constant #quir.angle<3.1415926535900001 : !quir.angle<64>> + %c0_i32 = arith.constant 0 : i32 + %0 = memref.alloca() : memref + %1 = memref.alloca() : memref + qcs.init + qcs.shot_init {qcs.num_shots = 1 : i32} + %2 = quir.declare_qubit {id = 0 : i32} : !quir.qubit<1> + %3 = quir.declare_qubit {id = 1 : i32} : !quir.qubit<1> + quir.builtin_U %2, %angle, %angle_0, %angle_1 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> + quir.builtin_CX %2, %3 : !quir.qubit<1>, !quir.qubit<1> + %4 = quir.measure(%2) : (!quir.qubit<1>) -> i1 + affine.store %4, %1[] : memref + %5 = quir.measure(%3) : (!quir.qubit<1>) -> i1 + affine.store %5, %0[] : memref + %6 = llvm.mlir.addressof @str_endline : !llvm.ptr> + %7 = llvm.mlir.constant(0 : index) : i64 + %8 = llvm.getelementptr %6[%7, %7] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %9 = llvm.mlir.addressof @str_digit : !llvm.ptr> + %10 = llvm.mlir.constant(0 : index) : i64 + %11 = llvm.getelementptr %9[%10, %10] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %12 = llvm.mlir.addressof @str_creg_c0 : !llvm.ptr> + %13 = llvm.mlir.constant(0 : index) : i64 + %14 = llvm.getelementptr %12[%13, %13] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %15 = llvm.call @printf(%14) : (!llvm.ptr) -> i32 + %16 = affine.load %1[] : memref + %17 = llvm.call @printf(%11, %16) : (!llvm.ptr, i1) -> i32 + %18 = llvm.call @printf(%8) : (!llvm.ptr) -> i32 + %19 = llvm.mlir.addressof @str_creg_c1 : !llvm.ptr> + %20 = llvm.mlir.constant(0 : index) : i64 + %21 = llvm.getelementptr %19[%20, %20] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %22 = llvm.call @printf(%21) : (!llvm.ptr) -> i32 + %23 = affine.load %0[] : memref + %24 = llvm.call @printf(%11, %23) : (!llvm.ptr, i1) -> i32 + %25 = llvm.call @printf(%8) : (!llvm.ptr) -> i32 + qcs.finalize + return %c0_i32 : i32 + } +} + diff --git a/targets/simulators/aer/test/Conversion/bell.qasm b/targets/simulators/aer/test/Conversion/bell.qasm new file mode 100644 index 000000000..f6e6b5f0b --- /dev/null +++ b/targets/simulators/aer/test/Conversion/bell.qasm @@ -0,0 +1,44 @@ +// RUN: echo "{}" > %t +// RUN: qss-compiler -X=qasm --emit=mlir --target aer-simulator --config %t %s --num-shots=1 --simulator-output-cregs --quir-eliminate-variables --simulator-quir-to-aer | FileCheck %s + +// +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023. +// +// 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. +// + +OPENQASM 3.0; + +gate cx control, target {} + +qubit $0; +qubit $1; +bit c0; +bit c1; +// Allocations of classical bits are moved forward +// CHECK: {{.*}} = memref.alloca() : memref +// CHECK: {{.*}} = memref.alloca() : memref +// CHECK: {{.*}} = llvm.call @aer_allocate_qubits{{.*$}} +// CHECK: {{.*}} = llvm.call @aer_allocate_qubits{{.*$}} + + + +U(1.57079632679, 0.0, 3.14159265359) $0; +cx $0, $1; +// CHECK: llvm.call @aer_apply_u3{{.*$}} +// CHECK: llvm.call @aer_apply_cx{{.*$}} + +measure $0 -> c0; +measure $1 -> c1; +// CHECK: {{.*}} = llvm.call @aer_apply_measure{{.*$}} +// CHECK: affine.store %{{[0-9]+}}, %{{[0-9]+}}[] : memref +// CHECK: {{.*}} = llvm.call @aer_apply_measure{{.*$}} +// CHECK: affine.store %{{[0-9]+}}, %{{[0-9]+}}[] : memref diff --git a/targets/simulators/aer/test/Conversion/bell_quir_to_aer.mlir b/targets/simulators/aer/test/Conversion/bell_quir_to_aer.mlir new file mode 100644 index 000000000..97faf6e7f --- /dev/null +++ b/targets/simulators/aer/test/Conversion/bell_quir_to_aer.mlir @@ -0,0 +1,92 @@ +// RUN: echo "{}" > %t +// RUN: qss-compiler -X=mlir --emit=mlir --target aer-simulator --config %t %s --simulator-quir-to-aer | FileCheck %s + +// +// This file was generated by: +// $ qss-compiler -X=mlir --emit=mlir --target aer-simulator --config test.cfg bell.qasm \ +// $ --simulator-output-cregs --quir-eliminate-variables +// + +// +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023. +// +// 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. +// + +module { + llvm.mlir.global private constant @str_creg_c1(" c1 : \00") + llvm.mlir.global private constant @str_creg_c0(" c0 : \00") + llvm.mlir.global private constant @str_digit("%d\00") + llvm.mlir.global private constant @str_endline("\0A\00") + llvm.func @printf(!llvm.ptr, ...) -> i32 + func @cx(%arg0: !quir.qubit<1>, %arg1: !quir.qubit<1>) { + return + } + func @main() -> i32 { + + // CHECK: {{.*}} = llvm.call @aer_state() : () -> !llvm.ptr + + %angle = quir.constant #quir.angle<1.57079632679 : !quir.angle<64>> + %angle_0 = quir.constant #quir.angle<0.000000e+00 : !quir.angle<64>> + %angle_1 = quir.constant #quir.angle<3.1415926535900001 : !quir.angle<64>> + // CHECK: %[[angle:.*]] = arith.constant 1.57079632679 : f64 + // CHECK: %[[angle0:.*]] = arith.constant 0.000000e+00 : f64 + // CHECK: %[[angle1:.*]] = arith.constant 3.1415926535900001 : f64 + %c0_i32 = arith.constant 0 : i32 + %0 = memref.alloca() : memref + %1 = memref.alloca() : memref + + // CHECK: llvm.call @aer_state_configure{{.*$}} + // CHECK-NEXT: llvm.call @aer_state_configure{{.*$}} + // CHECK-NEXT: llvm.call @aer_state_configure{{.*$}} + + qcs.init + qcs.shot_init {qcs.num_shots = 1 : i32} + %2 = quir.declare_qubit {id = 0 : i32} : !quir.qubit<1> + // CHECK: %[[q0:.*]] = llvm.call @aer_allocate_qubits{{.*$}} + %3 = quir.declare_qubit {id = 1 : i32} : !quir.qubit<1> + // CHECK: %[[q1:.*]] = llvm.call @aer_allocate_qubits{{.*$}} + quir.builtin_U %2, %angle, %angle_0, %angle_1 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> + // CHECK: llvm.call @aer_apply_u3({{.*}}, %[[q0]], %[[angle]], %[[angle0]], %[[angle1]]) : (!llvm.ptr, i64, f64, f64, f64) -> () + quir.builtin_CX %2, %3 : !quir.qubit<1>, !quir.qubit<1> + // CHECK: llvm.call @aer_apply_cx({{.*}}, %[[q0]], %[[q1]]) : (!llvm.ptr, i64, i64) -> () + %4 = quir.measure(%2) : (!quir.qubit<1>) -> i1 + // CHECK: {{.*}} = llvm.call @aer_apply_measure{{.*$}} + affine.store %4, %1[] : memref + %5 = quir.measure(%3) : (!quir.qubit<1>) -> i1 + // CHECK: {{.*}} = llvm.call @aer_apply_measure{{.*$}} + affine.store %5, %0[] : memref + %6 = llvm.mlir.addressof @str_endline : !llvm.ptr> + %7 = llvm.mlir.constant(0 : index) : i64 + %8 = llvm.getelementptr %6[%7, %7] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %9 = llvm.mlir.addressof @str_digit : !llvm.ptr> + %10 = llvm.mlir.constant(0 : index) : i64 + %11 = llvm.getelementptr %9[%10, %10] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %12 = llvm.mlir.addressof @str_creg_c0 : !llvm.ptr> + %13 = llvm.mlir.constant(0 : index) : i64 + %14 = llvm.getelementptr %12[%13, %13] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %15 = llvm.call @printf(%14) : (!llvm.ptr) -> i32 + %16 = affine.load %1[] : memref + %17 = llvm.call @printf(%11, %16) : (!llvm.ptr, i1) -> i32 + %18 = llvm.call @printf(%8) : (!llvm.ptr) -> i32 + %19 = llvm.mlir.addressof @str_creg_c1 : !llvm.ptr> + %20 = llvm.mlir.constant(0 : index) : i64 + %21 = llvm.getelementptr %19[%20, %20] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %22 = llvm.call @printf(%21) : (!llvm.ptr) -> i32 + %23 = affine.load %0[] : memref + %24 = llvm.call @printf(%11, %23) : (!llvm.ptr, i1) -> i32 + %25 = llvm.call @printf(%8) : (!llvm.ptr) -> i32 + qcs.finalize + // CHECK: llvm.call @aer_state_finalize{{.*$}} + return %c0_i32 : i32 + } +} + diff --git a/targets/simulators/aer/test/Conversion/output_cregs.mlir b/targets/simulators/aer/test/Conversion/output_cregs.mlir new file mode 100644 index 000000000..da1d73621 --- /dev/null +++ b/targets/simulators/aer/test/Conversion/output_cregs.mlir @@ -0,0 +1,83 @@ +// RUN: echo "{}" > %t +// RUN: qss-compiler -X=mlir --emit=mlir --target aer-simulator --config %t %s --simulator-output-cregs | FileCheck %s + +// +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023. +// +// 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. +// + +module { + oq3.declare_variable @c0 : !quir.cbit<1> + oq3.declare_variable @c1 : !quir.cbit<1> + func @cx(%arg0: !quir.qubit<1>, %arg1: !quir.qubit<1>) { + return + } + + // CHECK: llvm.func @printf(!llvm.ptr, ...) -> i32 + + func @main() -> i32 { + qcs.init + qcs.shot_init {qcs.num_shots = 1 : i32} + %0 = quir.declare_qubit {id = 0 : i32} : !quir.qubit<1> + %1 = quir.declare_qubit {id = 1 : i32} : !quir.qubit<1> + %false = arith.constant false + %2 = "oq3.cast"(%false) : (i1) -> !quir.cbit<1> + oq3.variable_assign @c0 : !quir.cbit<1> = %2 + %false_0 = arith.constant false + %3 = "oq3.cast"(%false_0) : (i1) -> !quir.cbit<1> + oq3.variable_assign @c1 : !quir.cbit<1> = %3 + %angle = quir.constant #quir.angle<1.57079632679 : !quir.angle<64>> + %angle_1 = quir.constant #quir.angle<0.000000e+00 : !quir.angle<64>> + %angle_2 = quir.constant #quir.angle<3.1415926535900001 : !quir.angle<64>> + quir.builtin_U %0, %angle, %angle_1, %angle_2 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> + quir.builtin_CX %0, %1 : !quir.qubit<1>, !quir.qubit<1> + %4 = quir.measure(%0) : (!quir.qubit<1>) -> i1 + oq3.cbit_assign_bit @c0<1> [0] : i1 = %4 + %5 = quir.measure(%1) : (!quir.qubit<1>) -> i1 + oq3.cbit_assign_bit @c1<1> [0] : i1 = %5 + + // CHECK: func @main() -> i32 { + // CHECK-NEXT: qcs.init + // CHECK-NEXT: qcs.shot_init {qcs.num_shots = 1 : i32} + // CHECK-NEXT: %0 = quir.declare_qubit {id = 0 : i32} : !quir.qubit<1> + // CHECK-NEXT: %1 = quir.declare_qubit {id = 1 : i32} : !quir.qubit<1> + // CHECK-NEXT: %false = arith.constant false + // CHECK-NEXT: %2 = "oq3.cast"(%false) : (i1) -> !quir.cbit<1> + // CHECK-NEXT: oq3.variable_assign @c0 : !quir.cbit<1> = %2 + // CHECK-NEXT: %false_0 = arith.constant false + // CHECK-NEXT: %3 = "oq3.cast"(%false_0) : (i1) -> !quir.cbit<1> + // CHECK-NEXT: oq3.variable_assign @c1 : !quir.cbit<1> = %3 + // CHECK-NEXT: %angle = quir.constant #quir.angle<1.57079632679 : !quir.angle<64>> + // CHECK-NEXT: %angle_1 = quir.constant #quir.angle<0.000000e+00 : !quir.angle<64>> + // CHECK-NEXT: %angle_2 = quir.constant #quir.angle<3.1415926535900001 : !quir.angle<64>> + // CHECK-NEXT: quir.builtin_U %0, %angle, %angle_1, %angle_2 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> + // CHECK-NEXT: quir.builtin_CX %0, %1 : !quir.qubit<1>, !quir.qubit<1> + // CHECK-NEXT: %4 = quir.measure(%0) : (!quir.qubit<1>) -> i1 + // CHECK-NEXT: oq3.cbit_assign_bit @c0<1> [0] : i1 = %4 + // CHECK-NEXT: %5 = quir.measure(%1) : (!quir.qubit<1>) -> i1 + // CHECK-NEXT: oq3.cbit_assign_bit @c1<1> [0] : i1 = %5 + + // CHECK: %[[C0:.*]] = oq3.variable_load @c0 : !quir.cbit<1> + // CHECK-NEXT: %[[RES0:.*]] = oq3.cbit_extractbit(%[[C0]] : !quir.cbit<1>) [0] : i1 + // CHECK: llvm.call @printf({{.*}}, %[[RES0]]) : (!llvm.ptr, i1) -> i32 + // CHECK: %[[C1:.*]] = oq3.variable_load @c1 : !quir.cbit<1> + // CHECK-NEXT: %[[RES1:.*]] = oq3.cbit_extractbit(%[[C1]] : !quir.cbit<1>) [0] : i1 + // CHECK: llvm.call @printf({{.*}}, %[[RES1]]) : (!llvm.ptr, i1) -> i32 + + qcs.finalize + %c0_i32 = arith.constant 0 : i32 + return %c0_i32 : i32 + // CHECK: qcs.finalize + // CHECK-NEXT: %c0_i32 = arith.constant 0 : i32 + // CHECK-NEXT: return %c0_i32 : i32 + } +} diff --git a/targets/simulators/aer/test/Conversion/teleport.qasm b/targets/simulators/aer/test/Conversion/teleport.qasm new file mode 100644 index 000000000..fb84c4124 --- /dev/null +++ b/targets/simulators/aer/test/Conversion/teleport.qasm @@ -0,0 +1,61 @@ +// RUN: echo "{}" > %t +// RUN: qss-compiler -X=qasm --emit=mlir --target aer-simulator --config %t %s --num-shots=1 --break-reset --simulator-output-cregs --quir-eliminate-variables --simulator-quir-to-aer | FileCheck %s + +// +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023. +// +// 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. +// + +OPENQASM 3.0; + +gate cx control, target { } + +qubit $0; +qubit $1; +qubit $2; +bit[3] c; +// Allocations of classical bits are moved forward +// CHECK: {{.*}} = memref.alloca() : memref +// CHECK: {{.*}} = llvm.call @aer_allocate_qubits{{.*$}} +// CHECK: {{.*}} = llvm.call @aer_allocate_qubits{{.*$}} +// CHECK: {{.*}} = llvm.call @aer_allocate_qubits{{.*$}} + +U(0.3, 0.2, 0.1) $0; +// CHECK: llvm.call @aer_apply_u3{{.*$}} + +U(1.57079632679, 0, 3.14159265359) $1; // h $1; +cx $1, $2; +cx $0, $1; +U(1.57079632679, 0, 3.14159265359) $0; // h $0; +// CHECK: llvm.call @aer_apply_u3{{.*$}} +// CHECK: llvm.call @aer_apply_cx{{.*$}} +// CHECK: llvm.call @aer_apply_cx{{.*$}} +// CHECK: llvm.call @aer_apply_u3{{.*$}} + +c[0] = measure $0; +c[1] = measure $1; +// CHECK: {{.*}} = llvm.call @aer_apply_measure{{.*$}} +// CHECK: {{.*}} = llvm.call @aer_apply_measure{{.*$}} +// Consecutive store to classical registers are merged: +// CHECK: affine.store %{{[0-9]+}}, %{{[0-9]+}}[] : memref + +if (c[0]==1) { +// CHECK: scf.if %{{[0-9]+}} { + U(0, 0, 3.14159265359) $2; // z $2; + // CHECK: llvm.call @aer_apply_u3{{.*$}} +} + +if (c[1]==1) { +// CHECK: scf.if %{{[0-9]+}} { + U(3.14159265359, 0, 3.14159265359) $2; // x $2; + // CHECK: llvm.call @aer_apply_u3{{.*$}} +} diff --git a/targets/simulators/aer/test/Conversion/teleport_quir_to_aer.mlir b/targets/simulators/aer/test/Conversion/teleport_quir_to_aer.mlir new file mode 100644 index 000000000..389c89158 --- /dev/null +++ b/targets/simulators/aer/test/Conversion/teleport_quir_to_aer.mlir @@ -0,0 +1,120 @@ +// RUN: echo "{}" > %t +// RUN: qss-compiler -X=mlir --emit=mlir --target aer-simulator --config %t %s --num-shots=1 --simulator-quir-to-aer | FileCheck %s + +// +// This file was generated by: +// $ qss-compiler -X=mlir --emit=mlir --target aer-simulator --config test.cfg teleport.qasm \ +// $ --simulator-output-cregs --quir-eliminate-variables +// + +// +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023. +// +// 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. +// + +module { + llvm.mlir.global private constant @str_creg_c(" c : \00") + llvm.mlir.global private constant @str_digit("%d\00") + llvm.mlir.global private constant @str_endline("\0A\00") + llvm.func @printf(!llvm.ptr, ...) -> i32 + func @cx(%arg0: !quir.qubit<1>, %arg1: !quir.qubit<1>) { + return + } + func @main() -> i32 { + // CHECK: {{.*}} = llvm.call @aer_state() : () -> !llvm.ptr + + %c0_i3 = arith.constant 0 : i3 + %angle = quir.constant #quir.angle<3.000000e-01 : !quir.angle<64>> + %angle_0 = quir.constant #quir.angle<2.000000e-01 : !quir.angle<64>> + %angle_1 = quir.constant #quir.angle<1.000000e-01 : !quir.angle<64>> + %angle_2 = quir.constant #quir.angle<1.57079632679 : !quir.angle<64>> + %angle_3 = quir.constant #quir.angle<0.000000e+00 : !quir.angle<64>> + %angle_4 = quir.constant #quir.angle<3.1415926535900001 : !quir.angle<64>> + // CHECK: %[[angle:.*]] = arith.constant 3.000000e-01 : f64 + // CHECK-NEXT: %[[angle0:.*]] = arith.constant 2.000000e-01 : f64 + // CHECK-NEXT: %[[angle1:.*]] = arith.constant 1.000000e-01 : f64 + // CHECK-NEXT: %[[angle2:.*]] = arith.constant 1.57079632679 : f64 + // CHECK-NEXT: %[[angle3:.*]] = arith.constant 0.000000e+00 : f64 + // CHECK-NEXT: %[[angle4:.*]] = arith.constant 3.1415926535900001 : f64 + %c1_i32 = arith.constant 1 : i32 + %c0_i32 = arith.constant 0 : i32 + %0 = memref.alloca() : memref + + qcs.init + // CHECK: llvm.call @aer_state_configure{{.*$}} + // CHECK-NEXT: llvm.call @aer_state_configure{{.*$}} + // CHECK-NEXT: llvm.call @aer_state_configure{{.*$}} + + qcs.shot_init {qcs.num_shots = 1 : i32} + %1 = quir.declare_qubit {id = 0 : i32} : !quir.qubit<1> + %2 = quir.declare_qubit {id = 1 : i32} : !quir.qubit<1> + %3 = quir.declare_qubit {id = 2 : i32} : !quir.qubit<1> + // CHECK: %[[q0:.*]] = llvm.call @aer_allocate_qubits{{.*$}} + // CHECK: %[[q1:.*]] = llvm.call @aer_allocate_qubits{{.*$}} + // CHECK: %[[q2:.*]] = llvm.call @aer_allocate_qubits{{.*$}} + quir.builtin_U %1, %angle, %angle_0, %angle_1 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> + quir.builtin_U %2, %angle_2, %angle_3, %angle_4 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> + quir.builtin_CX %2, %3 : !quir.qubit<1>, !quir.qubit<1> + quir.builtin_CX %1, %2 : !quir.qubit<1>, !quir.qubit<1> + quir.builtin_U %1, %angle_2, %angle_3, %angle_4 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> + // CHECK: llvm.call @aer_apply_u3({{.*}}, %[[q0]], %[[angle]], %[[angle0]], %[[angle1]]) : (!llvm.ptr, i64, f64, f64, f64) -> () + // CHECK: llvm.call @aer_apply_u3({{.*}}, %[[q1]], %[[angle2]], %[[angle3]], %[[angle4]]) : (!llvm.ptr, i64, f64, f64, f64) -> () + // CHECK: llvm.call @aer_apply_cx({{.*}}, %[[q1]], %[[q2]]) : (!llvm.ptr, i64, i64) -> () + // CHECK: llvm.call @aer_apply_cx({{.*}}, %[[q0]], %[[q1]]) : (!llvm.ptr, i64, i64) -> () + // CHECK: llvm.call @aer_apply_u3({{.*}}, %[[q0]], %[[angle2]], %[[angle3]], %[[angle4]]) : (!llvm.ptr, i64, f64, f64, f64) -> () + %4 = quir.measure(%1) : (!quir.qubit<1>) -> i1 + // CHECK: {{.*}} = llvm.call @aer_apply_measure{{.*$}} + %5 = oq3.cbit_insertbit(%c0_i3 : i3) [0] = %4 : i3 + %6 = quir.measure(%2) : (!quir.qubit<1>) -> i1 + // CHECK: {{.*}} = llvm.call @aer_apply_measure{{.*$}} + %7 = oq3.cbit_insertbit(%5 : i3) [1] = %6 : i3 + affine.store %7, %0[] : memref + %8 = "oq3.cast"(%4) : (i1) -> i32 + %9 = arith.cmpi eq, %8, %c1_i32 : i32 + scf.if %9 { + // CHECK: scf.if {{.*}} { + quir.builtin_U %3, %angle_3, %angle_3, %angle_4 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> + // CHECK: llvm.call @aer_apply_u3({{.*}}, %[[q2]], %[[angle3]], %[[angle3]], %[[angle4]]) : (!llvm.ptr, i64, f64, f64, f64) -> () + // CHECK-NEXT: } + } + %10 = "oq3.cast"(%6) : (i1) -> i32 + %11 = arith.cmpi eq, %10, %c1_i32 : i32 + scf.if %11 { + // CHECK: scf.if {{.*}} { + quir.builtin_U %3, %angle_4, %angle_3, %angle_4 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> + // CHECK: llvm.call @aer_apply_u3({{.*}}, %[[q2]], %[[angle4]], %[[angle3]], %[[angle4]]) : (!llvm.ptr, i64, f64, f64, f64) -> () + // CHECK-NEXT: } + } + %12 = llvm.mlir.addressof @str_endline : !llvm.ptr> + %13 = llvm.mlir.constant(0 : index) : i64 + %14 = llvm.getelementptr %12[%13, %13] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %15 = llvm.mlir.addressof @str_digit : !llvm.ptr> + %16 = llvm.mlir.constant(0 : index) : i64 + %17 = llvm.getelementptr %15[%16, %16] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %18 = llvm.mlir.addressof @str_creg_c : !llvm.ptr> + %19 = llvm.mlir.constant(0 : index) : i64 + %20 = llvm.getelementptr %18[%19, %19] : (!llvm.ptr>, i64, i64) -> !llvm.ptr + %21 = llvm.call @printf(%20) : (!llvm.ptr) -> i32 + %22 = affine.load %0[] : memref + %23 = oq3.cbit_extractbit(%22 : i3) [2] : i1 + %24 = llvm.call @printf(%17, %23) : (!llvm.ptr, i1) -> i32 + %25 = oq3.cbit_extractbit(%22 : i3) [1] : i1 + %26 = llvm.call @printf(%17, %25) : (!llvm.ptr, i1) -> i32 + %27 = oq3.cbit_extractbit(%22 : i3) [0] : i1 + %28 = llvm.call @printf(%17, %27) : (!llvm.ptr, i1) -> i32 + %29 = llvm.call @printf(%14) : (!llvm.ptr) -> i32 + qcs.finalize + // CHECK: llvm.call @aer_state_finalize{{.*$}} + return %c0_i32 : i32 + } +} + diff --git a/targets/simulators/aer/test/Conversion/test.cfg b/targets/simulators/aer/test/Conversion/test.cfg new file mode 100644 index 000000000..d66dd5bac --- /dev/null +++ b/targets/simulators/aer/test/Conversion/test.cfg @@ -0,0 +1,5 @@ +{ + "method" : "MPS", + "device" : "gpu", + "precision" : "double" +} diff --git a/targets/simulators/aer/test/lit.local.cfg.py b/targets/simulators/aer/test/lit.local.cfg.py new file mode 100644 index 000000000..d3c759e29 --- /dev/null +++ b/targets/simulators/aer/test/lit.local.cfg.py @@ -0,0 +1,17 @@ +# ===- lit.local.cfg.py --------------------------------------*- Python -*-===// +# +# (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. +# +# ===----------------------------------------------------------------------===// + +config.substitutions.append(("%TEST_CFG", lit_config.params["TEST_CFG"])) diff --git a/targets/simulators/aer/test/test.cfg b/targets/simulators/aer/test/test.cfg new file mode 100644 index 000000000..54d8e8b15 --- /dev/null +++ b/targets/simulators/aer/test/test.cfg @@ -0,0 +1,5 @@ +{ + "method" : "statevector", + "device" : "cpu", + "precision" : "double" +} \ No newline at end of file