Commit 1fe7e526 authored by Nguyen, Thien Minh's avatar Nguyen, Thien Minh
Browse files

Squashing all work to implement region-based OpenQASM3 modifier scope block



Descriptions: modifier (inv, ctrl, pow) Ops in the dialect has an associated (single-block) region. These ops have proper data flow tracking, i.e., subsequence quantum ops will take output of these modifier ops, not the wrapped instructions. The connection b/w the region-wrapped code and the op returns are handled by the quantum yield op (terminator for these modifier ops).

Lowering/optimization:

- If no optimization is required, these ops will be lowered to runtime (start/end) functions, identical to the current behavior. I need to separate part of this lowering (involves inlining the wrapped region into the parent region) to a individual pass, executed before the quantum-to-llvm dialect conversion b/c of some weird interactions with other dialect conversion patterns (e.g., qvs conversion) and type conversion. In some sense, when inlining the body, we are creating (cloning/copying) new MLIR Ops in the quantum dialect, so it needs to be executed before the whole quantum-to-llvm pass.

- If optimization is turned on: when possible (e.g., the wrapped code only contains qvs ops), it will resolve the modifier at compile time: reverse gates for inv, derive the ctr-gates, rewrite pow->for loops.

Modifiers that cannot be optimized will remain and  be handled by the lowering step above.

Added tests. There is an edge case that this doesn't cover is compute-action. I retain the existing behavior by directly calling runtime functions. Hence, the SSA tracking in this use case is not reliable for optimization.

