Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
547298b
Add qir/jit library and change qir/runner to use it
rturrado Jun 5, 2026
4034994
Fix typo in comment in src/qir/runtime/CMakeLists.txt
rturrado Jun 5, 2026
d1428e5
Add QIR program format support to the QDMI DDSim device
rturrado Jun 5, 2026
a5280fd
Add BUILD_MQT_CORE_QDMI_WITH_QIR option
rturrado Jun 5, 2026
9422a38
Update docs/qir/index.md
rturrado Jun 5, 2026
ac958b7
Clean up Session::run
rturrado Jun 5, 2026
a20336b
Update CHANGELOG.md
rturrado Jun 5, 2026
095cca1
Fix CMake configure error when MLIR is disabled
rturrado Jun 5, 2026
1108209
Wrap test fixture in anonymous namespace
rturrado Jun 5, 2026
6d763e8
Merge branch 'main' into 1695
rturrado Jun 5, 2026
596b6b3
Fix CHANGELOG.md
rturrado Jun 5, 2026
e44eba5
Merge branch 'main' into 1695
ystade Jun 6, 2026
71ecf0d
Try/catch JIT session code in the Runner
rturrado Jun 6, 2026
05e5ee5
Refactor submitQIRProgram and Runtime::getResults
rturrado Jun 6, 2026
ded5a3a
Make Runtime's output stream injectable
rturrado Jun 6, 2026
507b457
Merge remote-tracking branch 'origin/1695' into 1695
rturrado Jun 6, 2026
f920621
Fix missing <exception> header
rturrado Jun 6, 2026
8febb12
Record outputs into Runtime to support dynamic QIR
rturrado Jun 7, 2026
a1c36d2
Move QIR circuits to test/circuits and load them in dd-device tests
rturrado Jun 7, 2026
48a4528
Add Adaptive QIR profile support to the DDSIM QDMI device
rturrado Jun 7, 2026
f44a15a
Merge branch 'main' into 1695
rturrado Jun 9, 2026
f69c771
Address review feedback
rturrado Jun 9, 2026
0f77a35
Merge remote-tracking branch 'origin/1695' into 1695
rturrado Jun 9, 2026
d1f03bb
Address review feedback regarding jit::Session
rturrado Jun 9, 2026
1b8e2dc
Honor QDMI text/binary wire convention in PROGRAM payloads
rturrado Jun 10, 2026
e2a1fd7
Rename BUILD_MQT_CORE_QDMI_WITH_QIR to BUILD_MQT_CORE_QDMI_DDSIM_WITH…
rturrado Jun 11, 2026
b6144fe
Add QIR IR rewriter for stripping measurements
rturrado Jun 10, 2026
2b851ef
Add unit tests for the QIR IR rewriter
rturrado Jun 10, 2026
cb4abba
Add Execution mode to JitSession
rturrado Jun 11, 2026
5e42342
Add Runtime::takeState for state extraction
rturrado Jun 11, 2026
35e3386
Wire numShots == 0 for QIR Base Profile
rturrado Jun 11, 2026
a4829b0
Add JitSession memory and error path tests
rturrado Jun 12, 2026
e2c0510
Drop platform-specific MissingMainThrows test
rturrado Jun 12, 2026
678e19d
Cover Runtime::enlargeState resize branch
rturrado Jun 12, 2026
4e3a0d0
Merge branch 'main' of https://github.com/munich-quantum-toolkit/core…
rturrado Jun 12, 2026
e6d2652
Address comments from Yannick's code review
rturrado Jun 15, 2026
3769ae3
Extract `getProgram` into a shared QIR test helper
rturrado Jun 15, 2026
818ef78
Store `program_` as a variant of text and binary
rturrado Jun 15, 2026
1e59bde
Merge branch 'main' into 1695
rturrado Jun 15, 2026
d6e726d
Address comments from Lucas' code review
rturrado Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel

### Added

