diff --git a/.github/workflows/slurm.yml b/.github/workflows/slurm.yml index a0e77de002..ecd42b8372 100644 --- a/.github/workflows/slurm.yml +++ b/.github/workflows/slurm.yml @@ -80,7 +80,7 @@ jobs: - name: Build the MQT Core wheel timeout-minutes: 15 - run: uv build --wheel --out-dir test/slurm/dist + run: uv build --wheel --out-dir test/slurm/dist -Ccmake.define.DEPLOY=ON - name: Test Slurm admission and QDMI execution timeout-minutes: 15 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8777836052..654c661958 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,8 @@ endif() # Add path for custom modules list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +option(BUILD_MQT_CORE_TESTS "Build tests for the MQT Core project" ${PROJECT_IS_TOP_LEVEL}) + include(AddMQTPythonBinding) include(StandardProjectSettings) include(PreventInSourceBuilds) @@ -55,7 +57,6 @@ if(BUILD_MQT_CORE_BINDINGS) endif() option(MQT_CORE_INSTALL "Generate installation instructions for MQT Core" ${PROJECT_IS_TOP_LEVEL}) -option(BUILD_MQT_CORE_TESTS "Build tests for the MQT Core project" ${PROJECT_IS_TOP_LEVEL}) option(BUILD_MQT_CORE_SHARED_LIBS "Build MQT Core libraries as shared libraries" ${BUILD_SHARED_LIBS}) option(BUILD_MQT_CORE_MLIR "Build the MQT Core MLIR compiler infrastructure" ON) @@ -116,7 +117,10 @@ if(MQT_CORE_INSTALL) set(CMAKE_INSTALL_RPATH ${BASEPOINT} ${BASEPOINT}/${CMAKE_INSTALL_LIBDIR} ${BASEPOINT}/../${CMAKE_INSTALL_LIBDIR}) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) - set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + if(APPLE) + # Avoid invalidating code signatures by rewriting Mach-O libraries during installation. + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + endif() endif() # add main library code @@ -166,17 +170,18 @@ if(BUILD_MQT_CORE_MLIR) COMMENT "Copying and cleaning up generated MLIR documentation" VERBATIM) endif() - foreach(binding dd qdmi) - add_dependencies(${MQT_CORE_TARGET_NAME}-${binding}-bindings mqt-core-docs) - endforeach() + if(BUILD_MQT_CORE_BINDINGS) + foreach(binding dd qdmi) + add_dependencies(${MQT_CORE_TARGET_NAME}-${binding}-bindings mqt-core-docs) + endforeach() + endif() endif() endif() if(BUILD_MQT_CORE_BINDINGS) - set(MQT_CORE_WHEEL_TARGETS mqt-core-bench-bindings mqt-core-bench-library mqt-core-dd - mqt-core-dd-bindings mqt-core-qdmi-bindings) + set(MQT_CORE_WHEEL_TARGETS mqt-core-bench-bindings mqt-core-dd-bindings mqt-core-qdmi-bindings) if(BUILD_MQT_CORE_MLIR) - list(APPEND MQT_CORE_WHEEL_TARGETS mqt-core-bench mqt-core-mlir-bindings) + list(APPEND MQT_CORE_WHEEL_TARGETS mqt-cc mqt-core-bench mqt-core-mlir-bindings) endif() if(BUILD_MQT_CORE_MLIR AND BUILD_MQT_CORE_QDMI_DDSIM_DEVICE) list(APPEND MQT_CORE_WHEEL_TARGETS mqt-core-qdmi-ddsim-device) diff --git a/bindings/bench/CMakeLists.txt b/bindings/bench/CMakeLists.txt index 6dd2c13eaa..2f5323615b 100644 --- a/bindings/bench/CMakeLists.txt +++ b/bindings/bench/CMakeLists.txt @@ -36,7 +36,7 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-bench-bindings) MQT::CoreBench) # install the Python stub files in editable mode for better IDE support - if(SKBUILD_STATE STREQUAL "editable") + if(MQT_CORE_INSTALL AND SKBUILD_STATE STREQUAL "editable") file(GLOB_RECURSE BENCH_PYI_FILES ${PROJECT_SOURCE_DIR}/python/mqt/core/bench/*.pyi) install( FILES ${BENCH_PYI_FILES} diff --git a/bindings/dd/CMakeLists.txt b/bindings/dd/CMakeLists.txt index 03c929f529..387ed64f4d 100644 --- a/bindings/dd/CMakeLists.txt +++ b/bindings/dd/CMakeLists.txt @@ -18,10 +18,10 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-dd-bindings) INSTALL_DIR . LINK_LIBS - MQT::CoreDD) + MQT::CoreDDPython) # install the Python stub file in editable mode for better IDE support - if(SKBUILD_STATE STREQUAL "editable") + if(MQT_CORE_INSTALL AND SKBUILD_STATE STREQUAL "editable") install( FILES ${PROJECT_SOURCE_DIR}/python/mqt/core/dd.pyi DESTINATION . diff --git a/bindings/mlir/CMakeLists.txt b/bindings/mlir/CMakeLists.txt index 106dd3577e..cbeaa3f323 100644 --- a/bindings/mlir/CMakeLists.txt +++ b/bindings/mlir/CMakeLists.txt @@ -85,7 +85,7 @@ if(NOT TARGET ${TARGET_NAME}) MLIRMQTDialect MLIRMQTUtils MLIRQCTranslationSupport - MLIRQCODDFunctionality) + MLIRQCODDFunctionalityPython) if(MQT_QISKIT_CAPI_CANDIDATE_VERSION) target_compile_definitions( @@ -94,7 +94,7 @@ if(NOT TARGET ${TARGET_NAME}) endif() # install the Python stub file in editable mode for better IDE support - if(SKBUILD_STATE STREQUAL "editable") + if(MQT_CORE_INSTALL AND SKBUILD_STATE STREQUAL "editable") install( FILES ${PROJECT_SOURCE_DIR}/python/mqt/core/mlir.pyi DESTINATION . diff --git a/cmake/AddMQTCoreLibrary.cmake b/cmake/AddMQTCoreLibrary.cmake index 10d5f31c5d..6861bfb960 100644 --- a/cmake/AddMQTCoreLibrary.cmake +++ b/cmake/AddMQTCoreLibrary.cmake @@ -32,7 +32,7 @@ function(add_mqt_core_library name) elseif(BUILD_MQT_CORE_SHARED_LIBS) add_library(${name} SHARED ${ARG_UNPARSED_ARGUMENTS}) else() - add_library(${name} ${ARG_UNPARSED_ARGUMENTS}) + add_library(${name} STATIC ${ARG_UNPARSED_ARGUMENTS}) endif() if(NOT ARG_ALIAS_NAME) @@ -42,9 +42,16 @@ function(add_mqt_core_library name) endif() add_library(MQT::Core${ARG_ALIAS_NAME} ALIAS ${name}) + get_target_property(target_type ${name} TYPE) + if(target_type STREQUAL "STATIC_LIBRARY") + string(TOUPPER "MQT_CORE_${ARG_ALIAS_NAME}_STATIC_DEFINE" static_define) + target_compile_definitions(${name} PUBLIC ${static_define}) + endif() + target_compile_features(${name} PUBLIC cxx_std_20) - target_link_libraries(${name} PRIVATE MQT::ProjectWarnings MQT::ProjectOptions) + target_link_libraries(${name} + PRIVATE "$") if(ARG_HIDDEN_VISIBILITY) set_target_properties( @@ -53,6 +60,14 @@ function(add_mqt_core_library name) CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1 WINDOWS_EXPORT_ALL_SYMBOLS OFF) + elseif(WIN32) + if(target_type STREQUAL "SHARED_LIBRARY") + set_target_properties(${name} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + # CMake's automatic export scanner cannot read MSVC /GL objects. + set_target_properties(${name} PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF) + endif() + endif() endif() # Always compile with position-independent code to enable usage in shared libraries diff --git a/cmake/AddMQTPythonBinding.cmake b/cmake/AddMQTPythonBinding.cmake index 2406ceb981..701ea64651 100644 --- a/cmake/AddMQTPythonBinding.cmake +++ b/cmake/AddMQTPythonBinding.cmake @@ -15,6 +15,8 @@ function(add_mqt_python_binding package_name target_name) ${target_name} # Enable free-threaded support FREE_THREADED + # Keep computation in bindings optimized for speed. + NOMINSIZE # Suppress compiler warnings from the nanobind library NB_SUPPRESS_WARNINGS # Use nanobind's shared runtime. Split mode enables the Stable ABI internally. @@ -40,11 +42,11 @@ function(add_mqt_python_binding package_name target_name) # Keep statically linked dependencies local. if(APPLE) + # Restore the Python module namespace changed by HandleLLVMOptions, as in AddMLIRPython. + target_link_options(${target_name} PRIVATE "LINKER:-twolevel_namespace") target_link_options(${target_name} PRIVATE "LINKER:-exported_symbol,_PyInit_${module_name}") elseif(UNIX) target_link_options(${target_name} PRIVATE "LINKER:--exclude-libs,ALL") - elseif(WIN32) - set_target_properties(${target_name} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS OFF) endif() # Add project libraries to the link libraries @@ -58,8 +60,10 @@ function(add_mqt_python_binding package_name target_name) endif() # Install directive for scikit-build-core - install( - TARGETS ${target_name} - DESTINATION ${ARG_INSTALL_DIR} - COMPONENT ${MQT_${package_name}_TARGET_NAME}_Python) + if(MQT_CORE_INSTALL) + install( + TARGETS ${target_name} + DESTINATION ${ARG_INSTALL_DIR} + COMPONENT ${MQT_${package_name}_TARGET_NAME}_Python) + endif() endfunction() diff --git a/cmake/AddMQTQDMIDevice.cmake b/cmake/AddMQTQDMIDevice.cmake index 299208b471..bf1c907704 100644 --- a/cmake/AddMQTQDMIDevice.cmake +++ b/cmake/AddMQTQDMIDevice.cmake @@ -41,11 +41,6 @@ function(mqt_configure_qdmi_device target) foreach(runtime_file IN LISTS ARG_RUNTIME_FILES) get_filename_component(runtime_file_name "${runtime_file}" NAME) list(APPEND runtime_file_names "${runtime_file_name}") - add_custom_command( - TARGET ${target} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${runtime_file}" - "$/${runtime_file_name}") endforeach() set(device_entries @@ -98,11 +93,14 @@ function(mqt_configure_qdmi_device target) "{\n \"schema-version\": 1,\n \"qdmi\": {\n \"devices\": [\n${device_entries}\n ]\n }\n}\n" ) - add_custom_command( - TARGET ${target} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${fragment}" - "$/${target}.qdmi.json") + # Assets can change without relinking the device. + add_custom_target( + ${target}-qdmi-assets + COMMAND ${CMAKE_COMMAND} -E make_directory "$" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${fragment}" ${ARG_RUNTIME_FILES} + "$" + COMMAND_EXPAND_LISTS VERBATIM) + add_dependencies(${target} ${target}-qdmi-assets) set_target_properties( ${target} PROPERTIES QDMI_DEVICE_ID "${ARG_ID}" @@ -122,19 +120,11 @@ function(mqt_configure_qdmi_device target) else() set(fragment_install_dir ${CMAKE_INSTALL_LIBDIR}) endif() - set(install_arguments) - if(MQT_CORE_TARGET_NAME) - list(APPEND install_arguments COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) - endif() - install( - FILES "${fragment}" - DESTINATION ${fragment_install_dir} - ${install_arguments}) - if(ARG_RUNTIME_FILES) + if(MQT_CORE_INSTALL) install( - FILES ${ARG_RUNTIME_FILES} + FILES "${fragment}" ${ARG_RUNTIME_FILES} DESTINATION ${fragment_install_dir} - ${install_arguments}) + COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) endif() endfunction() @@ -151,7 +141,9 @@ function(mqt_copy_qdmi_runtime target) if(NOT TARGET ${target}) message(FATAL_ERROR "Unknown QDMI application target: ${target}") endif() - set_property(TARGET ${target} PROPERTY BUILD_WITH_INSTALL_RPATH FALSE) + if(NOT (APPLE AND SKBUILD)) + set_property(TARGET ${target} PROPERTY BUILD_WITH_INSTALL_RPATH FALSE) + endif() set(devices ${ARGN}) if(NOT devices) mqt_get_qdmi_device_targets(devices) @@ -178,22 +170,15 @@ function(mqt_copy_qdmi_runtime target) if(library_target STREQUAL "${target}" OR NOT library_type MATCHES "^(SHARED|MODULE)_LIBRARY$") continue() endif() + string(MAKE_C_IDENTIFIER "${target}-stage-${library_target}" staging_target) + if(TARGET ${staging_target}) + continue() + endif() get_target_property(imported ${library_target} IMPORTED) set(files "$") - if(WIN32) - if(imported) - # TARGET_RUNTIME_DLLS needs a local target to traverse imported dependencies. - string(MAKE_C_IDENTIFIER "${library_target}-dependencies" dependency_target) - if(NOT TARGET ${dependency_target}) - add_library(${dependency_target} MODULE EXCLUDE_FROM_ALL - "${CMAKE_CURRENT_FUNCTION_LIST_FILE}") - set_property(TARGET ${dependency_target} PROPERTY LINKER_LANGUAGE CXX) - target_link_libraries(${dependency_target} PRIVATE ${library}) - endif() - set(files "$") - else() - list(APPEND files "$") - endif() + if(UNIX AND library_type STREQUAL "SHARED_LIBRARY") + # Source installations may use a versioned SONAME in linked applications. + list(APPEND files "$") endif() if(library IN_LIST devices) get_target_property(manifest_name ${library_target} QDMI_MANIFEST_NAME) @@ -228,22 +213,14 @@ function(mqt_copy_qdmi_runtime target) endforeach() endif() endif() - if(NOT imported) - add_dependencies(${target} ${library_target}) - endif() - add_custom_command( - TARGET ${target} - POST_BUILD + add_custom_target( + ${staging_target} + COMMAND ${CMAKE_COMMAND} -E make_directory "$" COMMAND ${CMAKE_COMMAND} -E copy_if_different ${files} "$" - COMMAND_EXPAND_LISTS) - if(NOT WIN32 AND imported) - add_custom_command( - TARGET ${target} - POST_BUILD - COMMAND - ${CMAKE_COMMAND} "-DLIBRARY=$" - "-DDESTINATION=$" -P - "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CopyQDMISharedDependencies.cmake") + COMMAND_EXPAND_LISTS VERBATIM) + if(NOT imported) + add_dependencies(${staging_target} ${library_target}) endif() + add_dependencies(${target} ${staging_target}) endforeach() endfunction() diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 55f8f8be27..08df146a25 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -10,6 +10,12 @@ function(enable_project_options target_name) include(CheckCXXCompilerFlag) + if(APPLE) + target_link_options(${target_name} INTERFACE "$<$>:LINKER:-dead_strip>") + elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(${target_name} INTERFACE "$<$>:LINKER:--gc-sections>") + endif() + if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") option(ENABLE_BUILD_WITH_TIME_TRACE "Enable -ftime-trace to generate time tracing .json files on clang" OFF) diff --git a/cmake/CopyQDMISharedDependencies.cmake b/cmake/CopyQDMISharedDependencies.cmake deleted file mode 100644 index 05cf21fe1d..0000000000 --- a/cmake/CopyQDMISharedDependencies.cmake +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -# Copy dependencies shipped beside a shared library, leaving system libraries in place. -file(REAL_PATH "${LIBRARY}" library) -cmake_path(GET library PARENT_PATH library_directory) -file(GET_RUNTIME_DEPENDENCIES LIBRARIES "${library}" RESOLVED_DEPENDENCIES_VAR dependencies) -foreach(dependency IN LISTS dependencies) - cmake_path(GET dependency PARENT_PATH directory) - if(directory STREQUAL library_directory) - file( - COPY "${dependency}" - DESTINATION "${DESTINATION}" - FOLLOW_SYMLINK_CHAIN) - endif() -endforeach() diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 6c0762e29d..7c96a7e4dc 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -36,6 +36,8 @@ if(BUILD_MQT_CORE_MLIR) # jeff's transitive Cap'n Proto dependency contains source files that cannot share a unity # translation unit. Keep the complete dependency subtree out of unity builds. set(CMAKE_UNITY_BUILD OFF) + # Cap'n Proto requires compiler extensions. + set(CMAKE_CXX_EXTENSIONS ON) FetchContent_MakeAvailable(jeff-mlir) endblock() if(MSVC AND TARGET capnp) diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake index 469649865b..fc2837f381 100644 --- a/cmake/StandardProjectSettings.cmake +++ b/cmake/StandardProjectSettings.cmake @@ -21,8 +21,8 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) endif() # Require C++ standard -set_property(GLOBAL PROPERTY CMAKE_CXX_STANDARD_REQUIRED ON) -set_property(GLOBAL PROPERTY CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) # Generate compile_commands.json to make it easier to work with clang based tools set(CMAKE_EXPORT_COMPILE_COMMANDS @@ -49,11 +49,6 @@ if(DEFINED ENV{DEPLOY}) $ENV{DEPLOY} CACHE BOOL "Use deployment configuration from environment" FORCE) message(STATUS "Setting deployment configuration to '${DEPLOY}' from environment") -elseif(DEFINED ENV{CI}) - set(DEPLOY - ON - CACHE BOOL "Set deployment configuration to ON for CI" FORCE) - message(STATUS "Setting deployment configuration to '${DEPLOY}' for CI") endif() # set deployment specific options @@ -64,8 +59,10 @@ if(DEPLOY) CACHE STRING "" FORCE) endif() -# try to enable inter-procedural optimization per default for Release builds outside of deployment -if(NOT DEPLOY AND CMAKE_BUILD_TYPE STREQUAL "Release") +# MSVC /GL libraries repeat code generation at every test executable's link. +if(NOT DEPLOY + AND CMAKE_BUILD_TYPE STREQUAL "Release" + AND NOT (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND BUILD_MQT_CORE_TESTS)) option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" ON) else() option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) @@ -82,9 +79,4 @@ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${ipo_supported} CACHE BOOL "Enable Interprocedural Optimization" FORCE) -# export all symbols by default on Windows -set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS - ON - CACHE BOOL "Export all symbols on Windows") - set(CMAKE_CXX_SCAN_FOR_MODULES OFF) diff --git a/cmake/mqt-core-config.cmake.in b/cmake/mqt-core-config.cmake.in index 5af39c2b12..d8b4fa3d7d 100644 --- a/cmake/mqt-core-config.cmake.in +++ b/cmake/mqt-core-config.cmake.in @@ -10,17 +10,28 @@ @PACKAGE_INIT@ -include(CMakeFindDependencyMacro) -find_dependency(qdmi) - -if(TARGET MQT::CoreDD) +set(mqt-core_FOUND TRUE) +set(mqt-core_Runtime_FOUND TRUE) +set(mqt-core_Development_FOUND FALSE) +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/mqt-core-development-targets.cmake") + set(mqt-core_Development_FOUND TRUE) +elseif(mqt-core_FIND_REQUIRED_Development) + set(mqt-core_NOT_FOUND_MESSAGE + "The Development component requires an MQT Core source installation.") +endif() +check_required_components(mqt-core) +if(NOT mqt-core_FOUND) return() endif() -include("${CMAKE_CURRENT_LIST_DIR}/AddMQTPythonBinding.cmake") +include(CMakeFindDependencyMacro) +find_dependency(qdmi) + include("${CMAKE_CURRENT_LIST_DIR}/AddMQTQDMIDevice.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/PackageAddTest.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/mqt-core-targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/mqt-core-runtime-targets.cmake") +if(mqt-core_Development_FOUND) + include("${CMAKE_CURRENT_LIST_DIR}/mqt-core-development-targets.cmake") +endif() if(NOT mqt-core_FIND_QUIETLY) message(STATUS "Found mqt-core version ${mqt-core_VERSION}") diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 653fbf31ee..4929bba01f 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -215,8 +215,9 @@ The output format changes the file name, but not the semantic case ID. ## C++ API -The installed `MQT::CoreBench` target provides typed parameters, references, -evaluation, instances, instance specifications, and manifests. +A source installation provides the `MQT::CoreBench` target with typed +parameters, references, evaluation, instances, instance specifications, and +manifests. ```cpp #include "bench/Grover.hpp" @@ -231,7 +232,7 @@ int main() { ``` ```cmake -find_package(mqt-core CONFIG REQUIRED) +find_package(mqt-core CONFIG REQUIRED COMPONENTS Development) target_link_libraries(my-benchmark PRIVATE MQT::CoreBench) ``` diff --git a/docs/cpp_api.md b/docs/cpp_api.md index 2ce64c347c..646bc96361 100644 --- a/docs/cpp_api.md +++ b/docs/cpp_api.md @@ -10,63 +10,60 @@ The native C++ API reference documents the installed public headers, including decision diagrams, benchmarks, and QDMI. It is generated from the same source revision as this guide. -## Use the DD library +## Use the wheel's native runtime -Create a two-qubit GHZ state, which is a Bell state, and print its amplitudes. -Save this as `main.cpp`: +The Python wheel supplies the QDMI driver, device bundles, and the `mqt-cc` and +`mqt-core-bench` executables. Its CMake package exposes the QDMI C interfaces; +C++ DD, QDMI client, and benchmark libraries require a source installation. + +This application allocates a session through the builtin driver's C interface. +Save it as `main.cpp`: ```cpp -#include "dd/Edge.hpp" -#include "dd/Package.hpp" -#include "dd/StateGeneration.hpp" +#include #include int main() { - dd::Package package(2); - const auto state = dd::makeGHZState(2, package); - for (const auto amplitude : dd::getVector(state)) { - std::cout << amplitude << '\n'; + QDMI_Session session = nullptr; + if (QDMI_session_alloc(&session) != QDMI_SUCCESS) { + return 1; } - package.decRef(state); + QDMI_session_free(session); + std::cout << "QDMI driver available\n"; } ``` -The DD package owns its nodes. The state returned by `makeGHZState` holds a -reference until `decRef` releases it. Keep the package alive while using the -state. Converting a DD to a dense vector takes space exponential in the number -of qubits; this example has only four amplitudes. - Save the following as `CMakeLists.txt`: ```cmake cmake_minimum_required(VERSION 3.28) -project(dd-example LANGUAGES CXX) -find_package(mqt-core CONFIG REQUIRED) -add_executable(dd-example main.cpp) -target_link_libraries(dd-example PRIVATE MQT::CoreDD) +project(qdmi-example LANGUAGES CXX) +find_package(mqt-core CONFIG REQUIRED COMPONENTS Runtime) +add_executable(qdmi-example main.cpp) +target_link_libraries(qdmi-example PRIVATE MQT::CoreQDMIDriver) +mqt_copy_qdmi_runtime(qdmi-example MQT::CoreQDMI_DDSIM_Device) ``` -With the Python wheel installed in the active environment, CMake and Ninja -available, run: +With the wheel installed in the active environment, run: ```console cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH="$(mqt-core-cli --cmake_dir)" cmake --build build -./build/dd-example +./build/qdmi-example ``` -The last command is for a Unix shell; on Windows, run `build\dd-example.exe`. -The output has amplitude $1/\sqrt{2}$ at `00` and `11`, and zero at `01` and -`10`. For a separate C++ installation, point `CMAKE_PREFIX_PATH` at its install -prefix instead. See {doc}`installation` for source builds and other CMake -integration options. +On Windows, run `build\qdmi-example.exe`. The application prints +`QDMI driver available`. No LLVM/MLIR installation is required. + +CMake also provides `MQT::mqt-cc` and `MQT::mqt-core-bench` imported executable +targets. Use them in custom commands, or run `mqt-cc` and `mqt-core-bench` from +the environment's command line. ```{code-cell} ipython3 :tags: [remove-cell] from pathlib import Path from tempfile import TemporaryDirectory -import math import os import subprocess @@ -87,14 +84,25 @@ with TemporaryDirectory() as directory: subprocess.run( ["cmake", "--build", str(root / "build")], check=True, stderr=subprocess.STDOUT ) - executable = root / "build" / ("dd-example.exe" if os.name == "nt" else "dd-example") + executable = root / "build" / ("qdmi-example.exe" if os.name == "nt" else "qdmi-example") result = subprocess.run([str(executable)], check=True, capture_output=True, text=True) - amplitudes = [complex(*map(float, line.strip("()").split(","))) for line in result.stdout.splitlines()] - expected = [1 / math.sqrt(2), 0, 0, 1 / math.sqrt(2)] - assert len(amplitudes) == len(expected) - assert all(abs(actual - ideal) < 1e-6 for actual, ideal in zip(amplitudes, expected)) + assert result.stdout.strip() == "QDMI driver available" ``` +## Use the C++ development libraries + +Build and install MQT Core from source, then request the `Development` +component. It provides `MQT::CoreDD`, `MQT::CoreQDMI`, and `MQT::CoreBench`. The +DD library is static; compile consumers with a compatible C++ toolchain. + +```cmake +find_package(mqt-core CONFIG REQUIRED COMPONENTS Development) +target_link_libraries(my-application PRIVATE MQT::CoreDD) +``` + +Point `CMAKE_PREFIX_PATH` at the source installation's prefix. See +{doc}`installation` for source builds and other CMake integration options. + ## Extend the compiler or QIR runtime The MLIR compiler and QIR runtime use source-tree C++ interfaces. They are not diff --git a/docs/index.md b/docs/index.md index 91ae0b3e13..17984cf018 100644 --- a/docs/index.md +++ b/docs/index.md @@ -84,7 +84,8 @@ Move between Qiskit, OpenQASM, jeff, and QIR text or bitcode. Use the C++ libraries or work on the MLIR compiler infrastructure. -- **First example:** [C++ library quickstart](cpp_api.md#use-the-dd-library) +- **First example:** + [Native runtime quickstart](cpp_api.md#use-the-wheels-native-runtime) - **Guide:** [C++ compilation](mlir/target_compilation.md#c-source-tree-api) and [compiler development](development.md#mlir) - **API:** {doc}`C++ reference ` and diff --git a/docs/installation.md b/docs/installation.md index 841d41e868..9961056001 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -74,10 +74,21 @@ python -c "import mqt.core; print(mqt.core.__version__)" This prints the installed package version. -## Building from Source for Performance +## Build performance -To get the best performance and enable platform-specific optimizations not -available in portable wheels, we recommend building the library from source: +Release wheels use portable CPU settings and the assertion-free LLVM/MLIR 23.1.2 +SDK. Linux wheels target manylinux_2_28 and use Clang 22 with LLD and ThinLTO; +macOS wheels use Apple Clang and ThinLTO with a macOS 13.3 deployment target. +Windows wheels use MSVC with IPO, except for DLLs that require automatic symbol +exports. Wheels include the QDMI driver and device bundles, `mqt-cc`, and +`mqt-core-bench`. Their CMake package exposes the QDMI C interfaces and tool +targets without requiring LLVM/MLIR. See {doc}`cpp_api` for native consumers. +C++ development libraries and headers are available from source installations; +the DD library is static. + +### Building from source + +Build from source to tune Core for the machine that will run it: ::::{tab-set} :sync-group: installer @@ -106,6 +117,37 @@ This requires a C++20-capable [C++ compiler](https://en.wikipedia.org/wiki/List_of_compilers#C++_compilers) and [CMake](https://cmake.org/) 3.28 or newer. +Release source builds default to `DEPLOY=OFF`, which enables native CPU tuning +and LTO when the compiler supports them. On Linux, Clang with its matching LLD +linker is a useful choice; on macOS, use Apple Clang from Xcode. For example, +with Clang 23 installed on Linux: + +```console +CC=clang-23 CXX=clang++-23 uv pip install mqt.core --no-binary mqt.core \ + -Ccmake.define.CMAKE_LINKER_TYPE=LLD +``` + +Keep native builds on compatible CPUs. For redistribution, set +`-Ccmake.define.DEPLOY=ON` and choose the target platform's compiler and system +baseline. Cibuildwheel sets deployment mode explicitly for release wheels. The +`DEPLOY` environment variable overrides the CMake setting. + +Clang and Apple Clang use ThinLTO through CMake's `ENABLE_IPO` option. For a +local C++ build, `cmake --preset release` selects the same release defaults; +pass `-DENABLE_IPO=OFF` to disable LTO. GCC can use mold 3 or newer with +`-DCMAKE_LINKER_TYPE=MOLD`. Clang with mold also needs a matching LLVM LTO +plugin; LLD includes the required support. MSVC IPO applies to static libraries +and DLLs with explicit exports; CMake cannot extract automatic exports from MSVC +IPO objects. MSVC builds with native tests default IPO off because each test +link otherwise repeats code generation from the libraries. Use `-DENABLE_IPO=ON` +to test IPO explicitly. Builds without native tests and release wheels retain +IPO. + +Native tuning and LTO apply to the Core code being compiled. Prebuilt LLVM/MLIR +SDK libraries retain their own build settings, and LTO does not optimize across +separate shared libraries. Benchmark your application before changing the +compiler or LTO settings. + ## Integrating MQT Core into Your Project To use the MQT Core Python package in your project, add it as a dependency in @@ -186,7 +228,7 @@ FetchContent_Declare( mqt-core GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/core.git GIT_TAG ${MQT_CORE_REV} - FIND_PACKAGE_ARGS ${MQT_CORE_MINIMUM_VERSION}) + FIND_PACKAGE_ARGS ${MQT_CORE_MINIMUM_VERSION} COMPONENTS Development) list(APPEND FETCH_PACKAGES mqt-core) # Make all declared dependencies available. @@ -231,7 +273,7 @@ Then, in your project's {code}`CMakeLists.txt`, use {code}`find_package()` to locate the installed library: ```cmake -find_package(mqt-core REQUIRED) +find_package(mqt-core REQUIRED COMPONENTS Development) ``` ::: diff --git a/docs/qdmi/configuration.md b/docs/qdmi/configuration.md index 858811a4b9..c881e7f645 100644 --- a/docs/qdmi/configuration.md +++ b/docs/qdmi/configuration.md @@ -234,24 +234,27 @@ A cluster can configure more than one license for a device. For example, The count is a Slurm admission limit. It is not an access permission, a device availability check, or a device queue length. -## Installed C++ applications +## Native applications -The MQT Core Python distribution also supplies a CMake package. Use -`find_package(mqt-core)` to link its C++ QDMI library and copy the driver and -selected devices beside your application: +The Python wheel supplies a runtime CMake package for the builtin driver, QDMI +devices, and tools. Use `MQT::CoreQDMIDriver` to link the QDMI C client +interface; see {doc}`../cpp_api` for a complete example. + +The C++ QDMI wrapper requires a source installation. Request its `Development` +component and copy the driver and selected devices beside your application: ```cmake -find_package(mqt-core CONFIG REQUIRED) +find_package(mqt-core CONFIG REQUIRED COMPONENTS Development) add_executable(my-application main.cpp) target_link_libraries(my-application PRIVATE MQT::CoreQDMI) mqt_copy_qdmi_runtime(my-application MQT::CoreQDMIScDevice MQT::CoreQDMI_DDSIM_Device) ``` -The helper copies shared libraries, device manifests, and configuration files. -It also copies DLL dependencies on Windows and dependencies shipped beside -installed libraries on Linux and macOS. Static libraries are linked into the -application and need no copy. The application uses its build RPATH during the -build. This also works with a source installation of MQT Core. +The helper copies each library and its declared bundle files, including +manifests, configuration files, and the DDSIM worker. Builtin devices statically +link their implementation dependencies. Keep each bundle together when moving +it; supported platform runtimes are still required. The application uses its +build RPATH during the build. Manifests contain library filenames relative to their own directory. Keep each manifest beside its device library when moving an installation. The MQT Core @@ -272,3 +275,5 @@ configure_qdmi_device_target(TARGET example-device ID example.device PREFIX EXAM For such a target, `mqt_copy_qdmi_runtime` generates the manifest. Targets with an existing manifest can export `QDMI_MANIFEST_NAME` instead. Additional files listed in `QDMI_RUNTIME_FILES` are copied from the device library's directory. +Providers must declare any companion files their device needs; the helper does +not discover transitive shared-library dependencies. diff --git a/docs/qdmi/driver.md b/docs/qdmi/driver.md index aea5e319d5..708d9ffdb8 100644 --- a/docs/qdmi/driver.md +++ b/docs/qdmi/driver.md @@ -8,10 +8,10 @@ mystnb: # Using QDMI drivers -The C++ QDMI library (`MQT::CoreQDMI`) provides owning wrappers for the standard -QDMI Client Interface. Applications can replace the driver without rebuilding. -The library calls every driver through that interface. Each driver handles its -own device implementations, configuration, and authorization. +The source-installed C++ QDMI library (`MQT::CoreQDMI`) provides owning wrappers +for the standard QDMI Client Interface. Applications can replace the driver +without rebuilding. The library calls every driver through that interface. Each +driver handles its own device implementations, configuration, and authorization. The builtin MQT Core QDMI driver (`MQT::CoreQDMIDriver`, exposed in Python as `builtin_driver`) loads devices such as [the SC device](sc_device.md) and diff --git a/docs/qdmi/slurm.md b/docs/qdmi/slurm.md index 79142d969c..0ab6e59132 100644 --- a/docs/qdmi/slurm.md +++ b/docs/qdmi/slurm.md @@ -236,7 +236,7 @@ From a source checkout on a Linux Docker host with cgroup v2, build one wheel and run the fixture: ```console -uv build --wheel --out-dir test/slurm/dist +uv build --wheel --out-dir test/slurm/dist -Ccmake.define.DEPLOY=ON uv run --no-project --python 3.14 test/slurm/run_integration.py ``` diff --git a/docs/qir/index.md b/docs/qir/index.md index 67d20d5a25..57fe135b19 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -301,6 +301,12 @@ random-number generator, and output settings. QIR jobs can therefore execute concurrently without sharing measurements or output records. DDSIM records result bits directly and formats textual records only when capture is enabled. +QIR runtime functions do not propagate C++ exceptions. Allocations report +invalid resource requests and runtime limits through the QIR error-output +pointer when one is supplied. Other failures, including host-memory exhaustion, +stop the isolated DDSIM worker and fail its assigned program; concurrent and +later jobs remain usable. + ### Sampling and state extraction Sampling supports Base and Adaptive formats. With output capture disabled, for diff --git a/mlir/CMakeLists.txt b/mlir/CMakeLists.txt index 08e76575dd..f235e5313f 100644 --- a/mlir/CMakeLists.txt +++ b/mlir/CMakeLists.txt @@ -10,6 +10,11 @@ if(MSVC) add_compile_definitions(_SILENCE_NONFLOATING_COMPLEX_DEPRECATION_WARNING) endif() +if(NOT MQT_CORE_INSTALL) + # Suppress the install rules added by LLVM's library and tool helpers. + set(LLVM_INSTALL_TOOLCHAIN_ONLY ON) +endif() + function(mqt_mlir_apply_target_options target_name) target_link_libraries(${target_name} PRIVATE MQT::ProjectOptions) llvm_update_compile_flags(${target_name}) diff --git a/mlir/bench/CMakeLists.txt b/mlir/bench/CMakeLists.txt index 57e604da43..ce4758cffd 100644 --- a/mlir/bench/CMakeLists.txt +++ b/mlir/bench/CMakeLists.txt @@ -30,6 +30,8 @@ mlir_check_all_link_libraries(mqt-core-bench) if(MQT_CORE_INSTALL) include(GNUInstallDirs) - install(TARGETS mqt-core-bench RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) + install( + TARGETS mqt-core-bench + EXPORT mqt-core-runtime-targets + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) endif() diff --git a/mlir/include/mqt/Dialect/QIR/Execution/Runtime/QIR.h b/mlir/include/mqt/Dialect/QIR/Execution/Runtime/QIR.h index 8be3fcb7ce..a9a6fe93fc 100644 --- a/mlir/include/mqt/Dialect/QIR/Execution/Runtime/QIR.h +++ b/mlir/include/mqt/Dialect/QIR/Execution/Runtime/QIR.h @@ -21,13 +21,17 @@ #pragma once // NOLINTBEGIN(modernize-use-using) -// NOLINTBEGIN(readability-identifier-naming) +// QIR ABI names are prescribed by the specification. +// NOLINTBEGIN(readability-identifier-naming,bugprone-reserved-identifier) #include #include #ifdef __cplusplus +#define MQT_QIR_NOEXCEPT noexcept extern "C" { +#else +#define MQT_QIR_NOEXCEPT #endif // *** MEASUREMENT RESULTS *** @@ -50,49 +54,54 @@ typedef struct ArrayImpl Array; /// Creates a new 1-dimensional array. The int64_t is the size of each element /// in bytes. The int64_t is the length of the array. The bytes of the new array /// should be set to zero. -Array* __quantum__rt__array_create_1d(int32_t, int64_t); +Array* __quantum__rt__array_create_1d(int32_t, int64_t) MQT_QIR_NOEXCEPT; /// Returns the length of a dimension of the array. The int64_t is the /// zero-based dimension to return the length of; it must be 0 for a /// 1-dimensional array. -int64_t __quantum__rt__array_get_size_1d(const Array*); +int64_t __quantum__rt__array_get_size_1d(const Array*) MQT_QIR_NOEXCEPT; /// Returns a pointer to the element of the array at the zero-based index given /// by the int64_t. Returns nullptr if the index is out of bounds. -int8_t* __quantum__rt__array_get_element_ptr_1d(Array*, int64_t); +int8_t* __quantum__rt__array_get_element_ptr_1d(Array*, + int64_t) MQT_QIR_NOEXCEPT; /// Adds the given integer value to the reference count for the array. /// Deallocates the array if the reference count becomes 0. The behavior is /// undefined if the reference count becomes negative. -void __quantum__rt__array_update_reference_count(Array*, int32_t); +void __quantum__rt__array_update_reference_count(Array*, + int32_t) MQT_QIR_NOEXCEPT; // *** TUPLES *** typedef struct TupleImpl Tuple; /// Allocate a zero-initialized, suitably aligned tuple payload. -Tuple* __quantum__rt__tuple_create(int64_t size); -void __quantum__rt__tuple_update_reference_count(Tuple*, int32_t); +Tuple* __quantum__rt__tuple_create(int64_t size) MQT_QIR_NOEXCEPT; +void __quantum__rt__tuple_update_reference_count(Tuple*, + int32_t) MQT_QIR_NOEXCEPT; // *** QUANTUM INSTRUCTIONSET AND RUNTIME *** // See // https://github.com/qir-alliance/qir-spec/blob/main/specification/Memory_Management.md /// Allocate a single qubit using the QIR 2.1 error-pointer convention. -Qubit* __quantum__rt__qubit_allocate(bool* outError); +Qubit* __quantum__rt__qubit_allocate(bool* outError) MQT_QIR_NOEXCEPT; /// Allocate and release QIR 2.1 caller-owned arrays of qubit pointers. -void __quantum__rt__qubit_array_allocate(int64_t, Qubit**, bool* outError); -void __quantum__rt__qubit_array_release(int64_t, Qubit**); +void __quantum__rt__qubit_array_allocate(int64_t, Qubit**, + bool* outError) MQT_QIR_NOEXCEPT; +void __quantum__rt__qubit_array_release(int64_t, Qubit**) MQT_QIR_NOEXCEPT; /// Allocate and release dynamically managed QIR 2.1 results. -Result* __quantum__rt__result_allocate(bool* outError); -void __quantum__rt__result_release(Result*); -void __quantum__rt__result_array_allocate(int64_t, Result**, bool* outError); -void __quantum__rt__result_array_release(int64_t, Result**); +Result* __quantum__rt__result_allocate(bool* outError) MQT_QIR_NOEXCEPT; +void __quantum__rt__result_release(Result*) MQT_QIR_NOEXCEPT; +void __quantum__rt__result_array_allocate(int64_t, Result**, + bool* outError) MQT_QIR_NOEXCEPT; +void __quantum__rt__result_array_release(int64_t, Result**) MQT_QIR_NOEXCEPT; /// Releases a single qubit. Passing a null pointer as argument should cause a /// runtime failure. -void __quantum__rt__qubit_release(Qubit*); +void __quantum__rt__qubit_release(Qubit*) MQT_QIR_NOEXCEPT; // QUANTUM INSTRUCTION SET // @@ -102,52 +111,65 @@ void __quantum__rt__qubit_release(Qubit*); // a single target directly, otherwise a Tuple of parameters then targets. #define MQT_QIR_DECLARE_1_0(NAME, SUFFIX, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(Qubit*); \ - void __quantum__qis__c##NAME##__##SUFFIX(Qubit*, Qubit*); \ - void __quantum__qis__cc##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*); \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Qubit*); + void __quantum__qis__##NAME##__##SUFFIX(Qubit*) MQT_QIR_NOEXCEPT; \ + void __quantum__qis__c##NAME##__##SUFFIX(Qubit*, Qubit*) MQT_QIR_NOEXCEPT; \ + void __quantum__qis__cc##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Qubit*) MQT_QIR_NOEXCEPT; #define MQT_QIR_DECLARE_1_1(NAME, SUFFIX, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(double, Qubit*); \ - void __quantum__qis__c##NAME##__##SUFFIX(double, Qubit*, Qubit*); \ - void __quantum__qis__cc##NAME##__##SUFFIX(double, Qubit*, Qubit*, Qubit*); \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*); + void __quantum__qis__##NAME##__##SUFFIX(double, Qubit*) MQT_QIR_NOEXCEPT; \ + void __quantum__qis__c##NAME##__##SUFFIX(double, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ + void __quantum__qis__cc##NAME##__##SUFFIX(double, Qubit*, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*) MQT_QIR_NOEXCEPT; #define MQT_QIR_DECLARE_1_2(NAME, SUFFIX, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(double, double, Qubit*); \ - void __quantum__qis__c##NAME##__##SUFFIX(double, double, Qubit*, Qubit*); \ + void __quantum__qis__##NAME##__##SUFFIX(double, double, Qubit*) \ + MQT_QIR_NOEXCEPT; \ + void __quantum__qis__c##NAME##__##SUFFIX(double, double, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ void __quantum__qis__cc##NAME##__##SUFFIX(double, double, Qubit*, Qubit*, \ - Qubit*); \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*); + Qubit*) MQT_QIR_NOEXCEPT; \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*) MQT_QIR_NOEXCEPT; #define MQT_QIR_DECLARE_1_3(NAME, SUFFIX, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(double, double, double, Qubit*); \ + void __quantum__qis__##NAME##__##SUFFIX(double, double, double, Qubit*) \ + MQT_QIR_NOEXCEPT; \ void __quantum__qis__c##NAME##__##SUFFIX(double, double, double, Qubit*, \ - Qubit*); \ + Qubit*) MQT_QIR_NOEXCEPT; \ void __quantum__qis__cc##NAME##__##SUFFIX(double, double, double, Qubit*, \ - Qubit*, Qubit*); \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*); + Qubit*, Qubit*) MQT_QIR_NOEXCEPT; \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*) MQT_QIR_NOEXCEPT; #define MQT_QIR_DECLARE_2_0(NAME, SUFFIX, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(Qubit*, Qubit*); \ - void __quantum__qis__c##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*); \ - void __quantum__qis__cc##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*, Qubit*); \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*); + void __quantum__qis__##NAME##__##SUFFIX(Qubit*, Qubit*) MQT_QIR_NOEXCEPT; \ + void __quantum__qis__c##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ + void __quantum__qis__cc##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*) MQT_QIR_NOEXCEPT; #define MQT_QIR_DECLARE_2_1(NAME, SUFFIX, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(double, Qubit*, Qubit*); \ - void __quantum__qis__c##NAME##__##SUFFIX(double, Qubit*, Qubit*, Qubit*); \ + void __quantum__qis__##NAME##__##SUFFIX(double, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ + void __quantum__qis__c##NAME##__##SUFFIX(double, Qubit*, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ void __quantum__qis__cc##NAME##__##SUFFIX(double, Qubit*, Qubit*, Qubit*, \ - Qubit*); \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*); + Qubit*) MQT_QIR_NOEXCEPT; \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*) MQT_QIR_NOEXCEPT; #define MQT_QIR_DECLARE_2_2(NAME, SUFFIX, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(double, double, Qubit*, Qubit*); \ + void __quantum__qis__##NAME##__##SUFFIX(double, double, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ void __quantum__qis__c##NAME##__##SUFFIX(double, double, Qubit*, Qubit*, \ - Qubit*); \ + Qubit*) MQT_QIR_NOEXCEPT; \ void __quantum__qis__cc##NAME##__##SUFFIX(double, double, Qubit*, Qubit*, \ - Qubit*, Qubit*); \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*); + Qubit*, Qubit*) MQT_QIR_NOEXCEPT; \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*) MQT_QIR_NOEXCEPT; #define MQT_QIR_DECLARE_3_0(NAME, SUFFIX, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*); \ - void __quantum__qis__c##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*, Qubit*); \ + void __quantum__qis__##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ + void __quantum__qis__c##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*, Qubit*) \ + MQT_QIR_NOEXCEPT; \ void __quantum__qis__cc##NAME##__##SUFFIX(Qubit*, Qubit*, Qubit*, Qubit*, \ - Qubit*); \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*); + Qubit*) MQT_QIR_NOEXCEPT; \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array*, Tuple*) MQT_QIR_NOEXCEPT; #define MQT_GATE(KEY, NAME, GETTER, TARGETS, PARAMS, SUFFIX, CTL_SUFFIX) \ MQT_QIR_DECLARE_##TARGETS##_##PARAMS(NAME, SUFFIX, CTL_SUFFIX) @@ -163,61 +185,66 @@ void __quantum__rt__qubit_release(Qubit*); #undef MQT_QIR_DECLARE_3_0 /// Apply an arbitrary global phase. -void __quantum__qis__gphase__body(double); +void __quantum__qis__gphase__body(double) MQT_QIR_NOEXCEPT; /// Common QIS spelling for controlled X. -void __quantum__qis__cnot__body(Qubit*, Qubit*); +void __quantum__qis__cnot__body(Qubit*, Qubit*) MQT_QIR_NOEXCEPT; -void __quantum__qis__mz__body(Qubit*, Result*); -void __quantum__qis__reset__body(Qubit*); +void __quantum__qis__mz__body(Qubit*, Result*) MQT_QIR_NOEXCEPT; +void __quantum__qis__reset__body(Qubit*) MQT_QIR_NOEXCEPT; // cf. // https://github.com/qir-alliance/qir-spec/blob/main/specification/under_development/profiles/Adaptive_Profile.md#runtime-functions /// Initializes the execution environment. Sets all qubits to a zero-state if /// they are not dynamically managed. -void __quantum__rt__initialize(char*); +void __quantum__rt__initialize(char*) MQT_QIR_NOEXCEPT; /// Reads the value of the given measurement result and converts it to a boolean /// value. -bool __quantum__rt__read_result(Result*); +bool __quantum__rt__read_result(Result*) MQT_QIR_NOEXCEPT; /// Adds a measurement result to the generated output. The second parameter /// defines a string label for the result value. Depending on the output schema, /// the label is included in the output or omitted. -void __quantum__rt__result_record_output(Result*, const char*); +void __quantum__rt__result_record_output(Result*, const char*) MQT_QIR_NOEXCEPT; /// Adds a boolean value to the generated output. The second parameter defines /// a string label for the value. Depending on the output schema, the label is /// included in the output or omitted. -void __quantum__rt__bool_record_output(bool, const char*); +void __quantum__rt__bool_record_output(bool, const char*) MQT_QIR_NOEXCEPT; /// Adds an integer value to the generated output. The second parameter defines /// a string label for the value. Depending on the output schema, the label is /// included in the output or omitted. -void __quantum__rt__int_record_output(int64_t, const char*); +void __quantum__rt__int_record_output(int64_t, const char*) MQT_QIR_NOEXCEPT; /// Adds a floating-point value to the generated output. The second parameter /// defines a string label for the value. Depending on the output schema, the /// label is included in the output or omitted. -void __quantum__rt__double_record_output(double, const char*); +void __quantum__rt__double_record_output(double, const char*) MQT_QIR_NOEXCEPT; /// Inserts a marker in the generated output indicating that the next /// \p elementCount recorded values form the contents of a tuple. The second /// parameter defines a string label for the tuple. -void __quantum__rt__tuple_record_output(int64_t elementCount, const char*); +void __quantum__rt__tuple_record_output(int64_t elementCount, + const char*) MQT_QIR_NOEXCEPT; /// Inserts a marker in the generated output indicating that the next \p size /// recorded values form the contents of an array. The second parameter defines /// a string label for the array. -void __quantum__rt__array_record_output(int64_t size, const char*); +void __quantum__rt__array_record_output(int64_t size, + const char*) MQT_QIR_NOEXCEPT; /// Record the contents of a caller-owned result pointer array. -void __quantum__rt__result_array_record_output(int64_t, Result**, const char*); +void __quantum__rt__result_array_record_output(int64_t, Result**, + const char*) MQT_QIR_NOEXCEPT; -// NOLINTEND(readability-identifier-naming) +// NOLINTEND(readability-identifier-naming,bugprone-reserved-identifier) // NOLINTEND(modernize-use-using) #ifdef __cplusplus } // extern "C" #endif + +#undef MQT_QIR_NOEXCEPT diff --git a/mlir/lib/Dialect/QCO/Utils/CMakeLists.txt b/mlir/lib/Dialect/QCO/Utils/CMakeLists.txt index 71890bad49..ae756d1304 100644 --- a/mlir/lib/Dialect/QCO/Utils/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/Utils/CMakeLists.txt @@ -55,56 +55,62 @@ target_sources( FILES ${UTILS_HEADERS_BUILD}) -set(LLVM_REQUIRES_EH ON) -set(LLVM_REQUIRES_RTTI ON) -add_mlir_library( - MLIRQCODDAdapter - PARTIAL_SOURCES_INTENDED - DDAdapter.cpp - DEPENDS - MLIRQCOOpsIncGen - MLIRQCOInterfacesIncGen - LINK_LIBS - PUBLIC - MLIRQCODialect - MLIRQCOMatrix - MQT::CoreDD) +function(mqt_add_dd_adapter suffix dd_target) + set(LLVM_REQUIRES_EH ON) + set(LLVM_REQUIRES_RTTI ON) + add_mlir_library( + MLIRQCODDAdapter${suffix} + PARTIAL_SOURCES_INTENDED + DDAdapter.cpp + DEPENDS + MLIRQCOOpsIncGen + MLIRQCOInterfacesIncGen + LINK_LIBS + PUBLIC + MLIRQCODialect + MLIRQCOMatrix + ${dd_target}) -mqt_mlir_target_use_project_options(MLIRQCODDAdapter) + mqt_mlir_target_use_project_options(MLIRQCODDAdapter${suffix}) -target_sources( - MLIRQCODDAdapter PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES - ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mqt/Dialect/QCO/Utils/DDAdapter.h) + target_sources( + MLIRQCODDAdapter${suffix} + PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mqt/Dialect/QCO/Utils/DDAdapter.h) -add_mlir_library( - MLIRQCODDFunctionality - PARTIAL_SOURCES_INTENDED - DDFunctionality.cpp - DEPENDS - MLIRQCOOpsIncGen - MLIRQCOInterfacesIncGen - LINK_LIBS - PUBLIC - MLIRQCODDAdapter - MLIRQCODialect - MLIRQCOMatrix - MLIRArithDialect - MLIRFuncDialect - MLIRMathDialect - MLIRLLVMDialect - MLIRSCFDialect - PRIVATE - MLIRCBitDialect - MLIRMemRefDialect - MLIRMQTUtils - MLIRTensorDialect - MLIRQTensorDialect) + add_mlir_library( + MLIRQCODDFunctionality${suffix} + PARTIAL_SOURCES_INTENDED + DDFunctionality.cpp + DEPENDS + MLIRQCOOpsIncGen + MLIRQCOInterfacesIncGen + LINK_LIBS + PUBLIC + MLIRQCODDAdapter${suffix} + MLIRQCODialect + MLIRQCOMatrix + MLIRArithDialect + MLIRFuncDialect + MLIRMathDialect + MLIRLLVMDialect + MLIRSCFDialect + PRIVATE + MLIRCBitDialect + MLIRMemRefDialect + MLIRMQTUtils + MLIRTensorDialect + MLIRQTensorDialect) -mqt_mlir_target_use_project_options(MLIRQCODDFunctionality) -unset(LLVM_REQUIRES_EH) -unset(LLVM_REQUIRES_RTTI) + mqt_mlir_target_use_project_options(MLIRQCODDFunctionality${suffix}) -target_sources( - MLIRQCODDFunctionality - PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES - ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mqt/Dialect/QCO/Utils/DDFunctionality.h) + target_sources( + MLIRQCODDFunctionality${suffix} + PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR} FILES + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mqt/Dialect/QCO/Utils/DDFunctionality.h) +endfunction() + +mqt_add_dd_adapter("" MQT::CoreDD) +if(BUILD_MQT_CORE_BINDINGS) + mqt_add_dd_adapter(Python MQT::CoreDDPython) +endif() diff --git a/mlir/lib/Dialect/QIR/Execution/Runtime/QIR.cpp b/mlir/lib/Dialect/QIR/Execution/Runtime/QIR.cpp index f17e79f659..02cc09e7d3 100644 --- a/mlir/lib/Dialect/QIR/Execution/Runtime/QIR.cpp +++ b/mlir/lib/Dialect/QIR/Execution/Runtime/QIR.cpp @@ -21,7 +21,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -42,17 +45,35 @@ struct alignas(std::max_align_t) TupleHeader { } // namespace +[[noreturn]] static void fail(const char* message) noexcept { + std::fputs("QIR runtime failure: ", stderr); + std::fputs(message, stderr); + std::fputc('\n', stderr); + std::abort(); +} + +// Keep C++ exceptions inside the runtime, never across generated QIR frames. +template +static decltype(auto) runtimeCall(Function&& operation) noexcept { + try { + return std::forward(operation)(); + } catch (const std::exception& error) { + fail(error.what()); + } catch (...) { + fail("Unknown runtime failure"); + } +} + static auto getTupleHeader(Tuple* tuple) -> TupleHeader* { return reinterpret_cast(tuple) - 1; } static auto controlsFromArray(Array* array) -> llvm::SmallVector { if (array == nullptr) { - throw std::invalid_argument("QIR control array must not be null"); + fail("QIR control array must not be null"); } if (std::cmp_not_equal(array->elementSize, sizeof(Qubit*))) { - throw std::invalid_argument( - "QIR control array elements must contain qubit pointers"); + fail("QIR control array elements must contain qubit pointers"); } const auto size = __quantum__rt__array_get_size_1d(array); llvm::SmallVector controls(static_cast(size)); @@ -67,15 +88,17 @@ template static auto applyGateMatrix(llvm::ArrayRef parameters, std::span controls, std::span targets) -> void { - auto& runtime = qir::Runtime::getInstance(); - if constexpr (std::is_same_v) { - if (controls.empty() && targets.size() == 2) { - runtime.swap(targets[0], targets[1]); - return; + runtimeCall([&] { + auto& runtime = qir::Runtime::getInstance(); + if constexpr (std::is_same_v) { + if (controls.empty() && targets.size() == 2) { + runtime.swap(targets[0], targets[1]); + return; + } } - } - runtime.apply(mlir::qco::getStandardGateMatrix(parameters), controls, - targets); + runtime.apply(mlir::qco::getStandardGateMatrix(parameters), + controls, targets); + }); } template @@ -102,13 +125,11 @@ static auto applyControlled(Array* controlArray, Qubit* target) -> void { template static auto applyControlledTuple(Array* controls, Tuple* tuple) -> void { if (tuple == nullptr) { - throw std::invalid_argument( - "QIR generic controlled argument tuple must not be null"); + fail("QIR generic controlled argument tuple must not be null"); } const auto validateSize = [&](const std::size_t expected) { if (std::cmp_not_equal(getTupleHeader(tuple)->size, expected)) { - throw std::invalid_argument( - "QIR generic controlled argument tuple has an invalid size"); + fail("QIR generic controlled argument tuple has an invalid size"); } }; @@ -136,33 +157,96 @@ static auto applyControlledTuple(Array* controls, Tuple* tuple) -> void { } } +// Only explicit resource errors leave the runtime reusable. +template +static Resource* allocate(bool* outError) noexcept { + if (outError != nullptr) { + *outError = false; + } + try { + auto& runtime = qir::Runtime::getInstance(); + if constexpr (std::is_same_v) { + return runtime.qAlloc(); + } else { + return runtime.rAlloc(); + } + } catch (const std::logic_error& error) { + if (outError == nullptr) { + fail(error.what()); + } + *outError = true; + return nullptr; + } +} + +template +static void releaseArray(int64_t size, Resource** array) noexcept { + if (size < 0 || (size > 0 && array == nullptr)) { + fail("Invalid QIR resource array release"); + } + runtimeCall([&] { + auto& runtime = qir::Runtime::getInstance(); + for (auto* resource : std::span(array, static_cast(size))) { + if constexpr (std::is_same_v) { + runtime.qFree(resource); + } else { + runtime.rFree(resource); + } + } + }); +} + +template +static void allocateArray(int64_t size, Resource** array, + bool* outError) noexcept { + if (outError != nullptr) { + *outError = false; + } + if (size < 0 || (size > 0 && array == nullptr)) { + if (outError == nullptr) { + fail("Invalid QIR resource array allocation"); + } + *outError = true; + return; + } + for (int64_t i = 0; i < size; ++i) { + array[i] = allocate(outError); + if (outError != nullptr && *outError) { + releaseArray(i, array); + std::fill_n(array, i, nullptr); + return; + } + } +} + extern "C" { // *** ARRAYS *** -Array* __quantum__rt__array_create_1d(const int32_t size, const int64_t n) { +Array* __quantum__rt__array_create_1d(const int32_t size, + const int64_t n) noexcept { if (size <= 0 || n < 0) { - throw std::invalid_argument( - "QIR array element size must be positive and length nonnegative"); + fail("QIR array element size must be positive and length nonnegative"); } const auto elementSize = static_cast(size); const auto length = static_cast(n); constexpr auto maxObjectSize = static_cast(std::numeric_limits::max()); if (length > maxObjectSize / elementSize) { - throw std::length_error("QIR array allocation size overflow"); + fail("QIR array allocation size overflow"); } - auto* array = new Array; + auto array = std::make_unique(); array->refcount = 1; array->data = std::vector(length * elementSize, static_cast(0)); array->elementSize = size; - return array; + return array.release(); } -int64_t __quantum__rt__array_get_size_1d(const Array* array) { +int64_t __quantum__rt__array_get_size_1d(const Array* array) noexcept { return static_cast(array->data.size()) / array->elementSize; } -int8_t* __quantum__rt__array_get_element_ptr_1d(Array* array, const int64_t i) { +int8_t* __quantum__rt__array_get_element_ptr_1d(Array* array, + const int64_t i) noexcept { if (array == nullptr || i < 0 || i >= __quantum__rt__array_get_size_1d(array)) { return nullptr; @@ -171,7 +255,7 @@ int8_t* __quantum__rt__array_get_element_ptr_1d(Array* array, const int64_t i) { } void __quantum__rt__array_update_reference_count(Array* array, - const int32_t k) { + const int32_t k) noexcept { if (array != nullptr) { array->refcount += k; if (array->refcount == 0) { @@ -180,15 +264,15 @@ void __quantum__rt__array_update_reference_count(Array* array, } } -Tuple* __quantum__rt__tuple_create(const int64_t size) { +Tuple* __quantum__rt__tuple_create(const int64_t size) noexcept { if (size < 0) { - throw std::invalid_argument("QIR tuple size must not be negative"); + fail("QIR tuple size must not be negative"); } const auto payloadSize = static_cast(size); constexpr auto maxObjectSize = static_cast(std::numeric_limits::max()); if (payloadSize > maxObjectSize - sizeof(TupleHeader)) { - throw std::length_error("QIR tuple allocation size overflow"); + fail("QIR tuple allocation size overflow"); } const auto bytes = sizeof(TupleHeader) + payloadSize; auto* storage = static_cast( @@ -202,7 +286,7 @@ Tuple* __quantum__rt__tuple_create(const int64_t size) { } void __quantum__rt__tuple_update_reference_count(Tuple* tuple, - const int32_t k) { + const int32_t k) noexcept { if (tuple == nullptr) { return; } @@ -215,221 +299,198 @@ void __quantum__rt__tuple_update_reference_count(Tuple* tuple, } // *** QUANTUM INSTRUCTION SET AND RUNTIME *** -Qubit* __quantum__rt__qubit_allocate(bool* outError) { - if (outError != nullptr) { - *outError = false; - } - auto& runtime = qir::Runtime::getInstance(); - return runtime.qAlloc(); +Qubit* __quantum__rt__qubit_allocate(bool* outError) noexcept { + return allocate(outError); } void __quantum__rt__qubit_array_allocate(const int64_t size, Qubit** array, - bool* outError) { - if (outError != nullptr) { - *outError = false; - } - if (size < 0 || (size > 0 && array == nullptr)) { - if (outError != nullptr) { - *outError = true; - return; - } - throw std::invalid_argument("Invalid QIR qubit array allocation"); - } - for (auto*& qubit : std::span(array, static_cast(size))) { - qubit = qir::Runtime::getInstance().qAlloc(); - } + bool* outError) noexcept { + allocateArray(size, array, outError); } -void __quantum__rt__qubit_array_release(const int64_t size, Qubit** array) { - if (size < 0 || (size > 0 && array == nullptr)) { - throw std::invalid_argument("Invalid QIR qubit array release"); - } - for (Qubit* qubit : std::span(array, static_cast(size))) { - qir::Runtime::getInstance().qFree(qubit); - } +void __quantum__rt__qubit_array_release(const int64_t size, + Qubit** array) noexcept { + releaseArray(size, array); } -Result* __quantum__rt__result_allocate(bool* outError) { - if (outError != nullptr) { - *outError = false; - } - return qir::Runtime::getInstance().rAlloc(); +Result* __quantum__rt__result_allocate(bool* outError) noexcept { + return allocate(outError); } -void __quantum__rt__result_release(Result* result) { - qir::Runtime::getInstance().rFree(result); +void __quantum__rt__result_release(Result* result) noexcept { + return runtimeCall([&] { qir::Runtime::getInstance().rFree(result); }); } void __quantum__rt__result_array_allocate(const int64_t size, Result** array, - bool* outError) { - if (outError != nullptr) { - *outError = false; - } - if (size < 0 || (size > 0 && array == nullptr)) { - if (outError != nullptr) { - *outError = true; - return; - } - throw std::invalid_argument("Invalid QIR result array allocation"); - } - for (auto*& result : std::span(array, static_cast(size))) { - result = qir::Runtime::getInstance().rAlloc(); - } + bool* outError) noexcept { + allocateArray(size, array, outError); } -void __quantum__rt__result_array_release(const int64_t size, Result** array) { - if (size < 0 || (size > 0 && array == nullptr)) { - throw std::invalid_argument("Invalid QIR result array release"); - } - for (Result* result : std::span(array, static_cast(size))) { - qir::Runtime::getInstance().rFree(result); - } +void __quantum__rt__result_array_release(const int64_t size, + Result** array) noexcept { + releaseArray(size, array); } -void __quantum__rt__qubit_release(Qubit* qubit) { - auto& runtime = qir::Runtime::getInstance(); - runtime.qFree(qubit); +void __quantum__rt__qubit_release(Qubit* qubit) noexcept { + return runtimeCall([&] { + auto& runtime = qir::Runtime::getInstance(); + runtime.qFree(qubit); + }); } // QUANTUM INSTRUCTION SET #define MQT_QIR_DEFINE_1_0(KEY, NAME, SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(Qubit* target) { \ + void __quantum__qis__##NAME##__##SUFFIX(Qubit* target) noexcept { \ applyGate(target); \ } \ - void __quantum__qis__c##NAME##__##SUFFIX(Qubit* control, Qubit* target) { \ + void __quantum__qis__c##NAME##__##SUFFIX(Qubit* control, \ + Qubit* target) noexcept { \ applyGate(control, target); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(Qubit* control0, Qubit* control1, \ - Qubit* target) { \ + Qubit* target) noexcept { \ applyGate(control0, control1, target); \ } #define MQT_QIR_DEFINE_1_1(KEY, NAME, SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(double p0, Qubit* target) { \ + void __quantum__qis__##NAME##__##SUFFIX(double p0, Qubit* target) noexcept { \ applyGate(p0, target); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(double p0, Qubit* control, \ - Qubit* target) { \ + Qubit* target) noexcept { \ applyGate(p0, control, target); \ } \ - void __quantum__qis__cc##NAME##__##SUFFIX(double p0, Qubit* control0, \ - Qubit* control1, Qubit* target) { \ + void __quantum__qis__cc##NAME##__##SUFFIX( \ + double p0, Qubit* control0, Qubit* control1, Qubit* target) noexcept { \ applyGate(p0, control0, control1, target); \ } #define MQT_QIR_DEFINE_1_2(KEY, NAME, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(double p0, double p1, \ - Qubit* target) { \ + Qubit* target) noexcept { \ applyGate(p0, p1, target); \ } \ - void __quantum__qis__c##NAME##__##SUFFIX(double p0, double p1, \ - Qubit* control, Qubit* target) { \ + void __quantum__qis__c##NAME##__##SUFFIX( \ + double p0, double p1, Qubit* control, Qubit* target) noexcept { \ applyGate(p0, p1, control, target); \ } \ - void __quantum__qis__cc##NAME##__##SUFFIX( \ - double p0, double p1, Qubit* control0, Qubit* control1, Qubit* target) { \ + void __quantum__qis__cc##NAME##__##SUFFIX(double p0, double p1, \ + Qubit* control0, Qubit* control1, \ + Qubit* target) noexcept { \ applyGate(p0, p1, control0, control1, target); \ } #define MQT_QIR_DEFINE_1_3(KEY, NAME, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(double p0, double p1, double p2, \ - Qubit* target) { \ + Qubit* target) noexcept { \ applyGate(p0, p1, p2, target); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(double p0, double p1, double p2, \ - Qubit* control, Qubit* target) { \ + Qubit* control, \ + Qubit* target) noexcept { \ applyGate(p0, p1, p2, control, target); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(double p0, double p1, double p2, \ Qubit* control0, Qubit* control1, \ - Qubit* target) { \ + Qubit* target) noexcept { \ applyGate(p0, p1, p2, control0, control1, target); \ } #define MQT_QIR_DEFINE_2_0(KEY, NAME, SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(Qubit* target0, Qubit* target1) { \ + void __quantum__qis__##NAME##__##SUFFIX(Qubit* target0, \ + Qubit* target1) noexcept { \ applyGate(target0, target1); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(Qubit* control, Qubit* target0, \ - Qubit* target1) { \ + Qubit* target1) noexcept { \ applyGate(control, target0, target1); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(Qubit* control0, Qubit* control1, \ - Qubit* target0, Qubit* target1) { \ + Qubit* target0, \ + Qubit* target1) noexcept { \ applyGate(control0, control1, target0, target1); \ } #define MQT_QIR_DEFINE_2_1(KEY, NAME, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(double p0, Qubit* target0, \ - Qubit* target1) { \ + Qubit* target1) noexcept { \ applyGate(p0, target0, target1); \ } \ - void __quantum__qis__c##NAME##__##SUFFIX(double p0, Qubit* control, \ - Qubit* target0, Qubit* target1) { \ + void __quantum__qis__c##NAME##__##SUFFIX( \ + double p0, Qubit* control, Qubit* target0, Qubit* target1) noexcept { \ applyGate(p0, control, target0, target1); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(double p0, Qubit* control0, \ Qubit* control1, Qubit* target0, \ - Qubit* target1) { \ + Qubit* target1) noexcept { \ applyGate(p0, control0, control1, target0, \ target1); \ } #define MQT_QIR_DEFINE_2_2(KEY, NAME, SUFFIX) \ - void __quantum__qis__##NAME##__##SUFFIX(double p0, double p1, \ - Qubit* target0, Qubit* target1) { \ + void __quantum__qis__##NAME##__##SUFFIX( \ + double p0, double p1, Qubit* target0, Qubit* target1) noexcept { \ applyGate(p0, p1, target0, target1); \ } \ - void __quantum__qis__c##NAME##__##SUFFIX( \ - double p0, double p1, Qubit* control, Qubit* target0, Qubit* target1) { \ + void __quantum__qis__c##NAME##__##SUFFIX(double p0, double p1, \ + Qubit* control, Qubit* target0, \ + Qubit* target1) noexcept { \ applyGate(p0, p1, control, target0, target1); \ } \ - void __quantum__qis__cc##NAME##__##SUFFIX(double p0, double p1, \ - Qubit* control0, Qubit* control1, \ - Qubit* target0, Qubit* target1) { \ + void __quantum__qis__cc##NAME##__##SUFFIX( \ + double p0, double p1, Qubit* control0, Qubit* control1, Qubit* target0, \ + Qubit* target1) noexcept { \ applyGate(p0, p1, control0, control1, target0, \ target1); \ } #define MQT_QIR_DEFINE_3_0(KEY, NAME, SUFFIX) \ void __quantum__qis__##NAME##__##SUFFIX(Qubit* target0, Qubit* target1, \ - Qubit* target2) { \ + Qubit* target2) noexcept { \ applyGate(target0, target1, target2); \ } \ void __quantum__qis__c##NAME##__##SUFFIX(Qubit* control, Qubit* target0, \ - Qubit* target1, Qubit* target2) { \ + Qubit* target1, \ + Qubit* target2) noexcept { \ applyGate(control, target0, target1, target2); \ } \ void __quantum__qis__cc##NAME##__##SUFFIX(Qubit* control0, Qubit* control1, \ Qubit* target0, Qubit* target1, \ - Qubit* target2) { \ + Qubit* target2) noexcept { \ applyGate(control0, control1, target0, target1, \ target2); \ } #define MQT_QIR_DEFINE_CTL_1_0(KEY, NAME, CTL_SUFFIX) \ void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, \ - Qubit* target) { \ + Qubit* target) noexcept { \ applyControlled(controls, target); \ } #define MQT_QIR_DEFINE_CTL_1_1(KEY, NAME, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, \ + Tuple* args) noexcept { \ applyControlledTuple(controls, args); \ } #define MQT_QIR_DEFINE_CTL_1_2(KEY, NAME, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, \ + Tuple* args) noexcept { \ applyControlledTuple(controls, args); \ } #define MQT_QIR_DEFINE_CTL_1_3(KEY, NAME, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, \ + Tuple* args) noexcept { \ applyControlledTuple(controls, args); \ } #define MQT_QIR_DEFINE_CTL_2_0(KEY, NAME, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, \ + Tuple* args) noexcept { \ applyControlledTuple(controls, args); \ } #define MQT_QIR_DEFINE_CTL_2_1(KEY, NAME, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, \ + Tuple* args) noexcept { \ applyControlledTuple(controls, args); \ } #define MQT_QIR_DEFINE_CTL_2_2(KEY, NAME, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, \ + Tuple* args) noexcept { \ applyControlledTuple(controls, args); \ } #define MQT_QIR_DEFINE_CTL_3_0(KEY, NAME, CTL_SUFFIX) \ - void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, Tuple* args) { \ + void __quantum__qis__##NAME##__##CTL_SUFFIX(Array* controls, \ + Tuple* args) noexcept { \ applyControlledTuple(controls, args); \ } @@ -455,34 +516,41 @@ void __quantum__rt__qubit_release(Qubit* qubit) { #undef MQT_QIR_DEFINE_CTL_2_2 #undef MQT_QIR_DEFINE_CTL_3_0 -void __quantum__qis__gphase__body(const double phase) { +void __quantum__qis__gphase__body(const double phase) noexcept { qir::Runtime::getInstance().applyGlobalPhase(phase); } -void __quantum__qis__cnot__body(Qubit* control, Qubit* target) { +void __quantum__qis__cnot__body(Qubit* control, Qubit* target) noexcept { __quantum__qis__cx__body(control, target); } -void __quantum__qis__mz__body(Qubit* qubit, Result* result) { - auto& runtime = qir::Runtime::getInstance(); - runtime.measure(qubit, result); +void __quantum__qis__mz__body(Qubit* qubit, Result* result) noexcept { + return runtimeCall([&] { + auto& runtime = qir::Runtime::getInstance(); + runtime.measure(qubit, result); + }); } -void __quantum__qis__reset__body(Qubit* qubit) { - auto& runtime = qir::Runtime::getInstance(); - runtime.reset(std::array{qubit}); +void __quantum__qis__reset__body(Qubit* qubit) noexcept { + return runtimeCall([&] { + auto& runtime = qir::Runtime::getInstance(); + runtime.reset(std::array{qubit}); + }); } -void __quantum__rt__initialize(char* /*unused*/) { +void __quantum__rt__initialize(char* /*unused*/) noexcept { qir::Runtime::getInstance().reset(); } -bool __quantum__rt__read_result(Result* result) { - auto& runtime = qir::Runtime::getInstance(); - return runtime.deref(result).r; +bool __quantum__rt__read_result(Result* result) noexcept { + return runtimeCall([&] { + auto& runtime = qir::Runtime::getInstance(); + return runtime.deref(result).r; + }); } -void __quantum__rt__result_record_output(Result* result, const char* label) { +void __quantum__rt__result_record_output(Result* result, + const char* label) noexcept { const bool bit = __quantum__rt__read_result(result); auto& runtime = qir::Runtime::getInstance(); runtime.outputResult(bit, label); @@ -490,34 +558,37 @@ void __quantum__rt__result_record_output(Result* result, const char* label) { runtime.appendMeasurementBit(bit); } -void __quantum__rt__bool_record_output(bool value, const char* label) { +void __quantum__rt__bool_record_output(bool value, const char* label) noexcept { auto& runtime = qir::Runtime::getInstance(); runtime.outputBool(value, label); runtime.appendMeasurementBit(value); } -void __quantum__rt__int_record_output(int64_t value, const char* label) { +void __quantum__rt__int_record_output(int64_t value, + const char* label) noexcept { qir::Runtime::getInstance().outputInt(value, label); } -void __quantum__rt__double_record_output(double value, const char* label) { +void __quantum__rt__double_record_output(double value, + const char* label) noexcept { qir::Runtime::getInstance().outputFloat(value, label); } void __quantum__rt__tuple_record_output(int64_t elementCount, - const char* label) { + const char* label) noexcept { qir::Runtime::getInstance().outputTuple(elementCount, label); } -void __quantum__rt__array_record_output(int64_t size, const char* label) { +void __quantum__rt__array_record_output(int64_t size, + const char* label) noexcept { qir::Runtime::getInstance().outputArray(size, label); } void __quantum__rt__result_array_record_output(const int64_t size, Result** results, - const char* label) { + const char* label) noexcept { if (size < 0 || (size > 0 && results == nullptr)) { - throw std::invalid_argument("Invalid QIR result array output"); + fail("Invalid QIR result array output"); } auto& runtime = qir::Runtime::getInstance(); std::string values; @@ -525,7 +596,7 @@ void __quantum__rt__result_array_record_output(const int64_t size, values.reserve(static_cast(size)); } for (Result* result : std::span(results, static_cast(size))) { - const auto value = runtime.deref(result).r; + const auto value = __quantum__rt__read_result(result); if (runtime.hasOutput()) { values.push_back(value ? '1' : '0'); } diff --git a/mlir/tools/mqt-cc/CMakeLists.txt b/mlir/tools/mqt-cc/CMakeLists.txt index deacb9e471..dfdb7d3805 100644 --- a/mlir/tools/mqt-cc/CMakeLists.txt +++ b/mlir/tools/mqt-cc/CMakeLists.txt @@ -7,10 +7,9 @@ # Licensed under the MIT License # Build the compiler driver executable -add_mlir_tool(mqt-cc mqt-cc.cpp DEPENDS MQTCompilerPipeline MQTCompilerQDMIAdapter SUPPORT_PLUGINS) -if(BUILD_MQT_CORE_TESTS) - # add_mlir_tool excludes tools when LLVM_BUILD_TOOLS is disabled. The driver tests require mqt-cc - # in test-enabled default builds. +add_mlir_tool(mqt-cc mqt-cc.cpp DEPENDS MQTCompilerPipeline MQTCompilerQDMIAdapter) +if(BUILD_MQT_CORE_TESTS OR MQT_CORE_INSTALL) + # Installed LLVM SDKs may disable tools; Core's tests and installation require this executable. set_property(TARGET mqt-cc PROPERTY EXCLUDE_FROM_ALL FALSE) endif() llvm_map_components_to_libnames(llvm_native_libs bitwriter) @@ -24,12 +23,8 @@ target_link_libraries( MLIRJeffTranslation MLIRJeffToQCO MLIRBytecodeWriter - MLIRMathDialect - MLIRMQTDialect MLIRQIRUtils MLIRTargetLLVMIRExport - MLIRBuiltinToLLVMIRTranslation - MLIRLLVMToLLVMIRTranslation ${llvm_native_libs}) mqt_mlir_target_use_project_options(mqt-cc) @@ -40,4 +35,10 @@ if(qdmi_device_targets) endif() mlir_check_all_link_libraries(mqt-cc) -export_executable_symbols_for_plugins(mqt-cc) + +if(MQT_CORE_INSTALL) + install( + TARGETS mqt-cc + EXPORT mqt-core-runtime-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) +endif() diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 53cb601df4..9d2fee6864 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -19,35 +19,19 @@ #include "mqt/Conversion/QCToQCO/QCToQCO.h" #include "mqt/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.h" #include "mqt/Conversion/QCToQIR/QIRBase/QCToQIRBase.h" -#include "mqt/Dialect/CBit/IR/CBitDialect.h" #include "mqt/Dialect/MQT/IR/MQTAttributes.h" -#include "mqt/Dialect/MQT/IR/MQTDialect.h" #include "mqt/Dialect/MQT/Transforms/Passes.h" -#include "mqt/Dialect/QC/IR/QCDialect.h" #include "mqt/Dialect/QC/Translation/TranslateOpenQASMToQC.h" #include "mqt/Dialect/QC/Translation/TranslateQCToOpenQASM3.h" -#include "mqt/Dialect/QCO/IR/QCODialect.h" #include "mqt/Dialect/QCO/QCOUtils.h" #include "mqt/Dialect/QIR/Utils/QIRUtils.h" -#include "mqt/Dialect/QTensor/IR/QTensorDialect.h" #include "mqt/Support/Passes.h" -#include "jeff/IR/JeffDialect.h" #include "jeff/Translation/Deserialize.hpp" #include "jeff/Translation/Serialize.hpp" #include "mlir/AsmParser/AsmParser.h" #include "mlir/Bytecode/BytecodeWriter.h" -#include "mlir/Dialect/Arith/IR/Arith.h" -#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h" -#include "mlir/Dialect/Func/Extensions/InlinerExtension.h" -#include "mlir/Dialect/Func/IR/FuncOps.h" -#include "mlir/Dialect/LLVMIR/LLVMDialect.h" -#include "mlir/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.h" -#include "mlir/Dialect/Math/IR/Math.h" -#include "mlir/Dialect/MemRef/IR/MemRef.h" -#include "mlir/Dialect/SCF/IR/SCF.h" -#include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/IR/AsmState.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/MLIRContext.h" @@ -57,8 +41,6 @@ #include "mlir/Pass/PassRegistry.h" #include "mlir/Support/FileUtilities.h" #include "mlir/Support/LLVM.h" -#include "mlir/Target/LLVMIR/Dialect/Builtin/BuiltinToLLVMIRTranslation.h" -#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h" #include "mlir/Target/LLVMIR/Export.h" #include "mlir/Transforms/Passes.h" @@ -542,32 +524,18 @@ static int runCompiler(int argc, char** argv) { compilerTarget.emplace(std::move(*target)); } - // Set up MLIR context with all required dialects - DialectRegistry registry; - registry - .insert(); - registerBuiltinDialectTranslation(registry); - registerLLVMDialectTranslation(registry); - func::registerInlinerExtension(registry); - LLVM::registerInlinerInterface(registry); - llvm::SourceMgr sourceMgr; - MLIRContext context(registry); - context.loadAllAvailableDialects(); - SourceMgrDiagnosticHandler diagnosticHandler(sourceMgr, &context); + auto context = createCompilerContext(); + SourceMgrDiagnosticHandler diagnosticHandler(sourceMgr, context.get()); PassReproducerOptions reproducerOptions; - ParserConfig parserConfig(&context, /*verifyAfterParse=*/!runReproducer); + ParserConfig parserConfig(context.get(), /*verifyAfterParse=*/!runReproducer); if (runReproducer) { reproducerOptions.attachResourceParser(parserConfig); } std::optional selectedPayload; if (!payloadSpecification.empty()) { - const auto attribute = parseAttribute(payloadSpecification, &context); + const auto attribute = parseAttribute(payloadSpecification, context.get()); const auto payloadAttr = dyn_cast_if_present(attribute); if (!payloadAttr) { @@ -619,10 +587,10 @@ static int runCompiler(int argc, char** argv) { } break; case InputFormat::OpenQASM: - program.mod = loadOpenQASMFile(inputFilename, &context, sourceMgr); + program.mod = loadOpenQASMFile(inputFilename, context.get(), sourceMgr); break; case InputFormat::Jeff: - program = loadJeffFile(inputFilename, &context); + program = loadJeffFile(inputFilename, context.get()); break; } if (!program.mod) { @@ -642,7 +610,7 @@ static int runCompiler(int argc, char** argv) { const auto runPasses = [&](const function_ref populate, bool preservesLayout = false) { - PassManager pm(&context); + PassManager pm(context.get()); if (failed(applyPassManagerCLOptions(pm))) { return failure(); } @@ -654,7 +622,7 @@ static int runCompiler(int argc, char** argv) { }; if (isolated) { - PassManager pm(&context); + PassManager pm(context.get()); if (runReproducer) { if (failed(reproducerOptions.apply(pm))) { return 1; diff --git a/mlir/unittests/Dialect/QIR/Execution/Runtime/test_qir_runtime.cpp b/mlir/unittests/Dialect/QIR/Execution/Runtime/test_qir_runtime.cpp index 6abf0b8a29..6f4a01de5c 100644 --- a/mlir/unittests/Dialect/QIR/Execution/Runtime/test_qir_runtime.cpp +++ b/mlir/unittests/Dialect/QIR/Execution/Runtime/test_qir_runtime.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef _WIN32 #define SYSTEM _wsystem @@ -53,22 +54,24 @@ class QIRRuntimeTest : public testing::Test { }; TEST(QIRRuntimeArgumentsTest, RejectsInvalidArrayDimensions) { - EXPECT_THROW(__quantum__rt__array_create_1d(0, 1), std::invalid_argument); - EXPECT_THROW(__quantum__rt__array_create_1d(sizeof(Qubit*), -1), - std::invalid_argument); - EXPECT_THROW( + EXPECT_DEATH(__quantum__rt__array_create_1d(0, 1), + "element size must be positive"); + EXPECT_DEATH(__quantum__rt__array_create_1d(sizeof(Qubit*), -1), + "length nonnegative"); + EXPECT_DEATH( __quantum__rt__array_create_1d(2, std::numeric_limits::max()), - std::length_error); + "allocation size overflow"); } TEST(QIRRuntimeArgumentsTest, RejectsInvalidTupleSizes) { - EXPECT_THROW(__quantum__rt__tuple_create(-1), std::invalid_argument); - EXPECT_THROW(__quantum__rt__tuple_create(std::numeric_limits::max()), - std::length_error); + EXPECT_DEATH(__quantum__rt__tuple_create(-1), "size must not be negative"); + EXPECT_DEATH(__quantum__rt__tuple_create(std::numeric_limits::max()), + "allocation size overflow"); auto* controls = __quantum__rt__array_create_1d(sizeof(Qubit*), 0); auto* tuple = __quantum__rt__tuple_create(1); - EXPECT_THROW(__quantum__qis__rx__ctl(controls, tuple), std::invalid_argument); + EXPECT_DEATH(__quantum__qis__rx__ctl(controls, tuple), + "tuple has an invalid size"); __quantum__rt__tuple_update_reference_count(tuple, -1); __quantum__rt__array_update_reference_count(controls, -1); } @@ -80,26 +83,90 @@ TEST_F(QIRRuntimeTest, RejectsInvalidDynamicResourceUse) { __quantum__rt__qubit_release(qubit); __quantum__rt__result_release(result); - EXPECT_THROW(__quantum__qis__x__body(qubit), std::out_of_range); - EXPECT_THROW(__quantum__rt__read_result(result), std::out_of_range); - EXPECT_THROW(__quantum__rt__qubit_release(qubit), std::out_of_range); - EXPECT_THROW(__quantum__rt__result_release(result), std::out_of_range); + EXPECT_DEATH(__quantum__qis__x__body(qubit), "Qubit not allocated"); + EXPECT_DEATH(__quantum__rt__read_result(result), "Result not allocated"); + EXPECT_DEATH(__quantum__rt__qubit_release(qubit), + "qubit was not dynamically allocated"); + EXPECT_DEATH(__quantum__rt__result_release(result), + "result was not dynamically allocated"); } TEST_F(QIRRuntimeTest, RejectsMixedStaticAndDynamicResourceManagement) { __quantum__rt__initialize(nullptr); __quantum__qis__x__body(nullptr); - EXPECT_THROW(__quantum__rt__qubit_allocate(nullptr), std::logic_error); + EXPECT_DEATH(__quantum__rt__qubit_allocate(nullptr), + "Cannot dynamically allocate qubits"); __quantum__rt__initialize(nullptr); __quantum__qis__mz__body(nullptr, nullptr); - EXPECT_THROW(__quantum__rt__result_allocate(nullptr), std::logic_error); + EXPECT_DEATH(__quantum__rt__result_allocate(nullptr), + "Cannot dynamically allocate results"); +} + +TEST_F(QIRRuntimeTest, ReportsAllocationFailuresThroughErrorOutput) { + __quantum__rt__initialize(nullptr); + __quantum__qis__x__body(nullptr); + bool error = false; + EXPECT_EQ(__quantum__rt__qubit_allocate(&error), nullptr); + EXPECT_TRUE(error); + + __quantum__rt__initialize(nullptr); + __quantum__qis__mz__body(nullptr, nullptr); + error = false; + EXPECT_EQ(__quantum__rt__result_allocate(&error), nullptr); + EXPECT_TRUE(error); + __quantum__rt__initialize(nullptr); +} + +TEST_F(QIRRuntimeTest, ReportsInvalidBulkAllocationArguments) { + __quantum__rt__initialize(nullptr); + for (const int64_t size : {-1, 1}) { + bool error = false; + __quantum__rt__qubit_array_allocate(size, nullptr, &error); + EXPECT_TRUE(error); + error = false; + __quantum__rt__result_array_allocate(size, nullptr, &error); + EXPECT_TRUE(error); + EXPECT_DEATH(__quantum__rt__qubit_array_allocate(size, nullptr, nullptr), + "Invalid QIR resource array allocation"); + EXPECT_DEATH(__quantum__rt__result_array_allocate(size, nullptr, nullptr), + "Invalid QIR resource array allocation"); + } + bool error = true; + __quantum__rt__qubit_array_allocate(0, nullptr, &error); + EXPECT_FALSE(error); + error = true; + __quantum__rt__result_array_allocate(0, nullptr, &error); + EXPECT_FALSE(error); +} + +TEST_F(QIRRuntimeTest, RollsBackFailedBulkQubitAllocation) { + __quantum__rt__initialize(nullptr); + auto* retained = __quantum__rt__qubit_allocate(nullptr); + std::vector qubits(dd::Package::MAX_POSSIBLE_QUBITS); + bool error = false; + __quantum__rt__qubit_array_allocate(static_cast(qubits.size()), + qubits.data(), &error); + EXPECT_TRUE(error); + EXPECT_TRUE(std::ranges::all_of( + qubits, [](const auto* qubit) { return qubit == nullptr; })); + + // Reuse every released wire while preserving the earlier allocation. + qubits.pop_back(); + __quantum__rt__qubit_array_allocate(static_cast(qubits.size()), + qubits.data(), &error); + ASSERT_FALSE(error); + __quantum__rt__qubit_array_release(static_cast(qubits.size()), + qubits.data()); + __quantum__rt__qubit_release(retained); + __quantum__rt__initialize(nullptr); } TEST_F(QIRRuntimeTest, RejectsStaticQubitBeyondDDRange) { __quantum__rt__initialize(nullptr); auto* qubit = reinterpret_cast(dd::Package::MAX_POSSIBLE_QUBITS); - EXPECT_THROW(__quantum__qis__x__body(qubit), std::out_of_range); + EXPECT_DEATH(__quantum__qis__x__body(qubit), + "exceeds the supported qubit range"); __quantum__rt__initialize(nullptr); constexpr std::array params{}; @@ -950,7 +1017,7 @@ TEST_F(QIRRuntimeTest, ReusesReleasedWiresWithoutReusingHandles) { __quantum__qis__x__body(qubit); __quantum__rt__qubit_release(qubit); } - EXPECT_THROW(__quantum__qis__x__body(first), std::out_of_range); + EXPECT_DEATH(__quantum__qis__x__body(first), "Qubit not allocated"); auto state = Runtime::getInstance().takeState(); EXPECT_EQ(state.numQubits, 1); state.dd->decRef(state.edge); diff --git a/pyproject.toml b/pyproject.toml index 240b826589..f4ff5cb3d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,8 @@ Discussions = "https://github.com/munich-quantum-toolkit/core/discussions" PyPI = "https://pypi.org/project/mqt-core/" [project.scripts] -mqt-core-bench = "mqt.core._bench:main" +mqt-cc = "mqt.core._commands:compiler" +mqt-core-bench = "mqt.core._commands:benchmark" mqt-core-cli = "mqt.core.__main__:main" [project.entry-points."pennylane.plugins"] @@ -226,7 +227,6 @@ py-api = "cp311" components = [ "mqt-core_Runtime", - "mqt-core_Development", "mqt-core_Python", "qdmi_Development", ] @@ -242,7 +242,21 @@ required-version = ">=0.5.20" cache-keys = [ { file = "pyproject.toml" }, { git = { commit = true, tags = true } }, - { file = "bindings/**/*.cpp" }, + { file = "CMakeLists.txt" }, + { file = "cmake/**/*" }, + { file = "bindings/**/*" }, + { file = "include/**/*" }, + { file = "src/**/*" }, + { file = "mlir/CMakeLists.txt" }, + { file = "mlir/include/**/*" }, + { file = "mlir/lib/**/*" }, + { file = "mlir/bench/**/*" }, + { file = "mlir/tools/**/*" }, + { file = "vendor/**/*" }, + { file = "json/**/*" }, + { env = "SKBUILD_CMAKE_ARGS" }, + { env = "SKBUILD_CMAKE_BUILD_TYPE" }, + { env = "DEPLOY" }, ] [tool.uv.sources] @@ -254,6 +268,8 @@ build = "cp3*" skip = [ "*-musllinux_*", "cp314t-*" ] archs = "auto64" build-frontend = "uv" +config-settings = { "cmake.define.ENABLE_IPO" = "ON" } +environment = { DEPLOY = "ON" } test-command = "pytest test/python" test-groups = [ "test" ] test-sources = [ @@ -267,9 +283,16 @@ test-sources = [ "vendor/qiskit-c-api/2.5.0", ] -[tool.cibuildwheel.linux] -environment = { DEPLOY = "ON", PATH = "$PATH:/root/.local/bin", MLIR_DIR = "/opt/llvm-23.1.2/lib/cmake/mlir" } +[tool.cibuildwheel.windows] +# Preserve bundled DLL names and locations for CMake imports and standalone tools. +repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt --ignore-existing" + +[[tool.cibuildwheel.overrides]] +select = "*-manylinux_*" +config-settings = { "cmake.define.CMAKE_LINKER_TYPE" = "LLD" } +environment = { CC = "clang", CXX = "clang++", PATH = "/opt/clang/bin:$PATH:/root/.local/bin", MLIR_DIR = "/opt/llvm-23.1.2/lib/cmake/mlir" } before-all = """ +manylinux-install-clang -v 22.1.8.1 uv tool install "sccache>=0.10.0" mkdir -p /tmp/setup-mlir curl -fLsS https://github.com/munich-quantum-software/setup-mlir/archive/8d3eae73d0f0196fd30c787d91ebd2018cb6c709.tar.gz | tar -xz -C /tmp/setup-mlir --strip-components=1 @@ -279,12 +302,13 @@ before-test = "uvx sccache --show-stats" # QIR-Runner's Linux wheels require a newer glibc than the manylinux_2_28 # containers used to build and test MQT Core's broadly compatible wheels. test-groups = [ "test-base" ] +inherit.config-settings = "append" +inherit.environment = "append" -[tool.cibuildwheel.macos] +[[tool.cibuildwheel.overrides]] +select = "*-macosx_*" environment = { MACOSX_DEPLOYMENT_TARGET = "13.3" } - -[tool.cibuildwheel.windows] -repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt --ignore-existing" +inherit.environment = "append" [[tool.cibuildwheel.overrides]] select = [ "cp*-win_arm64", "cp3??t-*" ] diff --git a/python/mqt/core/__init__.py b/python/mqt/core/__init__.py index 02cd1df0d0..30a10468c5 100644 --- a/python/mqt/core/__init__.py +++ b/python/mqt/core/__init__.py @@ -12,20 +12,12 @@ import os import sys -from pathlib import Path # Register bundled libraries before importing native extensions on Windows. if sys.platform == "win32": # ruff:ignore[non-empty-init-module] Native imports need the DLL search path. + from importlib.metadata import distribution - def _dll_patch() -> None: - """Add bundled libraries to the Windows DLL search path.""" - import sysconfig # ruff:ignore[import-outside-top-level] only used in Windows - - bin_dir = Path(sysconfig.get_paths()["purelib"]) / "mqt" / "core" / "bin" - os.add_dll_directory(str(bin_dir)) - - _dll_patch() - del _dll_patch + os.add_dll_directory(str(distribution("mqt-core").locate_file("mqt/core/bin"))) from ._version import version as __version__ diff --git a/python/mqt/core/__main__.py b/python/mqt/core/__main__.py index 18e69950c1..ac998b29ad 100644 --- a/python/mqt/core/__main__.py +++ b/python/mqt/core/__main__.py @@ -29,7 +29,7 @@ def main() -> None: It provides the following command line options: - :code:`--version`: Print the version and exit. - - :code:`--include_dir`: Print the path to the mqt-core C++ include directory. + - :code:`--include_dir`: Print the path to the mqt-core native include directory. - :code:`--cmake_dir`: Print the path to the mqt-core CMake module directory. """ @@ -37,7 +37,7 @@ def main() -> None: parser.add_argument("--version", action="version", version=__version__, help="Print version and exit.") parser.add_argument( - "--include_dir", action="store_true", help="Print the path to the mqt-core C++ include directory." + "--include_dir", action="store_true", help="Print the path to the mqt-core native include directory." ) parser.add_argument( "--cmake_dir", action="store_true", help="Print the path to the mqt-core CMake module directory." diff --git a/python/mqt/core/_bench.py b/python/mqt/core/_bench.py deleted file mode 100644 index 7dc45dba66..0000000000 --- a/python/mqt/core/_bench.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Launch the structured benchmark driver bundled with MQT Core.""" - -from __future__ import annotations - -import os -import sys -from importlib.metadata import distribution -from pathlib import Path -from typing import NoReturn - - -def main() -> NoReturn: - """Replace this process with the bundled benchmark driver.""" - suffix = ".exe" if sys.platform == "win32" else "" - executable = Path(str(distribution("mqt-core").locate_file(f"mqt/core/bin/mqt-core-bench{suffix}"))) - os.execv(executable, [str(executable), *sys.argv[1:]]) # ruff: ignore[start-process-with-no-shell] diff --git a/python/mqt/core/_commands.py b/python/mqt/core/_commands.py index 95b4a0ba69..98d9223098 100644 --- a/python/mqt/core/_commands.py +++ b/python/mqt/core/_commands.py @@ -6,12 +6,32 @@ # # Licensed under the MIT License -"""Useful commands for obtaining information about mqt-core.""" +"""Commands for the bundled native tools and installation paths.""" from __future__ import annotations +import os +import sys from importlib.metadata import PackageNotFoundError, distribution from pathlib import Path +from typing import NoReturn + + +def compiler() -> NoReturn: + """Launch the bundled MQT compiler.""" + run_tool("mqt-cc") + + +def benchmark() -> NoReturn: + """Launch the bundled MQT benchmark driver.""" + run_tool("mqt-core-bench") + + +def run_tool(name: str) -> NoReturn: + """Replace this process with a bundled native tool.""" + suffix = ".exe" if sys.platform == "win32" else "" + executable = Path(str(distribution("mqt-core").locate_file(f"mqt/core/bin/{name}{suffix}"))) + os.execv(executable, [str(executable), *sys.argv[1:]]) # ruff: ignore[start-process-with-no-shell] def include_dir() -> Path: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0910581187..e145143c78 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,17 +8,6 @@ set(MQT_CORE_TARGETS "") -if(MQT_CORE_INSTALL) - set(MQT_CORE_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/cmake/mqt-core") - set(MQT_CORE_TARGETS_EXPORT_NAME "mqt-core-targets") - set(MQT_CORE_CMAKE_CONFIG_TEMPLATE "${PROJECT_SOURCE_DIR}/cmake/mqt-core-config.cmake.in") - set(MQT_CORE_CMAKE_CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}") - set(MQT_CORE_CMAKE_VERSION_CONFIG_FILE - "${MQT_CORE_CMAKE_CONFIG_DIR}/mqt-core-config-version.cmake") - set(MQT_CORE_CMAKE_PROJECT_CONFIG_FILE "${MQT_CORE_CMAKE_CONFIG_DIR}/mqt-core-config.cmake") - set(MQT_CORE_CMAKE_PROJECT_TARGETS_FILE "mqt-core-targets.cmake") -endif() - if(NOT TARGET MQT::ProjectWarnings) # use the warnings specified in CompilerWarnings.cmake add_library(project_warnings INTERFACE) @@ -29,8 +18,6 @@ if(NOT TARGET MQT::ProjectWarnings) add_library(MQT::ProjectWarnings ALIAS project_warnings) set_target_properties(project_warnings PROPERTIES EXPORT_NAME ProjectWarnings) - - list(APPEND MQT_CORE_TARGETS project_warnings) endif() if(NOT TARGET MQT::ProjectOptions) @@ -47,8 +34,6 @@ if(NOT TARGET MQT::ProjectOptions) add_library(MQT::ProjectOptions ALIAS project_options) set_target_properties(project_options PROPERTIES EXPORT_NAME ProjectOptions) - - list(APPEND MQT_CORE_TARGETS project_options) endif() add_subdirectory(bench) @@ -57,51 +42,59 @@ add_subdirectory(dd) add_subdirectory(qdmi) -# Installation instructions for the main library if(MQT_CORE_INSTALL) include(CMakePackageConfigHelpers) + set(config_install_dir "${CMAKE_INSTALL_DATADIR}/cmake/mqt-core") configure_package_config_file( - ${MQT_CORE_CMAKE_CONFIG_TEMPLATE} ${MQT_CORE_CMAKE_PROJECT_CONFIG_FILE} - INSTALL_DESTINATION ${MQT_CORE_CONFIG_INSTALL_DIR} - NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO) + ${PROJECT_SOURCE_DIR}/cmake/mqt-core-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/mqt-core-config.cmake + INSTALL_DESTINATION ${config_install_dir} + NO_SET_AND_CHECK_MACRO) write_basic_package_version_file( - ${MQT_CORE_CMAKE_VERSION_CONFIG_FILE} + ${CMAKE_CURRENT_BINARY_DIR}/mqt-core-config-version.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMinorVersion) - install( - FILES ${MQT_CORE_CMAKE_PROJECT_CONFIG_FILE} ${MQT_CORE_CMAKE_VERSION_CONFIG_FILE} - DESTINATION ${MQT_CORE_CONFIG_INSTALL_DIR} - COMPONENT ${MQT_CORE_TARGET_NAME}_Development) + mqt_get_qdmi_device_targets(runtime_targets) + list(APPEND runtime_targets ${MQT_CORE_TARGET_NAME}-qdmi-driver) + list(REMOVE_ITEM MQT_CORE_TARGETS ${runtime_targets}) + set_target_properties(${runtime_targets} PROPERTIES INTERFACE_COMPILE_FEATURES "") - # FILE_SET should be aligned with LIBRARY and ARCHIVE, so we turn cmake-format off + # Runtime exports contain only the C ABI and executable tools. # cmake-format: off + install( + TARGETS ${runtime_targets} + EXPORT mqt-core-runtime-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime + FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mqt-core + COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) + install( TARGETS ${MQT_CORE_TARGETS} - EXPORT ${MQT_CORE_TARGETS_EXPORT_NAME} + EXPORT mqt-core-development-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime - NAMELINK_COMPONENT ${MQT_CORE_TARGET_NAME}_Development - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT ${MQT_CORE_TARGET_NAME}_Development - FILE_SET HEADERS - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mqt-core - COMPONENT ${MQT_CORE_TARGET_NAME}_Development) + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Development + FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mqt-core + COMPONENT ${MQT_CORE_TARGET_NAME}_Development) # cmake-format: on install( - EXPORT ${MQT_CORE_TARGETS_EXPORT_NAME} - FILE ${MQT_CORE_CMAKE_PROJECT_TARGETS_FILE} + FILES ${CMAKE_CURRENT_BINARY_DIR}/mqt-core-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/mqt-core-config-version.cmake + ${PROJECT_SOURCE_DIR}/cmake/AddMQTQDMIDevice.cmake + DESTINATION ${config_install_dir} + COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) + install( + EXPORT mqt-core-runtime-targets NAMESPACE MQT:: - DESTINATION ${MQT_CORE_CONFIG_INSTALL_DIR} - COMPONENT ${MQT_CORE_TARGET_NAME}_Development) - + DESTINATION ${config_install_dir} + COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) install( - FILES ${PROJECT_SOURCE_DIR}/cmake/AddMQTPythonBinding.cmake - ${PROJECT_SOURCE_DIR}/cmake/AddMQTQDMIDevice.cmake - ${PROJECT_SOURCE_DIR}/cmake/CopyQDMISharedDependencies.cmake - ${PROJECT_SOURCE_DIR}/cmake/PackageAddTest.cmake - DESTINATION ${MQT_CORE_CONFIG_INSTALL_DIR} + EXPORT mqt-core-development-targets + NAMESPACE MQT:: + DESTINATION ${config_install_dir} COMPONENT ${MQT_CORE_TARGET_NAME}_Development) endif() diff --git a/src/bench/CMakeLists.txt b/src/bench/CMakeLists.txt index 93051da99a..3f08d5d8eb 100644 --- a/src/bench/CMakeLists.txt +++ b/src/bench/CMakeLists.txt @@ -11,7 +11,7 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-bench-library) ${MQT_CORE_INCLUDE_BUILD_DIR}/bench/*.inc) file(GLOB_RECURSE BENCH_SOURCES *.cpp) - add_mqt_core_library(${MQT_CORE_TARGET_NAME}-bench-library ALIAS_NAME Bench) + add_mqt_core_library(${MQT_CORE_TARGET_NAME}-bench-library ALIAS_NAME Bench HIDDEN_VISIBILITY) set_target_properties(${MQT_CORE_TARGET_NAME}-bench-library PROPERTIES OUTPUT_NAME ${MQT_CORE_TARGET_NAME}-bench) @@ -28,10 +28,6 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-bench-library) ${MQT_CORE_TARGET_NAME}-bench-library PUBLIC FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/.. FILES ${CMAKE_CURRENT_BINARY_DIR}/mqt_core_bench_export.h) - if(NOT BUILD_MQT_CORE_SHARED_LIBS) - target_compile_definitions(${MQT_CORE_TARGET_NAME}-bench-library - PUBLIC MQT_CORE_BENCH_STATIC_DEFINE) - endif() set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${MQT_CORE_TARGET_NAME}-bench-library diff --git a/src/dd/CMakeLists.txt b/src/dd/CMakeLists.txt index 1bc6df9b7a..0ae73b3c16 100644 --- a/src/dd/CMakeLists.txt +++ b/src/dd/CMakeLists.txt @@ -10,25 +10,46 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-dd) file(GLOB_RECURSE DD_HEADERS ${MQT_CORE_INCLUDE_BUILD_DIR}/dd/*.hpp) file(GLOB_RECURSE DD_SOURCES **.cpp) - add_mqt_core_library(${MQT_CORE_TARGET_NAME}-dd ALIAS_NAME DD) + add_mqt_core_library(${MQT_CORE_TARGET_NAME}-dd ALIAS_NAME DD FORCE_STATIC) - target_sources(${MQT_CORE_TARGET_NAME}-dd PRIVATE ${DD_SOURCES}) - - target_sources( - ${MQT_CORE_TARGET_NAME}-dd PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} - FILES ${DD_HEADERS}) - - target_link_libraries(${MQT_CORE_TARGET_NAME}-dd - PRIVATE $) + set(dd_targets ${MQT_CORE_TARGET_NAME}-dd) + set(dd_export_target ${MQT_CORE_TARGET_NAME}-dd) + if(BUILD_MQT_CORE_BINDINGS) + # Python's DD and MLIR modules exchange objects containing addresses of DD constants. + add_mqt_core_library(${MQT_CORE_TARGET_NAME}-dd-python ALIAS_NAME DDPython FORCE_SHARED) + list(APPEND dd_targets ${MQT_CORE_TARGET_NAME}-dd-python) + set(dd_export_target ${MQT_CORE_TARGET_NAME}-dd-python) + set_target_properties(${dd_export_target} PROPERTIES DEFINE_SYMBOL mqt_core_dd_python_EXPORTS) + if(MQT_CORE_INSTALL) + install( + TARGETS ${dd_export_target} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Python + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Python + ARCHIVE EXCLUDE_FROM_ALL) + endif() + endif() include(GenerateExportHeader) - generate_export_header(${MQT_CORE_TARGET_NAME}-dd BASE_NAME mqt_core_dd) - target_sources( - ${MQT_CORE_TARGET_NAME}-dd PUBLIC FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/.. - FILES ${CMAKE_CURRENT_BINARY_DIR}/mqt_core_dd_export.h) - if(NOT BUILD_MQT_CORE_SHARED_LIBS) - target_compile_definitions(${MQT_CORE_TARGET_NAME}-dd PUBLIC MQT_CORE_DD_STATIC_DEFINE) - endif() + generate_export_header(${dd_export_target} BASE_NAME mqt_core_dd EXPORT_FILE_NAME + mqt_core_dd_export.h) + foreach(dd_target IN LISTS dd_targets) + target_sources(${dd_target} PRIVATE ${DD_SOURCES}) + target_sources( + ${dd_target} + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_CORE_INCLUDE_BUILD_DIR} + FILES + ${DD_HEADERS} + FILE_SET + HEADERS + BASE_DIRS + ${CMAKE_CURRENT_BINARY_DIR}/.. + FILES + ${CMAKE_CURRENT_BINARY_DIR}/mqt_core_dd_export.h) + target_link_libraries(${dd_target} PRIVATE $) + endforeach() set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${MQT_CORE_TARGET_NAME}-dd diff --git a/src/qdmi/common/CMakeLists.txt b/src/qdmi/common/CMakeLists.txt index a9600949fb..3a7fad824b 100644 --- a/src/qdmi/common/CMakeLists.txt +++ b/src/qdmi/common/CMakeLists.txt @@ -9,7 +9,7 @@ set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-common) if(NOT TARGET ${TARGET_NAME}) - add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMICommon) + add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMICommon FORCE_STATIC HIDDEN_VISIBILITY) target_sources(${TARGET_NAME} PRIVATE Common.cpp DeviceConfiguration.cpp) @@ -27,7 +27,7 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE qdmi::qdmi_project_warnings ${CMAKE_DL_LIBS}) + PRIVATE $ ${CMAKE_DL_LIBS}) list(APPEND MQT_CORE_TARGETS ${TARGET_NAME}) endif() diff --git a/src/qdmi/devices/dd/CMakeLists.txt b/src/qdmi/devices/dd/CMakeLists.txt index 45db401e19..05b1a47050 100644 --- a/src/qdmi/devices/dd/CMakeLists.txt +++ b/src/qdmi/devices/dd/CMakeLists.txt @@ -22,16 +22,8 @@ if(NOT TARGET ${TARGET_NAME}) endif() target_sources(${TARGET_NAME} PRIVATE Device.cpp Worker.cpp WorkerProtocol.cpp) - target_sources( - ${TARGET_NAME} - PUBLIC FILE_SET - HEADERS - BASE_DIRS - ${MQT_CORE_INCLUDE_BUILD_DIR} - ${CMAKE_CURRENT_BINARY_DIR}/include - FILES - ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/devices/dd/Device.hpp - ${QDMI_HDRS}) + target_sources(${TARGET_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS + ${CMAKE_CURRENT_BINARY_DIR}/include FILES ${QDMI_HDRS}) target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreDD MQT::CoreQDMICommon LLVMSupport) mqt_llvm_target_disable_rtti(${TARGET_NAME}) @@ -67,8 +59,10 @@ if(NOT TARGET ${TARGET_NAME}) target_compile_definitions( ${TARGET_NAME} PRIVATE MQT_DDSIM_WORKER_NAME="${WORKER_TARGET}${CMAKE_EXECUTABLE_SUFFIX}") add_dependencies(${TARGET_NAME} ${WORKER_TARGET}) - install(TARGETS ${WORKER_TARGET} RUNTIME DESTINATION ${WORKER_DIRECTORY} - COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) + if(MQT_CORE_INSTALL) + install(TARGETS ${WORKER_TARGET} RUNTIME DESTINATION ${WORKER_DIRECTORY} + COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) + endif() mqt_configure_qdmi_device(${TARGET_NAME} ID mqt.ddsim.default PREFIX ${QDMI_PREFIX}) set_property( TARGET ${TARGET_NAME} diff --git a/src/qdmi/devices/sc/CMakeLists.txt b/src/qdmi/devices/sc/CMakeLists.txt index 330b288daf..71c3d05f9f 100644 --- a/src/qdmi/devices/sc/CMakeLists.txt +++ b/src/qdmi/devices/sc/CMakeLists.txt @@ -10,7 +10,7 @@ set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-sc-device-config) if(NOT TARGET ${TARGET_NAME}) - add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMIScDeviceConfig) + add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMIScDeviceConfig FORCE_STATIC HIDDEN_VISIBILITY) target_sources(${TARGET_NAME} PRIVATE Configuration.cpp) @@ -43,16 +43,8 @@ if(NOT TARGET ${TARGET_NAME}) target_sources(${TARGET_NAME} PRIVATE Device.cpp) - target_sources( - ${TARGET_NAME} - PUBLIC FILE_SET - HEADERS - BASE_DIRS - ${MQT_CORE_INCLUDE_BUILD_DIR} - ${CMAKE_CURRENT_BINARY_DIR}/include - FILES - ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/devices/sc/Device.hpp - ${QDMI_HDRS}) + target_sources(${TARGET_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS + ${CMAKE_CURRENT_BINARY_DIR}/include FILES ${QDMI_HDRS}) target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQDMICommon MQT::CoreQDMIScDeviceConfig) diff --git a/src/qdmi/driver/CMakeLists.txt b/src/qdmi/driver/CMakeLists.txt index 278e0e05c3..70c8f7c548 100644 --- a/src/qdmi/driver/CMakeLists.txt +++ b/src/qdmi/driver/CMakeLists.txt @@ -10,25 +10,19 @@ set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-driver) if(NOT TARGET ${TARGET_NAME}) # Add driver library - add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMIDriver FORCE_SHARED) + add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMIDriver FORCE_SHARED HIDDEN_VISIBILITY) set_target_properties( - ${TARGET_NAME} - PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.." - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.." - WINDOWS_EXPORT_ALL_SYMBOLS OFF) + ${TARGET_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.." + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/..") target_compile_definitions(${TARGET_NAME} PRIVATE QDMI_driver_EXPORTS) target_sources(${TARGET_NAME} PRIVATE DeviceRegistry.cpp Driver.cpp) - target_sources( - ${TARGET_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} FILES - ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/driver/DriverExtension.hpp) - target_link_libraries( ${TARGET_NAME} - PUBLIC qdmi::qdmi MQT::CoreQDMICommon - PRIVATE qdmi::qdmi_project_warnings $ - ${CMAKE_DL_LIBS}) + PUBLIC qdmi::qdmi + PRIVATE MQT::CoreQDMICommon qdmi::qdmi_project_warnings + $ ${CMAKE_DL_LIBS}) configure_qdmi_exports( TARGET diff --git a/test/bench/test_sha256.cpp b/test/bench/test_sha256.cpp deleted file mode 100644 index 338e3a1958..0000000000 --- a/test/bench/test_sha256.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "../../src/bench/SHA256.hpp" - -#include "gtest/gtest.h" - -namespace { - -TEST(SHA256, MatchesPublishedVectors) { - EXPECT_EQ(mqt::bench::detail::sha256Hex(""), - "e3b0c44298fc1c149afbf4c8996fb924" - "27ae41e4649b934ca495991b7852b855"); - EXPECT_EQ(mqt::bench::detail::sha256Hex("abc"), - "ba7816bf8f01cfea414140de5dae2223" - "b00361a396177a9cb410ff61f20015ad"); - EXPECT_EQ(mqt::bench::detail::sha256Hex( - "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), - "248d6a61d20638b8e5c026930c3e6039" - "a33ce45964ff2167f6ecedd419db06c1"); -} - -} // namespace diff --git a/test/cmake/installed_consumer/CMakeLists.txt b/test/cmake/installed_consumer/CMakeLists.txt deleted file mode 100644 index dc1ed876d7..0000000000 --- a/test/cmake/installed_consumer/CMakeLists.txt +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -cmake_minimum_required(VERSION 3.28) -project(mqt-core-installed-consumer LANGUAGES C CXX) - -set(CMAKE_EXPORT_COMPILE_COMMANDS OFF) -set(CMAKE_C_COMPILER_LAUNCHER "${CMAKE_COMMAND};-E;env") -set(CMAKE_CXX_COMPILER_LAUNCHER "${CMAKE_COMMAND};-E;env") -set(DEPLOY OFF) -set(ENV{DEPLOY} ON) - -set(consumer_settings - CMAKE_BUILD_TYPE - CMAKE_C_COMPILER_LAUNCHER - CMAKE_CXX_COMPILER_LAUNCHER - CMAKE_C_FLAGS - CMAKE_CXX_FLAGS - CMAKE_EXPORT_COMPILE_COMMANDS - CMAKE_VERIFY_INTERFACE_HEADER_SETS - CMAKE_INTERPROCEDURAL_OPTIMIZATION - CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS - CMAKE_CXX_SCAN_FOR_MODULES - CMAKE_OSX_DEPLOYMENT_TARGET - CMAKE_INSTALL_BINDIR - CMAKE_INSTALL_LIBDIR - CMAKE_INSTALL_DATADIR - DEPLOY - ENABLE_IPO - ENABLE_CACHE) -foreach(setting IN LISTS consumer_settings) - set(before_${setting} "${${setting}}") - get_property( - before_cache_${setting} - CACHE ${setting} - PROPERTY VALUE) - get_property( - before_cache_type_${setting} - CACHE ${setting} - PROPERTY TYPE) - if(DEFINED ${setting}) - set(defined_${setting} TRUE) - else() - set(defined_${setting} FALSE) - endif() -endforeach() -get_directory_property(before_compile_options COMPILE_OPTIONS) -set(consumer_properties USE_FOLDERS CMAKE_CXX_STANDARD_REQUIRED CXX_EXTENSIONS) -foreach(property IN LISTS consumer_properties) - get_property(before_${property} GLOBAL PROPERTY ${property}) -endforeach() - -find_package(mqt-core CONFIG REQUIRED) - -foreach(setting IN LISTS consumer_settings) - get_property( - after_cache - CACHE ${setting} - PROPERTY VALUE) - get_property( - after_cache_type - CACHE ${setting} - PROPERTY TYPE) - if((defined_${setting} AND NOT DEFINED ${setting}) - OR (NOT defined_${setting} AND DEFINED ${setting}) - OR NOT "${${setting}}" STREQUAL "${before_${setting}}" - OR NOT "${after_cache}" STREQUAL "${before_cache_${setting}}" - OR NOT "${after_cache_type}" STREQUAL "${before_cache_type_${setting}}") - message(FATAL_ERROR "find_package(mqt-core) changed ${setting}") - endif() -endforeach() -get_directory_property(after_compile_options COMPILE_OPTIONS) -if(NOT before_compile_options STREQUAL after_compile_options) - message(FATAL_ERROR "find_package(mqt-core) changed consumer compile options") -endif() -foreach(property IN LISTS consumer_properties) - get_property(after GLOBAL PROPERTY ${property}) - if(NOT "${after}" STREQUAL "${before_${property}}") - message(FATAL_ERROR "find_package(mqt-core) changed global property ${property}") - endif() -endforeach() - -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/device.c" "int consumer_device(void) { return 0; }\n") -add_library(consumer-device MODULE "${CMAKE_CURRENT_BINARY_DIR}/device.c") -target_link_libraries(consumer-device PRIVATE qdmi::qdmi) -mqt_configure_qdmi_device(consumer-device ID test.consumer PREFIX CONSUMER) -add_custom_command( - TARGET consumer-device - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E compare_files - "${CMAKE_CURRENT_BINARY_DIR}/$/consumer-device.qdmi.json" - "$/consumer-device.qdmi.json") diff --git a/test/python/test_cli.py b/test/python/test_cli.py index 227d17bb06..3a1954803f 100644 --- a/test/python/test_cli.py +++ b/test/python/test_cli.py @@ -19,8 +19,6 @@ import pytest -# Import the private module to test its process replacement directly. -import mqt.core._bench as benchmark_cli # ruff: ignore[import-private-name] from mqt.core import __version__ as mqt_core_version if TYPE_CHECKING: @@ -115,6 +113,16 @@ def test_cli_execute_module() -> None: assert mqt_core_version in output.decode() +@pytest.mark.script_launch_mode("subprocess") +def test_compiler_cli(script_runner: ScriptRunner, tmp_path: Path) -> None: + """Compile OpenQASM with the compiler bundled in the wheel.""" + source = tmp_path / "bell.qasm" + source.write_text('OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; h q[0]; cx q[0], q[1];') + ret = script_runner.run(["mqt-cc", str(source), "--emit=qco"]) + assert ret.success + assert "qco.h" in ret.stdout + + @pytest.mark.script_launch_mode("subprocess") def test_benchmark_cli(script_runner: ScriptRunner) -> None: """Run the bundled benchmark driver through its console script.""" @@ -130,19 +138,12 @@ def test_benchmark_cli(script_runner: ScriptRunner) -> None: assert '"teleportation"' in ret.stdout -@pytest.mark.parametrize(("platform", "suffix"), [("linux", ""), ("win32", ".exe")]) -def test_benchmark_cli_launcher(platform: str, suffix: str) -> None: - """Locate and execute the bundled benchmark driver on each platform.""" - executable = Path(f"installation/mqt/core/bin/mqt-core-bench{suffix}") - with ( - patch.object(benchmark_cli.sys, "platform", platform), - patch.object(benchmark_cli.sys, "argv", ["mqt-core-bench", "list"]), - patch.object(benchmark_cli, "distribution") as distribution_mock, - patch.object(benchmark_cli.os, "execv") as execv_mock, - ): - distribution_mock.return_value.locate_file.return_value = executable - benchmark_cli.main() - - distribution_mock.assert_called_once_with("mqt-core") - distribution_mock.return_value.locate_file.assert_called_once_with(f"mqt/core/bin/mqt-core-bench{suffix}") - execv_mock.assert_called_once_with(executable, [str(executable), "list"]) +@pytest.mark.parametrize("tool", ["mqt-cc", "mqt-core-bench"]) +def test_native_tool_entry_point(script_runner: ScriptRunner, tool: str) -> None: + """Resolve the console entry point and forward arguments to its native tool.""" + with patch("os.execv") as execute: + ret = script_runner.run([tool, "--help"]) + assert ret.success + executable, arguments = execute.call_args.args + assert Path(executable).is_file() + assert arguments == [str(executable), "--help"] diff --git a/test/qdmi/devices/dd/error_handling_test.cpp b/test/qdmi/devices/dd/error_handling_test.cpp index 89692a229d..d1ae6410c8 100644 --- a/test/qdmi/devices/dd/error_handling_test.cpp +++ b/test/qdmi/devices/dd/error_handling_test.cpp @@ -22,6 +22,10 @@ #include "llvm/Support/Threading.h" +#include +#include +#include + namespace { class ErrorHandling : public ::testing::Test { @@ -334,18 +338,20 @@ TEST_F(ErrorHandling, WorkerCrashesDoNotAffectConcurrentOrLaterJobs) { GTEST_SKIP() << "Concurrent crash isolation requires two worker slots"; } const qdmi_test::SessionGuard session{}; - for (const std::string operation : {"abort", "llvm.trap"}) { + for (const auto& [declaration, call] : { + std::pair{"declare void @abort()", "call void @abort()"}, + std::pair{"declare void @llvm.trap()", "call void @llvm.trap()"}, + std::pair{"declare void @__quantum__rt__qubit_release(ptr)", + "call void @__quantum__rt__qubit_release(ptr null)"}, + }) { const qdmi_test::JobGuard valid{session.session}; qdmi_test::ControlledJob running{valid.job}; const qdmi_test::JobGuard crash{session.session}; - std::string program = "declare void @"; - program += operation; - program += "()\n" - "define i64 @main() #0 { call void @"; - program += operation; - program += "() unreachable }\n" - "attributes #0 = { \"entry_point\" " - "\"qir_profiles\"=\"adaptive_profile\" }\n"; + const auto program = std::string(declaration) + + "\ndefine i64 @main() #0 { " + call + + " ret i64 0 }\n" + "attributes #0 = { \"entry_point\" " + "\"qir_profiles\"=\"adaptive_profile\" }\n"; ASSERT_EQ(qdmi_test::setProgram( crash.job, QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING, program), QDMI_SUCCESS); diff --git a/test/qdmi/driver/CMakeLists.txt b/test/qdmi/driver/CMakeLists.txt index abb1f4b363..ea354f0b5e 100644 --- a/test/qdmi/driver/CMakeLists.txt +++ b/test/qdmi/driver/CMakeLists.txt @@ -9,10 +9,13 @@ set(TARGET_NAME mqt-core-qdmi-driver-test) if(TARGET MQT::CoreQDMIDriver) + # These fixtures use automatic DLL exports, which cannot read MSVC IPO objects. add_library(mqt-core-qdmi-metadata-device SHARED metadata_device.cpp) set_target_properties( mqt-core-qdmi-metadata-device - PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device" + PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON + INTERPROCEDURAL_OPTIMIZATION OFF + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device" QDMI_DEVICE_ID "test.metadata-only" QDMI_DEVICE_PREFIX "TEST_METADATA" @@ -31,7 +34,9 @@ if(TARGET MQT::CoreQDMIDriver) target_link_libraries(mqt-core-qdmi-session-device PRIVATE qdmi::qdmi) set_target_properties( mqt-core-qdmi-session-device - PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device" + PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON + INTERPROCEDURAL_OPTIMIZATION OFF + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/device") package_add_test(${TARGET_NAME} mqt-core-qdmi-driver-testing test_driver.cpp) diff --git a/test/qdmi/driver/runtime_file_device/CMakeLists.txt b/test/qdmi/driver/runtime_file_device/CMakeLists.txt index 85ba22c9b3..ad5ab99b88 100644 --- a/test/qdmi/driver/runtime_file_device/CMakeLists.txt +++ b/test/qdmi/driver/runtime_file_device/CMakeLists.txt @@ -14,6 +14,8 @@ if(NOT MQT_CORE_QDMI_HELPER OR NOT MQT_CORE_QDMI_TEST_SOURCE_DIR) endif() set(QDMI_VERSION "test") +set(MQT_CORE_INSTALL ON) +set(MQT_CORE_TARGET_NAME mqt-core) include("${MQT_CORE_QDMI_HELPER}") add_library(mqt-core-qdmi-runtime-file-device SHARED