Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dialects: Add qssa dialect #2746

Merged
merged 8 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
28 changes: 28 additions & 0 deletions tests/filecheck/dialects/qssa/ops.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// RUN: XDSL_ROUNDTRIP
// RUN: XDSL_GENERIC_ROUNDTRIP

%q0, %q1 = qssa.alloc<2>

// CHECK: %q0, %q1 = qssa.alloc<2>

%0 = qssa.h %q0

// CHECK-NEXT: %0 = qssa.h %q0

%q2:2 = qssa.cz %q1 %0
alexarice marked this conversation as resolved.
Show resolved Hide resolved

// CHECK-NEXT: %q2, %q2_1 = qssa.cz %q1 %0

%q3, %q4 = qssa.cnot %q2#0 %q2#1
AntonLydike marked this conversation as resolved.
Show resolved Hide resolved

// CHECK-NEXT: %q3, %q4 = qssa.cnot %q2 %q2_1

%1 = qssa.measure %q3

// CHECK-NEXT: %1 = qssa.measure %q3

// CHECK-GENERIC: %q0, %q1 = "qssa.alloc"() <{"qubits" = 2 : i64}> : () -> (!qssa.qubit, !qssa.qubit)
// CHECK-GENERIC-NEXT: %0 = "qssa.h"(%q0) : (!qssa.qubit) -> !qssa.qubit
// CHECK-GENERIC-NEXT: %q2, %q2_1 = "qssa.cz"(%q1, %0) : (!qssa.qubit, !qssa.qubit) -> (!qssa.qubit, !qssa.qubit)
// CHECK-GENERIC-NEXT: %q3, %q4 = "qssa.cnot"(%q2, %q2_1) : (!qssa.qubit, !qssa.qubit) -> (!qssa.qubit, !qssa.qubit)
// CHECK-GENERIC-NEXT: %1 = "qssa.measure"(%q3) : (!qssa.qubit) -> i1
144 changes: 144 additions & 0 deletions xdsl/dialects/qssa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from abc import ABC

from xdsl.dialects.builtin import IntegerAttr, IntegerType
from xdsl.ir import Dialect, ParametrizedAttribute, SSAValue, TypeAttribute
from xdsl.irdl import (
IRDLOperation,
VarOpResult,
irdl_attr_definition,
irdl_op_definition,
operand_def,
prop_def,
result_def,
var_result_def,
)
from xdsl.parser import Parser
from xdsl.printer import Printer


@irdl_attr_definition
class QubitAttr(ParametrizedAttribute, TypeAttribute):
"""
Type for a single qubit
"""

name = "qssa.qubit"


qubit = QubitAttr()


class QubitBase(IRDLOperation, ABC):
pass


@irdl_op_definition
class QubitAllocOp(QubitBase):
name = "qssa.alloc"

qubits = prop_def(IntegerAttr)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

num, count, num_qubits or similar might be better for inferring the type from a point of readability, since the definition qubit = QubitAttr() would suggest that qubits : Sequence[QubitAttr]

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, looking at this again, I don't think we need this attribute at all. The number of qubits is simply the number of results. So the attribute can be dropped entirely

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I'd generally encourage to print some type information in the mlir, though.


res: VarOpResult = var_result_def(qubit)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the semantics, this reads like it allocates a series of single qubits, rather than an array of qubits as the op is described in the paper. You could consider parameterising QubitAttr similar to IntegerAttr, or maybe @AntonLydike could advise?

Copy link
Collaborator

@AntonLydike AntonLydike Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I was saying is that these two ops are both completely valid, just one has redundant information:

@irdl_op_definition
class QubitAllocOp(QubitBase):
    name = "qssa.alloc"

    qubits = prop_def(IntegerAttr)

    res: VarOpResult = var_result_def(qubit)

and

@irdl_op_definition
class QubitAllocOp(QubitBase):
    name = "qssa.alloc"

    res: VarOpResult = var_result_def(qubit)

