Loading mlir/parsers/qasm3/CMakeLists.txt +1 −11 Original line number Diff line number Diff line Loading @@ -6,17 +6,7 @@ if (APPLE) set(ANTLR_LIB ${XACC_ROOT}/lib/libantlr4-runtime.dylib) endif() file(GLOB SRC *.cpp antlr/generated/*.cpp utils/*.cpp visitor_handlers/quantum_types_handler.cpp visitor_handlers/quantum_instruction_handler.cpp visitor_handlers/classical_types_handler.cpp visitor_handlers/measurement_handler.cpp visitor_handlers/loop_stmt_handler.cpp visitor_handlers/conditional_handler.cpp visitor_handlers/subroutine_handler.cpp visitor_handlers/alias_handler.cpp visitor_handlers/compute_action_handler.cpp ) file(GLOB SRC *.cpp antlr/generated/*.cpp utils/*.cpp visitor_handlers/*.cpp) add_library(${LIBRARY_NAME} SHARED ${SRC}) target_compile_features(${LIBRARY_NAME} Loading mlir/parsers/qasm3/qasm3_visitor.hpp +40 −62 Original line number Diff line number Diff line Loading @@ -114,64 +114,9 @@ class qasm3_visitor : public qasm3::qasm3BaseVisitor { // Visit the compute-action-uncompute expression antlrcpp::Any visitCompute_action_stmt(qasm3Parser::Compute_action_stmtContext *context) override; // QCOR_EXPECT_TRUE handler antlrcpp::Any visitQcor_test_statement( qasm3Parser::Qcor_test_statementContext* context) override { auto location = get_location(builder, file_name, context); auto boolean_expr = context->booleanExpression(); qasm3_expression_generator exp_generator(builder, symbol_table, file_name); exp_generator.visit(boolean_expr); auto expr_value = exp_generator.current_value; // So we have a conditional result, want // to negate it and see if == true expr_value = builder.create<mlir::CmpIOp>( location, mlir::CmpIPredicate::ne, expr_value, get_or_create_constant_integer_value(1, location, builder.getI1Type(), symbol_table, builder)); auto currRegion = builder.getBlock()->getParent(); auto savept = builder.saveInsertionPoint(); auto thenBlock = builder.createBlock(currRegion, currRegion->end()); mlir::Block* exitBlock = builder.createBlock(currRegion, currRegion->end()); // Build up the THEN Block builder.setInsertionPointToStart(thenBlock); auto sl = "QCOR Test Failure: " + context->getText() + "\n"; llvm::StringRef string_type_name("StringType"); mlir::Identifier dialect = mlir::Identifier::get("quantum", builder.getContext()); auto str_type = mlir::OpaqueType::get(builder.getContext(), dialect, string_type_name); auto str_attr = builder.getStringAttr(sl); std::hash<std::string> hasher; auto hash = hasher(sl); std::stringstream ss; ss << "__internal_string_literal__" << hash; std::string var_name = ss.str(); auto var_name_attr = builder.getStringAttr(var_name); auto string_literal = builder.create<mlir::quantum::CreateStringLiteralOp>( location, str_type, str_attr, var_name_attr); builder.create<mlir::quantum::PrintOp>( location, llvm::makeArrayRef(std::vector<mlir::Value>{string_literal})); auto integer_attr = mlir::IntegerAttr::get(builder.getI32Type(), 1); auto ret = builder.create<mlir::ConstantOp>(location, integer_attr); builder.create<mlir::ReturnOp>(location, llvm::ArrayRef<mlir::Value>(ret)); // Restore the insertion point and create the conditional statement builder.restoreInsertionPoint(savept); builder.create<mlir::CondBranchOp>(location, expr_value, thenBlock, exitBlock); builder.setInsertionPointToStart(exitBlock); symbol_table.set_last_created_block(exitBlock); return 0; } qasm3Parser::Qcor_test_statementContext *context) override; antlrcpp::Any visitPragma(qasm3Parser::PragmaContext *ctx) override { // Handle the #pragma { export; } directive Loading @@ -192,11 +137,6 @@ class qasm3_visitor : public qasm3::qasm3BaseVisitor { mlir::ModuleOp m_module; std::string file_name = ""; bool enable_nisq_ifelse = false; // We keep reference to these blocks so that // we can handle break/continue correctly mlir::Block* current_loop_exit_block; mlir::Block* current_loop_header_block; mlir::Block* current_loop_incrementor_block; // The symbol table, keeps track of current scope ScopedSymbolTable symbol_table; Loading @@ -216,6 +156,24 @@ class qasm3_visitor : public qasm3::qasm3BaseVisitor { mlir::Type array_type; mlir::Type result_type; // Loop control vars for break/continue implementation with Region-based // Affine/SCF Ops. // Strategy: /// - A break-able for loop will have a bool (first in the pair) to control /// the loop body execution. i.e., bypass the whole loop if the break /// condition is triggered. /// - The second bool is the continue condition which will bypass all /// the remaining ops in the body. /// We use a stack to handle nested loops, which are all break-able. std::stack<std::pair<mlir::Value, mlir::Value>> loop_control_directive_bool_vars; // Early return loop control directive: return statement in the loop body. // This will escape all loops until the *FuncOp* body and return. // Note: MLIR validation will require ReturnOp in the **Region** of a FuncOp. // First value: the boolean to control the early return (if true) // Second value: the return value. std::optional<std::pair<mlir::Value, std::optional<mlir::Value>>> region_early_return_vars; // This method will add correct number of InstOps // based on quantum gate broadcasting void createInstOps_HandleBroadcast(std::string name, Loading @@ -226,6 +184,26 @@ class qasm3_visitor : public qasm3::qasm3BaseVisitor { mlir::Location location, antlr4::ParserRuleContext* context); // Helper to handle range-based for loop void createRangeBasedForLoop(qasm3Parser::LoopStatementContext *context); // Helper to handle set-based for loop: // e.g., for i in {1,4,6,7}: void createSetBasedForLoop(qasm3Parser::LoopStatementContext *context); // While loop void createWhileLoop(qasm3Parser::LoopStatementContext *context); // Insert MLIR loop break void insertLoopBreak(mlir::Location &location, mlir::OpBuilder *optional_builder = nullptr); void insertLoopContinue(mlir::Location &location, mlir::OpBuilder *optional_builder = nullptr); void handleReturnInLoop(mlir::Location &location); // Insert a conditional return. // Assert that the insert location is *returnable* // i.e., in the FuncOp region. void conditionalReturn(mlir::Location &location, mlir::Value cond, mlir::Value returnVal, mlir::OpBuilder *optional_builder = nullptr); // This function serves as a utility for creating a MemRef and // corresponding AllocOp of a given 1d shape. It will also store // initial values to all elements of the 1d array. Loading mlir/parsers/qasm3/tests/test_control_directives.cpp +330 −26 Original line number Diff line number Diff line Loading @@ -15,43 +15,42 @@ int countSubstring(const std::string &str, const std::string &sub) { } } // namespace // Check Affine-SCF constructs TEST(qasm3VisitorTester, checkCFG_AffineScf) { const std::string qasm_code = R"#(OPENQASM 3; TEST(qasm3VisitorTester, checkCtrlDirectives) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; int[64] iterate_value = 0; int[64] value_5 = 0; int[64] value_2 = 0; int[64] hit_continue_value = 0; for i in [0:10] { iterate_value = i; if (i == 5) { print("Iterate over 5"); value_5 = 5; print("breaking at 5"); break; } if (i == 2) { print("Iterate over 2"); value_2 = 2; hit_continue_value = i; print("continuing at 2"); continue; } print("i = ", i); } QCOR_EXPECT_TRUE(iterate_value == 9); QCOR_EXPECT_TRUE(value_5 == 5); QCOR_EXPECT_TRUE(value_2 == 2); QCOR_EXPECT_TRUE(iterate_value == 5); QCOR_EXPECT_TRUE(hit_continue_value == 2); print("made it out of the loop"); )#"; auto mlir = qcor::mlir_compile(qasm_code, "affine_scf", auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; // 1 for loop, 2 if blocks // We're now using Affine and SCF EXPECT_EQ(countSubstring(mlir, "affine.for"), 1); EXPECT_EQ(countSubstring(mlir, "scf.if"), 2); EXPECT_FALSE(qcor::execute(qasm_code, "affine_scf")); EXPECT_GT(countSubstring(mlir, "scf.if"), 1); EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } TEST(qasm3VisitorTester, checkCtrlDirectives) { TEST(qasm3VisitorTester, checkCtrlDirectivesComplex) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; Loading @@ -62,20 +61,324 @@ for i in [0:10] { if (i == 5) { print("breaking at 5"); break; } else { if (i == 3) { print("breaking at 3"); break; } } if (i == 2) { hit_continue_value = i; print("continuing at 2"); continue; } if (iterate_value == 2) { hit_continue_value = 5; print("SHOULD NEVER BE HERE!!!"); } print("i = ", i); } QCOR_EXPECT_TRUE(iterate_value == 5); QCOR_EXPECT_TRUE(hit_continue_value == 2); // The break at 3 in the else loop will be activated first. QCOR_EXPECT_TRUE(iterate_value == 3); print("made it out of the loop");)#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; // We're now using Affine and SCF EXPECT_EQ(countSubstring(mlir, "affine.for"), 1); EXPECT_GT(countSubstring(mlir, "scf.if"), 1); EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } print("made it out of the loop"); TEST(qasm3VisitorTester, checkCtrlDirectivesSetBasedForLoop) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; int[64] sum_value = 0; int[64] break_value = 0; int[64] loop_count = 0; for val in {1,3,5,7} { print("iter: ", val); if (val < 4) { sum_value += val; } else { break_value = val; break; } loop_count += 1; } print(sum_value); print(loop_count); print(break_value); QCOR_EXPECT_TRUE(sum_value == 4); QCOR_EXPECT_TRUE(loop_count == 2); QCOR_EXPECT_TRUE(break_value == 5);)#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; // Make sure we're using Affine and SCF EXPECT_EQ(countSubstring(mlir, "affine.for"), 1); EXPECT_GT(countSubstring(mlir, "scf.if"), 1); EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } TEST(qasm3VisitorTester, checkCtrlDirectivesWhileLoop) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; int[32] i = 0; int[32] j = 0; while (i < 10) { // Before break i += 1; if (i == 8) { print("Breaking at", i); break; } // After break j += 1; } print("make to the end, i =", i); print("make to the end, j =", j); QCOR_EXPECT_TRUE(i == 8); QCOR_EXPECT_TRUE(j == 7); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; // Make sure we're using Affine While loop EXPECT_EQ(countSubstring(mlir, "scf.while"), 1); EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } TEST(qasm3VisitorTester, checkEarlyReturn) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def generate_number(int[64]: count) -> int[64] { for i in [0:count] { if (i > 10) { print("Return at ", i); return 5; } print("i =", i); } print("make it to the end"); return 1; } int[64] val1 = generate_number(4); print("Result 1 =", val1); QCOR_EXPECT_TRUE(val1 == 1); // Call it with 20 -> activate the early return int[64] val2 = generate_number(20); print("Result 2 =", val2); QCOR_EXPECT_TRUE(val2 == 5); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } // Coverage for https://github.com/ORNL-QCI/qcor/issues/211 as well TEST(qasm3VisitorTester, checkEarlyReturnWith211) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def generate_number(int[64]: count) -> int[64] { for i in [0:count] { if (i > 10) { print("Return at ", i); return 3; } print("i =", i); } print("make it to the end"); return count; } int[64] arg_val = 7; int[64] val1 = generate_number(arg_val); print("Result 1 =", val1); QCOR_EXPECT_TRUE(val1 == arg_val); // Call it with 20 -> activate the early return int[64] val2 = generate_number(20); print("Result 2 =", val2); QCOR_EXPECT_TRUE(val2 == 3); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } TEST(qasm3VisitorTester, checkEarlyReturnNestedLoop) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def generate_number(int[64]: break_value, int[64]: max_run) -> int[64] { int[64] run_count = 0; for i in [0:10] { for j in [0:10] { run_count += 1; if (run_count > max_run) { print("Exceeding max run count of", max_run); return 3; } if (i == j && i > break_value) { print("Return at i = ", i); print("Return at j = ", j); return run_count; } print("i =", i); print("j =", j); } print("Out of inner loop"); } print("make it to the end"); return 0; } // Case 1: run to the end. int[64] val = generate_number(10, 100); print("Result =", val); QCOR_EXPECT_TRUE(val == 0); // Case 2: Return @ (i == j && i > break_value) // i = 0: 10; i = 1: 10; i = 2: j = 0, 1, 2 // => 23 runs (return run_count in this path) val = generate_number(1, 100); print("Result =", val); QCOR_EXPECT_TRUE(val == 23); // Case 3: return due to max_run limit // limit to 20 (less than 23) => return value 3 val = generate_number(1, 20); print("Result =", val); QCOR_EXPECT_TRUE(val == 3); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } // Nesting for and while loops TEST(qasm3VisitorTester, checkEarlyReturnNestedWhileLoop) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def generate_number(int[64]: break_value, int[64]: max_run) -> int[64] { int[64] run_count = 0; int[64] i = 0; while(run_count < max_run) { for j in [0:10] { run_count += 1; if (i == j && i > break_value) { print("Return at i = ", i); print("Return at j = ", j); return run_count; } print("i =", i); print("j =", j); } i += 1; print("Out of inner loop"); } print("make it to the end"); return 0; } // Case 1: early return @ (i == j && i > break_value) // => 23 runs (return run_count in this path) int[64] val = generate_number(1, 100); print("Result =", val); QCOR_EXPECT_TRUE(val == 23); // Case 2: return at the end // Make it to the end since run_count will hit 20 // before hitting the early return condition. val = generate_number(1, 20); print("Result =", val); QCOR_EXPECT_TRUE(val == 0); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } // Test a complex construction with nesting of different loop types, // break, return, etc... TEST(qasm3VisitorTester, checkControlDirectiveKitchenSink) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def find_number(int[64]: target, int[64]: max_run) -> int[64] { int[64] run_count = 0; for i in { 1, 3, 5, 7, 9} { while(run_count < max_run) { for j in [0:10] { run_count += 1; if (i == target && j == i) { print("Return at i = ", i); print("Return at j = ", j); print("Return run_count = ", run_count); return run_count; } print("i =", i); print("j =", j); } // Finish the searching [0->10], break the while loop // This is a weird construction, // just for testing. break; } print("Make it out of the loop"); print("run_count = ", run_count); } print("Not found"); return 0; } int[64] val = find_number(3, 100); print("Result =", val); // Find number 3 at 14 iterations. QCOR_EXPECT_TRUE(val == 14); val = find_number(2, 100); print("Result =", val); // Cannot find number 2 in the set: QCOR_EXPECT_TRUE(val == 0); val = find_number(7, 20); print("Result =", val); // Cannot find number 7 with only 20 iterations QCOR_EXPECT_TRUE(val == 0); val = find_number(7, 100); print("Result =", val); // But will find it at iteration 38... QCOR_EXPECT_TRUE(val == 38); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); Loading Loading @@ -164,12 +467,13 @@ QCOR_EXPECT_TRUE(c[3] == 1); )#"; // Make sure we can compile this in FTQC. // i.e., usual if ... auto mlir = qcor::mlir_compile(qasm_code, "iqpe", qcor::OutputType::LLVMIR, false); auto mlir = qcor::mlir_compile(qasm_code, "iqpe", qcor::OutputType::LLVMIR, false); std::cout << mlir << "\n"; // Execute (FTQC + optimization): validate expected results: 1101 EXPECT_FALSE(qcor::execute(qasm_code, "iqpe")); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); Loading mlir/parsers/qasm3/tests/test_loop_stmts.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,8 @@ QCOR_EXPECT_TRUE(i == 10); auto mlir2 = qcor::mlir_compile(while_stmt, "while_stmt", qcor::OutputType::MLIR, false); std::cout << mlir2 << "\n"; // We're using SCF while loop: EXPECT_TRUE(mlir2.find("scf.while") != std::string::npos); EXPECT_FALSE(qcor::execute(while_stmt, "while_stmt")); const std::string decrement = R"#(OPENQASM 3; Loading mlir/parsers/qasm3/tests/test_optimization.cpp +150 −17 Original line number Diff line number Diff line Loading @@ -25,8 +25,7 @@ x q[0]; cx q[0], q[1]; cx q[0], q[1]; )#"; auto llvm = qcor::mlir_compile(src, "test", qcor::OutputType::LLVMIR, true); auto llvm = qcor::mlir_compile(src, "test", qcor::OutputType::LLVMIR, true); std::cout << "LLVM:\n" << llvm << "\n"; // No instrucions left EXPECT_TRUE(llvm.find("__quantum__qis") == std::string::npos); Loading Loading @@ -423,8 +422,8 @@ for i in [0:2*n + 1] { x qb; } )#"; auto llvm = qcor::mlir_compile(src, "test_kernel1", qcor::OutputType::LLVMIR, false); auto llvm = qcor::mlir_compile(src, "test_kernel1", qcor::OutputType::LLVMIR, false); std::cout << "LLVM:\n" << llvm << "\n"; // Get the main kernel section only Loading @@ -437,6 +436,140 @@ for i in [0:2*n + 1] { } } TEST(qasm3PassManagerTester, checkConditionalBlock) { const std::string src = R"#(OPENQASM 3; include "qelib1.inc"; bit c; qubit q[2]; h q[0]; x q[1]; c = measure q[1]; if (c == 1) { // Always execute: z q[0]; } h q[0]; // This should be: H - Z - H == X // Check that the two H's before and after 'if' // don't connect. // i.e., checking that we don't accidentally cancel the two H gates // => left with Z => measure 0 vs. expected 1. bit d; d = measure q[0]; print("measure =", d); QCOR_EXPECT_TRUE(d == 1); )#"; auto llvm = qcor::mlir_compile(src, "test_kernel1", qcor::OutputType::LLVMIR, false); std::cout << "LLVM:\n" << llvm << "\n"; // Get the main kernel section only llvm = llvm.substr(llvm.find("@__internal_mlir_test_kernel1")); const auto last = llvm.find_first_of("}"); llvm = llvm.substr(0, last + 1); std::cout << "LLVM:\n" << llvm << "\n"; // 2 Hadamard gates, 1 Z gate // (Z gate in the conditional block) EXPECT_EQ(countSubstring(llvm, "__quantum__qis__h"), 2); EXPECT_EQ(countSubstring(llvm, "__quantum__qis__z"), 1); EXPECT_FALSE(qcor::execute(src, "test_kernel1")); } TEST(qasm3PassManagerTester, checkCPhaseMerge) { const std::string qasm_code = R"#(OPENQASM 3; include "qelib1.inc"; // Expected to get 4 bits (iteratively) of 1011 (or 1101 LSB) = 11(decimal): // phi_est = 11/16 (denom = 16 since we have 4 bits) // => phi = 2pi * 11/16 = 11pi/8 = 2pi - 5pi/8 // i.e. we estimate the -5*pi/8 angle... qubit q[2]; const bits_precision = 4; bit c[bits_precision]; // Prepare the eigen-state: |1> x q[1]; // First bit h q[0]; // Controlled rotation: CU^k for i in [0:8] { cphase(-5*pi/8) q[0], q[1]; } h q[0]; // Measure and reset measure q[0] -> c[0]; reset q[0]; // Second bit h q[0]; for i in [0:4] { cphase(-5*pi/8) q[0], q[1]; } // Conditional rotation if (c[0] == 1) { rz(-pi/2) q[0]; } h q[0]; // Measure and reset measure q[0] -> c[1]; reset q[0]; // Third bit h q[0]; for i in [0:2] { cphase(-5*pi/8) q[0], q[1]; } // Conditional rotation if (c[0] == 1) { rz(-pi/4) q[0]; } if (c[1] == 1) { rz(-pi/2) q[0]; } h q[0]; // Measure and reset measure q[0] -> c[2]; reset q[0]; // Fourth bit h q[0]; cphase(-5*pi/8) q[0], q[1]; // Conditional rotation if (c[0] == 1) { rz(-pi/8) q[0]; } if (c[1] == 1) { rz(-pi/4) q[0]; } if (c[2] == 1) { rz(-pi/2) q[0]; } h q[0]; measure q[0] -> c[3]; print(c[0], c[1], c[2], c[3]); QCOR_EXPECT_TRUE(c[0] == 1); QCOR_EXPECT_TRUE(c[1] == 1); QCOR_EXPECT_TRUE(c[2] == 0); QCOR_EXPECT_TRUE(c[3] == 1); )#"; auto llvm = qcor::mlir_compile(qasm_code, "test_kernel1", qcor::OutputType::LLVMIR, false); std::cout << "LLVM:\n" << llvm << "\n"; // Get the main kernel section only llvm = llvm.substr(llvm.find("@__internal_mlir_test_kernel1")); const auto last = llvm.find_first_of("}"); llvm = llvm.substr(0, last + 1); std::cout << "LLVM:\n" << llvm << "\n"; // All CPhase gates in for loops have been unrolled and then merged. EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cphase"), 4); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); Loading Loading
mlir/parsers/qasm3/CMakeLists.txt +1 −11 Original line number Diff line number Diff line Loading @@ -6,17 +6,7 @@ if (APPLE) set(ANTLR_LIB ${XACC_ROOT}/lib/libantlr4-runtime.dylib) endif() file(GLOB SRC *.cpp antlr/generated/*.cpp utils/*.cpp visitor_handlers/quantum_types_handler.cpp visitor_handlers/quantum_instruction_handler.cpp visitor_handlers/classical_types_handler.cpp visitor_handlers/measurement_handler.cpp visitor_handlers/loop_stmt_handler.cpp visitor_handlers/conditional_handler.cpp visitor_handlers/subroutine_handler.cpp visitor_handlers/alias_handler.cpp visitor_handlers/compute_action_handler.cpp ) file(GLOB SRC *.cpp antlr/generated/*.cpp utils/*.cpp visitor_handlers/*.cpp) add_library(${LIBRARY_NAME} SHARED ${SRC}) target_compile_features(${LIBRARY_NAME} Loading
mlir/parsers/qasm3/qasm3_visitor.hpp +40 −62 Original line number Diff line number Diff line Loading @@ -114,64 +114,9 @@ class qasm3_visitor : public qasm3::qasm3BaseVisitor { // Visit the compute-action-uncompute expression antlrcpp::Any visitCompute_action_stmt(qasm3Parser::Compute_action_stmtContext *context) override; // QCOR_EXPECT_TRUE handler antlrcpp::Any visitQcor_test_statement( qasm3Parser::Qcor_test_statementContext* context) override { auto location = get_location(builder, file_name, context); auto boolean_expr = context->booleanExpression(); qasm3_expression_generator exp_generator(builder, symbol_table, file_name); exp_generator.visit(boolean_expr); auto expr_value = exp_generator.current_value; // So we have a conditional result, want // to negate it and see if == true expr_value = builder.create<mlir::CmpIOp>( location, mlir::CmpIPredicate::ne, expr_value, get_or_create_constant_integer_value(1, location, builder.getI1Type(), symbol_table, builder)); auto currRegion = builder.getBlock()->getParent(); auto savept = builder.saveInsertionPoint(); auto thenBlock = builder.createBlock(currRegion, currRegion->end()); mlir::Block* exitBlock = builder.createBlock(currRegion, currRegion->end()); // Build up the THEN Block builder.setInsertionPointToStart(thenBlock); auto sl = "QCOR Test Failure: " + context->getText() + "\n"; llvm::StringRef string_type_name("StringType"); mlir::Identifier dialect = mlir::Identifier::get("quantum", builder.getContext()); auto str_type = mlir::OpaqueType::get(builder.getContext(), dialect, string_type_name); auto str_attr = builder.getStringAttr(sl); std::hash<std::string> hasher; auto hash = hasher(sl); std::stringstream ss; ss << "__internal_string_literal__" << hash; std::string var_name = ss.str(); auto var_name_attr = builder.getStringAttr(var_name); auto string_literal = builder.create<mlir::quantum::CreateStringLiteralOp>( location, str_type, str_attr, var_name_attr); builder.create<mlir::quantum::PrintOp>( location, llvm::makeArrayRef(std::vector<mlir::Value>{string_literal})); auto integer_attr = mlir::IntegerAttr::get(builder.getI32Type(), 1); auto ret = builder.create<mlir::ConstantOp>(location, integer_attr); builder.create<mlir::ReturnOp>(location, llvm::ArrayRef<mlir::Value>(ret)); // Restore the insertion point and create the conditional statement builder.restoreInsertionPoint(savept); builder.create<mlir::CondBranchOp>(location, expr_value, thenBlock, exitBlock); builder.setInsertionPointToStart(exitBlock); symbol_table.set_last_created_block(exitBlock); return 0; } qasm3Parser::Qcor_test_statementContext *context) override; antlrcpp::Any visitPragma(qasm3Parser::PragmaContext *ctx) override { // Handle the #pragma { export; } directive Loading @@ -192,11 +137,6 @@ class qasm3_visitor : public qasm3::qasm3BaseVisitor { mlir::ModuleOp m_module; std::string file_name = ""; bool enable_nisq_ifelse = false; // We keep reference to these blocks so that // we can handle break/continue correctly mlir::Block* current_loop_exit_block; mlir::Block* current_loop_header_block; mlir::Block* current_loop_incrementor_block; // The symbol table, keeps track of current scope ScopedSymbolTable symbol_table; Loading @@ -216,6 +156,24 @@ class qasm3_visitor : public qasm3::qasm3BaseVisitor { mlir::Type array_type; mlir::Type result_type; // Loop control vars for break/continue implementation with Region-based // Affine/SCF Ops. // Strategy: /// - A break-able for loop will have a bool (first in the pair) to control /// the loop body execution. i.e., bypass the whole loop if the break /// condition is triggered. /// - The second bool is the continue condition which will bypass all /// the remaining ops in the body. /// We use a stack to handle nested loops, which are all break-able. std::stack<std::pair<mlir::Value, mlir::Value>> loop_control_directive_bool_vars; // Early return loop control directive: return statement in the loop body. // This will escape all loops until the *FuncOp* body and return. // Note: MLIR validation will require ReturnOp in the **Region** of a FuncOp. // First value: the boolean to control the early return (if true) // Second value: the return value. std::optional<std::pair<mlir::Value, std::optional<mlir::Value>>> region_early_return_vars; // This method will add correct number of InstOps // based on quantum gate broadcasting void createInstOps_HandleBroadcast(std::string name, Loading @@ -226,6 +184,26 @@ class qasm3_visitor : public qasm3::qasm3BaseVisitor { mlir::Location location, antlr4::ParserRuleContext* context); // Helper to handle range-based for loop void createRangeBasedForLoop(qasm3Parser::LoopStatementContext *context); // Helper to handle set-based for loop: // e.g., for i in {1,4,6,7}: void createSetBasedForLoop(qasm3Parser::LoopStatementContext *context); // While loop void createWhileLoop(qasm3Parser::LoopStatementContext *context); // Insert MLIR loop break void insertLoopBreak(mlir::Location &location, mlir::OpBuilder *optional_builder = nullptr); void insertLoopContinue(mlir::Location &location, mlir::OpBuilder *optional_builder = nullptr); void handleReturnInLoop(mlir::Location &location); // Insert a conditional return. // Assert that the insert location is *returnable* // i.e., in the FuncOp region. void conditionalReturn(mlir::Location &location, mlir::Value cond, mlir::Value returnVal, mlir::OpBuilder *optional_builder = nullptr); // This function serves as a utility for creating a MemRef and // corresponding AllocOp of a given 1d shape. It will also store // initial values to all elements of the 1d array. Loading
mlir/parsers/qasm3/tests/test_control_directives.cpp +330 −26 Original line number Diff line number Diff line Loading @@ -15,43 +15,42 @@ int countSubstring(const std::string &str, const std::string &sub) { } } // namespace // Check Affine-SCF constructs TEST(qasm3VisitorTester, checkCFG_AffineScf) { const std::string qasm_code = R"#(OPENQASM 3; TEST(qasm3VisitorTester, checkCtrlDirectives) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; int[64] iterate_value = 0; int[64] value_5 = 0; int[64] value_2 = 0; int[64] hit_continue_value = 0; for i in [0:10] { iterate_value = i; if (i == 5) { print("Iterate over 5"); value_5 = 5; print("breaking at 5"); break; } if (i == 2) { print("Iterate over 2"); value_2 = 2; hit_continue_value = i; print("continuing at 2"); continue; } print("i = ", i); } QCOR_EXPECT_TRUE(iterate_value == 9); QCOR_EXPECT_TRUE(value_5 == 5); QCOR_EXPECT_TRUE(value_2 == 2); QCOR_EXPECT_TRUE(iterate_value == 5); QCOR_EXPECT_TRUE(hit_continue_value == 2); print("made it out of the loop"); )#"; auto mlir = qcor::mlir_compile(qasm_code, "affine_scf", auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; // 1 for loop, 2 if blocks // We're now using Affine and SCF EXPECT_EQ(countSubstring(mlir, "affine.for"), 1); EXPECT_EQ(countSubstring(mlir, "scf.if"), 2); EXPECT_FALSE(qcor::execute(qasm_code, "affine_scf")); EXPECT_GT(countSubstring(mlir, "scf.if"), 1); EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } TEST(qasm3VisitorTester, checkCtrlDirectives) { TEST(qasm3VisitorTester, checkCtrlDirectivesComplex) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; Loading @@ -62,20 +61,324 @@ for i in [0:10] { if (i == 5) { print("breaking at 5"); break; } else { if (i == 3) { print("breaking at 3"); break; } } if (i == 2) { hit_continue_value = i; print("continuing at 2"); continue; } if (iterate_value == 2) { hit_continue_value = 5; print("SHOULD NEVER BE HERE!!!"); } print("i = ", i); } QCOR_EXPECT_TRUE(iterate_value == 5); QCOR_EXPECT_TRUE(hit_continue_value == 2); // The break at 3 in the else loop will be activated first. QCOR_EXPECT_TRUE(iterate_value == 3); print("made it out of the loop");)#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; // We're now using Affine and SCF EXPECT_EQ(countSubstring(mlir, "affine.for"), 1); EXPECT_GT(countSubstring(mlir, "scf.if"), 1); EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } print("made it out of the loop"); TEST(qasm3VisitorTester, checkCtrlDirectivesSetBasedForLoop) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; int[64] sum_value = 0; int[64] break_value = 0; int[64] loop_count = 0; for val in {1,3,5,7} { print("iter: ", val); if (val < 4) { sum_value += val; } else { break_value = val; break; } loop_count += 1; } print(sum_value); print(loop_count); print(break_value); QCOR_EXPECT_TRUE(sum_value == 4); QCOR_EXPECT_TRUE(loop_count == 2); QCOR_EXPECT_TRUE(break_value == 5);)#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; // Make sure we're using Affine and SCF EXPECT_EQ(countSubstring(mlir, "affine.for"), 1); EXPECT_GT(countSubstring(mlir, "scf.if"), 1); EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } TEST(qasm3VisitorTester, checkCtrlDirectivesWhileLoop) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; int[32] i = 0; int[32] j = 0; while (i < 10) { // Before break i += 1; if (i == 8) { print("Breaking at", i); break; } // After break j += 1; } print("make to the end, i =", i); print("make to the end, j =", j); QCOR_EXPECT_TRUE(i == 8); QCOR_EXPECT_TRUE(j == 7); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; // Make sure we're using Affine While loop EXPECT_EQ(countSubstring(mlir, "scf.while"), 1); EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } TEST(qasm3VisitorTester, checkEarlyReturn) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def generate_number(int[64]: count) -> int[64] { for i in [0:count] { if (i > 10) { print("Return at ", i); return 5; } print("i =", i); } print("make it to the end"); return 1; } int[64] val1 = generate_number(4); print("Result 1 =", val1); QCOR_EXPECT_TRUE(val1 == 1); // Call it with 20 -> activate the early return int[64] val2 = generate_number(20); print("Result 2 =", val2); QCOR_EXPECT_TRUE(val2 == 5); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } // Coverage for https://github.com/ORNL-QCI/qcor/issues/211 as well TEST(qasm3VisitorTester, checkEarlyReturnWith211) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def generate_number(int[64]: count) -> int[64] { for i in [0:count] { if (i > 10) { print("Return at ", i); return 3; } print("i =", i); } print("make it to the end"); return count; } int[64] arg_val = 7; int[64] val1 = generate_number(arg_val); print("Result 1 =", val1); QCOR_EXPECT_TRUE(val1 == arg_val); // Call it with 20 -> activate the early return int[64] val2 = generate_number(20); print("Result 2 =", val2); QCOR_EXPECT_TRUE(val2 == 3); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } TEST(qasm3VisitorTester, checkEarlyReturnNestedLoop) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def generate_number(int[64]: break_value, int[64]: max_run) -> int[64] { int[64] run_count = 0; for i in [0:10] { for j in [0:10] { run_count += 1; if (run_count > max_run) { print("Exceeding max run count of", max_run); return 3; } if (i == j && i > break_value) { print("Return at i = ", i); print("Return at j = ", j); return run_count; } print("i =", i); print("j =", j); } print("Out of inner loop"); } print("make it to the end"); return 0; } // Case 1: run to the end. int[64] val = generate_number(10, 100); print("Result =", val); QCOR_EXPECT_TRUE(val == 0); // Case 2: Return @ (i == j && i > break_value) // i = 0: 10; i = 1: 10; i = 2: j = 0, 1, 2 // => 23 runs (return run_count in this path) val = generate_number(1, 100); print("Result =", val); QCOR_EXPECT_TRUE(val == 23); // Case 3: return due to max_run limit // limit to 20 (less than 23) => return value 3 val = generate_number(1, 20); print("Result =", val); QCOR_EXPECT_TRUE(val == 3); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } // Nesting for and while loops TEST(qasm3VisitorTester, checkEarlyReturnNestedWhileLoop) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def generate_number(int[64]: break_value, int[64]: max_run) -> int[64] { int[64] run_count = 0; int[64] i = 0; while(run_count < max_run) { for j in [0:10] { run_count += 1; if (i == j && i > break_value) { print("Return at i = ", i); print("Return at j = ", j); return run_count; } print("i =", i); print("j =", j); } i += 1; print("Out of inner loop"); } print("make it to the end"); return 0; } // Case 1: early return @ (i == j && i > break_value) // => 23 runs (return run_count in this path) int[64] val = generate_number(1, 100); print("Result =", val); QCOR_EXPECT_TRUE(val == 23); // Case 2: return at the end // Make it to the end since run_count will hit 20 // before hitting the early return condition. val = generate_number(1, 20); print("Result =", val); QCOR_EXPECT_TRUE(val == 0); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); std::cout << mlir << "\n"; EXPECT_FALSE(qcor::execute(uint_index, "uint_index")); } // Test a complex construction with nesting of different loop types, // break, return, etc... TEST(qasm3VisitorTester, checkControlDirectiveKitchenSink) { const std::string uint_index = R"#(OPENQASM 3; include "qelib1.inc"; def find_number(int[64]: target, int[64]: max_run) -> int[64] { int[64] run_count = 0; for i in { 1, 3, 5, 7, 9} { while(run_count < max_run) { for j in [0:10] { run_count += 1; if (i == target && j == i) { print("Return at i = ", i); print("Return at j = ", j); print("Return run_count = ", run_count); return run_count; } print("i =", i); print("j =", j); } // Finish the searching [0->10], break the while loop // This is a weird construction, // just for testing. break; } print("Make it out of the loop"); print("run_count = ", run_count); } print("Not found"); return 0; } int[64] val = find_number(3, 100); print("Result =", val); // Find number 3 at 14 iterations. QCOR_EXPECT_TRUE(val == 14); val = find_number(2, 100); print("Result =", val); // Cannot find number 2 in the set: QCOR_EXPECT_TRUE(val == 0); val = find_number(7, 20); print("Result =", val); // Cannot find number 7 with only 20 iterations QCOR_EXPECT_TRUE(val == 0); val = find_number(7, 100); print("Result =", val); // But will find it at iteration 38... QCOR_EXPECT_TRUE(val == 38); )#"; auto mlir = qcor::mlir_compile(uint_index, "uint_index", qcor::OutputType::MLIR, false); Loading Loading @@ -164,12 +467,13 @@ QCOR_EXPECT_TRUE(c[3] == 1); )#"; // Make sure we can compile this in FTQC. // i.e., usual if ... auto mlir = qcor::mlir_compile(qasm_code, "iqpe", qcor::OutputType::LLVMIR, false); auto mlir = qcor::mlir_compile(qasm_code, "iqpe", qcor::OutputType::LLVMIR, false); std::cout << mlir << "\n"; // Execute (FTQC + optimization): validate expected results: 1101 EXPECT_FALSE(qcor::execute(qasm_code, "iqpe")); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); Loading
mlir/parsers/qasm3/tests/test_loop_stmts.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,8 @@ QCOR_EXPECT_TRUE(i == 10); auto mlir2 = qcor::mlir_compile(while_stmt, "while_stmt", qcor::OutputType::MLIR, false); std::cout << mlir2 << "\n"; // We're using SCF while loop: EXPECT_TRUE(mlir2.find("scf.while") != std::string::npos); EXPECT_FALSE(qcor::execute(while_stmt, "while_stmt")); const std::string decrement = R"#(OPENQASM 3; Loading
mlir/parsers/qasm3/tests/test_optimization.cpp +150 −17 Original line number Diff line number Diff line Loading @@ -25,8 +25,7 @@ x q[0]; cx q[0], q[1]; cx q[0], q[1]; )#"; auto llvm = qcor::mlir_compile(src, "test", qcor::OutputType::LLVMIR, true); auto llvm = qcor::mlir_compile(src, "test", qcor::OutputType::LLVMIR, true); std::cout << "LLVM:\n" << llvm << "\n"; // No instrucions left EXPECT_TRUE(llvm.find("__quantum__qis") == std::string::npos); Loading Loading @@ -423,8 +422,8 @@ for i in [0:2*n + 1] { x qb; } )#"; auto llvm = qcor::mlir_compile(src, "test_kernel1", qcor::OutputType::LLVMIR, false); auto llvm = qcor::mlir_compile(src, "test_kernel1", qcor::OutputType::LLVMIR, false); std::cout << "LLVM:\n" << llvm << "\n"; // Get the main kernel section only Loading @@ -437,6 +436,140 @@ for i in [0:2*n + 1] { } } TEST(qasm3PassManagerTester, checkConditionalBlock) { const std::string src = R"#(OPENQASM 3; include "qelib1.inc"; bit c; qubit q[2]; h q[0]; x q[1]; c = measure q[1]; if (c == 1) { // Always execute: z q[0]; } h q[0]; // This should be: H - Z - H == X // Check that the two H's before and after 'if' // don't connect. // i.e., checking that we don't accidentally cancel the two H gates // => left with Z => measure 0 vs. expected 1. bit d; d = measure q[0]; print("measure =", d); QCOR_EXPECT_TRUE(d == 1); )#"; auto llvm = qcor::mlir_compile(src, "test_kernel1", qcor::OutputType::LLVMIR, false); std::cout << "LLVM:\n" << llvm << "\n"; // Get the main kernel section only llvm = llvm.substr(llvm.find("@__internal_mlir_test_kernel1")); const auto last = llvm.find_first_of("}"); llvm = llvm.substr(0, last + 1); std::cout << "LLVM:\n" << llvm << "\n"; // 2 Hadamard gates, 1 Z gate // (Z gate in the conditional block) EXPECT_EQ(countSubstring(llvm, "__quantum__qis__h"), 2); EXPECT_EQ(countSubstring(llvm, "__quantum__qis__z"), 1); EXPECT_FALSE(qcor::execute(src, "test_kernel1")); } TEST(qasm3PassManagerTester, checkCPhaseMerge) { const std::string qasm_code = R"#(OPENQASM 3; include "qelib1.inc"; // Expected to get 4 bits (iteratively) of 1011 (or 1101 LSB) = 11(decimal): // phi_est = 11/16 (denom = 16 since we have 4 bits) // => phi = 2pi * 11/16 = 11pi/8 = 2pi - 5pi/8 // i.e. we estimate the -5*pi/8 angle... qubit q[2]; const bits_precision = 4; bit c[bits_precision]; // Prepare the eigen-state: |1> x q[1]; // First bit h q[0]; // Controlled rotation: CU^k for i in [0:8] { cphase(-5*pi/8) q[0], q[1]; } h q[0]; // Measure and reset measure q[0] -> c[0]; reset q[0]; // Second bit h q[0]; for i in [0:4] { cphase(-5*pi/8) q[0], q[1]; } // Conditional rotation if (c[0] == 1) { rz(-pi/2) q[0]; } h q[0]; // Measure and reset measure q[0] -> c[1]; reset q[0]; // Third bit h q[0]; for i in [0:2] { cphase(-5*pi/8) q[0], q[1]; } // Conditional rotation if (c[0] == 1) { rz(-pi/4) q[0]; } if (c[1] == 1) { rz(-pi/2) q[0]; } h q[0]; // Measure and reset measure q[0] -> c[2]; reset q[0]; // Fourth bit h q[0]; cphase(-5*pi/8) q[0], q[1]; // Conditional rotation if (c[0] == 1) { rz(-pi/8) q[0]; } if (c[1] == 1) { rz(-pi/4) q[0]; } if (c[2] == 1) { rz(-pi/2) q[0]; } h q[0]; measure q[0] -> c[3]; print(c[0], c[1], c[2], c[3]); QCOR_EXPECT_TRUE(c[0] == 1); QCOR_EXPECT_TRUE(c[1] == 1); QCOR_EXPECT_TRUE(c[2] == 0); QCOR_EXPECT_TRUE(c[3] == 1); )#"; auto llvm = qcor::mlir_compile(qasm_code, "test_kernel1", qcor::OutputType::LLVMIR, false); std::cout << "LLVM:\n" << llvm << "\n"; // Get the main kernel section only llvm = llvm.substr(llvm.find("@__internal_mlir_test_kernel1")); const auto last = llvm.find_first_of("}"); llvm = llvm.substr(0, last + 1); std::cout << "LLVM:\n" << llvm << "\n"; // All CPhase gates in for loops have been unrolled and then merged. EXPECT_EQ(countSubstring(llvm, "__quantum__qis__cphase"), 4); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); auto ret = RUN_ALL_TESTS(); Loading