Unverified Commit 731de70f authored by Mccaskey, Alex's avatar Mccaskey, Alex Committed by GitHub
Browse files

Merge pull request #102 from tnguyen-ornl/tnguyen/mlir-alias

parents e0f4e9e4 ccddee37
Loading
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -17,11 +17,36 @@ def QallocOp : QuantumOp<"qalloc", []> {
    let results = (outs ArrayType:$qubits);
}

// Create an array holding Qubit pointers for aliasing purposes,
// i.e. not allocating new qubits.
def QaliasArrayAllocOp : QuantumOp<"createQubitArray", []> {
    let arguments = (ins AnyI64Attr:$size, StrAttr:$name);
    let results = (outs ArrayType:$qubits);
}

def ExtractQubitOp : QuantumOp<"qextract", []> {
    let arguments = (ins ArrayType:$qreg, AnyInteger:$idx);
    let results = (outs QubitType:$qbit);
}

// Assign a qubit pointer (specified by the Qubit array and index) to an alias pointer. 
// Signature: void qassign(Array* destination_array, int destination_idx, Array* source_array, int source_idx)
def AssignQubitOp : QuantumOp<"qassign", []> {
    let arguments = (ins ArrayType:$dest_qreg, AnyInteger:$dest_idx, ArrayType:$src_qreg, AnyInteger:$src_idx);
    let results = (outs);
}

// Extract array slice
def ArraySliceOp : QuantumOp<"qarray_slice", []> {
    let arguments = (ins ArrayType:$qreg, Variadic<I64>:$slice_range);
    let results = (outs ArrayType:$array_slice);
}
// Array Concatenation
def ArrayConcatOp : QuantumOp<"qarray_concat", []> {
    let arguments = (ins ArrayType:$qreg1, ArrayType:$qreg2);
    let results = (outs ArrayType:$concat_array);
}