Simply because we know that qbits == len(res). Therefore we don't need to carry this additional information at all. The resulting IR would look like this:

%qbit1, %qbit2 = qssa.alloc
%qbit3 = qssa.alloc

So you just specify how many qbits you allocate by the number of results.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference: 3.3.1 Allocation. qssa.alloc allocates an array of qubits of fixed or variable length. %q = qssa.alloc : qubit<10>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @alexarice does not talk about qbit arrays, but models each qbit as a single SSA value. But that's for him to answer 😄

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I seem to remember that semantically, an array of qubits may be understood to mean something different as compared to n single qubits, but I may be mistaken here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not directly based on the qssa dialect from the paper and is currently representing each individual qubit as a separate ssa value. I'd be open to renaming this dialect if this will cause confusion in the future

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of the discussion didn't load for me on github, but I believe dropping the parameter is not ideal as the parser does not have access to the number of results (at least from my testing)


def __init__(self, qubits: int):
super().__init__(
operands=(),
result_types=([qubit for _ in range(0, qubits)],),
properties={"qubits": IntegerAttr(qubits, 64)},
)

@classmethod
def parse(cls, parser: Parser) -> "QubitAllocOp":
with parser.in_angle_brackets():
qubits = parser.parse_integer()
attr_dict = parser.parse_optional_attr_dict()
return QubitAllocOp.create(
operands=(),
result_types=tuple(qubit for _ in range(0, qubits)),
properties={"qubits": IntegerAttr(qubits, 64)},
attributes=attr_dict or {},
)

def print(self, printer: Printer):
with printer.in_angle_brackets():
printer.print(self.qubits.value.data)
printer.print_op_attributes(self.attributes)


@irdl_op_definition
class HGateOp(QubitBase):
name = "qssa.h"

input = operand_def(qubit)

output = result_def(qubit)

assembly_format = "$input attr-dict"

def __init__(self, input: SSAValue):
super().__init__(operands=(input,), result_types=(qubit,))


@irdl_op_definition
class CNotGateOp(QubitBase):
name = "qssa.cnot"

in1 = operand_def(qubit)

in2 = operand_def(qubit)

out1 = result_def(qubit)

out2 = result_def(qubit)

assembly_format = "$in1 $in2 attr-dict"

def __init__(self, in1: SSAValue, in2: SSAValue):
super().__init__(operands=(in1, in2), result_types=(qubit, qubit))


@irdl_op_definition
class CZGateOp(QubitBase):
name = "qssa.cz"

in1 = operand_def(qubit)

in2 = operand_def(qubit)

out1 = result_def(qubit)

out2 = result_def(qubit)

assembly_format = "$in1 $in2 attr-dict"

def __init__(self, in1: SSAValue, in2: SSAValue):
super().__init__(operands=(in1, in2), result_types=(qubit, qubit))


@irdl_op_definition
class MeasureOp(QubitBase):
name = "qssa.measure"

input = operand_def(qubit)

output = result_def(IntegerType(1))

assembly_format = "$input attr-dict"

def __init__(self, input: SSAValue):
super().__init__(operands=(input,), result_types=(IntegerType(1),))


QSSA = Dialect(
"qssa",
[
QubitAllocOp,
HGateOp,
CZGateOp,
CNotGateOp,
MeasureOp,
],
[
QubitAttr,
],
)
6 changes: 6 additions & 0 deletions xdsl/tools/command_line_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ def get_printf():

return Printf

def get_qssa():
from xdsl.dialects.qssa import QSSA

return QSSA

def get_riscv_debug():
from xdsl.dialects.riscv_debug import RISCV_Debug

Expand Down Expand Up @@ -298,6 +303,7 @@ def get_x86():
"onnx": get_onnx,
"pdl": get_pdl,
"printf": get_printf,
"qssa": get_qssa,
"riscv": get_riscv,
"riscv_debug": get_riscv_debug,
"riscv_func": get_riscv_func,
Expand Down
Loading