Commit 4d71c67f authored by Nguyen, Thien Minh's avatar Nguyen, Thien Minh
Browse files

Merge branch 'master' into tnguyen/pnnl-tutorials

parents cb55e78f f198798c
Loading
Loading
Loading
Loading
+67 −31
Original line number Diff line number Diff line
@@ -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 {";
    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);
+43 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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);
}
+2 −1
Original line number Diff line number Diff line
@@ -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,
+429 −1
Original line number Diff line number Diff line
@@ -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)
  // 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();
+31 −2
Original line number Diff line number Diff line
@@ -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