def InstOp : QuantumOp<"inst", [AttrSizedOperandSegments]> {
    let arguments = (ins StrAttr:$name, Variadic<QubitType>:$qubits, Variadic<F64>:$params);
    let results = (outs Optional<ResultType>:$bit);
+5 −0
Original line number Diff line number Diff line
@@ -7,6 +7,11 @@ link_directories(${XACC_ROOT}/lib)
#target_include_directories(qasm3VisitorTester PRIVATE . ../../ ${XACC_ROOT}/include/gtest)
#target_link_libraries(qasm3VisitorTester qcor-mlir-api gtest gtest_main)

add_executable(qasm3VisitorAliasTester test_alias_handler.cpp)
add_test(NAME qcor_qasm3_quantum_alias_decl_tester COMMAND qasm3VisitorAliasTester)
target_include_directories(qasm3VisitorAliasTester PRIVATE . ../../ ${XACC_ROOT}/include/gtest)
target_link_libraries(qasm3VisitorAliasTester qcor-mlir-api gtest gtest_main)

add_executable(qasm3CompilerTester_Assignment test_assignment.cpp)
add_test(NAME qcor_qasm3_classical_assignment_tester COMMAND qasm3CompilerTester_Assignment)
target_include_directories(qasm3CompilerTester_Assignment PRIVATE . ../../ ${XACC_ROOT}/include/gtest)
+209 −0
Original line number Diff line number Diff line
#include "qcor_mlir_api.hpp"
#include "gtest/gtest.h"

TEST(qasm3VisitorTester, checkAlias) {
  const std::string alias_by_indicies = R"#(OPENQASM 3;
include "qelib1.inc";
qubit q[6];
// Test 1: Alias by indices
// myreg[0,1,2] refers to the qubit q[1,3,5]
let myreg = q[1, 3, 5];
// Apply x on qubits in the alias list
// Use broadcast to make sure it work
x myreg;
// Measure all qubits
bit m[6];
m = measure q;

for i in [0:6] {
  print(m[i]);
}
QCOR_EXPECT_TRUE(m[0] == 0);
QCOR_EXPECT_TRUE(m[1] == 1);
QCOR_EXPECT_TRUE(m[2] == 0);
QCOR_EXPECT_TRUE(m[3] == 1);
QCOR_EXPECT_TRUE(m[4] == 0);
QCOR_EXPECT_TRUE(m[5] == 1);
// Reset q to start next test
reset q;
bit m1[6];
m1 = measure q;
QCOR_EXPECT_TRUE(m1[0] == 0);
QCOR_EXPECT_TRUE(m1[1] == 0);
QCOR_EXPECT_TRUE(m1[2] == 0);
QCOR_EXPECT_TRUE(m1[3] == 0);
QCOR_EXPECT_TRUE(m1[4] == 0);
QCOR_EXPECT_TRUE(m1[5] == 0);

// Test 2: Alias by slice:
// 0, 1, 2, 3 (inclusive)
let myreg1 = q[0:3];
x myreg1;
// Measure all qubits
bit m2[6];
m2 = measure q;

for j in [0:6] {
  print(m2[j]);
}
QCOR_EXPECT_TRUE(m2[0] == 1);
QCOR_EXPECT_TRUE(m2[1] == 1);
QCOR_EXPECT_TRUE(m2[2] == 1);
QCOR_EXPECT_TRUE(m2[3] == 1);
QCOR_EXPECT_TRUE(m2[4] == 0);
QCOR_EXPECT_TRUE(m2[5] == 0);

// Reset q to start next test
reset q;

// Range with step size (0, 2, 4)
let myreg2 = q[0:2:5];
x myreg2;
// Measure all qubits
bit m3[6];
m3 = measure q;

for k in [0:6] {
  print(m3[k]);
}
QCOR_EXPECT_TRUE(m3[0] == 1);
QCOR_EXPECT_TRUE(m3[1] == 0);
QCOR_EXPECT_TRUE(m3[2] == 1);
QCOR_EXPECT_TRUE(m3[3] == 0);
QCOR_EXPECT_TRUE(m3[4] == 1);
QCOR_EXPECT_TRUE(m3[5] == 0);

// Reset q to start next test
reset q;
// Range with negative step:
// 4, 3, 2
let myreg3 = q[4:-1:2];
x myreg3;
// Measure all qubits
bit m4[6];
m4 = measure q;

for i1 in [0:6] {
  print(m4[i1]);
}
QCOR_EXPECT_TRUE(m4[0] == 0);
QCOR_EXPECT_TRUE(m4[1] == 0);
QCOR_EXPECT_TRUE(m4[2] == 1);
QCOR_EXPECT_TRUE(m4[3] == 1);
QCOR_EXPECT_TRUE(m4[4] == 1);
QCOR_EXPECT_TRUE(m4[5] == 0);

// Reset q to start next test
reset q;
// Range with start = stop
// This is q[5]
let myreg4 = q[5:5];
x myreg4;
// Measure all qubits
bit m5[6];
m5 = measure q;

for i2 in [0:6] {
  print(m5[i2]);
}
QCOR_EXPECT_TRUE(m5[0] == 0);
QCOR_EXPECT_TRUE(m5[1] == 0);
QCOR_EXPECT_TRUE(m5[2] == 0);
QCOR_EXPECT_TRUE(m5[3] == 0);
QCOR_EXPECT_TRUE(m5[4] == 0);
QCOR_EXPECT_TRUE(m5[5] == 1);

// Reset q to start next test
reset q;
// Range using negative indexing:
// Last 3 qubits
let myreg5 = q[-3:-1];
x myreg5;
// Measure all qubits
bit m6[6];
m6 = measure q;

for i3 in [0:6] {
  print(m6[i3]);
}
QCOR_EXPECT_TRUE(m6[0] == 0);
QCOR_EXPECT_TRUE(m6[1] == 0);
QCOR_EXPECT_TRUE(m6[2] == 0);
QCOR_EXPECT_TRUE(m6[3] == 1);
QCOR_EXPECT_TRUE(m6[4] == 1);
QCOR_EXPECT_TRUE(m6[5] == 1);

// Test concatenate:
// Reset q to start next test
reset q;
let even_set = q[0:2:5];
let odd_set = q[1:2:5];
let both = even_set || odd_set;
x both;
// Measure all qubits
bit m7[6];
m7 = measure q;

for i in [0:6] {
  print(m7[i]);
}
// All ones
QCOR_EXPECT_TRUE(m7[0] == 1);
QCOR_EXPECT_TRUE(m7[1] == 1);
QCOR_EXPECT_TRUE(m7[2] == 1);
QCOR_EXPECT_TRUE(m7[3] == 1);
QCOR_EXPECT_TRUE(m7[4] == 1);
QCOR_EXPECT_TRUE(m7[5] == 1);

// Test concatenate complex
// Reset q to start next test
reset q;
// Inline concat: 0, 3, 1, 5
let concat_inline = q[0:3:5] || q[1:4:5];
x concat_inline;
// Measure all qubits
bit m8[6];
m8 = measure q;

for i in [0:6] {
  print(m8[i]);
}
// 0, 3, 1, 5 ==> 1
QCOR_EXPECT_TRUE(m8[0] == 1);
QCOR_EXPECT_TRUE(m8[1] == 1);
QCOR_EXPECT_TRUE(m8[2] == 0);
QCOR_EXPECT_TRUE(m8[3] == 1);
QCOR_EXPECT_TRUE(m8[4] == 0);
QCOR_EXPECT_TRUE(m8[5] == 1);

// Reset q to start next test
reset q;
// Multi-concat: 0, 1, 2, 4, 5 (no 3)
let concat_multiple = q[0:1:2] || q[4:4] || q[5];
x concat_multiple;
// Measure all qubits
bit m9[6];
m9 = measure q;

for i in [0:6] {
  print(m9[i]);
}
// All ones except 3
QCOR_EXPECT_TRUE(m9[0] == 1);
QCOR_EXPECT_TRUE(m9[1] == 1);
QCOR_EXPECT_TRUE(m9[2] == 1);
QCOR_EXPECT_TRUE(m9[3] == 0);
QCOR_EXPECT_TRUE(m9[4] == 1);
QCOR_EXPECT_TRUE(m9[5] == 1);
)#";
  auto mlir = qcor::mlir_compile("qasm3", alias_by_indicies, "test",
                                 qcor::OutputType::MLIR, true);
  std::cout << "MLIR:\n" << mlir << "\n";
  EXPECT_FALSE(qcor::execute("qasm3", alias_by_indicies, "test"));
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  auto ret = RUN_ALL_TESTS();
  return ret;
}
+227 −47
Original line number Diff line number Diff line
@@ -19,54 +19,234 @@ antlrcpp::Any qasm3_visitor::visitAliasStatement(
  //     | indexIdentifier '||' indexIdentifier
  //     ;

  // Function to process the indexIdentifier block
  // We make this a function to allow re-entrance.
  const std::function<void(const std::string &,
                           decltype(context->indexIdentifier()))>
      processIdentifierDef = [this, &location, &processIdentifierDef](
                                 const std::string &in_aliasName,
                                 decltype(context->indexIdentifier())
                                     in_indexIdentifierContext) {
        // Helper to determine the qreg size
        const auto get_qreg_size = [&](const std::string &qreg_name) {
          uint64_t nqubits;
          auto qreg_value = symbol_table.get_symbol(qreg_name);
          if (auto op = qreg_value.getDefiningOp<mlir::quantum::QallocOp>()) {
            nqubits = op.size().getLimitedValue();
          } else {
            auto attributes = symbol_table.get_variable_attributes(qreg_name);
            if (!attributes.empty()) {
              try {
                nqubits = std::stoi(attributes[0]);
              } catch (...) {
                printErrorMessage(
      "Alias not yet supported yet - Thien, remove this for development.");

  // The name of the new alias register, pointing to previously allocated
  // register
  auto alias = context->Identifier()->getText();

  // Get the name and symbol Value of the original register
  auto allocated_variable = context->indexIdentifier()->Identifier()->getText();
                    "Could not infer qubit[] size from block argument.");
              }
            } else {
              printErrorMessage(
                  "Could not infer qubit[] size from block argument. No size "
                  "attribute for variable in symbol table.");
            }
          }
          return nqubits;
        };
        // The RHS has an Identifier (range- or index- based slicing)
        if (in_indexIdentifierContext->Identifier()) {
          // Get the name and symbol Value of the original register.
          // We need to determine the alias array size and cache it
          // for broadcast to work on the result array.
          auto allocated_variable =
              in_indexIdentifierContext->Identifier()->getText();
          auto allocated_symbol = symbol_table.get_symbol(allocated_variable);

          // handle q[1, 3, 5] comma syntax
  if (context->indexIdentifier()->LBRACKET()) {
          if (in_indexIdentifierContext->LBRACKET()) {
            // get the comma expression, count how many elements there are
            auto expressions =
        context->indexIdentifier()->expressionList()->expression();
                in_indexIdentifierContext->expressionList()->expression();
            auto n_expressions = expressions.size();

    // Allocate a new array of qubits of given size
    auto str_attr = builder.getStringAttr(alias);
            // Create a qubit array of given size
            // which keeps alias references to Qubits in the original input
            // array.
            auto str_attr = builder.getStringAttr(in_aliasName);
            auto integer_attr =
                mlir::IntegerAttr::get(builder.getI64Type(), n_expressions);
    mlir::Value allocation = builder.create<mlir::quantum::QallocOp>(
            mlir::Value alias_allocation =
                builder.create<mlir::quantum::QaliasArrayAllocOp>(
                    location, array_type, integer_attr, str_attr);
            // Add the alias register to the symbol table
            symbol_table.add_symbol(in_aliasName, alias_allocation,
                                    {std::to_string(n_expressions)});

            auto counter = 0;
            for (auto expr : expressions) {
              // GOAL HERE IS TO ASSIGN extracted qubits from original array
              // to the correct element of the alias array
      auto idx =
          symbol_table.evaluate_constant_integer_expression(expr->getText());
              auto idx = symbol_table.evaluate_constant_integer_expression(
                  expr->getText());
              auto dest_idx = get_or_create_constant_integer_value(
                  counter, location, builder.getI64Type(), symbol_table,
                  builder);
              auto src_idx = get_or_create_constant_integer_value(
                  idx, location, builder.getI64Type(), symbol_table, builder);
              ++counter;

              builder.create<mlir::quantum::AssignQubitOp>(
                  location, alias_allocation, dest_idx, allocated_symbol,
                  src_idx);
            }
          } else if (auto range_def =
                         in_indexIdentifierContext->rangeDefinition()) {
            // handle range definition
            const size_t n_expr = range_def->expression().size();
            // Minimum is two expressions and not more than 3
            if (n_expr < 2 || n_expr > 3) {
              printErrorMessage("Invalid array slice range.");
            }

      // get the extracted element from the original register
      auto extracted = builder.create<mlir::quantum::ExtractQubitOp>(
          location, qubit_type, allocated_symbol,
          get_or_create_constant_integer_value(
              idx, location, builder.getI64Type(), symbol_table, builder));
            auto range_start_expr = range_def->expression(0);
            auto range_stop_expr = (n_expr == 2) ? range_def->expression(1)
                                                 : range_def->expression(2);

      // use extracted with a new qassign dialect operation.
            const auto resolve_range_value =
                [&](auto *range_item_expr) -> int64_t {
              const std::string range_item_str = range_item_expr->getText();
              try {
                return std::stoi(range_item_str);
              } catch (std::exception &ex) {
                return symbol_table.evaluate_constant_integer_expression(
                    range_item_str);
              }
            };

  } else if (auto range_def = context->indexIdentifier()->rangeDefinition()) {
    // handle range definition
    // I think we can handle RANGE with a memref<3xi64>...
            const int64_t range_start = resolve_range_value(range_start_expr);
            const int64_t range_stop = resolve_range_value(range_stop_expr);
            const int64_t range_step =
                (n_expr == 2) ? 1
                              : resolve_range_value(range_def->expression(1));

            // Step must not be zero:
            if (range_step == 0) {
              printErrorMessage("Invalid range: step size must be non-zero.");
            }

            // std::cout << "Range: Start = " << range_start << "; Step = " <<
            // range_step << "; Stop = " << range_stop << "\n";
            auto range_start_mlir_val = get_or_create_constant_integer_value(
                range_start, location, builder.getI64Type(), symbol_table,
                builder);
            auto range_step_mlir_val = get_or_create_constant_integer_value(
                range_step, location, builder.getI64Type(), symbol_table,
                builder);
            auto range_stop_mlir_val = get_or_create_constant_integer_value(
                range_stop, location, builder.getI64Type(), symbol_table,
                builder);
            mlir::Value array_slice =
                builder.create<mlir::quantum::ArraySliceOp>(
                    location, array_type, allocated_symbol,
                    llvm::makeArrayRef(std::vector<mlir::Value>{
                        range_start_mlir_val, range_step_mlir_val,
                        range_stop_mlir_val}));

            // Determine and cache the slice size for instruction broadcasting.
            const int64_t orig_size = get_qreg_size(allocated_variable);
            const auto slice_size_calc = [](int64_t orig_size, int64_t start,
                                            int64_t step,
                                            int64_t end) -> int64_t {
              // If step > 0 and lo > hi, or step < 0 and lo < hi, the range is
              // empty. Else for step > 0, if n values are in the range, the
              // last one is lo + (n-1)*step, which must be <= hi.  Rearranging,
              // n <= (hi
              // - lo)/step + 1, so taking the floor of the RHS gives the proper
              // value.
              assert(step != 0);
              // Convert to positive indices (if given as negative)
              const int64_t lo = start >= 0 ? start : orig_size + start;
              const int64_t hi = end >= 0 ? end : orig_size + end;
              if (lo == hi) {
                return 1;
              }
              if (step > 0 && lo < hi) {
                return 1 + (hi - lo) / step;
              } else if (step < 0 && lo > hi) {
                return 1 + (lo - hi) / (-step);
              } else {
                return 0;
              }
            };
            const auto new_size =
                slice_size_calc(orig_size, range_start, range_step, range_stop);
            symbol_table.add_symbol(in_aliasName, array_slice,
                                    {std::to_string(new_size)});
          } else {
            printErrorMessage("Could not parse the alias statement.",
                              in_indexIdentifierContext);
          }
        } else if (in_indexIdentifierContext->indexIdentifier().size() == 2) {
          // handle concatenation
          // the RHS (is an indexIdentifier) is indexIdentifier ||
          // indexIdentifier
          auto firstIdentifier = in_indexIdentifierContext->indexIdentifier(0);
          auto secondIdentifier = in_indexIdentifierContext->indexIdentifier(1);
          const auto isPureIdentifier = [](auto *indexIdentifier) {
            // This is a simple name identifier
            return !indexIdentifier->LBRACKET() &&
                   !indexIdentifier->rangeDefinition() &&
                   indexIdentifier->indexIdentifier().empty();
          };

          // STRATEGY:
          // If the indexIdentifier is *pure* (just a var name),
          // the concatenate the var directly.
          // Otherwise, create a local identifier for the nested block (could be
          // both sides) the traverse down recursively.
          const auto process_block_and_gen_var_name =
              [&](decltype(
                  firstIdentifier) in_termIdentifierNode) -> std::string {
            static int64_t temp_var_counter = 0;
            if (isPureIdentifier(in_termIdentifierNode)) {
              return in_termIdentifierNode->Identifier()->getText();
            }
            // This is a complex one:
            const std::string new_var_name =
                "__internal_concat_temp_" + std::to_string(temp_var_counter++);
            // std::cout << "Process " << new_var_name << " = "
            //           << in_termIdentifierNode->getText() << "\n";
            processIdentifierDef(new_var_name, in_termIdentifierNode);
            return new_var_name;
          };

          // Process both blocks:
          const std::string lhs_temp_var =
              process_block_and_gen_var_name(firstIdentifier);
          const std::string rhs_temp_var =
              process_block_and_gen_var_name(secondIdentifier);
          auto first_reg_symbol = symbol_table.get_symbol(lhs_temp_var);
          auto second_reg_symbol = symbol_table.get_symbol(rhs_temp_var);
          const auto first_reg_size = get_qreg_size(lhs_temp_var);
          const auto second_reg_size = get_qreg_size(rhs_temp_var);
          mlir::Value array_concat =
              builder.create<mlir::quantum::ArrayConcatOp>(
                  location, array_type, first_reg_symbol, second_reg_symbol);
          const auto new_size = first_reg_size + second_reg_size;
          // std::cout << "Concatenate " << lhs_temp_var << "[" << first_reg_size
          //           << "] with " << rhs_temp_var << "[" << second_reg_size
          //           << "] -> " << in_aliasName << "[" << new_size << "].\n";
          symbol_table.add_symbol(in_aliasName, array_concat,
                                  {std::to_string(new_size)});
        } else {
          printErrorMessage("Could not parse the alias statement.",
                            in_indexIdentifierContext);
        }
      };

  // The name of the new alias register (LHS of the alias statement)
  // This will be associated with an alias array.
  auto alias = context->Identifier()->getText();
  // Main entrance: process top-level "alias = indexIdentifier"
  // This may call itself recursively if the RHS is a nested statement.
  // e.g. alias = q[1,3,5] || q[2] || q[4:2:8]
  processIdentifierDef(alias, context->indexIdentifier());

  return 0;
}
} // namespace qcor
 No newline at end of file
