Commit 29ddc409 authored by Nguyen, Thien Minh's avatar Nguyen, Thien Minh
Browse files

Complete Alias support



Signed-off-by: default avatarThien Nguyen <nguyentm@ornl.gov>
parent b53781ca
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -41,6 +41,11 @@ 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);
+0 −9
Original line number Diff line number Diff line
OPENQASM 3;
include "qelib1.inc";
qubit q[6];
// myreg[0] refers to the qubit q[1], myreg[1] -> q[3], etc.
let myreg = q[1, 3, 5];

for i in [0:3] {
  x q[i];
}
 No newline at end of file
+63 −0
Original line number Diff line number Diff line
@@ -132,6 +132,69 @@ 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);
+226 −139
Original line number Diff line number Diff line
@@ -19,16 +19,15 @@ antlrcpp::Any qasm3_visitor::visitAliasStatement(
  //     | indexIdentifier '||' indexIdentifier
  //     ;

  // 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.
  // We need to determine the alias array size and cache it 
  // for broadcast to work on the result array.
  auto allocated_variable = context->indexIdentifier()->Identifier()->getText();
  auto allocated_symbol = symbol_table.get_symbol(allocated_variable);
  // Determine the first qreg size
  // 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);
@@ -40,53 +39,63 @@ antlrcpp::Any qasm3_visitor::visitAliasStatement(
              try {
                nqubits = std::stoi(attributes[0]);
              } catch (...) {
          printErrorMessage("Could not infer qubit[] size from block argument.",
                            context);
                printErrorMessage(
                    "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.",
            context);
                  "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();
            // Create a qubit array of given size
    // which keeps alias references to Qubits in the original input array.
    auto str_attr = builder.getStringAttr(alias);
            // 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 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(alias, alias_allocation,
            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);
                  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);
                  location, alias_allocation, dest_idx, allocated_symbol,
                  src_idx);
            }
  } else if (auto range_def = context->indexIdentifier()->rangeDefinition()) {
          } 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
@@ -95,10 +104,11 @@ antlrcpp::Any qasm3_visitor::visitAliasStatement(
            }

            auto range_start_expr = range_def->expression(0);
    auto range_stop_expr =
        (n_expr == 2) ? range_def->expression(1) : range_def->expression(2);
            auto range_stop_expr = (n_expr == 2) ? range_def->expression(1)
                                                 : range_def->expression(2);

    const auto resolve_range_value = [&](auto *range_item_expr) -> int64_t {
            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);
@@ -111,7 +121,8 @@ antlrcpp::Any qasm3_visitor::visitAliasStatement(
            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));
                (n_expr == 2) ? 1
                              : resolve_range_value(range_def->expression(1));

            // Step must not be zero:
            if (range_step == 0) {
@@ -121,25 +132,32 @@ antlrcpp::Any qasm3_visitor::visitAliasStatement(
            // 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);
                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);
                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>(
                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}));
                        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.
                                            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;
@@ -155,11 +173,80 @@ antlrcpp::Any qasm3_visitor::visitAliasStatement(
                return 0;
              }
            };
    const auto new_size = slice_size_calc(orig_size, range_start, range_step, range_stop);
    symbol_table.add_symbol(alias, array_slice, {std::to_string(new_size)});
            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
+3 −2
Original line number Diff line number Diff line
@@ -86,11 +86,12 @@ Array *__quantum__rt__array_copy(Array *array, bool forceNewInstance) {
}

Array *__quantum__rt__array_concatenate(Array *head, Array *tail) {
  if (verbose)
    std::cout << "CALL: " << __PRETTY_FUNCTION__ << "\n";
  if (head && tail) {
    auto resultArray = new Array(*head);
    resultArray->append(*tail);
    if (verbose)
      std::cout << "[qir-qrt] Concatenate two arrays of size " << head->size()
                << " and " << tail->size() << ".\n";
    return resultArray;
  }

Loading