Loading mlir/dialect/include/Quantum/QuantumOps.td +67 −31 Original line number Diff line number Diff line Loading @@ -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">; Loading Loading @@ -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 {"; 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); Loading mlir/dialect/lib/Quantum/QuantumDialect.cpp +43 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -69,3 +70,44 @@ void QuantumDialect::initialize() { // printer << ")"; // } 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); } mlir/parsers/qasm3/qasm3_visitor.hpp +2 −1 Original line number Diff line number Diff line Loading @@ -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, Loading mlir/parsers/qasm3/tests/test_modifiers.cpp +429 −1 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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) // EXPECT_FALSE(qcor::execute(check_ccx, "ccx")); } TEST(qasm3VisitorTester, checkNestedModifier) { const std::string check_nested = R"#(OPENQASM 3; qubit q; qubit qq; inv @ pow(2) @ t q; pow(2) @ inv @ t q; ctrl @ pow(2) @ t q, qq; pow(2) @ ctrl @ t q, qq; )#"; auto llvm = qcor::mlir_compile(check_nested, "nested", qcor::OutputType::LLVMIR, false, 0); std::cout << "LLVM:\n" << llvm << "\n"; } // Modifier block in a kernel definition: // Note: we chase use-def for gate def: TEST(qasm3VisitorTester, checkModifierInKernelDef) { const std::string check_nested = R"#(OPENQASM 3; gate test1 q, qq { h q; ctrl @ x q, qq; x qq; } gate test2 q, qq { h q; ctrl @ pow(4) @ x q, qq; x qq; } gate test3 q, qq { h q; inv @ ctrl @ pow(4) @ t q, qq; x qq; } )#"; auto llvm = qcor::mlir_compile(check_nested, "nested", qcor::OutputType::LLVMIR, false, 0); std::cout << "LLVM:\n" << llvm << "\n"; } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); Loading mlir/parsers/qasm3/visitor_handlers/compute_action_handler.cpp +31 −2 Original line number Diff line number Diff line Loading @@ -22,9 +22,38 @@ antlrcpp::Any qasm3_visitor::visitCompute_action_stmt( visit(context->action_block); builder.create<mlir::quantum::ComputeMarkerOp>(location); builder.create<mlir::quantum::StartAdjointURegion>(location); const auto createFuncCall = [](const std::string &func_name, mlir::OpBuilder &opBuilder, mlir::ModuleOp &parentModule) { mlir::FlatSymbolRefAttr funcRef = [&]() { mlir::OpBuilder::InsertionGuard guard(opBuilder); opBuilder.setInsertionPointToStart( &parentModule.getRegion().getBlocks().front()); if (parentModule.lookupSymbol<mlir::FuncOp>(func_name)) { auto fnNameAttr = opBuilder.getSymbolRefAttr(func_name); return fnNameAttr; } auto func_decl = opBuilder.create<mlir::FuncOp>( opBuilder.getUnknownLoc(), func_name, opBuilder.getFunctionType(llvm::None, llvm::None)); func_decl.setVisibility(mlir::SymbolTable::Visibility::Private); return mlir::SymbolRefAttr::get(func_name, parentModule->getContext()); }(); opBuilder.create<mlir::CallOp>(opBuilder.getUnknownLoc(), funcRef, llvm::None, llvm::None); }; // Direct injection of Adj start/end functions. // !!FIXME!!: This compute_block is very generic, it'd be hard to infer all the qubit operands. // hence, we cannot enclose it in a modifier-scoped block yet. // SSA tracking (for optimization) for compute/action will not be guaranteed. createFuncCall("__quantum__rt__start_adj_u_region", builder, m_module); visit(context->compute_block); builder.create<mlir::quantum::EndAdjointURegion>(location); createFuncCall("__quantum__rt__end_adj_u_region", builder, m_module); builder.create<mlir::quantum::ComputeUnMarkerOp>(location); return 0; Loading Loading
mlir/dialect/include/Quantum/QuantumOps.td +67 −31 Original line number Diff line number Diff line Loading @@ -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">; Loading Loading @@ -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 {"; 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); Loading
mlir/dialect/lib/Quantum/QuantumDialect.cpp +43 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -69,3 +70,44 @@ void QuantumDialect::initialize() { // printer << ")"; // } 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); }
mlir/parsers/qasm3/qasm3_visitor.hpp +2 −1 Original line number Diff line number Diff line Loading @@ -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, Loading
mlir/parsers/qasm3/tests/test_modifiers.cpp +429 −1 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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) // EXPECT_FALSE(qcor::execute(check_ccx, "ccx")); } TEST(qasm3VisitorTester, checkNestedModifier) { const std::string check_nested = R"#(OPENQASM 3; qubit q; qubit qq; inv @ pow(2) @ t q; pow(2) @ inv @ t q; ctrl @ pow(2) @ t q, qq; pow(2) @ ctrl @ t q, qq; )#"; auto llvm = qcor::mlir_compile(check_nested, "nested", qcor::OutputType::LLVMIR, false, 0); std::cout << "LLVM:\n" << llvm << "\n"; } // Modifier block in a kernel definition: // Note: we chase use-def for gate def: TEST(qasm3VisitorTester, checkModifierInKernelDef) { const std::string check_nested = R"#(OPENQASM 3; gate test1 q, qq { h q; ctrl @ x q, qq; x qq; } gate test2 q, qq { h q; ctrl @ pow(4) @ x q, qq; x qq; } gate test3 q, qq { h q; inv @ ctrl @ pow(4) @ t q, qq; x qq; } )#"; auto llvm = qcor::mlir_compile(check_nested, "nested", qcor::OutputType::LLVMIR, false, 0); std::cout << "LLVM:\n" << llvm << "\n"; } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); Loading
mlir/parsers/qasm3/visitor_handlers/compute_action_handler.cpp +31 −2 Original line number Diff line number Diff line Loading @@ -22,9 +22,38 @@ antlrcpp::Any qasm3_visitor::visitCompute_action_stmt( visit(context->action_block); builder.create<mlir::quantum::ComputeMarkerOp>(location); builder.create<mlir::quantum::StartAdjointURegion>(location); const auto createFuncCall = [](const std::string &func_name, mlir::OpBuilder &opBuilder, mlir::ModuleOp &parentModule) { mlir::FlatSymbolRefAttr funcRef = [&]() { mlir::OpBuilder::InsertionGuard guard(opBuilder); opBuilder.setInsertionPointToStart( &parentModule.getRegion().getBlocks().front()); if (parentModule.lookupSymbol<mlir::FuncOp>(func_name)) { auto fnNameAttr = opBuilder.getSymbolRefAttr(func_name); return fnNameAttr; } auto func_decl = opBuilder.create<mlir::FuncOp>( opBuilder.getUnknownLoc(), func_name, opBuilder.getFunctionType(llvm::None, llvm::None)); func_decl.setVisibility(mlir::SymbolTable::Visibility::Private); return mlir::SymbolRefAttr::get(func_name, parentModule->getContext()); }(); opBuilder.create<mlir::CallOp>(opBuilder.getUnknownLoc(), funcRef, llvm::None, llvm::None); }; // Direct injection of Adj start/end functions. // !!FIXME!!: This compute_block is very generic, it'd be hard to infer all the qubit operands. // hence, we cannot enclose it in a modifier-scoped block yet. // SSA tracking (for optimization) for compute/action will not be guaranteed. createFuncCall("__quantum__rt__start_adj_u_region", builder, m_module); visit(context->compute_block); builder.create<mlir::quantum::EndAdjointURegion>(location); createFuncCall("__quantum__rt__end_adj_u_region", builder, m_module); builder.create<mlir::quantum::ComputeUnMarkerOp>(location); return 0; Loading