From 4d865f31d60e0db4ccba677ab752ab31ab68c8df Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Fri, 6 Mar 2026 09:32:58 +0100 Subject: [PATCH 01/64] Start getting started --- docs/mlir/GettingStarted.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 docs/mlir/GettingStarted.md diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md new file mode 100644 index 0000000000..2875475651 --- /dev/null +++ b/docs/mlir/GettingStarted.md @@ -0,0 +1,13 @@ +# Getting Started + +Getting started with the Multi-Level Intermediate Representation (MLIR) framework is not an easy task. Navigating through the overwhelming amount of online resources can already seem like a daunting task. Choosing the right ones, even more so. + +This tutorial attempts to cut through the noise. We draw from past experience to provide you a concise, but thorough, introduction to MLIR. Towards that end, we will guide you through some parts of the compilation process of a quantum program - and it all starts with a textual description of the program. + +#### Prerequisites + +## "Hello World" + +## The Optimization Dialect + +## Advanced Concepts From 1c73e873a8e38ea91adac64bfb723ec8855dd277 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 9 Mar 2026 08:03:41 +0100 Subject: [PATCH 02/64] Add snippet --- docs/mlir/GettingStarted.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index 2875475651..4ece3fca85 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -8,6 +8,14 @@ This tutorial attempts to cut through the noise. We draw from past experience to ## "Hello World" +```mlir +module { + func.func @main() { attributes = ["entry_point"] }{ + + } +} +``` + ## The Optimization Dialect ## Advanced Concepts From 2ba5dda4eaa86feb5648a5fbef3bde42dff2c0f4 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 9 Mar 2026 09:38:28 +0100 Subject: [PATCH 03/64] Improve intro to tutorial --- docs/mlir/GettingStarted.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index 4ece3fca85..52fc317b2d 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -1,10 +1,14 @@ # Getting Started -Getting started with the Multi-Level Intermediate Representation (MLIR) framework is not an easy task. Navigating through the overwhelming amount of online resources can already seem like a daunting task. Choosing the right ones, even more so. +The Multi-Level Intermediate Representation (MLIR) project is an extensive framework to build compilers for heterogenous hardware. We, the maintainers of the Munich Quantum Toolkit (MQT), explore MLIR for quantum compilation. That is, given a description of a quantum computation, transform this representation to one that is efficiently executable on a target architecture. -This tutorial attempts to cut through the noise. We draw from past experience to provide you a concise, but thorough, introduction to MLIR. Towards that end, we will guide you through some parts of the compilation process of a quantum program - and it all starts with a textual description of the program. +There is problem, however: Getting started with the MLIR framework is not an easy task. Navigating through the overwhelming amount of online resources can already seem like a daunting task. Choosing the right ones, even more so. This heavily impedes open-source contribution. Almost everyone can write Python code, but most do not know the intricacies of MLIR. -#### Prerequisites +This tutorial attempts to lower the barrier to contribute to our open-source quantum compilation stack. Towards that end, we guide you through the compilation of a quantum program. Alongside, we outline the fundamental data-structures and utilities that support this process. + +#### Installation + +If you haven't already, make sure to visit the [installation](https://mqt.readthedocs.io/projects/core/en/latest/installation.html) page, which describes how to setup the project (including MLIR) correctly. ## "Hello World" From 5dc2d404d3e12d073bac56f4cb053b918e213567 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 9 Mar 2026 13:32:08 +0100 Subject: [PATCH 04/64] Update "Hello World" section --- docs/mlir/GettingStarted.md | 22 ++++++++++++++++++---- docs/mlir/index.md | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index 52fc317b2d..d6d3e721cf 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -2,24 +2,38 @@ The Multi-Level Intermediate Representation (MLIR) project is an extensive framework to build compilers for heterogenous hardware. We, the maintainers of the Munich Quantum Toolkit (MQT), explore MLIR for quantum compilation. That is, given a description of a quantum computation, transform this representation to one that is efficiently executable on a target architecture. -There is problem, however: Getting started with the MLIR framework is not an easy task. Navigating through the overwhelming amount of online resources can already seem like a daunting task. Choosing the right ones, even more so. This heavily impedes open-source contribution. Almost everyone can write Python code, but most do not know the intricacies of MLIR. +There is problem, however: Getting started with the MLIR framework is not an easy task. Navigating through the overwhelming amount of online resources can already seem like a daunting task. Choosing the right ones, even more so. This heavily impedes open-source contribution. Almost everyone can write Python code, but most do not know the intricacies of MLIR. (TODO: Different motivation - just use the compiler - not contribute to it) This tutorial attempts to lower the barrier to contribute to our open-source quantum compilation stack. Towards that end, we guide you through the compilation of a quantum program. Alongside, we outline the fundamental data-structures and utilities that support this process. -#### Installation +**Installation** -If you haven't already, make sure to visit the [installation](https://mqt.readthedocs.io/projects/core/en/latest/installation.html) page, which describes how to setup the project (including MLIR) correctly. +If you haven't already, make sure to visit the [installation](https://mqt.readthedocs.io/projects/core/en/latest/installation.html) page which describes how to setup the project (including MLIR) correctly. ## "Hello World" +The fundamental computational unit in quantum computing is the _qubit_. Consequently, at some point a quantum computation needs to allocate (and subsequently deallocate) qubits. + ```mlir +/// file: allocation.mlir module { - func.func @main() { attributes = ["entry_point"] }{ + func.func @main() { + %q0 = qc.alloc : !qc.qubit + qc.dealloc %q0 : !qc.qubit + func.return } } ``` +The tiny snippet above already covers a lot of relevant MLIR concepts. + +One of the most important ones are _dialects_. A dialect groups operations (`alloc`, `dealloc`) and types (`qubit`) under a common namespace (`qc`). The example above combines built-in dialects with custom dialects. The [`builtin`](https://mlir.llvm.org/docs/Dialects/Builtin/) dialect provides the `module` operation (the `builtin.` is usually omitted) and the [`func`](https://mlir.llvm.org/docs/Dialects/Func/) dialect contains operations to define and call functions. The custom [`qc`](https://mqt.readthedocs.io/projects/core/en/latest/mlir/QC.html) (_"quantum circuit"_) dialect is defined in the MQT and extends the built-in ones with the necessary functionality for quantum computing. + +Operations can consume (_"operands"_) and produce (_"results"_) values. For instance, the `qc.alloc` operation produces the value `q0`, while the `qc.dealloc` operation consumes it. Furthermore, values in MLIR adhere to the static single-assignment (SSA) principle, where each variable is assigned exactly once and never reassigned. + +Moreover, purely from a visual perspective, one can notice that the snippet above contains nested structures. For example, the `module` operation contains the `func.func` operation. + ## The Optimization Dialect ## Advanced Concepts diff --git a/docs/mlir/index.md b/docs/mlir/index.md index 3b5e91b20c..e5db9842a0 100644 --- a/docs/mlir/index.md +++ b/docs/mlir/index.md @@ -19,6 +19,7 @@ So far, this comprises conversions between QC and QCO as well as from QC to QIR. QC QCO Conversions +GettingStarted ``` :::{note} From 4eb96a280d42377d9802d9609487f914334069fb Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 10 Mar 2026 10:31:46 +0100 Subject: [PATCH 05/64] Add bell state part --- docs/mlir/GettingStarted.md | 79 ++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index d6d3e721cf..148993b1f3 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -30,9 +30,84 @@ The tiny snippet above already covers a lot of relevant MLIR concepts. One of the most important ones are _dialects_. A dialect groups operations (`alloc`, `dealloc`) and types (`qubit`) under a common namespace (`qc`). The example above combines built-in dialects with custom dialects. The [`builtin`](https://mlir.llvm.org/docs/Dialects/Builtin/) dialect provides the `module` operation (the `builtin.` is usually omitted) and the [`func`](https://mlir.llvm.org/docs/Dialects/Func/) dialect contains operations to define and call functions. The custom [`qc`](https://mqt.readthedocs.io/projects/core/en/latest/mlir/QC.html) (_"quantum circuit"_) dialect is defined in the MQT and extends the built-in ones with the necessary functionality for quantum computing. -Operations can consume (_"operands"_) and produce (_"results"_) values. For instance, the `qc.alloc` operation produces the value `q0`, while the `qc.dealloc` operation consumes it. Furthermore, values in MLIR adhere to the static single-assignment (SSA) principle, where each variable is assigned exactly once and never reassigned. +Operations can consume (_"operands"_) and produce (_"results"_) values. For instance, `qc.alloc` produces the value `q0`, while `qc.dealloc` consumes it. Furthermore, values in MLIR adhere to the static single-assignment (SSA) principle, where each variable is assigned exactly once and never reassigned. -Moreover, purely from a visual perspective, one can notice that the snippet above contains nested structures. For example, the `module` operation contains the `func.func` operation. +Moreover, some operations contain others. For example, the `module` operation contains the `func.func` operation. In MLIR these nested structures are represented by _regions_ and _blocks_. The following figure visualizes the connection between operations, regions, and blocks succinctly. + +``` +┌──────────────────────┐ +│ Operation │ +├──────────────────────┤ +│┌────────────────────┐│ +││ Region ││ +│├────────────────────┤│ +││ ┌─────────────────┐││ +││ │ Block │││ +││ │┌───────────────┐│││ +││ ││Operation ││││ +││ │└───────────────┘│││ +││ └─────────────────┘││ +││ ┌─────────────────┐││ +││ │ Block │││ +││ └─────────────────┘││ +│└────────────────────┘│ +└──────────────────────┘ +``` + +In the snippet above, the `module` operation has one region with exactly one block. Inside this block is the `func.func` op, which again has one region with a single block. Finally, this inner block contains the quantum operations. + +As of now, our quantum program doesn't compute anything. Let's change that. + +```mlir +/// file: bell.mlir +module { + func.func @main() { + %q0 = qc.alloc : !qc.qubit + %q1 = qc.alloc : !qc.qubit + + qc.h %q0 : !qc.qubit + qc.ctrl(%q0) { + qc.x %q1 : !qc.qubit + } : !qc.qubit + + %c0 = qc.measure %q0 : !qc.qubit -> i1 + %c1 = qc.measure %q1 : !qc.qubit -> i1 + + qc.dealloc %q0 : !qc.qubit + qc.dealloc %q1 : !qc.qubit + + func.return + } +} +``` + +In the updated code snippet, we allocate a second qubit and construct the first Bell state by applying a Hadamard gate and a controlled NOT gate. Finally, we measure and deallocate both qubits. + +Sometimes (e.g. when writing unit tests) it can be useful to programmatically build programs. For that purpose, the `QCProgramBuilder` exists. The C++ snippet that follows constructs the above quantum computation programmatically. + +```cpp +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" + +void bell(MLIRContext* context) { + qc::QCProgramBuilder builder(context); + builder.initialize(); + + const auto q0 = builder.allocQubit(); + const auto q1 = builder.allocQubit(); + + builder.h(q0); + builder.cx(q0, q1); + + const auto c0 = builder.measure(q0); + const auto c1 = builder.measure(q1); + + builder.dealloc(q0); + builder.dealloc(q1); + + // Automatically adds the module and entry point function. + [[maybe_unused]] auto moduleOp = builder.finalize(); +} +``` ## The Optimization Dialect From 9f474d4c5bfea5c54a65ff3440dc7ac975759a4f Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 10 Mar 2026 11:02:06 +0100 Subject: [PATCH 06/64] Improve titles --- docs/mlir/GettingStarted.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index 148993b1f3..7e9bfc7f42 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -10,7 +10,7 @@ This tutorial attempts to lower the barrier to contribute to our open-source qua If you haven't already, make sure to visit the [installation](https://mqt.readthedocs.io/projects/core/en/latest/installation.html) page which describes how to setup the project (including MLIR) correctly. -## "Hello World" +## Understanding And Writing Quantum IR The fundamental computational unit in quantum computing is the _qubit_. Consequently, at some point a quantum computation needs to allocate (and subsequently deallocate) qubits. @@ -56,7 +56,11 @@ Moreover, some operations contain others. For example, the `module` operation co In the snippet above, the `module` operation has one region with exactly one block. Inside this block is the `func.func` op, which again has one region with a single block. Finally, this inner block contains the quantum operations. -As of now, our quantum program doesn't compute anything. Let's change that. +
+ +As of now, our quantum program doesn't compute anything. Let's change that! + +The following snippet allocates a second qubit and constructs the first Bell state by applying a Hadamard and subsequent controlled-X gate. Finally, both qubits are measured and deallocated. The datatype that represents the measurement outcome is `i1` - the MLIR-equivalent of a boolean. ```mlir /// file: bell.mlir @@ -81,9 +85,9 @@ module { } ``` -In the updated code snippet, we allocate a second qubit and construct the first Bell state by applying a Hadamard gate and a controlled NOT gate. Finally, we measure and deallocate both qubits. +## Optimizing Quantum IR -Sometimes (e.g. when writing unit tests) it can be useful to programmatically build programs. For that purpose, the `QCProgramBuilder` exists. The C++ snippet that follows constructs the above quantum computation programmatically. + From 0a53206ac03b40b31a4ba85681e9ed3cb9c456a5 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 10 Mar 2026 12:43:58 +0100 Subject: [PATCH 07/64] Update getting started --- docs/mlir/GettingStarted.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index 7e9bfc7f42..8c4bcac674 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -56,6 +56,8 @@ Moreover, some operations contain others. For example, the `module` operation co In the snippet above, the `module` operation has one region with exactly one block. Inside this block is the `func.func` op, which again has one region with a single block. Finally, this inner block contains the quantum operations. +TODO: What's up with the func.return, better visualization (connect with IR above) +
As of now, our quantum program doesn't compute anything. Let's change that! From bdd1ebea2a571342f167ddff279110edacef45d7 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 11 Mar 2026 10:29:08 +0100 Subject: [PATCH 08/64] Add more sections --- docs/mlir/GettingStarted.md | 78 ++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index 8c4bcac674..ff82283f3d 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -1,6 +1,6 @@ # Getting Started -The Multi-Level Intermediate Representation (MLIR) project is an extensive framework to build compilers for heterogenous hardware. We, the maintainers of the Munich Quantum Toolkit (MQT), explore MLIR for quantum compilation. That is, given a description of a quantum computation, transform this representation to one that is efficiently executable on a target architecture. +The Multi-Level Intermediate Representation (MLIR) project is an extensive framework to build compilers for heterogenous hardware. We, the maintainers of the Munich Quantum Toolkit (MQT), explore MLIR for quantum compilation. That is, given an intermediate representation (IR) - a description - of a quantum computation, transform this representation to one that is efficiently executable on a target architecture. There is problem, however: Getting started with the MLIR framework is not an easy task. Navigating through the overwhelming amount of online resources can already seem like a daunting task. Choosing the right ones, even more so. This heavily impedes open-source contribution. Almost everyone can write Python code, but most do not know the intricacies of MLIR. (TODO: Different motivation - just use the compiler - not contribute to it) @@ -12,10 +12,12 @@ If you haven't already, make sure to visit the [installation](https://mqt.readth ## Understanding And Writing Quantum IR +### Dynamic and Static Allocation + The fundamental computational unit in quantum computing is the _qubit_. Consequently, at some point a quantum computation needs to allocate (and subsequently deallocate) qubits. ```mlir -/// file: allocation.mlir +/// file: dynamic-allocation.mlir module { func.func @main() { %q0 = qc.alloc : !qc.qubit @@ -26,7 +28,23 @@ module { } ``` -The tiny snippet above already covers a lot of relevant MLIR concepts. +An allocation and deallocation defines the start and end of a qubit's logical scope or availability within a program, respectively. The snippet above allocates a dynamic qubit. That is, a qubit without a specific hardware location. To target a specific hardware qubit, we use the `static` operation and provide the respective hardware location. + +```mlir +/// file: static-allocation.mlir +module { + func.func @main() { + %q0 = qc.static 42 : !qc.qubit + qc.dealloc %q0 : !qc.qubit + + func.return + } +} +``` + +#### Interlude: MLIR Concepts + +The snippets above cover already a lot of relevant MLIR concepts. One of the most important ones are _dialects_. A dialect groups operations (`alloc`, `dealloc`) and types (`qubit`) under a common namespace (`qc`). The example above combines built-in dialects with custom dialects. The [`builtin`](https://mlir.llvm.org/docs/Dialects/Builtin/) dialect provides the `module` operation (the `builtin.` is usually omitted) and the [`func`](https://mlir.llvm.org/docs/Dialects/Func/) dialect contains operations to define and call functions. The custom [`qc`](https://mqt.readthedocs.io/projects/core/en/latest/mlir/QC.html) (_"quantum circuit"_) dialect is defined in the MQT and extends the built-in ones with the necessary functionality for quantum computing. @@ -58,11 +76,9 @@ In the snippet above, the `module` operation has one region with exactly one blo TODO: What's up with the func.return, better visualization (connect with IR above) -
- -As of now, our quantum program doesn't compute anything. Let's change that! +### Gates And Measurements -The following snippet allocates a second qubit and constructs the first Bell state by applying a Hadamard and subsequent controlled-X gate. Finally, both qubits are measured and deallocated. The datatype that represents the measurement outcome is `i1` - the MLIR-equivalent of a boolean. +Once the qubits are allocated, we can start applying gates to implement quantum algorithms. In the following snippet we construct the first Bell state by applying a Hadamard and controlled-X gate. Subsequently, we measure both qubits which produces two values of the datatype `i1` - the MLIR-equivalent of a boolean. ```mlir /// file: bell.mlir @@ -87,30 +103,36 @@ module { } ``` -## Optimizing Quantum IR - - +``` + +## Optimizing Quantum IR From 4cd43f3e0a937c7e4bbfecea1ac963637ad53f9b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 11 Mar 2026 13:14:40 +0100 Subject: [PATCH 09/64] Starting "Optimizing" Section --- docs/mlir/GettingStarted.md | 56 +++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index ff82283f3d..ebe457248a 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -10,7 +10,7 @@ This tutorial attempts to lower the barrier to contribute to our open-source qua If you haven't already, make sure to visit the [installation](https://mqt.readthedocs.io/projects/core/en/latest/installation.html) page which describes how to setup the project (including MLIR) correctly. -## Understanding And Writing Quantum IR +## Understanding Quantum IR ### Dynamic and Static Allocation @@ -42,39 +42,19 @@ module { } ``` -#### Interlude: MLIR Concepts - -The snippets above cover already a lot of relevant MLIR concepts. - -One of the most important ones are _dialects_. A dialect groups operations (`alloc`, `dealloc`) and types (`qubit`) under a common namespace (`qc`). The example above combines built-in dialects with custom dialects. The [`builtin`](https://mlir.llvm.org/docs/Dialects/Builtin/) dialect provides the `module` operation (the `builtin.` is usually omitted) and the [`func`](https://mlir.llvm.org/docs/Dialects/Func/) dialect contains operations to define and call functions. The custom [`qc`](https://mqt.readthedocs.io/projects/core/en/latest/mlir/QC.html) (_"quantum circuit"_) dialect is defined in the MQT and extends the built-in ones with the necessary functionality for quantum computing. - -Operations can consume (_"operands"_) and produce (_"results"_) values. For instance, `qc.alloc` produces the value `q0`, while `qc.dealloc` consumes it. Furthermore, values in MLIR adhere to the static single-assignment (SSA) principle, where each variable is assigned exactly once and never reassigned. - -Moreover, some operations contain others. For example, the `module` operation contains the `func.func` operation. In MLIR these nested structures are represented by _regions_ and _blocks_. The following figure visualizes the connection between operations, regions, and blocks succinctly. +### (Interlude: MLIR Concepts) +```{note} +If you are already familiar with the fundamental concepts of MLIR, you may skip this section. ``` -┌──────────────────────┐ -│ Operation │ -├──────────────────────┤ -│┌────────────────────┐│ -││ Region ││ -│├────────────────────┤│ -││ ┌─────────────────┐││ -││ │ Block │││ -││ │┌───────────────┐│││ -││ ││Operation ││││ -││ │└───────────────┘│││ -││ └─────────────────┘││ -││ ┌─────────────────┐││ -││ │ Block │││ -││ └─────────────────┘││ -│└────────────────────┘│ -└──────────────────────┘ -``` -In the snippet above, the `module` operation has one region with exactly one block. Inside this block is the `func.func` op, which again has one region with a single block. Finally, this inner block contains the quantum operations. +The short snippets above contain many fundamental concepts of MLIR. + +- **Dialects**: A dialect groups operations (`alloc`, `dealloc`) and types (`qubit`) under a common namespace (`qc`). The example above combines built-in dialects with custom dialects. The [`builtin`](https://mlir.llvm.org/docs/Dialects/Builtin/) dialect provides the `module` operation (the `builtin.` is usually omitted) and the [`func`](https://mlir.llvm.org/docs/Dialects/Func/) dialect contains operations to define and call functions. The custom [`qc`](https://mqt.readthedocs.io/projects/core/en/latest/mlir/QC.html) (_"quantum circuit"_) dialect is defined in the MQT and extends the built-in ones with the necessary functionality for quantum computing. +- **SSA Values**: Operations can consume (_"operands"_) and produce (_"results"_) values. For instance, `qc.alloc` produces the value `q0`, while `qc.dealloc` consumes it. Furthermore, values in MLIR adhere to the static single-assignment (SSA) principle, where each variable is assigned exactly once and never reassigned. +- **Regions and Blocks**: To represent hierarchical structures, operations may contain _"regions"_. A region consists of one to many _"blocks"_ which again contain operations. For instance, the `module` operation contains one region consisting of one block that contains the `func.func` operation. A block optionally requires a _"terminator"_ that defines the end of the current block. The `func.return` operation is such a terminator. The following figure visualizes the connection between operations, regions, and blocks succinctly. -TODO: What's up with the func.return, better visualization (connect with IR above) +TODO ### Gates And Measurements @@ -136,3 +116,19 @@ module { ``` ## Optimizing Quantum IR + +By combining built-in dialects and the QC dialect we can implement quantum algorithms in MLIR. This section outlines how to use the MQT Compiler Driver to optimize quantum programs. + +### User Interface + +The following command executes the compiler and performs a series of optimizations on the given quantum program. Files using the OpenQASM format will automatically be translated into the QC dialect. + +```console +$ mqt-cc [options] +``` + +### Programming Interface + +## Emitting Low-Level Quantum IR + +## Summary From 5c306082274f2c4cc7dc5b2fdaf41dbc9b2eeb8f Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 11 Mar 2026 15:11:49 +0100 Subject: [PATCH 10/64] Update "Optimizing" section --- docs/_static/qco-dataflow.svg | 174 ++++++++++++++++++++++++++++++++++ docs/mlir/GettingStarted.md | 58 +++++++++++- 2 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 docs/_static/qco-dataflow.svg diff --git a/docs/_static/qco-dataflow.svg b/docs/_static/qco-dataflow.svg new file mode 100644 index 0000000000..877a5a38d7 --- /dev/null +++ b/docs/_static/qco-dataflow.svg @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index ebe457248a..c5e3e0d5b5 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -12,7 +12,7 @@ If you haven't already, make sure to visit the [installation](https://mqt.readth ## Understanding Quantum IR -### Dynamic and Static Allocation +### Dynamic And Static Allocation The fundamental computational unit in quantum computing is the _qubit_. Consequently, at some point a quantum computation needs to allocate (and subsequently deallocate) qubits. @@ -50,7 +50,7 @@ If you are already familiar with the fundamental concepts of MLIR, you may skip The short snippets above contain many fundamental concepts of MLIR. -- **Dialects**: A dialect groups operations (`alloc`, `dealloc`) and types (`qubit`) under a common namespace (`qc`). The example above combines built-in dialects with custom dialects. The [`builtin`](https://mlir.llvm.org/docs/Dialects/Builtin/) dialect provides the `module` operation (the `builtin.` is usually omitted) and the [`func`](https://mlir.llvm.org/docs/Dialects/Func/) dialect contains operations to define and call functions. The custom [`qc`](https://mqt.readthedocs.io/projects/core/en/latest/mlir/QC.html) (_"quantum circuit"_) dialect is defined in the MQT and extends the built-in ones with the necessary functionality for quantum computing. +- **Dialects**: A dialect groups operations (`alloc`, `dealloc`) and types (`qubit`) under a common namespace (`qc`). The example above combines built-in dialects with custom dialects. The [`builtin`](https://mlir.llvm.org/docs/Dialects/Builtin/) dialect provides the `module` operation (the `builtin.` is usually omitted) and the [`func`](https://mlir.llvm.org/docs/Dialects/Func/) dialect contains operations to define and call functions. The custom [`qc`](./QC.html) (_"quantum circuit"_) dialect is defined in the MQT and extends the built-in ones with the necessary functionality for quantum computing. - **SSA Values**: Operations can consume (_"operands"_) and produce (_"results"_) values. For instance, `qc.alloc` produces the value `q0`, while `qc.dealloc` consumes it. Furthermore, values in MLIR adhere to the static single-assignment (SSA) principle, where each variable is assigned exactly once and never reassigned. - **Regions and Blocks**: To represent hierarchical structures, operations may contain _"regions"_. A region consists of one to many _"blocks"_ which again contain operations. For instance, the `module` operation contains one region consisting of one block that contains the `func.func` operation. A block optionally requires a _"terminator"_ that defines the end of the current block. The `func.return` operation is such a terminator. The following figure visualizes the connection between operations, regions, and blocks succinctly. @@ -115,11 +115,13 @@ module { } ``` +### Reusable Components + ## Optimizing Quantum IR By combining built-in dialects and the QC dialect we can implement quantum algorithms in MLIR. This section outlines how to use the MQT Compiler Driver to optimize quantum programs. -### User Interface +### External Interface The following command executes the compiler and performs a series of optimizations on the given quantum program. Files using the OpenQASM format will automatically be translated into the QC dialect. @@ -127,7 +129,55 @@ The following command executes the compiler and performs a series of optimizatio $ mqt-cc [options] ``` -### Programming Interface +For example, running `mqt-cc` on the first code snippet of this tutorial yields the following IR. + +```console +$ mqt-cc dynamic-allocation.mlir +module { + func.func @main() { + return + } +} +``` + +What happened? Because there are no unitary operations between the allocation and deallocation of the qubit, the `RemoveAllocDeallocPair` canonicalization pattern matches and removes the unused qubit from the program. + +### Internal Interface + +Internally, the optimizations are performed on the [`qco`](./QCO.md) (_"quantum circuit optimization"_) dialect. While the QC dialect is great for interfacing with other formats (such as OpenQASM), the QCO dialect is specifically designed for optimizations. The QC dialect uses _reference semantics_ while the QCO utilizes _value semantics_. + +The following IR describes the construction of the first Bell state (and subsequent measurement) in the QCO dialect. Notice how each unitary operation consumes and produces SSA values and each SSA value is used at most once (_"linear typing"_). + +```mlir +/// file: bell-qco.mlir +module { + func.func @main() { + %q0_0 = qco.alloc : !qco.qubit + %q1_0 = qco.alloc : !qco.qubit + + %q0_1 = qco.h %q0_0 : !qco.qubit -> !qco.qubit + %q0_2, %q1_1 = qco.ctrl(%q0_1) targets (%arg0 = %q1_0) { + %q0_2 = qco.x %arg0 : !qco.qubit -> !qco.qubit + qco.yield %q0_2 + } : ({!qco.qubit}, {!qco.qubit}) -> ({!qco.qubit}, {!qco.qubit}) + + %q0_3, %c0 = qco.measure %q0_2 : !qco.qubit + %q1_2, %c1 = qco.measure %q1_1 : !qco.qubit + + qco.dealloc %q0_3 : !qco.qubit + qco.dealloc %q1_2 : !qco.qubit + } +} +``` + +Although much harder to read and write as a human, the data-flow graph of the QCO dialect enables the constant lookup of dependencies between operations as well as the efficient traversal of the circuit. + +```{image} ../_static/qco-dataflow.svg +:width: 55% +:align: center +``` + +Fortunately, no one forces us to write QCO dialect. The MQT Compiler implements a transformation from the QC to QCO dialect. In MLIR, this transformation from one dialect to another is called _conversion_. As a fact, each time the `mqt-cc` executable is invoked, the compiler performs such a conversion. Using the `--record-intermediates` CLI option, one can inspect the IR after each step in the compilation pipeline. ## Emitting Low-Level Quantum IR From 7991d953447c864897c33a6c4b7ecce59d0ab8c3 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 12 Mar 2026 10:40:06 +0100 Subject: [PATCH 11/64] Add compilation pipeline figure --- docs/_static/compilation-pipeline.svg | 111 ++++++++++++++++++++++++++ docs/mlir/GettingStarted.md | 31 ++++--- 2 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 docs/_static/compilation-pipeline.svg diff --git a/docs/_static/compilation-pipeline.svg b/docs/_static/compilation-pipeline.svg new file mode 100644 index 0000000000..3215fe44d4 --- /dev/null +++ b/docs/_static/compilation-pipeline.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index c5e3e0d5b5..cbf9e57ad4 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -2,10 +2,6 @@ The Multi-Level Intermediate Representation (MLIR) project is an extensive framework to build compilers for heterogenous hardware. We, the maintainers of the Munich Quantum Toolkit (MQT), explore MLIR for quantum compilation. That is, given an intermediate representation (IR) - a description - of a quantum computation, transform this representation to one that is efficiently executable on a target architecture. -There is problem, however: Getting started with the MLIR framework is not an easy task. Navigating through the overwhelming amount of online resources can already seem like a daunting task. Choosing the right ones, even more so. This heavily impedes open-source contribution. Almost everyone can write Python code, but most do not know the intricacies of MLIR. (TODO: Different motivation - just use the compiler - not contribute to it) - -This tutorial attempts to lower the barrier to contribute to our open-source quantum compilation stack. Towards that end, we guide you through the compilation of a quantum program. Alongside, we outline the fundamental data-structures and utilities that support this process. - **Installation** If you haven't already, make sure to visit the [installation](https://mqt.readthedocs.io/projects/core/en/latest/installation.html) page which describes how to setup the project (including MLIR) correctly. @@ -50,7 +46,7 @@ If you are already familiar with the fundamental concepts of MLIR, you may skip The short snippets above contain many fundamental concepts of MLIR. -- **Dialects**: A dialect groups operations (`alloc`, `dealloc`) and types (`qubit`) under a common namespace (`qc`). The example above combines built-in dialects with custom dialects. The [`builtin`](https://mlir.llvm.org/docs/Dialects/Builtin/) dialect provides the `module` operation (the `builtin.` is usually omitted) and the [`func`](https://mlir.llvm.org/docs/Dialects/Func/) dialect contains operations to define and call functions. The custom [`qc`](./QC.html) (_"quantum circuit"_) dialect is defined in the MQT and extends the built-in ones with the necessary functionality for quantum computing. +- **Dialects**: A dialect groups operations (`alloc`, `dealloc`) and types (`qubit`) under a common namespace (`qc`). The example above combines built-in dialects with custom dialects. The [`builtin`](https://mlir.llvm.org/docs/Dialects/Builtin/) dialect provides the `module` operation (the `builtin.` is usually omitted) and the [`func`](https://mlir.llvm.org/docs/Dialects/Func/) dialect contains operations to define and call functions. The custom [`qc`](./QC.md) (_"quantum circuit"_) dialect is defined in the MQT and extends the built-in ones with the necessary functionality for quantum computing. - **SSA Values**: Operations can consume (_"operands"_) and produce (_"results"_) values. For instance, `qc.alloc` produces the value `q0`, while `qc.dealloc` consumes it. Furthermore, values in MLIR adhere to the static single-assignment (SSA) principle, where each variable is assigned exactly once and never reassigned. - **Regions and Blocks**: To represent hierarchical structures, operations may contain _"regions"_. A region consists of one to many _"blocks"_ which again contain operations. For instance, the `module` operation contains one region consisting of one block that contains the `func.func` operation. A block optionally requires a _"terminator"_ that defines the end of the current block. The `func.return` operation is such a terminator. The following figure visualizes the connection between operations, regions, and blocks succinctly. @@ -119,7 +115,9 @@ module { ## Optimizing Quantum IR -By combining built-in dialects and the QC dialect we can implement quantum algorithms in MLIR. This section outlines how to use the MQT Compiler Driver to optimize quantum programs. +By combining built-in dialects and the QC dialect we can implement quantum algorithms in MLIR. This section outlines how to use our compiler driver to optimize quantum programs. + + ### External Interface @@ -144,9 +142,9 @@ What happened? Because there are no unitary operations between the allocation an ### Internal Interface -Internally, the optimizations are performed on the [`qco`](./QCO.md) (_"quantum circuit optimization"_) dialect. While the QC dialect is great for interfacing with other formats (such as OpenQASM), the QCO dialect is specifically designed for optimizations. The QC dialect uses _reference semantics_ while the QCO utilizes _value semantics_. +Internally, the optimizations are performed on the [`qco`](./QCO.md) (_"quantum circuit optimization"_) dialect. While the QC dialect is great for exchanging with other formats (such as OpenQASM), the QCO dialect is specifically designed for optimizations. -The following IR describes the construction of the first Bell state (and subsequent measurement) in the QCO dialect. Notice how each unitary operation consumes and produces SSA values and each SSA value is used at most once (_"linear typing"_). +The following IR describes the construction of the first Bell state (and subsequent measurement) in the QCO dialect. Each unitary operation consumes and produces SSA values and each SSA value is used at most once (_"linear typing"_). Semantically, a qubit SSA value in the QCO dialect represents the state of the qubit (_"value semantics"_) whereas in the QC dialect a qubit SSA value references a qubit (_"reference semantics"_). ```mlir /// file: bell-qco.mlir @@ -170,15 +168,28 @@ module { } ``` -Although much harder to read and write as a human, the data-flow graph of the QCO dialect enables the constant lookup of dependencies between operations as well as the efficient traversal of the circuit. +The following figure illustrates the data-flow graph of the IR above. Thanks to the QCO dialect, the dependencies between operations become immediately apparent. For example, the controlled-X gate depends on the Hadamard gate because it consumes the `q0_1` qubit SSA value. Moreover, MLIR provides the necessary functionality to efficiently traverse the data-flow graph and thus the circuit. ```{image} ../_static/qco-dataflow.svg :width: 55% :align: center ``` -Fortunately, no one forces us to write QCO dialect. The MQT Compiler implements a transformation from the QC to QCO dialect. In MLIR, this transformation from one dialect to another is called _conversion_. As a fact, each time the `mqt-cc` executable is invoked, the compiler performs such a conversion. Using the `--record-intermediates` CLI option, one can inspect the IR after each step in the compilation pipeline. +Quantum IR in the QCO dialect can be quite complex. Writing it by hand is certainly a errorprone task. Fortunately, you don't have to. The compiler driver's interface accepts and produces quantum IR in the QC dialect. Under the hood, it transforms it to the QCO dialect, performs the optimizations, and transforms it back to the QC dialect. That's also why we refer to the QC dialect as interface dialect. The following figure depicts the interplay between the two dialects illustratively. + +```{image} ../_static/compilation-pipeline.svg +:width: 35% +:align: center +``` + +To print the quantum IR after each step in the compilation pipeline, you can supply the `--record-intermediates` option to the compiler driver. + +```console +$ mqt-cc --record-intermediates +``` ## Emitting Low-Level Quantum IR +TODO + ## Summary From 45b587cd23d15274e50a0834597a1500a0d87137 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 12 Mar 2026 10:53:50 +0100 Subject: [PATCH 12/64] Update figure --- docs/_static/compilation-pipeline.svg | 153 +++++++++++++++++--------- docs/mlir/GettingStarted.md | 6 +- 2 files changed, 106 insertions(+), 53 deletions(-) diff --git a/docs/_static/compilation-pipeline.svg b/docs/_static/compilation-pipeline.svg index 3215fe44d4..9a37cec5e8 100644 --- a/docs/_static/compilation-pipeline.svg +++ b/docs/_static/compilation-pipeline.svg @@ -9,17 +9,17 @@ Licensed under the MIT License --> - + - + - + - + @@ -45,67 +45,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - + - - + + + + + + + - - + + + - + - + - - - - - + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - + + diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index cbf9e57ad4..9abec212c3 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -115,7 +115,7 @@ module { ## Optimizing Quantum IR -By combining built-in dialects and the QC dialect we can implement quantum algorithms in MLIR. This section outlines how to use our compiler driver to optimize quantum programs. +By combining built-in dialects and the QC dialect we can implement quantum algorithms in MLIR. This section outlines how to use the compiler driver to optimize your quantum programs. @@ -138,7 +138,7 @@ module { } ``` -What happened? Because there are no unitary operations between the allocation and deallocation of the qubit, the `RemoveAllocDeallocPair` canonicalization pattern matches and removes the unused qubit from the program. +What happened? Because there are no unitary operations between the allocation and deallocation of the qubit, the `RemoveAllocDeallocPair` canonicalization pattern matches and removes the unused qubit from the program. This one example of the many optimizations implemented in the quantum compiler driver. ### Internal Interface @@ -178,7 +178,7 @@ The following figure illustrates the data-flow graph of the IR above. Thanks to Quantum IR in the QCO dialect can be quite complex. Writing it by hand is certainly a errorprone task. Fortunately, you don't have to. The compiler driver's interface accepts and produces quantum IR in the QC dialect. Under the hood, it transforms it to the QCO dialect, performs the optimizations, and transforms it back to the QC dialect. That's also why we refer to the QC dialect as interface dialect. The following figure depicts the interplay between the two dialects illustratively. ```{image} ../_static/compilation-pipeline.svg -:width: 35% +:width: 50% :align: center ``` From f662ba91305139d3e166e05b5a0725af3daaa265 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Thu, 12 Mar 2026 13:31:52 +0100 Subject: [PATCH 13/64] Minor updates --- docs/mlir/GettingStarted.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/mlir/GettingStarted.md b/docs/mlir/GettingStarted.md index 9abec212c3..3f511d2b2d 100644 --- a/docs/mlir/GettingStarted.md +++ b/docs/mlir/GettingStarted.md @@ -1,6 +1,8 @@ # Getting Started -The Multi-Level Intermediate Representation (MLIR) project is an extensive framework to build compilers for heterogenous hardware. We, the maintainers of the Munich Quantum Toolkit (MQT), explore MLIR for quantum compilation. That is, given an intermediate representation (IR) - a description - of a quantum computation, transform this representation to one that is efficiently executable on a target architecture. +The Multi-Level Intermediate Representation (MLIR) project is an extensive framework to build compilers for heterogenous hardware. The Munich Quantum Toolkit (MQT) utilizes MLIR for quantum compilation. That is, given an intermediate representation (IR) - a description - of a quantum computation, transform this representation to one that is efficiently executable on a target architecture. + +This tutorial teaches you how to understand, write, and optimize quantum IR in MLIR using the infrastructure built within the MQT. Getting started with MLIR is not an easy task. Thus, we attempt to also provide a soft introduction to the fundamental concepts of MLIR. **Installation** @@ -8,6 +10,8 @@ If you haven't already, make sure to visit the [installation](https://mqt.readth ## Understanding Quantum IR +Before we can optimize quantum IR, we first need to understand how to write it. Hence, this section describes how to create quantum programs in MLIR using the MQT. + ### Dynamic And Static Allocation The fundamental computational unit in quantum computing is the _qubit_. Consequently, at some point a quantum computation needs to allocate (and subsequently deallocate) qubits. @@ -38,7 +42,7 @@ module { } ``` -### (Interlude: MLIR Concepts) +### Interlude: MLIR Concepts ```{note} If you are already familiar with the fundamental concepts of MLIR, you may skip this section. @@ -133,12 +137,12 @@ For example, running `mqt-cc` on the first code snippet of this tutorial yields $ mqt-cc dynamic-allocation.mlir module { func.func @main() { - return + func.return } } ``` -What happened? Because there are no unitary operations between the allocation and deallocation of the qubit, the `RemoveAllocDeallocPair` canonicalization pattern matches and removes the unused qubit from the program. This one example of the many optimizations implemented in the quantum compiler driver. +What happened? Because there are no unitary operations between the allocation and deallocation of the qubit, the `RemoveAllocDeallocPair` canonicalization pattern matches and removes the unused qubit from the program. This one example of the many optimizations implemented in the quantum compiler driver. ### Internal Interface @@ -188,8 +192,6 @@ To print the quantum IR after each step in the compilation pipeline, you can sup $ mqt-cc --record-intermediates