+16 −0
Original line number Diff line number Diff line
@@ -68,6 +68,22 @@ int8_t *__quantum__rt__array_get_element_ptr_1d(Array *q, uint64_t idx);
Array *__quantum__rt__array_copy(Array *array, bool forceNewInstance);
// Concatenate
Array *__quantum__rt__array_concatenate(Array *head, Array *tail);
// Slice
// Creates and returns an array that is a slice of an existing array. 
// The int dim which dimension the slice is on (0 for 1d arrays). 
// The Range range specifies the slice.
// Note: QIR defines a Range as type { i64, i64, i64 }
// i.e. a struct of 3 int64_t
// and define an API at the *LLVM IR* level of passing this by value
// i.e. the signature is %Range, not "%struct.Range* byval(%struct.Range)" 
// Hence, it is actually equivalent to an expanded list of struct member.
// https://lists.llvm.org/pipermail/llvm-dev/2018-April/122714.html
// Until the spec. is updated (see https://github.com/microsoft/qsharp-language/issues/31)
// this is actually the C-ABI that will match the QIR IR.
Array *__quantum__rt__array_slice(Array *array, int32_t dim, int64_t range_start, int64_t range_step, int64_t range_end);
// Note: Overloading is not possible in C, so just keep the implementation in this local func.
Array *quantum__rt__array_slice(Array *array, int32_t dim, Range range);

// Ref. counting
void __quantum__rt__array_update_alias_count(Array *array, int64_t increment);
void __quantum__rt__array_update_reference_count(Array *aux, int64_t count);
Loading