diff --git a/include/Dialect/QCS/IR/QCSOps.h b/include/Dialect/QCS/IR/QCSOps.h index 7113862cf..2b88cbe16 100644 --- a/include/Dialect/QCS/IR/QCSOps.h +++ b/include/Dialect/QCS/IR/QCSOps.h @@ -27,6 +27,8 @@ #include "Dialect/QCS/IR/QCSTypes.h" +#include "mlir/IR/SymbolTable.h" + #define GET_OP_CLASSES #include "Dialect/QCS/IR/QCSOps.h.inc" diff --git a/include/Dialect/QCS/IR/QCSOps.td b/include/Dialect/QCS/IR/QCSOps.td index fd3cbf3b9..a7a180e8d 100644 --- a/include/Dialect/QCS/IR/QCSOps.td +++ b/include/Dialect/QCS/IR/QCSOps.td @@ -31,6 +31,7 @@ include "Dialect/QUIR/IR/QUIRTypeConstraints.td" include "Dialect/QCS/IR/QCSBase.td" include "mlir/Dialect/LLVMIR/LLVMOpBase.td" +include "mlir/IR/SymbolInterfaces.td" // Define a side effect that identifies an operation as not dead while not // interfering with memory operations (e.g., allows store-forwarding across @@ -225,4 +226,73 @@ def QCS_ShotInitOp : QCS_Op<"shot_init", [IsolatedFromAbove]> { }]; } +def QCS_DeclareParameterOp : QCS_Op<"declare_parameter", [Symbol]> { + let summary = "system input parameter subject to post compilation updates"; + let description = [{ + The `qcs.declare_parameter` operation adds a symbol defining an input parameter + which may be modified after compilation before/during program invocation. + The value of the input parameter + may be obtained using the qcs.use_input_parameter operation. + + Example: + + ``` + // quir.angle input parameter + qcs.declare_parameter "theta" : !quir.angle<64> = 3.14159 + ``` + }]; + + let arguments = (ins + SymbolNameAttr:$sym_name, + TypeAttr:$type, + OptionalAttr:$initial_value + ); + + let results = (outs); + + let assemblyFormat = [{ + attr-dict $sym_name `:` $type (`=` $initial_value^)? + }]; + + let builders = [ + OpBuilder<(ins "::llvm::StringRef":$sym_name, "::mlir::TypeAttr":$type), [{ + $_state.addAttribute("sym_name", $_builder.getStringAttr(sym_name)); + $_state.addAttribute("type", type); + }]>, + OpBuilder<(ins "::llvm::StringRef":$sym_name, "::mlir::TypeAttr":$type, "Attribute":$value), [{ + $_state.addAttribute("sym_name", $_builder.getStringAttr(sym_name)); + $_state.addAttribute("type", type); + $_state.addAttribute("initial_value", value); + }]>, + ]; +} + +def QCS_ParameterLoadOp : QCS_Op<"parameter_load", + [DeclareOpInterfaceMethods]> { + let summary = "Use the current value of a parameter"; + let description = [{ + The operation `qcs.parameter_load` returns the current value of the + classical parameter with the given name. + + Example: + + ```mlir + %2 = qcs.parameter_load "a" : !quir.angle<64> + ``` + }]; + + let arguments = (ins + FlatSymbolRefAttr:$parameter_name + ); + + let results = (outs AnyClassical:$res); + + let assemblyFormat = [{ + $parameter_name `:` type($res) attr-dict + }]; + // op is verified by its traits + let verifier = ?; +} + + #endif // QCS_OPS diff --git a/include/Dialect/QUIR/IR/QUIROps.td b/include/Dialect/QUIR/IR/QUIROps.td index 632ce6fd1..a0dabaa54 100644 --- a/include/Dialect/QUIR/IR/QUIROps.td +++ b/include/Dialect/QUIR/IR/QUIROps.td @@ -609,6 +609,13 @@ def QUIR_CircuitOp : QUIR_Op<"circuit", [ CArg<"ArrayRef", "{}">:$argAttrs )>]; let extraClassDeclaration = [{ + static CircuitOp create(Location location, StringRef name, FunctionType type, + ArrayRef attrs = {}); + static CircuitOp create(Location location, StringRef name, FunctionType type, + Operation::dialect_attr_range attrs); + static CircuitOp create(Location location, StringRef name, FunctionType type, + ArrayRef attrs, + ArrayRef argAttrs); /// Create a deep copy of this circuit and all of its blocks, remapping any /// operands that use values outside of the circuit using the map that is diff --git a/include/Frontend/OpenQASM3/QUIRGenQASM3Visitor.h b/include/Frontend/OpenQASM3/QUIRGenQASM3Visitor.h index 64f8991f1..5ab5c7d92 100644 --- a/include/Frontend/OpenQASM3/QUIRGenQASM3Visitor.h +++ b/include/Frontend/OpenQASM3/QUIRGenQASM3Visitor.h @@ -35,11 +35,16 @@ class QUIRGenQASM3Visitor : public BaseQASM3Visitor { // References to MLIR single static assignment Values // (TODO needs to be refactored) std::unordered_map ssaValues; + std::vector ssaOtherValues; mlir::OpBuilder builder; mlir::OpBuilder topLevelBuilder; + mlir::OpBuilder circuitParentBuilder; mlir::ModuleOp &newModule; + mlir::quir::CircuitOp currentCircuitOp; std::string filename; - bool hasFailed = false; + bool hasFailed{false}; + bool buildingInCircuit{false}; + uint circuitCount{0}; mlir::Location getLocation(const QASM::ASTBase *); bool assign(mlir::Value &, const std::string &); @@ -85,15 +90,21 @@ class QUIRGenQASM3Visitor : public BaseQASM3Visitor { QUIRGenQASM3Visitor(QASM::ASTStatementList *sList, mlir::OpBuilder b, mlir::ModuleOp &newModule, std::string f) : BaseQASM3Visitor(sList), builder(b), topLevelBuilder(b), - newModule(newModule), filename(std::move(f)), varHandler(builder) {} + circuitParentBuilder(b), newModule(newModule), filename(std::move(f)), + varHandler(builder) {} QUIRGenQASM3Visitor(mlir::OpBuilder b, mlir::ModuleOp &newModule, std::string filename) - : builder(b), topLevelBuilder(b), newModule(newModule), - filename(std::move(filename)), varHandler(builder) {} + : builder(b), topLevelBuilder(b), circuitParentBuilder(b), + newModule(newModule), filename(std::move(filename)), + varHandler(builder) {} void initialize(uint numShots, const std::string &shotDelay); + void startCircuit(mlir::Location location); + void finishCircuit(); + void switchCircuit(bool buildInCircuit, mlir::Location location); + void setInputFile(std::string); mlir::LogicalResult walkAST(); diff --git a/include/Frontend/OpenQASM3/QUIRVariableBuilder.h b/include/Frontend/OpenQASM3/QUIRVariableBuilder.h index 1ccc4fa07..3d8bbbbf5 100644 --- a/include/Frontend/OpenQASM3/QUIRVariableBuilder.h +++ b/include/Frontend/OpenQASM3/QUIRVariableBuilder.h @@ -39,7 +39,8 @@ namespace qssc::frontend::openqasm3 { class QUIRVariableBuilder { public: - QUIRVariableBuilder(mlir::OpBuilder &builder) : builder(builder) {} + QUIRVariableBuilder(mlir::OpBuilder &builder) + : builder(builder), classicalBuilder(builder) {} /// Generate code for declaring a variable (at the builder's current insertion /// point). @@ -57,6 +58,19 @@ class QUIRVariableBuilder { bool isInputVariable = false, bool isOutputVariable = false); + /// Generate code for declaring a input parameter (at the builder's current + /// insertion point). + /// + /// @param location source location related to the generated code. + /// @param variableName name of the variable. (_parameter will be added) + /// @param type type of the variable. + void generateParameterDeclaration(mlir::Location location, + llvm::StringRef variableName, + mlir::Type type, mlir::Value assignedValue); + + mlir::Value generateParameterLoad(mlir::Location location, + llvm::StringRef variableName); + /// Generate code for declaring an array (at the builder's current insertion /// point). /// @@ -201,9 +215,34 @@ class QUIRVariableBuilder { mlir::Type resolveQUIRVariableType(const QASM::ASTResultNode *node); + void setClassicalBuilder(mlir::OpBuilder b) { + classicalBuilder = b; + useClassicalBuilder = true; + } + void disableClassicalBuilder() { useClassicalBuilder = false; } + private: + // default builder - reference from QUIRGenQASM3Vistor class mlir::OpBuilder &builder; + // classical builder - used by QUIRGenQASM3Vistor class when + // building inside a quir.circuit operation. + // + // the classical builder is used to insert classical operations such as a + // oq3.variable_load operation which are added to support a real time + // operation such as a quir.call_gate. The classical builder maintains the + // insertion point for the supporting classical operations which should be + // inserted at the same scope as the `quir.call_circuit` corresponding to the + // currently being inserted `quir.circuit` + // + // see also: QUIRGenQASM3Visitor::switchCircuit + mlir::OpBuilder classicalBuilder; + bool useClassicalBuilder{false}; + + mlir::OpBuilder getClassicalBuilder() { + return (useClassicalBuilder) ? classicalBuilder : builder; + } + std::unordered_map variables; std::unordered_map lastDeclaration; diff --git a/lib/Dialect/QCS/IR/QCSOps.cpp b/lib/Dialect/QCS/IR/QCSOps.cpp index 6b2eb8161..69d6ebdc5 100644 --- a/lib/Dialect/QCS/IR/QCSOps.cpp +++ b/lib/Dialect/QCS/IR/QCSOps.cpp @@ -23,9 +23,54 @@ #include "Dialect/QCS/IR/QCSTypes.h" #include "mlir/Dialect/StandardOps/IR/Ops.h" +#include +#include using namespace mlir; using namespace mlir::qcs; #define GET_OP_CLASSES #include "Dialect/QCS/IR/QCSOps.cpp.inc" + +static LogicalResult +verifyQCSParameterOpSymbolUses(SymbolTableCollection &symbolTable, + mlir::Operation *op, + bool operandMustMatchSymbolType = false) { + assert(op); + + // Check that op has attribute variable_name + auto paramRefAttr = op->getAttrOfType("parameter_name"); + if (!paramRefAttr) + return op->emitOpError( + "requires a symbol reference attribute 'parameter_name'"); + + // Check that symbol reference resolves to a parameter declaration + auto declOp = + symbolTable.lookupNearestSymbolFrom(op, paramRefAttr); + if (!declOp) + return op->emitOpError() << "no valid reference to a parameter '" + << paramRefAttr.getValue() << "'"; + + assert(op->getNumResults() <= 1 && "assume none or single result"); + + // Check that type of variables matches result type of this Op + if (op->getNumResults() == 1) { + if (op->getResult(0).getType() != declOp.type()) + return op->emitOpError( + "type mismatch between variable declaration and variable use"); + } + + if (op->getNumOperands() > 0 && operandMustMatchSymbolType) { + assert(op->getNumOperands() == 1 && + "type check only supported for a single operand"); + if (op->getOperand(0).getType() != declOp.type()) + return op->emitOpError( + "type mismatch between variable declaration and variable assignment"); + } + return success(); +} + +LogicalResult +ParameterLoadOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + return verifyQCSParameterOpSymbolUses(symbolTable, getOperation(), true); +} diff --git a/lib/Dialect/QUIR/IR/QUIROps.cpp b/lib/Dialect/QUIR/IR/QUIROps.cpp index edb5915a1..76fc4e04f 100644 --- a/lib/Dialect/QUIR/IR/QUIROps.cpp +++ b/lib/Dialect/QUIR/IR/QUIROps.cpp @@ -377,6 +377,43 @@ static LogicalResult verify(CircuitOp op) { return success(); } +CircuitOp CircuitOp::create(Location location, StringRef name, + FunctionType type, ArrayRef attrs) { + OpBuilder builder(location->getContext()); + OperationState state(location, getOperationName()); + CircuitOp::build(builder, state, name, type, attrs); + return cast(Operation::create(state)); +} +CircuitOp CircuitOp::create(Location location, StringRef name, + FunctionType type, + Operation::dialect_attr_range attrs) { + SmallVector attrRef(attrs); + return create(location, name, type, llvm::makeArrayRef(attrRef)); +} +CircuitOp CircuitOp::create(Location location, StringRef name, + FunctionType type, ArrayRef attrs, + ArrayRef argAttrs) { + CircuitOp circ = create(location, name, type, attrs); + circ.setAllArgAttrs(argAttrs); + return circ; +} + +void CircuitOp::build(OpBuilder &builder, OperationState &state, StringRef name, + FunctionType type, ArrayRef attrs, + ArrayRef argAttrs) { + state.addAttribute(SymbolTable::getSymbolAttrName(), + builder.getStringAttr(name)); + state.addAttribute(getTypeAttrName(), TypeAttr::get(type)); + state.attributes.append(attrs.begin(), attrs.end()); + state.addRegion(); + + if (argAttrs.empty()) + return; + assert(type.getNumInputs() == argAttrs.size()); + function_interface_impl::addArgAndResultAttrs(builder, state, argAttrs, + /*resultAttrs=*/llvm::None); +} + /// Clone the internal blocks and attributes from this circuit to the /// destination circuit. void CircuitOp::cloneInto(CircuitOp dest, BlockAndValueMapping &mapper) { diff --git a/lib/Frontend/OpenQASM3/OpenQASM3Frontend.cpp b/lib/Frontend/OpenQASM3/OpenQASM3Frontend.cpp index a1536a2ee..84ddcfad2 100644 --- a/lib/Frontend/OpenQASM3/OpenQASM3Frontend.cpp +++ b/lib/Frontend/OpenQASM3/OpenQASM3Frontend.cpp @@ -225,6 +225,8 @@ llvm::Error qssc::frontend::openqasm3::parse( if (failed(visitor.walkAST())) return llvm::createStringError(llvm::inconvertibleErrorCode(), "Failed to emit QUIR"); + // make sure to finish the in progress quir.circuit + visitor.finishCircuit(); if (mlir::failed(mlir::verify(newModule))) { newModule.dump(); diff --git a/lib/Frontend/OpenQASM3/QUIRGenQASM3Visitor.cpp b/lib/Frontend/OpenQASM3/QUIRGenQASM3Visitor.cpp index 70de67170..5e1e92afd 100644 --- a/lib/Frontend/OpenQASM3/QUIRGenQASM3Visitor.cpp +++ b/lib/Frontend/OpenQASM3/QUIRGenQASM3Visitor.cpp @@ -40,12 +40,15 @@ #include "Dialect/OQ3/IR/OQ3Ops.h" #include "Dialect/QCS/IR/QCSAttributes.h" #include "Dialect/QCS/IR/QCSOps.h" +#include "Dialect/QUIR/IR/QUIROps.h" +#include "Dialect/QUIR/IR/QUIRTypes.h" #include #include #include +#include #include #include @@ -76,6 +79,12 @@ namespace qssc::frontend::openqasm3 { using ExpressionValueType = mlir::Value; +// temporary feature flags to be used during development of parameters support +static llvm::cl::opt + enableParameters("enable-parameters", + llvm::cl::desc("enable qasm3 input parameters"), + llvm::cl::init(false)); + auto QUIRGenQASM3Visitor::getLocation(const ASTBase *node) -> Location { return mlir::FileLineColLoc::get(builder.getContext(), filename, node->GetLineNo(), node->GetColNo()); @@ -216,6 +225,7 @@ void QUIRGenQASM3Visitor::initialize(uint numShots, // Set the builder to add circuit operations inside the for loop builder.setInsertionPointAfter(shotInit); + circuitParentBuilder = builder; } void QUIRGenQASM3Visitor::setInputFile(std::string fName) { @@ -238,6 +248,8 @@ QUIRGenQASM3Visitor::reportError(ASTBase const *location, } void QUIRGenQASM3Visitor::visit(const ASTForStatementNode *node) { + switchCircuit(false, getLocation(node)); + const ASTForLoopNode *loop = node->GetLoop(); if (loop->GetIVMethod() == ASTForLoopNode::IVMethod::IVDiscrete) { @@ -304,6 +316,8 @@ void QUIRGenQASM3Visitor::visit(const ASTForLoopNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTIfStatementNode *node) { + switchCircuit(false, getLocation(node)); + // Checking to see if the IfStatementNode has an else part or not bool hasElse = node->HasElse(); @@ -344,6 +358,10 @@ void QUIRGenQASM3Visitor::visit(const ASTIfStatementNode *node) { // Reset the OpBuilder and SSA values now that we've processed the 'if' // statement + + if (buildingInCircuit) + finishCircuit(); + builder = prevBuilder; std::swap(ssaValues, ifSsaValues); @@ -368,6 +386,10 @@ void QUIRGenQASM3Visitor::visit(const ASTIfStatementNode *node) { BaseQASM3Visitor::visit(opList); // Reset the OpBuilder and SSA values now that we've processed the 'else' // statement + + if (buildingInCircuit) + finishCircuit(); + builder = elseBuilder; std::swap(ssaValues, elseSsaValues); } @@ -378,6 +400,8 @@ void QUIRGenQASM3Visitor::visit(const ASTElseStatementNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTSwitchStatementNode *node) { + switchCircuit(false, getLocation(node)); + // Getting the number of cases we have unsigned caseSize = node->GetNumCaseStatements(); Location loc = getLocation(node); @@ -456,6 +480,8 @@ void QUIRGenQASM3Visitor::visit(const ASTSwitchStatementNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTWhileStatementNode *node) { + switchCircuit(false, getLocation(node)); + const ASTWhileLoopNode *loop = node->GetLoop(); Location loc = getLocation(node); @@ -503,6 +529,8 @@ void QUIRGenQASM3Visitor::visit(const ASTFunctionDefinitionNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTFunctionCallNode *node) { + switchCircuit(false, getLocation(node)); + std::vector operands; for (const auto *expr : *node) operands.push_back(visitAndGetExpressionValue( @@ -525,6 +553,8 @@ void QUIRGenQASM3Visitor::visit(const ASTFunctionCallNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTGateDeclarationNode *node) { + switchCircuit(false, getLocation(node)); + const ASTGateNode *gateNode = node->GetGateNode(); const size_t numQubits = gateNode->QubitsSize(); @@ -571,6 +601,10 @@ void QUIRGenQASM3Visitor::visit(const ASTGateDeclarationNode *node) { const ASTGateQOpList &opList = gateNode->GetOpList(); for (ASTGateQOpNode *i : opList) BaseQASM3Visitor::visit(i); + + if (buildingInCircuit) + finishCircuit(); + builder.create(getLocation(node)); // Restore SSA Values and OpBuilder as we exit the function @@ -593,6 +627,8 @@ static const std::string &resolveQCParam(const ASTGateNode *gateNode, } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTGateNode *node) { + switchCircuit(true, getLocation(node)); + const size_t numQubits = node->QubitsSize(); const size_t numParams = node->ParamsSize(); const size_t numQCParams = node->GetNumQCParams(); @@ -614,9 +650,10 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTGateNode *node) { // parameter and thus exists in the ssaValues map. If it fails then this // must be a normal angle variable use if (!assign(pos, param->GetGateParamName())) { - if (const auto *const ident = param->GetValueIdentifier()) + if (const auto *const ident = param->GetValueIdentifier()) { pos = varHandler.generateVariableUse(getLocation(node), ident); - else + ssaOtherValues.push_back(pos); + } else reportError(node, mlir::DiagnosticSeverity::Error) << "Unnamed expressions not supported by QUIRGen yet, assign to " "an identifier"; @@ -640,6 +677,8 @@ void QUIRGenQASM3Visitor::visit(const ASTHGateOpNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTUGateOpNode *node) { + switchCircuit(false, getLocation(node)); + const ASTGateNode *gateNode = node->GetGateNode(); const size_t numParams = gateNode->ParamsSize(); constexpr size_t fixedNumParams = 3; @@ -684,6 +723,8 @@ void QUIRGenQASM3Visitor::visit(const ASTUGateOpNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTCXGateOpNode *node) { + switchCircuit(true, getLocation(node)); + const ASTGateNode *gateNode = node->GetGateNode(); assert(gateNode->GetNumQCParams() == 2 && "expect 2 qubit parameters."); @@ -695,12 +736,14 @@ void QUIRGenQASM3Visitor::visit(const ASTCXGateOpNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTResetNode *node) { + switchCircuit(true, getLocation(node)); Value qubitRef = getCurrentValue(node->GetTarget()->GetName()); builder.create(getLocation(node), qubitRef); } mlir::Value QUIRGenQASM3Visitor::createMeasurement(const ASTMeasureNode *node, bool emitAssignment) { + switchCircuit(true, getLocation(node)); unsigned targetSize = node->GetTargetSize(); unsigned resultSize = node->GetResultSize(); // This means that the target/result wasn't an array, but a single qubit/bit. @@ -760,6 +803,7 @@ void QUIRGenQASM3Visitor::visit(const ASTMeasureNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTDelayStatementNode *node) { + switchCircuit(true, getLocation(node)); const ASTDelayNode *delayNode = node->GetDelay(); switch (delayNode->GetDelayType()) { @@ -818,6 +862,7 @@ void QUIRGenQASM3Visitor::visit(const ASTDelayStatementNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTBarrierNode *node) { + switchCircuit(true, getLocation(node)); const ASTIdentifierList &idList = node->GetOperandList(); const size_t numQubits = idList.Size(); std::vector args(numQubits); @@ -831,6 +876,7 @@ void QUIRGenQASM3Visitor::visit(const ASTBarrierNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTDeclarationNode *node) { + switchCircuit(false, getLocation(node)); const ASTIdentifierNode *idNode = node->GetIdentifier(); const mlir::Location loc = getLocation(node); @@ -853,7 +899,17 @@ void QUIRGenQASM3Visitor::visit(const ASTDeclarationNode *node) { // generate variable assignment so that they are reinitialized on every // shot. - varHandler.generateVariableAssignment(loc, idNode->GetName(), val); + + if (enableParameters && + node->GetModifierType() == QASM::ASTTypeInputModifier) { + varHandler.generateParameterDeclaration(loc, idNode->GetMangledName(), + variableType, val); + auto load = + varHandler.generateParameterLoad(loc, idNode->GetMangledName()); + varHandler.generateVariableAssignment(loc, idNode->GetName(), load); + } else + varHandler.generateVariableAssignment(loc, idNode->GetName(), val); + return; } @@ -875,6 +931,7 @@ void QUIRGenQASM3Visitor::visit(const ASTKernelDeclarationNode *node) { } void QUIRGenQASM3Visitor::visit(const ASTKernelNode *node) { + switchCircuit(false, getLocation(node)); const auto ¶ms = node->GetParameters(); const size_t numParams = params.size(); llvm::SmallVector inputs(numParams); @@ -925,6 +982,7 @@ Type QUIRGenQASM3Visitor::getQUIRTypeFromDeclaration( ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTQubitContainerNode *node) { + switchCircuit(false, getLocation(node)); const std::string &qId = node->GetName(); int id = stoi(node->GetIdentifier()->GetQubitMnemonic()); @@ -944,6 +1002,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTQubitNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTCBitNode *node) { + switchCircuit(false, getLocation(node)); LLVM_DEBUG(llvm::dbgs() << "ASTCBitNode \"" << node->AsString() << "\" size " << node->Size() << " strlen " << node->AsString().size() << "\n"); @@ -977,7 +1036,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTCBitNode *node) { measurement); } - // the node's string respresentation may be shorter or longer than the + // the node's string representation may be shorter or longer than the // classical bit expression, so truncate to trailing (least-significant bits) // or take full string auto stringRepr = llvm::StringRef(node->AsString()).take_back(node->Size()); @@ -996,6 +1055,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTCBitNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTDurationNode *node) { + switchCircuit(false, getLocation(node)); // TODO this node may refer to an identifier, not just the encoded value. Fix // when replacing the use of ssaValues. assert((ssaValues.find(node->GetName()) == ssaValues.end()) && @@ -1019,6 +1079,8 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTStretchNode *node) { // when replacing the use of ssaValues. // TODO: handling of stretch is broken. + switchCircuit(false, getLocation(node)); + const Value stretchRef = builder.create( getLocation(node), builder.getType()); ssaValues[node->GetName()] = stretchRef; @@ -1027,6 +1089,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTStretchNode *node) { ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTIdentifierRefNode *node) { + switchCircuit(false, getLocation(node)); unsigned index = node->IsIndexed() ? node->GetIndex() : 0; std::string const &variableName = node->GetIdentifier()->GetName(); @@ -1062,6 +1125,7 @@ QUIRGenQASM3Visitor::visit_(const ASTIdentifierRefNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTIdentifierNode *node) { + switchCircuit(false, getLocation(node)); llvm::StringRef variableName = node->GetName(); mlir::Location location = getLocation(node); @@ -1179,6 +1243,7 @@ QUIRGenQASM3Visitor::handleAssign(const ASTBinaryOpNode *node) { mlir::Value QUIRGenQASM3Visitor::visitAndGetExpressionValue(const ASTExpressionNode *node) { + switchCircuit(false, getLocation(node)); expression.reset(); BaseQASM3Visitor::visit(node); @@ -1188,11 +1253,12 @@ QUIRGenQASM3Visitor::visitAndGetExpressionValue(const ASTExpressionNode *node) { llvm::errs() << getLocation(node) << "\n"; llvm_unreachable("no expression returned by visitor!"); } - + ssaOtherValues.push_back((expression.getValue())); return expression.getValue(); } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTBinaryOpNode *node) { + switchCircuit(false, getLocation(node)); // some op types are handled separately switch (node->GetOpType()) { @@ -1338,6 +1404,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTBinaryOpNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTUnaryOpNode *node) { + switchCircuit(true, getLocation(node)); const ASTOperatorNode *operatorNode = nullptr; @@ -1408,6 +1475,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTUnaryOpNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTIntNode *node) { + switchCircuit(false, getLocation(node)); assert(node->GetIdentifier()); if (node->GetIdentifier()->HasSymbolTableEntry() && @@ -1429,6 +1497,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTIntNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTMPIntegerNode *node) { + switchCircuit(false, getLocation(node)); assert(node->GetIdentifier()); if (node->GetIdentifier()->HasSymbolTableEntry() && @@ -1452,6 +1521,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTMPIntegerNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTFloatNode *node) { + switchCircuit(false, getLocation(node)); // TODO this node may refer to an identifier, not just the encoded value. Fix // when replacing the use of ssaValues. assert(!varHandler.tracksVariable(node->GetName()) && @@ -1493,6 +1563,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTFloatNode *node) { ExpressionValueType QUIRGenQASM3Visitor::getValueFromLiteral(const ASTMPDecimalNode *node) { + switchCircuit(false, getLocation(node)); const unsigned bits = node->GetIdentifier()->GetBits(); double long value = 0.0; if (node->IsNumber()) @@ -1522,6 +1593,7 @@ QUIRGenQASM3Visitor::getValueFromLiteral(const ASTMPDecimalNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTMPDecimalNode *node) { + switchCircuit(false, getLocation(node)); // TODO this node may refer to an identifier, not just the encoded value. Fix // when replacing the use of ssaValues. assert((ssaValues.find(node->GetName()) == ssaValues.end()) && @@ -1532,6 +1604,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTMPDecimalNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTMPComplexNode *node) { + switchCircuit(false, getLocation(node)); // TODO this node may refer to an identifier, not just the encoded value. Fix // when replacing the use of ssaValues. std::string name = getExpressionName(node); @@ -1548,6 +1621,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTMPComplexNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTAngleNode *node) { + switchCircuit(false, getLocation(node)); assert(node->GetIdentifier()); if (node->GetIdentifier()->HasSymbolTableEntry() && @@ -1572,6 +1646,7 @@ ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTAngleNode *node) { } ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTBoolNode *node) { + switchCircuit(false, getLocation(node)); assert(node->GetIdentifier()); if (node->GetIdentifier()->HasSymbolTableEntry() && @@ -1607,6 +1682,7 @@ Type QUIRGenQASM3Visitor::getCastDestinationType( ExpressionValueType QUIRGenQASM3Visitor::visit_(const ASTCastExpressionNode *node) { + switchCircuit(false, getLocation(node)); // visiting the child expression is deferred to BaseQASM3Visitor expression.reset(); BaseQASM3Visitor::visit(node); @@ -1627,4 +1703,181 @@ mlir::Value QUIRGenQASM3Visitor::createVoidValue(QASM::ASTBase const *node) { return createVoidValue(getLocation(node)); } +void QUIRGenQASM3Visitor::startCircuit(mlir::Location location) { + + if (!enableParameters) + return; + + currentCircuitOp = topLevelBuilder.create( + location, "circuit_" + std::to_string(circuitCount++), + topLevelBuilder.getFunctionType( + /*inputs=*/ArrayRef(), + /*results=*/ArrayRef())); + auto *block = currentCircuitOp.addEntryBlock(); + + OpBuilder circuitBuilder(currentCircuitOp.getBody()); + circuitBuilder.create(location, ValueRange({})); + circuitBuilder.setInsertionPointToStart(block); + + // set builders so that classical operations are inserted into + // the shot loop rather than into the quir.circuit + varHandler.setClassicalBuilder(builder); + circuitParentBuilder = builder; + builder = circuitBuilder; + + buildingInCircuit = true; +} + +void QUIRGenQASM3Visitor::finishCircuit() { + + if (!enableParameters || !buildingInCircuit) + return; + + // rewrite the circuit and add a call circuit ops to fix region and usage + // + // a few things need to be done: + // 1: there are classical operations which define ssa values which will be + // used inside of quir.circuits. + // these ssa need to be added to the argument list of the circuit, + // the operand list of the call_circuit and the ssa uses need to be + // replaced with the arguments to the circuit. + // 2: there are measurement results inside the quir.circuit which need to + // be returned from the circuit and their uses replaced with the results + // of the call_circuit + + llvm::SmallVector inputTypes; + llvm::SmallVector inputValues; + llvm::SmallVector outputTypes; + llvm::SmallVector outputValues; + + // check all of the ssa saved values to determine if they + // are used inside the circuit op and insert an argument + // in the quir.circuit + + auto insertArgumentsAndReplaceUse = [&](Value value) { + for (auto *user : value.getUsers()) { + if (currentCircuitOp->isAncestor(user)) { + auto arg = currentCircuitOp.body().front().addArgument(value.getType(), + value.getLoc()); + value.replaceUsesWithIf(arg, [&](OpOperand &operand) { + return (operand.getOwner()->getParentOp() == currentCircuitOp); + }); + inputTypes.push_back(value.getType()); + inputValues.push_back(value); + break; + } + } + }; + + for (auto const &ssa : ssaValues) + insertArgumentsAndReplaceUse(ssa.second); + + // do the same thing for ssaOtherValues (new class of ssa values tracked for + // parameters) + for (auto const &ssa : ssaOtherValues) + insertArgumentsAndReplaceUse(ssa); + + // look for measurements inside of this circuit op and collect a list + // of outputs + + currentCircuitOp.walk([&](MeasureOp measOp) { + outputTypes.append(measOp.result_type_begin(), measOp.result_type_end()); + outputValues.append(measOp.result_begin(), measOp.result_end()); + }); + + // find the return op and set the outputs + // an empty return was added when the circuit was created so we can + // just insert the outputValues + currentCircuitOp.walk([&](mlir::quir::ReturnOp returnOp) { + returnOp->insertOperands(0, ValueRange(outputValues)); + }); + + // change the input / output types for the quir.circuit + currentCircuitOp.setType(topLevelBuilder.getFunctionType( + /*inputs=*/ArrayRef(inputTypes), + /*results=*/ArrayRef(outputTypes))); + + auto newCallOp = circuitParentBuilder.create( + currentCircuitOp->getLoc(), currentCircuitOp.getName(), + TypeRange(outputTypes), ValueRange(inputValues)); + + // replace the uses of the measurements outside of the circuit + // with the results of the call_circuit + + for (auto const &output : llvm::enumerate(outputValues)) { + Value value = output.value(); + auto replacementOp = newCallOp->getResult(output.index()); + + value.replaceUsesWithIf(replacementOp, [&](OpOperand &operand) { + return !isa(operand.getOwner()); + }); + } + + // move uses of the results after the call_circuit + for (auto const &output : newCallOp.getResults()) + for (auto *user : output.getUsers()) + user->moveAfter(newCallOp); + + // restore varHandler builder and vistor builder to + // use shot loop + varHandler.disableClassicalBuilder(); + builder = circuitParentBuilder; + + buildingInCircuit = false; +} + +void QUIRGenQASM3Visitor::switchCircuit(bool buildInCircuit, + mlir::Location location) { + + // Switch the state of building inside a quir.circuit or not. + // + // This method is used to control the building of operations inside + // or outside of a quir.circuit based on the AST Node type. This method + // should be called from each overridden ::visit_ method. The method + // should use the buildInCircuit argument to indicate if that ASTNode Type + // should be placed inside of a quir.circuit or not. The location is the + // ASTNode's location in the QASM3 file. + // + // The buildInCircuit argument is used with the buildingInCircuit class + // instance variable to determine if a quir.circuit should be started or + // finished. This (partially) enables the appropriate grouping of operations. + // Additional support for the grouping of operations is enabled by the + // QUIRVariableBuilder classicalBuilder. + // + // There are four cases: + + // Operations are being built in a quir.circuit and the operations being added + // for the current AST Node should be placed in a circuit. Do nothing with + // regard to the circuit building (this will group operations inside the + // quir.circuit). + + if (buildingInCircuit && buildInCircuit) + return; + + // Operations are NOT being built in a quir.circuit and the operations being + // added for the current AST Node should not be placed in a circuit. + // Do nothing. + + if (!buildingInCircuit && !buildInCircuit) + return; + + // Operations are being built in a quir.circuit and the operations being added + // for the current AST Node should NOT be placed in a circuit. Finish the + // current circuit (add quir.call_circuit and set builder location to + // after then quir.call_circuit operation, at the current classical scope). + + if (buildingInCircuit && !buildInCircuit) { + finishCircuit(); + return; + } + + // Operations are NOT being built in a quir.circuit and the operations being + // added for the current AST Node should be placed in a circuit. Start a new + // circuit. Create a new quir.circuit and create new operations inside the + // circuit. + + if (!buildingInCircuit && buildInCircuit) + startCircuit(location); +} + } // namespace qssc::frontend::openqasm3 diff --git a/lib/Frontend/OpenQASM3/QUIRVariableBuilder.cpp b/lib/Frontend/OpenQASM3/QUIRVariableBuilder.cpp index 0be1fabc8..7922b828c 100644 --- a/lib/Frontend/OpenQASM3/QUIRVariableBuilder.cpp +++ b/lib/Frontend/OpenQASM3/QUIRVariableBuilder.cpp @@ -22,8 +22,10 @@ #include "Frontend/OpenQASM3/QUIRVariableBuilder.h" #include "Dialect/OQ3/IR/OQ3Ops.h" +#include "Dialect/QCS/IR/QCSOps.h" #include "Dialect/QUIR/IR/QUIROps.h" +#include "Dialect/QUIR/IR/QUIRTypes.h" #include "mlir/Dialect/StandardOps/IR/Ops.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" @@ -70,6 +72,39 @@ void QUIRVariableBuilder::generateVariableDeclaration( variables.emplace(variableName.str(), type); } +void QUIRVariableBuilder::generateParameterDeclaration( + mlir::Location location, llvm::StringRef variableName, mlir::Type type, + mlir::Value assignedValue) { + + mlir::OpBuilder::InsertionGuard g(builder); + auto *symbolTableOp = mlir::SymbolTable::getNearestSymbolTable( + builder.getInsertionBlock()->getParentOp()); + assert(symbolTableOp && + "require surrounding op with a symbol table (should be the Module)"); + auto surroundingModuleOp = mlir::dyn_cast(*symbolTableOp); + assert(surroundingModuleOp && "assume symbol table residing in module"); + builder.setInsertionPoint(&surroundingModuleOp.front()); + + // add qcs input parameter + auto constantOp = + mlir::dyn_cast(assignedValue.getDefiningOp()); + auto declareParameterOp = builder.create( + location, variableName.str(), mlir::TypeAttr::get(type), + constantOp.value()); + + declareParameterOp->moveBefore(lastDeclaration[surroundingModuleOp]); + lastDeclaration[surroundingModuleOp] = declareParameterOp; +} + +mlir::Value +QUIRVariableBuilder::generateParameterLoad(mlir::Location location, + llvm::StringRef variableName) { + + auto op = getClassicalBuilder().create( + location, builder.getType(64), variableName.str()); + return op; +} + void QUIRVariableBuilder::generateArrayVariableDeclaration( mlir::Location location, llvm::StringRef variableName, mlir::Type elementType, int64_t width) { @@ -86,8 +121,8 @@ void QUIRVariableBuilder::generateVariableAssignment( mlir::Location location, llvm::StringRef variableName, mlir::Value assignedValue) { - builder.create(location, variableName, - assignedValue); + getClassicalBuilder().create( + location, variableName, assignedValue); } void QUIRVariableBuilder::generateArrayVariableElementAssignment( @@ -116,10 +151,12 @@ void QUIRVariableBuilder::generateCBitSingleBitAssignment( location, mlir::SymbolRefAttr::get(builder.getStringAttr(variableName)), cbitWithInsertedBit); #else - builder.create( - location, mlir::SymbolRefAttr::get(builder.getStringAttr(variableName)), - builder.getIndexAttr(bitPosition), builder.getIndexAttr(registerWidth), - assignedValue); + getClassicalBuilder().create( + location, + mlir::SymbolRefAttr::get( + getClassicalBuilder().getStringAttr(variableName)), + getClassicalBuilder().getIndexAttr(bitPosition), + getClassicalBuilder().getIndexAttr(registerWidth), assignedValue); #endif } @@ -127,8 +164,8 @@ mlir::Value QUIRVariableBuilder::generateVariableUse(mlir::Location location, llvm::StringRef variableName, mlir::Type variableType) { - return builder.create(location, variableType, - variableName); + return getClassicalBuilder().create( + location, variableType, variableName); } mlir::Value QUIRVariableBuilder::generateArrayVariableElementUse( diff --git a/test/Frontend/OpenQASM3/input-parameters-if.qasm b/test/Frontend/OpenQASM3/input-parameters-if.qasm new file mode 100644 index 000000000..7617dbfc5 --- /dev/null +++ b/test/Frontend/OpenQASM3/input-parameters-if.qasm @@ -0,0 +1,77 @@ +OPENQASM 3.0; +// RUN: qss-compiler -X=qasm --emit=mlir --enable-parameters %s | FileCheck %s + +// +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023. +// +// This code is licensed under the Apache License, Version 2.0 with LLVM +// Exceptions. You may obtain a copy of this license in the LICENSE.txt +// file in the root directory of this source tree. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + + +qubit $2; +qubit $3; + +bit is_excited; +bit other; +bit result; + +gate x q { } +gate rz(phi) q { } + +input angle theta = 3.141; +// CHECK: qcs.declare_parameter @_QX64_5thetaEE_ : !quir.angle<64> = #quir.angle<3.141000e+00 : !quir.angle<64>> + +x $2; +rz(theta) $2; +x $3; + +is_excited = measure $2; + + +// CHECK: quir.circuit @circuit_0(%arg0: !quir.qubit<1>, %arg1: !quir.qubit<1>, %arg2: !quir.angle<64>) -> i1 { +// CHECK-NEXT: quir.call_gate @x(%arg1) : (!quir.qubit<1>) -> () +// CHECK: %0 = quir.measure(%arg1) : (!quir.qubit<1>) -> i1 +// CHECK-NEXT: quir.return %0 : i1 +// CHECK: } + +// CHECK: quir.circuit @circuit_1(%arg0: !quir.qubit<1>) -> i1 { +// CHECK-NEXT: %0 = quir.measure(%arg0) : (!quir.qubit<1>) -> i1 +// CHECK-NEXT: quir.return %0 : i1 +// CHECK-NEXT: } + +// CHECK: func @main() -> i32 { +// CHECK: scf.for %arg0 = %c0 to %c1000 step %c1 { +// CHECK: [[QUBIT2:%.*]] = quir.declare_qubit {id = 2 : i32} : !quir.qubit<1> +// CHECK: [[QUBIT3:%.*]] = quir.declare_qubit {id = 3 : i32} : !quir.qubit<1> + +// CHECK: [[EXCITED:%.*]] = oq3.variable_load @is_excited : !quir.cbit<1> +// CHECK: [[CONST:%[0-9a-z_]+]] = arith.constant 1 : i32 +// CHECK: [[EXCITEDCAST:%[0-9]+]] = "oq3.cast"([[EXCITED]]) : (!quir.cbit<1>) -> i32 +// CHECK: [[COND0:%.*]] = arith.cmpi eq, [[EXCITEDCAST]], [[CONST]] : i32 +// CHECK: scf.if [[COND0]] { +if (is_excited == 1) { +// CHECK: [[MEASURE3:%.*]] = quir.call_circuit @circuit_1([[QUBIT3]]) : (!quir.qubit<1>) -> i1 +// CHECK: oq3.cbit_assign_bit @other<1> [0] : i1 = [[MEASURE3]] + other = measure $3; +// CHECK: [[OTHER:%.*]] = oq3.variable_load @other : !quir.cbit<1> +// CHECK: [[CONST:%[0-9a-z_]+]] = arith.constant 1 : i32 +// CHECK: [[OTHERCAST:%[0-9]+]] = "oq3.cast"([[OTHER]]) : (!quir.cbit<1>) -> i32 +// CHECK: [[COND1:%.*]] = arith.cmpi eq, [[OTHERCAST]], [[CONST]] : i32 +// CHECK: scf.if [[COND1]] { + if (other == 1){ +// CHECK: [[THETA:%.*]] = oq3.variable_load @theta : !quir.angle<64> +// CHECK: quir.call_circuit @circuit_2([[QUBIT2]], [[THETA]]) : (!quir.qubit<1>, !quir.angle<64>) -> () + x $2; + rz(theta) $2; + } +} +// CHECK: [[MEASURE2:%.*]] = quir.call_circuit @circuit_3([[QUBIT2]]) : (!quir.qubit<1>) -> i1 +// CHECK: oq3.cbit_assign_bit @result<1> [0] : i1 = [[MEASURE2]] +result = measure $2; diff --git a/test/Frontend/OpenQASM3/input-parameters-while.qasm b/test/Frontend/OpenQASM3/input-parameters-while.qasm new file mode 100644 index 000000000..8461fd9c2 --- /dev/null +++ b/test/Frontend/OpenQASM3/input-parameters-while.qasm @@ -0,0 +1,80 @@ +OPENQASM 3.0; +// RUN: qss-compiler -X=qasm --enable-parameters %s | FileCheck %s + +// +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023. +// +// This code is licensed under the Apache License, Version 2.0 with LLVM +// Exceptions. You may obtain a copy of this license in the LICENSE.txt +// file in the root directory of this source tree. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +gate h q { + U(1.57079632679, 0.0, 3.14159265359) q; +} + +gate rz(phi) q { } + +input angle theta = 3.141; +// CHECK: qcs.declare_parameter @_QX64_5thetaEE_ : !quir.angle<64> = #quir.angle<3.141000e+00 : !quir.angle<64>> + +qubit $0; +int n = 1; + +bit is_excited; + +// verify gate function has not changed +// CHECK: func @h(%arg0: !quir.qubit<1>) { +// CHECK: quir.builtin_U %arg0, %angle, %angle_0, %angle_1 : !quir.qubit<1>, !quir.angle<64>, !quir.angle<64>, !quir.angle<64> +// CHECK-NEXT: return +// CHECK-NEXT: } + +// CHECK: quir.circuit @circuit_0(%arg0: !quir.qubit<1>) -> i1 { +// CHECK-NEXT: quir.call_gate @h(%arg0) : (!quir.qubit<1>) -> () +// CHECK: %0 = quir.measure(%arg0) : (!quir.qubit<1>) -> i1 +// CHECK-NEXT: quir.return %0 : i1 +// CHECK-NEXT: } + +// CHECK: quir.circuit @circuit_1(%arg0: !quir.qubit<1>, %arg1: !quir.angle<64>) { +// CHECK-NEXT: quir.call_gate @h(%arg0) : (!quir.qubit<1>) -> () +// CHECK: quir.call_gate @rz(%arg0, %arg1) : (!quir.qubit<1>, !quir.angle<64>) -> () +// CHECK: quir.return +// CHECK-NEXT: } + +// CHECK: func @main() -> i32 { +// CHECK: scf.for %arg0 = %c0 to %c1000 step %c1 { +// CHECK: [[QUBIT:%.*]] = quir.declare_qubit {id = 0 : i32} : !quir.qubit<1> +// CHECK: scf.while : () -> () { +// CHECK: [[N:%.*]] = oq3.variable_load @n : i32 +// CHECK: %c0_i32_0 = arith.constant 0 : i32 +// CHECK: [[COND:%.*]] = arith.cmpi ne, [[N]], %c0_i32_0 : i32 +// CHECK: scf.condition([[COND]]) +// CHECK: } do { +while (n != 0) { + h $0; + is_excited = measure $0; + // CHECK: [[MEASURE:%.*]] = quir.call_circuit @circuit_0([[QUBIT]]) : (!quir.qubit<1>) -> i1 + // CHECK: oq3.cbit_assign_bit @is_excited<1> [0] : i1 = [[MEASURE]] + // CHECK: [[EXCITED:%.*]] = oq3.variable_load @is_excited : !quir.cbit<1> + // CHECK: [[COND2:%.*]] = "oq3.cast"([[EXCITED]]) : (!quir.cbit<1>) -> i1 + + // CHECK: scf.if [[COND2]] { + if (is_excited) { + // CHECK: [[THETA:%.*]] = oq3.variable_load @theta : !quir.angle<64> + // CHECK: quir.call_circuit @circuit_1([[QUBIT]], [[THETA]]) : (!quir.qubit<1>, !quir.angle<64>) -> () + // CHECK: } + h $0; + rz(theta) $0; + } + // error: Binary operation ASTOpTypeSub not supported yet. + // n = n - 1; + // CHECK: %c0_i32_0 = arith.constant 0 : i32 + // CHECK: oq3.variable_assign @n : i32 = %c0_i32_0 + n = 0; // workaround for n = n - 1 + // CHECK: scf.yield +} diff --git a/test/Frontend/OpenQASM3/input-parameters.qasm b/test/Frontend/OpenQASM3/input-parameters.qasm new file mode 100644 index 000000000..738fdb1d4 --- /dev/null +++ b/test/Frontend/OpenQASM3/input-parameters.qasm @@ -0,0 +1,61 @@ +OPENQASM 3; +// RUN: qss-compiler -X=qasm --emit=mlir --enable-parameters %s | FileCheck %s + +// +// This code is part of Qiskit. +// +// (C) Copyright IBM 2023. +// +// This code is licensed under the Apache License, Version 2.0 with LLVM +// Exceptions. You may obtain a copy of this license in the LICENSE.txt +// file in the root directory of this source tree. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +// This test case validates that input and output modifiers for variables are +// parsed correctly and are reflected in generated QUIR. + +qubit $0; +qubit $2; + +gate sx q { } +gate rz(phi) q { } + +input angle theta = 3.141; +// CHECK: qcs.declare_parameter @_QX64_5thetaEE_ : !quir.angle<64> = #quir.angle<3.141000e+00 : !quir.angle<64>> + +reset $0; + +sx $0; +rz(theta) $0; +sx $0; + +bit b; + +b = measure $0; + +// CHECK: quir.circuit @circuit_0(%arg0: !quir.qubit<1>, %arg1: !quir.angle<64>) { +// CHECK-NEXT: quir.reset %arg0 : !quir.qubit<1> +// CHECK-NEXT: quir.call_gate @sx(%arg0) : (!quir.qubit<1>) -> () +// CHECK: quir.return +// CHECK-NEXT: } + +// CHECK: quir.circuit @circuit_1(%arg0: !quir.qubit<1>) -> i1 { +// CHECK-NEXT: %0 = quir.measure(%arg0) : (!quir.qubit<1>) -> i1 +// CHECK-NEXT: quir.return %0 : i1 +// CHECK-NEXT: } + +// CHECK: func @main() -> i32 { +// CHECK: scf.for %arg0 = %c0 to %c1000 step %c1 { +// CHECK: %0 = quir.declare_qubit {id = 0 : i32} : !quir.qubit<1> +// CHECK: %1 = quir.declare_qubit {id = 2 : i32} : !quir.qubit<1> + +// CHECK: %2 = qcs.parameter_load @_QX64_5thetaEE_ : !quir.angle<64> +// CHECK: oq3.variable_assign @theta : !quir.angle<64> = %2 +// CHECK-NOT: oq3.variable_assign @theta : !quir.angle<64> = %angle + +// CHECK: quir.call_circuit @circuit_0(%0, %3) : (!quir.qubit<1>, !quir.angle<64>) -> () +// CHECK: %5 = quir.call_circuit @circuit_1(%0) : (!quir.qubit<1>) -> i1 +// CHECK: oq3.cbit_assign_bit @b<1> [0] : i1 = %5