diff --git a/include/mqt-core/qasm3/DebugInfo.hpp b/include/mqt-core/qasm3/DebugInfo.hpp new file mode 100644 index 0000000000..81e420bb38 --- /dev/null +++ b/include/mqt-core/qasm3/DebugInfo.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include +#include + +namespace qasm3 { + +/// Source location information for error reporting and diagnostics. +struct DebugInfo { + size_t line; ///< 1-based line number. + size_t column; ///< 1-based column number. + std::string filename; ///< Source file name. + std::shared_ptr parent; ///< Enclosing location. + + DebugInfo(const size_t l, const size_t c, std::string file, + std::shared_ptr parentDebugInfo = nullptr) + : line(l), column(c), filename(std::move(file)), + parent(std::move(parentDebugInfo)) {} + + /// Returns a human-readable `"file:line:col"` string. + [[nodiscard]] std::string toString() const { + return filename + ":" + std::to_string(line) + ":" + std::to_string(column); + } +}; + +} // namespace qasm3 diff --git a/include/mqt-core/qasm3/Exception.hpp b/include/mqt-core/qasm3/Exception.hpp index 926b1f2f82..6a9c761d2b 100644 --- a/include/mqt-core/qasm3/Exception.hpp +++ b/include/mqt-core/qasm3/Exception.hpp @@ -10,7 +10,7 @@ #pragma once -#include "Statement.hpp" +#include "DebugInfo.hpp" #include #include diff --git a/include/mqt-core/qasm3/Statement.hpp b/include/mqt-core/qasm3/Statement.hpp index 258c7a7900..c03e25c3f8 100644 --- a/include/mqt-core/qasm3/Statement.hpp +++ b/include/mqt-core/qasm3/Statement.hpp @@ -10,6 +10,7 @@ #pragma once +#include "DebugInfo.hpp" // IWYU pragma: export #include "Statement_fwd.hpp" // IWYU pragma: export #include "Types_fwd.hpp" #include "ir/Permutation.hpp" @@ -31,22 +32,6 @@ enum ComparisonKind : std::uint8_t; namespace qasm3 { class InstVisitor; -struct DebugInfo { - size_t line; - size_t column; - std::string filename; - std::shared_ptr parent; - - DebugInfo(const size_t l, const size_t c, std::string file, - std::shared_ptr parentDebugInfo = nullptr) - : line(l), column(c), filename(std::move(std::move(file))), - parent(std::move(parentDebugInfo)) {} - - [[nodiscard]] std::string toString() const { - return filename + ":" + std::to_string(line) + ":" + std::to_string(column); - } -}; - // Expressions class Expression { public: diff --git a/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h b/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h index 165136a500..1e9cb95e46 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h +++ b/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h @@ -25,8 +25,9 @@ namespace qc { /** * @brief Translate an OpenQASM 3 program to a QC program. * - * @param sourceMgr Source manager containing the OpenQASM3 program. - * @param context MLIRContext to create the module in. + * @param sourceMgr Source manager containing the OpenQASM 3 program. + * @param context The MLIRContext to create the module in. + * @return A module containing the QC program. */ [[nodiscard]] OwningOpRef translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context); @@ -34,8 +35,9 @@ translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context); /** * @brief Translate an OpenQASM 3 program to a QC program. * - * @param source String containing the OpenQASM3 program. - * @param context MLIRContext to create the module in. + * @param source String containing the OpenQASM 3 program. + * @param context The MLIRContext to create the module in. + * @return A module containing the QC program. */ [[nodiscard]] OwningOpRef translateQASM3ToQC(StringRef source, MLIRContext* context); diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h new file mode 100644 index 0000000000..676a31c833 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include + +namespace llvm { +class SourceMgr; +} // namespace llvm + +namespace mlir { + +// Forward declarations +class MLIRContext; +class ModuleOp; + +namespace qc::detail { + +/** + * @brief Translate an OpenQASM 3 program to a QC program. + * + * @details + * Lexes, parses, and lowers @p sourceMgr's main buffer in a single pass. On any + * error, a diagnostic is emitted through @p context and a null module is + * returned. + * + * @param sourceMgr Source manager containing the OpenQASM 3 program. + * @param context The MLIRContext to create the module in. + * @return A module containing the QC program. + */ +[[nodiscard]] OwningOpRef +translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context); + +} // namespace qc::detail + +} // namespace mlir diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h new file mode 100644 index 0000000000..0b497effe8 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include +#include + +#include + +namespace mlir::qc::detail { + +/// The kind of a lexical token. +enum class TokenKind : uint8_t { + Eof, + Error, + + // Keywords + OpenQASM, + Include, + Const, + Qubit, + Qreg, + Bit, + CReg, + Gate, + Opaque, + Barrier, + Reset, + Measure, + If, + Else, + For, + While, + In, + Gphase, + Inv, + Pow, + Ctrl, + NegCtrl, + + // Types + Int, + Uint, + Bool, + Float, + Angle, + Duration, + True, + False, + + // Identifiers and literals + Identifier, + HardwareQubit, + StringLiteral, + IntegerLiteral, + FloatLiteral, + + // Punctuation + LParen, + RParen, + LBracket, + RBracket, + LBrace, + RBrace, + Comma, + Semicolon, + Colon, + Arrow, + At, + + // Operators + Equals, + Plus, + Minus, + Asterisk, + Slash, + Tilde, + ExclamationPoint, + AmpAmp, // `&&` + PipePipe, // `||` + CompoundAssign, // `+=`, `-=`, ... + + // Comparisons + EqualsEquals, + NotEquals, + Less, + LessEquals, + Greater, + GreaterEquals, +}; + +/// A human-readable name for @p kind, used in diagnostics. +[[nodiscard]] StringRef describe(TokenKind kind); + +/** + * @brief A single lexical token. + * + * @details + * Spellings are zero-copy views into the source buffer. Literals are + * pre-parsed. + */ +struct Token { + TokenKind kind = TokenKind::Eof; + SMLoc loc; + StringRef identifier; ///< For `Identifier` tokens. + StringRef stringValue; ///< For `StringLiteral` tokens. + int64_t intValue = 0; + double floatValue = 0.0; +}; + +/** + * @brief A zero-copy lexer over an OpenQASM 3 source buffer. + * + * @details + * The lexer holds pointers into the buffer and produces tokens on demand + * without allocating. Token locations are `SMLoc`s into the buffer, so they can + * be resolved to line/column via the owning `llvm::SourceMgr`. + */ +class Lexer { +public: + explicit Lexer(StringRef buffer) : cur(buffer.begin()), end(buffer.end()) {} + + /// Produce the next token, or an `Eof`/`Error` token at the end/on failure. + [[nodiscard]] Token next(); + +private: + [[nodiscard]] bool atEnd() const { return cur == end; } + void skipTrivia(); + [[nodiscard]] Token lexIdentifierOrKeyword(const char* start); + [[nodiscard]] Token lexNumber(const char* start); + [[nodiscard]] Token lexString(const char* start); + [[nodiscard]] Token lexHardwareQubit(const char* start); + + const char* cur; + const char* end; +}; + +} // namespace mlir::qc::detail diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h new file mode 100644 index 0000000000..dcd0379110 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h @@ -0,0 +1,1357 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace mlir::qc::detail { + +/** + * @defgroup ParseVocabulary Transient parse vocabulary + * @brief The vocabulary the parser hands to a sink. + * + * @details + * These types are cheap, trivially destructible, and (where they need to + * outlive a single statement, i.e. gate-definition bodies) allocated in a bump + * allocator. There is no persistent whole-program syntax tree: flat statements + * stream straight to the sink as they are recognized. + */ + +/** + * @ingroup ParseVocabulary + * @brief A (sub-)expression. + * + * @details + * Bump-allocated; children are borrowed pointers. + */ +struct Expr { + enum class Kind : uint8_t { Int, Float, Identifier, Neg, Add, Sub, Mul, Div }; + + SMLoc loc; + Kind kind = Kind::Int; + int64_t intValue = 0; + double floatValue = 0.0; + StringRef identifier; + const Expr* lhs = nullptr; + const Expr* rhs = nullptr; +}; + +/** + * @ingroup ParseVocabulary + * @brief A gate modifier: `inv @`, `pow(e) @`, `ctrl(e) @`, or `negctrl(e) @`. + */ +struct Modifier { + enum class Kind : uint8_t { Inv, Pow, Ctrl, NegCtrl }; + Kind kind = Kind::Inv; + const Expr* argument = nullptr; +}; + +/** + * @ingroup ParseVocabulary + * @brief A gate operand: a (possibly indexed) identifier, or a hardware qubit. + */ +struct Operand { + SMLoc loc; + StringRef identifier; + const Expr* index = nullptr; + std::optional hardwareQubit; +}; + +/// A (possibly indexed) classical reference (e.g., `c` or `c[0]`). +struct BitReference { + SMLoc loc; + StringRef identifier; + const Expr* index = nullptr; +}; + +/** + * @ingroup ParseVocabulary + * @brief A resolved gate call. + * + * @details + * Array members are borrowed for the duration of the sink call (top-level) or + * bump-allocated (gate-definition bodies). + */ +struct GateCall { + SMLoc loc; + StringRef identifier; + ArrayRef modifiers; + ArrayRef parameters; + ArrayRef operands; +}; + +/** + * @ingroup ParseVocabulary + * @brief A branch condition: a boolean-valued expression. + * + * @details + * Bump-allocated; children are borrowed pointers. Register comparisons (e.g., + * `c == 5`) are not supported yet. + */ +struct Condition { + enum class Kind : uint8_t { Measurement, Bit, Literal, Not, And, Or }; + + SMLoc loc; + Kind kind = Kind::Bit; + Operand operand; ///< For `Measurement`. + BitReference bit; ///< For `Bit`. + bool literalValue = false; ///< For `Literal`. + const Condition* lhs = nullptr; ///< For `Not`, `And`, and `Or`. + const Condition* rhs = nullptr; ///< For `And` and `Or`. +}; + +/** + * @ingroup ParseVocabulary + * @brief The kind of a declared classical variable. + * + * @details + * Used to select the grammar of an assignment's right-hand side (numeric vs. + * boolean). + */ +enum class ClassicalKind : uint8_t { Numeric, Boolean }; + +/** + * @ingroup ParseVocabulary + * @brief A numeric declaration type. + */ +enum class NumericType : uint8_t { Float, Int }; + +//===----------------------------------------------------------------------===// +// Sink concept +//===----------------------------------------------------------------------===// + +/** + * @brief The interface a `Parser` drives to lower parse events to a target. + * + * @details + * A sink consumes the events produced by `Parser` and lowers them to a target + * representation. The parser is templated over the sink, so dispatch is fully + * static. Diagnostics are routed through `error`; every event returns a + * `LogicalResult` (or, for `if`, an opaque scope handle). + * + * Control flow uses continuations: the sink opens the target region and calls + * back into the parser to emit the body into it, so no block is ever + * materialized. + */ +template +concept QASMSink = + requires(S s, SMLoc loc, StringRef str, const Expr& expr, + const Operand& operand, const BitReference& reference, + const Condition& condition, const GateCall& call, + ArrayRef operands, ArrayRef names, + ArrayRef body, function_ref cont, + double d, bool flag, NumericType numericType) { + s.error(loc, str); + s.include(loc, str); + s.numericDecl(loc, numericType, str, &expr); + s.boolDecl(loc, str, &condition); + s.numericAssign(loc, str, expr); + s.boolAssign(loc, str, condition); + s.classicalKind(str); + s.qubitRegister(loc, str, &expr); + s.classicalRegister(loc, str, &expr); + s.measure(loc, reference, operand); + s.reset(loc, operand); + s.barrier(loc, operands); + s.gateCall(call); + s.gateDefinition(loc, str, names, names, body); + s.ifConditionOnly(loc, condition); + s.ifBegin(loc, condition, flag); + s.forStmt(loc, str, expr, expr, expr, cont); + s.whileStmt(loc, condition, cont); + // The sink must additionally provide `ifElse(scope)` and `ifEnd(scope, + // bool)` taking the opaque scope returned by `ifBegin`; those are not + // expressible here without naming the scope type. + }; + +//===----------------------------------------------------------------------===// +// Parser +//===----------------------------------------------------------------------===// + +/** + * @brief A single-pass recursive-descent parser for OpenQASM 3. + * + * @details + * The parser owns no persistent syntax tree. As it recognizes each construct it + * calls the corresponding sink event; control-flow bodies are emitted by having + * the sink call back through the continuations the parser supplies. Expressions + * and gate-definition bodies are bump-allocated. + */ +template + requires QASMSink +class Parser { +public: + Parser(Lexer& lexer, Sink& sink, llvm::BumpPtrAllocator& allocator) + : lexer(lexer), sink(sink), allocator(allocator) { + currentToken = lexer.next(); + nextToken = lexer.next(); + } + + [[nodiscard]] LogicalResult parseProgram() { + while (!atEnd()) { + if (failed(parseStatement())) { + return failure(); + } + } + return success(); + } + +private: + //===--- Token scaffolding --------------------------------------------===// + + void advance() { + currentToken = nextToken; + nextToken = lexer.next(); + } + + [[nodiscard]] const Token& current() const { return currentToken; } + [[nodiscard]] const Token& peek() const { return nextToken; } + [[nodiscard]] bool atEnd() const { + return currentToken.kind == TokenKind::Eof; + } + + [[nodiscard]] LogicalResult expect(const TokenKind kind) { + if (current().kind != kind) { + return sink.error(current().loc, "expected " + describe(kind) + ", got " + + describe(current().kind)); + } + advance(); + return success(); + } + + //===--- Allocation helpers -------------------------------------------===// + + [[nodiscard]] Expr* makeExpr() { + return new (allocator.Allocate()) Expr(); + } + + [[nodiscard]] Condition* makeCondition() { + return new (allocator.Allocate()) Condition(); + } + + template [[nodiscard]] ArrayRef copyToArena(ArrayRef values) { + if (values.empty()) { + return {}; + } + auto* storage = allocator.Allocate(values.size()); + std::uninitialized_copy(values.begin(), values.end(), storage); + return {storage, values.size()}; + } + + //===--- Program and statements ---------------------------------------===// + + [[nodiscard]] LogicalResult parseStatement() { + switch (current().kind) { + case TokenKind::OpenQASM: + return parseVersion(); + case TokenKind::Include: + return parseInclude(); + case TokenKind::Const: + case TokenKind::Int: + case TokenKind::Uint: + case TokenKind::Bool: + case TokenKind::Float: + return parseScalarDeclaration(); + case TokenKind::Angle: + case TokenKind::Duration: + return sink.error(current().loc, + "'angle' and 'duration' declarations are not supported " + "yet"); + case TokenKind::Qubit: + return parseQuantumDecl(); + case TokenKind::Qreg: + return parseQregDecl(); + case TokenKind::Bit: + return parseClassicalDecl(); + case TokenKind::CReg: + return parseCregDecl(); + case TokenKind::Gate: + return parseGateStatement(); + case TokenKind::Opaque: + return sink.error(current().loc, + "opaque gate declarations are not supported"); + case TokenKind::Barrier: + return parseBarrier(); + case TokenKind::Reset: + return parseReset(); + case TokenKind::Measure: + return parseMeasure(); + case TokenKind::If: + return parseIf(); + case TokenKind::For: + return parseFor(); + case TokenKind::While: + return parseWhile(); + case TokenKind::Inv: + case TokenKind::Pow: + case TokenKind::Ctrl: + case TokenKind::NegCtrl: + case TokenKind::Gphase: + return parseGateCallStatement(); + case TokenKind::Identifier: + switch (peek().kind) { + case TokenKind::LBracket: + case TokenKind::Equals: + case TokenKind::CompoundAssign: + return parseAssignment(); + default: + return parseGateCallStatement(); + } + default: + return sink.error(current().loc, "unexpected token"); + } + } + + /// Parse a `{ ... }` block or a single statement, emitting into whatever + /// region the sink has made current. + [[nodiscard]] LogicalResult parseBlock() { + if (current().kind == TokenKind::LBrace) { + advance(); + while (!atEnd() && current().kind != TokenKind::RBrace) { + if (failed(parseStatement())) { + return failure(); + } + } + return expect(TokenKind::RBrace); + } + return parseStatement(); + } + + //===--- Helpers ------------------------------------------------------===// + + [[nodiscard]] FailureOr parseDesignator() { + if (failed(expect(TokenKind::LBracket))) { + return failure(); + } + auto expr = parseExpression(); + if (failed(expr)) { + return failure(); + } + if (failed(expect(TokenKind::RBracket))) { + return failure(); + } + return *expr; + } + + //===--- Version ------------------------------------------------------===// + + [[nodiscard]] LogicalResult parseVersion() { + advance(); // OPENQASM + double version = 0.0; + if (current().kind == TokenKind::FloatLiteral) { + version = current().floatValue; + } else if (current().kind == TokenKind::IntegerLiteral) { + version = static_cast(current().intValue); + } else { + return sink.error(current().loc, + "version must be a float or integer literal"); + } + advance(); + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return success(); + } + + //===--- Include ------------------------------------------------------===// + + [[nodiscard]] LogicalResult parseInclude() { + const auto loc = current().loc; + advance(); // include + if (current().kind != TokenKind::StringLiteral) { + return sink.error(current().loc, "expected a string literal"); + } + const auto filename = current().stringValue; + advance(); + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.include(loc, filename); + } + + //===--- Declarations -------------------------------------------------===// + + /// Parse `[const] (int|uint|float|bool) = ;`. + [[nodiscard]] LogicalResult parseScalarDeclaration() { + const auto loc = current().loc; + if (current().kind == TokenKind::Const) { + advance(); // const + } + + NumericType numericType = NumericType::Float; + bool isBool = false; + switch (current().kind) { + case TokenKind::Float: + numericType = NumericType::Float; + break; + case TokenKind::Int: + case TokenKind::Uint: + numericType = NumericType::Int; + break; + case TokenKind::Bool: + isBool = true; + break; + case TokenKind::Angle: + case TokenKind::Duration: + return sink.error(current().loc, + "'angle' and 'duration' declarations are not supported " + "yet"); + default: + return sink.error(current().loc, "expected a scalar type"); + } + advance(); // type + + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected identifier"); + } + const auto id = current().identifier; + advance(); + + const bool hasInitializer = current().kind == TokenKind::Equals; + if (hasInitializer) { + advance(); + } + + if (isBool) { + const Condition* initializer = nullptr; + if (hasInitializer) { + auto condition = parseCondition(); + if (failed(condition)) { + return failure(); + } + initializer = *condition; + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.boolDecl(loc, id, initializer); + } + + const Expr* initializer = nullptr; + if (hasInitializer) { + auto value = parseExpression(); + if (failed(value)) { + return failure(); + } + initializer = *value; + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.numericDecl(loc, numericType, id, initializer); + } + + /// Parse `qubit[] ;`. + [[nodiscard]] LogicalResult parseQuantumDecl() { + const auto loc = current().loc; + advance(); // qubit + const Expr* size = nullptr; + if (current().kind == TokenKind::LBracket) { + auto designator = parseDesignator(); + if (failed(designator)) { + return failure(); + } + size = *designator; + } + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected identifier"); + } + const auto id = current().identifier; + advance(); + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.qubitRegister(loc, id, size); + } + + /// Parse `qreg [];`. + [[nodiscard]] LogicalResult parseQregDecl() { + const auto loc = current().loc; + advance(); // qreg + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected identifier"); + } + const auto id = current().identifier; + advance(); + const Expr* size = nullptr; + if (current().kind == TokenKind::LBracket) { + auto designator = parseDesignator(); + if (failed(designator)) { + return failure(); + } + size = *designator; + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.qubitRegister(loc, id, size); + } + + /// Parse `bit[] (= );`. + [[nodiscard]] LogicalResult parseClassicalDecl() { + const auto loc = current().loc; + advance(); // bit + const Expr* size = nullptr; + if (current().kind == TokenKind::LBracket) { + auto designator = parseDesignator(); + if (failed(designator)) { + return failure(); + } + size = *designator; + } + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected identifier"); + } + const auto id = current().identifier; + advance(); + + std::optional measureSource; + if (current().kind == TokenKind::Equals) { + advance(); + if (failed(expect(TokenKind::Measure))) { + return failure(); + } + auto operand = parseGateOperand(); + if (failed(operand)) { + return failure(); + } + measureSource = *operand; + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + + if (failed(sink.classicalRegister(loc, id, size))) { + return failure(); + } + if (measureSource) { + const BitReference target{.loc = loc, .identifier = id, .index = nullptr}; + return sink.measure(loc, target, *measureSource); + } + return success(); + } + + /// Parse `creg [];`. + [[nodiscard]] LogicalResult parseCregDecl() { + const auto loc = current().loc; + advance(); // creg + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected identifier"); + } + const auto id = current().identifier; + advance(); + const Expr* size = nullptr; + if (current().kind == TokenKind::LBracket) { + auto designator = parseDesignator(); + if (failed(designator)) { + return failure(); + } + size = *designator; + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.classicalRegister(loc, id, size); + } + + //===--- Assignment ---------------------------------------------------===// + + /// Parse ` = measure ;` or ` = ;`. + [[nodiscard]] LogicalResult parseAssignment() { + const auto loc = current().loc; + auto target = parseBitReference(); + if (failed(target)) { + return failure(); + } + const BitReference& reference = *target; + + if (current().kind == TokenKind::CompoundAssign) { + return sink.error(current().loc, + "compound assignments are not supported yet"); + } + if (failed(expect(TokenKind::Equals))) { + return failure(); + } + + // Measure assignment + if (current().kind == TokenKind::Measure) { + advance(); + auto operand = parseGateOperand(); + if (failed(operand)) { + return failure(); + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.measure(loc, reference, *operand); + } + + // Value assignment + const auto kind = sink.classicalKind(reference.identifier); + if (!kind) { + return sink.error(reference.loc, + "cannot assign to '" + reference.identifier + "'"); + } + if (reference.index != nullptr) { + return sink.error(reference.loc, + "cannot assign to an element of a scalar"); + } + + if (*kind == ClassicalKind::Boolean) { + auto condition = parseCondition(); + if (failed(condition)) { + return failure(); + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.boolAssign(loc, reference.identifier, **condition); + } + + auto value = parseExpression(); + if (failed(value)) { + return failure(); + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.numericAssign(loc, reference.identifier, **value); + } + + //===--- Measure ------------------------------------------------------===// + + [[nodiscard]] LogicalResult parseMeasure() { + const auto loc = current().loc; + advance(); // measure + auto operand = parseGateOperand(); + if (failed(operand)) { + return failure(); + } + if (failed(expect(TokenKind::Arrow))) { + return failure(); + } + auto target = parseBitReference(); + if (failed(target)) { + return failure(); + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.measure(loc, *target, *operand); + } + + //===--- Reset --------------------------------------------------------===// + + [[nodiscard]] LogicalResult parseReset() { + const auto loc = current().loc; + advance(); // reset + auto operand = parseGateOperand(); + if (failed(operand)) { + return failure(); + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.reset(loc, *operand); + } + + //===--- Barrier ------------------------------------------------------===// + + [[nodiscard]] LogicalResult parseBarrier() { + const auto loc = current().loc; + advance(); // barrier + SmallVector operands; + while (current().kind != TokenKind::Semicolon) { + auto operand = parseGateOperand(); + if (failed(operand)) { + return failure(); + } + operands.push_back(*operand); + if (current().kind != TokenKind::Semicolon && + failed(expect(TokenKind::Comma))) { + return failure(); + } + } + advance(); // ; + return sink.barrier(loc, operands); + } + + //===--- Gate definitions and calls -----------------------------------===// + + [[nodiscard]] LogicalResult parseGateStatement() { + const auto loc = current().loc; + advance(); // gate + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected gate name"); + } + const auto id = current().identifier; + advance(); + + // Parse parameters + SmallVector parameters; + if (current().kind == TokenKind::LParen) { + advance(); + if (failed(parseIdentifierList(parameters))) { + return failure(); + } + if (failed(expect(TokenKind::RParen))) { + return failure(); + } + } + + // Parse target qubits + SmallVector targets; + if (failed(parseIdentifierList(targets))) { + return failure(); + } + + // Parse body + if (failed(expect(TokenKind::LBrace))) { + return failure(); + } + SmallVector body; + while (current().kind != TokenKind::RBrace) { + // Bodies outlive this frame, so the call's arrays are copied into the + // arena; the scratch buffers below are discarded. + SmallVector callModifiers; + SmallVector callParameters; + SmallVector callOperands; + auto call = parseGateCall(callModifiers, callParameters, callOperands, + /*persist=*/true); + if (failed(call)) { + return failure(); + } + body.push_back(*call); + } + if (failed(expect(TokenKind::RBrace))) { + return failure(); + } + + return sink.gateDefinition(loc, id, copyToArena(ArrayRef(parameters)), + copyToArena(ArrayRef(targets)), + copyToArena(ArrayRef(body))); + } + + [[nodiscard]] LogicalResult parseGateCallStatement() { + // The scratch buffers must outlive the sink call, so they live here rather + // than inside `parseGateCall`. + SmallVector modifiers; + SmallVector parameters; + SmallVector operands; + auto call = parseGateCall(modifiers, parameters, operands, + /*persist=*/false); + if (failed(call)) { + return failure(); + } + return sink.gateCall(*call); + } + + [[nodiscard]] FailureOr + parseGateCall(SmallVectorImpl& modifiers, + SmallVectorImpl& parameters, + SmallVectorImpl& operands, const bool persist) { + GateCall call; + call.loc = current().loc; + + while (current().kind == TokenKind::Inv || + current().kind == TokenKind::Pow || + current().kind == TokenKind::Ctrl || + current().kind == TokenKind::NegCtrl) { + auto modifier = parseModifier(); + if (failed(modifier)) { + return failure(); + } + modifiers.push_back(*modifier); + if (failed(expect(TokenKind::At))) { + return failure(); + } + } + + switch (current().kind) { + case TokenKind::Gphase: { + call.identifier = "gphase"; + advance(); + break; + } + case TokenKind::Identifier: { + call.identifier = current().identifier; + advance(); + break; + } + default: + return sink.error(current().loc, "expected gate name"); + } + + // Parse parameters + if (current().kind == TokenKind::LParen) { + advance(); + while (current().kind != TokenKind::RParen) { + auto parameter = parseExpression(); + if (failed(parameter)) { + return failure(); + } + parameters.push_back(*parameter); + if (current().kind != TokenKind::RParen && + failed(expect(TokenKind::Comma))) { + return failure(); + } + } + advance(); // ) + } + + if (current().kind == TokenKind::LBracket) { + return sink.error(current().loc, + "gate calls with designators are not supported yet"); + } + + // Parse target qubits + while (current().kind != TokenKind::Semicolon) { + auto operand = parseGateOperand(); + if (failed(operand)) { + return failure(); + } + operands.push_back(*operand); + if (current().kind != TokenKind::Semicolon && + failed(expect(TokenKind::Comma))) { + return failure(); + } + } + advance(); // ; + + // Top-level calls are consumed by the sink while the caller's buffers are + // still alive, so borrowing them is safe. Gate definitions outlive this + // frame, so their arrays are copied into the arena. + if (persist) { + call.modifiers = copyToArena(ArrayRef(modifiers)); + call.parameters = copyToArena(ArrayRef(parameters)); + call.operands = copyToArena(ArrayRef(operands)); + return call; + } + call.modifiers = modifiers; + call.parameters = parameters; + call.operands = operands; + return call; + } + + [[nodiscard]] FailureOr parseModifier() { + Modifier modifier; + switch (current().kind) { + case TokenKind::Inv: + modifier.kind = Modifier::Kind::Inv; + advance(); + return modifier; + case TokenKind::Pow: + modifier.kind = Modifier::Kind::Pow; + advance(); + if (failed(expect(TokenKind::LParen))) { + return failure(); + } + { + auto argument = parseExpression(); + if (failed(argument)) { + return failure(); + } + modifier.argument = *argument; + } + if (failed(expect(TokenKind::RParen))) { + return failure(); + } + return modifier; + case TokenKind::Ctrl: + case TokenKind::NegCtrl: + modifier.kind = current().kind == TokenKind::Ctrl + ? Modifier::Kind::Ctrl + : Modifier::Kind::NegCtrl; + advance(); + if (current().kind == TokenKind::LParen) { + advance(); + auto argument = parseExpression(); + if (failed(argument)) { + return failure(); + } + modifier.argument = *argument; + if (failed(expect(TokenKind::RParen))) { + return failure(); + } + } + return modifier; + default: + return sink.error(current().loc, "expected a gate modifier"); + } + } + + [[nodiscard]] FailureOr parseGateOperand() { + Operand operand; + operand.loc = current().loc; + if (current().kind == TokenKind::HardwareQubit) { + operand.hardwareQubit = static_cast(current().intValue); + advance(); + return operand; + } + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected a gate operand"); + } + operand.identifier = current().identifier; + advance(); + const Expr* index = nullptr; + if (current().kind == TokenKind::LBracket) { + auto designator = parseDesignator(); + if (failed(designator)) { + return failure(); + } + index = *designator; + } + operand.index = index; + return operand; + } + + [[nodiscard]] FailureOr parseBitReference() { + BitReference reference; + reference.loc = current().loc; + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected an identifier"); + } + reference.identifier = current().identifier; + advance(); + const Expr* index = nullptr; + if (current().kind == TokenKind::LBracket) { + auto designator = parseDesignator(); + if (failed(designator)) { + return failure(); + } + index = *designator; + } + reference.index = index; + return reference; + } + + [[nodiscard]] LogicalResult + parseIdentifierList(SmallVectorImpl& identifiers) { + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected an identifier"); + } + identifiers.push_back(current().identifier); + advance(); + while (current().kind == TokenKind::Comma) { + advance(); + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected an identifier"); + } + identifiers.push_back(current().identifier); + advance(); + } + return success(); + } + + //===--- Control flow -------------------------------------------------===// + + [[nodiscard]] LogicalResult parseIf() { + const auto loc = current().loc; + advance(); // if + if (failed(expect(TokenKind::LParen))) { + return failure(); + } + auto conditionOrFailure = parseCondition(); + if (failed(conditionOrFailure)) { + return failure(); + } + const auto& condition = **conditionOrFailure; + if (failed(expect(TokenKind::RParen))) { + return failure(); + } + + const bool thenEmpty = + current().kind == TokenKind::LBrace && peek().kind == TokenKind::RBrace; + if (thenEmpty) { + advance(); // { + advance(); // } + if (current().kind != TokenKind::Else) { + return sink.ifConditionOnly(loc, condition); + } + advance(); // else + auto scope = sink.ifBegin(loc, condition, /*invert=*/true); + if (failed(scope)) { + return failure(); + } + if (failed(parseBlock())) { + return failure(); + } + return sink.ifEnd(*scope, /*hadElse=*/false); + } + + auto scope = sink.ifBegin(loc, condition, /*invert=*/false); + if (failed(scope)) { + return failure(); + } + if (failed(parseBlock())) { + return failure(); + } + + if (current().kind == TokenKind::Else) { + advance(); // else + const bool elseEmpty = current().kind == TokenKind::LBrace && + peek().kind == TokenKind::RBrace; + if (elseEmpty) { + advance(); // { + advance(); // } + return sink.ifEnd(*scope, /*hadElse=*/false); + } + if (failed(sink.ifElse(*scope))) { + return failure(); + } + if (failed(parseBlock())) { + return failure(); + } + return sink.ifEnd(*scope, /*hadElse=*/true); + } + + return sink.ifEnd(*scope, /*hadElse=*/false); + } + + [[nodiscard]] LogicalResult parseFor() { + const auto loc = current().loc; + advance(); // for + if (current().kind != TokenKind::Int && current().kind != TokenKind::Uint) { + return sink.error(current().loc, "expected 'int' or 'uint' after 'for'"); + } + advance(); // type + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected loop variable"); + } + const auto variable = current().identifier; + advance(); // identifier + if (failed(expect(TokenKind::In)) || failed(expect(TokenKind::LBracket))) { + return failure(); + } + auto start = parseExpression(); + if (failed(start)) { + return failure(); + } + if (failed(expect(TokenKind::Colon))) { + return failure(); + } + auto second = parseExpression(); + if (failed(second)) { + return failure(); + } + + const Expr* step = nullptr; + const Expr* stop = nullptr; + if (current().kind == TokenKind::Colon) { + advance(); + auto third = parseExpression(); + if (failed(third)) { + return failure(); + } + step = *second; + stop = *third; + } else { + auto* one = makeExpr(); + one->loc = loc; + one->kind = Expr::Kind::Int; + one->intValue = 1; + step = one; + stop = *second; + } + if (failed(expect(TokenKind::RBracket))) { + return failure(); + } + + return sink.forStmt(loc, variable, **start, *step, *stop, + [this] { return parseBlock(); }); + } + + [[nodiscard]] LogicalResult parseWhile() { + const auto loc = current().loc; + advance(); // while + if (failed(expect(TokenKind::LParen))) { + return failure(); + } + auto conditionOrFailure = parseCondition(); + if (failed(conditionOrFailure)) { + return failure(); + } + const auto& condition = **conditionOrFailure; + if (failed(expect(TokenKind::RParen))) { + return failure(); + } + return sink.whileStmt(loc, condition, [this] { return parseBlock(); }); + } + + //===--- Conditions --------------------------------------------------===// + + /// cond := andCond ('||' andCond)* + [[nodiscard]] FailureOr parseCondition() { + auto lhs = parseAndCondition(); + if (failed(lhs)) { + return failure(); + } + const auto* result = *lhs; + while (current().kind == TokenKind::PipePipe) { + const auto loc = current().loc; + advance(); + auto rhs = parseAndCondition(); + if (failed(rhs)) { + return failure(); + } + result = makeBinaryCondition(Condition::Kind::Or, result, *rhs, loc); + } + return result; + } + + /// andCond := unaryCond ('&&' unaryCond)* + [[nodiscard]] FailureOr parseAndCondition() { + auto lhs = parseUnaryCondition(); + if (failed(lhs)) { + return failure(); + } + const auto* result = *lhs; + while (current().kind == TokenKind::AmpAmp) { + const auto loc = current().loc; + advance(); + auto rhs = parseUnaryCondition(); + if (failed(rhs)) { + return failure(); + } + result = makeBinaryCondition(Condition::Kind::And, result, *rhs, loc); + } + return result; + } + + /// unaryCond := ('!' | '~') unaryCond | primaryCond + [[nodiscard]] FailureOr parseUnaryCondition() { + if (current().kind == TokenKind::ExclamationPoint || + current().kind == TokenKind::Tilde) { + const auto loc = current().loc; + advance(); + auto operand = parseUnaryCondition(); + if (failed(operand)) { + return failure(); + } + auto* condition = makeCondition(); + condition->loc = loc; + condition->kind = Condition::Kind::Not; + condition->lhs = *operand; + return condition; + } + return parsePrimaryCondition(); + } + + /// primaryCond := 'measure' gateOperand | '(' cond ')' | reference + [[nodiscard]] FailureOr parsePrimaryCondition() { + const auto loc = current().loc; + switch (current().kind) { + case TokenKind::True: + case TokenKind::False: { + auto* condition = makeCondition(); + condition->loc = loc; + condition->kind = Condition::Kind::Literal; + condition->literalValue = current().kind == TokenKind::True; + advance(); + return finishPrimaryCondition(condition); + } + case TokenKind::Measure: { + advance(); + auto operand = parseGateOperand(); + if (failed(operand)) { + return failure(); + } + auto* condition = makeCondition(); + condition->loc = loc; + condition->kind = Condition::Kind::Measurement; + condition->operand = *operand; + return finishPrimaryCondition(condition); + } + case TokenKind::LParen: { + advance(); + auto inner = parseCondition(); + if (failed(inner)) { + return failure(); + } + if (failed(expect(TokenKind::RParen))) { + return failure(); + } + return finishPrimaryCondition(*inner); + } + case TokenKind::Identifier: { + auto bit = parseBitReference(); + if (failed(bit)) { + return failure(); + } + auto* condition = makeCondition(); + condition->loc = loc; + condition->kind = Condition::Kind::Bit; + condition->bit = *bit; + return finishPrimaryCondition(condition); + } + default: + return sink.error(loc, "unsupported condition expression"); + } + } + + /// Reject a register comparison (e.g. `c == 5`) trailing a primary. + [[nodiscard]] FailureOr + finishPrimaryCondition(const Condition* condition) { + switch (current().kind) { + case TokenKind::EqualsEquals: + case TokenKind::NotEquals: + case TokenKind::Less: + case TokenKind::LessEquals: + case TokenKind::Greater: + case TokenKind::GreaterEquals: + return sink.error(current().loc, + "register comparisons are not supported"); + default: + return condition; + } + } + + [[nodiscard]] Condition* makeBinaryCondition(const Condition::Kind kind, + const Condition* lhs, + const Condition* rhs, + const SMLoc loc) { + auto* condition = makeCondition(); + condition->loc = loc; + condition->kind = kind; + condition->lhs = lhs; + condition->rhs = rhs; + return condition; + } + + //===--- Expressions --------------------------------------------------===// + + [[nodiscard]] FailureOr parseExpression() { + auto lhs = parseTerm(); + if (failed(lhs)) { + return failure(); + } + const Expr* result = *lhs; + while (current().kind == TokenKind::Plus || + current().kind == TokenKind::Minus) { + const auto kind = + current().kind == TokenKind::Plus ? Expr::Kind::Add : Expr::Kind::Sub; + const auto loc = current().loc; + advance(); + auto rhs = parseTerm(); + if (failed(rhs)) { + return failure(); + } + result = makeBinary(kind, result, *rhs, loc); + } + return result; + } + + [[nodiscard]] FailureOr parseTerm() { + auto lhs = parseUnary(); + if (failed(lhs)) { + return failure(); + } + const Expr* result = *lhs; + while (current().kind == TokenKind::Asterisk || + current().kind == TokenKind::Slash) { + const auto kind = current().kind == TokenKind::Asterisk ? Expr::Kind::Mul + : Expr::Kind::Div; + const auto loc = current().loc; + advance(); + auto rhs = parseUnary(); + if (failed(rhs)) { + return failure(); + } + result = makeBinary(kind, result, *rhs, loc); + } + return result; + } + + [[nodiscard]] FailureOr parseUnary() { + if (current().kind == TokenKind::Minus) { + const auto loc = current().loc; + advance(); + auto operand = parseUnary(); + if (failed(operand)) { + return failure(); + } + auto* expr = makeExpr(); + expr->loc = loc; + expr->kind = Expr::Kind::Neg; + expr->lhs = *operand; + return expr; + } + return parsePrimary(); + } + + [[nodiscard]] FailureOr parsePrimary() { + auto* expr = makeExpr(); + expr->loc = current().loc; + switch (current().kind) { + case TokenKind::FloatLiteral: + expr->kind = Expr::Kind::Float; + expr->floatValue = current().floatValue; + advance(); + return expr; + case TokenKind::IntegerLiteral: + expr->kind = Expr::Kind::Int; + expr->intValue = current().intValue; + advance(); + return expr; + case TokenKind::Identifier: + expr->kind = Expr::Kind::Identifier; + expr->identifier = current().identifier; + advance(); + return expr; + case TokenKind::LParen: { + advance(); + auto inner = parseExpression(); + if (failed(inner)) { + return failure(); + } + if (failed(expect(TokenKind::RParen))) { + return failure(); + } + return *inner; + } + default: + return sink.error(current().loc, "expected expression"); + } + } + + [[nodiscard]] Expr* makeBinary(const Expr::Kind kind, const Expr* lhs, + const Expr* rhs, const SMLoc loc) { + auto* expr = makeExpr(); + expr->loc = loc; + expr->kind = kind; + expr->lhs = lhs; + expr->rhs = rhs; + return expr; + } + + Lexer& lexer; + Sink& sink; + llvm::BumpPtrAllocator& allocator; + Token currentToken; + Token nextToken; +}; + +} // namespace mlir::qc::detail diff --git a/mlir/lib/Dialect/QC/Translation/CMakeLists.txt b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt index c1d403de3a..6650f0d091 100644 --- a/mlir/lib/Dialect/QC/Translation/CMakeLists.txt +++ b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt @@ -10,6 +10,8 @@ add_mlir_library( MLIRQCTranslation TranslateQuantumComputationToQC.cpp TranslateQASM3ToQC.cpp + qasm3/QASM3Lexer.cpp + qasm3/QASM3Emitter.cpp LINK_LIBS MLIRArithDialect MLIRFuncDialect diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp index bfc7bfe989..fe7bf2ab7b 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp @@ -10,1047 +10,28 @@ #include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h" -#include "ir/Definitions.hpp" -#include "ir/operations/OpType.hpp" -#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" -#include "mlir/Dialect/QC/IR/QCOps.h" -#include "qasm3/Exception.hpp" -#include "qasm3/Gate.hpp" -#include "qasm3/InstVisitor.hpp" -#include "qasm3/NestedEnvironment.hpp" -#include "qasm3/Parser.hpp" -#include "qasm3/Statement.hpp" -#include "qasm3/StdGates.hpp" -#include "qasm3/Types.hpp" -#include "qasm3/passes/ConstEvalPass.hpp" -#include "qasm3/passes/TypeCheckPass.hpp" +#include "mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h" -#include -#include -#include -#include +#include #include -#include -#include -#include -#include #include -#include +#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include namespace mlir::qc { -namespace { - -/// Signature: (builder, gate operands, evaluated parameters). -/// For gates with implicit controls (cx, ccx, ...), all qubits including -/// the controls are part of the range, matching OpenQASM 3 operand order. -using GateFn = - std::function)>; - -} // namespace - -/** - * @brief Build the table mapping OpenQASM 3 gate identifiers to - * QCProgramBuilder emitters. - * - * @details - * Each entry maps an OpenQASM 3 gate identifier to a lambda that emits the - * corresponding QC operation via the QCProgramBuilder. - */ -static llvm::StringMap buildGateDispatch() { - llvm::StringMap d; - - // ZeroTargetOneParameter - d["gphase"] = [](auto& b, auto /*q*/, auto p) { b.gphase(p[0]); }; - - // OneTargetZeroParameter - d["id"] = [](auto& b, auto q, auto) { b.id(q[0]); }; - d["x"] = [](auto& b, auto q, auto) { b.x(q[0]); }; - d["y"] = [](auto& b, auto q, auto) { b.y(q[0]); }; - d["z"] = [](auto& b, auto q, auto) { b.z(q[0]); }; - d["h"] = [](auto& b, auto q, auto) { b.h(q[0]); }; - d["s"] = [](auto& b, auto q, auto) { b.s(q[0]); }; - d["sdg"] = [](auto& b, auto q, auto) { b.sdg(q[0]); }; - d["t"] = [](auto& b, auto q, auto) { b.t(q[0]); }; - d["tdg"] = [](auto& b, auto q, auto) { b.tdg(q[0]); }; - d["sx"] = [](auto& b, auto q, auto) { b.sx(q[0]); }; - d["sxdg"] = [](auto& b, auto q, auto) { b.sxdg(q[0]); }; - - // OneTargetOneParameter - d["rx"] = [](auto& b, auto q, auto p) { b.rx(p[0], q[0]); }; - d["ry"] = [](auto& b, auto q, auto p) { b.ry(p[0], q[0]); }; - d["rz"] = [](auto& b, auto q, auto p) { b.rz(p[0], q[0]); }; - d["p"] = [](auto& b, auto q, auto p) { b.p(p[0], q[0]); }; - d["u1"] = [](auto& b, auto q, auto p) { b.p(p[0], q[0]); }; // alias - d["phase"] = [](auto& b, auto q, auto p) { b.p(p[0], q[0]); }; // alias - - // OneTargetTwoParameter - d["r"] = [](auto& b, auto q, auto p) { b.r(p[0], p[1], q[0]); }; - d["u2"] = [](auto& b, auto q, auto p) { b.u2(p[0], p[1], q[0]); }; - - // OneTargetThreeParameter - auto uFn = [](auto& b, auto q, auto p) { b.u(p[0], p[1], p[2], q[0]); }; - d["U"] = uFn; - d["u3"] = uFn; // alias - d["u"] = uFn; // alias - - // TwoTargetZeroParameter - d["swap"] = [](auto& b, auto q, auto) { b.swap(q[0], q[1]); }; - d["iswap"] = [](auto& b, auto q, auto) { b.iswap(q[0], q[1]); }; - d["dcx"] = [](auto& b, auto q, auto) { b.dcx(q[0], q[1]); }; - d["ecr"] = [](auto& b, auto q, auto) { b.ecr(q[0], q[1]); }; - - // TwoTargetOneParameter - d["rxx"] = [](auto& b, auto q, auto p) { b.rxx(p[0], q[0], q[1]); }; - d["ryy"] = [](auto& b, auto q, auto p) { b.ryy(p[0], q[0], q[1]); }; - d["rzx"] = [](auto& b, auto q, auto p) { b.rzx(p[0], q[0], q[1]); }; - d["rzz"] = [](auto& b, auto q, auto p) { b.rzz(p[0], q[0], q[1]); }; - - // TwoTargetTwoParameter - d["xx_plus_yy"] = [](auto& b, auto q, auto p) { - b.xx_plus_yy(p[0], p[1], q[0], q[1]); - }; - d["xx_minus_yy"] = [](auto& b, auto q, auto p) { - b.xx_minus_yy(p[0], p[1], q[0], q[1]); - }; - - // Controlled OneTargetZeroParameter - d["cx"] = [](auto& b, auto q, auto) { b.cx(q[0], q[1]); }; - d["cnot"] = [](auto& b, auto q, auto) { b.cx(q[0], q[1]); }; // alias - d["cy"] = [](auto& b, auto q, auto) { b.cy(q[0], q[1]); }; - d["cz"] = [](auto& b, auto q, auto) { b.cz(q[0], q[1]); }; - d["ch"] = [](auto& b, auto q, auto) { b.ch(q[0], q[1]); }; - d["csx"] = [](auto& b, auto q, auto) { b.csx(q[0], q[1]); }; - - // Controlled OneTargetOneParameter - d["crx"] = [](auto& b, auto q, auto p) { b.crx(p[0], q[0], q[1]); }; - d["cry"] = [](auto& b, auto q, auto p) { b.cry(p[0], q[0], q[1]); }; - d["crz"] = [](auto& b, auto q, auto p) { b.crz(p[0], q[0], q[1]); }; - d["cp"] = [](auto& b, auto q, auto p) { b.cp(p[0], q[0], q[1]); }; - d["cphase"] = [](auto& b, auto q, auto p) { - b.cp(p[0], q[0], q[1]); - }; // alias - - // Controlled TwoTargetZeroParameter - d["cswap"] = [](auto& b, auto q, auto) { b.cswap(q[0], q[1], q[2]); }; - d["fredkin"] = [](auto& b, auto q, auto) { - b.cswap(q[0], q[1], q[2]); - }; // alias - - // Multi-controlled gates - auto mcxFn = [](auto& b, auto q, auto) { b.mcx(q.drop_back(1), q.back()); }; - d["mcx"] = mcxFn; - d["mcx_gray"] = mcxFn; - - d["mcx_vchain"] = [](auto& b, auto q, auto) { - const size_t n = q.size() - ((q.size() + 1) / 2) + 2; - b.mcx(q.slice(0, n - 1), q[n - 1]); - }; - - d["mcx_recursive"] = [](auto& b, auto q, auto) { - const size_t n = (q.size() > 5) ? q.size() - 1 : q.size(); - b.mcx(q.slice(0, n - 1), q[n - 1]); - }; - - d["mcphase"] = [](auto& b, auto q, auto p) { - b.mcp(p[0], q.drop_back(1), q.back()); - }; - - return d; -} - -static llvm::StringMap> convertToStringMap( - const std::map>& sourceMap) { - llvm::StringMap> targetMap; - for (const auto& [key, value] : sourceMap) { - targetMap.insert(std::make_pair(key, value)); - } - return targetMap; -} - -namespace { - -/// Map from OpenQASM 3 gate identifier to QCProgramBuilder emitter. -const llvm::StringMap GATE_DISPATCH = buildGateDispatch(); - -/// Map of qubits in the current scope. -using QubitScope = llvm::StringMap>; - -/** - * @brief AST visitor that translates an OpenQASM 3 program to a QC program. - * - * @details - * Implements qasm3::InstVisitor to walk the AST produced by qasm3::Parser and - * emit QC operations via the QCProgramBuilder. - */ -class MLIRQasmImporter final : public qasm3::InstVisitor { -public: - explicit MLIRQasmImporter(MLIRContext* ctx) - : builder(ctx), typeCheckPass(constEvalPass), - gates(convertToStringMap(qasm3::STANDARD_GATES)) { - initBuiltins(); - builder.initialize(); - } - - void - visitProgram(const std::vector>& program) { - for (const auto& stmt : program) { - constEvalPass.processStatement(*stmt); - typeCheckPass.processStatement(*stmt); - stmt->accept(this); - } - } - - OwningOpRef finalize() { return builder.finalize(); } - -private: - QCProgramBuilder builder; - qasm3::const_eval::ConstEvalPass constEvalPass; - qasm3::type_checking::TypeCheckPass typeCheckPass; - qasm3::NestedEnvironment> - declarations; - - /// Map from qubit-register name to allocated qubit values. - QubitScope qubitRegisters; - - /// Map from classical-register name to ClassicalRegister. - llvm::StringMap classicalRegisters; - - /// Map from classical-register name to measurement results. - llvm::StringMap> bitValues; - - /// Map from gate identifier to OpenQASM 3 definition. - llvm::StringMap> gates; - - bool openQASM2CompatMode{false}; - - //===--- Initialization -----------------------------------------------===// - - void initBuiltins() { - using namespace qasm3::const_eval; - using namespace qasm3::type_checking; - - auto floatType = - InferredType{std::dynamic_pointer_cast( - std::make_shared>(qasm3::Float, - 64))}; - - auto addConstant = [&](const std::string& name, double value) { - constEvalPass.addConst(name, ConstEvalValue(value)); - typeCheckPass.addBuiltin(name, floatType); - }; - - addConstant("pi", ::qc::PI); - addConstant("π", ::qc::PI); - addConstant("tau", ::qc::TAU); - addConstant("τ", ::qc::TAU); - addConstant("euler", ::qc::E); - addConstant("ℇ", ::qc::E); - - const qasm3::GateInfo mcxInfo{.nControls = 0, - .nTargets = 0, - .nParameters = 0, - .type = ::qc::OpType::X}; - gates["mcx"] = std::make_shared(mcxInfo); - gates["mcx_gray"] = std::make_shared(mcxInfo); - gates["mcx_vchain"] = std::make_shared(mcxInfo); - gates["mcx_recursive"] = std::make_shared(mcxInfo); - - const qasm3::GateInfo mcphaseInfo{.nControls = 0, - .nTargets = 0, - .nParameters = 1, - .type = ::qc::OpType::P}; - gates["mcphase"] = std::make_shared(mcphaseInfo); - } - -public: - //===--- InstVisitor overrides ----------------------------------------===// - - void - visitGateStatement(std::shared_ptr stmt) override { - const auto& id = stmt->identifier; - if (stmt->isOpaque) { - if (!gates.contains(id)) { - throw qasm3::CompilerError("Unsupported opaque gate '" + id + "'.", - stmt->debugInfo); - } - return; - } - if (gates.contains(id)) { - throw qasm3::CompilerError("Gate '" + id + "' already declared.", - stmt->debugInfo); - } - std::vector params; - for (const auto& p : stmt->parameters->identifiers) { - const auto& param = p->identifier; - if (std::ranges::find(params, param) != params.end()) { - throw qasm3::CompilerError( - "Parameter is already declared in compound gate.", stmt->debugInfo); - } - params.push_back(param); - } - std::vector targets; - for (const auto& t : stmt->qubits->identifiers) { - const auto& target = t->identifier; - if (std::ranges::find(targets, target) != targets.end()) { - throw qasm3::CompilerError( - "Target is already declared in compound gate.", stmt->debugInfo); - } - targets.push_back(target); - } - gates[id] = std::make_shared( - std::move(params), std::move(targets), stmt->statements); - } - - void visitVersionDeclaration(const std::shared_ptr - versionDeclaration) override { - if (versionDeclaration->version < 3) { - openQASM2CompatMode = true; - } - } - - void visitDeclarationStatement( - std::shared_ptr stmt) override { - const auto& id = stmt->identifier; - if (declarations.find(id).has_value()) { - throw qasm3::CompilerError("Identifier '" + id + "' already declared.", - stmt->debugInfo); - } - declarations.emplace(id, stmt); - - if (stmt->isConst) { - // Nothing to emit - return; - } - - const auto sizedType = - std::dynamic_pointer_cast>( - std::get<1>(stmt->type)); - if (!sizedType) { - throw qasm3::CompilerError("Only sized types are supported.", - stmt->debugInfo); - } - const auto size = static_cast(sizedType->getDesignator()); - - switch (sizedType->type) { - case qasm3::Qubit: { - const auto& reg = builder.allocQubitRegister(size); - qubitRegisters[id] = reg.qubits; - break; - } - case qasm3::Bit: - case qasm3::Int: - case qasm3::Uint: { - classicalRegisters[id] = builder.allocClassicalBitRegister(size, id); - break; - } - default: - throw qasm3::CompilerError("Unsupported declaration type.", - stmt->debugInfo); - } - - // Handle declarations through measure expressions - if (stmt->expression) { - const auto& innerExpr = stmt->expression->expression; - if (const auto measureExpr = - std::dynamic_pointer_cast(innerExpr)) { - auto target = std::make_shared(id); - visitMeasureAssignment(target, measureExpr, stmt->debugInfo); - return; - } - throw qasm3::CompilerError( - "Only measure expressions can declare variables.", stmt->debugInfo); - } - } - - void visitInitialLayout( - std::shared_ptr /*initialLayout*/) override {} - - void visitOutputPermutation( - std::shared_ptr /*outputPermutation*/) - override {} - - void visitGateCallStatement( - std::shared_ptr stmt) override { - applyGateCallStatement(stmt, qubitRegisters); - } - - void visitAssignmentStatement( - std::shared_ptr stmt) override { - const auto& innerId = stmt->identifier->identifier; - assert(declarations.find(innerId).has_value()); - assert(!declarations.find(innerId)->get()->isConst); - - const auto& innerExpr = stmt->expression->expression; - if (const auto measureExpr = - std::dynamic_pointer_cast(innerExpr)) { - visitMeasureAssignment(stmt->identifier, measureExpr, stmt->debugInfo); - return; - } - - throw qasm3::CompilerError("Classical computations are not supported.", - stmt->debugInfo); - } - - void visitMeasureAssignment( - const std::shared_ptr& target, - const std::shared_ptr& measureExpr, - const std::shared_ptr& debugInfo) { - const auto& bits = resolveClassicalBits(target, debugInfo); - const auto& operand = resolveGateOperand(measureExpr->gate, debugInfo); - SmallVector qubits; - if (std::holds_alternative(operand)) { - qubits.push_back(std::get(operand)); - } else { - qubits = std::get>(operand); - } - if (bits.size() != qubits.size()) { - throw qasm3::CompilerError("The classical register and the quantum " - "register must have the same width.", - debugInfo); - } - for (const auto& [bit, qubit] : llvm::zip_equal(bits, qubits)) { - auto result = MeasureOp::create( - builder, qubit, builder.getStringAttr(bit.registerName), - builder.getI64IntegerAttr(bit.registerSize), - builder.getI64IntegerAttr(bit.registerIndex)) - .getResult(); - auto& regBits = bitValues[bit.registerName]; - const auto index = static_cast(bit.registerIndex); - if (regBits.size() <= index) { - regBits.resize(index + 1); - } - regBits[index] = result; - } - } - - void visitBarrierStatement( - std::shared_ptr stmt) override { - SmallVector qubits; - for (const auto& gate : stmt->gates) { - const auto& operand = resolveGateOperand(gate, stmt->debugInfo); - if (std::holds_alternative(operand)) { - qubits.push_back(std::get(operand)); - } else { - llvm::append_range(qubits, std::get>(operand)); - } - } - builder.barrier(qubits); - } - - void - visitResetStatement(std::shared_ptr stmt) override { - const auto& operand = resolveGateOperand(stmt->gate, stmt->debugInfo); - if (std::holds_alternative(operand)) { - builder.reset(std::get(operand)); - } else { - for (auto qubit : std::get>(operand)) { - builder.reset(qubit); - } - } - } - - void visitIfStatement(std::shared_ptr stmt) override { - if (stmt->thenStatements.empty() && stmt->elseStatements.empty()) { - throw qasm3::CompilerError( - "If statements with empty then and else blocks are not supported.", - stmt->debugInfo); - } - - auto condition = translateCondition(stmt->condition, stmt->debugInfo); - auto hasElse = !stmt->elseStatements.empty(); - - std::vector> thenStatements; - if (stmt->thenStatements.empty()) { - thenStatements = stmt->elseStatements; - hasElse = false; - auto trueValue = builder.boolConstant(true); - condition = - arith::XOrIOp::create(builder, condition, trueValue).getResult(); - } else { - thenStatements = stmt->thenStatements; - } - - auto ifOp = - scf::IfOp::create(builder, condition, /*withElseRegion=*/hasElse); - - // Save current insertion point - OpBuilder::InsertionGuard guard(builder); - - // Then block - builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); - emitBlockStatements(thenStatements, stmt->debugInfo); - - // Else block - if (hasElse) { - builder.setInsertionPointToStart(&ifOp.getElseRegion().front()); - emitBlockStatements(stmt->elseStatements, stmt->debugInfo); - } - } - - //===--- Core gate application ----------------------------------------===// - - /** - * @brief Apply a GateCallStatement by emitting the corresponding QC - * operations. - * - * @param stmt The GateCallStatement to apply. - * @param scope The current qubit scope for resolving operands. If called from - * the main visitor, this is the top-level qubitRegisters map. If called - * recursively for a compound gate, this is the local scope of the - * CompoundGate. - */ - void - applyGateCallStatement(const std::shared_ptr& stmt, - const QubitScope& scope) { - const auto& id = stmt->identifier; - auto it = gates.find(id); - - // OpenQASM 2 compatibility: - // Strip leading c characters and treat them as implicit control modifiers - auto resolvedId = id; - size_t numCompatControls = 0; - if (openQASM2CompatMode && it == gates.end()) { - while (!resolvedId.empty() && resolvedId.front() == 'c') { - resolvedId = resolvedId.substr(1); - ++numCompatControls; - } - if (numCompatControls > 0) { - it = gates.find(resolvedId); - } - } - - if (it == gates.end()) { - throw qasm3::CompilerError("No OpenQASM definition found for gate '" + - id + "'.", - stmt->debugInfo); - } - - // Evaluate parameters to doubles - SmallVector params; - params.reserve(stmt->arguments.size()); - for (const auto& arg : stmt->arguments) { - auto result = constEvalPass.visit(arg); - if (!result.has_value()) { - throw qasm3::CompilerError("Gate parameter could not be evaluated.", - stmt->debugInfo); - } - params.push_back(result->toExpr()->asFP()); - } - - // Expand operands to MLIR values - SmallVector operands; - SmallVector> operandsBroadcasting; - auto broadcasting = false; - for (const auto& operand : stmt->operands) { - const auto& resolvedOperand = - resolveGateOperandInScope(operand, scope, stmt->debugInfo); - if (const auto* operand = std::get_if(&resolvedOperand)) { - operands.push_back(*operand); - } else if (const auto* operand = - std::get_if>(&resolvedOperand)) { - operandsBroadcasting.push_back(*operand); - broadcasting = true; - } - } - - if (broadcasting && !operands.empty()) { - throw qasm3::CompilerError("Gate operands must be single qubits or " - "quantum registers and not a mix of both.", - stmt->debugInfo); - } - - if (broadcasting && numCompatControls != 0) { - throw qasm3::CompilerError("OpenQASM 2 gates cannot be broadcasted.", - stmt->debugInfo); - } - - size_t broadcastWidth = 0; - if (broadcasting) { - for (const auto& operand : operandsBroadcasting) { - if (broadcastWidth == 0) { - broadcastWidth = operand.size(); - } else if (broadcastWidth != operand.size()) { - throw qasm3::CompilerError( - "All broadcasting operands must have the same width.", - stmt->debugInfo); - } - } - } - - auto invert = false; - size_t numControls = 0; - SmallVector posControls; - SmallVector negControls; - SmallVector> posControlsBroadcasting; - SmallVector> negControlsBroadcasting; - - // Parse modifiers - for (const auto& mod : stmt->modifiers) { - if (std::dynamic_pointer_cast(mod)) { - invert = !invert; - } else if (const auto* ctrlMod = - dynamic_cast(mod.get())) { - const auto n = - evaluatePositiveConstant(ctrlMod->expression, stmt->debugInfo, 1); - for (size_t i = 0; i < n; ++i, ++numControls) { - const auto positive = ctrlMod->ctrlType; - if (!broadcasting) { - if (numControls >= operands.size()) { - throw qasm3::CompilerError("Control index out of bounds.", - stmt->debugInfo); - } - auto operand = operands[numControls]; - if (positive) { - posControls.push_back(operand); - } else { - negControls.push_back(operand); - } - } else { - if (numControls >= operandsBroadcasting.size()) { - throw qasm3::CompilerError("Control index out of bounds.", - stmt->debugInfo); - } - const auto& operand = operandsBroadcasting[numControls]; - if (positive) { - posControlsBroadcasting.push_back(operand); - } else { - negControlsBroadcasting.push_back(operand); - } - } - } - } else { - throw qasm3::CompilerError( - "Only ctrl, negctrl, and inv modifiers are supported.", - stmt->debugInfo); - } - } - - // OpenQASM 2 compatibility: - // Append implicit control qubits - for (size_t i = 0; i < numCompatControls; ++i, ++numControls) { - if (numControls >= operands.size()) { - throw qasm3::CompilerError("Control index out of bounds.", - stmt->debugInfo); - } - posControls.push_back(operands[numControls]); - } - - // Remaining operands are target qubits - SmallVector targets; - SmallVector> targetsBroadcasting; - if (!broadcasting) { - targets = llvm::to_vector(llvm::drop_begin(operands, numControls)); - } else { - targetsBroadcasting = - llvm::to_vector(llvm::drop_begin(operandsBroadcasting, numControls)); - } - - // Inline compound gate - if (const auto* compound = - dynamic_cast(it->second.get())) { - if (broadcasting) { - throw qasm3::CompilerError( - "Broadcasted compound gates are not supported.", stmt->debugInfo); - } - applyCompoundGate(*compound, params, targets, posControls, negControls, - invert, stmt->debugInfo); - return; - } - - // Emit standard gate - const auto dispIt = GATE_DISPATCH.find(resolvedId); - if (dispIt == GATE_DISPATCH.end()) { - throw qasm3::CompilerError( - "No MLIR definition found for gate '" + id + "'.", stmt->debugInfo); - } - - if (it->second->getNParameters() != params.size()) { - throw qasm3::CompilerError("Invalid number of parameters for gate '" + - id + "'.", - stmt->debugInfo); - } - - if (!broadcasting) { - emitGate(dispIt->second, params, targets, posControls, negControls, - invert); - } else { - for (size_t b = 0; b < broadcastWidth; ++b) { - SmallVector bTargets; - bTargets.reserve(targetsBroadcasting.size()); - for (const auto& target : targetsBroadcasting) { - bTargets.push_back(target[b]); - } - SmallVector bPosControls; - bPosControls.reserve(posControlsBroadcasting.size()); - for (const auto& ctrl : posControlsBroadcasting) { - bPosControls.push_back(ctrl[b]); - } - SmallVector bNegControls; - bNegControls.reserve(negControlsBroadcasting.size()); - for (const auto& ctrl : negControlsBroadcasting) { - bNegControls.push_back(ctrl[b]); - } - emitGate(dispIt->second, params, bTargets, bPosControls, bNegControls, - invert); - } - } - } - - /// Helper function to build a gate with potential modifiers. - void buildModifiedGate(function_ref bodyFn, - ValueRange targets, ValueRange posControls, - ValueRange negControls, bool invert) { - auto wrappedBodyFn = [&](ValueRange qubits) { - if (invert) { - builder.inv(qubits, function_ref(bodyFn)); - } else { - bodyFn(qubits); - } - }; - - if (posControls.empty() && negControls.empty()) { - wrappedBodyFn(targets); - return; - } - - SmallVector controls; - controls.append(posControls.begin(), posControls.end()); - controls.append(negControls.begin(), negControls.end()); - - for (auto control : negControls) { - builder.x(control); - } - builder.ctrl(controls, targets, - function_ref(wrappedBodyFn)); - for (auto control : negControls) { - builder.x(control); - } - } - - /// Emit a standard gate. - void emitGate(const GateFn& gateFn, const SmallVector& params, - ValueRange targets, ValueRange posControls, - ValueRange negControls, bool invert) { - auto bodyFn = [&](ValueRange qubits) { gateFn(builder, qubits, params); }; - buildModifiedGate(bodyFn, targets, posControls, negControls, invert); - } - - /// Inline a compound gate. - void applyCompoundGate(const qasm3::CompoundGate& gate, - const SmallVector& params, ValueRange targets, - ValueRange posControls, ValueRange negControls, - bool invert, - const std::shared_ptr& debugInfo) { - assert(gate.parameterNames.size() == params.size()); - assert(gate.targetNames.size() == targets.size()); - - // Map from internal target name to index in targets list. This map is - // needed because the qubits may be aliased if the CompoundGate is inlined - // within a modifier region. - llvm::StringMap> targetsMap; - - for (const auto& [targetName, target] : - llvm::zip_equal(gate.targetNames, targets)) { - auto it = llvm::find(targets, target); - if (it == targets.end()) { - throw qasm3::CompilerError( - "Target '" + targetName + "' not found in operands.", debugInfo); - } - const auto index = - static_cast(std::distance(targets.begin(), it)); - targetsMap[targetName].push_back(index); - } - - // Bind parameters as constants - constEvalPass.pushEnv(); - for (size_t i = 0; i < gate.parameterNames.size(); ++i) { - constEvalPass.addConst(gate.parameterNames[i], - qasm3::const_eval::ConstEvalValue(params[i])); - } - - auto bodyFn = [&](ValueRange qubits) { - QubitScope localScope; - for (const auto& [name, indices] : targetsMap) { - SmallVector args; - for (auto index : indices) { - args.push_back(qubits[index]); - } - localScope[name] = std::move(args); - } - for (const auto& stmt : gate.body) { - if (const auto gateCall = - std::dynamic_pointer_cast(stmt)) { - applyGateCallStatement(gateCall, localScope); - continue; - } - throw qasm3::CompilerError("Compound operations with non-quantum " - "statements are not supported.", - debugInfo); - } - }; - - buildModifiedGate(bodyFn, targets, posControls, negControls, invert); - - constEvalPass.popEnv(); - } - - //===--- IfStatement helpers ------------------------------------------===// - - /// Helper function to emit quantum statements within an IfOp's then/else - /// regions. - void emitBlockStatements( - const std::vector>& statements, - const std::shared_ptr& debugInfo) { - for (const auto& stmt : statements) { - if (const auto gateCall = - std::dynamic_pointer_cast(stmt)) { - applyGateCallStatement(gateCall, qubitRegisters); - continue; - } - throw qasm3::CompilerError( - "If statements with non-quantum statements are not supported.", - debugInfo); - } - } - - /// Translate an OpenQASM 3 condition to MLIR. - [[nodiscard]] Value - translateCondition(const std::shared_ptr& condition, - const std::shared_ptr& debugInfo) { - // Single bit (c[0]) - if (const auto& id = - std::dynamic_pointer_cast(condition)) { - return lookupBitValue(id, debugInfo); - } - - // Unary negation (!c[0] or ~c[0]) - if (const auto unaryExpr = - std::dynamic_pointer_cast(condition)) { - if (unaryExpr->op != qasm3::UnaryExpression::LogicalNot && - unaryExpr->op != qasm3::UnaryExpression::BitwiseNot) { - throw qasm3::CompilerError( - "Only ! and ~ are supported in if statements.", debugInfo); - } - const auto& id = std::dynamic_pointer_cast( - unaryExpr->operand); - if (!id) { - throw qasm3::CompilerError("Unary expression has unsupported operand.", - debugInfo); - } - auto value = lookupBitValue(id, debugInfo); - auto trueValue = builder.boolConstant(true); - return arith::XOrIOp::create(builder, value, trueValue).getResult(); - } - - // Register comparison (creg == N, creg != N, etc.) - if (const auto binaryExpr = - std::dynamic_pointer_cast(condition)) { - throw qasm3::CompilerError("Register comparisons are not supported.", - debugInfo); - } - - throw qasm3::CompilerError( - "Unsupported condition expression in if statement.", debugInfo); - } - - /// Look up the most recent measurement result for a classical bit. - [[nodiscard]] Value - lookupBitValue(const std::shared_ptr& id, - const std::shared_ptr& debugInfo) const { - const auto& regName = id->identifier; - auto it = bitValues.find(regName); - if (it == bitValues.end()) { - throw qasm3::CompilerError("No classical bit of register '" + regName + - "' has been measured yet.", - debugInfo); - } - const auto& regBits = it->second; - - if (id->indices.empty()) { - assert(regBits.size() == 1); - return regBits[0]; - } - - if (id->indices.size() != 1 || - id->indices[0]->indexExpressions.size() != 1) { - throw qasm3::CompilerError("Only single-index expressions are supported.", - debugInfo); - } - const auto& indexExpression = id->indices[0]->indexExpressions[0]; - const auto index = evaluatePositiveConstant(indexExpression, debugInfo); - if (index >= regBits.size() || !regBits[index]) { - throw qasm3::CompilerError("Bit " + std::to_string(index) + - " of register '" + regName + - "' has been not measured yet.", - debugInfo); - } - return regBits[index]; - } - - //===--- Operand resolution helpers ------------------------------------===// - - /** - * @brief Resolve a qubit operand against the top-level qubitRegisters map. - * - * @return A variant containing - * - a `Value` if the operand is, e.g., `q[0]`, - * - a `Value` if the operand `q` is a single-qubit register, or - * - a `SmallVector` if the operand `q` is a multi-qubit register. - */ - [[nodiscard]] std::variant> - resolveGateOperand(const std::shared_ptr& operand, - const std::shared_ptr& debugInfo) { - return resolveGateOperandInScope(operand, qubitRegisters, debugInfo); - } - - /** - * @brief Resolve a qubit operand against @p scope. - * - * @return A variant containing - * - a `Value` if the operand is, e.g., `q[0]`, - * - a `Value` if the operand `q` is a single-qubit register, or - * - a `SmallVector` if the operand `q` is a multi-qubit register. - */ - [[nodiscard]] std::variant> - resolveGateOperandInScope( - const std::shared_ptr& operand, - const QubitScope& scope, - const std::shared_ptr& debugInfo) { - if (operand->isHardwareQubit()) { - return builder.staticQubit(operand->getHardwareQubit()); - } - - const auto& id = operand->getIdentifier(); - const auto& name = id->identifier; - auto it = scope.find(name); - if (it == scope.end()) { - throw qasm3::CompilerError("Unknown qubit register '" + name + "'.", - debugInfo); - } - - const auto& qubits = it->second; - - if (id->indices.empty()) { - if (qubits.size() == 1) { - return qubits[0]; - } - // Return full register - return qubits; - } - - if (id->indices.size() != 1 || - id->indices[0]->indexExpressions.size() != 1) { - throw qasm3::CompilerError("Only single-index expressions are supported.", - debugInfo); - } - const auto& indexExpression = id->indices[0]->indexExpressions[0]; - const auto index = evaluatePositiveConstant(indexExpression, debugInfo); - if (index >= qubits.size()) { - throw qasm3::CompilerError("Qubit index out of bounds.", debugInfo); - } - return qubits[index]; - } - - /// Resolve a classical bit operand. - [[nodiscard]] SmallVector resolveClassicalBits( - const std::shared_ptr& operand, - const std::shared_ptr& debugInfo) const { - const auto& name = operand->identifier; - auto it = classicalRegisters.find(name); - if (it == classicalRegisters.end()) { - throw qasm3::CompilerError("Unknown classical register '" + name + "'.", - debugInfo); - } - - const auto& creg = it->second; - SmallVector bits; - - if (operand->indices.empty()) { - for (int64_t i = 0; i < creg.size; ++i) { - bits.push_back(creg[i]); - } - return bits; - } - - if (operand->indices.size() != 1 || - operand->indices[0]->indexExpressions.size() != 1) { - throw qasm3::CompilerError("Only single-index expressions are supported.", - debugInfo); - } - const auto& indexExpression = operand->indices[0]->indexExpressions[0]; - const auto index = evaluatePositiveConstant(indexExpression, debugInfo); - if (std::cmp_greater_equal(index, creg.size)) { - throw qasm3::CompilerError("Classical bit index out of bounds.", - debugInfo); - } - bits.push_back(creg[static_cast(index)]); - return bits; - } - - /// Evaluate a constant expression to a positive integer. - static size_t - evaluatePositiveConstant(const std::shared_ptr& expr, - const std::shared_ptr& debugInfo, - size_t defaultValue = 0) { - if (!expr) { - return defaultValue; - } - const auto constVal = std::dynamic_pointer_cast(expr); - if (!constVal) { - throw qasm3::CompilerError("Expected a constant integer expression.", - debugInfo); - } - return static_cast(constVal->getUInt()); - } -}; - -} // namespace - //===----------------------------------------------------------------------===// // Public API //===----------------------------------------------------------------------===// OwningOpRef translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context) { - try { - auto buffer = - sourceMgr.getMemoryBuffer(sourceMgr.getMainFileID())->getBuffer(); - std::string_view view(buffer.data(), buffer.size()); - std::istringstream input((std::string(view))); - - qasm3::Parser parser(input); - const auto program = parser.parseProgram(); - - MLIRQasmImporter importer(context); - importer.visitProgram(program); - return importer.finalize(); - } catch (const qasm3::CompilerError& e) { - llvm::errs() << "Import error: " << e.what() << "\n"; - return nullptr; - } catch (const std::exception& e) { - llvm::errs() << "Import error: " << e.what() << "\n"; - return nullptr; - } + // Route diagnostics through source manager + const SourceMgrDiagnosticHandler handler(sourceMgr, context); + return detail::translateQASM3ToQC(sourceMgr, context); } OwningOpRef translateQASM3ToQC(StringRef source, diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp new file mode 100644 index 0000000000..7bafa0b674 --- /dev/null +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -0,0 +1,1328 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h" + +#include "ir/operations/OpType.hpp" +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" +#include "mlir/Dialect/QC/IR/QCOps.h" +#include "mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h" +#include "mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h" +#include "qasm3/Gate.hpp" +#include "qasm3/StdGates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qc { + +using qasm3::GateInfo; + +using detail::BitReference; +using detail::Condition; +using detail::Expr; +using detail::GateCall; +using detail::Lexer; +using detail::Modifier; +using detail::Operand; +using detail::Parser; + +namespace { + +/** + * @brief A stored compound-gate definition. + * + * @details + * Array members are bump-allocated by the parser and remain valid for the + * duration of the import. + */ +struct StoredGate { + ArrayRef parameters; + ArrayRef targets; + ArrayRef body; +}; + +/** + * @brief A named qubit binding in scope. + * + * @details + * For top-level registers, `memref` holds the backing register and `qubits` + * holds the eagerly extracted values. For compound gates, `memref` is @c null + * and `qubits` holds the aliased values. + */ +struct QubitBinding { + Value memref; + SmallVector qubits; +}; + +using QubitScope = llvm::StringMap; + +/// Signature: (builder, gate operands, gate parameters). Owns the callable, as +/// `GATE_DISPATCH` outlives the lambdas that build it. +using GateFn = std::function; + +} // namespace + +/// Build the table mapping each gate identifier to a `QCProgramBuilder` +/// emitter. +static llvm::StringMap buildGateDispatch() { + llvm::StringMap d; + + // ZeroTargetOneParameter + d["gphase"] = [](auto& b, auto /*q*/, auto p) { b.gphase(p[0]); }; + + // OneTargetZeroParameter + d["id"] = [](auto& b, auto q, auto) { b.id(q[0]); }; + d["x"] = [](auto& b, auto q, auto) { b.x(q[0]); }; + d["y"] = [](auto& b, auto q, auto) { b.y(q[0]); }; + d["z"] = [](auto& b, auto q, auto) { b.z(q[0]); }; + d["h"] = [](auto& b, auto q, auto) { b.h(q[0]); }; + d["s"] = [](auto& b, auto q, auto) { b.s(q[0]); }; + d["sdg"] = [](auto& b, auto q, auto) { b.sdg(q[0]); }; + d["t"] = [](auto& b, auto q, auto) { b.t(q[0]); }; + d["tdg"] = [](auto& b, auto q, auto) { b.tdg(q[0]); }; + d["sx"] = [](auto& b, auto q, auto) { b.sx(q[0]); }; + d["sxdg"] = [](auto& b, auto q, auto) { b.sxdg(q[0]); }; + + // OneTargetOneParameter + d["rx"] = [](auto& b, auto q, auto p) { b.rx(p[0], q[0]); }; + d["ry"] = [](auto& b, auto q, auto p) { b.ry(p[0], q[0]); }; + d["rz"] = [](auto& b, auto q, auto p) { b.rz(p[0], q[0]); }; + d["p"] = [](auto& b, auto q, auto p) { b.p(p[0], q[0]); }; + d["u1"] = [](auto& b, auto q, auto p) { b.p(p[0], q[0]); }; // alias + d["phase"] = [](auto& b, auto q, auto p) { b.p(p[0], q[0]); }; // alias + + // OneTargetTwoParameter + d["r"] = [](auto& b, auto q, auto p) { b.r(p[0], p[1], q[0]); }; + d["u2"] = [](auto& b, auto q, auto p) { b.u2(p[0], p[1], q[0]); }; + + // OneTargetThreeParameter + auto uFn = [](auto& b, auto q, auto p) { b.u(p[0], p[1], p[2], q[0]); }; + d["U"] = uFn; + d["u3"] = uFn; // alias + d["u"] = uFn; // alias + + // TwoTargetZeroParameter + d["swap"] = [](auto& b, auto q, auto) { b.swap(q[0], q[1]); }; + d["iswap"] = [](auto& b, auto q, auto) { b.iswap(q[0], q[1]); }; + d["dcx"] = [](auto& b, auto q, auto) { b.dcx(q[0], q[1]); }; + d["ecr"] = [](auto& b, auto q, auto) { b.ecr(q[0], q[1]); }; + + // TwoTargetOneParameter + d["rxx"] = [](auto& b, auto q, auto p) { b.rxx(p[0], q[0], q[1]); }; + d["ryy"] = [](auto& b, auto q, auto p) { b.ryy(p[0], q[0], q[1]); }; + d["rzx"] = [](auto& b, auto q, auto p) { b.rzx(p[0], q[0], q[1]); }; + d["rzz"] = [](auto& b, auto q, auto p) { b.rzz(p[0], q[0], q[1]); }; + + // TwoTargetTwoParameter + d["xx_plus_yy"] = [](auto& b, auto q, auto p) { + b.xx_plus_yy(p[0], p[1], q[0], q[1]); + }; + d["xx_minus_yy"] = [](auto& b, auto q, auto p) { + b.xx_minus_yy(p[0], p[1], q[0], q[1]); + }; + + // Controlled OneTargetZeroParameter + d["cx"] = [](auto& b, auto q, auto) { b.cx(q[0], q[1]); }; + d["cnot"] = [](auto& b, auto q, auto) { b.cx(q[0], q[1]); }; // alias + d["cy"] = [](auto& b, auto q, auto) { b.cy(q[0], q[1]); }; + d["cz"] = [](auto& b, auto q, auto) { b.cz(q[0], q[1]); }; + d["ch"] = [](auto& b, auto q, auto) { b.ch(q[0], q[1]); }; + d["csx"] = [](auto& b, auto q, auto) { b.csx(q[0], q[1]); }; + + // Controlled OneTargetOneParameter + d["crx"] = [](auto& b, auto q, auto p) { b.crx(p[0], q[0], q[1]); }; + d["cry"] = [](auto& b, auto q, auto p) { b.cry(p[0], q[0], q[1]); }; + d["crz"] = [](auto& b, auto q, auto p) { b.crz(p[0], q[0], q[1]); }; + d["cp"] = [](auto& b, auto q, auto p) { b.cp(p[0], q[0], q[1]); }; + d["cphase"] = [](auto& b, auto q, auto p) { + b.cp(p[0], q[0], q[1]); + }; // alias + + // Controlled TwoTargetZeroParameter + d["cswap"] = [](auto& b, auto q, auto) { b.cswap(q[0], q[1], q[2]); }; + d["fredkin"] = [](auto& b, auto q, auto) { + b.cswap(q[0], q[1], q[2]); + }; // alias + + // Multi-controlled gates + auto mcxFn = [](auto& b, auto q, auto) { b.mcx(q.drop_back(1), q.back()); }; + d["mcx"] = mcxFn; + d["mcx_gray"] = mcxFn; + + d["mcx_vchain"] = [](auto& b, auto q, auto) { + const size_t n = q.size() - ((q.size() + 1) / 2) + 2; + b.mcx(q.slice(0, n - 1), q[n - 1]); + }; + + d["mcx_recursive"] = [](auto& b, auto q, auto) { + const size_t n = (q.size() > 5) ? q.size() - 1 : q.size(); + b.mcx(q.slice(0, n - 1), q[n - 1]); + }; + + d["mcphase"] = [](auto& b, auto q, auto p) { + b.mcp(p[0], q.drop_back(1), q.back()); + }; + + return d; +} + +/// Build the table mapping a gate identifier to its metadata. +static llvm::StringMap> buildGateTable() { + llvm::StringMap> t; + for (const auto& [name, gate] : qasm3::STANDARD_GATES) { + const auto* standard = dynamic_cast(gate.get()); + assert(standard != nullptr && "STANDARD_GATES entry is not a StandardGate"); + t.insert({name, standard->info}); + } + + constexpr GateInfo mcxInfo{ + .nControls = 0, .nTargets = 0, .nParameters = 0, .type = ::qc::OpType::X}; + t["mcx"] = mcxInfo; + t["mcx_gray"] = mcxInfo; + t["mcx_vchain"] = mcxInfo; + t["mcx_recursive"] = mcxInfo; + + t["mcphase"] = GateInfo{ + .nControls = 0, .nTargets = 0, .nParameters = 1, .type = ::qc::OpType::P}; + + return t; +} + +/// Look up a built-in numeric constant and emit it as an `f64`-typed value. +static std::optional lookupBuiltinConstant(StringRef name, + QCProgramBuilder& builder) { + auto constant = [&](double value) -> Value { + return arith::ConstantOp::create(builder, builder.getF64FloatAttr(value)) + .getResult(); + }; + if (name == "pi" || name == "π") { + return constant(std::numbers::pi); + } + if (name == "tau" || name == "τ") { + return constant(2 * std::numbers::pi); + } + if (name == "euler" || name == "ℇ") { + return constant(std::numbers::e); + } + return std::nullopt; +} + +//===----------------------------------------------------------------------===// +// QCEmitter +//===----------------------------------------------------------------------===// + +namespace { + +/// Map from gate identifier to `QCProgramBuilder` emitter. +const llvm::StringMap GATE_DISPATCH = buildGateDispatch(); + +/** + * @brief Lowers OpenQASM 3 parse events to QC operations. + * + * @details + * Models the sink concept consumed by `Parser`. All events emit eagerly via the + * `QCProgramBuilder` and report errors as diagnostics through `LogicalResult`. + * Handles name resolution, semantic validation, and target-specific + * restrictions. + */ +class QCEmitter { +public: + QCEmitter(MLIRContext* ctx, llvm::SourceMgr& sourceMgr) + : builder(ctx), sourceMgr(sourceMgr), gates(buildGateTable()) {} + + void initialize() { builder.initialize(); } + OwningOpRef finalize() { return builder.finalize(); } + + //===--- Diagnostics --------------------------------------------------===// + + LogicalResult error(SMLoc loc, const Twine& message) { + Location location = UnknownLoc::get(builder.getContext()); + if (loc.isValid()) { + const auto [line, col] = sourceMgr.getLineAndColumn(loc); + location = + FileLineColLoc::get(builder.getStringAttr(""), line, col); + } + return emitError(location, message); + } + + //===--- Include ------------------------------------------------------===// + + LogicalResult include(SMLoc loc, StringRef filename) { + if (filename != "stdgates.inc" && filename != "qelib1.inc") { + return error(loc, "unsupported include '" + filename + + "'; only 'stdgates.inc' and 'qelib1.inc' are " + "supported"); + } + return success(); + } + + //===--- Declarations and assignments ---------------------------------===// + + LogicalResult numericDecl(SMLoc loc, detail::NumericType type, StringRef id, + const Expr* initializer) { + if (failed(declare(loc, id))) { + return failure(); + } + switch (type) { + case detail::NumericType::Float: + variableKinds[id] = VariableKind::Float; + break; + case detail::NumericType::Int: + variableKinds[id] = VariableKind::Int; + break; + } + if (initializer != nullptr) { + return assignNumeric(id, *initializer); + } + return success(); + } + + LogicalResult boolDecl(SMLoc loc, StringRef id, + const Condition* initializer) { + if (failed(declare(loc, id))) { + return failure(); + } + variableKinds[id] = VariableKind::Bool; + if (initializer != nullptr) { + return assignBool(id, *initializer); + } + return success(); + } + + LogicalResult numericAssign(SMLoc /*loc*/, StringRef id, const Expr& value) { + return assignNumeric(id, value); + } + + LogicalResult boolAssign(SMLoc /*loc*/, StringRef id, + const Condition& value) { + return assignBool(id, value); + } + + [[nodiscard]] std::optional + classicalKind(StringRef id) const { + auto it = variableKinds.find(id); + if (it == variableKinds.end()) { + return std::nullopt; + } + return it->second == VariableKind::Bool ? detail::ClassicalKind::Boolean + : detail::ClassicalKind::Numeric; + } + + /** + * @brief Bind a numeric variable @p id to the value of @p value. + * + * @details + * Shared by `numericDecl` and `numericAssign`. + */ + LogicalResult assignNumeric(StringRef id, const Expr& value) { + switch (variableKinds.lookup(id)) { + case VariableKind::Float: { + auto emitted = emitAngle(value); + if (failed(emitted)) { + return failure(); + } + numericConstants.insert(id, *emitted); + return success(); + } + case VariableKind::Int: { + auto emitted = evaluateConstant(value); + if (failed(emitted)) { + return failure(); + } + integerConstants.insert(id, *emitted); + return success(); + } + default: + llvm_unreachable("unknown variable kind"); + } + } + + /** + * @brief Bind a boolean variable @p id to the value of @p value. + * + * @details + * Shared by `boolDecl` and `boolAssign`. + */ + LogicalResult assignBool(StringRef id, const Condition& value) { + auto emitted = emitCondition(value); + if (failed(emitted)) { + return failure(); + } + booleanConstants.insert(id, *emitted); + return success(); + } + + LogicalResult qubitRegister(SMLoc loc, StringRef id, const Expr* size) { + if (failed(declare(loc, id))) { + return failure(); + } + if (size != nullptr) { + auto count = evaluateConstant(*size); + if (failed(count)) { + return failure(); + } + const auto [value, qubits] = builder.allocQubitRegister(*count); + qubitRegisters[id] = {.memref = value, .qubits = qubits}; + } else { + qubitRegisters[id] = {.memref = nullptr, + .qubits = {builder.allocQubit()}}; + } + return success(); + } + + LogicalResult classicalRegister(SMLoc loc, StringRef id, const Expr* size) { + if (failed(declare(loc, id))) { + return failure(); + } + int64_t count = 1; + if (size != nullptr) { + auto evaluated = evaluateConstant(*size); + if (failed(evaluated)) { + return failure(); + } + count = *evaluated; + } + classicalRegisters[id] = builder.allocClassicalBitRegister(count, id.str()); + return success(); + } + + //===--- Measure ------------------------------------------------------===// + + LogicalResult measure(SMLoc loc, const BitReference& target, + const Operand& operand) { + auto bits = resolveClassicalBits(target); + if (failed(bits)) { + return failure(); + } + auto resolved = resolveOperand(operand, qubitRegisters); + if (failed(resolved)) { + return failure(); + } + SmallVector qubits; + if (auto* qubit = std::get_if(&*resolved)) { + qubits.push_back(*qubit); + } else { + qubits = std::get>(*resolved); + } + if (bits->size() != qubits.size()) { + return error(loc, "the classical register and the quantum register must " + "have the same width"); + } + for (const auto& [bit, qubit] : zip_equal(*bits, qubits)) { + auto result = MeasureOp::create( + builder, qubit, builder.getStringAttr(bit.registerName), + builder.getI64IntegerAttr(bit.registerSize), + builder.getI64IntegerAttr(bit.registerIndex)) + .getResult(); + auto& registerBits = bitValues[bit.registerName]; + const auto index = static_cast(bit.registerIndex); + if (registerBits.size() <= index) { + registerBits.resize(index + 1); + } + registerBits[index] = result; + } + return success(); + } + + //===--- Reset --------------------------------------------------------===// + + LogicalResult reset(SMLoc /*loc*/, const Operand& operand) { + auto resolved = resolveOperand(operand, qubitRegisters); + if (failed(resolved)) { + return failure(); + } + if (auto* qubit = std::get_if(&*resolved)) { + builder.reset(*qubit); + } else { + for (auto qubit : std::get>(*resolved)) { + builder.reset(qubit); + } + } + return success(); + } + + //===--- Barrier ------------------------------------------------------===// + + LogicalResult barrier(SMLoc /*loc*/, ArrayRef operands) { + SmallVector qubits; + for (const auto& operand : operands) { + auto resolved = resolveOperand(operand, qubitRegisters); + if (failed(resolved)) { + return failure(); + } + if (auto* qubit = std::get_if(&*resolved)) { + qubits.push_back(*qubit); + } else { + append_range(qubits, std::get>(*resolved)); + } + } + builder.barrier(qubits); + return success(); + } + + //===--- Gate definitions and calls -----------------------------------===// + + LogicalResult gateDefinition(SMLoc loc, StringRef id, + ArrayRef parameters, + ArrayRef targets, + ArrayRef body) { + if (gates.contains(id)) { + return error(loc, "gate '" + id + "' already declared"); + } + for (size_t i = 0; i < parameters.size(); ++i) { + for (size_t j = i + 1; j < parameters.size(); ++j) { + if (parameters[i] == parameters[j]) { + return error(loc, "parameter is already declared in compound gate"); + } + } + } + for (size_t i = 0; i < targets.size(); ++i) { + for (size_t j = i + 1; j < targets.size(); ++j) { + if (targets[i] == targets[j]) { + return error(loc, "target is already declared in compound gate"); + } + } + } + gates[id] = + StoredGate{.parameters = parameters, .targets = targets, .body = body}; + return success(); + } + + LogicalResult gateCall(const GateCall& call) { + return emitGateCall(call, qubitRegisters); + } + + //===--- Control flow -------------------------------------------------===// + + LogicalResult ifConditionOnly(SMLoc /*loc*/, const Condition& condition) { + return LogicalResult{emitCondition(condition)}; + } + + struct IfScope { + scf::IfOp op; + OpBuilder::InsertPoint savedInsertionPoint; + }; + + FailureOr ifBegin(SMLoc /*loc*/, const Condition& condition, + bool invert) { + auto condOrFailure = emitCondition(condition); + if (failed(condOrFailure)) { + return failure(); + } + auto cond = *condOrFailure; + if (invert) { + auto trueValue = builder.boolConstant(true); + cond = arith::XOrIOp::create(builder, cond, trueValue).getResult(); + } + auto ifOp = scf::IfOp::create(builder, cond, /*withElseRegion=*/false); + IfScope scope{.op = ifOp, + .savedInsertionPoint = builder.saveInsertionPoint()}; + builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); + return scope; + } + + LogicalResult ifElse(IfScope& scope) { + auto* elseBlock = builder.createBlock(&scope.op.getElseRegion()); + builder.setInsertionPointToStart(elseBlock); + return success(); + } + + LogicalResult ifEnd(IfScope& scope, bool hadElse) { + if (hadElse) { + scf::YieldOp::create(builder); + } + builder.restoreInsertionPoint(scope.savedInsertionPoint); + return success(); + } + + LogicalResult forStmt(SMLoc loc, StringRef variable, const Expr& start, + const Expr& step, const Expr& stop, + function_ref body) { + auto stepConstant = evaluateConstant(step); + if (failed(stepConstant)) { + return failure(); + } + if (*stepConstant < 1) { + return error(loc, "for loops with a non-positive step are not supported"); + } + + auto startValue = emitIndex(start); + auto stepValue = emitIndex(step); + auto stopValue = emitIndex(stop); + if (failed(startValue) || failed(stepValue) || failed(stopValue)) { + return failure(); + } + + // OpenQASM 3's range is inclusive of the stop value + auto one = + arith::ConstantOp::create(builder, builder.getIndexAttr(1)).getResult(); + auto inclusiveStop = + arith::AddIOp::create(builder, *stopValue, one).getResult(); + + ValueScope loopScope(loopVariables); + ValueScope loadScope(dynamicallyLoadedQubits); + + auto status = success(); + builder.scfFor(*startValue, inclusiveStop, *stepValue, [&](Value iv) { + loopVariables.insert(variable, iv); + status = body(); + }); + return status; + } + + LogicalResult whileStmt(SMLoc /*loc*/, const Condition& condition, + function_ref body) { + auto status = success(); + builder.scfWhile( + [&] { + ValueScope loadScope(dynamicallyLoadedQubits); + auto cond = emitCondition(condition); + if (failed(cond)) { + status = failure(); + builder.scfCondition(builder.boolConstant(false)); + } else { + builder.scfCondition(*cond); + } + }, + [&] { + ValueScope loadScope(dynamicallyLoadedQubits); + if (succeeded(status)) { + status = body(); + } + }); + return status; + } + +private: + using ValueTable = llvm::ScopedHashTable; + using ValueScope = llvm::ScopedHashTableScope; + using IntegerTable = llvm::ScopedHashTable; + using IntegerScope = llvm::ScopedHashTableScope; + + /** + * @brief The declared type of a classical scalar variable. + * + * @details + * Tracked so that assignments know how to lower their right-hand side. + */ + enum class VariableKind : uint8_t { Float, Int, Bool }; + + //===--- Declarations -------------------------------------------------===// + + LogicalResult declare(SMLoc loc, StringRef id) { + if (!declaredNames.insert(id).second) { + return error(loc, "identifier '" + id + "' already declared"); + } + return success(); + } + + //===--- Gate calls ---------------------------------------------------===// + + LogicalResult emitGateCall(const GateCall& call, const QubitScope& scope) { + auto it = gates.find(call.identifier); + if (it == gates.end()) { + return error(call.loc, "no OpenQASM definition found for gate '" + + call.identifier + "'"); + } + + // Resolve parameters + SmallVector parameters; + parameters.reserve(call.parameters.size()); + for (const auto* argument : call.parameters) { + auto value = emitAngle(*argument); + if (failed(value)) { + return failure(); + } + parameters.push_back(*value); + } + + // Resolve operands. If any operand is a register, the call is broadcast + // over the qubits of the register(s). All registers must have the same + // length, and any single-qubit operands are repeated in each iteration. + SmallVector>> resolvedOperands; + resolvedOperands.reserve(call.operands.size()); + std::optional broadcastWidth; + for (const auto& operand : call.operands) { + auto resolved = resolveOperand(operand, scope); + if (failed(resolved)) { + return failure(); + } + if (const auto* reg = std::get_if>(&*resolved)) { + if (broadcastWidth && *broadcastWidth != reg->size()) { + return error(call.loc, + "all broadcasting operands must have the same width"); + } + broadcastWidth = reg->size(); + } + resolvedOperands.push_back(std::move(*resolved)); + } + + // No broadcasting + if (!broadcastWidth) { + SmallVector operands; + operands.reserve(resolvedOperands.size()); + for (const auto& resolved : resolvedOperands) { + operands.push_back(std::get(resolved)); + } + return emitGateInvocation(it->second, call.identifier, parameters, + call.modifiers, operands, call.loc); + } + + for (size_t b = 0; b < *broadcastWidth; ++b) { + SmallVector operands; + operands.reserve(resolvedOperands.size()); + for (const auto& resolved : resolvedOperands) { + if (const auto* value = std::get_if(&resolved)) { + operands.push_back(*value); + } else { + operands.push_back(std::get>(resolved)[b]); + } + } + if (failed(emitGateInvocation(it->second, call.identifier, parameters, + call.modifiers, operands, call.loc))) { + return failure(); + } + } + return success(); + } + + LogicalResult + emitGateInvocation(const std::variant& definition, + StringRef id, ValueRange parameters, + ArrayRef modifiers, + const SmallVector& operands, SMLoc loc) { + auto split = splitControlsAndTargets(modifiers, operands, loc); + if (failed(split)) { + return failure(); + } + if (const auto* compound = std::get_if(&definition)) { + return emitCompoundGate(*compound, parameters, split->targets, + split->posControls, split->negControls, + split->invert, loc); + } + const auto& gate = std::get(definition); + auto gateFn = resolveStandardGate(gate, id, parameters, loc); + if (failed(gateFn)) { + return failure(); + } + emitStandardGate(**gateFn, parameters, split->targets, split->posControls, + split->negControls, split->invert); + return success(); + } + + template struct ControlsAndTargets { + bool invert = false; + SmallVector posControls; + SmallVector negControls; + SmallVector targets; + }; + + FailureOr controlCount(const Expr* argument) { + if (argument == nullptr) { + return static_cast(1); + } + auto value = evaluateConstant(*argument); + if (failed(value)) { + return failure(); + } + return static_cast(*value); + } + + template + FailureOr> + splitControlsAndTargets(ArrayRef modifiers, + const SmallVector& operands, SMLoc loc) { + ControlsAndTargets result; + size_t numControls = 0; + for (const auto& modifier : modifiers) { + switch (modifier.kind) { + case Modifier::Kind::Inv: + result.invert = !result.invert; + break; + case Modifier::Kind::Pow: + return error(loc, "power modifiers are not supported yet"); + case Modifier::Kind::Ctrl: + case Modifier::Kind::NegCtrl: { + auto count = controlCount(modifier.argument); + if (failed(count)) { + return failure(); + } + auto& controls = modifier.kind == Modifier::Kind::Ctrl + ? result.posControls + : result.negControls; + for (size_t i = 0; i < *count; ++i, ++numControls) { + if (numControls >= operands.size()) { + return error(loc, "control index out of bounds"); + } + controls.push_back(operands[numControls]); + } + break; + } + } + } + result.targets = to_vector(drop_begin(operands, numControls)); + return result; + } + + LogicalResult emitCompoundGate(const StoredGate& gate, ValueRange parameters, + ValueRange targets, ValueRange posControls, + ValueRange negControls, bool invert, + SMLoc loc) { + if (gate.parameters.size() != parameters.size()) { + return error(loc, "invalid number of parameters for compound gate"); + } + if (gate.targets.size() != targets.size()) { + return error(loc, "invalid number of target qubits for compound gate"); + } + + // Map each internal target name to its position(s) in the operand list. + // Positions may repeat if the qubits are aliased within a modifier region. + llvm::StringMap> targetsMap; + for (const auto& [targetName, target] : zip_equal(gate.targets, targets)) { + auto it = llvm::find(targets, target); + if (it == targets.end()) { + return error(loc, "target '" + targetName + "' not found in operands"); + } + targetsMap[targetName].push_back( + static_cast(std::distance(targets.begin(), it))); + } + + ValueScope parameterScope(numericConstants); + for (const auto& [name, value] : zip_equal(gate.parameters, parameters)) { + numericConstants.insert(name, value); + } + + auto status = success(); + auto bodyFn = [&](ValueRange qubits) { + QubitScope localScope; + for (const auto& [name, indices] : targetsMap) { + SmallVector args; + for (auto index : indices) { + args.push_back(qubits[index]); + } + localScope[name] = {.memref = nullptr, .qubits = std::move(args)}; + } + for (const auto& bodyCall : gate.body) { + if (succeeded(status)) { + status = emitGateCall(bodyCall, localScope); + } + } + }; + emitModifiedGate(bodyFn, targets, posControls, negControls, invert); + return status; + } + + FailureOr resolveStandardGate(const GateInfo& gate, + StringRef id, + ValueRange parameters, + SMLoc loc) { + const auto it = GATE_DISPATCH.find(id); + if (it == GATE_DISPATCH.end()) { + return error(loc, "no MLIR definition found for gate '" + id + "'"); + } + if (gate.nParameters != parameters.size()) { + return error(loc, "invalid number of parameters for gate '" + id + "'"); + } + return &it->second; + } + + void emitModifiedGate(function_ref bodyFn, + ValueRange targets, ValueRange posControls, + ValueRange negControls, bool invert) { + auto wrappedBodyFn = [&](ValueRange qubits) { + if (invert) { + builder.inv(qubits, function_ref(bodyFn)); + } else { + bodyFn(qubits); + } + }; + + if (posControls.empty() && negControls.empty()) { + wrappedBodyFn(targets); + return; + } + + SmallVector controls; + controls.append(posControls.begin(), posControls.end()); + controls.append(negControls.begin(), negControls.end()); + + for (auto control : negControls) { + builder.x(control); + } + builder.ctrl(controls, targets, + function_ref(wrappedBodyFn)); + for (auto control : negControls) { + builder.x(control); + } + } + + void emitStandardGate(const GateFn& gateFn, ValueRange parameters, + ValueRange targets, ValueRange posControls, + ValueRange negControls, bool invert) { + auto bodyFn = [&](ValueRange qubits) { + gateFn(builder, qubits, parameters); + }; + emitModifiedGate(bodyFn, targets, posControls, negControls, invert); + } + + //===--- Bit reference resolution -------------------------------------===// + + FailureOr lookupBitValue(const BitReference& bit) { + auto it = bitValues.find(bit.identifier); + if (it == bitValues.end()) { + return error(bit.loc, "no classical bit of register '" + bit.identifier + + "' has been measured yet"); + } + const auto& registerBits = it->second; + + if (bit.index == nullptr) { + return registerBits[0]; + } + auto index = evaluateConstant(*bit.index); + if (failed(index)) { + return failure(); + } + const auto i = static_cast(*index); + if (i >= registerBits.size() || !registerBits[i]) { + return error(bit.loc, "bit " + Twine(*index) + " of register '" + + bit.identifier + "' has not been measured yet"); + } + return registerBits[i]; + } + + //===--- Operand resolution -------------------------------------------===// + + FailureOr>> + resolveOperand(const Operand& operand, const QubitScope& scope) { + if (operand.hardwareQubit) { + return std::variant>{ + builder.staticQubit(*operand.hardwareQubit)}; + } + + auto it = scope.find(operand.identifier); + if (it == scope.end()) { + return error(operand.loc, + "unknown qubit register '" + operand.identifier + "'"); + } + const auto& [memref, qubits] = it->second; + + if (operand.index == nullptr) { + if (qubits.size() == 1) { + return std::variant>{qubits[0]}; + } + return std::variant>{qubits}; + } + + const auto& indexExpr = *operand.index; + if (isConstantIndex(indexExpr)) { + auto index = evaluateConstant(indexExpr); + if (failed(index)) { + return failure(); + } + const auto i = static_cast(*index); + if (i >= qubits.size()) { + return error(operand.loc, "qubit index out of bounds"); + } + return std::variant>{qubits[i]}; + } + + if (!memref) { + return error(operand.loc, + "dynamic qubit indexing requires a qubit register"); + } + auto loaded = loadDynamicElement(operand.identifier, memref, indexExpr); + if (failed(loaded)) { + return failure(); + } + return std::variant>{*loaded}; + } + + [[nodiscard]] bool isConstantIndex(const Expr& expr) const { + switch (expr.kind) { + case Expr::Kind::Int: + case Expr::Kind::Float: + return true; + case Expr::Kind::Identifier: + // A declared integer constant folds; a loop variable does not. + return integerConstants.count(expr.identifier) != 0; + case Expr::Kind::Neg: + return isConstantIndex(*expr.lhs); + case Expr::Kind::Add: + case Expr::Kind::Sub: + case Expr::Kind::Mul: + case Expr::Kind::Div: + return isConstantIndex(*expr.lhs) && isConstantIndex(*expr.rhs); + } + llvm_unreachable("unknown expression kind"); + } + + static std::string indexKey(const Expr& expr) { + switch (expr.kind) { + case Expr::Kind::Int: + return std::to_string(expr.intValue); + case Expr::Kind::Float: + return std::to_string(expr.floatValue); + case Expr::Kind::Identifier: + return expr.identifier.str(); + case Expr::Kind::Neg: + return "(-" + indexKey(*expr.lhs) + ")"; + case Expr::Kind::Add: + return "(" + indexKey(*expr.lhs) + "+" + indexKey(*expr.rhs) + ")"; + case Expr::Kind::Sub: + return "(" + indexKey(*expr.lhs) + "-" + indexKey(*expr.rhs) + ")"; + case Expr::Kind::Mul: + return "(" + indexKey(*expr.lhs) + "*" + indexKey(*expr.rhs) + ")"; + case Expr::Kind::Div: + return "(" + indexKey(*expr.lhs) + "/" + indexKey(*expr.rhs) + ")"; + } + llvm_unreachable("unknown expression kind"); + } + + FailureOr loadDynamicElement(StringRef name, Value memref, + const Expr& indexExpr) { + const std::string key = name.str() + "[" + indexKey(indexExpr) + "]"; + if (Value cached = dynamicallyLoadedQubits.lookup(key)) { + return cached; + } + auto index = emitIndex(indexExpr); + if (failed(index)) { + return failure(); + } + auto loaded = builder.memrefLoad(memref, *index); + dynamicallyLoadedQubits.insert(keySaver.save(key), loaded); + return loaded; + } + + FailureOr> + resolveClassicalBits(const BitReference& reference) { + auto it = classicalRegisters.find(reference.identifier); + if (it == classicalRegisters.end()) { + return error(reference.loc, + "unknown classical register '" + reference.identifier + "'"); + } + const auto& creg = it->second; + + SmallVector bits; + if (reference.index == nullptr) { + for (int64_t i = 0; i < creg.size; ++i) { + bits.push_back(creg[i]); + } + return bits; + } + auto index = evaluateConstant(*reference.index); + if (failed(index)) { + return failure(); + } + if (*index < 0 || *index >= creg.size) { + return error(reference.loc, "classical bit index out of bounds"); + } + bits.push_back(creg[*index]); + return bits; + } + + //===--- Conditions ---------------------------------------------------===// + + FailureOr emitCondition(const Condition& condition) { + switch (condition.kind) { + case Condition::Kind::Measurement: { + auto resolved = resolveOperand(condition.operand, qubitRegisters); + if (failed(resolved)) { + return failure(); + } + auto* qubit = std::get_if(&*resolved); + if (qubit == nullptr) { + return error(condition.loc, + "measurement condition must be a single qubit"); + } + return builder.measure(*qubit); + } + case Condition::Kind::Bit: + // A bare identifier may name a declared `bool`; otherwise it is a + // classical bit reference. + if (condition.bit.index == nullptr) { + if (Value value = booleanConstants.lookup(condition.bit.identifier)) { + return value; + } + } + return lookupBitValue(condition.bit); + case Condition::Kind::Literal: + return builder.boolConstant(condition.literalValue); + case Condition::Kind::Not: { + auto value = emitCondition(*condition.lhs); + if (failed(value)) { + return failure(); + } + auto trueValue = builder.boolConstant(true); + return arith::XOrIOp::create(builder, *value, trueValue).getResult(); + } + case Condition::Kind::And: + case Condition::Kind::Or: { + auto lhs = emitCondition(*condition.lhs); + auto rhs = emitCondition(*condition.rhs); + if (failed(lhs) || failed(rhs)) { + return failure(); + } + if (condition.kind == Condition::Kind::And) { + return arith::AndIOp::create(builder, *lhs, *rhs).getResult(); + } + return arith::OrIOp::create(builder, *lhs, *rhs).getResult(); + } + } + llvm_unreachable("unknown condition kind"); + } + + //===--- Expressions --------------------------------------------------===// + + FailureOr emitAngle(const Expr& expr) { + switch (expr.kind) { + case Expr::Kind::Int: + return arith::ConstantOp::create( + builder, + builder.getF64FloatAttr(static_cast(expr.intValue))) + .getResult(); + case Expr::Kind::Float: + return arith::ConstantOp::create(builder, + builder.getF64FloatAttr(expr.floatValue)) + .getResult(); + case Expr::Kind::Identifier: + return resolveParameter(expr.identifier, expr.loc); + case Expr::Kind::Neg: { + auto operand = emitAngle(*expr.lhs); + if (failed(operand)) { + return failure(); + } + return arith::NegFOp::create(builder, *operand).getResult(); + } + case Expr::Kind::Add: + case Expr::Kind::Sub: + case Expr::Kind::Mul: + case Expr::Kind::Div: { + auto lhs = emitAngle(*expr.lhs); + auto rhs = emitAngle(*expr.rhs); + if (failed(lhs) || failed(rhs)) { + return failure(); + } + switch (expr.kind) { + case Expr::Kind::Add: + return arith::AddFOp::create(builder, *lhs, *rhs).getResult(); + case Expr::Kind::Sub: + return arith::SubFOp::create(builder, *lhs, *rhs).getResult(); + case Expr::Kind::Mul: + return arith::MulFOp::create(builder, *lhs, *rhs).getResult(); + default: + return arith::DivFOp::create(builder, *lhs, *rhs).getResult(); + } + } + } + llvm_unreachable("unknown expression kind"); + } + + FailureOr resolveParameter(StringRef name, SMLoc loc) { + if (auto value = numericConstants.lookup(name)) { + return value; + } + // An integer constant used as an angle is materialized as an `f64`. + if (integerConstants.count(name) != 0) { + return arith::ConstantOp::create( + builder, builder.getF64FloatAttr(static_cast( + integerConstants.lookup(name)))) + .getResult(); + } + if (auto value = lookupBuiltinConstant(name, builder)) { + return *value; + } + return error(loc, "unknown identifier '" + name + "'"); + } + + FailureOr emitIndex(const Expr& expr) { + switch (expr.kind) { + case Expr::Kind::Int: + return arith::ConstantOp::create(builder, + builder.getIndexAttr(expr.intValue)) + .getResult(); + case Expr::Kind::Neg: { + auto operand = emitIndex(*expr.lhs); + if (failed(operand)) { + return failure(); + } + auto zero = arith::ConstantOp::create(builder, builder.getIndexAttr(0)) + .getResult(); + return arith::SubIOp::create(builder, zero, *operand).getResult(); + } + case Expr::Kind::Add: + case Expr::Kind::Sub: + case Expr::Kind::Mul: + case Expr::Kind::Div: { + auto lhs = emitIndex(*expr.lhs); + auto rhs = emitIndex(*expr.rhs); + if (failed(lhs) || failed(rhs)) { + return failure(); + } + switch (expr.kind) { + case Expr::Kind::Add: + return arith::AddIOp::create(builder, *lhs, *rhs).getResult(); + case Expr::Kind::Sub: + return arith::SubIOp::create(builder, *lhs, *rhs).getResult(); + case Expr::Kind::Mul: + return arith::MulIOp::create(builder, *lhs, *rhs).getResult(); + default: + return arith::DivSIOp::create(builder, *lhs, *rhs).getResult(); + } + } + case Expr::Kind::Identifier: + if (Value value = loopVariables.lookup(expr.identifier)) { + return value; + } + if (integerConstants.count(expr.identifier) != 0) { + return arith::ConstantOp::create( + builder, builder.getIndexAttr( + integerConstants.lookup(expr.identifier))) + .getResult(); + } + return error(expr.loc, "expected an integer expression"); + case Expr::Kind::Float: + return error(expr.loc, "expected an integer expression"); + } + llvm_unreachable("unknown expression kind"); + } + + FailureOr evaluateConstant(const Expr& expr) { + switch (expr.kind) { + case Expr::Kind::Int: + return expr.intValue; + case Expr::Kind::Neg: { + auto operand = evaluateConstant(*expr.lhs); + if (failed(operand)) { + return failure(); + } + return -*operand; + } + case Expr::Kind::Add: + case Expr::Kind::Sub: + case Expr::Kind::Mul: + case Expr::Kind::Div: { + auto lhs = evaluateConstant(*expr.lhs); + auto rhs = evaluateConstant(*expr.rhs); + if (failed(lhs) || failed(rhs)) { + return failure(); + } + switch (expr.kind) { + case Expr::Kind::Add: + return *lhs + *rhs; + case Expr::Kind::Sub: + return *lhs - *rhs; + case Expr::Kind::Mul: + return *lhs * *rhs; + default: + if (*rhs == 0) { + return error(expr.loc, "division by zero in constant expression"); + } + return *lhs / *rhs; + } + } + case Expr::Kind::Identifier: + if (integerConstants.count(expr.identifier) != 0) { + return integerConstants.lookup(expr.identifier); + } + return error(expr.loc, "expected a constant integer expression"); + case Expr::Kind::Float: + return error(expr.loc, "expected a constant integer expression"); + } + llvm_unreachable("unknown expression kind"); + } + + //===--- State --------------------------------------------------------===// + + QCProgramBuilder builder; + llvm::SourceMgr& sourceMgr; + + llvm::StringSet<> declaredNames; + + ValueTable numericConstants; + ValueScope numericConstantsScope{numericConstants}; + IntegerTable integerConstants; + IntegerScope integerConstantsScope{integerConstants}; + ValueTable booleanConstants; + ValueScope booleanConstantsScope{booleanConstants}; + ValueTable loopVariables; + ValueScope loopVariablesScope{loopVariables}; + ValueTable dynamicallyLoadedQubits; + ValueScope dynamicallyLoadedQubitsScope{dynamicallyLoadedQubits}; + + llvm::BumpPtrAllocator keyStorage; + llvm::StringSaver keySaver{keyStorage}; + + llvm::StringMap variableKinds; + + QubitScope qubitRegisters; + llvm::StringMap classicalRegisters; + llvm::StringMap> bitValues; + llvm::StringMap> gates; +}; + +} // namespace + +namespace detail { + +OwningOpRef translateQASM3ToQC(llvm::SourceMgr& sourceMgr, + MLIRContext* context) { + const auto bufferId = sourceMgr.getMainFileID(); + const StringRef buffer = sourceMgr.getMemoryBuffer(bufferId)->getBuffer(); + + Lexer lexer(buffer); + QCEmitter emitter(context, sourceMgr); + llvm::BumpPtrAllocator allocator; + Parser parser(lexer, emitter, allocator); + + emitter.initialize(); + const auto status = parser.parseProgram(); + auto mod = emitter.finalize(); + if (failed(status)) { + return nullptr; + } + return mod; +} + +} // namespace detail + +} // namespace mlir::qc diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp new file mode 100644 index 0000000000..e91d59bed0 --- /dev/null +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h" + +#include +#include +#include + +#include + +// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) +namespace mlir::qc::detail { + +[[nodiscard]] static bool canStartIdentifier(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || + static_cast(c) >= 0x80; // allow UTF-8 (e.g. π, τ, ℇ) +} + +[[nodiscard]] static bool canContinueIdentifier(char c) { + return canStartIdentifier(c) || (c >= '0' && c <= '9'); +} + +[[nodiscard]] static bool isDigit(char c) { return c >= '0' && c <= '9'; } + +[[nodiscard]] static TokenKind keywordKind(StringRef text) { + return llvm::StringSwitch(text) + .Case("OPENQASM", TokenKind::OpenQASM) + .Case("include", TokenKind::Include) + .Case("const", TokenKind::Const) + .Case("qubit", TokenKind::Qubit) + .Case("qreg", TokenKind::Qreg) + .Case("bit", TokenKind::Bit) + .Case("creg", TokenKind::CReg) + .Case("gate", TokenKind::Gate) + .Case("opaque", TokenKind::Opaque) + .Case("barrier", TokenKind::Barrier) + .Case("reset", TokenKind::Reset) + .Case("measure", TokenKind::Measure) + .Case("if", TokenKind::If) + .Case("else", TokenKind::Else) + .Case("for", TokenKind::For) + .Case("while", TokenKind::While) + .Case("in", TokenKind::In) + .Case("gphase", TokenKind::Gphase) + .Case("inv", TokenKind::Inv) + .Case("pow", TokenKind::Pow) + .Case("ctrl", TokenKind::Ctrl) + .Case("negctrl", TokenKind::NegCtrl) + .Case("int", TokenKind::Int) + .Case("uint", TokenKind::Uint) + .Case("bool", TokenKind::Bool) + .Case("float", TokenKind::Float) + .Case("angle", TokenKind::Angle) + .Case("duration", TokenKind::Duration) + .Case("true", TokenKind::True) + .Case("false", TokenKind::False) + .Default(TokenKind::Identifier); +} + +StringRef describe(const TokenKind kind) { + switch (kind) { + case TokenKind::Eof: + return "end of input"; + case TokenKind::Error: + return "invalid token"; + case TokenKind::Identifier: + return "identifier"; + case TokenKind::HardwareQubit: + return "hardware qubit"; + case TokenKind::StringLiteral: + return "string literal"; + case TokenKind::IntegerLiteral: + return "integer literal"; + case TokenKind::FloatLiteral: + return "float literal"; + case TokenKind::LParen: + return "'('"; + case TokenKind::RParen: + return "')'"; + case TokenKind::LBracket: + return "'['"; + case TokenKind::RBracket: + return "']'"; + case TokenKind::LBrace: + return "'{'"; + case TokenKind::RBrace: + return "'}'"; + case TokenKind::Comma: + return "','"; + case TokenKind::Semicolon: + return "';'"; + case TokenKind::Colon: + return "':'"; + case TokenKind::Arrow: + return "'->'"; + case TokenKind::At: + return "'@'"; + case TokenKind::Equals: + return "'='"; + default: + return "token"; + } +} + +void Lexer::skipTrivia() { + while (!atEnd()) { + const char c = *cur; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { + ++cur; + continue; + } + if (c == '/' && (cur + 1) != end && cur[1] == '/') { + // Line comment + cur += 2; + while (!atEnd() && *cur != '\n') { + ++cur; + } + continue; + } + if (c == '/' && (cur + 1) != end && cur[1] == '*') { + cur += 2; + while (!atEnd() && (*cur != '*' || (cur + 1) == end || cur[1] != '/')) { + ++cur; + } + if (!atEnd()) { + cur += 2; // consume the closing */ + } + continue; + } + break; + } +} + +Token Lexer::lexIdentifierOrKeyword(const char* start) { + while (!atEnd() && canContinueIdentifier(*cur)) { + ++cur; + } + const StringRef text(start, static_cast(cur - start)); + Token token; + token.loc = SMLoc::getFromPointer(start); + token.kind = keywordKind(text); + if (token.kind == TokenKind::Identifier) { + token.identifier = text; + } + return token; +} + +Token Lexer::lexNumber(const char* start) { + bool isFloat = false; + while (!atEnd() && isDigit(*cur)) { + ++cur; + } + if (!atEnd() && *cur == '.') { + isFloat = true; + ++cur; + while (!atEnd() && isDigit(*cur)) { + ++cur; + } + } + if (!atEnd() && (*cur == 'e' || *cur == 'E')) { + isFloat = true; + ++cur; + if (!atEnd() && (*cur == '+' || *cur == '-')) { + ++cur; + } + while (!atEnd() && isDigit(*cur)) { + ++cur; + } + } + const StringRef text(start, static_cast(cur - start)); + Token token; + token.loc = SMLoc::getFromPointer(start); + if (isFloat) { + token.kind = TokenKind::FloatLiteral; + if (text.getAsDouble(token.floatValue)) { + token.kind = TokenKind::Error; + } + } else { + token.kind = TokenKind::IntegerLiteral; + if (text.getAsInteger(10, token.intValue)) { + token.kind = TokenKind::Error; + } + } + return token; +} + +Token Lexer::lexString(const char* start) { + ++cur; // consume opening quote + const char* contentStart = cur; + while (!atEnd() && *cur != '"') { + ++cur; + } + Token token; + token.loc = SMLoc::getFromPointer(start); + if (atEnd()) { + token.kind = TokenKind::Error; + return token; + } + token.kind = TokenKind::StringLiteral; + token.stringValue = + StringRef(contentStart, static_cast(cur - contentStart)); + ++cur; // consume closing quote + return token; +} + +Token Lexer::lexHardwareQubit(const char* start) { + ++cur; // consume '$' + const char* digitsStart = cur; + while (!atEnd() && isDigit(*cur)) { + ++cur; + } + Token token; + token.loc = SMLoc::getFromPointer(start); + const StringRef digits(digitsStart, static_cast(cur - digitsStart)); + if (digits.empty() || digits.getAsInteger(10, token.intValue)) { + token.kind = TokenKind::Error; + return token; + } + token.kind = TokenKind::HardwareQubit; + return token; +} + +Token Lexer::next() { + skipTrivia(); + + Token token; + token.loc = SMLoc::getFromPointer(cur); + if (atEnd()) { + token.kind = TokenKind::Eof; + return token; + } + + const char* start = cur; + const char c = *cur; + + if (canStartIdentifier(c)) { + return lexIdentifierOrKeyword(start); + } + if (isDigit(c)) { + return lexNumber(start); + } + if (c == '"') { + return lexString(start); + } + if (c == '$') { + return lexHardwareQubit(start); + } + + // Punctuation and operators + const auto peek = [&](const char expected) { + return (cur + 1) != end && cur[1] == expected; + }; + const auto single = [&](const TokenKind kind) { + ++cur; + token.kind = kind; + return token; + }; + const auto twoChar = [&](const TokenKind kind) { + cur += 2; + token.kind = kind; + return token; + }; + + switch (c) { + case '(': + return single(TokenKind::LParen); + case ')': + return single(TokenKind::RParen); + case '[': + return single(TokenKind::LBracket); + case ']': + return single(TokenKind::RBracket); + case '{': + return single(TokenKind::LBrace); + case '}': + return single(TokenKind::RBrace); + case ',': + return single(TokenKind::Comma); + case ';': + return single(TokenKind::Semicolon); + case ':': + return single(TokenKind::Colon); + case '@': + return single(TokenKind::At); + case '~': + return peek('=') ? twoChar(TokenKind::CompoundAssign) + : single(TokenKind::Tilde); + case '=': + return peek('=') ? twoChar(TokenKind::EqualsEquals) + : single(TokenKind::Equals); + case '!': + return peek('=') ? twoChar(TokenKind::NotEquals) + : single(TokenKind::ExclamationPoint); + case '<': + return peek('=') ? twoChar(TokenKind::LessEquals) : single(TokenKind::Less); + case '>': + return peek('=') ? twoChar(TokenKind::GreaterEquals) + : single(TokenKind::Greater); + case '+': + return peek('=') ? twoChar(TokenKind::CompoundAssign) + : single(TokenKind::Plus); + case '-': + if (peek('>')) { + return twoChar(TokenKind::Arrow); + } + return peek('=') ? twoChar(TokenKind::CompoundAssign) + : single(TokenKind::Minus); + case '*': + return peek('=') ? twoChar(TokenKind::CompoundAssign) + : single(TokenKind::Asterisk); + case '/': + return peek('=') ? twoChar(TokenKind::CompoundAssign) + : single(TokenKind::Slash); + case '&': + if (peek('&')) { + return twoChar(TokenKind::AmpAmp); + } + if (peek('=')) { + return twoChar(TokenKind::CompoundAssign); + } + break; + case '|': + if (peek('|')) { + return twoChar(TokenKind::PipePipe); + } + if (peek('=')) { + return twoChar(TokenKind::CompoundAssign); + } + break; + case '%': + case '^': + if (peek('=')) { + return twoChar(TokenKind::CompoundAssign); + } + break; + default: + break; + } + + return single(TokenKind::Error); +} + +} // namespace mlir::qc::detail +// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index b18c858b2d..6cd2a08059 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -67,6 +67,13 @@ class QASM3TranslationTest } // namespace +static void singleMeasurementToTwoBits(qc::QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + const auto& c = b.allocClassicalBitRegister(2); + b.measure(q[0], c[0]); + b.measure(q[1], c[1]); +} + static void twoX(qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.x(q[0]); @@ -80,11 +87,6 @@ static void singleNegControlledX(qc::QCProgramBuilder& b) { b.x(q[0]); } -static void tripleControlledX(qc::QCProgramBuilder& b) { - auto q = b.allocQubitRegister(4); - b.mcx({q[0], q[1], q[2]}, q[3]); -} - static void mixedControlledX(qc::QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.x(q[1]); @@ -113,6 +115,55 @@ static void ifNot(qc::QCProgramBuilder& b) { b.scfIf(cond, [&] { b.x(q[0]); }); } +static void forLoopOffsetIndex(qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.scfFor(0, 1, 1, [&](Value iv) { + auto one = arith::ConstantOp::create(b, b.getIndexAttr(1)).getResult(); + auto index = arith::AddIOp::create(b, iv, one).getResult(); + auto q = b.memrefLoad(reg.value, index); + b.h(q); + }); +} + +static void nestedForLoopWhileOp(qc::QCProgramBuilder& b) { + auto reg = b.allocQubitRegister(2); + b.scfFor(0, 2, 1, [&](Value iv) { + auto q = b.memrefLoad(reg.value, iv); + b.h(q); + }); + b.scfFor(0, 2, 1, [&](Value iv) { + b.scfWhile( + [&] { + auto q = b.memrefLoad(reg.value, iv); + auto measureResult = b.measure(q); + b.scfCondition(measureResult); + }, + [&] { + auto q = b.memrefLoad(reg.value, iv); + b.h(q); + }); + }); +} + +static void broadcastRegisterAndQubit(qc::QCProgramBuilder& b) { + auto r = b.allocQubitRegister(3); + auto q = b.allocQubit(); + b.cx(r[0], q); + b.cx(r[1], q); + b.cx(r[2], q); +} + +static void broadcastCompoundGate(qc::QCProgramBuilder& b) { + auto r = b.allocQubitRegister(3); + auto q = b.allocQubit(); + b.x(r[0]); + b.cx(r[0], q); + b.x(r[1]); + b.cx(r[1], q); + b.x(r[2]); + b.cx(r[2], q); +} + TEST_P(QASM3TranslationTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; const auto& source = GetParam().source; @@ -158,6 +209,9 @@ INSTANTIATE_TEST_SUITE_P( QASM3TranslationTestCase{ "SingleMeasurementToSingleBit", qasm::singleMeasurementToSingleBit, MQT_NAMED_BUILDER(qc::singleMeasurementToSingleBit)}, + QASM3TranslationTestCase{"SingleMeasurementToTwoBits", + qasm::singleMeasurementToTwoBits, + MQT_NAMED_BUILDER(singleMeasurementToTwoBits)}, QASM3TranslationTestCase{ "RepeatedMeasurementToSameBit", qasm::repeatedMeasurementToSameBit, MQT_NAMED_BUILDER(qc::repeatedMeasurementToSameBit)}, @@ -201,9 +255,6 @@ INSTANTIATE_TEST_SUITE_P( QASM3TranslationTestCase{"MultipleControlledX", qasm::multipleControlledX, MQT_NAMED_BUILDER(qc::multipleControlledX)}, - QASM3TranslationTestCase{"TripleControlledXOpenQASM2", - qasm::tripleControlledXOpenQASM2, - MQT_NAMED_BUILDER(tripleControlledX)}, QASM3TranslationTestCase{"MixedControlledX", qasm::mixedControlledX, MQT_NAMED_BUILDER(mixedControlledX)}, QASM3TranslationTestCase{"TwoMixedControlledX", @@ -273,6 +324,8 @@ INSTANTIATE_TEST_SUITE_P( qasm::multipleControlledSxdg, MQT_NAMED_BUILDER(qc::multipleControlledSxdg)}, QASM3TranslationTestCase{"RX", qasm::rx, MQT_NAMED_BUILDER(qc::rx)}, + QASM3TranslationTestCase{"RXTheta", qasm::rxTheta, + MQT_NAMED_BUILDER(qc::rx)}, QASM3TranslationTestCase{"SingleControlledRX", qasm::singleControlledRx, MQT_NAMED_BUILDER(qc::singleControlledRx)}, QASM3TranslationTestCase{"MultipleControlledRX", @@ -416,4 +469,31 @@ INSTANTIATE_TEST_SUITE_P( QASM3TranslationTestCase{"IfEmptyThen", qasm::ifEmptyThen, MQT_NAMED_BUILDER(ifNot)}, QASM3TranslationTestCase{"IfElse", qasm::ifElse, - MQT_NAMED_BUILDER(qc::ifElse)})); + MQT_NAMED_BUILDER(qc::ifElse)}, + QASM3TranslationTestCase{"NestedIfForLoop", qasm::nestedIfOpForLoop, + MQT_NAMED_BUILDER(qc::nestedIfOpForLoop)}, + QASM3TranslationTestCase{"SimpleWhileReset", qasm::simpleWhileReset, + MQT_NAMED_BUILDER(qc::simpleWhileReset)}, + QASM3TranslationTestCase{"SimpleForLoop", qasm::simpleForLoop, + MQT_NAMED_BUILDER(qc::simpleForLoop)}, + QASM3TranslationTestCase{"ForLoopOffsetIndex", qasm::forLoopOffsetIndex, + MQT_NAMED_BUILDER(forLoopOffsetIndex)}, + QASM3TranslationTestCase{"NestedForLoopIfOp", qasm::nestedForLoopIfOp, + MQT_NAMED_BUILDER(qc::nestedForLoopIfOp)}, + QASM3TranslationTestCase{"NestedForLoopWhileOp", + qasm::nestedForLoopWhileOp, + MQT_NAMED_BUILDER(nestedForLoopWhileOp)}, + QASM3TranslationTestCase{ + "NestedForLoopCtrlOpWithSeparateQubit", + qasm::nestedForLoopCtrlOpWithSeparateQubit, + MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithSeparateQubit)}, + QASM3TranslationTestCase{ + "NestedForLoopCtrlOpWithExtractedQubit", + qasm::nestedForLoopCtrlOpWithExtractedQubit, + MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithExtractedQubit)}, + QASM3TranslationTestCase{"BroadcastRegisterAndQubit", + qasm::broadcastRegisterAndQubit, + MQT_NAMED_BUILDER(broadcastRegisterAndQubit)}, + QASM3TranslationTestCase{"BroadcastCompoundGate", + qasm::broadcastCompoundGate, + MQT_NAMED_BUILDER(broadcastCompoundGate)})); diff --git a/mlir/unittests/programs/qasm_programs.cpp b/mlir/unittests/programs/qasm_programs.cpp index 2386d7e8c7..cc353c4f6c 100644 --- a/mlir/unittests/programs/qasm_programs.cpp +++ b/mlir/unittests/programs/qasm_programs.cpp @@ -43,6 +43,12 @@ bit[1] c; measure q[0] -> c[0]; )qasm"; +const std::string singleMeasurementToTwoBits = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +bit[2] c = measure q; +)qasm"; + const std::string repeatedMeasurementToSameBit = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; @@ -155,12 +161,6 @@ qubit[3] q; ctrl(2) @ x q[0], q[1], q[2]; )qasm"; -const std::string tripleControlledXOpenQASM2 = R"qasm(OPENQASM 2.0; -include "qelib1.inc"; -qreg q[4]; -cccx q[0], q[1], q[2], q[3]; -)qasm"; - const std::string mixedControlledX = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[3] q; @@ -355,6 +355,13 @@ qubit[1] q; rx(0.123) q[0]; )qasm"; +const std::string rxTheta = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +const float theta = 2 * (0.123 + 0.1 - 0.1) / 2; +qubit[1] q; +rx(theta) q[0]; +)qasm"; + const std::string singleControlledRx = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; @@ -711,6 +718,8 @@ gate compound q0, q1 { ctrl(2) @ compound q[0], q[1], q[2], q[3]; )qasm"; +// --- IfOp ----------------------------------------------------------------- // + const std::string simpleIf = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; @@ -765,5 +774,117 @@ if (c) { } )qasm"; +const std::string nestedIfOpForLoop = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +qubit qCond; +h qCond; +bit c = measure qCond; +if (c) { + h qCond; +} else { + for uint i in [0:2] { + h q[i]; + } +} +)qasm"; + +// --- WhileOp -------------------------------------------------------------- // + +const std::string simpleWhileReset = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit q; +h q; +while (measure q) { + h q; +} +)qasm"; + +// --- ForOp ---------------------------------------------------------------- // + +const std::string simpleForLoop = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +for uint i in [0:1] { + h q[i]; +} +)qasm"; + +const std::string nestedForLoopIfOp = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +qubit qCond; +for uint i in [0:1] { + h qCond; + if (measure qCond) { + h q[i]; + } +} +)qasm"; + +const std::string nestedForLoopWhileOp = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +for uint i in [0:1] { + h q[i]; +} +for uint i in [0:1] { + while (measure q[i]) { + h q[i]; + } +} +)qasm"; + +const std::string forLoopOffsetIndex = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +for uint i in [0:0] { + h q[i + 1]; +} +)qasm"; + +const std::string nestedForLoopCtrlOpWithSeparateQubit = + R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +qubit control; +h control; +for uint i in [0:2] { + h q[i]; + cx control, q[i]; +} +)qasm"; + +const std::string nestedForLoopCtrlOpWithExtractedQubit = + R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +h q[0]; +for uint i in [1:3] { + h q[i]; + cx q[0], q[i]; +} +)qasm"; + +// --- Broadcasting --------------------------------------------------------- // + +const std::string broadcastRegisterAndQubit = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] r; +qubit q; +cx r, q; +)qasm"; + +const std::string broadcastCompoundGate = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +gate compound a, b { + x a; + cx a, b; +} +qubit[3] r; +qubit q; +compound r, q; +)qasm"; + } // namespace mlir::qasm // NOLINTEND(readability-identifier-naming) diff --git a/mlir/unittests/programs/qasm_programs.h b/mlir/unittests/programs/qasm_programs.h index 5da45582c7..9fc3a5959a 100644 --- a/mlir/unittests/programs/qasm_programs.h +++ b/mlir/unittests/programs/qasm_programs.h @@ -30,6 +30,9 @@ extern const std::string allocLargeRegister; /// Measures a single qubit into a single classical bit. extern const std::string singleMeasurementToSingleBit; +/// Measures a two-qubit register into a two-bit register. +extern const std::string singleMeasurementToTwoBits; + /// Repeatedly measures a single qubit into the same classical bit. extern const std::string repeatedMeasurementToSameBit; @@ -78,9 +81,6 @@ extern const std::string singleNegControlledX; /// Creates a circuit with a multi-controlled X gate. extern const std::string multipleControlledX; -/// Creates a circuit with a triple-controlled X gate in OpenQASM 2. -extern const std::string tripleControlledXOpenQASM2; - /// Creates a circuit with an X gate that is positively and negatively /// controlled. extern const std::string mixedControlledX; @@ -179,6 +179,9 @@ extern const std::string multipleControlledSxdg; /// Creates a circuit with just an RX gate. extern const std::string rx; +/// Creates a circuit with an RX gate with a declared theta. +extern const std::string rxTheta; + /// Creates a circuit with a single controlled RX gate. extern const std::string singleControlledRx; @@ -355,6 +358,8 @@ extern const std::string ctrlTwo; /// non-controlled gate. extern const std::string ctrlTwoMixed; +// --- IfOp ----------------------------------------------------------------- // + /// Creates a circuit with a simple if operation with one qubit. extern const std::string simpleIf; @@ -370,5 +375,48 @@ extern const std::string ifEmptyThen; /// Creates a circuit with an if operation with an else branch. extern const std::string ifElse; +/// Creates a circuit with an if operation with a nested for operation with +/// a register. +extern const std::string nestedIfOpForLoop; + +// --- WhileOp -------------------------------------------------------------- // + +/// Creates a circuit with a while operation using a while loop. +extern const std::string simpleWhileReset; + +// --- ForOp ---------------------------------------------------------------- // + +/// Creates a circuit with a simple for operation with a register. +extern const std::string simpleForLoop; + +/// Creates a circuit with an if operation with a nested for operation with +/// a register. +extern const std::string nestedForLoopIfOp; + +/// Creates a circuit with a for operation with a register and a nested while +/// operation. +extern const std::string nestedForLoopWhileOp; + +/// Creates a circuit with a for operation that indexes a register with a +/// non-trivial expression (`q[i + 1]`) in the loop variable. +extern const std::string forLoopOffsetIndex; + +/// Creates a circuit with a for operation with a register and a qubit and a +/// nested ctrl operation where the qubit is separately allocated from the +/// register. +extern const std::string nestedForLoopCtrlOpWithSeparateQubit; + +/// Creates a circuit with a for operation with a register and a qubit and a +/// nested ctrl operation where the qubit is extracted from the register. +extern const std::string nestedForLoopCtrlOpWithExtractedQubit; + +// --- Broadcasting --------------------------------------------------------- // + +/// Broadcasts a controlled X gate over a register and a single qubit. +extern const std::string broadcastRegisterAndQubit; + +/// Broadcasts a compound gate over a register and a single qubit. +extern const std::string broadcastCompoundGate; + } // namespace mlir::qasm // NOLINTEND(readability-identifier-naming)