diff --git a/.agent/plans/package-policy.md b/.agent/plans/package-policy.md index 3bd3970f21..8c360f9dc4 100644 --- a/.agent/plans/package-policy.md +++ b/.agent/plans/package-policy.md @@ -28,20 +28,9 @@ those defaults before any helper call. ## Validation -`test/cmake/installed_consumer/CMakeLists.txt` is one standalone regression. Run -it against an installed prefix, then build its device target: - -```console -cmake -S test/cmake/installed_consumer -B build/installed-consumer -DCMAKE_PREFIX_PATH= -cmake --build build/installed-consumer -``` - -It checks that package discovery preserves consumer settings and their cache -entries, including unset defaults, and builds a QDMI device with its colocated -manifest through the installed helper. The regression failed against the prior -installed config and passed after the change with QDMI 1.4.0. - -Local validation passed: the `release-no-mlir` shared build and installation -with GCC 13, configuration with `MQT_CORE_INSTALL=OFF`, the installed consumer's -configuration and build, and repository lint. Full compiler and wheel -qualification belongs to the separate release validation work. +Historical validation passed with GCC 13 and QDMI 1.4.0: the `release-no-mlir` +shared build and installation, configuration with `MQT_CORE_INSTALL=OFF`, and +repository lint. A separate installed-consumer probe checked that package +discovery preserved consumer settings and cache entries and that the installed +helper built a QDMI device with its colocated manifest. That probe is not part +of the repository test suite. 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..755fec1e37 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,7 +116,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 @@ -173,10 +176,9 @@ if(BUILD_MQT_CORE_MLIR) 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/dd/CMakeLists.txt b/bindings/dd/CMakeLists.txt index 03c929f529..c74ac8d78e 100644 --- a/bindings/dd/CMakeLists.txt +++ b/bindings/dd/CMakeLists.txt @@ -18,7 +18,7 @@ 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") diff --git a/bindings/mlir/CMakeLists.txt b/bindings/mlir/CMakeLists.txt index 106dd3577e..12a7742342 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( diff --git a/cmake/AddMQTCoreLibrary.cmake b/cmake/AddMQTCoreLibrary.cmake index 10d5f31c5d..86d2b9e667 100644 --- a/cmake/AddMQTCoreLibrary.cmake +++ b/cmake/AddMQTCoreLibrary.cmake @@ -44,7 +44,8 @@ function(add_mqt_core_library name) 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 +54,15 @@ function(add_mqt_core_library name) CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1 WINDOWS_EXPORT_ALL_SYMBOLS OFF) + elseif(WIN32) + get_target_property(target_type ${name} TYPE) + 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..2a736e4e61 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 diff --git a/cmake/AddMQTQDMIDevice.cmake b/cmake/AddMQTQDMIDevice.cmake index 299208b471..6360f223fb 100644 --- a/cmake/AddMQTQDMIDevice.cmake +++ b/cmake/AddMQTQDMIDevice.cmake @@ -151,7 +151,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) @@ -180,20 +182,9 @@ function(mqt_copy_qdmi_runtime target) 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) @@ -236,14 +227,5 @@ function(mqt_copy_qdmi_runtime target) POST_BUILD 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") - endif() endforeach() endfunction() 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/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake index 469649865b..26a6f37c97 100644 --- a/cmake/StandardProjectSettings.cmake +++ b/cmake/StandardProjectSettings.cmake @@ -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 @@ -82,9 +77,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..5b3f91bfc1 100644 --- a/cmake/mqt-core-config.cmake.in +++ b/cmake/mqt-core-config.cmake.in @@ -13,14 +13,19 @@ include(CMakeFindDependencyMacro) find_dependency(qdmi) -if(TARGET MQT::CoreDD) - return() -endif() - -include("${CMAKE_CURRENT_LIST_DIR}/AddMQTPythonBinding.cmake") 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") +set(mqt-core_Runtime_FOUND TRUE) + +set(mqt-core_Development_FOUND FALSE) +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/mqt-core-development-targets.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/mqt-core-development-targets.cmake") + set(mqt-core_Development_FOUND TRUE) +elseif("Development" IN_LIST mqt-core_FIND_COMPONENTS) + 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_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..6591b36656 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,34 @@ 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. + +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 +225,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 +270,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/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..d3f9e8a81d 100644 --- a/mlir/tools/mqt-cc/CMakeLists.txt +++ b/mlir/tools/mqt-cc/CMakeLists.txt @@ -8,9 +8,8 @@ # 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. +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) @@ -41,3 +40,11 @@ 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 + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Runtime) +endif() 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..93c7e52444 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,6 +111,7 @@ Discussions = "https://github.com/munich-quantum-toolkit/core/discussions" PyPI = "https://pypi.org/project/mqt-core/" [project.scripts] +mqt-cc = "mqt.core._commands:compiler" mqt-core-bench = "mqt.core._bench:main" mqt-core-cli = "mqt.core.__main__:main" @@ -226,7 +227,6 @@ py-api = "cp311" components = [ "mqt-core_Runtime", - "mqt-core_Development", "mqt-core_Python", "qdmi_Development", ] @@ -254,6 +254,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 +269,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 +288,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/__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 index 7dc45dba66..c7daf67219 100644 --- a/python/mqt/core/_bench.py +++ b/python/mqt/core/_bench.py @@ -10,15 +10,11 @@ from __future__ import annotations -import os -import sys -from importlib.metadata import distribution -from pathlib import Path from typing import NoReturn +from ._commands import run_tool + 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] + run_tool("mqt-core-bench") diff --git a/python/mqt/core/_commands.py b/python/mqt/core/_commands.py index 95b4a0ba69..2f1cff152b 100644 --- a/python/mqt/core/_commands.py +++ b/python/mqt/core/_commands.py @@ -10,8 +10,23 @@ 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 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..1d2072196f 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) diff --git a/src/dd/CMakeLists.txt b/src/dd/CMakeLists.txt index 1bc6df9b7a..68f1567316 100644 --- a/src/dd/CMakeLists.txt +++ b/src/dd/CMakeLists.txt @@ -10,25 +10,45 @@ 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_compile_definitions(${MQT_CORE_TARGET_NAME}-dd PUBLIC MQT_CORE_DD_STATIC_DEFINE) - 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) + 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() 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..6762daaa4c 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}) 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/qdmi/test_builtin_driver.py b/test/python/qdmi/test_builtin_driver.py index 7906d74465..c0d4faf548 100644 --- a/test/python/qdmi/test_builtin_driver.py +++ b/test/python/qdmi/test_builtin_driver.py @@ -11,8 +11,10 @@ from __future__ import annotations import json +import shutil import subprocess import sys +from importlib.metadata import distribution from pathlib import Path import pytest @@ -20,6 +22,34 @@ from mqt.core.qdmi import builtin_driver +@pytest.mark.parametrize("device", ["ddsim", "sc"]) +def test_relocated_device_library(device: str, tmp_path: Path) -> None: + """Load a device without the wheel's private C++ libraries or Python imports.""" + package = distribution("mqt-core") + manifest_name = f"mqt-core-qdmi-{device}-device.qdmi.json" + assert package.files is not None + manifest = next(package.locate_file(file) for file in package.files if file.name == manifest_name) + entry = json.loads(manifest.read_text())["qdmi"]["devices"][0] + library = tmp_path / entry["library"] + shutil.copy2(manifest.parent / entry["library"], library) + script = """ +import ctypes +import sys + +library = ctypes.CDLL(sys.argv[1]) +assert getattr(library, sys.argv[2] + "_QDMI_device_initialize")() == 0 +assert getattr(library, sys.argv[2] + "_QDMI_device_finalize")() == 0 +""" + result = subprocess.run( # ruff: ignore[subprocess-without-shell-equals-true] + [sys.executable, "-c", script, str(library), entry["prefix"]], + cwd=tmp_path, + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 0, result.stderr + + def test_manifest_registration_and_offline_enumeration(tmp_path: Path) -> None: """List configured IDs even when their libraries cannot be loaded.""" malformed = tmp_path / "malformed.qdmi.json" diff --git a/test/python/test_cli.py b/test/python/test_cli.py index 227d17bb06..5909efa63d 100644 --- a/test/python/test_cli.py +++ b/test/python/test_cli.py @@ -20,7 +20,7 @@ import pytest # Import the private module to test its process replacement directly. -import mqt.core._bench as benchmark_cli # ruff: ignore[import-private-name] +import mqt.core._commands as commands # ruff: ignore[import-private-name] from mqt.core import __version__ as mqt_core_version if TYPE_CHECKING: @@ -115,6 +115,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.""" @@ -131,18 +141,19 @@ def test_benchmark_cli(script_runner: ScriptRunner) -> None: @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}") +@pytest.mark.parametrize("tool", ["mqt-core-bench", "mqt-cc"]) +def test_native_tool_launcher(platform: str, suffix: str, tool: str) -> None: + """Locate and execute bundled tools on each platform.""" + executable = Path(f"installation/mqt/core/bin/{tool}{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, + patch.object(commands.sys, "platform", platform), + patch.object(commands.sys, "argv", [tool, "--help"]), + patch.object(commands, "distribution") as distribution_mock, + patch.object(commands.os, "execv") as execv_mock, ): distribution_mock.return_value.locate_file.return_value = executable - benchmark_cli.main() + commands.run_tool(tool) 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"]) + distribution_mock.return_value.locate_file.assert_called_once_with(f"mqt/core/bin/{tool}{suffix}") + execv_mock.assert_called_once_with(executable, [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)