Loading mlir/dialect/include/Quantum/QuantumOps.td +25 −0 Original line number Diff line number Diff line Loading @@ -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); Loading mlir/parsers/qasm3/tests/CMakeLists.txt +5 −0 Original line number Diff line number Diff line Loading @@ -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) Loading mlir/parsers/qasm3/tests/test_alias_handler.cpp 0 → 100644 +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; } mlir/parsers/qasm3/visitor_handlers/alias_handler.cpp +227 −47 Original line number Diff line number Diff line Loading @@ -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 mlir/qir_qrt/qir-qrt.hpp +16 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
mlir/dialect/include/Quantum/QuantumOps.td +25 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
mlir/parsers/qasm3/tests/CMakeLists.txt +5 −0 Original line number Diff line number Diff line Loading @@ -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) Loading
mlir/parsers/qasm3/tests/test_alias_handler.cpp 0 → 100644 +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; }
mlir/parsers/qasm3/visitor_handlers/alias_handler.cpp +227 −47 Original line number Diff line number Diff line Loading @@ -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
mlir/qir_qrt/qir-qrt.hpp +16 −0 Original line number Diff line number Diff line Loading @@ -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