Signed-off-by: Nguyen, Thien Minh's avatarThien Nguyen <nguyentm@ornl.gov>
parent 37ebd8dd
......@@ -3,6 +3,8 @@
include "QuantumDialect.td"
include "mlir/Interfaces/SideEffectInterfaces.td"
include "mlir/Interfaces/ControlFlowInterfaces.td"
include "mlir/Interfaces/LoopLikeInterface.td"
def QubitType : OpaqueType<"quantum", "Qubit", "opaque qubit type">;
def ResultType : OpaqueType<"quantum", "Result", "opaque result type">;
......@@ -76,55 +78,89 @@ def ArrayConcatOp : QuantumOp<"qarray_concat", []> {
}];
}
def StartCtrlURegion : QuantumOp<"start_ctrl_u_region", []> {
let arguments = (ins);
let results = (outs);
// A SSA terminator to end modifier (pow, ctrl, inv) block.
def ModifierEndOp : QuantumOp<"modifier_end", [
Terminator, NoSideEffect,
ParentOneOf<["PowURegion, AdjURegion, CtrlURegion"]>
]> {
let summary = "Implicit terminator of a modifier (ctrl/inv/pow) region";
let description = [{
`quantum.modifier_end` operations terminate modifier regions.}];
let arguments = (ins Variadic<QubitType>:$qubits);
// Printer to denote the qubit SSA values to be returned by the modifier
// region.
let printer = [{
p << "q.ctrl_region {";
auto op = *this;
p << "q.yield " << op.getOperands();
}];
}
def EndCtrlURegion : QuantumOp<"end_ctrl_u_region", []> {
let arguments = (ins QubitType:$ctrl_qubit);
let results = (outs);
let printer = [{ auto op = *this;
p << "} (ctrl_bit = " << op.ctrl_qubit() << ") // END CTRL";
}];
}
def PowURegion : QuantumOp<"pow_u_region", [
NoRegionArguments, SingleBlockImplicitTerminator<"quantum::ModifierEndOp">
]> {
let summary = "Scoped Power-U Region";
let description = [{
`pow_u_region` operation represents a block of code whose quantum gates are
repeated a set number of times.}];
// Rationale: we wrap modifier block as a proper value-semantics op.
// i.e., forwarding SSA vars at input and output.
// Note: we use pow of Index type to be compatible with loop bound types.
// i.e., if optimization enabled, any constant `pow` values can be propagated
// to SCF/Affine loops w/o the need for casting (prevent loop unrolling)
let arguments = (ins Index:$pow, Variadic<QubitType>:$qubits);
let results = (outs Variadic<QubitType>:$result);
let regions = (region SizedRegion<1>:$body);
let skipDefaultBuilders = 1;
let builders = [
OpBuilderDAG<(ins "Value":$pow, "ValueRange":$qubits)>
];
def StartAdjointURegion : QuantumOp<"start_adj_u_region", []> {
let arguments = (ins);
let results = (outs);
let printer = [{
p << "q.adj_region {";
let printer = [{
auto op = *this;
p << "q.pow(" << op.pow() << ")";
p.printRegion(op.body(), /*printEntryBlockArgs=*/false);
}];
}
def EndAdjointURegion : QuantumOp<"end_adj_u_region", []> {
let arguments = (ins);
let results = (outs);
let printer = [{
p << "} // END ADJOINT";
}];
}
def AdjURegion : QuantumOp<"adj_u_region", []> {
let summary = "Scoped Adjoint-U Region";
let description = [{
`adj_u_region` operation represents a block of code whose Adjoint should be applied.}];
let arguments = (ins Variadic<QubitType>:$qubits);
let results = (outs Variadic<QubitType>:$result);
let regions = (region SizedRegion<1>:$body);
let skipDefaultBuilders = 1;
let builders = [
OpBuilderDAG<(ins "ValueRange":$qubits)>
];
def StartPowURegion : QuantumOp<"start_pow_u_region", []> {
let arguments = (ins);
let results = (outs);
let printer = [{
p << "q.pow_u_region {";
auto op = *this;
p << "q.adj";
p.printRegion(op.body(), /*printEntryBlockArgs=*/false);
}];
}
def EndPowURegion : QuantumOp<"end_pow_u_region", []> {
let arguments = (ins AnyI64:$pow);
let results = (outs);
def CtrlURegion : QuantumOp<"ctrl_u_region", []> {
let summary = "Scoped Controlled-U Region";
let description = [{
`ctrl_u_region` operation represents a block of code whose controlled version should be applied.}];
let arguments = (ins QubitType:$ctrl_qubit, Variadic<QubitType>:$qubits);
let results = (outs Variadic<QubitType>:$result);
let regions = (region SizedRegion<1>:$body);
let skipDefaultBuilders = 1;
let builders = [
OpBuilderDAG<(ins "Value":$ctrl_qubit, "ValueRange":$qubits)>
];
let printer = [{
auto op = *this;
p << "} (pow = " << op.pow() << ") // END POWER";
p << "q.ctrl(" << op.ctrl_qubit() << ")";
p.printRegion(op.body(), /*printEntryBlockArgs=*/false);
}];
}
def ComputeMarkerOp : QuantumOp<"compute_marker", []> {
let arguments = (ins);
let results = (outs);
......
......@@ -12,6 +12,7 @@
#include "Quantum/QuantumOps.h"
#include "mlir/IR/OpImplementation.h"
#include "mlir/Transforms/InliningUtils.h"
#include "mlir/IR/Builders.h"
using namespace mlir;
using namespace mlir::quantum;
......@@ -68,4 +69,45 @@ void QuantumDialect::initialize() {
// }
// printer << ")";
// }
\ No newline at end of file
// }
void PowURegion::build(OpBuilder &builder, OperationState &result, Value pow, ValueRange qubits) {
assert(pow.getType().isIntOrIndex());
result.addOperands(pow);
result.addOperands(qubits);
std::vector<Type> resultTypes;
for (const auto &qubitOperand : qubits) {
resultTypes.emplace_back(qubitOperand.getType());
}
result.addTypes(resultTypes);
OpBuilder::InsertionGuard guard(builder);
Region *body = result.addRegion();
builder.createBlock(body);
}
void AdjURegion::build(OpBuilder &builder, OperationState &result, ValueRange qubits) {
result.addOperands(qubits);
std::vector<Type> resultTypes;
for (const auto &qubitOperand : qubits) {
resultTypes.emplace_back(qubitOperand.getType());
}
result.addTypes(resultTypes);
OpBuilder::InsertionGuard guard(builder);
Region *body = result.addRegion();
builder.createBlock(body);
}
void CtrlURegion::build(OpBuilder &builder, OperationState &result, Value ctrl_bit, ValueRange qubits) {
assert(ctrl_bit.getType().isa<mlir::OpaqueType>());
result.addOperands(ctrl_bit);
result.addOperands(qubits);
std::vector<Type> resultTypes;
resultTypes.emplace_back(ctrl_bit.getType());
for (const auto &qubitOperand : qubits) {
resultTypes.emplace_back(qubitOperand.getType());
}
result.addTypes(resultTypes);
OpBuilder::InsertionGuard guard(builder);
Region *body = result.addRegion();
builder.createBlock(body);
}
......@@ -186,7 +186,8 @@ class qasm3_visitor : public qasm3::qasm3BaseVisitor {
region_early_return_vars;
// This method will add correct number of InstOps
// based on quantum gate broadcasting
void createInstOps_HandleBroadcast(std::string name,
// Returns the all the updated qubit values (as updated in the symbol table)
std::vector<mlir::Value> createInstOps_HandleBroadcast(std::string name,
std::vector<mlir::Value> qbit_values,
std::vector<std::string> qbit_names,
std::vector<std::string> symbol_table_qbit_keys,
......
......@@ -10,7 +10,20 @@
*******************************************************************************/
#include "gtest/gtest.h"
#include "qcor_mlir_api.hpp"
namespace {
// returns count of non-overlapping occurrences of 'sub' in 'str'
int countSubstring(const std::string &str, const std::string &sub) {
if (sub.length() == 0)
return 0;
int count = 0;
for (size_t offset = str.find(sub); offset != std::string::npos;
offset = str.find(sub, offset + sub.length())) {
++count;
}
return count;
}
} // namespace
// Kitchen-sink *functional* testing of modifiers (pow/inv/ctrl)
TEST(qasm3VisitorTester, checkPow) {
const std::string check_pow = R"#(OPENQASM 3;
include "qelib1.inc";
......@@ -89,6 +102,421 @@ print(count);
EXPECT_FALSE(qcor::execute(check_pow, "check_pow"));
}
// Test codegen and optimization for modifiers
TEST(qasm3VisitorTester, checkPowOpt) {
{
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q, qq;
// These are identity ops
pow(2) @ x q;
pow(2) @ h q;
// SS = Z
pow(4) @ s q;
// TTTT = Z
pow(8) @ t q;
// Check gate-def (should be inlined)
gate test r, s {
x r;
h s;
}
pow(2) @ test q, qq;
)#";
auto llvm = qcor::mlir_compile(src, "pow_test", qcor::OutputType::LLVMIR, true);
std::cout << "LLVM:\n" << llvm << "\n";
// Get the main kernel
llvm = llvm.substr(llvm.find("@__internal_mlir_pow_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
// All instructions are cancelled thanks to inlining/unrolling/gate merge...
EXPECT_TRUE(llvm.find("__quantum__qis") == std::string::npos);
}
{
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q, qq;
pow(3) @ x q;
pow(3) @ cx q, qq;
)#";
auto llvm = qcor::mlir_compile(src, "pow_test", qcor::OutputType::LLVMIR, true);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_pow_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__x"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cnot"), 1);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_pow_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_pow_u_region"), 0);
}
{
// Test code-gen no optimization.
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q, qq;
pow(3) @ x q;
pow(3) @ cx q, qq;
)#";
auto llvm =
qcor::mlir_compile(src, "pow_test", qcor::OutputType::LLVMIR, false, /* opt-level */0);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_pow_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__x"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cnot"), 1);
// Runtime functions are used:
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_pow_u_region"), 2);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_pow_u_region"), 2);
}
}
TEST(qasm3VisitorTester, checkInvOpt) {
{
// Note: we tested each gate on different qubits to validate the adjoint
// mapping (prevent gate merging to operate...)
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[6];
// Self adjoint
inv @ x q[0];
inv @ y q[1];
inv @ z q[2];
inv @ h q[3];
inv @ cx q[4], q[5];
)#";
auto llvm = qcor::mlir_compile(src, "inv_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_inv_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__x"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__y"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__z"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__h"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cnot"), 1);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_adj_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_adj_u_region"), 0);
}
{
// Gate-Adjoint pair
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[2];
inv @ t q[0];
inv @ s q[1];
)#";
auto llvm =
qcor::mlir_compile(src, "inv_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_inv_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__tdg"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__sdg"), 1);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_adj_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_adj_u_region"), 0);
}
{
// Gate-Adjoint pair
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[2];
inv @ tdg q[0];
inv @ sdg q[1];
)#";
auto llvm =
qcor::mlir_compile(src, "inv_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_inv_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__t"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__s"), 1);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_adj_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_adj_u_region"), 0);
}
{
// Parametric gates
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[5];
inv @ rx(1.0) q[0];
inv @ ry(-1.0) q[1];
inv @ rz(1.0) q[2];
inv @ cphase(-1.0) q[3], q[4];
)#";
auto llvm =
qcor::mlir_compile(src, "inv_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_inv_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
// Check the angle values change signs
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__rx(double -1.0"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__ry(double 1.0"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__rz(double -1.0"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cphase(double 1.0"), 1);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_adj_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_adj_u_region"), 0);
}
}
TEST(qasm3VisitorTester, checkCtrlOpt) {
{
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[2];
ctrl @ x q[0], q[1];
)#";
auto llvm =
qcor::mlir_compile(src, "ctrl_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_ctrl_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cnot"), 1);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_ctrl_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_ctrl_u_region"), 0);
}
{
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[2];
ctrl @ z q[0], q[1];
)#";
auto llvm =
qcor::mlir_compile(src, "ctrl_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_ctrl_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
// CZ = H - CX - H
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cnot"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__h"), 2);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_ctrl_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_ctrl_u_region"), 0);
}
{
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[2];
ctrl @ t q[0], q[1];
)#";
auto llvm =
qcor::mlir_compile(src, "ctrl_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_ctrl_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
// Ctrl-T => CU1 => CPHASE
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cphase"), 1);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_ctrl_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_ctrl_u_region"), 0);
}
{
// No optimization, using runtime
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[2];
ctrl @ t q[0], q[1];
)#";
auto llvm = qcor::mlir_compile(src, "ctrl_test", qcor::OutputType::LLVMIR,
false, 0);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_ctrl_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
// T remain, wrapped in runtime functions
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__t"), 1);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_ctrl_u_region"), 1);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_ctrl_u_region"), 1);
}
{
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[3];
ctrl @ cx q[0], q[1], q[2];
)#";
auto llvm =
qcor::mlir_compile(src, "ctrl_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_ctrl_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
// CCX => 15 gates
EXPECT_EQ(countSubstring(llvm, "__quantum__qis"), 15);
// 6 CNOT's, 2 H, 4 T, 3 Tdg
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cnot("), 6);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__h("), 2);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__t("), 4);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__tdg("), 3);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_ctrl_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_ctrl_u_region"), 0);
}
// Check inv (adjoint) of controlled
{
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[3];
inv @ ctrl @ cx q[0], q[1], q[2];
)#";
auto llvm =
qcor::mlir_compile(src, "ctrl_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_ctrl_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
// CCX => 15 gates (same number when inverse)
EXPECT_EQ(countSubstring(llvm, "__quantum__qis"), 15);
// 6 CNOT's, 2 H, 4 T, 3 Tdg => 6 CNOT's, 2 H, 4 Tdg, 3 T
// Note: T is mapped to Tdg and vice verssa
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cnot("), 6);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__h("), 2);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__t("), 3);
EXPECT_EQ(countSubstring(llvm, "__quantum__qis__tdg("), 4);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_ctrl_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_ctrl_u_region"), 0);
}
// Check fully expanded and gate cancellation
{
const std::string src = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[3];
// Controlled then adjoint of controlled
ctrl @ cx q[0], q[1], q[2];
inv @ ctrl @ cx q[0], q[1], q[2];
)#";
auto llvm =
qcor::mlir_compile(src, "ctrl_test", qcor::OutputType::LLVMIR, false);
std::cout << "LLVM:\n" << llvm << "\n";
llvm = llvm.substr(llvm.find("@__internal_mlir_ctrl_test"));
const auto last = llvm.find_first_of("}");
llvm = llvm.substr(0, last + 1);
std::cout << "LLVM:\n" << llvm << "\n";
// No gate left
EXPECT_EQ(countSubstring(llvm, "__quantum__qis"), 0);
// No runtime involved
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__start_ctrl_u_region"), 0);
EXPECT_EQ(countSubstring(llvm, "__quantum__rt__end_ctrl_u_region"), 0);
}
}
// Test CCX truth table
TEST(qasm3VisitorTester, checkCtrlCx) {
// This test iterates over the all 3-bit values => check CCX
const std::string check_ccx = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[3];
bit ans[3];
bit expected[3];
for val in [0:8] {
int[64] test = val;
print("val =", test);
for i in [0:3] {
if (bool(test[i])) {
x q[i];
}
}
measure q [0:2] -> expected [0:2];
ctrl @ cx q[0], q[1], q[2];
measure q [0:2]->ans [0:2];
print("answer =", ans[0], ans[1], ans[2]);
print("expected =", expected[0], expected[1], expected[2]);
QCOR_EXPECT_TRUE(ans[0] == expected[0]);
QCOR_EXPECT_TRUE(ans[1] == expected[1]);
if (test == 7) {
QCOR_EXPECT_TRUE(ans[2] == 0);
}
if (test == 3) {
QCOR_EXPECT_TRUE(ans[2] == 1);
}
if (ans[0]) {
x q[0];
}
if (ans[1]) {
x q[1];
}
if (ans[2]) {
x q[2];
}
}
)#";
auto llvm =
qcor::mlir_compile(check_ccx, "ccx", qcor::OutputType::LLVMIR, true);
// Runt the test
// TODO: debug the JIT engine, failing to run this (okay with qcor driver)