- ✨ Add QIR program format support to the DDSIM QDMI Device ([#1766]) ([**@rturrado**])
- 🚸 Add [CMake presets] to provide a standardized and reproducible way to configure builds ([#1660]) ([**@denialhaag**])
- ✨ Add a `quantum-loop-unroll` pass for unrolling for-loop operations containing quantum operations ([#1718]) ([**@MatthiasReumann**])
- ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**], [**@burgholzer**])
Expand Down Expand Up @@ -405,6 +406,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
[#1781]: https://github.com/munich-quantum-toolkit/core/pull/1781
[#1776]: https://github.com/munich-quantum-toolkit/core/pull/1776
[#1774]: https://github.com/munich-quantum-toolkit/core/pull/1774
[#1766]: https://github.com/munich-quantum-toolkit/core/pull/1766
[#1765]: https://github.com/munich-quantum-toolkit/core/pull/1765
[#1762]: https://github.com/munich-quantum-toolkit/core/pull/1762
[#1751]: https://github.com/munich-quantum-toolkit/core/pull/1751
Expand Down Expand Up @@ -656,6 +658,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
[**@simon1hofmann**]: https://github.com/simon1hofmann
[**@keefehuang**]: https://github.com/keefehuang
[**@J4MMlE**]: https://github.com/J4MMlE
[**@rturrado**]: https://github.com/rturrado

<!-- General links -->

Expand Down
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ endif()
cmake_dependent_option(BUILD_MQT_CORE_QIR_RUNNER "Build the QIR runner of the MQT Core project" ON
"BUILD_MQT_CORE_MLIR" OFF)

cmake_dependent_option(
BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR "Enable QIR program format support for the DDSIM QDMI Device"
ON "BUILD_MQT_CORE_MLIR" OFF)

# add main library code
add_subdirectory(src)

Expand Down
9 changes: 8 additions & 1 deletion docs/qir/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ See {cite:p}`stadeTowardsSupportingQIR2025` for more details.

### Building the Runner

To build this tool, the CMake option `BUILD_MQT_CORE_QIR_RUNNER` has to be enabled (which depends on `BUILD_MQT_CORE_MLIR` being set).
To build this tool, the CMake option `BUILD_MQT_CORE_QIR_RUNNER` has to be enabled.
It is enabled by default, but depends on `BUILD_MQT_CORE_MLIR` being set.
From the root of the repository, you can build the runner as follows:

```bash
Expand All @@ -36,3 +37,9 @@ The `mqt-core-qir-runner` can be used to execute a QIR file (typically with a `.

This will simulate the circuit and print the measurement results to the console.
The runner supports the QIR Base Profile.

### QIR Support in the DDSIM QDMI Device

The QDMI Device accepts jobs in the following program formats: QASM2, QASM3, QIR Base/Adaptive Profile Module (LLVM bitcode), and QIR Base/Adaptive Profile String (LLVM assembly).
These QIR formats are only supported when the `BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR` CMake option is enabled.
It is enabled by default, but depends on `BUILD_MQT_CORE_MLIR` being set.
54 changes: 52 additions & 2 deletions include/mqt-core/qdmi/devices/dd/Device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <future>
#include <limits>
#include <map>
Expand All @@ -30,6 +31,8 @@
#include <random>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>

namespace qdmi::dd {
class Device final : public Singleton<Device> {
Expand Down Expand Up @@ -189,8 +192,11 @@ struct MQT_DDSIM_QDMI_Device_Job_impl_d {
/// The program format
QDMI_Program_Format format_ = QDMI_PROGRAM_FORMAT_QASM3;

/// The quantum program associated with the job
std::string program_;
/// The quantum program associated with the job.
/// Text formats (QASM2/3, QIR Base/Adaptive String) are stored as
/// @c std::string; binary formats (QIR Base/Adaptive Module) are stored as
/// @c std::vector<std::byte>.
std::variant<std::string, std::vector<std::byte>> program_;

/// The number of shots for the job
size_t numShots_ = 1024U;
Expand Down Expand Up @@ -234,6 +240,41 @@ struct MQT_DDSIM_QDMI_Device_Job_impl_d {
auto getProbabilities(size_t size, void* data, size_t* sizeRet)
-> QDMI_STATUS;

/// Run @p body on a worker thread with the standard job lifecycle:
/// - increase the running-job count,
/// - set status to RUNNING,
/// - run @p body,
/// - set status to DONE or FAILED, and
/// - decrease the running-job count.
/// Typically, @p body will:
/// - parse the program,
/// - run or simulate it, and
/// - store the results in the job's output fields.
/// @returns @c QDMI_SUCCESS once the worker has been spawned.
/// Failures inside @p body are reported through the job status (FAILED),
/// not through the return value.
auto submitProgramAsync(std::function<void()> body) -> QDMI_STATUS;

/// Submit a QASM 2 or QASM 3 program.
/// Dispatches to the sampling or the state-extraction helper depending on
/// @c numShots_.
auto submitQASMProgram() -> QDMI_STATUS;
/// Sampling path for a QASM program (@c numShots_ > 0).
auto submitQASMProgramSampling() -> QDMI_STATUS;
/// State-extraction path for a QASM program (@c numShots_ == 0).
auto submitQASMProgramStateExtraction() -> QDMI_STATUS;

#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR
/// Submit a QIR Base/Adaptive Module or String program.
/// Dispatches to the sampling or the state-extraction helper depending on
/// @c numShots_.
auto submitQIRProgram() -> QDMI_STATUS;
/// Sampling path for a QIR program (@c numShots_ > 0).
auto submitQIRProgramSampling() -> QDMI_STATUS;
/// State-extraction path for a QIR Base Profile program (@c numShots_ == 0).
auto submitQIRProgramStateExtraction() -> QDMI_STATUS;
#endif

public:
/// Constructor for the MQT_DDSIM_QDMI_Device_Job_impl_d.
explicit MQT_DDSIM_QDMI_Device_Job_impl_d(
Expand All @@ -251,6 +292,15 @@ struct MQT_DDSIM_QDMI_Device_Job_impl_d {

/**
* @brief Sets a parameter for the job.
* @note When setting @c QDMI_DEVICE_JOB_PARAMETER_PROGRAM, the device uses
* the current @c QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT to decide whether
* the payload's wire @p size:
* - includes a trailing @c '\0' (text formats: QASM2, QASM3,
* QIR Base/Adaptive String) or
* - is the exact byte count (binary formats: QIR Base/Adaptive Module).
* Callers should therefore set @c PROGRAMFORMAT before @c PROGRAM.
* The default of @c QDMI_PROGRAM_FORMAT_QASM3 is assumed if @c PROGRAMFORMAT
* is not set.
* @see MQT_DDSIM_QDMI_device_job_set_parameter
*/
auto setParameter(QDMI_Device_Job_Parameter param, size_t size,
Expand Down
50 changes: 50 additions & 0 deletions include/mqt-core/qir/jit/IRRewriter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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
*/

#pragma once

#include <llvm/ADT/StringRef.h>
#include <llvm/IR/Module.h>

#include <array>
#include <cstddef>

namespace qir {

/// The set of call targets that @ref stripMeasurementRelatedCalls erases.
inline constexpr std::array<llvm::StringRef, 5> STRIP_TARGETS = {
"__quantum__qis__mz__body",
"__quantum__qis__m__body",
"__quantum__qis__measure__body",
"__quantum__rt__result_record_output",
"__quantum__rt__result_update_reference_count",
};

/**
* @brief Strips QIR measurement-related calls from @p m in place.
* @details Erases calls to the QIR measurement intrinsics, to the
* result-recording intrinsic, and to the result reference-count update
* intrinsic (whose Result operands would otherwise reference the null
* pointers left by the stripped measurements).
* Intended for QIR Base Profile programs only: in Adaptive Profile programs,
* measurement results feed classical control flow, so removing them silently
* changes observable behavior.
*
* The typical use is to prepare a Base Profile module for state-vector
* extraction: after this transform the JIT'd @c main can be run once and
* the resulting state remains in the @ref qir::Runtime DD instead of being
* collapsed by measurement.
*
* @param m Module to rewrite in place.
* @return Number of instructions erased.
*/
std::size_t stripMeasurementRelatedCalls(llvm::Module& m);

} // namespace qir
126 changes: 126 additions & 0 deletions include/mqt-core/qir/jit/Session.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* 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
*/

#pragma once

#include <llvm/ADT/ArrayRef.h>
#include <llvm/ADT/StringRef.h>
#include <llvm/ExecutionEngine/Orc/LLJIT.h>
#include <llvm/ExecutionEngine/Orc/ThreadSafeModule.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/Support/Error.h>

#include <memory>
#include <string>

namespace qir {

/**
* @brief Whether the JIT'd program runs to produce measurement samples or
* to leave the final quantum state in @ref qir::Runtime for external
* extraction.
* @details In @c StateExtraction mode the session strips QIR measurement
* and result-management calls from the IR before JIT-compiling, so the
* runtime's quantum state remains intact after @c main returns. Intended
* for QIR Base Profile programs only.
*/
enum class Execution { Sampling, StateExtraction };

/**
* @brief In-process JIT executor for QIR programs.
* @details The session does the following, in order:
* - Loads an LLVM module from either an IR file (text or bitcode) or
* an in-memory buffer,
* - JIT-compiles it via LLVM's OrcJIT with lazy compilation.
* - wires up the QIR runtime symbols, and
* - runs the module's @c main function.
* A session owns a single LLJIT instance and is not meant to be reused across
* modules; create a new @ref JitSession for each program.
*/
class JitSession {
public:
/// Signature of the @c main function produced by QIR-compiled modules.
using MainFn = int(int, char**);

/**
* @brief Build a session by loading IR from a file on disk.
* @param inputFile Path to a textual IR or bitcode file.
* @param mode Execution mode; see @ref Execution.
* @throws std::runtime_error if the file cannot be parsed or the JIT fails
* to initialize.
*/
explicit JitSession(llvm::StringRef inputFile,
Execution mode = Execution::Sampling);

/**
* @brief Build a session by loading IR from a memory buffer.
* @details Accepts either textual IR or bitcode. The buffer does not have
* to be null-terminated.
* @param irBytes Byte view of the IR.
* @param bufferName Identifier used in diagnostics.
* @param mode Execution mode; see @ref Execution.
* @throws std::runtime_error if the IR cannot be parsed or the JIT fails
* to initialize.
*/
JitSession(llvm::StringRef irBytes, llvm::StringRef bufferName,
Execution mode = Execution::Sampling);

/// Tears down the LLJIT and any JIT'd resources owned by the session.
~JitSession();

/**
* @brief Executes the JIT'd @c main function.
* @param args Argument strings passed as @c argv (excluding @c argv[0]).
* @param progName Value used as @c argv[0].
* @return The integer returned by the JIT'd @c main.
*/
int run(llvm::ArrayRef<std::string> args = {},
llvm::StringRef progName = "") const;

private:
llvm::orc::ThreadSafeContext tsCtx_{std::make_unique<llvm::LLVMContext>()};
llvm::orc::ThreadSafeModule module_;
std::unique_ptr<llvm::orc::LLJIT> jit_;
MainFn* mainFn_ = nullptr;

/// Registers the QIR runtime symbols with @c llvm::sys::DynamicLibrary so the
/// JIT can resolve them at link time.
/// Safe to call multiple times; the work runs only on the first call.
static void registerRuntimeSymbols();

/// Initializes the native target, asm printer and asm parser.
/// Safe to call multiple times; the work runs only on the first call.
static void initNativeTargets();

/// Parses LLVM IR from @p irPath using the session's thread-safe context.
llvm::Expected<llvm::orc::ThreadSafeModule>
loadModuleFromFile(llvm::StringRef irPath);

/// Parses LLVM IR (textual or bitcode) from @p irBytes using the session's
/// thread-safe context. @p bufferName is used in diagnostics.
llvm::Expected<llvm::orc::ThreadSafeModule>
loadModuleFromMemory(llvm::StringRef irBytes, llvm::StringRef bufferName);

/// Prepares the session to run the program:
/// - Validates the loaded module.
/// - Optionally strips measurement and result management calls
/// (for @ref Execution::StateExtraction).
/// - Builds the @c LLJIT instance
/// - Registers QIR runtime symbols
/// - Resolves @c main.
/// @throws std::runtime_error if loading failed or the JIT cannot start.
void initialize(llvm::Expected<llvm::orc::ThreadSafeModule> llvmModule,
Execution mode);

/// Tears down the @c LLJIT.
void deinitialize() const;
};

} // namespace qir
Loading
Loading