From 270457a3922d2c61e3824db68d0ac8f1f8646bfc Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:32:57 +0200 Subject: [PATCH 01/24] Claude's first solution --- include/mqt-core/qasm3/DebugInfo.hpp | 36 + include/mqt-core/qasm3/Exception.hpp | 2 +- include/mqt-core/qasm3/Statement.hpp | 17 +- .../lib/Dialect/QC/Translation/CMakeLists.txt | 1 + .../Dialect/QC/Translation/QASM3Parser.cpp | 1713 +++++++++++++++++ mlir/lib/Dialect/QC/Translation/QASM3Parser.h | 54 + .../QC/Translation/TranslateQASM3ToQC.cpp | 1022 +--------- .../QC/Translation/test_qasm3_translation.cpp | 75 +- mlir/unittests/programs/qasm_programs.cpp | 52 + mlir/unittests/programs/qasm_programs.h | 19 + 10 files changed, 1957 insertions(+), 1034 deletions(-) create mode 100644 include/mqt-core/qasm3/DebugInfo.hpp create mode 100644 mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp create mode 100644 mlir/lib/Dialect/QC/Translation/QASM3Parser.h diff --git a/include/mqt-core/qasm3/DebugInfo.hpp b/include/mqt-core/qasm3/DebugInfo.hpp new file mode 100644 index 0000000000..ea28c94224 --- /dev/null +++ b/include/mqt-core/qasm3/DebugInfo.hpp @@ -0,0 +1,36 @@ +/* + * 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 { + +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); + } +}; + +} // 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/lib/Dialect/QC/Translation/CMakeLists.txt b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt index c1d403de3a..bf75040f7c 100644 --- a/mlir/lib/Dialect/QC/Translation/CMakeLists.txt +++ b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt @@ -10,6 +10,7 @@ add_mlir_library( MLIRQCTranslation TranslateQuantumComputationToQC.cpp TranslateQASM3ToQC.cpp + QASM3Parser.cpp LINK_LIBS MLIRArithDialect MLIRFuncDialect diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp new file mode 100644 index 0000000000..886580be48 --- /dev/null +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp @@ -0,0 +1,1713 @@ +/* + * 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 "QASM3Parser.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/DebugInfo.hpp" +#include "qasm3/Exception.hpp" +#include "qasm3/Gate.hpp" +#include "qasm3/NestedEnvironment.hpp" +#include "qasm3/Scanner.hpp" +#include "qasm3/StdGates.hpp" +#include "qasm3/Token.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 + +namespace mlir::qc { + +namespace { + +using qasm3::CompilerError; +using qasm3::DebugInfo; +using qasm3::GateInfo; +using qasm3::Token; + +/// Signature: (builder, gate operands, gate 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; + +/// Build the table mapping each OpenQASM 3 gate identifier to a lambda that +/// emits the corresponding QC operation via the `QCProgramBuilder`. +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; +} + +/// Map from OpenQASM 3 gate identifier to QCProgramBuilder emitter. +const llvm::StringMap GATE_DISPATCH = buildGateDispatch(); + +/// A named qubit binding in scope. For a top-level register, `memref` is the +/// backing memref value (used for dynamic indexing, e.g. by a for-loop +/// variable) and `qubits` holds the eagerly extracted per-index qubit values +/// (used for literal indexing). For a compound-gate-local alias, `memref` is +/// null: such targets are accessed by bare name only, never dynamically +/// indexed. +struct QubitBinding { + Value memref; + SmallVector qubits; +}; + +/// Map of qubits in the current scope. +using QubitScope = llvm::StringMap; + +/// Look up a well-known OpenQASM 3 numeric constant (`pi`, `tau`, `euler`, +/// and their Unicode aliases) by identifier. +std::optional lookupBuiltinConstant(llvm::StringRef name) { + if (name == "pi" || name == "π") { + return ::qc::PI; + } + if (name == "tau" || name == "τ") { + return ::qc::TAU; + } + if (name == "euler" || name == "ℇ") { + return ::qc::E; + } + return std::nullopt; +} + +//===----------------------------------------------------------------------===// +// Parsed representations +//===----------------------------------------------------------------------===// + +/// A small closed expression tree used both for gate-parameter (float) and +/// structural (integer) positions, and stored verbatim inside compound-gate +/// bodies so a gate body can be parsed once and replayed at every call site. +struct ParsedExpr { + enum class Kind : uint8_t { Literal, Ident, Unary, Binary }; + + Kind kind{Kind::Literal}; + + // Literal + double fpValue{}; + int64_t intValue{}; + bool isFloatLiteral{false}; + + // Ident + std::string name; + + // Operator token. Unary: Minus (negation). Binary: Plus, Minus, Asterisk, + // Slash. + Token::Kind op{}; + std::vector children; + + static ParsedExpr makeInt(int64_t value) { + ParsedExpr e; + e.kind = Kind::Literal; + e.intValue = value; + e.isFloatLiteral = false; + return e; + } +}; + +/// A single gate modifier (ctrl/negctrl/inv/pow) in the order written. +struct ParsedModifier { + enum class Kind : uint8_t { Inv, Pow, Ctrl, NegCtrl } kind{Kind::Inv}; + std::optional expression; +}; + +/// A gate operand: either a hardware qubit, or a (possibly indexed) named +/// qubit. The index expression is kept unresolved so the same operand can be +/// resolved against different scopes (top-level vs. compound-gate-local) and +/// so a for-loop induction variable can be recognised at resolution time. +struct ParsedOperand { + bool isHardwareQubit{false}; + uint64_t hardwareQubit{}; + std::string name; + std::optional index; +}; + +/// A parsed gate-call statement (front half only): its modifiers, parameter +/// expressions and operands, ready to be resolved and emitted. +struct ParsedGateCall { + std::string identifier; + bool operandsOptional{false}; + std::vector modifiers; + std::vector parameters; + std::vector operands; + std::shared_ptr debugInfo; +}; + +/// A compound-gate definition: a body of gate calls parsed once at declaration +/// time and replayed at each call site with fresh parameter/target bindings. +struct ParsedCompoundGate { + std::vector parameterNames; + std::vector targetNames; + std::vector body; +}; + +/// A (possibly indexed) classical-bit reference. +struct ParsedBitRef { + std::string name; + std::optional index; +}; + +/// Build the gate table mapping every natively supported OpenQASM 3 gate +/// identifier to its `GateInfo`. Compound gates are added to this same table +/// as the program declares them. +llvm::StringMap> buildGateTable() { + llvm::StringMap> t; + for (const auto& [name, gate] : qasm3::STANDARD_GATES) { + // Every entry in STANDARD_GATES is a StandardGate (CompoundGate only ever + // arises from user `gate` declarations), so this cast always succeeds. + const auto* standard = dynamic_cast(gate.get()); + assert(standard != nullptr && "STANDARD_GATES entry is not a StandardGate"); + t.insert({name, standard->info}); + } + + const 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; +} + +//===----------------------------------------------------------------------===// +// QASM3Parser +//===----------------------------------------------------------------------===// + +/// Single-pass OpenQASM 3 to QC dialect translator. Consumes `qasm3::Scanner` +/// tokens directly and emits QC operations via the `QCProgramBuilder` as it +/// parses. Each `parseX` handler parses its own tokens and drives the builder +/// at the point the corresponding statement takes effect; targeted validation +/// helpers raise `CompilerError` with a specific message where the check is +/// meaningful. +class QASM3Parser final { +public: + explicit QASM3Parser(MLIRContext* ctx) + : builder(ctx), gates(buildGateTable()) {} + + OwningOpRef parse(std::string_view source) { + input = std::make_unique(std::string(source)); + scanner = std::make_unique(input.get()); + // Prime the token window: the first advance loads the first token into the + // lookahead, the second moves it into `current()`. Two calls are needed + // because both `current()` and `peek()` start empty. + advance(); + advance(); + + builder.initialize(); + parseProgram(); + return builder.finalize(); + } + +private: + //===--- State --------------------------------------------------------===// + + QCProgramBuilder builder; + + std::unique_ptr input; + std::unique_ptr scanner; + Token currentToken{0, 0}; + Token nextToken{0, 0}; + + /// Names already declared at file scope (qubit/bit registers and constants). + llvm::StringMap declaredNames; ///< name -> isConst + + /// Map from a `const`-declared or compound-gate-parameter identifier to the + /// MLIR value it is bound to. + qasm3::NestedEnvironment parameterConstants; + + /// Map from a for-loop induction-variable identifier to its runtime value. + qasm3::NestedEnvironment loopVariables; + + /// Map from a qubit-register name to the qubit most recently loaded from it + /// via a dynamic (loop-variable) index, within the current for-loop's scope. + qasm3::NestedEnvironment loadedDynamicElements; + + /// 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 its definition (native or compound). + llvm::StringMap> gates; + + bool openQASM2CompatMode{false}; + + //===--- Token scaffolding --------------------------------------------===// + + /// Advance the token cursor by one: `current()` moves to what `peek()` + /// returned, and a fresh token is pulled from the scanner into the lookahead. + void advance() { + currentToken = nextToken; + nextToken = scanner->next(); + } + + /// The token the parser is currently positioned on. + [[nodiscard]] const Token& current() const { return currentToken; } + + /// The next token, without consuming it (one-token lookahead). + [[nodiscard]] const Token& peek() const { return nextToken; } + + /// Whether the entire input has been consumed. + [[nodiscard]] bool isAtEnd() const { + return currentToken.kind == Token::Kind::Eof; + } + + [[nodiscard]] std::shared_ptr makeDebugInfo(const Token& t) const { + return std::make_shared(t.line, t.col, ""); + } + + [[noreturn]] void error(const Token& t, const std::string& msg) const { + throw CompilerError(msg, makeDebugInfo(t)); + } + + Token expect(const Token::Kind expected) { + if (current().kind != expected) { + error(current(), "Expected '" + Token::kindToString(expected) + + "', got '" + Token::kindToString(current().kind) + + "'."); + } + auto token = current(); + advance(); + return token; + } + + //===--- Program & statement dispatch ---------------------------------===// + + void parseProgram() { + while (!isAtEnd()) { + if (current().kind == Token::Kind::OpenQasm) { + parseVersionDeclaration(); + continue; + } + parseStatement(); + } + } + + void parseVersionDeclaration() { + expect(Token::Kind::OpenQasm); + double version = 0.0; + if (current().kind == Token::Kind::FloatLiteral) { + version = current().valReal; + advance(); + } else if (current().kind == Token::Kind::IntegerLiteral) { + version = static_cast(current().val); + advance(); + } else { + error(current(), + "Version declaration must be a float or integer literal."); + } + expect(Token::Kind::Semicolon); + if (version < 3) { + openQASM2CompatMode = true; + } + } + + /// The "big switch": parse one statement, emit MLIR for it, move on. Also + /// used to emit statements nested in an if/for/while body. + void parseStatement() { + switch (current().kind) { + case Token::Kind::Include: + parseInclude(); + return; + case Token::Kind::Const: + advance(); + parseDeclaration(/*isConst=*/true); + return; + case Token::Kind::Qubit: + case Token::Kind::Qreg: + case Token::Kind::Bit: + case Token::Kind::CReg: + case Token::Kind::Int: + case Token::Kind::Uint: + case Token::Kind::Float: + case Token::Kind::Angle: + case Token::Kind::Bool: + case Token::Kind::Duration: + parseDeclaration(/*isConst=*/false); + return; + case Token::Kind::Gate: + parseGateDeclaration(/*isOpaque=*/false); + return; + case Token::Kind::Opaque: + parseGateDeclaration(/*isOpaque=*/true); + return; + case Token::Kind::InitialLayout: + case Token::Kind::OutputPermutation: + // Not relevant for the QC translation. + advance(); + return; + case Token::Kind::Barrier: + parseBarrier(); + return; + case Token::Kind::Reset: + parseReset(); + return; + case Token::Kind::Measure: + parseMeasure(); + return; + case Token::Kind::If: + parseIf(); + return; + case Token::Kind::For: + parseFor(); + return; + case Token::Kind::While: + parseWhile(); + return; + case Token::Kind::Inv: + case Token::Kind::Pow: + case Token::Kind::Ctrl: + case Token::Kind::NegCtrl: + case Token::Kind::Gphase: + emitGateCall(parseGateCall(), qubitRegisters); + return; + case Token::Kind::Identifier: + switch (peek().kind) { + case Token::Kind::LBracket: + case Token::Kind::Equals: + case Token::Kind::PlusEquals: + case Token::Kind::MinusEquals: + case Token::Kind::AsteriskEquals: + case Token::Kind::SlashEquals: + case Token::Kind::AmpersandEquals: + case Token::Kind::PipeEquals: + case Token::Kind::TildeEquals: + case Token::Kind::CaretEquals: + case Token::Kind::LeftShitEquals: + case Token::Kind::RightShiftEquals: + case Token::Kind::PercentEquals: + case Token::Kind::DoubleAsteriskEquals: + parseAssignment(); + return; + default: + emitGateCall(parseGateCall(), qubitRegisters); + return; + } + default: + error(current(), "Unexpected token '" + current().toString() + "'."); + } + } + + /// Parse and emit either a `{ ... }` block or a single statement. + void parseBlockOrStatement() { + if (current().kind == Token::Kind::LBrace) { + advance(); + while (!isAtEnd() && current().kind != Token::Kind::RBrace) { + parseStatement(); + } + expect(Token::Kind::RBrace); + } else { + parseStatement(); + } + } + + //===--- Include ------------------------------------------------------===// + + void parseInclude() { + const auto beginToken = expect(Token::Kind::Include); + const auto filename = expect(Token::Kind::StringLiteral).str; + expect(Token::Kind::Semicolon); + // `stdgates.inc` and `qelib1.inc` are fully covered by the native gate + // table, so they are no-ops. Anything else is a real mistake. + if (filename != "stdgates.inc" && filename != "qelib1.inc") { + error(beginToken, "Unsupported include '" + filename + + "'. Only 'stdgates.inc' and 'qelib1.inc' are " + "supported."); + } + } + + //===--- Declarations -------------------------------------------------===// + + void parseDeclaration(bool isConst) { + const auto beginToken = current(); + const auto debugInfo = makeDebugInfo(beginToken); + + const auto typeKind = current().kind; + const bool oldStyle = + typeKind == Token::Kind::Qreg || typeKind == Token::Kind::CReg; + advance(); + + std::optional designator; + if (!oldStyle && current().kind == Token::Kind::LBracket) { + designator = parseTypeDesignator(debugInfo); + } + + const auto id = expect(Token::Kind::Identifier).str; + + if (current().kind == Token::Kind::LBracket) { + if (oldStyle) { + designator = parseTypeDesignator(debugInfo); + } else { + error(current(), "In OpenQASM 3.0, the designator has been changed to " + "`type[designator] identifier;`"); + } + } + + std::optional initExpr; + std::optional measureInit; + if (current().kind == Token::Kind::Equals) { + advance(); + if (current().kind == Token::Kind::Measure) { + advance(); + measureInit = parseGateOperand(); + } else { + initExpr = parseExpression(); + } + } + + expect(Token::Kind::Semicolon); + + if (declaredNames.contains(id)) { + error(beginToken, "Identifier '" + id + "' already declared."); + } + declaredNames[id] = isConst; + + if (isConst) { + if (!initExpr) { + error(beginToken, + "Constant declaration initialization expression must be " + "initialized."); + } + parameterConstants.emplace(id, emitFloatExpression(*initExpr, debugInfo)); + return; + } + + // The type must be a sized register type. + const bool sized = + typeKind == Token::Kind::Qubit || typeKind == Token::Kind::Qreg || + typeKind == Token::Kind::Bit || typeKind == Token::Kind::CReg || + typeKind == Token::Kind::Int || typeKind == Token::Kind::Uint; + if (!sized) { + error(beginToken, "Only sized types are supported."); + } + + const auto size = designator.value_or(1); + + switch (typeKind) { + case Token::Kind::Qubit: + case Token::Kind::Qreg: { + if (size == 1 && !designator) { + // `qubit q;` (no register syntax): allocate a bare qubit rather than a + // size-1 register, matching how such declarations are naturally built + // directly with the QCProgramBuilder. + qubitRegisters[id] = {Value{}, {builder.allocQubit()}}; + } else { + const auto reg = builder.allocQubitRegister(size); + qubitRegisters[id] = {reg.value, reg.qubits}; + } + break; + } + case Token::Kind::Bit: + case Token::Kind::CReg: + case Token::Kind::Int: + case Token::Kind::Uint: + classicalRegisters[id] = builder.allocClassicalBitRegister(size, id); + break; + default: + error(beginToken, "Unsupported declaration type."); + } + + if (measureInit) { + emitMeasureAssignment(ParsedBitRef{id, std::nullopt}, *measureInit, + debugInfo); + return; + } + if (initExpr) { + error(beginToken, "Only measure expressions can declare variables."); + } + } + + int64_t parseTypeDesignator(const std::shared_ptr& debugInfo) { + expect(Token::Kind::LBracket); + const auto expr = parseExpression(); + expect(Token::Kind::RBracket); + return evaluateIntegerConstant(expr, debugInfo); + } + + //===--- Measurement --------------------------------------------------===// + + void parseAssignment() { + const auto beginToken = current(); + const auto debugInfo = makeDebugInfo(beginToken); + const auto target = parseBitRef(); + + // Consume the assignment operator; only plain `=` with a measure RHS is + // supported (classical computation is out of scope). + if (current().kind != Token::Kind::Equals) { + error(current(), "Classical computations are not supported."); + } + advance(); + + if (current().kind != Token::Kind::Measure) { + error(current(), "Classical computations are not supported."); + } + advance(); + const auto operand = parseGateOperand(); + expect(Token::Kind::Semicolon); + + emitMeasureAssignment(target, operand, debugInfo); + } + + void parseMeasure() { + const auto beginToken = expect(Token::Kind::Measure); + const auto debugInfo = makeDebugInfo(beginToken); + const auto operand = parseGateOperand(); + expect(Token::Kind::Arrow); + const auto target = parseBitRef(); + expect(Token::Kind::Semicolon); + emitMeasureAssignment(target, operand, debugInfo); + } + + void emitMeasureAssignment(const ParsedBitRef& target, + const ParsedOperand& operand, + const std::shared_ptr& debugInfo) { + const auto bits = resolveClassicalBits(target, debugInfo); + const auto resolved = resolveGateOperand(operand, debugInfo); + SmallVector qubits; + if (std::holds_alternative(resolved)) { + qubits.push_back(std::get(resolved)); + } else { + qubits = std::get>(resolved); + } + if (bits.size() != qubits.size()) { + error(*debugInfo, "The classical register and the quantum register must " + "have the same width."); + } + 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; + } + } + + //===--- Barrier ------------------------------------------------------===// + + void parseBarrier() { + const auto beginToken = expect(Token::Kind::Barrier); + const auto debugInfo = makeDebugInfo(beginToken); + SmallVector qubits; + while (current().kind != Token::Kind::Semicolon) { + const auto operand = parseGateOperand(); + const auto resolved = resolveGateOperand(operand, debugInfo); + if (std::holds_alternative(resolved)) { + qubits.push_back(std::get(resolved)); + } else { + llvm::append_range(qubits, std::get>(resolved)); + } + if (current().kind != Token::Kind::Semicolon) { + expect(Token::Kind::Comma); + } + } + expect(Token::Kind::Semicolon); + builder.barrier(qubits); + } + + //===--- Reset --------------------------------------------------------===// + + void parseReset() { + const auto beginToken = expect(Token::Kind::Reset); + const auto debugInfo = makeDebugInfo(beginToken); + const auto operand = parseGateOperand(); + expect(Token::Kind::Semicolon); + const auto resolved = resolveGateOperand(operand, debugInfo); + if (std::holds_alternative(resolved)) { + builder.reset(std::get(resolved)); + } else { + for (auto qubit : std::get>(resolved)) { + builder.reset(qubit); + } + } + } + + //===--- Control flow -------------------------------------------------===// + + void parseIf() { + const auto beginToken = expect(Token::Kind::If); + expect(Token::Kind::LParen); + auto condition = parseCondition(); + expect(Token::Kind::RParen); + + // Empty `{}` then-block (detectable with a single-token lookahead). + if (current().kind == Token::Kind::LBrace && + peek().kind == Token::Kind::RBrace) { + expect(Token::Kind::LBrace); + expect(Token::Kind::RBrace); + if (current().kind != Token::Kind::Else) { + error(beginToken, + "If statements with empty then and else blocks are not " + "supported."); + } + expect(Token::Kind::Else); + // Emit the else block under the negated condition. + auto trueValue = builder.boolConstant(true); + condition = + arith::XOrIOp::create(builder, condition, trueValue).getResult(); + auto ifOp = scf::IfOp::create(builder, condition, + /*withElseRegion=*/false); + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); + parseBlockOrStatement(); + return; + } + + // Non-empty then-block. The else region only comes into being once we see + // an `else`, which appears after the then-block; build it lazily. + auto ifOp = scf::IfOp::create(builder, condition, /*withElseRegion=*/false); + { + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); + parseBlockOrStatement(); + } + + if (current().kind == Token::Kind::Else) { + expect(Token::Kind::Else); + // An empty else block is treated as no else at all. + if (current().kind == Token::Kind::LBrace && + peek().kind == Token::Kind::RBrace) { + expect(Token::Kind::LBrace); + expect(Token::Kind::RBrace); + return; + } + OpBuilder::InsertionGuard guard(builder); + auto* elseBlock = builder.createBlock(&ifOp.getElseRegion()); + builder.setInsertionPointToStart(elseBlock); + parseBlockOrStatement(); + scf::YieldOp::create(builder); + } + } + + /// Translate a for-loop into an `scf.for` loop. The bounds and step must + /// resolve to constants, since they decide the shape of the emitted loop. + /// OpenQASM 3's range is inclusive of the stop value while `scf.for`'s upper + /// bound is exclusive, hence the `+ 1`. + void parseFor() { + const auto beginToken = expect(Token::Kind::For); + const auto debugInfo = makeDebugInfo(beginToken); + + // The loop-variable type is not tracked further: the loop variable is + // always treated as an unsigned integer index. + if (current().kind != Token::Kind::Int && + current().kind != Token::Kind::Uint) { + error(current(), "Expected 'int' or 'uint' after 'for'."); + } + advance(); + + const auto loopVariable = expect(Token::Kind::Identifier).str; + + expect(Token::Kind::In); + expect(Token::Kind::LBracket); + + const auto start = parseExpression(); + expect(Token::Kind::Colon); + const auto second = parseExpression(); + + ParsedExpr step = ParsedExpr::makeInt(1); + ParsedExpr stop; + if (current().kind == Token::Kind::Colon) { + advance(); + step = second; + stop = parseExpression(); + } else { + stop = second; + } + + expect(Token::Kind::RBracket); + + const auto startVal = evaluatePositiveConstant(start, debugInfo); + const auto stepVal = evaluatePositiveConstant(step, debugInfo, 1); + const auto stopVal = evaluatePositiveConstant(stop, debugInfo); + + loopVariables.push(); + loadedDynamicElements.push(); + + builder.scfFor(static_cast(startVal), + static_cast(stopVal + 1), + static_cast(stepVal), [&](Value iv) { + loopVariables.emplace(loopVariable, iv); + parseBlockOrStatement(); + }); + + loadedDynamicElements.pop(); + loopVariables.pop(); + } + + /// Translate a while-loop into an `scf.while` loop: the condition is + /// (re-)computed in the "before" region on every iteration. + void parseWhile() { + expect(Token::Kind::While); + expect(Token::Kind::LParen); + + builder.scfWhile( + [&] { + const auto condition = parseCondition(); + expect(Token::Kind::RParen); + builder.scfCondition(condition); + }, + [&] { parseBlockOrStatement(); }); + } + + /// Translate an OpenQASM 3 if/while condition to an `i1` MLIR value. + [[nodiscard]] Value parseCondition() { + const auto debugInfo = makeDebugInfo(current()); + + // Fresh measurement used directly as the condition (e.g. `if (measure q)` + // or `while (measure q)`), evaluated anew every time it is reached. + if (current().kind == Token::Kind::Measure) { + advance(); + const auto operand = parseGateOperand(); + const auto resolved = resolveGateOperand(operand, debugInfo); + if (!std::holds_alternative(resolved)) { + error(*debugInfo, "Measurement condition must be a single qubit."); + } + return builder.measure(std::get(resolved)); + } + + // Unary negation (!c[0] or ~c[0]). + if (current().kind == Token::Kind::ExclamationPoint || + current().kind == Token::Kind::Tilde) { + advance(); + if (current().kind != Token::Kind::Identifier) { + error(current(), "Unary expression has unsupported operand."); + } + const auto bit = parseBitRef(); + const auto value = lookupBitValue(bit, debugInfo); + auto trueValue = builder.boolConstant(true); + return arith::XOrIOp::create(builder, value, trueValue).getResult(); + } + + // Single bit (c or c[0]), or an unsupported register comparison. + if (current().kind == Token::Kind::Identifier) { + const auto bit = parseBitRef(); + switch (current().kind) { + case Token::Kind::DoubleEquals: + case Token::Kind::NotEquals: + case Token::Kind::LessThan: + case Token::Kind::GreaterThan: + case Token::Kind::LessThanEquals: + case Token::Kind::GreaterThanEquals: + error(*debugInfo, "Register comparisons are not supported."); + default: + break; + } + return lookupBitValue(bit, debugInfo); + } + + error(*debugInfo, "Unsupported condition expression in if statement."); + } + + /// Look up the most recent measurement result for a classical bit. + [[nodiscard]] Value + lookupBitValue(const ParsedBitRef& bit, + const std::shared_ptr& debugInfo) const { + const auto& regName = bit.name; + auto it = bitValues.find(regName); + if (it == bitValues.end()) { + error(*debugInfo, "No classical bit of register '" + regName + + "' has been measured yet."); + } + const auto& regBits = it->second; + + if (!bit.index) { + assert(regBits.size() == 1); + return regBits[0]; + } + + const auto index = evaluatePositiveConstant(*bit.index, debugInfo); + if (index >= regBits.size() || !regBits[index]) { + error(*debugInfo, "Bit " + std::to_string(index) + " of register '" + + regName + "' has been not measured yet."); + } + return regBits[index]; + } + + //===--- Gate declarations --------------------------------------------===// + + void parseGateDeclaration(bool isOpaque) { + const auto beginToken = current(); + advance(); // `gate` or `opaque` + const auto id = expect(Token::Kind::Identifier).str; + + std::vector parameters; + if (current().kind == Token::Kind::LParen) { + advance(); + parameters = parseIdentifierList(); + expect(Token::Kind::RParen); + } + const auto targets = parseIdentifierList(); + + if (isOpaque) { + expect(Token::Kind::Semicolon); + if (!gates.contains(id)) { + error(beginToken, "Unsupported opaque gate '" + id + "'."); + } + return; + } + + expect(Token::Kind::LBrace); + std::vector body; + while (current().kind != Token::Kind::RBrace) { + body.push_back(parseGateCall()); + } + expect(Token::Kind::RBrace); + + if (gates.contains(id)) { + error(beginToken, "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]) { + error(beginToken, "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]) { + error(beginToken, "Target is already declared in compound gate."); + } + } + } + + gates[id] = ParsedCompoundGate{std::move(parameters), std::move(targets), + std::move(body)}; + } + + std::vector parseIdentifierList() { + std::vector identifiers; + identifiers.push_back(expect(Token::Kind::Identifier).str); + while (current().kind == Token::Kind::Comma) { + advance(); + identifiers.push_back(expect(Token::Kind::Identifier).str); + } + return identifiers; + } + + //===--- Gate calls ---------------------------------------------------===// + + ParsedGateCall parseGateCall() { + ParsedGateCall call; + call.debugInfo = makeDebugInfo(current()); + + while (current().kind == Token::Kind::Inv || + current().kind == Token::Kind::Pow || + current().kind == Token::Kind::Ctrl || + current().kind == Token::Kind::NegCtrl) { + call.modifiers.push_back(parseGateModifier()); + expect(Token::Kind::At); + } + + if (current().kind == Token::Kind::Gphase) { + advance(); + call.identifier = "gphase"; + call.operandsOptional = true; + } else { + call.identifier = expect(Token::Kind::Identifier).str; + } + + if (current().kind == Token::Kind::LParen) { + advance(); + while (current().kind != Token::Kind::RParen) { + call.parameters.push_back(parseExpression()); + if (current().kind != Token::Kind::RParen) { + expect(Token::Kind::Comma); + } + } + expect(Token::Kind::RParen); + } + + if (current().kind == Token::Kind::LBracket) { + error(current(), "Designator not yet supported for gate call statements"); + } + + while (current().kind != Token::Kind::Semicolon) { + call.operands.push_back(parseGateOperand()); + if (current().kind != Token::Kind::Semicolon) { + expect(Token::Kind::Comma); + } + } + + if (!call.operandsOptional && call.operands.empty()) { + error(current(), "Expected gate operands"); + } + + expect(Token::Kind::Semicolon); + return call; + } + + ParsedModifier parseGateModifier() { + ParsedModifier mod; + if (current().kind == Token::Kind::Inv) { + advance(); + mod.kind = ParsedModifier::Kind::Inv; + return mod; + } + if (current().kind == Token::Kind::Pow) { + advance(); + expect(Token::Kind::LParen); + mod.kind = ParsedModifier::Kind::Pow; + mod.expression = parseExpression(); + expect(Token::Kind::RParen); + return mod; + } + // ctrl / negctrl + mod.kind = current().kind == Token::Kind::Ctrl + ? ParsedModifier::Kind::Ctrl + : ParsedModifier::Kind::NegCtrl; + advance(); + if (current().kind == Token::Kind::LParen) { + advance(); + mod.expression = parseExpression(); + expect(Token::Kind::RParen); + } + return mod; + } + + ParsedOperand parseGateOperand() { + ParsedOperand operand; + if (current().kind == Token::Kind::HardwareQubit) { + operand.isHardwareQubit = true; + operand.hardwareQubit = static_cast(current().val); + advance(); + return operand; + } + operand.name = expect(Token::Kind::Identifier).str; + if (current().kind == Token::Kind::LBracket) { + advance(); + operand.index = parseExpression(); + expect(Token::Kind::RBracket); + } + return operand; + } + + ParsedBitRef parseBitRef() { + ParsedBitRef ref; + ref.name = expect(Token::Kind::Identifier).str; + if (current().kind == Token::Kind::LBracket) { + advance(); + ref.index = parseExpression(); + expect(Token::Kind::RBracket); + } + return ref; + } + + /// Resolve and emit a parsed gate call against `scope`: expand operands, + /// interpret modifiers, handle broadcasting, then either inline a compound + /// gate or emit a standard gate. + void emitGateCall(const ParsedGateCall& call, const QubitScope& scope) { + const auto& id = call.identifier; + const auto& debugInfo = call.debugInfo; + 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()) { + error(*debugInfo, "No OpenQASM definition found for gate '" + id + "'."); + } + + // Translate parameters into MLIR values. Constant folding of the resulting + // `arith` computation is left to MLIR's own canonicalizer. + SmallVector params; + params.reserve(call.parameters.size()); + for (const auto& arg : call.parameters) { + params.push_back(emitFloatExpression(arg, debugInfo)); + } + + // Expand operands to MLIR values. + SmallVector operands; + SmallVector> operandsBroadcasting; + auto broadcasting = false; + for (const auto& operand : call.operands) { + const auto resolved = + resolveGateOperandInScope(operand, scope, debugInfo); + if (const auto* value = std::get_if(&resolved)) { + operands.push_back(*value); + } else if (const auto* values = + std::get_if>(&resolved)) { + operandsBroadcasting.push_back(*values); + broadcasting = true; + } + } + + if (broadcasting && !operands.empty()) { + error(*debugInfo, "Gate operands must be single qubits or quantum " + "registers and not a mix of both."); + } + + if (broadcasting && numCompatControls != 0) { + error(*debugInfo, "OpenQASM 2 gates cannot be broadcasted."); + } + + size_t broadcastWidth = 0; + if (broadcasting) { + for (const auto& operand : operandsBroadcasting) { + if (broadcastWidth == 0) { + broadcastWidth = operand.size(); + } else if (broadcastWidth != operand.size()) { + error(*debugInfo, + "All broadcasting operands must have the same width."); + } + } + } + + auto invert = false; + size_t numControls = 0; + SmallVector posControls; + SmallVector negControls; + SmallVector> posControlsBroadcasting; + SmallVector> negControlsBroadcasting; + + // Parse modifiers. + for (const auto& mod : call.modifiers) { + if (mod.kind == ParsedModifier::Kind::Inv) { + invert = !invert; + } else if (mod.kind == ParsedModifier::Kind::Ctrl || + mod.kind == ParsedModifier::Kind::NegCtrl) { + const auto n = evaluatePositiveConstant(mod.expression, debugInfo, 1); + for (size_t i = 0; i < n; ++i, ++numControls) { + const auto positive = mod.kind == ParsedModifier::Kind::Ctrl; + if (!broadcasting) { + if (numControls >= operands.size()) { + error(*debugInfo, "Control index out of bounds."); + } + auto operand = operands[numControls]; + if (positive) { + posControls.push_back(operand); + } else { + negControls.push_back(operand); + } + } else { + if (numControls >= operandsBroadcasting.size()) { + error(*debugInfo, "Control index out of bounds."); + } + const auto& operand = operandsBroadcasting[numControls]; + if (positive) { + posControlsBroadcasting.push_back(operand); + } else { + negControlsBroadcasting.push_back(operand); + } + } + } + } else { + error(*debugInfo, + "Only ctrl, negctrl, and inv modifiers are supported."); + } + } + + // OpenQASM 2 compatibility: append implicit control qubits. + for (size_t i = 0; i < numCompatControls; ++i, ++numControls) { + if (numControls >= operands.size()) { + error(*debugInfo, "Control index out of bounds."); + } + 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 = std::get_if(&it->second)) { + if (broadcasting) { + error(*debugInfo, "Broadcasted compound gates are not supported."); + } + emitCompoundGate(*compound, params, targets, posControls, negControls, + invert, debugInfo); + return; + } + + // Emit standard gate. + const auto dispIt = GATE_DISPATCH.find(resolvedId); + if (dispIt == GATE_DISPATCH.end()) { + error(*debugInfo, "No MLIR definition found for gate '" + id + "'."); + } + + if (std::get(it->second).nParameters != params.size()) { + error(*debugInfo, "Invalid number of parameters for gate '" + id + "'."); + } + + if (!broadcasting) { + emitStandardGate(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]); + } + emitStandardGate(dispIt->second, params, bTargets, bPosControls, + bNegControls, invert); + } + } + } + + /// Emit a gate body wrapped in its ctrl/negctrl/inv modifiers. + 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); + } + } + + /// Emit a standard gate. + void emitStandardGate(const GateFn& gateFn, ValueRange params, + ValueRange targets, ValueRange posControls, + ValueRange negControls, bool invert) { + auto bodyFn = [&](ValueRange qubits) { gateFn(builder, qubits, params); }; + emitModifiedGate(bodyFn, targets, posControls, negControls, invert); + } + + /// Inline a compound gate. + void emitCompoundGate(const ParsedCompoundGate& gate, ValueRange 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 compound gate is inlined + // within a modifier region. + llvm::StringMap> targetsMap; + + for (const auto& [targetName, target] : + llvm::zip_equal(gate.targetNames, targets)) { + auto iter = llvm::find(targets, target); + if (iter == targets.end()) { + error(*debugInfo, "Target '" + targetName + "' not found in operands."); + } + const auto index = + static_cast(std::distance(targets.begin(), iter)); + targetsMap[targetName].push_back(index); + } + + // Bind parameters so they can be referenced by name inside the gate body. + parameterConstants.push(); + for (size_t i = 0; i < gate.parameterNames.size(); ++i) { + parameterConstants.emplace(gate.parameterNames[i], 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] = {Value{}, std::move(args)}; + } + for (const auto& bodyCall : gate.body) { + emitGateCall(bodyCall, localScope); + } + }; + + emitModifiedGate(bodyFn, targets, posControls, negControls, invert); + + parameterConstants.pop(); + } + + //===--- Operand resolution -------------------------------------------===// + + [[nodiscard]] std::variant> + resolveGateOperand(const ParsedOperand& operand, + const std::shared_ptr& debugInfo) { + return resolveGateOperandInScope(operand, qubitRegisters, debugInfo); + } + + [[nodiscard]] std::variant> + resolveGateOperandInScope(const ParsedOperand& operand, + const QubitScope& scope, + const std::shared_ptr& debugInfo) { + if (operand.isHardwareQubit) { + return builder.staticQubit(operand.hardwareQubit); + } + + const auto& name = operand.name; + auto it = scope.find(name); + if (it == scope.end()) { + error(*debugInfo, "Unknown qubit register '" + name + "'."); + } + + const auto& binding = it->second; + + if (!operand.index) { + if (binding.qubits.size() == 1) { + return binding.qubits[0]; + } + // Return full register. + return binding.qubits; + } + + // Dynamic index via a bound for-loop variable (e.g. `q[i]`). + const auto& indexExpr = *operand.index; + if (indexExpr.kind == ParsedExpr::Kind::Ident) { + if (const auto iv = loopVariables.find(indexExpr.name)) { + if (!binding.memref) { + error(*debugInfo, + "Dynamic qubit indexing requires a qubit register."); + } + if (const auto cached = loadedDynamicElements.find(name)) { + return *cached; + } + const auto loaded = builder.memrefLoad(binding.memref, *iv); + loadedDynamicElements.emplace(name, loaded); + return loaded; + } + } + + const auto index = evaluatePositiveConstant(indexExpr, debugInfo); + if (index >= binding.qubits.size()) { + error(*debugInfo, "Qubit index out of bounds."); + } + return binding.qubits[index]; + } + + [[nodiscard]] SmallVector + resolveClassicalBits(const ParsedBitRef& operand, + const std::shared_ptr& debugInfo) const { + const auto& name = operand.name; + auto it = classicalRegisters.find(name); + if (it == classicalRegisters.end()) { + error(*debugInfo, "Unknown classical register '" + name + "'."); + } + + const auto& creg = it->second; + SmallVector bits; + + if (!operand.index) { + for (int64_t i = 0; i < creg.size; ++i) { + bits.push_back(creg[i]); + } + return bits; + } + + const auto index = evaluatePositiveConstant(*operand.index, debugInfo); + if (std::cmp_greater_equal(index, creg.size)) { + error(*debugInfo, "Classical bit index out of bounds."); + } + bits.push_back(creg[static_cast(index)]); + return bits; + } + + //===--- Expression parsing -------------------------------------------===// + + /// expr := term (('+' | '-') term)* + ParsedExpr parseExpression() { + auto x = parseTerm(); + while (current().kind == Token::Kind::Plus || + current().kind == Token::Kind::Minus) { + const auto op = current().kind; + advance(); + auto y = parseTerm(); + ParsedExpr binary; + binary.kind = ParsedExpr::Kind::Binary; + binary.op = op; + binary.children.push_back(std::move(x)); + binary.children.push_back(std::move(y)); + x = std::move(binary); + } + return x; + } + + /// term := unary (('*' | '/') unary)* + ParsedExpr parseTerm() { + auto x = parseUnary(); + while (current().kind == Token::Kind::Asterisk || + current().kind == Token::Kind::Slash) { + const auto op = current().kind; + advance(); + auto y = parseUnary(); + ParsedExpr binary; + binary.kind = ParsedExpr::Kind::Binary; + binary.op = op; + binary.children.push_back(std::move(x)); + binary.children.push_back(std::move(y)); + x = std::move(binary); + } + return x; + } + + /// unary := '-' unary | primary + ParsedExpr parseUnary() { + if (current().kind == Token::Kind::Minus) { + advance(); + ParsedExpr unary; + unary.kind = ParsedExpr::Kind::Unary; + unary.op = Token::Kind::Minus; + unary.children.push_back(parseUnary()); + return unary; + } + return parsePrimary(); + } + + /// primary := literal | ident | '(' expr ')' + ParsedExpr parsePrimary() { + ParsedExpr e; + switch (current().kind) { + case Token::Kind::FloatLiteral: + e.kind = ParsedExpr::Kind::Literal; + e.isFloatLiteral = true; + e.fpValue = current().valReal; + advance(); + return e; + case Token::Kind::IntegerLiteral: + e.kind = ParsedExpr::Kind::Literal; + e.isFloatLiteral = false; + e.intValue = current().val; + advance(); + return e; + case Token::Kind::Identifier: + e.kind = ParsedExpr::Kind::Ident; + e.name = current().str; + advance(); + return e; + case Token::Kind::LParen: { + advance(); + auto inner = parseExpression(); + expect(Token::Kind::RParen); + return inner; + } + default: + error(current(), + "Expected expression, got '" + current().toString() + "'."); + } + } + + //===--- Expression evaluation ----------------------------------------===// + + /// Emit the `arith` ops for a scalar gate-parameter expression and return the + /// resulting f64 value; constant folding is left to MLIR's canonicalizer. + [[nodiscard]] Value + emitFloatExpression(const ParsedExpr& expr, + const std::shared_ptr& debugInfo) { + switch (expr.kind) { + case ParsedExpr::Kind::Literal: { + const auto value = expr.isFloatLiteral + ? expr.fpValue + : static_cast(expr.intValue); + return arith::ConstantOp::create(builder, builder.getF64FloatAttr(value)) + .getResult(); + } + case ParsedExpr::Kind::Ident: + return resolveParameterIdentifier(expr.name, debugInfo); + case ParsedExpr::Kind::Unary: { + const auto operand = emitFloatExpression(expr.children[0], debugInfo); + if (expr.op == Token::Kind::Minus) { + return arith::NegFOp::create(builder, operand).getResult(); + } + error(*debugInfo, + "Unsupported unary operator in gate parameter expression."); + } + case ParsedExpr::Kind::Binary: { + const auto lhs = emitFloatExpression(expr.children[0], debugInfo); + const auto rhs = emitFloatExpression(expr.children[1], debugInfo); + switch (expr.op) { + case Token::Kind::Plus: + return arith::AddFOp::create(builder, lhs, rhs).getResult(); + case Token::Kind::Minus: + return arith::SubFOp::create(builder, lhs, rhs).getResult(); + case Token::Kind::Asterisk: + return arith::MulFOp::create(builder, lhs, rhs).getResult(); + case Token::Kind::Slash: + return arith::DivFOp::create(builder, lhs, rhs).getResult(); + default: + error(*debugInfo, + "Unsupported binary operator in gate parameter expression."); + } + } + } + error(*debugInfo, "Unsupported gate parameter expression."); + } + + /// Resolve an identifier referenced in a gate-parameter expression: either a + /// `const`-declared/compound-gate-parameter name, or a builtin constant. + [[nodiscard]] Value + resolveParameterIdentifier(const std::string& name, + const std::shared_ptr& debugInfo) { + if (const auto value = parameterConstants.find(name)) { + return *value; + } + if (const auto value = lookupBuiltinConstant(name)) { + return arith::ConstantOp::create(builder, builder.getF64FloatAttr(*value)) + .getResult(); + } + error(*debugInfo, "Unknown identifier '" + name + "'."); + } + + /// Statically evaluate an integer-constant expression. These positions + /// (register sizes, loop bounds/step, control counts, literal indices) need a + /// concrete `int64_t` at build time, so a genuinely dynamic value is an + /// error. + [[nodiscard]] int64_t + evaluateIntegerConstant(const ParsedExpr& expr, + const std::shared_ptr& debugInfo) const { + switch (expr.kind) { + case ParsedExpr::Kind::Literal: + if (expr.isFloatLiteral) { + error(*debugInfo, "Expected a constant integer expression."); + } + return expr.intValue; + case ParsedExpr::Kind::Unary: + if (expr.op == Token::Kind::Minus) { + return -evaluateIntegerConstant(expr.children[0], debugInfo); + } + error(*debugInfo, "Expected a constant integer expression."); + case ParsedExpr::Kind::Binary: { + const auto lhs = evaluateIntegerConstant(expr.children[0], debugInfo); + const auto rhs = evaluateIntegerConstant(expr.children[1], debugInfo); + switch (expr.op) { + case Token::Kind::Plus: + return lhs + rhs; + case Token::Kind::Minus: + return lhs - rhs; + case Token::Kind::Asterisk: + return lhs * rhs; + case Token::Kind::Slash: + if (rhs == 0) { + error(*debugInfo, "Division by zero in constant expression."); + } + return lhs / rhs; + default: + error(*debugInfo, "Expected a constant integer expression."); + } + } + case ParsedExpr::Kind::Ident: + default: + error(*debugInfo, "Expected a constant integer expression."); + } + } + + /// Evaluate an expression to a non-negative integer. + [[nodiscard]] size_t + evaluatePositiveConstant(const ParsedExpr& expr, + const std::shared_ptr& debugInfo) const { + return static_cast(evaluateIntegerConstant(expr, debugInfo)); + } + + /// Evaluate an optional expression to a non-negative integer, using + /// @p defaultValue when the expression is absent. + [[nodiscard]] size_t + evaluatePositiveConstant(const std::optional& expr, + const std::shared_ptr& debugInfo, + size_t defaultValue) const { + if (!expr) { + return defaultValue; + } + return evaluatePositiveConstant(*expr, debugInfo); + } + + /// error() overload taking an already-built DebugInfo. + [[noreturn]] void error(const DebugInfo& debugInfo, + const std::string& msg) const { + throw CompilerError(msg, std::make_shared(debugInfo)); + } +}; + +} // namespace + +namespace detail { + +OwningOpRef parseQASM3(std::string_view source, + MLIRContext* context) { + QASM3Parser parser(context); + return parser.parse(source); +} + +} // namespace detail + +} // namespace mlir::qc diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.h b/mlir/lib/Dialect/QC/Translation/QASM3Parser.h new file mode 100644 index 0000000000..7a79a9f6bd --- /dev/null +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.h @@ -0,0 +1,54 @@ +/* + * 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 + +namespace mlir { + +// Forward declarations +class MLIRContext; +class ModuleOp; + +namespace qc::detail { + +/** + * @brief Parse an OpenQASM 3 program and emit the equivalent QC dialect module. + * + * @details + * Implementation detail of `translateQASM3ToQC`; not part of the public API + * (this header lives with the library sources and is not installed). Prefer the + * `translateQASM3ToQC` entry points declared in + * `mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h`, which wrap this in the + * diagnostic-reporting `try`/`catch`. + * + * This is a single-pass, hand-written recursive-descent parser that consumes + * `qasm3::Scanner` tokens directly and emits QC operations via the + * `QCProgramBuilder` as it parses, without building an intermediate AST. It + * depends on the legacy `qasm3` package only for its `Scanner`/`Token` lexer, + * the `GateInfo`/`NestedEnvironment` helpers, and `DebugInfo`/`CompilerError` + * for diagnostics. + * + * On a malformed program it throws a `qasm3::CompilerError` (or another + * `std::exception`); callers are expected to translate that into a diagnostic. + * + * @param source The OpenQASM 3 program text. + * @param context The MLIRContext to create the module in. + * @return The constructed QC dialect module. + */ +[[nodiscard]] OwningOpRef parseQASM3(std::string_view source, + MLIRContext* context); + +} // namespace qc::detail + +} // namespace mlir diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp index bfc7bfe989..1a9149686f 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp @@ -10,1022 +10,22 @@ #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 "QASM3Parser.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 //===----------------------------------------------------------------------===// @@ -1033,20 +33,10 @@ class MLIRQasmImporter final : public qasm3::InstVisitor { OwningOpRef translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context) { try { - auto buffer = + const 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; + return detail::parseQASM3(std::string_view(buffer.data(), buffer.size()), + context); } catch (const std::exception& e) { llvm::errs() << "Import error: " << e.what() << "\n"; return nullptr; diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index b18c858b2d..04fc37a2f7 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -416,4 +416,77 @@ 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{"NestedForLoopIfOp", qasm::nestedForLoopIfOp, + MQT_NAMED_BUILDER(qc::nestedForLoopIfOp)}, + QASM3TranslationTestCase{"SimpleWhileReset", qasm::simpleWhileReset, + MQT_NAMED_BUILDER(qc::simpleWhileReset)}, + QASM3TranslationTestCase{"SimpleForLoop", qasm::simpleForLoop, + MQT_NAMED_BUILDER(qc::simpleForLoop)}, + QASM3TranslationTestCase{ + "NestedForLoopCtrlOpWithSeparateQubit", + qasm::nestedForLoopCtrlOpWithSeparateQubit, + MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithSeparateQubit)}, + QASM3TranslationTestCase{ + "NestedForLoopCtrlOpWithExtractedQubit", + qasm::nestedForLoopCtrlOpWithExtractedQubit, + MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithExtractedQubit)})); + +namespace { + +/// Build a context with all dialects required by the QASM3 translation loaded. +std::unique_ptr makeTranslationContext() { + DialectRegistry registry; + registry.insert(); + auto context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + return context; +} + +} // namespace + +// `stdgates.inc` and `qelib1.inc` are fully covered by the native gate table +// and treated as no-ops; any other include is a hard error. +TEST(QASM3TranslationIncludeTest, RejectsUnknownInclude) { + const auto context = makeTranslationContext(); + const std::string source = R"qasm(OPENQASM 3.0; +include "other.inc"; +qubit q; +)qasm"; + EXPECT_FALSE(qc::translateQASM3ToQC(source, context.get())); +} + +TEST(QASM3TranslationIncludeTest, AcceptsQelib1Include) { + const auto context = makeTranslationContext(); + const std::string source = R"qasm(OPENQASM 3.0; +include "qelib1.inc"; +qubit q; +x q; +)qasm"; + EXPECT_TRUE(qc::translateQASM3ToQC(source, context.get())); +} + +// A folded structural constant index (e.g. `q[1 + 1]`) now resolves to the same +// static qubit as the literal index it evaluates to. +TEST(QASM3TranslationFoldingTest, FoldsStructuralConstantIndex) { + const auto context = makeTranslationContext(); + const std::string folded = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +x q[1 + 1]; +)qasm"; + const std::string literal = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +x q[2]; +)qasm"; + + auto foldedModule = qc::translateQASM3ToQC(folded, context.get()); + auto literalModule = qc::translateQASM3ToQC(literal, context.get()); + ASSERT_TRUE(foldedModule); + ASSERT_TRUE(literalModule); + EXPECT_TRUE(areModulesEquivalentWithPermutations(foldedModule.get(), + literalModule.get())); +} diff --git a/mlir/unittests/programs/qasm_programs.cpp b/mlir/unittests/programs/qasm_programs.cpp index 2386d7e8c7..bed3d2cbca 100644 --- a/mlir/unittests/programs/qasm_programs.cpp +++ b/mlir/unittests/programs/qasm_programs.cpp @@ -765,5 +765,57 @@ if (c) { } )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 simpleWhileReset = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit q; +h q; +while (measure q) { + h q; +} +)qasm"; + +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 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"; + } // 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..e8e9223238 100644 --- a/mlir/unittests/programs/qasm_programs.h +++ b/mlir/unittests/programs/qasm_programs.h @@ -370,5 +370,24 @@ 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 nestedForLoopIfOp; + +/// Creates a circuit with a while operation using a while loop. +extern const std::string simpleWhileReset; + +/// Creates a circuit with a simple for operation with a register. +extern const std::string simpleForLoop; + +/// 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; + } // namespace mlir::qasm // NOLINTEND(readability-identifier-naming) From a78ff53d237e4c1286d4a65592c26ab88e3f2344 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 3 Jul 2026 21:00:23 +0200 Subject: [PATCH 02/24] Daniel's first cleanup --- .../Dialect/QC/Translation/QASM3Parser.cpp | 491 +++++++++--------- 1 file changed, 240 insertions(+), 251 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp index 886580be48..6790762957 100644 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp @@ -184,16 +184,21 @@ struct QubitBinding { using QubitScope = llvm::StringMap; /// Look up a well-known OpenQASM 3 numeric constant (`pi`, `tau`, `euler`, -/// and their Unicode aliases) by identifier. -std::optional lookupBuiltinConstant(llvm::StringRef name) { +/// and their Unicode aliases) by identifier and emit it as an `f64` MLIR value. +std::optional lookupBuiltinConstant(llvm::StringRef name, + QCProgramBuilder& builder) { + const auto constant = [&](double value) -> Value { + return arith::ConstantOp::create(builder, builder.getF64FloatAttr(value)) + .getResult(); + }; if (name == "pi" || name == "π") { - return ::qc::PI; + return constant(::qc::PI); } if (name == "tau" || name == "τ") { - return ::qc::TAU; + return constant(::qc::TAU); } if (name == "euler" || name == "ℇ") { - return ::qc::E; + return constant(::qc::E); } return std::nullopt; } @@ -206,54 +211,47 @@ std::optional lookupBuiltinConstant(llvm::StringRef name) { /// structural (integer) positions, and stored verbatim inside compound-gate /// bodies so a gate body can be parsed once and replayed at every call site. struct ParsedExpr { - enum class Kind : uint8_t { Literal, Ident, Unary, Binary }; + enum class Kind : uint8_t { IntLiteral, FloatLiteral, Ident, Unary, Binary }; - Kind kind{Kind::Literal}; + Kind kind{Kind::IntLiteral}; // Literal - double fpValue{}; + double floatValue{}; int64_t intValue{}; - bool isFloatLiteral{false}; // Ident std::string name; - // Operator token. Unary: Minus (negation). Binary: Plus, Minus, Asterisk, - // Slash. + // Unary or Binary Token::Kind op{}; std::vector children; static ParsedExpr makeInt(int64_t value) { ParsedExpr e; - e.kind = Kind::Literal; + e.kind = Kind::IntLiteral; e.intValue = value; - e.isFloatLiteral = false; return e; } }; -/// A single gate modifier (ctrl/negctrl/inv/pow) in the order written. +/// A gate modifier. struct ParsedModifier { - enum class Kind : uint8_t { Inv, Pow, Ctrl, NegCtrl } kind{Kind::Inv}; + Token::Kind kind{Token::Kind::Inv}; std::optional expression; }; -/// A gate operand: either a hardware qubit, or a (possibly indexed) named -/// qubit. The index expression is kept unresolved so the same operand can be -/// resolved against different scopes (top-level vs. compound-gate-local) and -/// so a for-loop induction variable can be recognised at resolution time. +/// A gate operand: either a (possibly indexed) named qubit, or a hardware +/// qubit. struct ParsedOperand { - bool isHardwareQubit{false}; - uint64_t hardwareQubit{}; std::string name; std::optional index; + std::optional hardwareQubit; }; /// A parsed gate-call statement (front half only): its modifiers, parameter /// expressions and operands, ready to be resolved and emitted. struct ParsedGateCall { std::string identifier; - bool operandsOptional{false}; std::vector modifiers; std::vector parameters; std::vector operands; @@ -318,11 +316,10 @@ class QASM3Parser final { OwningOpRef parse(std::string_view source) { input = std::make_unique(std::string(source)); scanner = std::make_unique(input.get()); - // Prime the token window: the first advance loads the first token into the - // lookahead, the second moves it into `current()`. Two calls are needed - // because both `current()` and `peek()` start empty. - advance(); - advance(); + + // Initialize the token windows + currentToken = scanner->next(); + nextToken = scanner->next(); builder.initialize(); parseProgram(); @@ -391,17 +388,20 @@ class QASM3Parser final { return std::make_shared(t.line, t.col, ""); } - [[noreturn]] void error(const Token& t, const std::string& msg) const { + void error(const Token& t, const std::string& msg) const { throw CompilerError(msg, makeDebugInfo(t)); } + void error(const DebugInfo& debugInfo, const std::string& msg) const { + throw CompilerError(msg, std::make_shared(debugInfo)); + } + Token expect(const Token::Kind expected) { - if (current().kind != expected) { - error(current(), "Expected '" + Token::kindToString(expected) + - "', got '" + Token::kindToString(current().kind) + - "'."); - } auto token = current(); + if (token.kind != expected) { + error(token, "Expected '" + Token::kindToString(expected) + "', got '" + + Token::kindToString(token.kind) + "'."); + } advance(); return token; } @@ -410,37 +410,15 @@ class QASM3Parser final { void parseProgram() { while (!isAtEnd()) { - if (current().kind == Token::Kind::OpenQasm) { - parseVersionDeclaration(); - continue; - } parseStatement(); } } - void parseVersionDeclaration() { - expect(Token::Kind::OpenQasm); - double version = 0.0; - if (current().kind == Token::Kind::FloatLiteral) { - version = current().valReal; - advance(); - } else if (current().kind == Token::Kind::IntegerLiteral) { - version = static_cast(current().val); - advance(); - } else { - error(current(), - "Version declaration must be a float or integer literal."); - } - expect(Token::Kind::Semicolon); - if (version < 3) { - openQASM2CompatMode = true; - } - } - - /// The "big switch": parse one statement, emit MLIR for it, move on. Also - /// used to emit statements nested in an if/for/while body. void parseStatement() { switch (current().kind) { + case Token::Kind::OpenQasm: + parseVersionDeclaration(); + return; case Token::Kind::Include: parseInclude(); return; @@ -461,14 +439,14 @@ class QASM3Parser final { parseDeclaration(/*isConst=*/false); return; case Token::Kind::Gate: - parseGateDeclaration(/*isOpaque=*/false); + parseGateDeclaration(); return; case Token::Kind::Opaque: - parseGateDeclaration(/*isOpaque=*/true); + error(current(), "Opaque gate declarations are not supported."); return; case Token::Kind::InitialLayout: case Token::Kind::OutputPermutation: - // Not relevant for the QC translation. + // No counterparts in QC advance(); return; case Token::Kind::Barrier: @@ -536,6 +514,27 @@ class QASM3Parser final { } } + //===--- Version ------------------------------------------------------===// + + void parseVersionDeclaration() { + expect(Token::Kind::OpenQasm); + double version = 0.0; + if (current().kind == Token::Kind::FloatLiteral) { + version = current().valReal; + advance(); + } else if (current().kind == Token::Kind::IntegerLiteral) { + version = static_cast(current().val); + advance(); + } else { + error(current(), + "Version declaration must be a float or integer literal."); + } + expect(Token::Kind::Semicolon); + if (version < 3) { + openQASM2CompatMode = true; + } + } + //===--- Include ------------------------------------------------------===// void parseInclude() { @@ -553,25 +552,33 @@ class QASM3Parser final { //===--- Declarations -------------------------------------------------===// + // TODO: Can this function be broken up into several ones? Maybe one for + // qubits and registers thereof, one for classical bits and registers thereof, + // and one for "numbers"? void parseDeclaration(bool isConst) { - const auto beginToken = current(); - const auto debugInfo = makeDebugInfo(beginToken); - - const auto typeKind = current().kind; - const bool oldStyle = - typeKind == Token::Kind::Qreg || typeKind == Token::Kind::CReg; + const auto declaration = current(); + const auto debugInfo = makeDebugInfo(declaration); advance(); + const bool oldStyle = declaration.kind == Token::Kind::Qreg || + declaration.kind == Token::Kind::CReg; + std::optional designator; if (!oldStyle && current().kind == Token::Kind::LBracket) { - designator = parseTypeDesignator(debugInfo); + expect(Token::Kind::LBracket); + const auto expr = parseExpression(); + expect(Token::Kind::RBracket); + designator = evaluateIntegerConstant(expr, debugInfo); } const auto id = expect(Token::Kind::Identifier).str; if (current().kind == Token::Kind::LBracket) { if (oldStyle) { - designator = parseTypeDesignator(debugInfo); + expect(Token::Kind::LBracket); + const auto expr = parseExpression(); + expect(Token::Kind::RBracket); + designator = evaluateIntegerConstant(expr, debugInfo); } else { error(current(), "In OpenQASM 3.0, the designator has been changed to " "`type[designator] identifier;`"); @@ -593,13 +600,13 @@ class QASM3Parser final { expect(Token::Kind::Semicolon); if (declaredNames.contains(id)) { - error(beginToken, "Identifier '" + id + "' already declared."); + error(declaration, "Identifier '" + id + "' already declared."); } declaredNames[id] = isConst; if (isConst) { if (!initExpr) { - error(beginToken, + error(declaration, "Constant declaration initialization expression must be " "initialized."); } @@ -608,17 +615,19 @@ class QASM3Parser final { } // The type must be a sized register type. - const bool sized = - typeKind == Token::Kind::Qubit || typeKind == Token::Kind::Qreg || - typeKind == Token::Kind::Bit || typeKind == Token::Kind::CReg || - typeKind == Token::Kind::Int || typeKind == Token::Kind::Uint; + const bool sized = declaration.kind == Token::Kind::Qubit || + declaration.kind == Token::Kind::Qreg || + declaration.kind == Token::Kind::Bit || + declaration.kind == Token::Kind::CReg || + declaration.kind == Token::Kind::Int || + declaration.kind == Token::Kind::Uint; if (!sized) { - error(beginToken, "Only sized types are supported."); + error(declaration, "Only sized types are supported."); } const auto size = designator.value_or(1); - switch (typeKind) { + switch (declaration.kind) { case Token::Kind::Qubit: case Token::Kind::Qreg: { if (size == 1 && !designator) { @@ -639,7 +648,7 @@ class QASM3Parser final { classicalRegisters[id] = builder.allocClassicalBitRegister(size, id); break; default: - error(beginToken, "Unsupported declaration type."); + error(declaration, "Unsupported declaration type."); } if (measureInit) { @@ -648,44 +657,39 @@ class QASM3Parser final { return; } if (initExpr) { - error(beginToken, "Only measure expressions can declare variables."); + error(declaration, "Only measure expressions can declare variables."); } } - int64_t parseTypeDesignator(const std::shared_ptr& debugInfo) { - expect(Token::Kind::LBracket); - const auto expr = parseExpression(); - expect(Token::Kind::RBracket); - return evaluateIntegerConstant(expr, debugInfo); - } - - //===--- Measurement --------------------------------------------------===// + //===--- Assignments --------------------------------------------------===// + /// Parse a `c = measure q` statement. void parseAssignment() { - const auto beginToken = current(); - const auto debugInfo = makeDebugInfo(beginToken); + const auto debugInfo = makeDebugInfo(current()); const auto target = parseBitRef(); - // Consume the assignment operator; only plain `=` with a measure RHS is - // supported (classical computation is out of scope). if (current().kind != Token::Kind::Equals) { - error(current(), "Classical computations are not supported."); + error(current(), "Classical computations are not supported yet."); } advance(); if (current().kind != Token::Kind::Measure) { - error(current(), "Classical computations are not supported."); + error(current(), "Classical computations are not supported yet."); } advance(); + const auto operand = parseGateOperand(); expect(Token::Kind::Semicolon); emitMeasureAssignment(target, operand, debugInfo); } + //===--- Measurement --------------------------------------------------===// + + /// Parse a `measure q -> c;` statement. void parseMeasure() { - const auto beginToken = expect(Token::Kind::Measure); - const auto debugInfo = makeDebugInfo(beginToken); + const auto measure = expect(Token::Kind::Measure); + const auto debugInfo = makeDebugInfo(measure); const auto operand = parseGateOperand(); expect(Token::Kind::Arrow); const auto target = parseBitRef(); @@ -709,25 +713,28 @@ class QASM3Parser final { "have the same width."); } 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); + const auto& registerName = bit.registerName; + const auto registerSize = bit.registerSize; + const auto registerIndex = bit.registerIndex; + auto result = + MeasureOp::create(builder, qubit, builder.getStringAttr(registerName), + builder.getI64IntegerAttr(registerSize), + builder.getI64IntegerAttr(registerIndex)) + .getResult(); + auto& registerBits = bitValues[registerName]; + const auto index = static_cast(registerIndex); + if (registerBits.size() <= index) { + registerBits.resize(index + 1); } - regBits[index] = result; + registerBits[index] = result; } } //===--- Barrier ------------------------------------------------------===// void parseBarrier() { - const auto beginToken = expect(Token::Kind::Barrier); - const auto debugInfo = makeDebugInfo(beginToken); + const auto barrier = expect(Token::Kind::Barrier); + const auto debugInfo = makeDebugInfo(barrier); SmallVector qubits; while (current().kind != Token::Kind::Semicolon) { const auto operand = parseGateOperand(); @@ -748,10 +755,9 @@ class QASM3Parser final { //===--- Reset --------------------------------------------------------===// void parseReset() { - const auto beginToken = expect(Token::Kind::Reset); - const auto debugInfo = makeDebugInfo(beginToken); + const auto reset = expect(Token::Kind::Reset); + const auto debugInfo = makeDebugInfo(reset); const auto operand = parseGateOperand(); - expect(Token::Kind::Semicolon); const auto resolved = resolveGateOperand(operand, debugInfo); if (std::holds_alternative(resolved)) { builder.reset(std::get(resolved)); @@ -760,58 +766,55 @@ class QASM3Parser final { builder.reset(qubit); } } + expect(Token::Kind::Semicolon); } - //===--- Control flow -------------------------------------------------===// + //===--- SCF ----------------------------------------------------------===// void parseIf() { - const auto beginToken = expect(Token::Kind::If); + expect(Token::Kind::If); expect(Token::Kind::LParen); auto condition = parseCondition(); expect(Token::Kind::RParen); - // Empty `{}` then-block (detectable with a single-token lookahead). + // Empty then block if (current().kind == Token::Kind::LBrace && peek().kind == Token::Kind::RBrace) { expect(Token::Kind::LBrace); expect(Token::Kind::RBrace); if (current().kind != Token::Kind::Else) { - error(beginToken, - "If statements with empty then and else blocks are not " - "supported."); + return; } expect(Token::Kind::Else); - // Emit the else block under the negated condition. auto trueValue = builder.boolConstant(true); condition = arith::XOrIOp::create(builder, condition, trueValue).getResult(); auto ifOp = scf::IfOp::create(builder, condition, /*withElseRegion=*/false); OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); + auto* thenBlock = &ifOp.getThenRegion().front(); + builder.setInsertionPointToStart(thenBlock); parseBlockOrStatement(); return; } - // Non-empty then-block. The else region only comes into being once we see - // an `else`, which appears after the then-block; build it lazily. auto ifOp = scf::IfOp::create(builder, condition, /*withElseRegion=*/false); - { - OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); - parseBlockOrStatement(); - } + + OpBuilder::InsertionGuard guard(builder); + + auto* thenBlock = &ifOp.getThenRegion().front(); + builder.setInsertionPointToStart(thenBlock); + parseBlockOrStatement(); if (current().kind == Token::Kind::Else) { expect(Token::Kind::Else); - // An empty else block is treated as no else at all. if (current().kind == Token::Kind::LBrace && peek().kind == Token::Kind::RBrace) { expect(Token::Kind::LBrace); expect(Token::Kind::RBrace); return; } - OpBuilder::InsertionGuard guard(builder); + auto* elseBlock = builder.createBlock(&ifOp.getElseRegion()); builder.setInsertionPointToStart(elseBlock); parseBlockOrStatement(); @@ -819,13 +822,9 @@ class QASM3Parser final { } } - /// Translate a for-loop into an `scf.for` loop. The bounds and step must - /// resolve to constants, since they decide the shape of the emitted loop. - /// OpenQASM 3's range is inclusive of the stop value while `scf.for`'s upper - /// bound is exclusive, hence the `+ 1`. void parseFor() { - const auto beginToken = expect(Token::Kind::For); - const auto debugInfo = makeDebugInfo(beginToken); + const auto forToken = expect(Token::Kind::For); + const auto debugInfo = makeDebugInfo(forToken); // The loop-variable type is not tracked further: the loop variable is // always treated as an unsigned integer index. @@ -840,6 +839,8 @@ class QASM3Parser final { expect(Token::Kind::In); expect(Token::Kind::LBracket); + // TODO: Related to my comment below, I'd like to directly use + // `mlir::Value`s for start, top, and step. const auto start = parseExpression(); expect(Token::Kind::Colon); const auto second = parseExpression(); @@ -856,9 +857,9 @@ class QASM3Parser final { expect(Token::Kind::RBracket); - const auto startVal = evaluatePositiveConstant(start, debugInfo); - const auto stepVal = evaluatePositiveConstant(step, debugInfo, 1); - const auto stopVal = evaluatePositiveConstant(stop, debugInfo); + const auto startVal = evaluateNonNegativeConstant(start, debugInfo); + const auto stepVal = evaluateNonNegativeConstant(step, debugInfo, 1); + const auto stopVal = evaluateNonNegativeConstant(stop, debugInfo); loopVariables.push(); loadedDynamicElements.push(); @@ -874,8 +875,6 @@ class QASM3Parser final { loopVariables.pop(); } - /// Translate a while-loop into an `scf.while` loop: the condition is - /// (re-)computed in the "before" region on every iteration. void parseWhile() { expect(Token::Kind::While); expect(Token::Kind::LParen); @@ -889,7 +888,7 @@ class QASM3Parser final { [&] { parseBlockOrStatement(); }); } - /// Translate an OpenQASM 3 if/while condition to an `i1` MLIR value. + /// Translate a 3 condition to an `i1` MLIR value [[nodiscard]] Value parseCondition() { const auto debugInfo = makeDebugInfo(current()); @@ -942,32 +941,33 @@ class QASM3Parser final { [[nodiscard]] Value lookupBitValue(const ParsedBitRef& bit, const std::shared_ptr& debugInfo) const { - const auto& regName = bit.name; - auto it = bitValues.find(regName); + const auto& registerName = bit.name; + auto it = bitValues.find(registerName); if (it == bitValues.end()) { - error(*debugInfo, "No classical bit of register '" + regName + + error(*debugInfo, "No classical bit of register '" + registerName + "' has been measured yet."); } - const auto& regBits = it->second; + const auto& registerBits = it->second; if (!bit.index) { - assert(regBits.size() == 1); - return regBits[0]; + assert(registerBits.size() == 1); + return registerBits[0]; } - const auto index = evaluatePositiveConstant(*bit.index, debugInfo); - if (index >= regBits.size() || !regBits[index]) { + const auto index = evaluateNonNegativeConstant(*bit.index, debugInfo); + if (index >= registerBits.size() || !registerBits[index]) { error(*debugInfo, "Bit " + std::to_string(index) + " of register '" + - regName + "' has been not measured yet."); + registerName + "' has been not measured yet."); } - return regBits[index]; + return registerBits[index]; } //===--- Gate declarations --------------------------------------------===// - void parseGateDeclaration(bool isOpaque) { - const auto beginToken = current(); - advance(); // `gate` or `opaque` + void parseGateDeclaration() { + const auto gate = current(); + advance(); + const auto id = expect(Token::Kind::Identifier).str; std::vector parameters; @@ -976,15 +976,8 @@ class QASM3Parser final { parameters = parseIdentifierList(); expect(Token::Kind::RParen); } - const auto targets = parseIdentifierList(); - if (isOpaque) { - expect(Token::Kind::Semicolon); - if (!gates.contains(id)) { - error(beginToken, "Unsupported opaque gate '" + id + "'."); - } - return; - } + const auto targets = parseIdentifierList(); expect(Token::Kind::LBrace); std::vector body; @@ -994,20 +987,23 @@ class QASM3Parser final { expect(Token::Kind::RBrace); if (gates.contains(id)) { - error(beginToken, "Gate '" + id + "' already declared."); + error(gate, "Gate '" + id + "' already declared."); } + // Verify parameters for (size_t i = 0; i < parameters.size(); ++i) { for (size_t j = i + 1; j < parameters.size(); ++j) { if (parameters[i] == parameters[j]) { - error(beginToken, "Parameter is already declared in compound gate."); + error(gate, "Parameter is already declared in compound gate."); } } } + + // Verify targets for (size_t i = 0; i < targets.size(); ++i) { for (size_t j = i + 1; j < targets.size(); ++j) { if (targets[i] == targets[j]) { - error(beginToken, "Target is already declared in compound gate."); + error(gate, "Target is already declared in compound gate."); } } } @@ -1032,6 +1028,7 @@ class QASM3Parser final { ParsedGateCall call; call.debugInfo = makeDebugInfo(current()); + // Parse modifiers while (current().kind == Token::Kind::Inv || current().kind == Token::Kind::Pow || current().kind == Token::Kind::Ctrl || @@ -1043,11 +1040,11 @@ class QASM3Parser final { if (current().kind == Token::Kind::Gphase) { advance(); call.identifier = "gphase"; - call.operandsOptional = true; } else { call.identifier = expect(Token::Kind::Identifier).str; } + // Parse parameters if (current().kind == Token::Kind::LParen) { advance(); while (current().kind != Token::Kind::RParen) { @@ -1060,9 +1057,11 @@ class QASM3Parser final { } if (current().kind == Token::Kind::LBracket) { - error(current(), "Designator not yet supported for gate call statements"); + error(current(), + "`gateCallStatement`s with designators are not supported yet"); } + // Parse operands while (current().kind != Token::Kind::Semicolon) { call.operands.push_back(parseGateOperand()); if (current().kind != Token::Kind::Semicolon) { @@ -1070,46 +1069,40 @@ class QASM3Parser final { } } - if (!call.operandsOptional && call.operands.empty()) { - error(current(), "Expected gate operands"); - } - expect(Token::Kind::Semicolon); return call; } ParsedModifier parseGateModifier() { ParsedModifier mod; - if (current().kind == Token::Kind::Inv) { + mod.kind = current().kind; + switch (current().kind) { + case Token::Kind::Inv: advance(); - mod.kind = ParsedModifier::Kind::Inv; return mod; - } - if (current().kind == Token::Kind::Pow) { + case Token::Kind::Pow: advance(); expect(Token::Kind::LParen); - mod.kind = ParsedModifier::Kind::Pow; mod.expression = parseExpression(); expect(Token::Kind::RParen); return mod; - } - // ctrl / negctrl - mod.kind = current().kind == Token::Kind::Ctrl - ? ParsedModifier::Kind::Ctrl - : ParsedModifier::Kind::NegCtrl; - advance(); - if (current().kind == Token::Kind::LParen) { + case Token::Kind::Ctrl: + case Token::Kind::NegCtrl: advance(); - mod.expression = parseExpression(); - expect(Token::Kind::RParen); + if (current().kind == Token::Kind::LParen) { + advance(); + mod.expression = parseExpression(); + expect(Token::Kind::RParen); + } + return mod; + default: + llvm_unreachable("Unknown gate modifier"); } - return mod; } ParsedOperand parseGateOperand() { ParsedOperand operand; if (current().kind == Token::Kind::HardwareQubit) { - operand.isHardwareQubit = true; operand.hardwareQubit = static_cast(current().val); advance(); return operand; @@ -1143,7 +1136,7 @@ class QASM3Parser final { auto it = gates.find(id); // OpenQASM 2 compatibility: strip leading `c` characters and treat them as - // implicit control modifiers. + // implicit control modifiers auto resolvedId = id; size_t numCompatControls = 0; if (openQASM2CompatMode && it == gates.end()) { @@ -1160,15 +1153,14 @@ class QASM3Parser final { error(*debugInfo, "No OpenQASM definition found for gate '" + id + "'."); } - // Translate parameters into MLIR values. Constant folding of the resulting - // `arith` computation is left to MLIR's own canonicalizer. + // Evaluate parameters SmallVector params; params.reserve(call.parameters.size()); for (const auto& arg : call.parameters) { params.push_back(emitFloatExpression(arg, debugInfo)); } - // Expand operands to MLIR values. + // Evaluate operands SmallVector operands; SmallVector> operandsBroadcasting; auto broadcasting = false; @@ -1212,15 +1204,20 @@ class QASM3Parser final { SmallVector> posControlsBroadcasting; SmallVector> negControlsBroadcasting; - // Parse modifiers. + // Evaluate modifiers for (const auto& mod : call.modifiers) { - if (mod.kind == ParsedModifier::Kind::Inv) { + switch (mod.kind) { + case Token::Kind::Inv: invert = !invert; - } else if (mod.kind == ParsedModifier::Kind::Ctrl || - mod.kind == ParsedModifier::Kind::NegCtrl) { - const auto n = evaluatePositiveConstant(mod.expression, debugInfo, 1); + break; + case Token::Kind::Pow: + error(*debugInfo, "Power modifiers are not supported yet."); + case Token::Kind::Ctrl: + case Token::Kind::NegCtrl: { + const auto n = + evaluateNonNegativeConstant(mod.expression, debugInfo, 1); + const auto positive = mod.kind == Token::Kind::Ctrl; for (size_t i = 0; i < n; ++i, ++numControls) { - const auto positive = mod.kind == ParsedModifier::Kind::Ctrl; if (!broadcasting) { if (numControls >= operands.size()) { error(*debugInfo, "Control index out of bounds."); @@ -1243,13 +1240,14 @@ class QASM3Parser final { } } } - } else { - error(*debugInfo, - "Only ctrl, negctrl, and inv modifiers are supported."); + break; + } + default: + llvm_unreachable("Unknown gate modifier"); } } - // OpenQASM 2 compatibility: append implicit control qubits. + // OpenQASM 2 compatibility: append implicit control qubits for (size_t i = 0; i < numCompatControls; ++i, ++numControls) { if (numControls >= operands.size()) { error(*debugInfo, "Control index out of bounds."); @@ -1257,7 +1255,7 @@ class QASM3Parser final { posControls.push_back(operands[numControls]); } - // Remaining operands are target qubits. + // Remaining operands are target qubits SmallVector targets; SmallVector> targetsBroadcasting; if (!broadcasting) { @@ -1267,7 +1265,7 @@ class QASM3Parser final { llvm::to_vector(llvm::drop_begin(operandsBroadcasting, numControls)); } - // Inline compound gate. + // Inline compound gate if (const auto* compound = std::get_if(&it->second)) { if (broadcasting) { error(*debugInfo, "Broadcasted compound gates are not supported."); @@ -1277,7 +1275,7 @@ class QASM3Parser final { return; } - // Emit standard gate. + // Emit standard gate const auto dispIt = GATE_DISPATCH.find(resolvedId); if (dispIt == GATE_DISPATCH.end()) { error(*debugInfo, "No MLIR definition found for gate '" + id + "'."); @@ -1313,7 +1311,7 @@ class QASM3Parser final { } } - /// Emit a gate body wrapped in its ctrl/negctrl/inv modifiers. + /// Emit a gate body wrapped in its modifiers. void emitModifiedGate(function_ref bodyFn, ValueRange targets, ValueRange posControls, ValueRange negControls, bool invert) { @@ -1413,8 +1411,8 @@ class QASM3Parser final { resolveGateOperandInScope(const ParsedOperand& operand, const QubitScope& scope, const std::shared_ptr& debugInfo) { - if (operand.isHardwareQubit) { - return builder.staticQubit(operand.hardwareQubit); + if (operand.hardwareQubit) { + return builder.staticQubit(*operand.hardwareQubit); } const auto& name = operand.name; @@ -1450,7 +1448,7 @@ class QASM3Parser final { } } - const auto index = evaluatePositiveConstant(indexExpr, debugInfo); + const auto index = evaluateNonNegativeConstant(indexExpr, debugInfo); if (index >= binding.qubits.size()) { error(*debugInfo, "Qubit index out of bounds."); } @@ -1476,7 +1474,7 @@ class QASM3Parser final { return bits; } - const auto index = evaluatePositiveConstant(*operand.index, debugInfo); + const auto index = evaluateNonNegativeConstant(*operand.index, debugInfo); if (std::cmp_greater_equal(index, creg.size)) { error(*debugInfo, "Classical bit index out of bounds."); } @@ -1539,25 +1537,26 @@ class QASM3Parser final { ParsedExpr parsePrimary() { ParsedExpr e; switch (current().kind) { - case Token::Kind::FloatLiteral: - e.kind = ParsedExpr::Kind::Literal; - e.isFloatLiteral = true; - e.fpValue = current().valReal; - advance(); + case Token::Kind::FloatLiteral: { + const auto value = expect(Token::Kind::FloatLiteral); + e.kind = ParsedExpr::Kind::FloatLiteral; + e.floatValue = value.valReal; return e; - case Token::Kind::IntegerLiteral: - e.kind = ParsedExpr::Kind::Literal; - e.isFloatLiteral = false; - e.intValue = current().val; - advance(); + } + case Token::Kind::IntegerLiteral: { + const auto value = expect(Token::Kind::IntegerLiteral); + e.kind = ParsedExpr::Kind::IntLiteral; + e.intValue = value.val; return e; - case Token::Kind::Identifier: + } + case Token::Kind::Identifier: { + const auto value = expect(Token::Kind::Identifier); e.kind = ParsedExpr::Kind::Ident; - e.name = current().str; - advance(); + e.name = value.str; return e; + } case Token::Kind::LParen: { - advance(); + expect(Token::Kind::LParen); auto inner = parseExpression(); expect(Token::Kind::RParen); return inner; @@ -1570,19 +1569,19 @@ class QASM3Parser final { //===--- Expression evaluation ----------------------------------------===// - /// Emit the `arith` ops for a scalar gate-parameter expression and return the - /// resulting f64 value; constant folding is left to MLIR's canonicalizer. + /// Translate a `ParsedExpr` to an `f64` MLIR value. [[nodiscard]] Value emitFloatExpression(const ParsedExpr& expr, const std::shared_ptr& debugInfo) { switch (expr.kind) { - case ParsedExpr::Kind::Literal: { - const auto value = expr.isFloatLiteral - ? expr.fpValue - : static_cast(expr.intValue); - return arith::ConstantOp::create(builder, builder.getF64FloatAttr(value)) + case ParsedExpr::Kind::IntLiteral: + return arith::ConstantOp::create(builder, + builder.getF64FloatAttr(expr.intValue)) + .getResult(); + case ParsedExpr::Kind::FloatLiteral: + return arith::ConstantOp::create(builder, + builder.getF64FloatAttr(expr.floatValue)) .getResult(); - } case ParsedExpr::Kind::Ident: return resolveParameterIdentifier(expr.name, debugInfo); case ParsedExpr::Kind::Unary: { @@ -1614,21 +1613,19 @@ class QASM3Parser final { error(*debugInfo, "Unsupported gate parameter expression."); } - /// Resolve an identifier referenced in a gate-parameter expression: either a - /// `const`-declared/compound-gate-parameter name, or a builtin constant. [[nodiscard]] Value resolveParameterIdentifier(const std::string& name, const std::shared_ptr& debugInfo) { if (const auto value = parameterConstants.find(name)) { return *value; } - if (const auto value = lookupBuiltinConstant(name)) { - return arith::ConstantOp::create(builder, builder.getF64FloatAttr(*value)) - .getResult(); + if (const auto value = lookupBuiltinConstant(name, builder)) { + return *value; } error(*debugInfo, "Unknown identifier '" + name + "'."); } + // TODO: Define emitIntExpression /// Statically evaluate an integer-constant expression. These positions /// (register sizes, loop bounds/step, control counts, literal indices) need a /// concrete `int64_t` at build time, so a genuinely dynamic value is an @@ -1637,10 +1634,7 @@ class QASM3Parser final { evaluateIntegerConstant(const ParsedExpr& expr, const std::shared_ptr& debugInfo) const { switch (expr.kind) { - case ParsedExpr::Kind::Literal: - if (expr.isFloatLiteral) { - error(*debugInfo, "Expected a constant integer expression."); - } + case ParsedExpr::Kind::IntLiteral: return expr.intValue; case ParsedExpr::Kind::Unary: if (expr.op == Token::Kind::Minus) { @@ -1666,6 +1660,7 @@ class QASM3Parser final { error(*debugInfo, "Expected a constant integer expression."); } } + case ParsedExpr::Kind::FloatLiteral: case ParsedExpr::Kind::Ident: default: error(*debugInfo, "Expected a constant integer expression."); @@ -1673,28 +1668,22 @@ class QASM3Parser final { } /// Evaluate an expression to a non-negative integer. - [[nodiscard]] size_t - evaluatePositiveConstant(const ParsedExpr& expr, - const std::shared_ptr& debugInfo) const { + [[nodiscard]] size_t evaluateNonNegativeConstant( + const ParsedExpr& expr, + const std::shared_ptr& debugInfo) const { return static_cast(evaluateIntegerConstant(expr, debugInfo)); } /// Evaluate an optional expression to a non-negative integer, using /// @p defaultValue when the expression is absent. [[nodiscard]] size_t - evaluatePositiveConstant(const std::optional& expr, - const std::shared_ptr& debugInfo, - size_t defaultValue) const { + evaluateNonNegativeConstant(const std::optional& expr, + const std::shared_ptr& debugInfo, + size_t defaultValue) const { if (!expr) { return defaultValue; } - return evaluatePositiveConstant(*expr, debugInfo); - } - - /// error() overload taking an already-built DebugInfo. - [[noreturn]] void error(const DebugInfo& debugInfo, - const std::string& msg) const { - throw CompilerError(msg, std::make_shared(debugInfo)); + return evaluateNonNegativeConstant(*expr, debugInfo); } }; From c0cfada098bf80c9357a798a845c885c4292083d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 5 Jul 2026 23:51:59 +0200 Subject: [PATCH 03/24] Define emitIntegerExpression --- .../Dialect/QC/Translation/QASM3Parser.cpp | 74 +++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp index 6790762957..04f6d0e473 100644 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp @@ -184,7 +184,8 @@ struct QubitBinding { using QubitScope = llvm::StringMap; /// Look up a well-known OpenQASM 3 numeric constant (`pi`, `tau`, `euler`, -/// and their Unicode aliases) by identifier and emit it as an `f64` MLIR value. +/// and their Unicode aliases) by identifier and emit it as an `f64`-typed MLIR +/// value. std::optional lookupBuiltinConstant(llvm::StringRef name, QCProgramBuilder& builder) { const auto constant = [&](double value) -> Value { @@ -839,8 +840,6 @@ class QASM3Parser final { expect(Token::Kind::In); expect(Token::Kind::LBracket); - // TODO: Related to my comment below, I'd like to directly use - // `mlir::Value`s for start, top, and step. const auto start = parseExpression(); expect(Token::Kind::Colon); const auto second = parseExpression(); @@ -857,19 +856,23 @@ class QASM3Parser final { expect(Token::Kind::RBracket); - const auto startVal = evaluateNonNegativeConstant(start, debugInfo); - const auto stepVal = evaluateNonNegativeConstant(step, debugInfo, 1); - const auto stopVal = evaluateNonNegativeConstant(stop, debugInfo); + auto startVal = emitIntegerExpression(start, debugInfo); + auto stepVal = emitIntegerExpression(step, debugInfo); + auto stopVal = emitIntegerExpression(stop, debugInfo); + + // OpenQASM 3's range is inclusive of the stop value while `scf.for`'s upper + // bound is exclusive, hence the `+ 1`. + auto one = + arith::ConstantOp::create(builder, builder.getIndexAttr(1)).getResult(); + stopVal = arith::AddIOp::create(builder, stopVal, one).getResult(); loopVariables.push(); loadedDynamicElements.push(); - builder.scfFor(static_cast(startVal), - static_cast(stopVal + 1), - static_cast(stepVal), [&](Value iv) { - loopVariables.emplace(loopVariable, iv); - parseBlockOrStatement(); - }); + builder.scfFor(startVal, stopVal, stepVal, [&](Value iv) { + loopVariables.emplace(loopVariable, iv); + parseBlockOrStatement(); + }); loadedDynamicElements.pop(); loopVariables.pop(); @@ -888,7 +891,7 @@ class QASM3Parser final { [&] { parseBlockOrStatement(); }); } - /// Translate a 3 condition to an `i1` MLIR value + /// Translate a 3 condition to an `i1`-typed MLIR value. [[nodiscard]] Value parseCondition() { const auto debugInfo = makeDebugInfo(current()); @@ -1569,7 +1572,7 @@ class QASM3Parser final { //===--- Expression evaluation ----------------------------------------===// - /// Translate a `ParsedExpr` to an `f64` MLIR value. + /// Translate a `ParsedExpr` to an `f64`-typed MLIR value. [[nodiscard]] Value emitFloatExpression(const ParsedExpr& expr, const std::shared_ptr& debugInfo) { @@ -1613,6 +1616,48 @@ class QASM3Parser final { error(*debugInfo, "Unsupported gate parameter expression."); } + /// Translate a `ParsedExpr` to an `index`-typed MLIR value. + [[nodiscard]] Value + emitIntegerExpression(const ParsedExpr& expr, + const std::shared_ptr& debugInfo) { + switch (expr.kind) { + case ParsedExpr::Kind::IntLiteral: + return arith::ConstantOp::create(builder, + builder.getIndexAttr(expr.intValue)) + .getResult(); + case ParsedExpr::Kind::Unary: { + if (expr.op == Token::Kind::Minus) { + const auto operand = emitIntegerExpression(expr.children[0], debugInfo); + const auto zero = + arith::ConstantOp::create(builder, builder.getIndexAttr(0)) + .getResult(); + return arith::SubIOp::create(builder, zero, operand).getResult(); + } + error(*debugInfo, "Unsupported unary operator in integer expression."); + } + case ParsedExpr::Kind::Binary: { + const auto lhs = emitIntegerExpression(expr.children[0], debugInfo); + const auto rhs = emitIntegerExpression(expr.children[1], debugInfo); + switch (expr.op) { + case Token::Kind::Plus: + return arith::AddIOp::create(builder, lhs, rhs).getResult(); + case Token::Kind::Minus: + return arith::SubIOp::create(builder, lhs, rhs).getResult(); + case Token::Kind::Asterisk: + return arith::MulIOp::create(builder, lhs, rhs).getResult(); + case Token::Kind::Slash: + return arith::DivSIOp::create(builder, lhs, rhs).getResult(); + default: + error(*debugInfo, "Unsupported binary operator in integer expression."); + } + } + case ParsedExpr::Kind::FloatLiteral: + case ParsedExpr::Kind::Ident: + default: + error(*debugInfo, "Expected an integer expression."); + } + } + [[nodiscard]] Value resolveParameterIdentifier(const std::string& name, const std::shared_ptr& debugInfo) { @@ -1625,7 +1670,6 @@ class QASM3Parser final { error(*debugInfo, "Unknown identifier '" + name + "'."); } - // TODO: Define emitIntExpression /// Statically evaluate an integer-constant expression. These positions /// (register sizes, loop bounds/step, control counts, literal indices) need a /// concrete `int64_t` at build time, so a genuinely dynamic value is an From 9518633768f1cada199ed5049a13e801ef124dae Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 6 Jul 2026 00:26:10 +0200 Subject: [PATCH 04/24] Split up parseDeclaration --- .../Dialect/QC/Translation/QASM3Parser.cpp | 176 ++++++++++-------- 1 file changed, 94 insertions(+), 82 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp index 04f6d0e473..e363c1bb7d 100644 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp @@ -424,20 +424,23 @@ class QASM3Parser final { parseInclude(); return; case Token::Kind::Const: - advance(); - parseDeclaration(/*isConst=*/true); + parseConstantDeclaration(); return; case Token::Kind::Qubit: case Token::Kind::Qreg: + parseQuantumDeclaration(); + return; case Token::Kind::Bit: case Token::Kind::CReg: case Token::Kind::Int: case Token::Kind::Uint: + parseClassicalDeclaration(); + return; + case Token::Kind::Bool: case Token::Kind::Float: case Token::Kind::Angle: - case Token::Kind::Bool: case Token::Kind::Duration: - parseDeclaration(/*isConst=*/false); + error(current(), "Declaration type is not supported yet."); return; case Token::Kind::Gate: parseGateDeclaration(); @@ -553,112 +556,121 @@ class QASM3Parser final { //===--- Declarations -------------------------------------------------===// - // TODO: Can this function be broken up into several ones? Maybe one for - // qubits and registers thereof, one for classical bits and registers thereof, - // and one for "numbers"? - void parseDeclaration(bool isConst) { - const auto declaration = current(); - const auto debugInfo = makeDebugInfo(declaration); - advance(); - - const bool oldStyle = declaration.kind == Token::Kind::Qreg || - declaration.kind == Token::Kind::CReg; + /// Parse a `[]` designator and evaluate it to a constant. + int64_t parseDesignator(const std::shared_ptr& debugInfo) { + expect(Token::Kind::LBracket); + const auto expr = parseExpression(); + expect(Token::Kind::RBracket); + return evaluateIntegerConstant(expr, debugInfo); + } - std::optional designator; - if (!oldStyle && current().kind == Token::Kind::LBracket) { - expect(Token::Kind::LBracket); - const auto expr = parseExpression(); - expect(Token::Kind::RBracket); - designator = evaluateIntegerConstant(expr, debugInfo); + void registerDeclaredName(const Token& keyword, const std::string& id, + bool isConst) { + if (declaredNames.contains(id)) { + error(keyword, "Identifier '" + id + "' already declared."); } + declaredNames[id] = isConst; + } - const auto id = expect(Token::Kind::Identifier).str; + /// `const = ;` + void parseConstantDeclaration() { + const auto keyword = expect(Token::Kind::Const); + const auto debugInfo = makeDebugInfo(keyword); + advance(); // type keyword if (current().kind == Token::Kind::LBracket) { - if (oldStyle) { - expect(Token::Kind::LBracket); - const auto expr = parseExpression(); - expect(Token::Kind::RBracket); - designator = evaluateIntegerConstant(expr, debugInfo); - } else { - error(current(), "In OpenQASM 3.0, the designator has been changed to " - "`type[designator] identifier;`"); - } + parseDesignator(debugInfo); } + const auto id = expect(Token::Kind::Identifier).str; std::optional initExpr; - std::optional measureInit; if (current().kind == Token::Kind::Equals) { advance(); - if (current().kind == Token::Kind::Measure) { - advance(); - measureInit = parseGateOperand(); - } else { - initExpr = parseExpression(); - } + initExpr = parseExpression(); } - expect(Token::Kind::Semicolon); - if (declaredNames.contains(id)) { - error(declaration, "Identifier '" + id + "' already declared."); + registerDeclaredName(keyword, id, /*isConst=*/true); + if (!initExpr) { + error(keyword, "Constant declaration initialization expression must be " + "initialized."); } - declaredNames[id] = isConst; + parameterConstants.emplace(id, emitFloatExpression(*initExpr, debugInfo)); + } - if (isConst) { - if (!initExpr) { - error(declaration, - "Constant declaration initialization expression must be " - "initialized."); + /// `qubit ;`, `qubit[] ;`, or `qreg [];`. + void parseQuantumDeclaration() { + const auto keyword = current(); + const auto debugInfo = makeDebugInfo(keyword); + const bool oldStyle = keyword.kind == Token::Kind::Qreg; + advance(); + + std::optional size; + if (!oldStyle && current().kind == Token::Kind::LBracket) { + size = parseDesignator(debugInfo); + } + const auto id = expect(Token::Kind::Identifier).str; + if (current().kind == Token::Kind::LBracket) { + if (!oldStyle) { + error(current(), "In OpenQASM 3.0, the designator has been changed to " + "`type[designator] identifier;`"); } - parameterConstants.emplace(id, emitFloatExpression(*initExpr, debugInfo)); - return; + size = parseDesignator(debugInfo); } + expect(Token::Kind::Semicolon); + + registerDeclaredName(keyword, id, /*isConst=*/false); - // The type must be a sized register type. - const bool sized = declaration.kind == Token::Kind::Qubit || - declaration.kind == Token::Kind::Qreg || - declaration.kind == Token::Kind::Bit || - declaration.kind == Token::Kind::CReg || - declaration.kind == Token::Kind::Int || - declaration.kind == Token::Kind::Uint; - if (!sized) { - error(declaration, "Only sized types are supported."); + if (size) { + const auto reg = builder.allocQubitRegister(*size); + qubitRegisters[id] = {reg.value, reg.qubits}; + } else { + // `qubit q;` (no register syntax): allocate a bare qubit rather than a + // size-1 register, matching how such declarations are naturally built + // directly with the QCProgramBuilder. + qubitRegisters[id] = {Value{}, {builder.allocQubit()}}; } + } - const auto size = designator.value_or(1); + /// `bit ;`, `bit[] ;`, `creg [];`, or the analogous + /// integer forms, optionally initialized from a measurement. + void parseClassicalDeclaration() { + const auto keyword = current(); + const auto debugInfo = makeDebugInfo(keyword); + const bool oldStyle = keyword.kind == Token::Kind::CReg; + advance(); - switch (declaration.kind) { - case Token::Kind::Qubit: - case Token::Kind::Qreg: { - if (size == 1 && !designator) { - // `qubit q;` (no register syntax): allocate a bare qubit rather than a - // size-1 register, matching how such declarations are naturally built - // directly with the QCProgramBuilder. - qubitRegisters[id] = {Value{}, {builder.allocQubit()}}; - } else { - const auto reg = builder.allocQubitRegister(size); - qubitRegisters[id] = {reg.value, reg.qubits}; + std::optional size; + if (!oldStyle && current().kind == Token::Kind::LBracket) { + size = parseDesignator(debugInfo); + } + const auto id = expect(Token::Kind::Identifier).str; + if (current().kind == Token::Kind::LBracket) { + if (!oldStyle) { + error(current(), "In OpenQASM 3.0, the designator has been changed to " + "`type[designator] identifier;`"); } - break; + size = parseDesignator(debugInfo); } - case Token::Kind::Bit: - case Token::Kind::CReg: - case Token::Kind::Int: - case Token::Kind::Uint: - classicalRegisters[id] = builder.allocClassicalBitRegister(size, id); - break; - default: - error(declaration, "Unsupported declaration type."); + + std::optional measureInit; + if (current().kind == Token::Kind::Equals) { + advance(); + if (current().kind != Token::Kind::Measure) { + error(keyword, "Only measure expressions can declare variables."); + } + advance(); + measureInit = parseGateOperand(); } + expect(Token::Kind::Semicolon); + + registerDeclaredName(keyword, id, /*isConst=*/false); + classicalRegisters[id] = + builder.allocClassicalBitRegister(size.value_or(1), id); if (measureInit) { emitMeasureAssignment(ParsedBitRef{id, std::nullopt}, *measureInit, debugInfo); - return; - } - if (initExpr) { - error(declaration, "Only measure expressions can declare variables."); } } From 2edd3aed363097a3517623828c4214a5accce7d8 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 6 Jul 2026 01:49:45 +0200 Subject: [PATCH 05/24] Split up emitGateCall --- .../Dialect/QC/Translation/QASM3Parser.cpp | 210 ++++++++++-------- 1 file changed, 113 insertions(+), 97 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp index e363c1bb7d..a98e141cdf 100644 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp @@ -1175,85 +1175,136 @@ class QASM3Parser final { params.push_back(emitFloatExpression(arg, debugInfo)); } - // Evaluate operands + // Resolve operands SmallVector operands; SmallVector> operandsBroadcasting; - auto broadcasting = false; for (const auto& operand : call.operands) { const auto resolved = resolveGateOperandInScope(operand, scope, debugInfo); if (const auto* value = std::get_if(&resolved)) { operands.push_back(*value); - } else if (const auto* values = - std::get_if>(&resolved)) { - operandsBroadcasting.push_back(*values); - broadcasting = true; + } else { + operandsBroadcasting.push_back(std::get>(resolved)); } } - if (broadcasting && !operands.empty()) { + if (!operandsBroadcasting.empty() && !operands.empty()) { error(*debugInfo, "Gate operands must be single qubits or quantum " "registers and not a mix of both."); } - if (broadcasting && numCompatControls != 0) { - error(*debugInfo, "OpenQASM 2 gates cannot be broadcasted."); + // Handle broadcasted calls + if (!operandsBroadcasting.empty()) { + if (numCompatControls != 0) { + error(*debugInfo, "OpenQASM 2 gates cannot be broadcasted."); + } + emitBroadcastGateCall(it->second, resolvedId, id, params, call.modifiers, + operandsBroadcasting, debugInfo); + return; } - size_t broadcastWidth = 0; - if (broadcasting) { - for (const auto& operand : operandsBroadcasting) { - if (broadcastWidth == 0) { - broadcastWidth = operand.size(); - } else if (broadcastWidth != operand.size()) { - error(*debugInfo, - "All broadcasting operands must have the same width."); - } + const auto split = splitControlsAndTargets( + call.modifiers, numCompatControls, operands, debugInfo); + + // Inline compound gate + if (const auto* compound = std::get_if(&it->second)) { + emitCompoundGate(*compound, params, split.targets, split.posControls, + split.negControls, split.invert, debugInfo); + return; + } + + // Emit standard gate + const auto& gateFn = + resolveStandardGate(it->second, resolvedId, id, params, debugInfo); + emitStandardGate(gateFn, params, split.targets, split.posControls, + split.negControls, split.invert); + } + + /// Emit a broadcasting gate call: every operand is a register of the same + /// width, and the gate is emitted once per register index. + void + emitBroadcastGateCall(const std::variant& gate, + llvm::StringRef resolvedId, const std::string& id, + ValueRange params, + const std::vector& modifiers, + const SmallVector>& operands, + const std::shared_ptr& debugInfo) { + if (std::holds_alternative(gate)) { + error(*debugInfo, "Broadcasted compound gates are not supported yet."); + } + + const auto broadcastWidth = operands.front().size(); + for (const auto& operand : operands) { + if (operand.size() != broadcastWidth) { + error(*debugInfo, + "All broadcasting operands must have the same width."); } } - auto invert = false; - size_t numControls = 0; - SmallVector posControls; - SmallVector negControls; - SmallVector> posControlsBroadcasting; - SmallVector> negControlsBroadcasting; + // OpenQASM 2 gates cannot be broadcasted + const auto split = splitControlsAndTargets>( + modifiers, /*numCompatControls=*/0, operands, debugInfo); + + const auto& gateFn = + resolveStandardGate(gate, resolvedId, id, params, debugInfo); - // Evaluate modifiers - for (const auto& mod : call.modifiers) { + for (size_t b = 0; b < broadcastWidth; ++b) { + const auto slice = [&](const SmallVector>& lists) { + SmallVector qubits; + qubits.reserve(lists.size()); + for (const auto& list : lists) { + qubits.push_back(list[b]); + } + return qubits; + }; + emitStandardGate(gateFn, params, slice(split.targets), + slice(split.posControls), slice(split.negControls), + split.invert); + } + } + + template struct ControlsAndTargets { + bool invert = false; + SmallVector posControls; + SmallVector negControls; + SmallVector targets; + }; + + /// Partition @p operands into controls and targets. + template + ControlsAndTargets + splitControlsAndTargets(const std::vector& modifiers, + size_t numCompatControls, + const SmallVector& operands, + const std::shared_ptr& debugInfo) const { + ControlsAndTargets result; + size_t numControls = 0; + for (const auto& mod : modifiers) { switch (mod.kind) { case Token::Kind::Inv: - invert = !invert; + result.invert = !result.invert; break; case Token::Kind::Pow: error(*debugInfo, "Power modifiers are not supported yet."); - case Token::Kind::Ctrl: + case Token::Kind::Ctrl: { + const auto n = + evaluateNonNegativeConstant(mod.expression, debugInfo, 1); + for (size_t i = 0; i < n; ++i, ++numControls) { + if (numControls >= operands.size()) { + error(*debugInfo, "Control index out of bounds."); + } + result.posControls.push_back(operands[numControls]); + } + break; + } case Token::Kind::NegCtrl: { const auto n = evaluateNonNegativeConstant(mod.expression, debugInfo, 1); - const auto positive = mod.kind == Token::Kind::Ctrl; for (size_t i = 0; i < n; ++i, ++numControls) { - if (!broadcasting) { - if (numControls >= operands.size()) { - error(*debugInfo, "Control index out of bounds."); - } - auto operand = operands[numControls]; - if (positive) { - posControls.push_back(operand); - } else { - negControls.push_back(operand); - } - } else { - if (numControls >= operandsBroadcasting.size()) { - error(*debugInfo, "Control index out of bounds."); - } - const auto& operand = operandsBroadcasting[numControls]; - if (positive) { - posControlsBroadcasting.push_back(operand); - } else { - negControlsBroadcasting.push_back(operand); - } + if (numControls >= operands.size()) { + error(*debugInfo, "Control index out of bounds."); } + result.negControls.push_back(operands[numControls]); } break; } @@ -1267,63 +1318,28 @@ class QASM3Parser final { if (numControls >= operands.size()) { error(*debugInfo, "Control index out of bounds."); } - 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)); + result.posControls.push_back(operands[numControls]); } - // Inline compound gate - if (const auto* compound = std::get_if(&it->second)) { - if (broadcasting) { - error(*debugInfo, "Broadcasted compound gates are not supported."); - } - emitCompoundGate(*compound, params, targets, posControls, negControls, - invert, debugInfo); - return; - } + result.targets = llvm::to_vector(llvm::drop_begin(operands, numControls)); + return result; + } - // Emit standard gate + /// Look up a standard gate's emitter, checking it exists and that the given + /// number of parameters matches its signature. + const GateFn& + resolveStandardGate(const std::variant& gate, + llvm::StringRef resolvedId, const std::string& id, + ValueRange params, + const std::shared_ptr& debugInfo) const { const auto dispIt = GATE_DISPATCH.find(resolvedId); if (dispIt == GATE_DISPATCH.end()) { error(*debugInfo, "No MLIR definition found for gate '" + id + "'."); } - - if (std::get(it->second).nParameters != params.size()) { + if (std::get(gate).nParameters != params.size()) { error(*debugInfo, "Invalid number of parameters for gate '" + id + "'."); } - - if (!broadcasting) { - emitStandardGate(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]); - } - emitStandardGate(dispIt->second, params, bTargets, bPosControls, - bNegControls, invert); - } - } + return dispIt->second; } /// Emit a gate body wrapped in its modifiers. From 7c0dc25f9cfaaf61daa304758aa5075d6be62974 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 6 Jul 2026 02:30:08 +0200 Subject: [PATCH 06/24] Daniel's second cleanup --- .../QC/Translation/TranslateQASM3ToQC.h | 6 +- .../Dialect/QC/Translation/QASM3Parser.cpp | 522 ++++++++++-------- mlir/lib/Dialect/QC/Translation/QASM3Parser.h | 23 +- .../QC/Translation/test_qasm3_translation.cpp | 105 ++-- mlir/unittests/programs/qasm_programs.cpp | 57 +- mlir/unittests/programs/qasm_programs.h | 23 +- 6 files changed, 414 insertions(+), 322 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h b/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h index 165136a500..fb2a6d0811 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h +++ b/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h @@ -26,7 +26,8 @@ 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 context The MLIRContext to create the module in. + * @return A module containing the QC program. */ [[nodiscard]] OwningOpRef translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context); @@ -35,7 +36,8 @@ 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 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/lib/Dialect/QC/Translation/QASM3Parser.cpp b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp index a98e141cdf..7e51f5810b 100644 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -58,12 +59,10 @@ using qasm3::GateInfo; using qasm3::Token; /// Signature: (builder, gate operands, gate 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; -/// Build the table mapping each OpenQASM 3 gate identifier to a lambda that -/// emits the corresponding QC operation via the `QCProgramBuilder`. +/// Build the table mapping each gate identifier to a lambda that emits the +/// corresponding QC operation via the `QCProgramBuilder`. llvm::StringMap buildGateDispatch() { llvm::StringMap d; @@ -166,15 +165,17 @@ llvm::StringMap buildGateDispatch() { return d; } -/// Map from OpenQASM 3 gate identifier to QCProgramBuilder emitter. +/// Map from gate identifier to `QCProgramBuilder` emitter. const llvm::StringMap GATE_DISPATCH = buildGateDispatch(); -/// A named qubit binding in scope. For a top-level register, `memref` is the -/// backing memref value (used for dynamic indexing, e.g. by a for-loop -/// variable) and `qubits` holds the eagerly extracted per-index qubit values -/// (used for literal indexing). For a compound-gate-local alias, `memref` is -/// null: such targets are accessed by bare name only, never dynamically -/// indexed. +/** + * @brief Represents a named qubit binding in scope. + * + * @details + * For top-level registers, `memref` holds the backing register and `qubits` + * holds eagerly extracted values. For compound gates, `memref` is null and + * `qubits` holds the alaiased values. + */ struct QubitBinding { Value memref; SmallVector qubits; @@ -183,12 +184,11 @@ struct QubitBinding { /// Map of qubits in the current scope. using QubitScope = llvm::StringMap; -/// Look up a well-known OpenQASM 3 numeric constant (`pi`, `tau`, `euler`, -/// and their Unicode aliases) by identifier and emit it as an `f64`-typed MLIR +/// Look up a built-in numeric constant and emit it as an `f64`-typed MLIR /// value. std::optional lookupBuiltinConstant(llvm::StringRef name, QCProgramBuilder& builder) { - const auto constant = [&](double value) -> Value { + auto constant = [&](double value) -> Value { return arith::ConstantOp::create(builder, builder.getF64FloatAttr(value)) .getResult(); }; @@ -208,17 +208,15 @@ std::optional lookupBuiltinConstant(llvm::StringRef name, // Parsed representations //===----------------------------------------------------------------------===// -/// A small closed expression tree used both for gate-parameter (float) and -/// structural (integer) positions, and stored verbatim inside compound-gate -/// bodies so a gate body can be parsed once and replayed at every call site. +/// A parsed expression. struct ParsedExpr { enum class Kind : uint8_t { IntLiteral, FloatLiteral, Ident, Unary, Binary }; Kind kind{Kind::IntLiteral}; // Literal - double floatValue{}; int64_t intValue{}; + double floatValue{}; // Ident std::string name; @@ -226,31 +224,23 @@ struct ParsedExpr { // Unary or Binary Token::Kind op{}; std::vector children; - - static ParsedExpr makeInt(int64_t value) { - ParsedExpr e; - e.kind = Kind::IntLiteral; - e.intValue = value; - return e; - } }; -/// A gate modifier. +/// A parsed gate modifier. struct ParsedModifier { Token::Kind kind{Token::Kind::Inv}; std::optional expression; }; -/// A gate operand: either a (possibly indexed) named qubit, or a hardware -/// qubit. +/// A parsed gate operand. Either a (possibly indexed) named qubit, or a +/// hardware qubit. struct ParsedOperand { std::string name; std::optional index; std::optional hardwareQubit; }; -/// A parsed gate-call statement (front half only): its modifiers, parameter -/// expressions and operands, ready to be resolved and emitted. +/// A parsed gate call. struct ParsedGateCall { std::string identifier; std::vector modifiers; @@ -259,28 +249,23 @@ struct ParsedGateCall { std::shared_ptr debugInfo; }; -/// A compound-gate definition: a body of gate calls parsed once at declaration -/// time and replayed at each call site with fresh parameter/target bindings. +/// A parsed compound gate. struct ParsedCompoundGate { std::vector parameterNames; std::vector targetNames; std::vector body; }; -/// A (possibly indexed) classical-bit reference. +/// A parsed classical bit reference. struct ParsedBitRef { std::string name; std::optional index; }; -/// Build the gate table mapping every natively supported OpenQASM 3 gate -/// identifier to its `GateInfo`. Compound gates are added to this same table -/// as the program declares them. +/// Build the table mapping a gate identifier to its metadata. llvm::StringMap> buildGateTable() { llvm::StringMap> t; for (const auto& [name, gate] : qasm3::STANDARD_GATES) { - // Every entry in STANDARD_GATES is a StandardGate (CompoundGate only ever - // arises from user `gate` declarations), so this cast always succeeds. const auto* standard = dynamic_cast(gate.get()); assert(standard != nullptr && "STANDARD_GATES entry is not a StandardGate"); t.insert({name, standard->info}); @@ -303,12 +288,14 @@ llvm::StringMap> buildGateTable() { // QASM3Parser //===----------------------------------------------------------------------===// -/// Single-pass OpenQASM 3 to QC dialect translator. Consumes `qasm3::Scanner` -/// tokens directly and emits QC operations via the `QCProgramBuilder` as it -/// parses. Each `parseX` handler parses its own tokens and drives the builder -/// at the point the corresponding statement takes effect; targeted validation -/// helpers raise `CompilerError` with a specific message where the check is -/// meaningful. +/** + * @brief OpenQASM 3 parser that builds a QC program. + * + * @details + * Consumes `qasm3::Scanner` tokens directly and emits QC operations via the + * `QCProgramBuilder` as it parses. Each `parse` function parses all tokens of a + * given statement. Validation helpers raise `CompilerError`s wherever needed. + */ class QASM3Parser final { public: explicit QASM3Parser(MLIRContext* ctx) @@ -337,38 +324,41 @@ class QASM3Parser final { Token currentToken{0, 0}; Token nextToken{0, 0}; - /// Names already declared at file scope (qubit/bit registers and constants). - llvm::StringMap declaredNames; ///< name -> isConst + /// Set of declared identifiers. + llvm::StringSet<> declaredNames; - /// Map from a `const`-declared or compound-gate-parameter identifier to the - /// MLIR value it is bound to. + /// Map from a parameter constant to its `f64`-typed MLIR value. qasm3::NestedEnvironment parameterConstants; - /// Map from a for-loop induction-variable identifier to its runtime value. + /// Map from an induction variable to its MLIR value. qasm3::NestedEnvironment loopVariables; - /// Map from a qubit-register name to the qubit most recently loaded from it - /// via a dynamic (loop-variable) index, within the current for-loop's scope. - qasm3::NestedEnvironment loadedDynamicElements; + /// Cache of dynamically loaded qubits. + qasm3::NestedEnvironment dynamicallyLoadedQubits; - /// Map from qubit-register name to allocated qubit values. + /// Map from qubit-register name to `QubitScope`. QubitScope qubitRegisters; - /// Map from classical-register name to ClassicalRegister. + /// 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 its definition (native or compound). + /// Map from gate identifier to its metadata. llvm::StringMap> gates; bool openQASM2CompatMode{false}; //===--- Token scaffolding --------------------------------------------===// - /// Advance the token cursor by one: `current()` moves to what `peek()` - /// returned, and a fresh token is pulled from the scanner into the lookahead. + /** + * @brief Advance the token cursor by one. + * + * @details + * `current()` moves to what `peek()` returned, and a fresh token is pulled + * from the scanner into the lookahead. + */ void advance() { currentToken = nextToken; nextToken = scanner->next(); @@ -377,7 +367,7 @@ class QASM3Parser final { /// The token the parser is currently positioned on. [[nodiscard]] const Token& current() const { return currentToken; } - /// The next token, without consuming it (one-token lookahead). + /// The next token, without consuming it. [[nodiscard]] const Token& peek() const { return nextToken; } /// Whether the entire input has been consumed. @@ -385,18 +375,23 @@ class QASM3Parser final { return currentToken.kind == Token::Kind::Eof; } - [[nodiscard]] std::shared_ptr makeDebugInfo(const Token& t) const { - return std::make_shared(t.line, t.col, ""); + /// Create a `DebugInfo` object from @p token. + [[nodiscard]] std::shared_ptr + makeDebugInfo(const Token& token) const { + return std::make_shared(token.line, token.col, ""); } - void error(const Token& t, const std::string& msg) const { - throw CompilerError(msg, makeDebugInfo(t)); + /// Throw a `CompilerError` at the position of @p token. + void error(const Token& token, const std::string& msg) const { + throw CompilerError(msg, makeDebugInfo(token)); } + /// Throw a `CompilerError` using an existing @p debugInfo. void error(const DebugInfo& debugInfo, const std::string& msg) const { throw CompilerError(msg, std::make_shared(debugInfo)); } + /// Assert that the current token matches an expected kind, then advance. Token expect(const Token::Kind expected) { auto token = current(); if (token.kind != expected) { @@ -407,7 +402,7 @@ class QASM3Parser final { return token; } - //===--- Program & statement dispatch ---------------------------------===// + //===--- Program and statement dispatch -------------------------------===// void parseProgram() { while (!isAtEnd()) { @@ -427,15 +422,19 @@ class QASM3Parser final { parseConstantDeclaration(); return; case Token::Kind::Qubit: + parseQubitDeclaration(); + return; case Token::Kind::Qreg: - parseQuantumDeclaration(); + parseQregDeclaration(); return; case Token::Kind::Bit: + parseBitDeclaration(); + return; case Token::Kind::CReg: + parseCregDeclaration(); + return; case Token::Kind::Int: case Token::Kind::Uint: - parseClassicalDeclaration(); - return; case Token::Kind::Bool: case Token::Kind::Float: case Token::Kind::Angle: @@ -505,7 +504,7 @@ class QASM3Parser final { } } - /// Parse and emit either a `{ ... }` block or a single statement. + /// Parse a `{ ... }` block or a single statement. void parseBlockOrStatement() { if (current().kind == Token::Kind::LBrace) { advance(); @@ -545,8 +544,6 @@ class QASM3Parser final { const auto beginToken = expect(Token::Kind::Include); const auto filename = expect(Token::Kind::StringLiteral).str; expect(Token::Kind::Semicolon); - // `stdgates.inc` and `qelib1.inc` are fully covered by the native gate - // table, so they are no-ops. Anything else is a real mistake. if (filename != "stdgates.inc" && filename != "qelib1.inc") { error(beginToken, "Unsupported include '" + filename + "'. Only 'stdgates.inc' and 'qelib1.inc' are " @@ -556,127 +553,128 @@ class QASM3Parser final { //===--- Declarations -------------------------------------------------===// - /// Parse a `[]` designator and evaluate it to a constant. - int64_t parseDesignator(const std::shared_ptr& debugInfo) { - expect(Token::Kind::LBracket); - const auto expr = parseExpression(); - expect(Token::Kind::RBracket); - return evaluateIntegerConstant(expr, debugInfo); - } - - void registerDeclaredName(const Token& keyword, const std::string& id, - bool isConst) { - if (declaredNames.contains(id)) { - error(keyword, "Identifier '" + id + "' already declared."); - } - declaredNames[id] = isConst; - } - - /// `const = ;` + /// Parse `const float = ;`. void parseConstantDeclaration() { - const auto keyword = expect(Token::Kind::Const); - const auto debugInfo = makeDebugInfo(keyword); - advance(); // type keyword + const auto constToken = expect(Token::Kind::Const); + const auto debugInfo = makeDebugInfo(constToken); - if (current().kind == Token::Kind::LBracket) { - parseDesignator(debugInfo); + if (current().kind != Token::Kind::Float || + peek().kind != Token::Kind::Identifier) { + error(*debugInfo, "Only `const float = ;` declarations " + "are supported for now."); } + expect(Token::Kind::Float); const auto id = expect(Token::Kind::Identifier).str; - std::optional initExpr; - if (current().kind == Token::Kind::Equals) { - advance(); - initExpr = parseExpression(); - } + expect(Token::Kind::Equals); + auto initExpr = parseExpression(); expect(Token::Kind::Semicolon); - registerDeclaredName(keyword, id, /*isConst=*/true); - if (!initExpr) { - error(keyword, "Constant declaration initialization expression must be " - "initialized."); - } - parameterConstants.emplace(id, emitFloatExpression(*initExpr, debugInfo)); + registerDeclaredName(constToken, id); + parameterConstants.emplace(id, emitFloatExpression(initExpr, debugInfo)); } - /// `qubit ;`, `qubit[] ;`, or `qreg [];`. - void parseQuantumDeclaration() { - const auto keyword = current(); - const auto debugInfo = makeDebugInfo(keyword); - const bool oldStyle = keyword.kind == Token::Kind::Qreg; - advance(); - + /// Parse `qubit[] ;`. + void parseQubitDeclaration() { + const auto qubit = expect(Token::Kind::Qubit); + const auto debugInfo = makeDebugInfo(qubit); std::optional size; - if (!oldStyle && current().kind == Token::Kind::LBracket) { + if (current().kind == Token::Kind::LBracket) { size = parseDesignator(debugInfo); } const auto id = expect(Token::Kind::Identifier).str; + expect(Token::Kind::Semicolon); + registerDeclaredName(qubit, id); + if (size) { + const auto reg = builder.allocQubitRegister(*size); + qubitRegisters[id] = {reg.value, reg.qubits}; + } else { + qubitRegisters[id] = {nullptr, {builder.allocQubit()}}; + } + } + + /// Parse `qreg [];`. + void parseQregDeclaration() { + const auto qreg = expect(Token::Kind::Qreg); + const auto debugInfo = makeDebugInfo(qreg); + const auto id = expect(Token::Kind::Identifier).str; + std::optional size; if (current().kind == Token::Kind::LBracket) { - if (!oldStyle) { - error(current(), "In OpenQASM 3.0, the designator has been changed to " - "`type[designator] identifier;`"); - } size = parseDesignator(debugInfo); } expect(Token::Kind::Semicolon); - - registerDeclaredName(keyword, id, /*isConst=*/false); - + registerDeclaredName(qreg, id); if (size) { const auto reg = builder.allocQubitRegister(*size); qubitRegisters[id] = {reg.value, reg.qubits}; } else { - // `qubit q;` (no register syntax): allocate a bare qubit rather than a - // size-1 register, matching how such declarations are naturally built - // directly with the QCProgramBuilder. - qubitRegisters[id] = {Value{}, {builder.allocQubit()}}; + qubitRegisters[id] = {nullptr, {builder.allocQubit()}}; } } - /// `bit ;`, `bit[] ;`, `creg [];`, or the analogous - /// integer forms, optionally initialized from a measurement. - void parseClassicalDeclaration() { - const auto keyword = current(); - const auto debugInfo = makeDebugInfo(keyword); - const bool oldStyle = keyword.kind == Token::Kind::CReg; - advance(); + /// Parse `bit[] (= );`. + void parseBitDeclaration() { + const auto bit = expect(Token::Kind::Bit); + const auto debugInfo = makeDebugInfo(bit); std::optional size; - if (!oldStyle && current().kind == Token::Kind::LBracket) { - size = parseDesignator(debugInfo); - } - const auto id = expect(Token::Kind::Identifier).str; if (current().kind == Token::Kind::LBracket) { - if (!oldStyle) { - error(current(), "In OpenQASM 3.0, the designator has been changed to " - "`type[designator] identifier;`"); - } size = parseDesignator(debugInfo); } - std::optional measureInit; + const auto id = expect(Token::Kind::Identifier).str; + + std::optional operand; if (current().kind == Token::Kind::Equals) { advance(); - if (current().kind != Token::Kind::Measure) { - error(keyword, "Only measure expressions can declare variables."); - } - advance(); - measureInit = parseGateOperand(); + expect(Token::Kind::Measure); + operand = parseGateOperand(); } + expect(Token::Kind::Semicolon); - registerDeclaredName(keyword, id, /*isConst=*/false); + registerDeclaredName(bit, id); classicalRegisters[id] = builder.allocClassicalBitRegister(size.value_or(1), id); - if (measureInit) { - emitMeasureAssignment(ParsedBitRef{id, std::nullopt}, *measureInit, + if (operand) { + emitMeasureAssignment(ParsedBitRef{id, std::nullopt}, *operand, debugInfo); } } + /// Parse `creg [];`. + void parseCregDeclaration() { + const auto creg = expect(Token::Kind::CReg); + const auto debugInfo = makeDebugInfo(creg); + const auto id = expect(Token::Kind::Identifier).str; + std::optional size; + if (current().kind == Token::Kind::LBracket) { + size = parseDesignator(debugInfo); + } + expect(Token::Kind::Semicolon); + registerDeclaredName(creg, id); + classicalRegisters[id] = + builder.allocClassicalBitRegister(size.value_or(1), id); + } + + /// Parse a `[]` designator. + int64_t parseDesignator(const std::shared_ptr& debugInfo) { + expect(Token::Kind::LBracket); + const auto expr = parseExpression(); + expect(Token::Kind::RBracket); + return evaluateIntegerConstant(expr, debugInfo); + } + + void registerDeclaredName(const Token& token, const std::string& id) { + if (!declaredNames.insert(id).second) { + error(token, "Identifier '" + id + "' already declared."); + } + } + //===--- Assignments --------------------------------------------------===// - /// Parse a `c = measure q` statement. + /// Parse `c = measure q;`. void parseAssignment() { const auto debugInfo = makeDebugInfo(current()); const auto target = parseBitRef(); @@ -699,7 +697,7 @@ class QASM3Parser final { //===--- Measurement --------------------------------------------------===// - /// Parse a `measure q -> c;` statement. + /// Parse `measure q -> c;`. void parseMeasure() { const auto measure = expect(Token::Kind::Measure); const auto debugInfo = makeDebugInfo(measure); @@ -839,8 +837,6 @@ class QASM3Parser final { const auto forToken = expect(Token::Kind::For); const auto debugInfo = makeDebugInfo(forToken); - // The loop-variable type is not tracked further: the loop variable is - // always treated as an unsigned integer index. if (current().kind != Token::Kind::Int && current().kind != Token::Kind::Uint) { error(current(), "Expected 'int' or 'uint' after 'for'."); @@ -856,7 +852,7 @@ class QASM3Parser final { expect(Token::Kind::Colon); const auto second = parseExpression(); - ParsedExpr step = ParsedExpr::makeInt(1); + ParsedExpr step = {.kind = ParsedExpr::Kind::IntLiteral, .intValue = 1}; ParsedExpr stop; if (current().kind == Token::Kind::Colon) { advance(); @@ -872,21 +868,20 @@ class QASM3Parser final { auto stepVal = emitIntegerExpression(step, debugInfo); auto stopVal = emitIntegerExpression(stop, debugInfo); - // OpenQASM 3's range is inclusive of the stop value while `scf.for`'s upper - // bound is exclusive, hence the `+ 1`. + // OpenQASM 3's range is inclusive of the stop value auto one = arith::ConstantOp::create(builder, builder.getIndexAttr(1)).getResult(); stopVal = arith::AddIOp::create(builder, stopVal, one).getResult(); loopVariables.push(); - loadedDynamicElements.push(); + dynamicallyLoadedQubits.push(); builder.scfFor(startVal, stopVal, stepVal, [&](Value iv) { loopVariables.emplace(loopVariable, iv); parseBlockOrStatement(); }); - loadedDynamicElements.pop(); + dynamicallyLoadedQubits.pop(); loopVariables.pop(); } @@ -896,19 +891,24 @@ class QASM3Parser final { builder.scfWhile( [&] { + dynamicallyLoadedQubits.push(); const auto condition = parseCondition(); expect(Token::Kind::RParen); builder.scfCondition(condition); + dynamicallyLoadedQubits.pop(); }, - [&] { parseBlockOrStatement(); }); + [&] { + dynamicallyLoadedQubits.push(); + parseBlockOrStatement(); + dynamicallyLoadedQubits.pop(); + }); } - /// Translate a 3 condition to an `i1`-typed MLIR value. + /// Translate a condition to an `i1`-typed MLIR value. [[nodiscard]] Value parseCondition() { const auto debugInfo = makeDebugInfo(current()); - // Fresh measurement used directly as the condition (e.g. `if (measure q)` - // or `while (measure q)`), evaluated anew every time it is reached. + // Measurement (e.g., measure q) if (current().kind == Token::Kind::Measure) { advance(); const auto operand = parseGateOperand(); @@ -919,7 +919,7 @@ class QASM3Parser final { return builder.measure(std::get(resolved)); } - // Unary negation (!c[0] or ~c[0]). + // Unary negation (!c[0] or ~c[0]) if (current().kind == Token::Kind::ExclamationPoint || current().kind == Token::Kind::Tilde) { advance(); @@ -932,7 +932,7 @@ class QASM3Parser final { return arith::XOrIOp::create(builder, value, trueValue).getResult(); } - // Single bit (c or c[0]), or an unsupported register comparison. + // Single bit (c or c[0]), or an unsupported register comparison if (current().kind == Token::Kind::Identifier) { const auto bit = parseBitRef(); switch (current().kind) { @@ -980,11 +980,14 @@ class QASM3Parser final { //===--- Gate declarations --------------------------------------------===// void parseGateDeclaration() { - const auto gate = current(); - advance(); + const auto gate = expect(Token::Kind::Gate); const auto id = expect(Token::Kind::Identifier).str; + if (gates.contains(id)) { + error(gate, "Gate '" + id + "' already declared."); + } + // Parse parameters std::vector parameters; if (current().kind == Token::Kind::LParen) { advance(); @@ -992,8 +995,10 @@ class QASM3Parser final { expect(Token::Kind::RParen); } + // Parse targets const auto targets = parseIdentifierList(); + // Parse body expect(Token::Kind::LBrace); std::vector body; while (current().kind != Token::Kind::RBrace) { @@ -1001,10 +1006,6 @@ class QASM3Parser final { } expect(Token::Kind::RBrace); - if (gates.contains(id)) { - error(gate, "Gate '" + id + "' already declared."); - } - // Verify parameters for (size_t i = 0; i < parameters.size(); ++i) { for (size_t j = i + 1; j < parameters.size(); ++j) { @@ -1052,6 +1053,7 @@ class QASM3Parser final { expect(Token::Kind::At); } + // Parse identifier if (current().kind == Token::Kind::Gphase) { advance(); call.identifier = "gphase"; @@ -1072,8 +1074,7 @@ class QASM3Parser final { } if (current().kind == Token::Kind::LBracket) { - error(current(), - "`gateCallStatement`s with designators are not supported yet"); + error(current(), "Gate calls with designators are not supported yet."); } // Parse operands @@ -1142,12 +1143,12 @@ class QASM3Parser final { return ref; } - /// Resolve and emit a parsed gate call against `scope`: expand operands, - /// interpret modifiers, handle broadcasting, then either inline a compound - /// gate or emit a standard gate. + /// Resolve and emit a @p gate against @p scope. void emitGateCall(const ParsedGateCall& call, const QubitScope& scope) { const auto& id = call.identifier; const auto& debugInfo = call.debugInfo; + + // Resolve identifier auto it = gates.find(id); // OpenQASM 2 compatibility: strip leading `c` characters and treat them as @@ -1168,7 +1169,7 @@ class QASM3Parser final { error(*debugInfo, "No OpenQASM definition found for gate '" + id + "'."); } - // Evaluate parameters + // Resolve parameters SmallVector params; params.reserve(call.parameters.size()); for (const auto& arg : call.parameters) { @@ -1198,11 +1199,16 @@ class QASM3Parser final { if (numCompatControls != 0) { error(*debugInfo, "OpenQASM 2 gates cannot be broadcasted."); } - emitBroadcastGateCall(it->second, resolvedId, id, params, call.modifiers, - operandsBroadcasting, debugInfo); + if (std::holds_alternative(it->second)) { + error(*debugInfo, "Broadcasted compound gates are not supported yet."); + } + emitBroadcastedGateCall(std::get(it->second), id, resolvedId, + params, call.modifiers, operandsBroadcasting, + debugInfo); return; } + // Handle non-broadcasted calls const auto split = splitControlsAndTargets( call.modifiers, numCompatControls, operands, debugInfo); @@ -1214,25 +1220,19 @@ class QASM3Parser final { } // Emit standard gate + const auto& gate = std::get(it->second); const auto& gateFn = - resolveStandardGate(it->second, resolvedId, id, params, debugInfo); + resolveStandardGate(gate, id, resolvedId, params, debugInfo); emitStandardGate(gateFn, params, split.targets, split.posControls, split.negControls, split.invert); } - /// Emit a broadcasting gate call: every operand is a register of the same - /// width, and the gate is emitted once per register index. - void - emitBroadcastGateCall(const std::variant& gate, - llvm::StringRef resolvedId, const std::string& id, - ValueRange params, - const std::vector& modifiers, - const SmallVector>& operands, - const std::shared_ptr& debugInfo) { - if (std::holds_alternative(gate)) { - error(*debugInfo, "Broadcasted compound gates are not supported yet."); - } - + /// Emit a broadcasted gate call. + void emitBroadcastedGateCall(const GateInfo& gate, const std::string& fullId, + llvm::StringRef resolvedId, ValueRange params, + const std::vector& modifiers, + const SmallVector>& operands, + const std::shared_ptr& debugInfo) { const auto broadcastWidth = operands.front().size(); for (const auto& operand : operands) { if (operand.size() != broadcastWidth) { @@ -1246,7 +1246,7 @@ class QASM3Parser final { modifiers, /*numCompatControls=*/0, operands, debugInfo); const auto& gateFn = - resolveStandardGate(gate, resolvedId, id, params, debugInfo); + resolveStandardGate(gate, fullId, resolvedId, params, debugInfo); for (size_t b = 0; b < broadcastWidth; ++b) { const auto slice = [&](const SmallVector>& lists) { @@ -1325,19 +1325,18 @@ class QASM3Parser final { return result; } - /// Look up a standard gate's emitter, checking it exists and that the given - /// number of parameters matches its signature. + /// Look up the `QCProgramBuilder` emitter of a @p gate. const GateFn& - resolveStandardGate(const std::variant& gate, - llvm::StringRef resolvedId, const std::string& id, - ValueRange params, + resolveStandardGate(const GateInfo& gate, const std::string& fullId, + llvm::StringRef resolvedId, ValueRange params, const std::shared_ptr& debugInfo) const { const auto dispIt = GATE_DISPATCH.find(resolvedId); if (dispIt == GATE_DISPATCH.end()) { - error(*debugInfo, "No MLIR definition found for gate '" + id + "'."); + error(*debugInfo, "No MLIR definition found for gate '" + fullId + "'."); } - if (std::get(gate).nParameters != params.size()) { - error(*debugInfo, "Invalid number of parameters for gate '" + id + "'."); + if (gate.nParameters != params.size()) { + error(*debugInfo, + "Invalid number of parameters for gate '" + fullId + "'."); } return dispIt->second; } @@ -1418,7 +1417,7 @@ class QASM3Parser final { for (auto index : indices) { args.push_back(qubits[index]); } - localScope[name] = {Value{}, std::move(args)}; + localScope[name] = {nullptr, std::move(args)}; } for (const auto& bodyCall : gate.body) { emitGateCall(bodyCall, localScope); @@ -1458,34 +1457,84 @@ class QASM3Parser final { if (binding.qubits.size() == 1) { return binding.qubits[0]; } - // Return full register. + // Return full register return binding.qubits; } - // Dynamic index via a bound for-loop variable (e.g. `q[i]`). const auto& indexExpr = *operand.index; - if (indexExpr.kind == ParsedExpr::Kind::Ident) { - if (const auto iv = loopVariables.find(indexExpr.name)) { - if (!binding.memref) { - error(*debugInfo, - "Dynamic qubit indexing requires a qubit register."); - } - if (const auto cached = loadedDynamicElements.find(name)) { - return *cached; - } - const auto loaded = builder.memrefLoad(binding.memref, *iv); - loadedDynamicElements.emplace(name, loaded); - return loaded; + if (isConstantIndex(indexExpr)) { + const auto index = evaluateNonNegativeConstant(indexExpr, debugInfo); + if (index >= binding.qubits.size()) { + error(*debugInfo, "Qubit index out of bounds."); } + return binding.qubits[index]; } - const auto index = evaluateNonNegativeConstant(indexExpr, debugInfo); - if (index >= binding.qubits.size()) { - error(*debugInfo, "Qubit index out of bounds."); + if (!binding.memref) { + error(*debugInfo, "Dynamic qubit indexing requires a qubit register."); + } + return loadDynamicElement(name, binding.memref, indexExpr, debugInfo); + } + + /// Whether @p expr can be folded to a constant at translation time. + static bool isConstantIndex(const ParsedExpr& expr) { + switch (expr.kind) { + case ParsedExpr::Kind::IntLiteral: + case ParsedExpr::Kind::FloatLiteral: + return true; + case ParsedExpr::Kind::Ident: + return false; + case ParsedExpr::Kind::Unary: + return isConstantIndex(expr.children[0]); + case ParsedExpr::Kind::Binary: + return isConstantIndex(expr.children[0]) && + isConstantIndex(expr.children[1]); } - return binding.qubits[index]; + return false; } + /// Get a stable string representation of an index expression. + static std::string getIndexKey(const ParsedExpr& expr) { + switch (expr.kind) { + case ParsedExpr::Kind::IntLiteral: + return std::to_string(expr.intValue); + case ParsedExpr::Kind::FloatLiteral: + return std::to_string(expr.floatValue); + case ParsedExpr::Kind::Ident: + return expr.name; + case ParsedExpr::Kind::Unary: + return "(" + Token::kindToString(expr.op) + + getIndexKey(expr.children[0]) + ")"; + case ParsedExpr::Kind::Binary: + return "(" + getIndexKey(expr.children[0]) + + Token::kindToString(expr.op) + getIndexKey(expr.children[1]) + ")"; + } + return ""; + } + + /** + * @brief Load a qubit from @p memref at a runtime index + * + * @details + * The result within the current region is cached so that repeated references + * to the same element reuse a single load. + */ + [[nodiscard]] Value + loadDynamicElement(const std::string& name, Value memref, + const ParsedExpr& indexExpr, + const std::shared_ptr& debugInfo) { + const auto key = name + "[" + getIndexKey(indexExpr) + "]"; + if (const auto cached = dynamicallyLoadedQubits.find(key)) { + return *cached; + } + const auto index = emitIntegerExpression(indexExpr, debugInfo); + const auto loaded = builder.memrefLoad(memref, index); + dynamicallyLoadedQubits.emplace(key, loaded); + return loaded; + } + + //===--- Bit resolution -----------------------------------------------===// + [[nodiscard]] SmallVector resolveClassicalBits(const ParsedBitRef& operand, const std::shared_ptr& debugInfo) const { @@ -1644,6 +1693,18 @@ class QASM3Parser final { error(*debugInfo, "Unsupported gate parameter expression."); } + [[nodiscard]] Value + resolveParameterIdentifier(const std::string& name, + const std::shared_ptr& debugInfo) { + if (const auto value = parameterConstants.find(name)) { + return *value; + } + if (const auto value = lookupBuiltinConstant(name, builder)) { + return *value; + } + error(*debugInfo, "Unknown identifier '" + name + "'."); + } + /// Translate a `ParsedExpr` to an `index`-typed MLIR value. [[nodiscard]] Value emitIntegerExpression(const ParsedExpr& expr, @@ -1679,29 +1740,18 @@ class QASM3Parser final { error(*debugInfo, "Unsupported binary operator in integer expression."); } } - case ParsedExpr::Kind::FloatLiteral: case ParsedExpr::Kind::Ident: + if (const auto iv = loopVariables.find(expr.name)) { + return *iv; + } + error(*debugInfo, "Expected an integer expression."); + case ParsedExpr::Kind::FloatLiteral: default: error(*debugInfo, "Expected an integer expression."); } } - [[nodiscard]] Value - resolveParameterIdentifier(const std::string& name, - const std::shared_ptr& debugInfo) { - if (const auto value = parameterConstants.find(name)) { - return *value; - } - if (const auto value = lookupBuiltinConstant(name, builder)) { - return *value; - } - error(*debugInfo, "Unknown identifier '" + name + "'."); - } - - /// Statically evaluate an integer-constant expression. These positions - /// (register sizes, loop bounds/step, control counts, literal indices) need a - /// concrete `int64_t` at build time, so a genuinely dynamic value is an - /// error. + /// Statically evaluate `ParsedExpr` to an `int64_t`. [[nodiscard]] int64_t evaluateIntegerConstant(const ParsedExpr& expr, const std::shared_ptr& debugInfo) const { @@ -1739,15 +1789,15 @@ class QASM3Parser final { } } - /// Evaluate an expression to a non-negative integer. + /// Statically evaluate `ParsedExpr` to an `size_t`. [[nodiscard]] size_t evaluateNonNegativeConstant( const ParsedExpr& expr, const std::shared_ptr& debugInfo) const { return static_cast(evaluateIntegerConstant(expr, debugInfo)); } - /// Evaluate an optional expression to a non-negative integer, using - /// @p defaultValue when the expression is absent. + /// Statically evaluate `ParsedExpr` to an `size_t`, using @p defaultValue + /// when the expression is absent. [[nodiscard]] size_t evaluateNonNegativeConstant(const std::optional& expr, const std::shared_ptr& debugInfo, diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.h b/mlir/lib/Dialect/QC/Translation/QASM3Parser.h index 7a79a9f6bd..9c3a8a9e50 100644 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.h +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.h @@ -23,28 +23,15 @@ class ModuleOp; namespace qc::detail { /** - * @brief Parse an OpenQASM 3 program and emit the equivalent QC dialect module. + * @brief Parse an OpenQASM 3 program and emit a QC program. * * @details - * Implementation detail of `translateQASM3ToQC`; not part of the public API - * (this header lives with the library sources and is not installed). Prefer the - * `translateQASM3ToQC` entry points declared in - * `mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h`, which wrap this in the - * diagnostic-reporting `try`/`catch`. + * Implementation detail of `translateQASM3ToQC`; not part of the public API. + * Prefer the `translateQASM3ToQC` entry points. * - * This is a single-pass, hand-written recursive-descent parser that consumes - * `qasm3::Scanner` tokens directly and emits QC operations via the - * `QCProgramBuilder` as it parses, without building an intermediate AST. It - * depends on the legacy `qasm3` package only for its `Scanner`/`Token` lexer, - * the `GateInfo`/`NestedEnvironment` helpers, and `DebugInfo`/`CompilerError` - * for diagnostics. - * - * On a malformed program it throws a `qasm3::CompilerError` (or another - * `std::exception`); callers are expected to translate that into a diagnostic. - * - * @param source The OpenQASM 3 program text. + * @param source String containing the OpenQASM3 program. * @param context The MLIRContext to create the module in. - * @return The constructed QC dialect module. + * @return A module containing the QC program */ [[nodiscard]] OwningOpRef parseQASM3(std::string_view source, MLIRContext* context); diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index 04fc37a2f7..67f39aec63 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -113,6 +113,39 @@ 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); + }); +} + +// The qubit is loaded once per `scf.while` region (in both the before and after +// region), because a value loaded in the before region would not dominate the +// after region. +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); + }); + }); +} + TEST_P(QASM3TranslationTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; const auto& source = GetParam().source; @@ -273,6 +306,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", @@ -417,12 +452,19 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(ifNot)}, QASM3TranslationTestCase{"IfElse", qasm::ifElse, MQT_NAMED_BUILDER(qc::ifElse)}, - QASM3TranslationTestCase{"NestedForLoopIfOp", qasm::nestedForLoopIfOp, - MQT_NAMED_BUILDER(qc::nestedForLoopIfOp)}, + 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{"NestedForLooWhilefOp", + qasm::nestedForLoopWhileOp, + MQT_NAMED_BUILDER(nestedForLoopWhileOp)}, QASM3TranslationTestCase{ "NestedForLoopCtrlOpWithSeparateQubit", qasm::nestedForLoopCtrlOpWithSeparateQubit, @@ -431,62 +473,3 @@ INSTANTIATE_TEST_SUITE_P( "NestedForLoopCtrlOpWithExtractedQubit", qasm::nestedForLoopCtrlOpWithExtractedQubit, MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithExtractedQubit)})); - -namespace { - -/// Build a context with all dialects required by the QASM3 translation loaded. -std::unique_ptr makeTranslationContext() { - DialectRegistry registry; - registry.insert(); - auto context = std::make_unique(); - context->appendDialectRegistry(registry); - context->loadAllAvailableDialects(); - return context; -} - -} // namespace - -// `stdgates.inc` and `qelib1.inc` are fully covered by the native gate table -// and treated as no-ops; any other include is a hard error. -TEST(QASM3TranslationIncludeTest, RejectsUnknownInclude) { - const auto context = makeTranslationContext(); - const std::string source = R"qasm(OPENQASM 3.0; -include "other.inc"; -qubit q; -)qasm"; - EXPECT_FALSE(qc::translateQASM3ToQC(source, context.get())); -} - -TEST(QASM3TranslationIncludeTest, AcceptsQelib1Include) { - const auto context = makeTranslationContext(); - const std::string source = R"qasm(OPENQASM 3.0; -include "qelib1.inc"; -qubit q; -x q; -)qasm"; - EXPECT_TRUE(qc::translateQASM3ToQC(source, context.get())); -} - -// A folded structural constant index (e.g. `q[1 + 1]`) now resolves to the same -// static qubit as the literal index it evaluates to. -TEST(QASM3TranslationFoldingTest, FoldsStructuralConstantIndex) { - const auto context = makeTranslationContext(); - const std::string folded = R"qasm(OPENQASM 3.0; -include "stdgates.inc"; -qubit[3] q; -x q[1 + 1]; -)qasm"; - const std::string literal = R"qasm(OPENQASM 3.0; -include "stdgates.inc"; -qubit[3] q; -x q[2]; -)qasm"; - - auto foldedModule = qc::translateQASM3ToQC(folded, context.get()); - auto literalModule = qc::translateQASM3ToQC(literal, context.get()); - ASSERT_TRUE(foldedModule); - ASSERT_TRUE(literalModule); - EXPECT_TRUE(areModulesEquivalentWithPermutations(foldedModule.get(), - literalModule.get())); -} diff --git a/mlir/unittests/programs/qasm_programs.cpp b/mlir/unittests/programs/qasm_programs.cpp index bed3d2cbca..d70b765637 100644 --- a/mlir/unittests/programs/qasm_programs.cpp +++ b/mlir/unittests/programs/qasm_programs.cpp @@ -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,18 +774,23 @@ if (c) { } )qasm"; -const std::string nestedForLoopIfOp = R"qasm(OPENQASM 3.0; +const std::string nestedIfOpForLoop = R"qasm(OPENQASM 3.0; include "stdgates.inc"; -qubit[2] q; +qubit[3] q; qubit qCond; -for uint i in [0:1] { +h qCond; +bit c = measure qCond; +if (c) { h qCond; - if (measure 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; @@ -786,6 +800,8 @@ while (measure q) { } )qasm"; +// --- ForOp ---------------------------------------------------------------- // + const std::string simpleForLoop = R"qasm(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; @@ -794,6 +810,39 @@ for uint i in [0:1] { } )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"; diff --git a/mlir/unittests/programs/qasm_programs.h b/mlir/unittests/programs/qasm_programs.h index e8e9223238..b46ba71729 100644 --- a/mlir/unittests/programs/qasm_programs.h +++ b/mlir/unittests/programs/qasm_programs.h @@ -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; @@ -372,14 +377,30 @@ extern const std::string ifElse; /// Creates a circuit with an if operation with a nested for operation with /// a register. -extern const std::string nestedForLoopIfOp; +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. From 7e379f53b72d3bd214ad9908975b36ce38201947 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:27:47 +0200 Subject: [PATCH 07/24] Fix linter errors --- .../Dialect/QC/Translation/QASM3Parser.cpp | 65 ++++++++++++------- mlir/lib/Dialect/QC/Translation/QASM3Parser.h | 2 +- .../QC/Translation/TranslateQASM3ToQC.cpp | 1 - 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp index 7e51f5810b..2edca83654 100644 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp @@ -27,19 +27,21 @@ #include #include #include +#include #include #include #include #include +#include #include #include -#include #include #include #include #include #include +#include #include #include #include @@ -61,9 +63,11 @@ using qasm3::Token; /// Signature: (builder, gate operands, gate parameters). using GateFn = std::function; +} // namespace + /// Build the table mapping each gate identifier to a lambda that emits the /// corresponding QC operation via the `QCProgramBuilder`. -llvm::StringMap buildGateDispatch() { +static llvm::StringMap buildGateDispatch() { llvm::StringMap d; // ZeroTargetOneParameter @@ -165,6 +169,8 @@ llvm::StringMap buildGateDispatch() { return d; } +namespace { + /// Map from gate identifier to `QCProgramBuilder` emitter. const llvm::StringMap GATE_DISPATCH = buildGateDispatch(); @@ -184,10 +190,12 @@ struct QubitBinding { /// Map of qubits in the current scope. using QubitScope = llvm::StringMap; +} // namespace + /// Look up a built-in numeric constant and emit it as an `f64`-typed MLIR /// value. -std::optional lookupBuiltinConstant(llvm::StringRef name, - QCProgramBuilder& builder) { +static std::optional lookupBuiltinConstant(llvm::StringRef name, + QCProgramBuilder& builder) { auto constant = [&](double value) -> Value { return arith::ConstantOp::create(builder, builder.getF64FloatAttr(value)) .getResult(); @@ -208,6 +216,8 @@ std::optional lookupBuiltinConstant(llvm::StringRef name, // Parsed representations //===----------------------------------------------------------------------===// +namespace { + /// A parsed expression. struct ParsedExpr { enum class Kind : uint8_t { IntLiteral, FloatLiteral, Ident, Unary, Binary }; @@ -262,8 +272,11 @@ struct ParsedBitRef { std::optional index; }; +} // namespace + /// Build the table mapping a gate identifier to its metadata. -llvm::StringMap> buildGateTable() { +static llvm::StringMap> +buildGateTable() { llvm::StringMap> t; for (const auto& [name, gate] : qasm3::STANDARD_GATES) { const auto* standard = dynamic_cast(gate.get()); @@ -288,6 +301,8 @@ llvm::StringMap> buildGateTable() { // QASM3Parser //===----------------------------------------------------------------------===// +namespace { + /** * @brief OpenQASM 3 parser that builds a QC program. * @@ -376,18 +391,18 @@ class QASM3Parser final { } /// Create a `DebugInfo` object from @p token. - [[nodiscard]] std::shared_ptr - makeDebugInfo(const Token& token) const { + [[nodiscard]] static std::shared_ptr + makeDebugInfo(const Token& token) { return std::make_shared(token.line, token.col, ""); } /// Throw a `CompilerError` at the position of @p token. - void error(const Token& token, const std::string& msg) const { + static void error(const Token& token, const std::string& msg) { throw CompilerError(msg, makeDebugInfo(token)); } /// Throw a `CompilerError` using an existing @p debugInfo. - void error(const DebugInfo& debugInfo, const std::string& msg) const { + static void error(const DebugInfo& debugInfo, const std::string& msg) { throw CompilerError(msg, std::make_shared(debugInfo)); } @@ -587,9 +602,10 @@ class QASM3Parser final { registerDeclaredName(qubit, id); if (size) { const auto reg = builder.allocQubitRegister(*size); - qubitRegisters[id] = {reg.value, reg.qubits}; + qubitRegisters[id] = {.memref = reg.value, .qubits = reg.qubits}; } else { - qubitRegisters[id] = {nullptr, {builder.allocQubit()}}; + qubitRegisters[id] = {.memref = nullptr, + .qubits = {builder.allocQubit()}}; } } @@ -606,9 +622,10 @@ class QASM3Parser final { registerDeclaredName(qreg, id); if (size) { const auto reg = builder.allocQubitRegister(*size); - qubitRegisters[id] = {reg.value, reg.qubits}; + qubitRegisters[id] = {.memref = reg.value, .qubits = reg.qubits}; } else { - qubitRegisters[id] = {nullptr, {builder.allocQubit()}}; + qubitRegisters[id] = {.memref = nullptr, + .qubits = {builder.allocQubit()}}; } } @@ -638,8 +655,8 @@ class QASM3Parser final { builder.allocClassicalBitRegister(size.value_or(1), id); if (operand) { - emitMeasureAssignment(ParsedBitRef{id, std::nullopt}, *operand, - debugInfo); + emitMeasureAssignment(ParsedBitRef{.name = id, .index = std::nullopt}, + *operand, debugInfo); } } @@ -1024,8 +1041,9 @@ class QASM3Parser final { } } - gates[id] = ParsedCompoundGate{std::move(parameters), std::move(targets), - std::move(body)}; + gates[id] = ParsedCompoundGate{.parameterNames = std::move(parameters), + .targetNames = targets, + .body = std::move(body)}; } std::vector parseIdentifierList() { @@ -1272,7 +1290,7 @@ class QASM3Parser final { /// Partition @p operands into controls and targets. template - ControlsAndTargets + [[nodiscard]] ControlsAndTargets splitControlsAndTargets(const std::vector& modifiers, size_t numCompatControls, const SmallVector& operands, @@ -1326,10 +1344,10 @@ class QASM3Parser final { } /// Look up the `QCProgramBuilder` emitter of a @p gate. - const GateFn& + [[nodiscard]] static const GateFn& resolveStandardGate(const GateInfo& gate, const std::string& fullId, llvm::StringRef resolvedId, ValueRange params, - const std::shared_ptr& debugInfo) const { + const std::shared_ptr& debugInfo) { const auto dispIt = GATE_DISPATCH.find(resolvedId); if (dispIt == GATE_DISPATCH.end()) { error(*debugInfo, "No MLIR definition found for gate '" + fullId + "'."); @@ -1417,7 +1435,7 @@ class QASM3Parser final { for (auto index : indices) { args.push_back(qubits[index]); } - localScope[name] = {nullptr, std::move(args)}; + localScope[name] = {.memref = nullptr, .qubits = std::move(args)}; } for (const auto& bodyCall : gate.body) { emitGateCall(bodyCall, localScope); @@ -1655,8 +1673,9 @@ class QASM3Parser final { const std::shared_ptr& debugInfo) { switch (expr.kind) { case ParsedExpr::Kind::IntLiteral: - return arith::ConstantOp::create(builder, - builder.getF64FloatAttr(expr.intValue)) + return arith::ConstantOp::create( + builder, + builder.getF64FloatAttr(static_cast(expr.intValue))) .getResult(); case ParsedExpr::Kind::FloatLiteral: return arith::ConstantOp::create(builder, diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.h b/mlir/lib/Dialect/QC/Translation/QASM3Parser.h index 9c3a8a9e50..a35f27387a 100644 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.h +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.h @@ -31,7 +31,7 @@ namespace qc::detail { * * @param source String containing the OpenQASM3 program. * @param context The MLIRContext to create the module in. - * @return A module containing the QC program + * @return A module containing the QC program. */ [[nodiscard]] OwningOpRef parseQASM3(std::string_view source, MLIRContext* context); diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp index 1a9149686f..95f9ebece4 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp @@ -13,7 +13,6 @@ #include "QASM3Parser.h" #include -#include #include #include #include From cb39887733d086619797de6c6d6fd583abf05114 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 8 Jul 2026 17:29:28 +0200 Subject: [PATCH 08/24] Address the Rabbit's comments --- include/mqt-core/qasm3/DebugInfo.hpp | 12 ++-- .../Dialect/QC/Translation/QASM3Parser.cpp | 55 +++++++++++-------- .../QC/Translation/test_qasm3_translation.cpp | 2 +- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/include/mqt-core/qasm3/DebugInfo.hpp b/include/mqt-core/qasm3/DebugInfo.hpp index ea28c94224..81e420bb38 100644 --- a/include/mqt-core/qasm3/DebugInfo.hpp +++ b/include/mqt-core/qasm3/DebugInfo.hpp @@ -17,17 +17,19 @@ namespace qasm3 { +/// Source location information for error reporting and diagnostics. struct DebugInfo { - size_t line; - size_t column; - std::string filename; - std::shared_ptr parent; + 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(std::move(file))), + : 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); } diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp index 2edca83654..b1b8a7a81c 100644 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp +++ b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp @@ -397,12 +397,13 @@ class QASM3Parser final { } /// Throw a `CompilerError` at the position of @p token. - static void error(const Token& token, const std::string& msg) { + [[noreturn]] static void error(const Token& token, const std::string& msg) { throw CompilerError(msg, makeDebugInfo(token)); } /// Throw a `CompilerError` using an existing @p debugInfo. - static void error(const DebugInfo& debugInfo, const std::string& msg) { + [[noreturn]] static void error(const DebugInfo& debugInfo, + const std::string& msg) { throw CompilerError(msg, std::make_shared(debugInfo)); } @@ -599,14 +600,7 @@ class QASM3Parser final { } const auto id = expect(Token::Kind::Identifier).str; expect(Token::Kind::Semicolon); - registerDeclaredName(qubit, id); - if (size) { - const auto reg = builder.allocQubitRegister(*size); - qubitRegisters[id] = {.memref = reg.value, .qubits = reg.qubits}; - } else { - qubitRegisters[id] = {.memref = nullptr, - .qubits = {builder.allocQubit()}}; - } + declareQubitRegister(qubit, id, size); } /// Parse `qreg [];`. @@ -619,14 +613,7 @@ class QASM3Parser final { size = parseDesignator(debugInfo); } expect(Token::Kind::Semicolon); - registerDeclaredName(qreg, id); - if (size) { - const auto reg = builder.allocQubitRegister(*size); - qubitRegisters[id] = {.memref = reg.value, .qubits = reg.qubits}; - } else { - qubitRegisters[id] = {.memref = nullptr, - .qubits = {builder.allocQubit()}}; - } + declareQubitRegister(qreg, id, size); } /// Parse `bit[] (= );`. @@ -650,9 +637,7 @@ class QASM3Parser final { expect(Token::Kind::Semicolon); - registerDeclaredName(bit, id); - classicalRegisters[id] = - builder.allocClassicalBitRegister(size.value_or(1), id); + declareClassicalRegister(bit, id, size); if (operand) { emitMeasureAssignment(ParsedBitRef{.name = id, .index = std::nullopt}, @@ -670,9 +655,7 @@ class QASM3Parser final { size = parseDesignator(debugInfo); } expect(Token::Kind::Semicolon); - registerDeclaredName(creg, id); - classicalRegisters[id] = - builder.allocClassicalBitRegister(size.value_or(1), id); + declareClassicalRegister(creg, id, size); } /// Parse a `[]` designator. @@ -689,6 +672,25 @@ class QASM3Parser final { } } + void declareQubitRegister(const Token& token, const std::string& id, + const std::optional& size) { + registerDeclaredName(token, id); + if (size) { + const auto reg = builder.allocQubitRegister(*size); + qubitRegisters[id] = {.memref = reg.value, .qubits = reg.qubits}; + } else { + qubitRegisters[id] = {.memref = nullptr, + .qubits = {builder.allocQubit()}}; + } + } + + void declareClassicalRegister(const Token& token, const std::string& id, + const std::optional& size) { + registerDeclaredName(token, id); + classicalRegisters[id] = + builder.allocClassicalBitRegister(size.value_or(1), id); + } + //===--- Assignments --------------------------------------------------===// /// Parse `c = measure q;`. @@ -879,6 +881,11 @@ class QASM3Parser final { stop = second; } + if (step.intValue < 1) { + error(*debugInfo, + "For loops with a non-positive step are not supported."); + } + expect(Token::Kind::RBracket); auto startVal = emitIntegerExpression(start, debugInfo); diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index 67f39aec63..49cbfbc6c2 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -462,7 +462,7 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(forLoopOffsetIndex)}, QASM3TranslationTestCase{"NestedForLoopIfOp", qasm::nestedForLoopIfOp, MQT_NAMED_BUILDER(qc::nestedForLoopIfOp)}, - QASM3TranslationTestCase{"NestedForLooWhilefOp", + QASM3TranslationTestCase{"NestedForLoopWhileOp", qasm::nestedForLoopWhileOp, MQT_NAMED_BUILDER(nestedForLoopWhileOp)}, QASM3TranslationTestCase{ From 853879a5a50453950169bba90249d517588ae686 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 9 Jul 2026 19:19:28 +0200 Subject: [PATCH 09/24] Let Claude refactor the translation to rely purely on LLVM concepts --- .../QC/Translation/qasm3/QASM3Emitter.h | 44 + .../Dialect/QC/Translation/qasm3/QASM3Lexer.h | 136 ++ .../QC/Translation/qasm3/QASM3Parser.h | 1097 ++++++++++ .../lib/Dialect/QC/Translation/CMakeLists.txt | 3 +- .../Dialect/QC/Translation/QASM3Parser.cpp | 1850 ----------------- mlir/lib/Dialect/QC/Translation/QASM3Parser.h | 41 - .../QC/Translation/TranslateQASM3ToQC.cpp | 19 +- .../QC/Translation/qasm3/QASM3Emitter.cpp | 1201 +++++++++++ .../QC/Translation/qasm3/QASM3Lexer.cpp | 338 +++ .../QC/Translation/test_qasm3_translation.cpp | 8 - mlir/unittests/programs/qasm_programs.cpp | 6 - mlir/unittests/programs/qasm_programs.h | 3 - 12 files changed, 2824 insertions(+), 1922 deletions(-) create mode 100644 mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h create mode 100644 mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h create mode 100644 mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h delete mode 100644 mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp delete mode 100644 mlir/lib/Dialect/QC/Translation/QASM3Parser.h create mode 100644 mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp create mode 100644 mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp 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..d22fdc6bf5 --- /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 Import an OpenQASM 3 program into a QC-dialect module. + * + * @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 whose main buffer holds the program. + * @param context The MLIRContext to create the module in. + * @return The imported module, or null on failure. + */ +[[nodiscard]] OwningOpRef importQASM3(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..415ce523c0 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h @@ -0,0 +1,136 @@ +/* + * 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 + +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, + // Type keywords (only a subset is supported; the rest produce diagnostics) + Int, + Uint, + Bool, + Float, + Angle, + Duration, + + // 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, + CompoundAssign, // any `=` such as `+=`, `-=`, ... + + // Comparisons + EqualsEquals, + NotEquals, + Less, + LessEquals, + Greater, + GreaterEquals, +}; + +/// A human-readable name for @p kind, used in diagnostics. +[[nodiscard]] llvm::StringRef describe(TokenKind kind); + +/// A single lexical token. Spellings are zero-copy views into the source +/// buffer; literals are pre-parsed. +struct Token { + TokenKind kind = TokenKind::Eof; + llvm::StringRef spelling; ///< Identifier text or string-literal contents. + llvm::SMLoc loc; + 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(llvm::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..3f38586683 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h @@ -0,0 +1,1097 @@ +/* + * 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 { + +//===----------------------------------------------------------------------===// +// Transient parse vocabulary +// +// These types are the vocabulary the parser hands to a sink. They 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. +//===----------------------------------------------------------------------===// + +/// A (sub-)expression. Bump-allocated; children are borrowed pointers. +struct Expr { + enum class Kind : uint8_t { Int, Float, Ident, Neg, Add, Sub, Mul, Div }; + + Kind kind = Kind::Int; + int64_t intValue = 0; + double floatValue = 0.0; + llvm::StringRef ident; + const Expr* lhs = nullptr; + const Expr* rhs = nullptr; + llvm::SMLoc loc; +}; + +/// 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; ///< `pow`/`ctrl`/`negctrl` argument, or null. +}; + +/// A gate operand: a (possibly indexed) identifier, or a hardware qubit. +struct Operand { + llvm::StringRef identifier; + const Expr* index = nullptr; + std::optional hardwareQubit; + llvm::SMLoc loc; +}; + +/// A (possibly indexed) classical reference, e.g. `c` or `c[0]`. +struct Reference { + llvm::StringRef identifier; + const Expr* index = nullptr; + llvm::SMLoc loc; +}; + +/// A resolved gate call. Array members are borrowed for the duration of the +/// sink call (top-level) or bump-allocated (gate-definition bodies). +struct GateCall { + llvm::StringRef identifier; + llvm::ArrayRef modifiers; + llvm::ArrayRef parameters; + llvm::ArrayRef operands; + llvm::SMLoc loc; +}; + +/// A branch condition. Only the restricted forms below are supported. +struct Condition { + enum class Kind : uint8_t { Measurement, Bit, NegatedBit }; + Kind kind = Kind::Bit; + Operand operand; ///< For `Measurement`. + Reference bit; ///< For `Bit`/`NegatedBit`. + llvm::SMLoc loc; +}; + +//===----------------------------------------------------------------------===// +// 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, llvm::SMLoc loc, llvm::StringRef str, const Expr& expr, + const Operand& operand, const Reference& reference, + const Condition& condition, const GateCall& call, + llvm::ArrayRef operands, + llvm::ArrayRef names, + llvm::ArrayRef body, + llvm::function_ref cont, double d, bool flag) { + s.error(loc, str); + s.version(d); + s.include(loc, str); + s.constDecl(loc, str, expr); + 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.conditionOnly(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 (!isAtEnd()) { + 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 isAtEnd() 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(); + } + + template + [[nodiscard]] llvm::ArrayRef copyToArena(llvm::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: + return parseConstDecl(); + case TokenKind::Qubit: + return parseQuantumDecl(); + case TokenKind::Qreg: + return parseOldStyleDecl(/*classical=*/false); + case TokenKind::CReg: + return parseOldStyleDecl(/*classical=*/true); + case TokenKind::Bit: + return parseClassicalDecl(); + case TokenKind::Int: + case TokenKind::Uint: + case TokenKind::Bool: + case TokenKind::Float: + case TokenKind::Angle: + case TokenKind::Duration: + return sink.error(current().loc, "declaration type is not supported yet"); + case TokenKind::Gate: + return parseGateDefinition(); + 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 (!isAtEnd() && current().kind != TokenKind::RBrace) { + if (failed(parseStatement())) { + return failure(); + } + } + return expect(TokenKind::RBrace); + } + return parseStatement(); + } + + //===--- Version and include ------------------------------------------===// + + [[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 sink.version(version); + } + + [[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().spelling; + advance(); + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.include(loc, filename); + } + + //===--- Declarations -------------------------------------------------===// + + [[nodiscard]] LogicalResult parseConstDecl() { + const auto loc = current().loc; + advance(); // const + if (current().kind != TokenKind::Float || + peek().kind != TokenKind::Identifier) { + return sink.error(loc, "only `const float = ;` " + "declarations are supported for now"); + } + advance(); // float + const auto id = current().spelling; + advance(); + if (failed(expect(TokenKind::Equals))) { + return failure(); + } + auto value = parseExpression(); + if (failed(value)) { + return failure(); + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.constDecl(loc, id, **value); + } + + [[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().spelling; + advance(); + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.qubitRegister(loc, id, size); + } + + [[nodiscard]] LogicalResult parseOldStyleDecl(const bool classical) { + const auto loc = current().loc; + advance(); // qreg / creg + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected identifier"); + } + const auto id = current().spelling; + 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 classical ? sink.classicalRegister(loc, id, size) + : sink.qubitRegister(loc, id, size); + } + + [[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().spelling; + 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 Reference target{.identifier = id, .index = nullptr, .loc = loc}; + return sink.measure(loc, target, *measureSource); + } + return success(); + } + + [[nodiscard]] mlir::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; + } + + //===--- Assignment and measurement -----------------------------------===// + + [[nodiscard]] LogicalResult parseAssignment() { + const auto loc = current().loc; + auto target = parseReference(); + if (failed(target)) { + return failure(); + } + if (current().kind != TokenKind::Equals) { + return sink.error(current().loc, + "classical computations are not supported yet"); + } + advance(); + if (current().kind != TokenKind::Measure) { + return sink.error(current().loc, + "classical computations are not supported yet"); + } + advance(); + auto operand = parseGateOperand(); + if (failed(operand)) { + return failure(); + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.measure(loc, *target, *operand); + } + + [[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 = parseReference(); + if (failed(target)) { + return failure(); + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.measure(loc, *target, *operand); + } + + //===--- Barrier and reset --------------------------------------------===// + + [[nodiscard]] LogicalResult parseBarrier() { + const auto loc = current().loc; + advance(); // barrier + llvm::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); + } + + [[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); + } + + //===--- Control flow -------------------------------------------------===// + + [[nodiscard]] LogicalResult parseIf() { + const auto loc = current().loc; + advance(); // if + if (failed(expect(TokenKind::LParen))) { + return failure(); + } + auto condition = parseCondition(); + if (failed(condition)) { + return failure(); + } + 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.conditionOnly(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(); + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected loop variable"); + } + const auto variable = current().spelling; + advance(); + 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->kind = Expr::Kind::Int; + one->intValue = 1; + one->loc = loc; + 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 condition = parseCondition(); + if (failed(condition)) { + return failure(); + } + if (failed(expect(TokenKind::RParen))) { + return failure(); + } + return sink.whileStmt(loc, *condition, [this] { return parseBlock(); }); + } + + [[nodiscard]] mlir::FailureOr parseCondition() { + Condition condition; + condition.loc = current().loc; + + if (current().kind == TokenKind::Measure) { + advance(); + condition.kind = Condition::Kind::Measurement; + auto operand = parseGateOperand(); + if (failed(operand)) { + return failure(); + } + condition.operand = *operand; + return condition; + } + + if (current().kind == TokenKind::ExclamationPoint || + current().kind == TokenKind::Tilde) { + advance(); + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, + "unary expression has unsupported operand"); + } + condition.kind = Condition::Kind::NegatedBit; + auto bit = parseReference(); + if (failed(bit)) { + return failure(); + } + condition.bit = *bit; + return condition; + } + + if (current().kind == TokenKind::Identifier) { + condition.kind = Condition::Kind::Bit; + auto bit = parseReference(); + if (failed(bit)) { + return failure(); + } + condition.bit = *bit; + switch (current().kind) { + case TokenKind::EqualsEquals: + case TokenKind::NotEquals: + case TokenKind::Less: + case TokenKind::LessEquals: + case TokenKind::Greater: + case TokenKind::GreaterEquals: + return sink.error(condition.loc, + "register comparisons are not supported"); + default: + break; + } + return condition; + } + + return sink.error(condition.loc, + "unsupported condition expression in if statement"); + } + + //===--- Gate definitions and calls -----------------------------------===// + + [[nodiscard]] LogicalResult parseGateDefinition() { + const auto loc = current().loc; + advance(); // gate + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected gate name"); + } + const auto id = current().spelling; + advance(); + + llvm::SmallVector parameters; + if (current().kind == TokenKind::LParen) { + advance(); + if (failed(parseIdentifierList(parameters))) { + return failure(); + } + if (failed(expect(TokenKind::RParen))) { + return failure(); + } + } + + llvm::SmallVector targets; + if (failed(parseIdentifierList(targets))) { + return failure(); + } + + if (failed(expect(TokenKind::LBrace))) { + return failure(); + } + llvm::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. + llvm::SmallVector callModifiers; + llvm::SmallVector callParameters; + llvm::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(llvm::ArrayRef(parameters)), + copyToArena(llvm::ArrayRef(targets)), + copyToArena(llvm::ArrayRef(body))); + } + + [[nodiscard]] LogicalResult parseGateCallStatement() { + // The scratch buffers must outlive the sink call, so they live here rather + // than inside `parseGateCall`. + llvm::SmallVector modifiers; + llvm::SmallVector parameters; + llvm::SmallVector operands; + auto call = parseGateCall(modifiers, parameters, operands, + /*persist=*/false); + if (failed(call)) { + return failure(); + } + return sink.gateCall(*call); + } + + [[nodiscard]] mlir::FailureOr + parseGateCall(llvm::SmallVectorImpl& modifiers, + llvm::SmallVectorImpl& parameters, + llvm::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(); + } + } + + if (current().kind == TokenKind::Gphase) { + call.identifier = "gphase"; + advance(); + } else if (current().kind == TokenKind::Identifier) { + call.identifier = current().spelling; + advance(); + } else { + return sink.error(current().loc, "expected gate name"); + } + + 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"); + } + + 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-definition bodies outlive + // this frame, so their arrays are copied into the arena. + if (persist) { + call.modifiers = copyToArena(llvm::ArrayRef(modifiers)); + call.parameters = copyToArena(llvm::ArrayRef(parameters)); + call.operands = copyToArena(llvm::ArrayRef(operands)); + return call; + } + call.modifiers = modifiers; + call.parameters = parameters; + call.operands = operands; + return call; + } + + [[nodiscard]] mlir::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]] mlir::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().spelling; + advance(); + if (current().kind == TokenKind::LBracket) { + advance(); + auto index = parseExpression(); + if (failed(index)) { + return failure(); + } + operand.index = *index; + if (failed(expect(TokenKind::RBracket))) { + return failure(); + } + } + return operand; + } + + [[nodiscard]] mlir::FailureOr parseReference() { + Reference reference; + reference.loc = current().loc; + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected an identifier"); + } + reference.identifier = current().spelling; + advance(); + if (current().kind == TokenKind::LBracket) { + advance(); + auto index = parseExpression(); + if (failed(index)) { + return failure(); + } + reference.index = *index; + if (failed(expect(TokenKind::RBracket))) { + return failure(); + } + } + return reference; + } + + [[nodiscard]] LogicalResult + parseIdentifierList(llvm::SmallVectorImpl& identifiers) { + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected an identifier"); + } + identifiers.push_back(current().spelling); + advance(); + while (current().kind == TokenKind::Comma) { + advance(); + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected an identifier"); + } + identifiers.push_back(current().spelling); + advance(); + } + return success(); + } + + //===--- Expressions --------------------------------------------------===// + + [[nodiscard]] mlir::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]] mlir::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]] mlir::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->kind = Expr::Kind::Neg; + expr->loc = loc; + expr->lhs = *operand; + return expr; + } + return parsePrimary(); + } + + [[nodiscard]] mlir::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::Ident; + expr->ident = current().spelling; + 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 llvm::SMLoc loc) { + auto* expr = makeExpr(); + expr->kind = kind; + expr->loc = loc; + 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 bf75040f7c..6650f0d091 100644 --- a/mlir/lib/Dialect/QC/Translation/CMakeLists.txt +++ b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt @@ -10,7 +10,8 @@ add_mlir_library( MLIRQCTranslation TranslateQuantumComputationToQC.cpp TranslateQASM3ToQC.cpp - QASM3Parser.cpp + qasm3/QASM3Lexer.cpp + qasm3/QASM3Emitter.cpp LINK_LIBS MLIRArithDialect MLIRFuncDialect diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp b/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp deleted file mode 100644 index b1b8a7a81c..0000000000 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.cpp +++ /dev/null @@ -1,1850 +0,0 @@ -/* - * 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 "QASM3Parser.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/DebugInfo.hpp" -#include "qasm3/Exception.hpp" -#include "qasm3/Gate.hpp" -#include "qasm3/NestedEnvironment.hpp" -#include "qasm3/Scanner.hpp" -#include "qasm3/StdGates.hpp" -#include "qasm3/Token.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 - -namespace mlir::qc { - -namespace { - -using qasm3::CompilerError; -using qasm3::DebugInfo; -using qasm3::GateInfo; -using qasm3::Token; - -/// Signature: (builder, gate operands, gate parameters). -using GateFn = std::function; - -} // namespace - -/// Build the table mapping each 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; -} - -namespace { - -/// Map from gate identifier to `QCProgramBuilder` emitter. -const llvm::StringMap GATE_DISPATCH = buildGateDispatch(); - -/** - * @brief Represents a named qubit binding in scope. - * - * @details - * For top-level registers, `memref` holds the backing register and `qubits` - * holds eagerly extracted values. For compound gates, `memref` is null and - * `qubits` holds the alaiased values. - */ -struct QubitBinding { - Value memref; - SmallVector qubits; -}; - -/// Map of qubits in the current scope. -using QubitScope = llvm::StringMap; - -} // namespace - -/// Look up a built-in numeric constant and emit it as an `f64`-typed MLIR -/// value. -static std::optional lookupBuiltinConstant(llvm::StringRef name, - QCProgramBuilder& builder) { - auto constant = [&](double value) -> Value { - return arith::ConstantOp::create(builder, builder.getF64FloatAttr(value)) - .getResult(); - }; - if (name == "pi" || name == "π") { - return constant(::qc::PI); - } - if (name == "tau" || name == "τ") { - return constant(::qc::TAU); - } - if (name == "euler" || name == "ℇ") { - return constant(::qc::E); - } - return std::nullopt; -} - -//===----------------------------------------------------------------------===// -// Parsed representations -//===----------------------------------------------------------------------===// - -namespace { - -/// A parsed expression. -struct ParsedExpr { - enum class Kind : uint8_t { IntLiteral, FloatLiteral, Ident, Unary, Binary }; - - Kind kind{Kind::IntLiteral}; - - // Literal - int64_t intValue{}; - double floatValue{}; - - // Ident - std::string name; - - // Unary or Binary - Token::Kind op{}; - std::vector children; -}; - -/// A parsed gate modifier. -struct ParsedModifier { - Token::Kind kind{Token::Kind::Inv}; - std::optional expression; -}; - -/// A parsed gate operand. Either a (possibly indexed) named qubit, or a -/// hardware qubit. -struct ParsedOperand { - std::string name; - std::optional index; - std::optional hardwareQubit; -}; - -/// A parsed gate call. -struct ParsedGateCall { - std::string identifier; - std::vector modifiers; - std::vector parameters; - std::vector operands; - std::shared_ptr debugInfo; -}; - -/// A parsed compound gate. -struct ParsedCompoundGate { - std::vector parameterNames; - std::vector targetNames; - std::vector body; -}; - -/// A parsed classical bit reference. -struct ParsedBitRef { - std::string name; - std::optional index; -}; - -} // namespace - -/// 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}); - } - - const 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; -} - -//===----------------------------------------------------------------------===// -// QASM3Parser -//===----------------------------------------------------------------------===// - -namespace { - -/** - * @brief OpenQASM 3 parser that builds a QC program. - * - * @details - * Consumes `qasm3::Scanner` tokens directly and emits QC operations via the - * `QCProgramBuilder` as it parses. Each `parse` function parses all tokens of a - * given statement. Validation helpers raise `CompilerError`s wherever needed. - */ -class QASM3Parser final { -public: - explicit QASM3Parser(MLIRContext* ctx) - : builder(ctx), gates(buildGateTable()) {} - - OwningOpRef parse(std::string_view source) { - input = std::make_unique(std::string(source)); - scanner = std::make_unique(input.get()); - - // Initialize the token windows - currentToken = scanner->next(); - nextToken = scanner->next(); - - builder.initialize(); - parseProgram(); - return builder.finalize(); - } - -private: - //===--- State --------------------------------------------------------===// - - QCProgramBuilder builder; - - std::unique_ptr input; - std::unique_ptr scanner; - Token currentToken{0, 0}; - Token nextToken{0, 0}; - - /// Set of declared identifiers. - llvm::StringSet<> declaredNames; - - /// Map from a parameter constant to its `f64`-typed MLIR value. - qasm3::NestedEnvironment parameterConstants; - - /// Map from an induction variable to its MLIR value. - qasm3::NestedEnvironment loopVariables; - - /// Cache of dynamically loaded qubits. - qasm3::NestedEnvironment dynamicallyLoadedQubits; - - /// Map from qubit-register name to `QubitScope`. - 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 its metadata. - llvm::StringMap> gates; - - bool openQASM2CompatMode{false}; - - //===--- Token scaffolding --------------------------------------------===// - - /** - * @brief Advance the token cursor by one. - * - * @details - * `current()` moves to what `peek()` returned, and a fresh token is pulled - * from the scanner into the lookahead. - */ - void advance() { - currentToken = nextToken; - nextToken = scanner->next(); - } - - /// The token the parser is currently positioned on. - [[nodiscard]] const Token& current() const { return currentToken; } - - /// The next token, without consuming it. - [[nodiscard]] const Token& peek() const { return nextToken; } - - /// Whether the entire input has been consumed. - [[nodiscard]] bool isAtEnd() const { - return currentToken.kind == Token::Kind::Eof; - } - - /// Create a `DebugInfo` object from @p token. - [[nodiscard]] static std::shared_ptr - makeDebugInfo(const Token& token) { - return std::make_shared(token.line, token.col, ""); - } - - /// Throw a `CompilerError` at the position of @p token. - [[noreturn]] static void error(const Token& token, const std::string& msg) { - throw CompilerError(msg, makeDebugInfo(token)); - } - - /// Throw a `CompilerError` using an existing @p debugInfo. - [[noreturn]] static void error(const DebugInfo& debugInfo, - const std::string& msg) { - throw CompilerError(msg, std::make_shared(debugInfo)); - } - - /// Assert that the current token matches an expected kind, then advance. - Token expect(const Token::Kind expected) { - auto token = current(); - if (token.kind != expected) { - error(token, "Expected '" + Token::kindToString(expected) + "', got '" + - Token::kindToString(token.kind) + "'."); - } - advance(); - return token; - } - - //===--- Program and statement dispatch -------------------------------===// - - void parseProgram() { - while (!isAtEnd()) { - parseStatement(); - } - } - - void parseStatement() { - switch (current().kind) { - case Token::Kind::OpenQasm: - parseVersionDeclaration(); - return; - case Token::Kind::Include: - parseInclude(); - return; - case Token::Kind::Const: - parseConstantDeclaration(); - return; - case Token::Kind::Qubit: - parseQubitDeclaration(); - return; - case Token::Kind::Qreg: - parseQregDeclaration(); - return; - case Token::Kind::Bit: - parseBitDeclaration(); - return; - case Token::Kind::CReg: - parseCregDeclaration(); - return; - case Token::Kind::Int: - case Token::Kind::Uint: - case Token::Kind::Bool: - case Token::Kind::Float: - case Token::Kind::Angle: - case Token::Kind::Duration: - error(current(), "Declaration type is not supported yet."); - return; - case Token::Kind::Gate: - parseGateDeclaration(); - return; - case Token::Kind::Opaque: - error(current(), "Opaque gate declarations are not supported."); - return; - case Token::Kind::InitialLayout: - case Token::Kind::OutputPermutation: - // No counterparts in QC - advance(); - return; - case Token::Kind::Barrier: - parseBarrier(); - return; - case Token::Kind::Reset: - parseReset(); - return; - case Token::Kind::Measure: - parseMeasure(); - return; - case Token::Kind::If: - parseIf(); - return; - case Token::Kind::For: - parseFor(); - return; - case Token::Kind::While: - parseWhile(); - return; - case Token::Kind::Inv: - case Token::Kind::Pow: - case Token::Kind::Ctrl: - case Token::Kind::NegCtrl: - case Token::Kind::Gphase: - emitGateCall(parseGateCall(), qubitRegisters); - return; - case Token::Kind::Identifier: - switch (peek().kind) { - case Token::Kind::LBracket: - case Token::Kind::Equals: - case Token::Kind::PlusEquals: - case Token::Kind::MinusEquals: - case Token::Kind::AsteriskEquals: - case Token::Kind::SlashEquals: - case Token::Kind::AmpersandEquals: - case Token::Kind::PipeEquals: - case Token::Kind::TildeEquals: - case Token::Kind::CaretEquals: - case Token::Kind::LeftShitEquals: - case Token::Kind::RightShiftEquals: - case Token::Kind::PercentEquals: - case Token::Kind::DoubleAsteriskEquals: - parseAssignment(); - return; - default: - emitGateCall(parseGateCall(), qubitRegisters); - return; - } - default: - error(current(), "Unexpected token '" + current().toString() + "'."); - } - } - - /// Parse a `{ ... }` block or a single statement. - void parseBlockOrStatement() { - if (current().kind == Token::Kind::LBrace) { - advance(); - while (!isAtEnd() && current().kind != Token::Kind::RBrace) { - parseStatement(); - } - expect(Token::Kind::RBrace); - } else { - parseStatement(); - } - } - - //===--- Version ------------------------------------------------------===// - - void parseVersionDeclaration() { - expect(Token::Kind::OpenQasm); - double version = 0.0; - if (current().kind == Token::Kind::FloatLiteral) { - version = current().valReal; - advance(); - } else if (current().kind == Token::Kind::IntegerLiteral) { - version = static_cast(current().val); - advance(); - } else { - error(current(), - "Version declaration must be a float or integer literal."); - } - expect(Token::Kind::Semicolon); - if (version < 3) { - openQASM2CompatMode = true; - } - } - - //===--- Include ------------------------------------------------------===// - - void parseInclude() { - const auto beginToken = expect(Token::Kind::Include); - const auto filename = expect(Token::Kind::StringLiteral).str; - expect(Token::Kind::Semicolon); - if (filename != "stdgates.inc" && filename != "qelib1.inc") { - error(beginToken, "Unsupported include '" + filename + - "'. Only 'stdgates.inc' and 'qelib1.inc' are " - "supported."); - } - } - - //===--- Declarations -------------------------------------------------===// - - /// Parse `const float = ;`. - void parseConstantDeclaration() { - const auto constToken = expect(Token::Kind::Const); - const auto debugInfo = makeDebugInfo(constToken); - - if (current().kind != Token::Kind::Float || - peek().kind != Token::Kind::Identifier) { - error(*debugInfo, "Only `const float = ;` declarations " - "are supported for now."); - } - expect(Token::Kind::Float); - const auto id = expect(Token::Kind::Identifier).str; - - expect(Token::Kind::Equals); - auto initExpr = parseExpression(); - expect(Token::Kind::Semicolon); - - registerDeclaredName(constToken, id); - parameterConstants.emplace(id, emitFloatExpression(initExpr, debugInfo)); - } - - /// Parse `qubit[] ;`. - void parseQubitDeclaration() { - const auto qubit = expect(Token::Kind::Qubit); - const auto debugInfo = makeDebugInfo(qubit); - std::optional size; - if (current().kind == Token::Kind::LBracket) { - size = parseDesignator(debugInfo); - } - const auto id = expect(Token::Kind::Identifier).str; - expect(Token::Kind::Semicolon); - declareQubitRegister(qubit, id, size); - } - - /// Parse `qreg [];`. - void parseQregDeclaration() { - const auto qreg = expect(Token::Kind::Qreg); - const auto debugInfo = makeDebugInfo(qreg); - const auto id = expect(Token::Kind::Identifier).str; - std::optional size; - if (current().kind == Token::Kind::LBracket) { - size = parseDesignator(debugInfo); - } - expect(Token::Kind::Semicolon); - declareQubitRegister(qreg, id, size); - } - - /// Parse `bit[] (= );`. - void parseBitDeclaration() { - const auto bit = expect(Token::Kind::Bit); - const auto debugInfo = makeDebugInfo(bit); - - std::optional size; - if (current().kind == Token::Kind::LBracket) { - size = parseDesignator(debugInfo); - } - - const auto id = expect(Token::Kind::Identifier).str; - - std::optional operand; - if (current().kind == Token::Kind::Equals) { - advance(); - expect(Token::Kind::Measure); - operand = parseGateOperand(); - } - - expect(Token::Kind::Semicolon); - - declareClassicalRegister(bit, id, size); - - if (operand) { - emitMeasureAssignment(ParsedBitRef{.name = id, .index = std::nullopt}, - *operand, debugInfo); - } - } - - /// Parse `creg [];`. - void parseCregDeclaration() { - const auto creg = expect(Token::Kind::CReg); - const auto debugInfo = makeDebugInfo(creg); - const auto id = expect(Token::Kind::Identifier).str; - std::optional size; - if (current().kind == Token::Kind::LBracket) { - size = parseDesignator(debugInfo); - } - expect(Token::Kind::Semicolon); - declareClassicalRegister(creg, id, size); - } - - /// Parse a `[]` designator. - int64_t parseDesignator(const std::shared_ptr& debugInfo) { - expect(Token::Kind::LBracket); - const auto expr = parseExpression(); - expect(Token::Kind::RBracket); - return evaluateIntegerConstant(expr, debugInfo); - } - - void registerDeclaredName(const Token& token, const std::string& id) { - if (!declaredNames.insert(id).second) { - error(token, "Identifier '" + id + "' already declared."); - } - } - - void declareQubitRegister(const Token& token, const std::string& id, - const std::optional& size) { - registerDeclaredName(token, id); - if (size) { - const auto reg = builder.allocQubitRegister(*size); - qubitRegisters[id] = {.memref = reg.value, .qubits = reg.qubits}; - } else { - qubitRegisters[id] = {.memref = nullptr, - .qubits = {builder.allocQubit()}}; - } - } - - void declareClassicalRegister(const Token& token, const std::string& id, - const std::optional& size) { - registerDeclaredName(token, id); - classicalRegisters[id] = - builder.allocClassicalBitRegister(size.value_or(1), id); - } - - //===--- Assignments --------------------------------------------------===// - - /// Parse `c = measure q;`. - void parseAssignment() { - const auto debugInfo = makeDebugInfo(current()); - const auto target = parseBitRef(); - - if (current().kind != Token::Kind::Equals) { - error(current(), "Classical computations are not supported yet."); - } - advance(); - - if (current().kind != Token::Kind::Measure) { - error(current(), "Classical computations are not supported yet."); - } - advance(); - - const auto operand = parseGateOperand(); - expect(Token::Kind::Semicolon); - - emitMeasureAssignment(target, operand, debugInfo); - } - - //===--- Measurement --------------------------------------------------===// - - /// Parse `measure q -> c;`. - void parseMeasure() { - const auto measure = expect(Token::Kind::Measure); - const auto debugInfo = makeDebugInfo(measure); - const auto operand = parseGateOperand(); - expect(Token::Kind::Arrow); - const auto target = parseBitRef(); - expect(Token::Kind::Semicolon); - emitMeasureAssignment(target, operand, debugInfo); - } - - void emitMeasureAssignment(const ParsedBitRef& target, - const ParsedOperand& operand, - const std::shared_ptr& debugInfo) { - const auto bits = resolveClassicalBits(target, debugInfo); - const auto resolved = resolveGateOperand(operand, debugInfo); - SmallVector qubits; - if (std::holds_alternative(resolved)) { - qubits.push_back(std::get(resolved)); - } else { - qubits = std::get>(resolved); - } - if (bits.size() != qubits.size()) { - error(*debugInfo, "The classical register and the quantum register must " - "have the same width."); - } - for (const auto& [bit, qubit] : llvm::zip_equal(bits, qubits)) { - const auto& registerName = bit.registerName; - const auto registerSize = bit.registerSize; - const auto registerIndex = bit.registerIndex; - auto result = - MeasureOp::create(builder, qubit, builder.getStringAttr(registerName), - builder.getI64IntegerAttr(registerSize), - builder.getI64IntegerAttr(registerIndex)) - .getResult(); - auto& registerBits = bitValues[registerName]; - const auto index = static_cast(registerIndex); - if (registerBits.size() <= index) { - registerBits.resize(index + 1); - } - registerBits[index] = result; - } - } - - //===--- Barrier ------------------------------------------------------===// - - void parseBarrier() { - const auto barrier = expect(Token::Kind::Barrier); - const auto debugInfo = makeDebugInfo(barrier); - SmallVector qubits; - while (current().kind != Token::Kind::Semicolon) { - const auto operand = parseGateOperand(); - const auto resolved = resolveGateOperand(operand, debugInfo); - if (std::holds_alternative(resolved)) { - qubits.push_back(std::get(resolved)); - } else { - llvm::append_range(qubits, std::get>(resolved)); - } - if (current().kind != Token::Kind::Semicolon) { - expect(Token::Kind::Comma); - } - } - expect(Token::Kind::Semicolon); - builder.barrier(qubits); - } - - //===--- Reset --------------------------------------------------------===// - - void parseReset() { - const auto reset = expect(Token::Kind::Reset); - const auto debugInfo = makeDebugInfo(reset); - const auto operand = parseGateOperand(); - const auto resolved = resolveGateOperand(operand, debugInfo); - if (std::holds_alternative(resolved)) { - builder.reset(std::get(resolved)); - } else { - for (auto qubit : std::get>(resolved)) { - builder.reset(qubit); - } - } - expect(Token::Kind::Semicolon); - } - - //===--- SCF ----------------------------------------------------------===// - - void parseIf() { - expect(Token::Kind::If); - expect(Token::Kind::LParen); - auto condition = parseCondition(); - expect(Token::Kind::RParen); - - // Empty then block - if (current().kind == Token::Kind::LBrace && - peek().kind == Token::Kind::RBrace) { - expect(Token::Kind::LBrace); - expect(Token::Kind::RBrace); - if (current().kind != Token::Kind::Else) { - return; - } - expect(Token::Kind::Else); - auto trueValue = builder.boolConstant(true); - condition = - arith::XOrIOp::create(builder, condition, trueValue).getResult(); - auto ifOp = scf::IfOp::create(builder, condition, - /*withElseRegion=*/false); - OpBuilder::InsertionGuard guard(builder); - auto* thenBlock = &ifOp.getThenRegion().front(); - builder.setInsertionPointToStart(thenBlock); - parseBlockOrStatement(); - return; - } - - auto ifOp = scf::IfOp::create(builder, condition, /*withElseRegion=*/false); - - OpBuilder::InsertionGuard guard(builder); - - auto* thenBlock = &ifOp.getThenRegion().front(); - builder.setInsertionPointToStart(thenBlock); - parseBlockOrStatement(); - - if (current().kind == Token::Kind::Else) { - expect(Token::Kind::Else); - if (current().kind == Token::Kind::LBrace && - peek().kind == Token::Kind::RBrace) { - expect(Token::Kind::LBrace); - expect(Token::Kind::RBrace); - return; - } - - auto* elseBlock = builder.createBlock(&ifOp.getElseRegion()); - builder.setInsertionPointToStart(elseBlock); - parseBlockOrStatement(); - scf::YieldOp::create(builder); - } - } - - void parseFor() { - const auto forToken = expect(Token::Kind::For); - const auto debugInfo = makeDebugInfo(forToken); - - if (current().kind != Token::Kind::Int && - current().kind != Token::Kind::Uint) { - error(current(), "Expected 'int' or 'uint' after 'for'."); - } - advance(); - - const auto loopVariable = expect(Token::Kind::Identifier).str; - - expect(Token::Kind::In); - expect(Token::Kind::LBracket); - - const auto start = parseExpression(); - expect(Token::Kind::Colon); - const auto second = parseExpression(); - - ParsedExpr step = {.kind = ParsedExpr::Kind::IntLiteral, .intValue = 1}; - ParsedExpr stop; - if (current().kind == Token::Kind::Colon) { - advance(); - step = second; - stop = parseExpression(); - } else { - stop = second; - } - - if (step.intValue < 1) { - error(*debugInfo, - "For loops with a non-positive step are not supported."); - } - - expect(Token::Kind::RBracket); - - auto startVal = emitIntegerExpression(start, debugInfo); - auto stepVal = emitIntegerExpression(step, debugInfo); - auto stopVal = emitIntegerExpression(stop, debugInfo); - - // OpenQASM 3's range is inclusive of the stop value - auto one = - arith::ConstantOp::create(builder, builder.getIndexAttr(1)).getResult(); - stopVal = arith::AddIOp::create(builder, stopVal, one).getResult(); - - loopVariables.push(); - dynamicallyLoadedQubits.push(); - - builder.scfFor(startVal, stopVal, stepVal, [&](Value iv) { - loopVariables.emplace(loopVariable, iv); - parseBlockOrStatement(); - }); - - dynamicallyLoadedQubits.pop(); - loopVariables.pop(); - } - - void parseWhile() { - expect(Token::Kind::While); - expect(Token::Kind::LParen); - - builder.scfWhile( - [&] { - dynamicallyLoadedQubits.push(); - const auto condition = parseCondition(); - expect(Token::Kind::RParen); - builder.scfCondition(condition); - dynamicallyLoadedQubits.pop(); - }, - [&] { - dynamicallyLoadedQubits.push(); - parseBlockOrStatement(); - dynamicallyLoadedQubits.pop(); - }); - } - - /// Translate a condition to an `i1`-typed MLIR value. - [[nodiscard]] Value parseCondition() { - const auto debugInfo = makeDebugInfo(current()); - - // Measurement (e.g., measure q) - if (current().kind == Token::Kind::Measure) { - advance(); - const auto operand = parseGateOperand(); - const auto resolved = resolveGateOperand(operand, debugInfo); - if (!std::holds_alternative(resolved)) { - error(*debugInfo, "Measurement condition must be a single qubit."); - } - return builder.measure(std::get(resolved)); - } - - // Unary negation (!c[0] or ~c[0]) - if (current().kind == Token::Kind::ExclamationPoint || - current().kind == Token::Kind::Tilde) { - advance(); - if (current().kind != Token::Kind::Identifier) { - error(current(), "Unary expression has unsupported operand."); - } - const auto bit = parseBitRef(); - const auto value = lookupBitValue(bit, debugInfo); - auto trueValue = builder.boolConstant(true); - return arith::XOrIOp::create(builder, value, trueValue).getResult(); - } - - // Single bit (c or c[0]), or an unsupported register comparison - if (current().kind == Token::Kind::Identifier) { - const auto bit = parseBitRef(); - switch (current().kind) { - case Token::Kind::DoubleEquals: - case Token::Kind::NotEquals: - case Token::Kind::LessThan: - case Token::Kind::GreaterThan: - case Token::Kind::LessThanEquals: - case Token::Kind::GreaterThanEquals: - error(*debugInfo, "Register comparisons are not supported."); - default: - break; - } - return lookupBitValue(bit, debugInfo); - } - - error(*debugInfo, "Unsupported condition expression in if statement."); - } - - /// Look up the most recent measurement result for a classical bit. - [[nodiscard]] Value - lookupBitValue(const ParsedBitRef& bit, - const std::shared_ptr& debugInfo) const { - const auto& registerName = bit.name; - auto it = bitValues.find(registerName); - if (it == bitValues.end()) { - error(*debugInfo, "No classical bit of register '" + registerName + - "' has been measured yet."); - } - const auto& registerBits = it->second; - - if (!bit.index) { - assert(registerBits.size() == 1); - return registerBits[0]; - } - - const auto index = evaluateNonNegativeConstant(*bit.index, debugInfo); - if (index >= registerBits.size() || !registerBits[index]) { - error(*debugInfo, "Bit " + std::to_string(index) + " of register '" + - registerName + "' has been not measured yet."); - } - return registerBits[index]; - } - - //===--- Gate declarations --------------------------------------------===// - - void parseGateDeclaration() { - const auto gate = expect(Token::Kind::Gate); - - const auto id = expect(Token::Kind::Identifier).str; - if (gates.contains(id)) { - error(gate, "Gate '" + id + "' already declared."); - } - - // Parse parameters - std::vector parameters; - if (current().kind == Token::Kind::LParen) { - advance(); - parameters = parseIdentifierList(); - expect(Token::Kind::RParen); - } - - // Parse targets - const auto targets = parseIdentifierList(); - - // Parse body - expect(Token::Kind::LBrace); - std::vector body; - while (current().kind != Token::Kind::RBrace) { - body.push_back(parseGateCall()); - } - expect(Token::Kind::RBrace); - - // Verify parameters - for (size_t i = 0; i < parameters.size(); ++i) { - for (size_t j = i + 1; j < parameters.size(); ++j) { - if (parameters[i] == parameters[j]) { - error(gate, "Parameter is already declared in compound gate."); - } - } - } - - // Verify targets - for (size_t i = 0; i < targets.size(); ++i) { - for (size_t j = i + 1; j < targets.size(); ++j) { - if (targets[i] == targets[j]) { - error(gate, "Target is already declared in compound gate."); - } - } - } - - gates[id] = ParsedCompoundGate{.parameterNames = std::move(parameters), - .targetNames = targets, - .body = std::move(body)}; - } - - std::vector parseIdentifierList() { - std::vector identifiers; - identifiers.push_back(expect(Token::Kind::Identifier).str); - while (current().kind == Token::Kind::Comma) { - advance(); - identifiers.push_back(expect(Token::Kind::Identifier).str); - } - return identifiers; - } - - //===--- Gate calls ---------------------------------------------------===// - - ParsedGateCall parseGateCall() { - ParsedGateCall call; - call.debugInfo = makeDebugInfo(current()); - - // Parse modifiers - while (current().kind == Token::Kind::Inv || - current().kind == Token::Kind::Pow || - current().kind == Token::Kind::Ctrl || - current().kind == Token::Kind::NegCtrl) { - call.modifiers.push_back(parseGateModifier()); - expect(Token::Kind::At); - } - - // Parse identifier - if (current().kind == Token::Kind::Gphase) { - advance(); - call.identifier = "gphase"; - } else { - call.identifier = expect(Token::Kind::Identifier).str; - } - - // Parse parameters - if (current().kind == Token::Kind::LParen) { - advance(); - while (current().kind != Token::Kind::RParen) { - call.parameters.push_back(parseExpression()); - if (current().kind != Token::Kind::RParen) { - expect(Token::Kind::Comma); - } - } - expect(Token::Kind::RParen); - } - - if (current().kind == Token::Kind::LBracket) { - error(current(), "Gate calls with designators are not supported yet."); - } - - // Parse operands - while (current().kind != Token::Kind::Semicolon) { - call.operands.push_back(parseGateOperand()); - if (current().kind != Token::Kind::Semicolon) { - expect(Token::Kind::Comma); - } - } - - expect(Token::Kind::Semicolon); - return call; - } - - ParsedModifier parseGateModifier() { - ParsedModifier mod; - mod.kind = current().kind; - switch (current().kind) { - case Token::Kind::Inv: - advance(); - return mod; - case Token::Kind::Pow: - advance(); - expect(Token::Kind::LParen); - mod.expression = parseExpression(); - expect(Token::Kind::RParen); - return mod; - case Token::Kind::Ctrl: - case Token::Kind::NegCtrl: - advance(); - if (current().kind == Token::Kind::LParen) { - advance(); - mod.expression = parseExpression(); - expect(Token::Kind::RParen); - } - return mod; - default: - llvm_unreachable("Unknown gate modifier"); - } - } - - ParsedOperand parseGateOperand() { - ParsedOperand operand; - if (current().kind == Token::Kind::HardwareQubit) { - operand.hardwareQubit = static_cast(current().val); - advance(); - return operand; - } - operand.name = expect(Token::Kind::Identifier).str; - if (current().kind == Token::Kind::LBracket) { - advance(); - operand.index = parseExpression(); - expect(Token::Kind::RBracket); - } - return operand; - } - - ParsedBitRef parseBitRef() { - ParsedBitRef ref; - ref.name = expect(Token::Kind::Identifier).str; - if (current().kind == Token::Kind::LBracket) { - advance(); - ref.index = parseExpression(); - expect(Token::Kind::RBracket); - } - return ref; - } - - /// Resolve and emit a @p gate against @p scope. - void emitGateCall(const ParsedGateCall& call, const QubitScope& scope) { - const auto& id = call.identifier; - const auto& debugInfo = call.debugInfo; - - // Resolve 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()) { - error(*debugInfo, "No OpenQASM definition found for gate '" + id + "'."); - } - - // Resolve parameters - SmallVector params; - params.reserve(call.parameters.size()); - for (const auto& arg : call.parameters) { - params.push_back(emitFloatExpression(arg, debugInfo)); - } - - // Resolve operands - SmallVector operands; - SmallVector> operandsBroadcasting; - for (const auto& operand : call.operands) { - const auto resolved = - resolveGateOperandInScope(operand, scope, debugInfo); - if (const auto* value = std::get_if(&resolved)) { - operands.push_back(*value); - } else { - operandsBroadcasting.push_back(std::get>(resolved)); - } - } - - if (!operandsBroadcasting.empty() && !operands.empty()) { - error(*debugInfo, "Gate operands must be single qubits or quantum " - "registers and not a mix of both."); - } - - // Handle broadcasted calls - if (!operandsBroadcasting.empty()) { - if (numCompatControls != 0) { - error(*debugInfo, "OpenQASM 2 gates cannot be broadcasted."); - } - if (std::holds_alternative(it->second)) { - error(*debugInfo, "Broadcasted compound gates are not supported yet."); - } - emitBroadcastedGateCall(std::get(it->second), id, resolvedId, - params, call.modifiers, operandsBroadcasting, - debugInfo); - return; - } - - // Handle non-broadcasted calls - const auto split = splitControlsAndTargets( - call.modifiers, numCompatControls, operands, debugInfo); - - // Inline compound gate - if (const auto* compound = std::get_if(&it->second)) { - emitCompoundGate(*compound, params, split.targets, split.posControls, - split.negControls, split.invert, debugInfo); - return; - } - - // Emit standard gate - const auto& gate = std::get(it->second); - const auto& gateFn = - resolveStandardGate(gate, id, resolvedId, params, debugInfo); - emitStandardGate(gateFn, params, split.targets, split.posControls, - split.negControls, split.invert); - } - - /// Emit a broadcasted gate call. - void emitBroadcastedGateCall(const GateInfo& gate, const std::string& fullId, - llvm::StringRef resolvedId, ValueRange params, - const std::vector& modifiers, - const SmallVector>& operands, - const std::shared_ptr& debugInfo) { - const auto broadcastWidth = operands.front().size(); - for (const auto& operand : operands) { - if (operand.size() != broadcastWidth) { - error(*debugInfo, - "All broadcasting operands must have the same width."); - } - } - - // OpenQASM 2 gates cannot be broadcasted - const auto split = splitControlsAndTargets>( - modifiers, /*numCompatControls=*/0, operands, debugInfo); - - const auto& gateFn = - resolveStandardGate(gate, fullId, resolvedId, params, debugInfo); - - for (size_t b = 0; b < broadcastWidth; ++b) { - const auto slice = [&](const SmallVector>& lists) { - SmallVector qubits; - qubits.reserve(lists.size()); - for (const auto& list : lists) { - qubits.push_back(list[b]); - } - return qubits; - }; - emitStandardGate(gateFn, params, slice(split.targets), - slice(split.posControls), slice(split.negControls), - split.invert); - } - } - - template struct ControlsAndTargets { - bool invert = false; - SmallVector posControls; - SmallVector negControls; - SmallVector targets; - }; - - /// Partition @p operands into controls and targets. - template - [[nodiscard]] ControlsAndTargets - splitControlsAndTargets(const std::vector& modifiers, - size_t numCompatControls, - const SmallVector& operands, - const std::shared_ptr& debugInfo) const { - ControlsAndTargets result; - size_t numControls = 0; - for (const auto& mod : modifiers) { - switch (mod.kind) { - case Token::Kind::Inv: - result.invert = !result.invert; - break; - case Token::Kind::Pow: - error(*debugInfo, "Power modifiers are not supported yet."); - case Token::Kind::Ctrl: { - const auto n = - evaluateNonNegativeConstant(mod.expression, debugInfo, 1); - for (size_t i = 0; i < n; ++i, ++numControls) { - if (numControls >= operands.size()) { - error(*debugInfo, "Control index out of bounds."); - } - result.posControls.push_back(operands[numControls]); - } - break; - } - case Token::Kind::NegCtrl: { - const auto n = - evaluateNonNegativeConstant(mod.expression, debugInfo, 1); - for (size_t i = 0; i < n; ++i, ++numControls) { - if (numControls >= operands.size()) { - error(*debugInfo, "Control index out of bounds."); - } - result.negControls.push_back(operands[numControls]); - } - break; - } - default: - llvm_unreachable("Unknown gate modifier"); - } - } - - // OpenQASM 2 compatibility: append implicit control qubits - for (size_t i = 0; i < numCompatControls; ++i, ++numControls) { - if (numControls >= operands.size()) { - error(*debugInfo, "Control index out of bounds."); - } - result.posControls.push_back(operands[numControls]); - } - - result.targets = llvm::to_vector(llvm::drop_begin(operands, numControls)); - return result; - } - - /// Look up the `QCProgramBuilder` emitter of a @p gate. - [[nodiscard]] static const GateFn& - resolveStandardGate(const GateInfo& gate, const std::string& fullId, - llvm::StringRef resolvedId, ValueRange params, - const std::shared_ptr& debugInfo) { - const auto dispIt = GATE_DISPATCH.find(resolvedId); - if (dispIt == GATE_DISPATCH.end()) { - error(*debugInfo, "No MLIR definition found for gate '" + fullId + "'."); - } - if (gate.nParameters != params.size()) { - error(*debugInfo, - "Invalid number of parameters for gate '" + fullId + "'."); - } - return dispIt->second; - } - - /// Emit a gate body wrapped in its modifiers. - 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); - } - } - - /// Emit a standard gate. - void emitStandardGate(const GateFn& gateFn, ValueRange params, - ValueRange targets, ValueRange posControls, - ValueRange negControls, bool invert) { - auto bodyFn = [&](ValueRange qubits) { gateFn(builder, qubits, params); }; - emitModifiedGate(bodyFn, targets, posControls, negControls, invert); - } - - /// Inline a compound gate. - void emitCompoundGate(const ParsedCompoundGate& gate, ValueRange 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 compound gate is inlined - // within a modifier region. - llvm::StringMap> targetsMap; - - for (const auto& [targetName, target] : - llvm::zip_equal(gate.targetNames, targets)) { - auto iter = llvm::find(targets, target); - if (iter == targets.end()) { - error(*debugInfo, "Target '" + targetName + "' not found in operands."); - } - const auto index = - static_cast(std::distance(targets.begin(), iter)); - targetsMap[targetName].push_back(index); - } - - // Bind parameters so they can be referenced by name inside the gate body. - parameterConstants.push(); - for (size_t i = 0; i < gate.parameterNames.size(); ++i) { - parameterConstants.emplace(gate.parameterNames[i], 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] = {.memref = nullptr, .qubits = std::move(args)}; - } - for (const auto& bodyCall : gate.body) { - emitGateCall(bodyCall, localScope); - } - }; - - emitModifiedGate(bodyFn, targets, posControls, negControls, invert); - - parameterConstants.pop(); - } - - //===--- Operand resolution -------------------------------------------===// - - [[nodiscard]] std::variant> - resolveGateOperand(const ParsedOperand& operand, - const std::shared_ptr& debugInfo) { - return resolveGateOperandInScope(operand, qubitRegisters, debugInfo); - } - - [[nodiscard]] std::variant> - resolveGateOperandInScope(const ParsedOperand& operand, - const QubitScope& scope, - const std::shared_ptr& debugInfo) { - if (operand.hardwareQubit) { - return builder.staticQubit(*operand.hardwareQubit); - } - - const auto& name = operand.name; - auto it = scope.find(name); - if (it == scope.end()) { - error(*debugInfo, "Unknown qubit register '" + name + "'."); - } - - const auto& binding = it->second; - - if (!operand.index) { - if (binding.qubits.size() == 1) { - return binding.qubits[0]; - } - // Return full register - return binding.qubits; - } - - const auto& indexExpr = *operand.index; - if (isConstantIndex(indexExpr)) { - const auto index = evaluateNonNegativeConstant(indexExpr, debugInfo); - if (index >= binding.qubits.size()) { - error(*debugInfo, "Qubit index out of bounds."); - } - return binding.qubits[index]; - } - - if (!binding.memref) { - error(*debugInfo, "Dynamic qubit indexing requires a qubit register."); - } - return loadDynamicElement(name, binding.memref, indexExpr, debugInfo); - } - - /// Whether @p expr can be folded to a constant at translation time. - static bool isConstantIndex(const ParsedExpr& expr) { - switch (expr.kind) { - case ParsedExpr::Kind::IntLiteral: - case ParsedExpr::Kind::FloatLiteral: - return true; - case ParsedExpr::Kind::Ident: - return false; - case ParsedExpr::Kind::Unary: - return isConstantIndex(expr.children[0]); - case ParsedExpr::Kind::Binary: - return isConstantIndex(expr.children[0]) && - isConstantIndex(expr.children[1]); - } - return false; - } - - /// Get a stable string representation of an index expression. - static std::string getIndexKey(const ParsedExpr& expr) { - switch (expr.kind) { - case ParsedExpr::Kind::IntLiteral: - return std::to_string(expr.intValue); - case ParsedExpr::Kind::FloatLiteral: - return std::to_string(expr.floatValue); - case ParsedExpr::Kind::Ident: - return expr.name; - case ParsedExpr::Kind::Unary: - return "(" + Token::kindToString(expr.op) + - getIndexKey(expr.children[0]) + ")"; - case ParsedExpr::Kind::Binary: - return "(" + getIndexKey(expr.children[0]) + - Token::kindToString(expr.op) + getIndexKey(expr.children[1]) + ")"; - } - return ""; - } - - /** - * @brief Load a qubit from @p memref at a runtime index - * - * @details - * The result within the current region is cached so that repeated references - * to the same element reuse a single load. - */ - [[nodiscard]] Value - loadDynamicElement(const std::string& name, Value memref, - const ParsedExpr& indexExpr, - const std::shared_ptr& debugInfo) { - const auto key = name + "[" + getIndexKey(indexExpr) + "]"; - if (const auto cached = dynamicallyLoadedQubits.find(key)) { - return *cached; - } - const auto index = emitIntegerExpression(indexExpr, debugInfo); - const auto loaded = builder.memrefLoad(memref, index); - dynamicallyLoadedQubits.emplace(key, loaded); - return loaded; - } - - //===--- Bit resolution -----------------------------------------------===// - - [[nodiscard]] SmallVector - resolveClassicalBits(const ParsedBitRef& operand, - const std::shared_ptr& debugInfo) const { - const auto& name = operand.name; - auto it = classicalRegisters.find(name); - if (it == classicalRegisters.end()) { - error(*debugInfo, "Unknown classical register '" + name + "'."); - } - - const auto& creg = it->second; - SmallVector bits; - - if (!operand.index) { - for (int64_t i = 0; i < creg.size; ++i) { - bits.push_back(creg[i]); - } - return bits; - } - - const auto index = evaluateNonNegativeConstant(*operand.index, debugInfo); - if (std::cmp_greater_equal(index, creg.size)) { - error(*debugInfo, "Classical bit index out of bounds."); - } - bits.push_back(creg[static_cast(index)]); - return bits; - } - - //===--- Expression parsing -------------------------------------------===// - - /// expr := term (('+' | '-') term)* - ParsedExpr parseExpression() { - auto x = parseTerm(); - while (current().kind == Token::Kind::Plus || - current().kind == Token::Kind::Minus) { - const auto op = current().kind; - advance(); - auto y = parseTerm(); - ParsedExpr binary; - binary.kind = ParsedExpr::Kind::Binary; - binary.op = op; - binary.children.push_back(std::move(x)); - binary.children.push_back(std::move(y)); - x = std::move(binary); - } - return x; - } - - /// term := unary (('*' | '/') unary)* - ParsedExpr parseTerm() { - auto x = parseUnary(); - while (current().kind == Token::Kind::Asterisk || - current().kind == Token::Kind::Slash) { - const auto op = current().kind; - advance(); - auto y = parseUnary(); - ParsedExpr binary; - binary.kind = ParsedExpr::Kind::Binary; - binary.op = op; - binary.children.push_back(std::move(x)); - binary.children.push_back(std::move(y)); - x = std::move(binary); - } - return x; - } - - /// unary := '-' unary | primary - ParsedExpr parseUnary() { - if (current().kind == Token::Kind::Minus) { - advance(); - ParsedExpr unary; - unary.kind = ParsedExpr::Kind::Unary; - unary.op = Token::Kind::Minus; - unary.children.push_back(parseUnary()); - return unary; - } - return parsePrimary(); - } - - /// primary := literal | ident | '(' expr ')' - ParsedExpr parsePrimary() { - ParsedExpr e; - switch (current().kind) { - case Token::Kind::FloatLiteral: { - const auto value = expect(Token::Kind::FloatLiteral); - e.kind = ParsedExpr::Kind::FloatLiteral; - e.floatValue = value.valReal; - return e; - } - case Token::Kind::IntegerLiteral: { - const auto value = expect(Token::Kind::IntegerLiteral); - e.kind = ParsedExpr::Kind::IntLiteral; - e.intValue = value.val; - return e; - } - case Token::Kind::Identifier: { - const auto value = expect(Token::Kind::Identifier); - e.kind = ParsedExpr::Kind::Ident; - e.name = value.str; - return e; - } - case Token::Kind::LParen: { - expect(Token::Kind::LParen); - auto inner = parseExpression(); - expect(Token::Kind::RParen); - return inner; - } - default: - error(current(), - "Expected expression, got '" + current().toString() + "'."); - } - } - - //===--- Expression evaluation ----------------------------------------===// - - /// Translate a `ParsedExpr` to an `f64`-typed MLIR value. - [[nodiscard]] Value - emitFloatExpression(const ParsedExpr& expr, - const std::shared_ptr& debugInfo) { - switch (expr.kind) { - case ParsedExpr::Kind::IntLiteral: - return arith::ConstantOp::create( - builder, - builder.getF64FloatAttr(static_cast(expr.intValue))) - .getResult(); - case ParsedExpr::Kind::FloatLiteral: - return arith::ConstantOp::create(builder, - builder.getF64FloatAttr(expr.floatValue)) - .getResult(); - case ParsedExpr::Kind::Ident: - return resolveParameterIdentifier(expr.name, debugInfo); - case ParsedExpr::Kind::Unary: { - const auto operand = emitFloatExpression(expr.children[0], debugInfo); - if (expr.op == Token::Kind::Minus) { - return arith::NegFOp::create(builder, operand).getResult(); - } - error(*debugInfo, - "Unsupported unary operator in gate parameter expression."); - } - case ParsedExpr::Kind::Binary: { - const auto lhs = emitFloatExpression(expr.children[0], debugInfo); - const auto rhs = emitFloatExpression(expr.children[1], debugInfo); - switch (expr.op) { - case Token::Kind::Plus: - return arith::AddFOp::create(builder, lhs, rhs).getResult(); - case Token::Kind::Minus: - return arith::SubFOp::create(builder, lhs, rhs).getResult(); - case Token::Kind::Asterisk: - return arith::MulFOp::create(builder, lhs, rhs).getResult(); - case Token::Kind::Slash: - return arith::DivFOp::create(builder, lhs, rhs).getResult(); - default: - error(*debugInfo, - "Unsupported binary operator in gate parameter expression."); - } - } - } - error(*debugInfo, "Unsupported gate parameter expression."); - } - - [[nodiscard]] Value - resolveParameterIdentifier(const std::string& name, - const std::shared_ptr& debugInfo) { - if (const auto value = parameterConstants.find(name)) { - return *value; - } - if (const auto value = lookupBuiltinConstant(name, builder)) { - return *value; - } - error(*debugInfo, "Unknown identifier '" + name + "'."); - } - - /// Translate a `ParsedExpr` to an `index`-typed MLIR value. - [[nodiscard]] Value - emitIntegerExpression(const ParsedExpr& expr, - const std::shared_ptr& debugInfo) { - switch (expr.kind) { - case ParsedExpr::Kind::IntLiteral: - return arith::ConstantOp::create(builder, - builder.getIndexAttr(expr.intValue)) - .getResult(); - case ParsedExpr::Kind::Unary: { - if (expr.op == Token::Kind::Minus) { - const auto operand = emitIntegerExpression(expr.children[0], debugInfo); - const auto zero = - arith::ConstantOp::create(builder, builder.getIndexAttr(0)) - .getResult(); - return arith::SubIOp::create(builder, zero, operand).getResult(); - } - error(*debugInfo, "Unsupported unary operator in integer expression."); - } - case ParsedExpr::Kind::Binary: { - const auto lhs = emitIntegerExpression(expr.children[0], debugInfo); - const auto rhs = emitIntegerExpression(expr.children[1], debugInfo); - switch (expr.op) { - case Token::Kind::Plus: - return arith::AddIOp::create(builder, lhs, rhs).getResult(); - case Token::Kind::Minus: - return arith::SubIOp::create(builder, lhs, rhs).getResult(); - case Token::Kind::Asterisk: - return arith::MulIOp::create(builder, lhs, rhs).getResult(); - case Token::Kind::Slash: - return arith::DivSIOp::create(builder, lhs, rhs).getResult(); - default: - error(*debugInfo, "Unsupported binary operator in integer expression."); - } - } - case ParsedExpr::Kind::Ident: - if (const auto iv = loopVariables.find(expr.name)) { - return *iv; - } - error(*debugInfo, "Expected an integer expression."); - case ParsedExpr::Kind::FloatLiteral: - default: - error(*debugInfo, "Expected an integer expression."); - } - } - - /// Statically evaluate `ParsedExpr` to an `int64_t`. - [[nodiscard]] int64_t - evaluateIntegerConstant(const ParsedExpr& expr, - const std::shared_ptr& debugInfo) const { - switch (expr.kind) { - case ParsedExpr::Kind::IntLiteral: - return expr.intValue; - case ParsedExpr::Kind::Unary: - if (expr.op == Token::Kind::Minus) { - return -evaluateIntegerConstant(expr.children[0], debugInfo); - } - error(*debugInfo, "Expected a constant integer expression."); - case ParsedExpr::Kind::Binary: { - const auto lhs = evaluateIntegerConstant(expr.children[0], debugInfo); - const auto rhs = evaluateIntegerConstant(expr.children[1], debugInfo); - switch (expr.op) { - case Token::Kind::Plus: - return lhs + rhs; - case Token::Kind::Minus: - return lhs - rhs; - case Token::Kind::Asterisk: - return lhs * rhs; - case Token::Kind::Slash: - if (rhs == 0) { - error(*debugInfo, "Division by zero in constant expression."); - } - return lhs / rhs; - default: - error(*debugInfo, "Expected a constant integer expression."); - } - } - case ParsedExpr::Kind::FloatLiteral: - case ParsedExpr::Kind::Ident: - default: - error(*debugInfo, "Expected a constant integer expression."); - } - } - - /// Statically evaluate `ParsedExpr` to an `size_t`. - [[nodiscard]] size_t evaluateNonNegativeConstant( - const ParsedExpr& expr, - const std::shared_ptr& debugInfo) const { - return static_cast(evaluateIntegerConstant(expr, debugInfo)); - } - - /// Statically evaluate `ParsedExpr` to an `size_t`, using @p defaultValue - /// when the expression is absent. - [[nodiscard]] size_t - evaluateNonNegativeConstant(const std::optional& expr, - const std::shared_ptr& debugInfo, - size_t defaultValue) const { - if (!expr) { - return defaultValue; - } - return evaluateNonNegativeConstant(*expr, debugInfo); - } -}; - -} // namespace - -namespace detail { - -OwningOpRef parseQASM3(std::string_view source, - MLIRContext* context) { - QASM3Parser parser(context); - return parser.parse(source); -} - -} // namespace detail - -} // namespace mlir::qc diff --git a/mlir/lib/Dialect/QC/Translation/QASM3Parser.h b/mlir/lib/Dialect/QC/Translation/QASM3Parser.h deleted file mode 100644 index a35f27387a..0000000000 --- a/mlir/lib/Dialect/QC/Translation/QASM3Parser.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 - -namespace mlir { - -// Forward declarations -class MLIRContext; -class ModuleOp; - -namespace qc::detail { - -/** - * @brief Parse an OpenQASM 3 program and emit a QC program. - * - * @details - * Implementation detail of `translateQASM3ToQC`; not part of the public API. - * Prefer the `translateQASM3ToQC` entry points. - * - * @param source String containing the OpenQASM3 program. - * @param context The MLIRContext to create the module in. - * @return A module containing the QC program. - */ -[[nodiscard]] OwningOpRef parseQASM3(std::string_view source, - MLIRContext* context); - -} // namespace qc::detail - -} // namespace mlir diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp index 95f9ebece4..0f204e3f91 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp @@ -10,17 +10,15 @@ #include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h" -#include "QASM3Parser.h" +#include "mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h" #include #include -#include #include +#include #include #include -#include -#include #include namespace mlir::qc { @@ -31,15 +29,10 @@ namespace mlir::qc { OwningOpRef translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context) { - try { - const auto buffer = - sourceMgr.getMemoryBuffer(sourceMgr.getMainFileID())->getBuffer(); - return detail::parseQASM3(std::string_view(buffer.data(), buffer.size()), - context); - } catch (const std::exception& e) { - llvm::errs() << "Import error: " << e.what() << "\n"; - return nullptr; - } + // Route diagnostics through the source manager so errors carry source + // locations and snippets. + const SourceMgrDiagnosticHandler handler(sourceMgr, context); + return detail::importQASM3(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..89e342d425 --- /dev/null +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -0,0 +1,1201 @@ +/* + * 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/Definitions.hpp" +#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 + +namespace mlir::qc { + +namespace { + +using qasm3::GateInfo; + +using detail::Condition; +using detail::Expr; +using detail::GateCall; +using detail::Lexer; +using detail::Modifier; +using detail::Operand; +using detail::Parser; +using detail::Reference; + +/// Signature: (builder, gate operands, gate parameters). +using GateFn = std::function; + +/// A stored compound-gate definition. Array members are bump-allocated by the +/// parser and remain valid for the duration of the import. +struct StoredGate { + llvm::ArrayRef parameters; + llvm::ArrayRef targets; + llvm::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 null and + * `qubits` holds the aliased values. + */ +struct QubitBinding { + Value memref; + SmallVector qubits; +}; + +using QubitScope = llvm::StringMap; + +/// Look up a built-in numeric constant and emit it as an `f64`-typed value. +std::optional lookupBuiltinConstant(llvm::StringRef name, + QCProgramBuilder& builder) { + auto constant = [&](double value) -> Value { + return arith::ConstantOp::create(builder, builder.getF64FloatAttr(value)) + .getResult(); + }; + if (name == "pi" || name == "π") { + return constant(::qc::PI); + } + if (name == "tau" || name == "τ") { + return constant(::qc::TAU); + } + if (name == "euler" || name == "ℇ") { + return constant(::qc::E); + } + return std::nullopt; +} + +/// Build the table mapping each gate identifier to a `QCProgramBuilder` +/// emitter. +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; +} + +/// Map from gate identifier to `QCProgramBuilder` emitter. +const llvm::StringMap GATE_DISPATCH = buildGateDispatch(); + +/// Build the table mapping a gate identifier to its metadata. +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}); + } + + const 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; +} + +//===----------------------------------------------------------------------===// +// QCEmitter +//===----------------------------------------------------------------------===// + +/** + * @brief Lowers OpenQASM 3 parse events to QC-dialect operations. + * + * @details + * Models the sink concept consumed by `Parser`. All events emit eagerly via the + * `QCProgramBuilder` and report errors as diagnostics through `LogicalResult`; + * name resolution, semantic validation, and target-specific restrictions are + * handled here. + */ +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(llvm::SMLoc loc, const Twine& message) { + return emitError(translate(loc), message); + } + + //===--- Program events -----------------------------------------------===// + + LogicalResult version(double /*version*/) { + // The version declaration has no effect on translation. + return success(); + } + + LogicalResult include(llvm::SMLoc loc, llvm::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(); + } + + LogicalResult constDecl(llvm::SMLoc loc, llvm::StringRef id, + const Expr& value) { + if (failed(declare(loc, id))) { + return failure(); + } + auto emitted = emitAngle(value); + if (failed(emitted)) { + return failure(); + } + parameterConstants.insert(id, *emitted); + return success(); + } + + LogicalResult qubitRegister(llvm::SMLoc loc, llvm::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 reg = builder.allocQubitRegister(*count); + qubitRegisters[id] = {.memref = reg.value, .qubits = reg.qubits}; + } else { + qubitRegisters[id] = {.memref = nullptr, + .qubits = {builder.allocQubit()}}; + } + return success(); + } + + LogicalResult classicalRegister(llvm::SMLoc loc, llvm::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(); + } + + //===--- Measurement, reset, barrier ----------------------------------===// + + LogicalResult measure(llvm::SMLoc loc, const Reference& 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 (std::holds_alternative(*resolved)) { + qubits.push_back(std::get(*resolved)); + } 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] : 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& registerBits = bitValues[bit.registerName]; + const auto index = static_cast(bit.registerIndex); + if (registerBits.size() <= index) { + registerBits.resize(index + 1); + } + registerBits[index] = result; + } + return success(); + } + + LogicalResult reset(llvm::SMLoc /*loc*/, const Operand& operand) { + auto resolved = resolveOperand(operand, qubitRegisters); + if (failed(resolved)) { + return failure(); + } + if (std::holds_alternative(*resolved)) { + builder.reset(std::get(*resolved)); + } else { + for (auto qubit : std::get>(*resolved)) { + builder.reset(qubit); + } + } + return success(); + } + + LogicalResult barrier(llvm::SMLoc /*loc*/, llvm::ArrayRef operands) { + SmallVector qubits; + for (const auto& operand : operands) { + auto resolved = resolveOperand(operand, qubitRegisters); + if (failed(resolved)) { + return failure(); + } + if (std::holds_alternative(*resolved)) { + qubits.push_back(std::get(*resolved)); + } else { + llvm::append_range(qubits, std::get>(*resolved)); + } + } + builder.barrier(qubits); + return success(); + } + + //===--- Gate definitions and calls -----------------------------------===// + + LogicalResult gateDefinition(llvm::SMLoc loc, llvm::StringRef id, + llvm::ArrayRef parameters, + llvm::ArrayRef targets, + llvm::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 conditionOnly(llvm::SMLoc /*loc*/, const Condition& condition) { + return LogicalResult{emitCondition(condition)}; + } + + struct IfScope { + scf::IfOp op; + OpBuilder::InsertPoint savedInsertionPoint; + }; + + FailureOr ifBegin(llvm::SMLoc /*loc*/, const Condition& condition, + bool invert) { + auto value = emitCondition(condition); + if (failed(value)) { + return failure(); + } + Value condValue = *value; + if (invert) { + auto trueValue = builder.boolConstant(true); + condValue = + arith::XOrIOp::create(builder, condValue, trueValue).getResult(); + } + auto ifOp = scf::IfOp::create(builder, condValue, /*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(llvm::SMLoc loc, llvm::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(); + Value inclusiveStop = + arith::AddIOp::create(builder, *stopValue, one).getResult(); + + ValueScope loopScope(loopVariables); + ValueScope loadScope(dynamicallyLoadedQubits); + + LogicalResult status = success(); + builder.scfFor(*startValue, inclusiveStop, *stepValue, [&](Value iv) { + loopVariables.insert(variable, iv); + if (succeeded(status)) { + status = body(); + } + }); + return status; + } + + LogicalResult whileStmt(llvm::SMLoc /*loc*/, const Condition& condition, + function_ref body) { + LogicalResult status = success(); + builder.scfWhile( + [&] { + ValueScope loadScope(dynamicallyLoadedQubits); + auto value = emitCondition(condition); + if (failed(value)) { + status = failure(); + builder.scfCondition(builder.boolConstant(false)); + } else { + builder.scfCondition(*value); + } + }, + [&] { + ValueScope loadScope(dynamicallyLoadedQubits); + if (succeeded(status)) { + status = body(); + } + }); + return status; + } + +private: + using ScopedValueTable = llvm::ScopedHashTable; + using ValueScope = llvm::ScopedHashTableScope; + + //===--- Locations ----------------------------------------------------===// + + Location translate(llvm::SMLoc loc) { + if (!loc.isValid()) { + return UnknownLoc::get(builder.getContext()); + } + const auto [line, col] = sourceMgr.getLineAndColumn(loc); + return FileLineColLoc::get(builder.getStringAttr(""), line, col); + } + + //===--- Symbol declarations ------------------------------------------===// + + LogicalResult declare(llvm::SMLoc loc, llvm::StringRef id) { + if (!declaredNames.insert(id).second) { + return error(loc, "identifier '" + id + "' already declared"); + } + return success(); + } + + //===--- Conditions ---------------------------------------------------===// + + FailureOr emitCondition(const Condition& condition) { + switch (condition.kind) { + case Condition::Kind::Measurement: { + auto resolved = resolveOperand(condition.operand, qubitRegisters); + if (failed(resolved)) { + return failure(); + } + if (!std::holds_alternative(*resolved)) { + return error(condition.loc, + "measurement condition must be a single qubit"); + } + return builder.measure(std::get(*resolved)); + } + case Condition::Kind::NegatedBit: { + auto value = lookupBitValue(condition.bit); + if (failed(value)) { + return failure(); + } + auto trueValue = builder.boolConstant(true); + return arith::XOrIOp::create(builder, *value, trueValue).getResult(); + } + case Condition::Kind::Bit: + return lookupBitValue(condition.bit); + } + llvm_unreachable("unknown condition kind"); + } + + FailureOr lookupBitValue(const Reference& 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]; + } + + //===--- Gate calls ---------------------------------------------------===// + + 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(llvm::ArrayRef modifiers, + const SmallVector& operands, llvm::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 = llvm::to_vector(llvm::drop_begin(operands, numControls)); + return result; + } + + FailureOr resolveStandardGate(const GateInfo& gate, + llvm::StringRef id, + ValueRange parameters, + llvm::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); + } + + LogicalResult emitCompoundGate(const StoredGate& gate, ValueRange parameters, + ValueRange targets, ValueRange posControls, + ValueRange negControls, bool invert, + llvm::SMLoc loc) { + if (gate.parameters.size() != parameters.size() || + gate.targets.size() != targets.size()) { + return error(loc, "invalid number of arguments 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] : + llvm::zip_equal(gate.targets, targets)) { + auto iter = llvm::find(targets, target); + if (iter == targets.end()) { + return error(loc, "target '" + targetName + "' not found in operands"); + } + targetsMap[targetName].push_back( + static_cast(std::distance(targets.begin(), iter))); + } + + ValueScope parameterScope(parameterConstants); + for (const auto& [name, value] : + llvm::zip_equal(gate.parameters, parameters)) { + parameterConstants.insert(name, value); + } + + LogicalResult 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; + } + + LogicalResult emitBroadcastedGateCall( + const GateInfo& gate, llvm::StringRef id, ValueRange parameters, + llvm::ArrayRef modifiers, + const SmallVector>& operands, llvm::SMLoc loc) { + const auto broadcastWidth = operands.front().size(); + for (const auto& operand : operands) { + if (operand.size() != broadcastWidth) { + return error(loc, "all broadcasting operands must have the same width"); + } + } + + auto split = + splitControlsAndTargets>(modifiers, operands, loc); + if (failed(split)) { + return failure(); + } + auto gateFn = resolveStandardGate(gate, id, parameters, loc); + if (failed(gateFn)) { + return failure(); + } + + for (size_t b = 0; b < broadcastWidth; ++b) { + const auto slice = [&](const SmallVector>& lists) { + SmallVector qubits; + qubits.reserve(lists.size()); + for (const auto& list : lists) { + qubits.push_back(list[b]); + } + return qubits; + }; + emitStandardGate(**gateFn, parameters, slice(split->targets), + slice(split->posControls), slice(split->negControls), + split->invert); + } + return success(); + } + + 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 + "'"); + } + + 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); + } + + SmallVector operands; + SmallVector> broadcasting; + for (const auto& operand : call.operands) { + auto resolved = resolveOperand(operand, scope); + if (failed(resolved)) { + return failure(); + } + if (const auto* value = std::get_if(&*resolved)) { + operands.push_back(*value); + } else { + broadcasting.push_back(std::get>(*resolved)); + } + } + + if (!broadcasting.empty() && !operands.empty()) { + return error(call.loc, "gate operands must be single qubits or quantum " + "registers and not a mix of both"); + } + + if (!broadcasting.empty()) { + if (std::holds_alternative(it->second)) { + return error(call.loc, + "broadcasted compound gates are not supported yet"); + } + return emitBroadcastedGateCall(std::get(it->second), + call.identifier, parameters, + call.modifiers, broadcasting, call.loc); + } + + auto split = + splitControlsAndTargets(call.modifiers, operands, call.loc); + if (failed(split)) { + return failure(); + } + + if (const auto* compound = std::get_if(&it->second)) { + return emitCompoundGate(*compound, parameters, split->targets, + split->posControls, split->negControls, + split->invert, call.loc); + } + + const auto& gate = std::get(it->second); + auto gateFn = + resolveStandardGate(gate, call.identifier, parameters, call.loc); + if (failed(gateFn)) { + return failure(); + } + emitStandardGate(**gateFn, parameters, split->targets, split->posControls, + split->negControls, split->invert); + return success(); + } + + //===--- 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& binding = it->second; + + if (operand.index == nullptr) { + if (binding.qubits.size() == 1) { + return std::variant>{binding.qubits[0]}; + } + return std::variant>{binding.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 >= binding.qubits.size()) { + return error(operand.loc, "qubit index out of bounds"); + } + return std::variant>{binding.qubits[i]}; + } + + if (!binding.memref) { + return error(operand.loc, + "dynamic qubit indexing requires a qubit register"); + } + auto loaded = + loadDynamicElement(operand.identifier, binding.memref, indexExpr); + if (failed(loaded)) { + return failure(); + } + return std::variant>{*loaded}; + } + + static bool isConstantIndex(const Expr& expr) { + switch (expr.kind) { + case Expr::Kind::Int: + case Expr::Kind::Float: + return true; + case Expr::Kind::Ident: + return false; + 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::Ident: + return expr.ident.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(llvm::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(); + } + Value loaded = builder.memrefLoad(memref, *index); + dynamicallyLoadedQubits.insert(keySaver.save(key), loaded); + return loaded; + } + + FailureOr> + resolveClassicalBits(const Reference& 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; + } + + //===--- Expression lowering ------------------------------------------===// + + 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::Ident: + return resolveParameter(expr.ident, 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(llvm::StringRef name, llvm::SMLoc loc) { + if (Value value = parameterConstants.lookup(name)) { + return value; + } + 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::Ident: + if (Value value = loopVariables.lookup(expr.ident)) { + return value; + } + 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::Float: + case Expr::Kind::Ident: + return error(expr.loc, "expected a constant integer expression"); + } + llvm_unreachable("unknown expression kind"); + } + + //===--- State --------------------------------------------------------===// + + QCProgramBuilder builder; + llvm::SourceMgr& sourceMgr; + + llvm::StringSet<> declaredNames; + + ScopedValueTable parameterConstants; + ValueScope parameterConstantsScope{parameterConstants}; + ScopedValueTable loopVariables; + ValueScope loopVariablesScope{loopVariables}; + ScopedValueTable dynamicallyLoadedQubits; + ValueScope dynamicallyLoadedQubitsScope{dynamicallyLoadedQubits}; + + llvm::BumpPtrAllocator keyStorage; + llvm::StringSaver keySaver{keyStorage}; + + QubitScope qubitRegisters; + llvm::StringMap classicalRegisters; + llvm::StringMap> bitValues; + llvm::StringMap> gates; +}; + +} // namespace + +namespace detail { + +OwningOpRef importQASM3(llvm::SourceMgr& sourceMgr, + MLIRContext* context) { + const auto bufferId = sourceMgr.getMainFileID(); + const llvm::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 module = emitter.finalize(); + if (failed(status)) { + return nullptr; + } + return module; +} + +} // 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..a8670f5e39 --- /dev/null +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp @@ -0,0 +1,338 @@ +/* + * 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 + +namespace mlir::qc::detail { + +namespace { + +[[nodiscard]] bool isIdentifierStart(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || + static_cast(c) >= 0x80; // allow UTF-8 (e.g. π, τ, ℇ) +} + +[[nodiscard]] bool isIdentifierContinue(char c) { + return isIdentifierStart(c) || (c >= '0' && c <= '9'); +} + +[[nodiscard]] bool isDigit(char c) { return c >= '0' && c <= '9'; } + +[[nodiscard]] TokenKind keywordKind(llvm::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) + .Default(TokenKind::Identifier); +} + +} // namespace + +llvm::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() && isIdentifierContinue(*cur)) { + ++cur; + } + const llvm::StringRef text(start, static_cast(cur - start)); + Token token; + token.kind = keywordKind(text); + token.spelling = text; + token.loc = llvm::SMLoc::getFromPointer(start); + 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 llvm::StringRef text(start, static_cast(cur - start)); + Token token; + token.loc = llvm::SMLoc::getFromPointer(start); + token.spelling = text; + 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 = llvm::SMLoc::getFromPointer(start); + if (atEnd()) { + token.kind = TokenKind::Error; + return token; + } + token.kind = TokenKind::StringLiteral; + token.spelling = + llvm::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 = llvm::SMLoc::getFromPointer(start); + const llvm::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 = llvm::SMLoc::getFromPointer(cur); + if (atEnd()) { + token.kind = TokenKind::Eof; + return token; + } + + const char* start = cur; + const char c = *cur; + + if (isIdentifierStart(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 '%': + case '&': + case '|': + case '^': + if (peek('=')) { + return twoChar(TokenKind::CompoundAssign); + } + break; + default: + break; + } + + return single(TokenKind::Error); +} + +} // namespace mlir::qc::detail diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index 49cbfbc6c2..e7649482da 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -80,11 +80,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]); @@ -234,9 +229,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", diff --git a/mlir/unittests/programs/qasm_programs.cpp b/mlir/unittests/programs/qasm_programs.cpp index d70b765637..c0bb864740 100644 --- a/mlir/unittests/programs/qasm_programs.cpp +++ b/mlir/unittests/programs/qasm_programs.cpp @@ -155,12 +155,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; diff --git a/mlir/unittests/programs/qasm_programs.h b/mlir/unittests/programs/qasm_programs.h index b46ba71729..27f4c09a7a 100644 --- a/mlir/unittests/programs/qasm_programs.h +++ b/mlir/unittests/programs/qasm_programs.h @@ -78,9 +78,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; From 5b1af5f030e89ad29a3fcf6bbe6a0c713dcc76ab Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 01:16:21 +0200 Subject: [PATCH 10/24] Generalize conditions --- .../Dialect/QC/Translation/qasm3/QASM3Lexer.h | 2 + .../QC/Translation/qasm3/QASM3Parser.h | 163 +++++++++++++----- .../QC/Translation/qasm3/QASM3Emitter.cpp | 20 ++- .../QC/Translation/qasm3/QASM3Lexer.cpp | 16 +- 4 files changed, 156 insertions(+), 45 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h index 415ce523c0..5c9b65c658 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h @@ -81,6 +81,8 @@ enum class TokenKind : uint8_t { Slash, Tilde, ExclamationPoint, + AmpAmp, // `&&` + PipePipe, // `||` CompoundAssign, // any `=` such as `+=`, `-=`, ... // Comparisons diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h index 3f38586683..ef67f7d042 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h @@ -84,12 +84,16 @@ struct GateCall { llvm::SMLoc loc; }; -/// A branch condition. Only the restricted forms below are supported. +/// A branch condition: a boolean-valued expression. Bump-allocated; children +/// are borrowed pointers. Register comparisons (e.g. `c == 5`) are not yet +/// supported. struct Condition { - enum class Kind : uint8_t { Measurement, Bit, NegatedBit }; + enum class Kind : uint8_t { Measurement, Bit, Not, And, Or }; Kind kind = Kind::Bit; - Operand operand; ///< For `Measurement`. - Reference bit; ///< For `Bit`/`NegatedBit`. + Operand operand; ///< For `Measurement`. + Reference bit; ///< For `Bit`. + const Condition* lhs = nullptr; ///< For `Not`, `And`, and `Or`. + const Condition* rhs = nullptr; ///< For `And` and `Or`. llvm::SMLoc loc; }; @@ -200,6 +204,10 @@ class Parser { return new (allocator.Allocate()) Expr(); } + [[nodiscard]] Condition* makeCondition() { + return new (allocator.Allocate()) Condition(); + } + template [[nodiscard]] llvm::ArrayRef copyToArena(llvm::ArrayRef values) { if (values.empty()) { @@ -543,6 +551,7 @@ class Parser { if (failed(condition)) { return failure(); } + const Condition& cond = **condition; if (failed(expect(TokenKind::RParen))) { return failure(); } @@ -553,10 +562,10 @@ class Parser { advance(); // { advance(); // } if (current().kind != TokenKind::Else) { - return sink.conditionOnly(loc, *condition); + return sink.conditionOnly(loc, cond); } advance(); // else - auto scope = sink.ifBegin(loc, *condition, /*invert=*/true); + auto scope = sink.ifBegin(loc, cond, /*invert=*/true); if (failed(scope)) { return failure(); } @@ -566,7 +575,7 @@ class Parser { return sink.ifEnd(*scope, /*hadElse=*/false); } - auto scope = sink.ifBegin(loc, *condition, /*invert=*/false); + auto scope = sink.ifBegin(loc, cond, /*invert=*/false); if (failed(scope)) { return failure(); } @@ -657,67 +666,141 @@ class Parser { if (failed(condition)) { return failure(); } + const Condition& cond = **condition; if (failed(expect(TokenKind::RParen))) { return failure(); } - return sink.whileStmt(loc, *condition, [this] { return parseBlock(); }); + return sink.whileStmt(loc, cond, [this] { return parseBlock(); }); } - [[nodiscard]] mlir::FailureOr parseCondition() { - Condition condition; - condition.loc = current().loc; + /// cond := andCond ('||' andCond)* + [[nodiscard]] mlir::FailureOr parseCondition() { + auto lhs = parseAndCondition(); + if (failed(lhs)) { + return failure(); + } + const Condition* 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]] mlir::FailureOr parseAndCondition() { + auto lhs = parseUnaryCondition(); + if (failed(lhs)) { + return failure(); + } + const Condition* 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]] mlir::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->kind = Condition::Kind::Not; + condition->loc = loc; + condition->lhs = *operand; + return condition; + } + return parsePrimaryCondition(); + } + + /// primaryCond := 'measure' gateOperand | '(' cond ')' | reference + [[nodiscard]] mlir::FailureOr parsePrimaryCondition() { + const auto loc = current().loc; if (current().kind == TokenKind::Measure) { advance(); - condition.kind = Condition::Kind::Measurement; auto operand = parseGateOperand(); if (failed(operand)) { return failure(); } - condition.operand = *operand; - return condition; + auto* condition = makeCondition(); + condition->kind = Condition::Kind::Measurement; + condition->operand = *operand; + condition->loc = loc; + return finishPrimaryCondition(condition); } - if (current().kind == TokenKind::ExclamationPoint || - current().kind == TokenKind::Tilde) { + if (current().kind == TokenKind::LParen) { advance(); - if (current().kind != TokenKind::Identifier) { - return sink.error(current().loc, - "unary expression has unsupported operand"); + auto inner = parseCondition(); + if (failed(inner)) { + return failure(); } - condition.kind = Condition::Kind::NegatedBit; - auto bit = parseReference(); - if (failed(bit)) { + if (failed(expect(TokenKind::RParen))) { return failure(); } - condition.bit = *bit; - return condition; + return finishPrimaryCondition(*inner); } if (current().kind == TokenKind::Identifier) { - condition.kind = Condition::Kind::Bit; auto bit = parseReference(); if (failed(bit)) { return failure(); } - condition.bit = *bit; - switch (current().kind) { - case TokenKind::EqualsEquals: - case TokenKind::NotEquals: - case TokenKind::Less: - case TokenKind::LessEquals: - case TokenKind::Greater: - case TokenKind::GreaterEquals: - return sink.error(condition.loc, - "register comparisons are not supported"); - default: - break; - } + auto* condition = makeCondition(); + condition->kind = Condition::Kind::Bit; + condition->bit = *bit; + condition->loc = loc; + return finishPrimaryCondition(condition); + } + + return sink.error(loc, "unsupported condition expression"); + } + + /// Reject a register comparison (e.g. `c == 5`) trailing a primary. + [[nodiscard]] mlir::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; } + } - return sink.error(condition.loc, - "unsupported condition expression in if statement"); + [[nodiscard]] Condition* makeBinaryCondition(const Condition::Kind kind, + const Condition* lhs, + const Condition* rhs, + const llvm::SMLoc loc) { + auto* condition = makeCondition(); + condition->kind = kind; + condition->loc = loc; + condition->lhs = lhs; + condition->rhs = rhs; + return condition; } //===--- Gate definitions and calls -----------------------------------===// diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 89e342d425..73dc18b56e 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -576,16 +576,28 @@ class QCEmitter { } return builder.measure(std::get(*resolved)); } - case Condition::Kind::NegatedBit: { - auto value = lookupBitValue(condition.bit); + case Condition::Kind::Bit: + return lookupBitValue(condition.bit); + 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::Bit: - return lookupBitValue(condition.bit); + 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"); } diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp index a8670f5e39..d06aec06be 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp @@ -320,9 +320,23 @@ Token Lexer::next() { case '/': return peek('=') ? twoChar(TokenKind::CompoundAssign) : single(TokenKind::Slash); - case '%': 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); From 7069858bb59d3e0049f12cd356436b35f3e6f904 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 01:35:58 +0200 Subject: [PATCH 11/24] Generalize declarations --- .../Dialect/QC/Translation/qasm3/QASM3Lexer.h | 2 + .../QC/Translation/qasm3/QASM3Parser.h | 148 ++++++++++++------ .../QC/Translation/qasm3/QASM3Emitter.cpp | 75 ++++++++- .../QC/Translation/qasm3/QASM3Lexer.cpp | 2 + 4 files changed, 175 insertions(+), 52 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h index 5c9b65c658..13428c97c1 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h @@ -52,6 +52,8 @@ enum class TokenKind : uint8_t { Float, Angle, Duration, + True, + False, // Identifiers and literals Identifier, diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h index ef67f7d042..f5486b88a8 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h @@ -88,15 +88,22 @@ struct GateCall { /// are borrowed pointers. Register comparisons (e.g. `c == 5`) are not yet /// supported. struct Condition { - enum class Kind : uint8_t { Measurement, Bit, Not, And, Or }; + enum class Kind : uint8_t { Measurement, Bit, Literal, Not, And, Or }; Kind kind = Kind::Bit; Operand operand; ///< For `Measurement`. Reference 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`. llvm::SMLoc loc; }; +/// A supported arithmetic scalar declaration type (`bool` is handled +/// separately, as its initializer is a boolean condition). `const` and +/// non-`const` declarations are lowered identically, so constness is not +/// tracked here. +enum class ScalarType : uint8_t { Float, Int }; + //===----------------------------------------------------------------------===// // Sink concept //===----------------------------------------------------------------------===// @@ -115,33 +122,33 @@ struct Condition { * materialized. */ template -concept QASMSink = - requires(S s, llvm::SMLoc loc, llvm::StringRef str, const Expr& expr, - const Operand& operand, const Reference& reference, - const Condition& condition, const GateCall& call, - llvm::ArrayRef operands, - llvm::ArrayRef names, - llvm::ArrayRef body, - llvm::function_ref cont, double d, bool flag) { - s.error(loc, str); - s.version(d); - s.include(loc, str); - s.constDecl(loc, str, expr); - 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.conditionOnly(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. - }; +concept QASMSink = requires( + S s, llvm::SMLoc loc, llvm::StringRef str, const Expr& expr, + const Operand& operand, const Reference& reference, + const Condition& condition, const GateCall& call, + llvm::ArrayRef operands, llvm::ArrayRef names, + llvm::ArrayRef body, llvm::function_ref cont, + double d, bool flag, ScalarType scalarType) { + s.error(loc, str); + s.version(d); + s.include(loc, str); + s.scalarDecl(loc, scalarType, str, expr); + s.boolDecl(loc, str, condition); + 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.conditionOnly(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 @@ -227,7 +234,16 @@ class Parser { case TokenKind::Include: return parseInclude(); case TokenKind::Const: - return parseConstDecl(); + 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: @@ -236,13 +252,6 @@ class Parser { return parseOldStyleDecl(/*classical=*/true); case TokenKind::Bit: return parseClassicalDecl(); - case TokenKind::Int: - case TokenKind::Uint: - case TokenKind::Bool: - case TokenKind::Float: - case TokenKind::Angle: - case TokenKind::Duration: - return sink.error(current().loc, "declaration type is not supported yet"); case TokenKind::Gate: return parseGateDefinition(); case TokenKind::Opaque: @@ -331,20 +340,61 @@ class Parser { //===--- Declarations -------------------------------------------------===// - [[nodiscard]] LogicalResult parseConstDecl() { + /// Parse `[const] (int|uint|float|bool) = ;`. `const` and + /// non-`const` declarations are parsed and lowered identically. `bool` + /// initializers are boolean conditions; all others are arithmetic. + [[nodiscard]] LogicalResult parseScalarDeclaration() { const auto loc = current().loc; - advance(); // const - if (current().kind != TokenKind::Float || - peek().kind != TokenKind::Identifier) { - return sink.error(loc, "only `const float = ;` " - "declarations are supported for now"); + if (current().kind == TokenKind::Const) { + advance(); // const + } + + ScalarType scalarType = ScalarType::Float; + bool isBool = false; + switch (current().kind) { + case TokenKind::Float: + scalarType = ScalarType::Float; + break; + case TokenKind::Int: + case TokenKind::Uint: + scalarType = ScalarType::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"); } - advance(); // float const auto id = current().spelling; advance(); + + // Only initialized declarations are supported, since there is no classical + // assignment to give an uninitialized variable a value later. if (failed(expect(TokenKind::Equals))) { return failure(); } + + if (isBool) { + auto condition = parseCondition(); + if (failed(condition)) { + return failure(); + } + if (failed(expect(TokenKind::Semicolon))) { + return failure(); + } + return sink.boolDecl(loc, id, **condition); + } + auto value = parseExpression(); if (failed(value)) { return failure(); @@ -352,7 +402,7 @@ class Parser { if (failed(expect(TokenKind::Semicolon))) { return failure(); } - return sink.constDecl(loc, id, **value); + return sink.scalarDecl(loc, scalarType, id, **value); } [[nodiscard]] LogicalResult parseQuantumDecl() { @@ -734,6 +784,16 @@ class Parser { [[nodiscard]] mlir::FailureOr parsePrimaryCondition() { const auto loc = current().loc; + if (current().kind == TokenKind::True || + current().kind == TokenKind::False) { + auto* condition = makeCondition(); + condition->kind = Condition::Kind::Literal; + condition->literalValue = current().kind == TokenKind::True; + condition->loc = loc; + advance(); + return finishPrimaryCondition(condition); + } + if (current().kind == TokenKind::Measure) { advance(); auto operand = parseGateOperand(); diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 73dc18b56e..13cc3d3d39 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -283,16 +283,42 @@ class QCEmitter { return success(); } - LogicalResult constDecl(llvm::SMLoc loc, llvm::StringRef id, - const Expr& value) { + LogicalResult scalarDecl(llvm::SMLoc loc, detail::ScalarType type, + llvm::StringRef id, const Expr& initializer) { if (failed(declare(loc, id))) { return failure(); } - auto emitted = emitAngle(value); - if (failed(emitted)) { + switch (type) { + case detail::ScalarType::Float: { + auto value = emitAngle(initializer); + if (failed(value)) { + return failure(); + } + parameterConstants.insert(id, *value); + return success(); + } + case detail::ScalarType::Int: { + auto value = evaluateConstant(initializer); + if (failed(value)) { + return failure(); + } + integerConstants.insert(id, *value); + return success(); + } + } + llvm_unreachable("unknown scalar type"); + } + + LogicalResult boolDecl(llvm::SMLoc loc, llvm::StringRef id, + const Condition& initializer) { + if (failed(declare(loc, id))) { return failure(); } - parameterConstants.insert(id, *emitted); + auto value = emitCondition(initializer); + if (failed(value)) { + return failure(); + } + booleanConstants.insert(id, *value); return success(); } @@ -541,6 +567,8 @@ class QCEmitter { private: using ScopedValueTable = llvm::ScopedHashTable; using ValueScope = llvm::ScopedHashTableScope; + using IntegerTable = llvm::ScopedHashTable; + using IntegerScope = llvm::ScopedHashTableScope; //===--- Locations ----------------------------------------------------===// @@ -577,7 +605,16 @@ class QCEmitter { return builder.measure(std::get(*resolved)); } 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)) { @@ -936,13 +973,14 @@ class QCEmitter { return std::variant>{*loaded}; } - static bool isConstantIndex(const Expr& expr) { + bool isConstantIndex(const Expr& expr) const { switch (expr.kind) { case Expr::Kind::Int: case Expr::Kind::Float: return true; case Expr::Kind::Ident: - return false; + // A declared integer constant folds; a loop variable does not. + return integerConstants.count(expr.ident) != 0; case Expr::Kind::Neg: return isConstantIndex(*expr.lhs); case Expr::Kind::Add: @@ -1068,6 +1106,13 @@ class QCEmitter { if (Value value = parameterConstants.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; } @@ -1113,6 +1158,12 @@ class QCEmitter { if (Value value = loopVariables.lookup(expr.ident)) { return value; } + if (integerConstants.count(expr.ident) != 0) { + return arith::ConstantOp::create( + builder, + builder.getIndexAttr(integerConstants.lookup(expr.ident))) + .getResult(); + } return error(expr.loc, "expected an integer expression"); case Expr::Kind::Float: return error(expr.loc, "expected an integer expression"); @@ -1154,8 +1205,12 @@ class QCEmitter { return *lhs / *rhs; } } - case Expr::Kind::Float: case Expr::Kind::Ident: + if (integerConstants.count(expr.ident) != 0) { + return integerConstants.lookup(expr.ident); + } + 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"); @@ -1170,6 +1225,10 @@ class QCEmitter { ScopedValueTable parameterConstants; ValueScope parameterConstantsScope{parameterConstants}; + IntegerTable integerConstants; + IntegerScope integerConstantsScope{integerConstants}; + ScopedValueTable booleanConstants; + ValueScope booleanConstantsScope{booleanConstants}; ScopedValueTable loopVariables; ValueScope loopVariablesScope{loopVariables}; ScopedValueTable dynamicallyLoadedQubits; diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp index d06aec06be..9484514c6c 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp @@ -61,6 +61,8 @@ namespace { .Case("float", TokenKind::Float) .Case("angle", TokenKind::Angle) .Case("duration", TokenKind::Duration) + .Case("true", TokenKind::True) + .Case("false", TokenKind::False) .Default(TokenKind::Identifier); } From d1d4bf2bdcf19ebb41edf3b2f02ea2938c88c28d Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 01:48:35 +0200 Subject: [PATCH 12/24] Generalize assignments --- .../QC/Translation/qasm3/QASM3Parser.h | 102 +++++++++++++----- .../QC/Translation/qasm3/QASM3Emitter.cpp | 90 ++++++++++++---- 2 files changed, 147 insertions(+), 45 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h index f5486b88a8..d72884d780 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h @@ -104,6 +104,10 @@ struct Condition { /// tracked here. enum class ScalarType : uint8_t { Float, Int }; +/// The kind of a declared classical variable, used to select the grammar of an +/// assignment's right-hand side (arithmetic vs. boolean). +enum class ClassicalKind : uint8_t { Numeric, Boolean }; + //===----------------------------------------------------------------------===// // Sink concept //===----------------------------------------------------------------------===// @@ -132,8 +136,11 @@ concept QASMSink = requires( s.error(loc, str); s.version(d); s.include(loc, str); - s.scalarDecl(loc, scalarType, str, expr); - s.boolDecl(loc, str, condition); + s.scalarDecl(loc, scalarType, str, &expr); + s.boolDecl(loc, str, &condition); + s.scalarAssign(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); @@ -378,31 +385,40 @@ class Parser { const auto id = current().spelling; advance(); - // Only initialized declarations are supported, since there is no classical - // assignment to give an uninitialized variable a value later. - if (failed(expect(TokenKind::Equals))) { - return failure(); + // The initializer is optional; an uninitialized variable can be given a + // value later by an assignment. + const bool hasInitializer = current().kind == TokenKind::Equals; + if (hasInitializer) { + advance(); } if (isBool) { - auto condition = parseCondition(); - if (failed(condition)) { - return failure(); + 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, **condition); + return sink.boolDecl(loc, id, initializer); } - auto value = parseExpression(); - if (failed(value)) { - return failure(); + 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.scalarDecl(loc, scalarType, id, **value); + return sink.scalarDecl(loc, scalarType, id, initializer); } [[nodiscard]] LogicalResult parseQuantumDecl() { @@ -509,30 +525,68 @@ class Parser { //===--- Assignment and measurement -----------------------------------===// + /// Parse ` = measure ;` (measurement) or ` = ;` + /// (classical-scalar assignment). The value grammar follows the target's + /// declared kind. [[nodiscard]] LogicalResult parseAssignment() { const auto loc = current().loc; auto target = parseReference(); if (failed(target)) { return failure(); } - if (current().kind != TokenKind::Equals) { + const Reference& reference = *target; + + if (current().kind == TokenKind::CompoundAssign) { return sink.error(current().loc, - "classical computations are not supported yet"); + "compound assignments are not supported yet"); } - advance(); - if (current().kind != TokenKind::Measure) { - return sink.error(current().loc, - "classical computations are not supported yet"); + if (failed(expect(TokenKind::Equals))) { + return failure(); } - advance(); - auto operand = parseGateOperand(); - if (failed(operand)) { + + // Measurement assignment: ` = measure ;`. + 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 to a classical scalar. + 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.measure(loc, *target, *operand); + return sink.scalarAssign(loc, reference.identifier, **value); } [[nodiscard]] LogicalResult parseMeasure() { diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 13cc3d3d39..6734782c73 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -284,41 +284,83 @@ class QCEmitter { } LogicalResult scalarDecl(llvm::SMLoc loc, detail::ScalarType type, - llvm::StringRef id, const Expr& initializer) { + llvm::StringRef id, const Expr* initializer) { if (failed(declare(loc, id))) { return failure(); } switch (type) { - case detail::ScalarType::Float: { - auto value = emitAngle(initializer); - if (failed(value)) { - return failure(); - } - parameterConstants.insert(id, *value); - return success(); - } - case detail::ScalarType::Int: { - auto value = evaluateConstant(initializer); - if (failed(value)) { - return failure(); - } - integerConstants.insert(id, *value); - return success(); + case detail::ScalarType::Float: + variableKinds[id] = VariableKind::Float; + break; + case detail::ScalarType::Int: + variableKinds[id] = VariableKind::Int; + break; } + if (initializer != nullptr) { + return assignScalar(id, *initializer); } - llvm_unreachable("unknown scalar type"); + return success(); } LogicalResult boolDecl(llvm::SMLoc loc, llvm::StringRef id, - const Condition& initializer) { + const Condition* initializer) { if (failed(declare(loc, id))) { return failure(); } - auto value = emitCondition(initializer); - if (failed(value)) { + variableKinds[id] = VariableKind::Bool; + if (initializer != nullptr) { + return assignBool(id, *initializer); + } + return success(); + } + + LogicalResult scalarAssign(llvm::SMLoc /*loc*/, llvm::StringRef id, + const Expr& value) { + return assignScalar(id, value); + } + + LogicalResult boolAssign(llvm::SMLoc /*loc*/, llvm::StringRef id, + const Condition& value) { + return assignBool(id, value); + } + + [[nodiscard]] std::optional + classicalKind(llvm::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; + } + + /// Bind (or rebind) a numeric scalar `id` to the value of @p value. Shared by + /// `scalarDecl` and `scalarAssign`. + LogicalResult assignScalar(llvm::StringRef id, const Expr& value) { + if (variableKinds.lookup(id) == VariableKind::Float) { + auto emitted = emitAngle(value); + if (failed(emitted)) { + return failure(); + } + parameterConstants.insert(id, *emitted); + return success(); + } + auto emitted = evaluateConstant(value); + if (failed(emitted)) { + return failure(); + } + integerConstants.insert(id, *emitted); + return success(); + } + + /// Bind (or rebind) a boolean scalar `id`. Shared by `boolDecl` and + /// `boolAssign`. + LogicalResult assignBool(llvm::StringRef id, const Condition& value) { + auto emitted = emitCondition(value); + if (failed(emitted)) { return failure(); } - booleanConstants.insert(id, *value); + booleanConstants.insert(id, *emitted); return success(); } @@ -570,6 +612,10 @@ class QCEmitter { using IntegerTable = llvm::ScopedHashTable; using IntegerScope = llvm::ScopedHashTableScope; + /// The declared type of a classical scalar variable, tracked so that + /// assignments know how to lower their right-hand side. + enum class VariableKind : uint8_t { Float, Int, Bool }; + //===--- Locations ----------------------------------------------------===// Location translate(llvm::SMLoc loc) { @@ -1237,6 +1283,8 @@ class QCEmitter { llvm::BumpPtrAllocator keyStorage; llvm::StringSaver keySaver{keyStorage}; + llvm::StringMap variableKinds; + QubitScope qubitRegisters; llvm::StringMap classicalRegisters; llvm::StringMap> bitValues; From 86f057791f59d02a1d7dce0643693401d00f1a97 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 02:00:11 +0200 Subject: [PATCH 13/24] Fix broadcasted gate calls --- .../QC/Translation/qasm3/QASM3Emitter.cpp | 117 ++++++++---------- .../QC/Translation/test_qasm3_translation.cpp | 22 +++- 2 files changed, 76 insertions(+), 63 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 6734782c73..cce9d6d9b8 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -865,40 +865,29 @@ class QCEmitter { return status; } - LogicalResult emitBroadcastedGateCall( - const GateInfo& gate, llvm::StringRef id, ValueRange parameters, - llvm::ArrayRef modifiers, - const SmallVector>& operands, llvm::SMLoc loc) { - const auto broadcastWidth = operands.front().size(); - for (const auto& operand : operands) { - if (operand.size() != broadcastWidth) { - return error(loc, "all broadcasting operands must have the same width"); - } - } - - auto split = - splitControlsAndTargets>(modifiers, operands, loc); + /// Emit a single (non-broadcast) invocation of a gate on already-resolved + /// single-qubit @p operands. + LogicalResult + emitGateInvocation(const std::variant& definition, + llvm::StringRef id, ValueRange parameters, + llvm::ArrayRef modifiers, + const SmallVector& operands, llvm::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(); } - - for (size_t b = 0; b < broadcastWidth; ++b) { - const auto slice = [&](const SmallVector>& lists) { - SmallVector qubits; - qubits.reserve(lists.size()); - for (const auto& list : lists) { - qubits.push_back(list[b]); - } - return qubits; - }; - emitStandardGate(**gateFn, parameters, slice(split->targets), - slice(split->posControls), slice(split->negControls), - split->invert); - } + emitStandardGate(**gateFn, parameters, split->targets, split->posControls, + split->negControls, split->invert); return success(); } @@ -919,55 +908,59 @@ class QCEmitter { parameters.push_back(*value); } - SmallVector operands; - SmallVector> broadcasting; + // Resolve operands in order, remembering which are whole registers. If any + // operand is a register, the call is broadcast over the qubits of the + // register(s); all such 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* value = std::get_if(&*resolved)) { - operands.push_back(*value); - } else { - broadcasting.push_back(std::get>(*resolved)); + 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)); } - if (!broadcasting.empty() && !operands.empty()) { - return error(call.loc, "gate operands must be single qubits or quantum " - "registers and not a mix of both"); - } - - if (!broadcasting.empty()) { - if (std::holds_alternative(it->second)) { - return error(call.loc, - "broadcasted compound gates are not supported yet"); + // No registers: a single invocation on the resolved qubits. + if (!broadcastWidth) { + SmallVector operands; + operands.reserve(resolvedOperands.size()); + for (const auto& resolved : resolvedOperands) { + operands.push_back(std::get(resolved)); } - return emitBroadcastedGateCall(std::get(it->second), - call.identifier, parameters, - call.modifiers, broadcasting, call.loc); - } - - auto split = - splitControlsAndTargets(call.modifiers, operands, call.loc); - if (failed(split)) { - return failure(); + return emitGateInvocation(it->second, call.identifier, parameters, + call.modifiers, operands, call.loc); } - if (const auto* compound = std::get_if(&it->second)) { - return emitCompoundGate(*compound, parameters, split->targets, - split->posControls, split->negControls, - split->invert, call.loc); + if (std::holds_alternative(it->second)) { + return error(call.loc, + "broadcasted compound gates are not supported yet"); } - const auto& gate = std::get(it->second); - auto gateFn = - resolveStandardGate(gate, call.identifier, parameters, call.loc); - if (failed(gateFn)) { - return failure(); + 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(); + } } - emitStandardGate(**gateFn, parameters, split->targets, split->posControls, - split->negControls, split->invert); return success(); } diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index e7649482da..6023b0b0d8 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -141,6 +141,17 @@ static void nestedForLoopWhileOp(qc::QCProgramBuilder& b) { }); } +// `cx q, r;` with a register `q` and a single qubit `r` broadcasts over the +// register: the register is indexed per iteration while the single qubit is +// repeated. +static void broadcastRegisterAndQubit(qc::QCProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + auto r = b.allocQubit(); + b.cx(q[0], r); + b.cx(q[1], r); + b.cx(q[2], r); +} + TEST_P(QASM3TranslationTest, ProgramEquivalence) { const auto name = " (" + GetParam().name + ")"; const auto& source = GetParam().source; @@ -464,4 +475,13 @@ INSTANTIATE_TEST_SUITE_P( QASM3TranslationTestCase{ "NestedForLoopCtrlOpWithExtractedQubit", qasm::nestedForLoopCtrlOpWithExtractedQubit, - MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithExtractedQubit)})); + MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithExtractedQubit)}, + QASM3TranslationTestCase{ + "BroadcastRegisterAndQubit", + R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +qubit r; +cx q, r; +)qasm", + MQT_NAMED_BUILDER(broadcastRegisterAndQubit)})); From 90e61c525fe05efb2bfcd783cbad0d5e24add40f Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:38:38 +0200 Subject: [PATCH 14/24] Clean up a bit --- .../QC/Translation/TranslateQASM3ToQC.h | 4 +- .../QC/Translation/qasm3/QASM3Emitter.h | 10 +- .../Dialect/QC/Translation/qasm3/QASM3Lexer.h | 25 +- .../QC/Translation/qasm3/QASM3Parser.h | 1074 +++++++++-------- .../QC/Translation/TranslateQASM3ToQC.cpp | 5 +- .../QC/Translation/qasm3/QASM3Emitter.cpp | 684 ++++++----- .../QC/Translation/qasm3/QASM3Lexer.cpp | 43 +- 7 files changed, 969 insertions(+), 876 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h b/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h index fb2a6d0811..1e9cb95e46 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h +++ b/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h @@ -25,7 +25,7 @@ namespace qc { /** * @brief Translate an OpenQASM 3 program to a QC program. * - * @param sourceMgr Source manager containing the OpenQASM3 program. + * @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. */ @@ -35,7 +35,7 @@ translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context); /** * @brief Translate an OpenQASM 3 program to a QC program. * - * @param source String containing the OpenQASM3 program. + * @param source String containing the OpenQASM 3 program. * @param context The MLIRContext to create the module in. * @return A module containing the QC program. */ diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h index d22fdc6bf5..676a31c833 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h @@ -25,19 +25,19 @@ class ModuleOp; namespace qc::detail { /** - * @brief Import an OpenQASM 3 program into a QC-dialect module. + * @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 whose main buffer holds the program. + * @param sourceMgr Source manager containing the OpenQASM 3 program. * @param context The MLIRContext to create the module in. - * @return The imported module, or null on failure. + * @return A module containing the QC program. */ -[[nodiscard]] OwningOpRef importQASM3(llvm::SourceMgr& sourceMgr, - MLIRContext* context); +[[nodiscard]] OwningOpRef +translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context); } // namespace qc::detail diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h index 13428c97c1..0b497effe8 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h @@ -12,6 +12,7 @@ #include #include +#include #include @@ -45,7 +46,8 @@ enum class TokenKind : uint8_t { Pow, Ctrl, NegCtrl, - // Type keywords (only a subset is supported; the rest produce diagnostics) + + // Types Int, Uint, Bool, @@ -85,7 +87,7 @@ enum class TokenKind : uint8_t { ExclamationPoint, AmpAmp, // `&&` PipePipe, // `||` - CompoundAssign, // any `=` such as `+=`, `-=`, ... + CompoundAssign, // `+=`, `-=`, ... // Comparisons EqualsEquals, @@ -97,14 +99,20 @@ enum class TokenKind : uint8_t { }; /// A human-readable name for @p kind, used in diagnostics. -[[nodiscard]] llvm::StringRef describe(TokenKind kind); +[[nodiscard]] StringRef describe(TokenKind kind); -/// A single lexical token. Spellings are zero-copy views into the source -/// buffer; literals are pre-parsed. +/** + * @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; - llvm::StringRef spelling; ///< Identifier text or string-literal contents. - llvm::SMLoc loc; + SMLoc loc; + StringRef identifier; ///< For `Identifier` tokens. + StringRef stringValue; ///< For `StringLiteral` tokens. int64_t intValue = 0; double floatValue = 0.0; }; @@ -119,8 +127,7 @@ struct Token { */ class Lexer { public: - explicit Lexer(llvm::StringRef buffer) - : cur(buffer.begin()), end(buffer.end()) {} + 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(); diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h index d72884d780..6f26c0d69e 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h @@ -29,85 +29,116 @@ namespace mlir::qc::detail { -//===----------------------------------------------------------------------===// -// Transient parse vocabulary -// -// These types are the vocabulary the parser hands to a sink. They 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. -//===----------------------------------------------------------------------===// +/** + * @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. + */ -/// A (sub-)expression. Bump-allocated; children are borrowed pointers. +/** + * @ingroup ParseVocabulary + * @brief A (sub-)expression. + * + * @details + * Bump-allocated; children are borrowed pointers. + */ struct Expr { - enum class Kind : uint8_t { Int, Float, Ident, Neg, Add, Sub, Mul, Div }; + 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; - llvm::StringRef ident; + StringRef identifier; const Expr* lhs = nullptr; const Expr* rhs = nullptr; - llvm::SMLoc loc; }; -/// A gate modifier: `inv @`, `pow(e) @`, `ctrl(e) @`, or `negctrl(e) @`. +/** + * @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; ///< `pow`/`ctrl`/`negctrl` argument, or null. + const Expr* argument = nullptr; }; -/// A gate operand: a (possibly indexed) identifier, or a hardware qubit. +/** + * @ingroup ParseVocabulary + * @brief A gate operand: a (possibly indexed) identifier, or a hardware qubit. + */ struct Operand { - llvm::StringRef identifier; + SMLoc loc; + StringRef identifier; const Expr* index = nullptr; std::optional hardwareQubit; - llvm::SMLoc loc; }; -/// A (possibly indexed) classical reference, e.g. `c` or `c[0]`. -struct Reference { - llvm::StringRef identifier; +/// A (possibly indexed) classical reference (e.g., `c` or `c[0]`). +struct BitReference { + SMLoc loc; + StringRef identifier; const Expr* index = nullptr; - llvm::SMLoc loc; }; -/// A resolved gate call. Array members are borrowed for the duration of the -/// sink call (top-level) or bump-allocated (gate-definition bodies). +/** + * @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 { - llvm::StringRef identifier; - llvm::ArrayRef modifiers; - llvm::ArrayRef parameters; - llvm::ArrayRef operands; - llvm::SMLoc loc; + SMLoc loc; + StringRef identifier; + ArrayRef modifiers; + ArrayRef parameters; + ArrayRef operands; }; -/// A branch condition: a boolean-valued expression. Bump-allocated; children -/// are borrowed pointers. Register comparisons (e.g. `c == 5`) are not yet -/// supported. +/** + * @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`. - Reference bit; ///< For `Bit`. + 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`. - llvm::SMLoc loc; }; -/// A supported arithmetic scalar declaration type (`bool` is handled -/// separately, as its initializer is a boolean condition). `const` and -/// non-`const` declarations are lowered identically, so constness is not -/// tracked here. -enum class ScalarType : uint8_t { Float, Int }; - -/// The kind of a declared classical variable, used to select the grammar of an -/// assignment's right-hand side (arithmetic vs. boolean). +/** + * @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 //===----------------------------------------------------------------------===// @@ -126,36 +157,36 @@ enum class ClassicalKind : uint8_t { Numeric, Boolean }; * materialized. */ template -concept QASMSink = requires( - S s, llvm::SMLoc loc, llvm::StringRef str, const Expr& expr, - const Operand& operand, const Reference& reference, - const Condition& condition, const GateCall& call, - llvm::ArrayRef operands, llvm::ArrayRef names, - llvm::ArrayRef body, llvm::function_ref cont, - double d, bool flag, ScalarType scalarType) { - s.error(loc, str); - s.version(d); - s.include(loc, str); - s.scalarDecl(loc, scalarType, str, &expr); - s.boolDecl(loc, str, &condition); - s.scalarAssign(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.conditionOnly(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. -}; +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.version(d); + 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 @@ -181,7 +212,7 @@ class Parser { } [[nodiscard]] LogicalResult parseProgram() { - while (!isAtEnd()) { + while (!atEnd()) { if (failed(parseStatement())) { return failure(); } @@ -199,7 +230,7 @@ class Parser { [[nodiscard]] const Token& current() const { return currentToken; } [[nodiscard]] const Token& peek() const { return nextToken; } - [[nodiscard]] bool isAtEnd() const { + [[nodiscard]] bool atEnd() const { return currentToken.kind == TokenKind::Eof; } @@ -222,8 +253,7 @@ class Parser { return new (allocator.Allocate()) Condition(); } - template - [[nodiscard]] llvm::ArrayRef copyToArena(llvm::ArrayRef values) { + template [[nodiscard]] ArrayRef copyToArena(ArrayRef values) { if (values.empty()) { return {}; } @@ -254,13 +284,13 @@ class Parser { case TokenKind::Qubit: return parseQuantumDecl(); case TokenKind::Qreg: - return parseOldStyleDecl(/*classical=*/false); - case TokenKind::CReg: - return parseOldStyleDecl(/*classical=*/true); + return parseQregDecl(); case TokenKind::Bit: return parseClassicalDecl(); + case TokenKind::CReg: + return parseCregDecl(); case TokenKind::Gate: - return parseGateDefinition(); + return parseGateStatement(); case TokenKind::Opaque: return sink.error(current().loc, "opaque gate declarations are not supported"); @@ -301,7 +331,7 @@ class Parser { [[nodiscard]] LogicalResult parseBlock() { if (current().kind == TokenKind::LBrace) { advance(); - while (!isAtEnd() && current().kind != TokenKind::RBrace) { + while (!atEnd() && current().kind != TokenKind::RBrace) { if (failed(parseStatement())) { return failure(); } @@ -311,7 +341,23 @@ class Parser { return parseStatement(); } - //===--- Version and include ------------------------------------------===// + //===--- 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 @@ -331,13 +377,15 @@ class Parser { return sink.version(version); } + //===--- 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().spelling; + const auto filename = current().stringValue; advance(); if (failed(expect(TokenKind::Semicolon))) { return failure(); @@ -347,24 +395,22 @@ class Parser { //===--- Declarations -------------------------------------------------===// - /// Parse `[const] (int|uint|float|bool) = ;`. `const` and - /// non-`const` declarations are parsed and lowered identically. `bool` - /// initializers are boolean conditions; all others are arithmetic. + /// Parse `[const] (int|uint|float|bool) = ;`. [[nodiscard]] LogicalResult parseScalarDeclaration() { const auto loc = current().loc; if (current().kind == TokenKind::Const) { advance(); // const } - ScalarType scalarType = ScalarType::Float; + NumericType numericType = NumericType::Float; bool isBool = false; switch (current().kind) { case TokenKind::Float: - scalarType = ScalarType::Float; + numericType = NumericType::Float; break; case TokenKind::Int: case TokenKind::Uint: - scalarType = ScalarType::Int; + numericType = NumericType::Int; break; case TokenKind::Bool: isBool = true; @@ -382,11 +428,9 @@ class Parser { if (current().kind != TokenKind::Identifier) { return sink.error(current().loc, "expected identifier"); } - const auto id = current().spelling; + const auto id = current().identifier; advance(); - // The initializer is optional; an uninitialized variable can be given a - // value later by an assignment. const bool hasInitializer = current().kind == TokenKind::Equals; if (hasInitializer) { advance(); @@ -418,9 +462,10 @@ class Parser { if (failed(expect(TokenKind::Semicolon))) { return failure(); } - return sink.scalarDecl(loc, scalarType, id, initializer); + return sink.numericDecl(loc, numericType, id, initializer); } + /// Parse `qubit[] ;`. [[nodiscard]] LogicalResult parseQuantumDecl() { const auto loc = current().loc; advance(); // qubit @@ -435,7 +480,7 @@ class Parser { if (current().kind != TokenKind::Identifier) { return sink.error(current().loc, "expected identifier"); } - const auto id = current().spelling; + const auto id = current().identifier; advance(); if (failed(expect(TokenKind::Semicolon))) { return failure(); @@ -443,13 +488,14 @@ class Parser { return sink.qubitRegister(loc, id, size); } - [[nodiscard]] LogicalResult parseOldStyleDecl(const bool classical) { + /// Parse `qreg [];`. + [[nodiscard]] LogicalResult parseQregDecl() { const auto loc = current().loc; - advance(); // qreg / creg + advance(); // qreg if (current().kind != TokenKind::Identifier) { return sink.error(current().loc, "expected identifier"); } - const auto id = current().spelling; + const auto id = current().identifier; advance(); const Expr* size = nullptr; if (current().kind == TokenKind::LBracket) { @@ -462,10 +508,10 @@ class Parser { if (failed(expect(TokenKind::Semicolon))) { return failure(); } - return classical ? sink.classicalRegister(loc, id, size) - : sink.qubitRegister(loc, id, size); + return sink.qubitRegister(loc, id, size); } + /// Parse `bit[] (= );`. [[nodiscard]] LogicalResult parseClassicalDecl() { const auto loc = current().loc; advance(); // bit @@ -480,7 +526,7 @@ class Parser { if (current().kind != TokenKind::Identifier) { return sink.error(current().loc, "expected identifier"); } - const auto id = current().spelling; + const auto id = current().identifier; advance(); std::optional measureSource; @@ -503,38 +549,45 @@ class Parser { return failure(); } if (measureSource) { - const Reference target{.identifier = id, .index = nullptr, .loc = loc}; + const BitReference target{.loc = loc, .identifier = id, .index = nullptr}; return sink.measure(loc, target, *measureSource); } return success(); } - [[nodiscard]] mlir::FailureOr parseDesignator() { - if (failed(expect(TokenKind::LBracket))) { - return failure(); + /// Parse `creg [];`. + [[nodiscard]] LogicalResult parseCregDecl() { + const auto loc = current().loc; + advance(); // creg + if (current().kind != TokenKind::Identifier) { + return sink.error(current().loc, "expected identifier"); } - auto expr = parseExpression(); - if (failed(expr)) { - return failure(); + 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::RBracket))) { + if (failed(expect(TokenKind::Semicolon))) { return failure(); } - return *expr; + return sink.classicalRegister(loc, id, size); } - //===--- Assignment and measurement -----------------------------------===// + //===--- Assignment ---------------------------------------------------===// - /// Parse ` = measure ;` (measurement) or ` = ;` - /// (classical-scalar assignment). The value grammar follows the target's - /// declared kind. + /// Parse ` = measure ;` or ` = ;`. [[nodiscard]] LogicalResult parseAssignment() { const auto loc = current().loc; - auto target = parseReference(); + auto target = parseBitReference(); if (failed(target)) { return failure(); } - const Reference& reference = *target; + const BitReference& reference = *target; if (current().kind == TokenKind::CompoundAssign) { return sink.error(current().loc, @@ -544,7 +597,7 @@ class Parser { return failure(); } - // Measurement assignment: ` = measure ;`. + // Measure assignment if (current().kind == TokenKind::Measure) { advance(); auto operand = parseGateOperand(); @@ -557,7 +610,7 @@ class Parser { return sink.measure(loc, reference, *operand); } - // Value assignment to a classical scalar. + // Value assignment const auto kind = sink.classicalKind(reference.identifier); if (!kind) { return sink.error(reference.loc, @@ -586,9 +639,11 @@ class Parser { if (failed(expect(TokenKind::Semicolon))) { return failure(); } - return sink.scalarAssign(loc, reference.identifier, **value); + return sink.numericAssign(loc, reference.identifier, **value); } + //===--- Measure ------------------------------------------------------===// + [[nodiscard]] LogicalResult parseMeasure() { const auto loc = current().loc; advance(); // measure @@ -599,7 +654,7 @@ class Parser { if (failed(expect(TokenKind::Arrow))) { return failure(); } - auto target = parseReference(); + auto target = parseBitReference(); if (failed(target)) { return failure(); } @@ -609,26 +664,7 @@ class Parser { return sink.measure(loc, *target, *operand); } - //===--- Barrier and reset --------------------------------------------===// - - [[nodiscard]] LogicalResult parseBarrier() { - const auto loc = current().loc; - advance(); // barrier - llvm::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); - } + //===--- Reset --------------------------------------------------------===// [[nodiscard]] LogicalResult parseReset() { const auto loc = current().loc; @@ -643,378 +679,134 @@ class Parser { return sink.reset(loc, *operand); } - //===--- Control flow -------------------------------------------------===// + //===--- Barrier ------------------------------------------------------===// - [[nodiscard]] LogicalResult parseIf() { + [[nodiscard]] LogicalResult parseBarrier() { const auto loc = current().loc; - advance(); // if - if (failed(expect(TokenKind::LParen))) { - return failure(); - } - auto condition = parseCondition(); - if (failed(condition)) { - return failure(); - } - const Condition& cond = **condition; - 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.conditionOnly(loc, cond); - } - advance(); // else - auto scope = sink.ifBegin(loc, cond, /*invert=*/true); - if (failed(scope)) { + advance(); // barrier + SmallVector operands; + while (current().kind != TokenKind::Semicolon) { + auto operand = parseGateOperand(); + if (failed(operand)) { return failure(); } - if (failed(parseBlock())) { + operands.push_back(*operand); + if (current().kind != TokenKind::Semicolon && + failed(expect(TokenKind::Comma))) { return failure(); } - return sink.ifEnd(*scope, /*hadElse=*/false); } + advance(); // ; + return sink.barrier(loc, operands); + } - auto scope = sink.ifBegin(loc, cond, /*invert=*/false); - if (failed(scope)) { - return failure(); - } - if (failed(parseBlock())) { - return failure(); + //===--- 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(); - 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))) { + // Parse parameters + SmallVector parameters; + if (current().kind == TokenKind::LParen) { + advance(); + if (failed(parseIdentifierList(parameters))) { return failure(); } - if (failed(parseBlock())) { + if (failed(expect(TokenKind::RParen))) { 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(); - if (current().kind != TokenKind::Identifier) { - return sink.error(current().loc, "expected loop variable"); - } - const auto variable = current().spelling; - advance(); - if (failed(expect(TokenKind::In)) || failed(expect(TokenKind::LBracket))) { - return failure(); - } - auto start = parseExpression(); - if (failed(start)) { - return failure(); - } - if (failed(expect(TokenKind::Colon))) { + // Parse target qubits + SmallVector targets; + if (failed(parseIdentifierList(targets))) { return failure(); } - auto second = parseExpression(); - if (failed(second)) { + + // Parse body + if (failed(expect(TokenKind::LBrace))) { return failure(); } - - const Expr* step = nullptr; - const Expr* stop = nullptr; - if (current().kind == TokenKind::Colon) { - advance(); - auto third = parseExpression(); - if (failed(third)) { + 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(); } - step = *second; - stop = *third; - } else { - auto* one = makeExpr(); - one->kind = Expr::Kind::Int; - one->intValue = 1; - one->loc = loc; - step = one; - stop = *second; + body.push_back(*call); } - if (failed(expect(TokenKind::RBracket))) { + if (failed(expect(TokenKind::RBrace))) { return failure(); } - return sink.forStmt(loc, variable, **start, *step, *stop, - [this] { return parseBlock(); }); + return sink.gateDefinition(loc, id, copyToArena(ArrayRef(parameters)), + copyToArena(ArrayRef(targets)), + copyToArena(ArrayRef(body))); } - [[nodiscard]] LogicalResult parseWhile() { - const auto loc = current().loc; - advance(); // while - if (failed(expect(TokenKind::LParen))) { - return failure(); - } - auto condition = parseCondition(); - if (failed(condition)) { - return failure(); - } - const Condition& cond = **condition; - if (failed(expect(TokenKind::RParen))) { + [[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.whileStmt(loc, cond, [this] { return parseBlock(); }); + return sink.gateCall(*call); } - /// cond := andCond ('||' andCond)* - [[nodiscard]] mlir::FailureOr parseCondition() { - auto lhs = parseAndCondition(); - if (failed(lhs)) { - return failure(); - } - const Condition* result = *lhs; - while (current().kind == TokenKind::PipePipe) { - const auto loc = current().loc; - advance(); - auto rhs = parseAndCondition(); - if (failed(rhs)) { + [[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(); } - result = makeBinaryCondition(Condition::Kind::Or, result, *rhs, loc); } - return result; - } - /// andCond := unaryCond ('&&' unaryCond)* - [[nodiscard]] mlir::FailureOr parseAndCondition() { - auto lhs = parseUnaryCondition(); - if (failed(lhs)) { - return failure(); + switch (current().kind) { + case TokenKind::Gphase: { + call.identifier = "gphase"; + advance(); + break; } - const Condition* result = *lhs; - while (current().kind == TokenKind::AmpAmp) { - const auto loc = current().loc; + case TokenKind::Identifier: { + call.identifier = current().identifier; advance(); - auto rhs = parseUnaryCondition(); - if (failed(rhs)) { - return failure(); - } - result = makeBinaryCondition(Condition::Kind::And, result, *rhs, loc); - } - return result; - } - - /// unaryCond := ('!' | '~') unaryCond | primaryCond - [[nodiscard]] mlir::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->kind = Condition::Kind::Not; - condition->loc = loc; - condition->lhs = *operand; - return condition; - } - return parsePrimaryCondition(); - } - - /// primaryCond := 'measure' gateOperand | '(' cond ')' | reference - [[nodiscard]] mlir::FailureOr parsePrimaryCondition() { - const auto loc = current().loc; - - if (current().kind == TokenKind::True || - current().kind == TokenKind::False) { - auto* condition = makeCondition(); - condition->kind = Condition::Kind::Literal; - condition->literalValue = current().kind == TokenKind::True; - condition->loc = loc; - advance(); - return finishPrimaryCondition(condition); - } - - if (current().kind == TokenKind::Measure) { - advance(); - auto operand = parseGateOperand(); - if (failed(operand)) { - return failure(); - } - auto* condition = makeCondition(); - condition->kind = Condition::Kind::Measurement; - condition->operand = *operand; - condition->loc = loc; - return finishPrimaryCondition(condition); - } - - if (current().kind == TokenKind::LParen) { - advance(); - auto inner = parseCondition(); - if (failed(inner)) { - return failure(); - } - if (failed(expect(TokenKind::RParen))) { - return failure(); - } - return finishPrimaryCondition(*inner); - } - - if (current().kind == TokenKind::Identifier) { - auto bit = parseReference(); - if (failed(bit)) { - return failure(); - } - auto* condition = makeCondition(); - condition->kind = Condition::Kind::Bit; - condition->bit = *bit; - condition->loc = loc; - return finishPrimaryCondition(condition); + break; } - - return sink.error(loc, "unsupported condition expression"); - } - - /// Reject a register comparison (e.g. `c == 5`) trailing a primary. - [[nodiscard]] mlir::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 llvm::SMLoc loc) { - auto* condition = makeCondition(); - condition->kind = kind; - condition->loc = loc; - condition->lhs = lhs; - condition->rhs = rhs; - return condition; - } - - //===--- Gate definitions and calls -----------------------------------===// - - [[nodiscard]] LogicalResult parseGateDefinition() { - const auto loc = current().loc; - advance(); // gate - if (current().kind != TokenKind::Identifier) { - return sink.error(current().loc, "expected gate name"); - } - const auto id = current().spelling; - advance(); - - llvm::SmallVector parameters; - if (current().kind == TokenKind::LParen) { - advance(); - if (failed(parseIdentifierList(parameters))) { - return failure(); - } - if (failed(expect(TokenKind::RParen))) { - return failure(); - } - } - - llvm::SmallVector targets; - if (failed(parseIdentifierList(targets))) { - return failure(); - } - - if (failed(expect(TokenKind::LBrace))) { - return failure(); - } - llvm::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. - llvm::SmallVector callModifiers; - llvm::SmallVector callParameters; - llvm::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(llvm::ArrayRef(parameters)), - copyToArena(llvm::ArrayRef(targets)), - copyToArena(llvm::ArrayRef(body))); - } - - [[nodiscard]] LogicalResult parseGateCallStatement() { - // The scratch buffers must outlive the sink call, so they live here rather - // than inside `parseGateCall`. - llvm::SmallVector modifiers; - llvm::SmallVector parameters; - llvm::SmallVector operands; - auto call = parseGateCall(modifiers, parameters, operands, - /*persist=*/false); - if (failed(call)) { - return failure(); - } - return sink.gateCall(*call); - } - - [[nodiscard]] mlir::FailureOr - parseGateCall(llvm::SmallVectorImpl& modifiers, - llvm::SmallVectorImpl& parameters, - llvm::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(); - } - } - - if (current().kind == TokenKind::Gphase) { - call.identifier = "gphase"; - advance(); - } else if (current().kind == TokenKind::Identifier) { - call.identifier = current().spelling; - advance(); - } else { return sink.error(current().loc, "expected gate name"); } + // Parse parameters if (current().kind == TokenKind::LParen) { advance(); while (current().kind != TokenKind::RParen) { @@ -1036,6 +828,7 @@ class Parser { "gate calls with designators are not supported yet"); } + // Parse target qubits while (current().kind != TokenKind::Semicolon) { auto operand = parseGateOperand(); if (failed(operand)) { @@ -1050,12 +843,12 @@ class Parser { advance(); // ; // Top-level calls are consumed by the sink while the caller's buffers are - // still alive, so borrowing them is safe. Gate-definition bodies outlive - // this frame, so their arrays are copied into the arena. + // 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(llvm::ArrayRef(modifiers)); - call.parameters = copyToArena(llvm::ArrayRef(parameters)); - call.operands = copyToArena(llvm::ArrayRef(operands)); + call.modifiers = copyToArena(ArrayRef(modifiers)); + call.parameters = copyToArena(ArrayRef(parameters)); + call.operands = copyToArena(ArrayRef(operands)); return call; } call.modifiers = modifiers; @@ -1064,7 +857,7 @@ class Parser { return call; } - [[nodiscard]] mlir::FailureOr parseModifier() { + [[nodiscard]] FailureOr parseModifier() { Modifier modifier; switch (current().kind) { case TokenKind::Inv: @@ -1111,7 +904,7 @@ class Parser { } } - [[nodiscard]] mlir::FailureOr parseGateOperand() { + [[nodiscard]] FailureOr parseGateOperand() { Operand operand; operand.loc = current().loc; if (current().kind == TokenKind::HardwareQubit) { @@ -1122,65 +915,336 @@ class Parser { if (current().kind != TokenKind::Identifier) { return sink.error(current().loc, "expected a gate operand"); } - operand.identifier = current().spelling; + operand.identifier = current().identifier; advance(); + const Expr* index = nullptr; if (current().kind == TokenKind::LBracket) { - advance(); - auto index = parseExpression(); - if (failed(index)) { - return failure(); - } - operand.index = *index; - if (failed(expect(TokenKind::RBracket))) { + auto designator = parseDesignator(); + if (failed(designator)) { return failure(); } + index = *designator; } + operand.index = index; return operand; } - [[nodiscard]] mlir::FailureOr parseReference() { - Reference reference; + [[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().spelling; + reference.identifier = current().identifier; advance(); + const Expr* index = nullptr; if (current().kind == TokenKind::LBracket) { - advance(); - auto index = parseExpression(); - if (failed(index)) { - return failure(); - } - reference.index = *index; - if (failed(expect(TokenKind::RBracket))) { + auto designator = parseDesignator(); + if (failed(designator)) { return failure(); } + index = *designator; } + reference.index = index; return reference; } [[nodiscard]] LogicalResult - parseIdentifierList(llvm::SmallVectorImpl& identifiers) { + parseIdentifierList(SmallVectorImpl& identifiers) { if (current().kind != TokenKind::Identifier) { return sink.error(current().loc, "expected an identifier"); } - identifiers.push_back(current().spelling); + 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().spelling); + 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]] mlir::FailureOr parseExpression() { + [[nodiscard]] FailureOr parseExpression() { auto lhs = parseTerm(); if (failed(lhs)) { return failure(); @@ -1201,7 +1265,7 @@ class Parser { return result; } - [[nodiscard]] mlir::FailureOr parseTerm() { + [[nodiscard]] FailureOr parseTerm() { auto lhs = parseUnary(); if (failed(lhs)) { return failure(); @@ -1222,7 +1286,7 @@ class Parser { return result; } - [[nodiscard]] mlir::FailureOr parseUnary() { + [[nodiscard]] FailureOr parseUnary() { if (current().kind == TokenKind::Minus) { const auto loc = current().loc; advance(); @@ -1231,15 +1295,15 @@ class Parser { return failure(); } auto* expr = makeExpr(); - expr->kind = Expr::Kind::Neg; expr->loc = loc; + expr->kind = Expr::Kind::Neg; expr->lhs = *operand; return expr; } return parsePrimary(); } - [[nodiscard]] mlir::FailureOr parsePrimary() { + [[nodiscard]] FailureOr parsePrimary() { auto* expr = makeExpr(); expr->loc = current().loc; switch (current().kind) { @@ -1254,8 +1318,8 @@ class Parser { advance(); return expr; case TokenKind::Identifier: - expr->kind = Expr::Kind::Ident; - expr->ident = current().spelling; + expr->kind = Expr::Kind::Identifier; + expr->identifier = current().identifier; advance(); return expr; case TokenKind::LParen: { @@ -1275,10 +1339,10 @@ class Parser { } [[nodiscard]] Expr* makeBinary(const Expr::Kind kind, const Expr* lhs, - const Expr* rhs, const llvm::SMLoc loc) { + const Expr* rhs, const SMLoc loc) { auto* expr = makeExpr(); - expr->kind = kind; expr->loc = loc; + expr->kind = kind; expr->lhs = lhs; expr->rhs = rhs; return expr; diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp index 0f204e3f91..fe7bf2ab7b 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp @@ -29,10 +29,9 @@ namespace mlir::qc { OwningOpRef translateQASM3ToQC(llvm::SourceMgr& sourceMgr, MLIRContext* context) { - // Route diagnostics through the source manager so errors carry source - // locations and snippets. + // Route diagnostics through source manager const SourceMgrDiagnosticHandler handler(sourceMgr, context); - return detail::importQASM3(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 index cce9d6d9b8..88a9e83b67 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -57,6 +57,7 @@ namespace { using qasm3::GateInfo; +using detail::BitReference; using detail::Condition; using detail::Expr; using detail::GateCall; @@ -64,17 +65,18 @@ using detail::Lexer; using detail::Modifier; using detail::Operand; using detail::Parser; -using detail::Reference; -/// Signature: (builder, gate operands, gate parameters). -using GateFn = std::function; - -/// A stored compound-gate definition. Array members are bump-allocated by the -/// parser and remain valid for the duration of the import. +/** + * @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 { - llvm::ArrayRef parameters; - llvm::ArrayRef targets; - llvm::ArrayRef body; + ArrayRef parameters; + ArrayRef targets; + ArrayRef body; }; /** @@ -93,7 +95,7 @@ struct QubitBinding { using QubitScope = llvm::StringMap; /// Look up a built-in numeric constant and emit it as an `f64`-typed value. -std::optional lookupBuiltinConstant(llvm::StringRef name, +std::optional lookupBuiltinConstant(StringRef name, QCProgramBuilder& builder) { auto constant = [&](double value) -> Value { return arith::ConstantOp::create(builder, builder.getF64FloatAttr(value)) @@ -111,6 +113,10 @@ std::optional lookupBuiltinConstant(llvm::StringRef name, return std::nullopt; } +/// Signature: (builder, gate operands, gate parameters). Owns the callable, as +/// `GATE_DISPATCH` outlives the lambdas that build it. +using GateFn = std::function; + /// Build the table mapping each gate identifier to a `QCProgramBuilder` /// emitter. llvm::StringMap buildGateDispatch() { @@ -245,13 +251,13 @@ llvm::StringMap> buildGateTable() { //===----------------------------------------------------------------------===// /** - * @brief Lowers OpenQASM 3 parse events to QC-dialect operations. + * @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`; - * name resolution, semantic validation, and target-specific restrictions are - * handled here. + * `QCProgramBuilder` and report errors as diagnostics through `LogicalResult`. + * Handles name resolution, semantic validation, and target-specific + * restrictions. */ class QCEmitter { public: @@ -263,18 +269,26 @@ class QCEmitter { //===--- Diagnostics --------------------------------------------------===// - LogicalResult error(llvm::SMLoc loc, const Twine& message) { - return emitError(translate(loc), message); + 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); } - //===--- Program events -----------------------------------------------===// + //===--- Version ------------------------------------------------------===// LogicalResult version(double /*version*/) { - // The version declaration has no effect on translation. + // The version declaration has no effect on the translation. return success(); } - LogicalResult include(llvm::SMLoc loc, llvm::StringRef filename) { + //===--- 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 " @@ -283,26 +297,28 @@ class QCEmitter { return success(); } - LogicalResult scalarDecl(llvm::SMLoc loc, detail::ScalarType type, - llvm::StringRef id, const Expr* initializer) { + //===--- 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::ScalarType::Float: + case detail::NumericType::Float: variableKinds[id] = VariableKind::Float; break; - case detail::ScalarType::Int: + case detail::NumericType::Int: variableKinds[id] = VariableKind::Int; break; } if (initializer != nullptr) { - return assignScalar(id, *initializer); + return assignNumeric(id, *initializer); } return success(); } - LogicalResult boolDecl(llvm::SMLoc loc, llvm::StringRef id, + LogicalResult boolDecl(SMLoc loc, StringRef id, const Condition* initializer) { if (failed(declare(loc, id))) { return failure(); @@ -314,18 +330,17 @@ class QCEmitter { return success(); } - LogicalResult scalarAssign(llvm::SMLoc /*loc*/, llvm::StringRef id, - const Expr& value) { - return assignScalar(id, value); + LogicalResult numericAssign(SMLoc /*loc*/, StringRef id, const Expr& value) { + return assignNumeric(id, value); } - LogicalResult boolAssign(llvm::SMLoc /*loc*/, llvm::StringRef id, + LogicalResult boolAssign(SMLoc /*loc*/, StringRef id, const Condition& value) { return assignBool(id, value); } [[nodiscard]] std::optional - classicalKind(llvm::StringRef id) const { + classicalKind(StringRef id) const { auto it = variableKinds.find(id); if (it == variableKinds.end()) { return std::nullopt; @@ -334,10 +349,15 @@ class QCEmitter { : detail::ClassicalKind::Numeric; } - /// Bind (or rebind) a numeric scalar `id` to the value of @p value. Shared by - /// `scalarDecl` and `scalarAssign`. - LogicalResult assignScalar(llvm::StringRef id, const Expr& value) { - if (variableKinds.lookup(id) == VariableKind::Float) { + /** + * @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(); @@ -345,17 +365,26 @@ class QCEmitter { parameterConstants.insert(id, *emitted); return success(); } - auto emitted = evaluateConstant(value); - if (failed(emitted)) { - return failure(); + case VariableKind::Int: { + auto emitted = evaluateConstant(value); + if (failed(emitted)) { + return failure(); + } + integerConstants.insert(id, *emitted); + return success(); + } + default: + llvm_unreachable("unknown variable kind"); } - integerConstants.insert(id, *emitted); - return success(); } - /// Bind (or rebind) a boolean scalar `id`. Shared by `boolDecl` and - /// `boolAssign`. - LogicalResult assignBool(llvm::StringRef id, const Condition& value) { + /** + * @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(); @@ -364,8 +393,7 @@ class QCEmitter { return success(); } - LogicalResult qubitRegister(llvm::SMLoc loc, llvm::StringRef id, - const Expr* size) { + LogicalResult qubitRegister(SMLoc loc, StringRef id, const Expr* size) { if (failed(declare(loc, id))) { return failure(); } @@ -383,8 +411,7 @@ class QCEmitter { return success(); } - LogicalResult classicalRegister(llvm::SMLoc loc, llvm::StringRef id, - const Expr* size) { + LogicalResult classicalRegister(SMLoc loc, StringRef id, const Expr* size) { if (failed(declare(loc, id))) { return failure(); } @@ -400,9 +427,9 @@ class QCEmitter { return success(); } - //===--- Measurement, reset, barrier ----------------------------------===// + //===--- Measure ------------------------------------------------------===// - LogicalResult measure(llvm::SMLoc loc, const Reference& target, + LogicalResult measure(SMLoc loc, const BitReference& target, const Operand& operand) { auto bits = resolveClassicalBits(target); if (failed(bits)) { @@ -413,8 +440,8 @@ class QCEmitter { return failure(); } SmallVector qubits; - if (std::holds_alternative(*resolved)) { - qubits.push_back(std::get(*resolved)); + if (auto* qubit = std::get_if(&*resolved)) { + qubits.push_back(*qubit); } else { qubits = std::get>(*resolved); } @@ -422,7 +449,7 @@ class QCEmitter { return error(loc, "the classical register and the quantum register must " "have the same width"); } - for (const auto& [bit, qubit] : llvm::zip_equal(*bits, qubits)) { + for (const auto& [bit, qubit] : zip_equal(*bits, qubits)) { auto result = MeasureOp::create( builder, qubit, builder.getStringAttr(bit.registerName), builder.getI64IntegerAttr(bit.registerSize), @@ -438,13 +465,15 @@ class QCEmitter { return success(); } - LogicalResult reset(llvm::SMLoc /*loc*/, const Operand& operand) { + //===--- Reset --------------------------------------------------------===// + + LogicalResult reset(SMLoc /*loc*/, const Operand& operand) { auto resolved = resolveOperand(operand, qubitRegisters); if (failed(resolved)) { return failure(); } - if (std::holds_alternative(*resolved)) { - builder.reset(std::get(*resolved)); + if (auto* qubit = std::get_if(&*resolved)) { + builder.reset(*qubit); } else { for (auto qubit : std::get>(*resolved)) { builder.reset(qubit); @@ -453,17 +482,19 @@ class QCEmitter { return success(); } - LogicalResult barrier(llvm::SMLoc /*loc*/, llvm::ArrayRef operands) { + //===--- 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 (std::holds_alternative(*resolved)) { - qubits.push_back(std::get(*resolved)); + if (auto* qubit = std::get_if(&*resolved)) { + qubits.push_back(*qubit); } else { - llvm::append_range(qubits, std::get>(*resolved)); + append_range(qubits, std::get>(*resolved)); } } builder.barrier(qubits); @@ -472,10 +503,10 @@ class QCEmitter { //===--- Gate definitions and calls -----------------------------------===// - LogicalResult gateDefinition(llvm::SMLoc loc, llvm::StringRef id, - llvm::ArrayRef parameters, - llvm::ArrayRef targets, - llvm::ArrayRef body) { + LogicalResult gateDefinition(SMLoc loc, StringRef id, + ArrayRef parameters, + ArrayRef targets, + ArrayRef body) { if (gates.contains(id)) { return error(loc, "gate '" + id + "' already declared"); } @@ -504,7 +535,7 @@ class QCEmitter { //===--- Control flow -------------------------------------------------===// - LogicalResult conditionOnly(llvm::SMLoc /*loc*/, const Condition& condition) { + LogicalResult ifConditionOnly(SMLoc /*loc*/, const Condition& condition) { return LogicalResult{emitCondition(condition)}; } @@ -513,19 +544,18 @@ class QCEmitter { OpBuilder::InsertPoint savedInsertionPoint; }; - FailureOr ifBegin(llvm::SMLoc /*loc*/, const Condition& condition, + FailureOr ifBegin(SMLoc /*loc*/, const Condition& condition, bool invert) { - auto value = emitCondition(condition); - if (failed(value)) { + auto condOrFailure = emitCondition(condition); + if (failed(condOrFailure)) { return failure(); } - Value condValue = *value; + auto cond = *condOrFailure; if (invert) { auto trueValue = builder.boolConstant(true); - condValue = - arith::XOrIOp::create(builder, condValue, trueValue).getResult(); + cond = arith::XOrIOp::create(builder, cond, trueValue).getResult(); } - auto ifOp = scf::IfOp::create(builder, condValue, /*withElseRegion=*/false); + auto ifOp = scf::IfOp::create(builder, cond, /*withElseRegion=*/false); IfScope scope{.op = ifOp, .savedInsertionPoint = builder.saveInsertionPoint()}; builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); @@ -546,8 +576,8 @@ class QCEmitter { return success(); } - LogicalResult forStmt(llvm::SMLoc loc, llvm::StringRef variable, - const Expr& start, const Expr& step, const Expr& stop, + 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)) { @@ -564,37 +594,35 @@ class QCEmitter { return failure(); } - // OpenQASM 3's range is inclusive of the stop value. + // OpenQASM 3's range is inclusive of the stop value auto one = arith::ConstantOp::create(builder, builder.getIndexAttr(1)).getResult(); - Value inclusiveStop = + auto inclusiveStop = arith::AddIOp::create(builder, *stopValue, one).getResult(); ValueScope loopScope(loopVariables); ValueScope loadScope(dynamicallyLoadedQubits); - LogicalResult status = success(); + auto status = success(); builder.scfFor(*startValue, inclusiveStop, *stepValue, [&](Value iv) { loopVariables.insert(variable, iv); - if (succeeded(status)) { - status = body(); - } + status = body(); }); return status; } - LogicalResult whileStmt(llvm::SMLoc /*loc*/, const Condition& condition, + LogicalResult whileStmt(SMLoc /*loc*/, const Condition& condition, function_ref body) { - LogicalResult status = success(); + auto status = success(); builder.scfWhile( [&] { ValueScope loadScope(dynamicallyLoadedQubits); - auto value = emitCondition(condition); - if (failed(value)) { + auto cond = emitCondition(condition); + if (failed(cond)) { status = failure(); builder.scfCondition(builder.boolConstant(false)); } else { - builder.scfCondition(*value); + builder.scfCondition(*cond); } }, [&] { @@ -607,109 +635,127 @@ class QCEmitter { } private: - using ScopedValueTable = llvm::ScopedHashTable; - using ValueScope = llvm::ScopedHashTableScope; - using IntegerTable = llvm::ScopedHashTable; - using IntegerScope = llvm::ScopedHashTableScope; - - /// The declared type of a classical scalar variable, tracked so that - /// assignments know how to lower their right-hand side. + 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 }; - //===--- Locations ----------------------------------------------------===// - - Location translate(llvm::SMLoc loc) { - if (!loc.isValid()) { - return UnknownLoc::get(builder.getContext()); - } - const auto [line, col] = sourceMgr.getLineAndColumn(loc); - return FileLineColLoc::get(builder.getStringAttr(""), line, col); - } - - //===--- Symbol declarations ------------------------------------------===// + //===--- Declarations -------------------------------------------------===// - LogicalResult declare(llvm::SMLoc loc, llvm::StringRef id) { + LogicalResult declare(SMLoc loc, StringRef id) { if (!declaredNames.insert(id).second) { return error(loc, "identifier '" + id + "' already declared"); } return success(); } - //===--- Conditions ---------------------------------------------------===// + //===--- Gate calls ---------------------------------------------------===// - FailureOr emitCondition(const Condition& condition) { - switch (condition.kind) { - case Condition::Kind::Measurement: { - auto resolved = resolveOperand(condition.operand, qubitRegisters); - if (failed(resolved)) { - return failure(); - } - if (!std::holds_alternative(*resolved)) { - return error(condition.loc, - "measurement condition must be a single qubit"); - } - return builder.measure(std::get(*resolved)); + 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 + "'"); } - 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); + + // Resolve parameters + SmallVector parameters; + parameters.reserve(call.parameters.size()); + for (const auto* argument : call.parameters) { + auto value = emitAngle(*argument); if (failed(value)) { return failure(); } - auto trueValue = builder.boolConstant(true); - return arith::XOrIOp::create(builder, *value, trueValue).getResult(); + parameters.push_back(*value); } - case Condition::Kind::And: - case Condition::Kind::Or: { - auto lhs = emitCondition(*condition.lhs); - auto rhs = emitCondition(*condition.rhs); - if (failed(lhs) || failed(rhs)) { + + // 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 (condition.kind == Condition::Kind::And) { - return arith::AndIOp::create(builder, *lhs, *rhs).getResult(); + 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(); } - return arith::OrIOp::create(builder, *lhs, *rhs).getResult(); + 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); } - llvm_unreachable("unknown condition kind"); - } - FailureOr lookupBitValue(const Reference& 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"); + if (std::holds_alternative(it->second)) { + return error(call.loc, + "broadcasted compound gates are not supported yet"); } - const auto& registerBits = it->second; - if (bit.index == nullptr) { - return registerBits[0]; + 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(); + } } - auto index = evaluateConstant(*bit.index); - if (failed(index)) { + 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(); } - 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"); + if (const auto* compound = std::get_if(&definition)) { + return emitCompoundGate(*compound, parameters, split->targets, + split->posControls, split->negControls, + split->invert, loc); } - return registerBits[i]; + 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(); } - //===--- Gate calls ---------------------------------------------------===// - template struct ControlsAndTargets { bool invert = false; SmallVector posControls; @@ -730,8 +776,8 @@ class QCEmitter { template FailureOr> - splitControlsAndTargets(llvm::ArrayRef modifiers, - const SmallVector& operands, llvm::SMLoc loc) { + splitControlsAndTargets(ArrayRef modifiers, + const SmallVector& operands, SMLoc loc) { ControlsAndTargets result; size_t numControls = 0; for (const auto& modifier : modifiers) { @@ -760,14 +806,62 @@ class QCEmitter { } } } - result.targets = llvm::to_vector(llvm::drop_begin(operands, numControls)); + 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(parameterConstants); + for (const auto& [name, value] : zip_equal(gate.parameters, parameters)) { + parameterConstants.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, - llvm::StringRef id, + StringRef id, ValueRange parameters, - llvm::SMLoc loc) { + SMLoc loc) { const auto it = GATE_DISPATCH.find(id); if (it == GATE_DISPATCH.end()) { return error(loc, "no MLIR definition found for gate '" + id + "'"); @@ -817,151 +911,29 @@ class QCEmitter { emitModifiedGate(bodyFn, targets, posControls, negControls, invert); } - LogicalResult emitCompoundGate(const StoredGate& gate, ValueRange parameters, - ValueRange targets, ValueRange posControls, - ValueRange negControls, bool invert, - llvm::SMLoc loc) { - if (gate.parameters.size() != parameters.size() || - gate.targets.size() != targets.size()) { - return error(loc, "invalid number of arguments for compound gate"); - } + //===--- Bit reference resolution -------------------------------------===// - // 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] : - llvm::zip_equal(gate.targets, targets)) { - auto iter = llvm::find(targets, target); - if (iter == targets.end()) { - return error(loc, "target '" + targetName + "' not found in operands"); - } - targetsMap[targetName].push_back( - static_cast(std::distance(targets.begin(), iter))); - } - - ValueScope parameterScope(parameterConstants); - for (const auto& [name, value] : - llvm::zip_equal(gate.parameters, parameters)) { - parameterConstants.insert(name, value); + 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; - LogicalResult 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; - } - - /// Emit a single (non-broadcast) invocation of a gate on already-resolved - /// single-qubit @p operands. - LogicalResult - emitGateInvocation(const std::variant& definition, - llvm::StringRef id, ValueRange parameters, - llvm::ArrayRef modifiers, - const SmallVector& operands, llvm::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); + if (bit.index == nullptr) { + return registerBits[0]; } - const auto& gate = std::get(definition); - auto gateFn = resolveStandardGate(gate, id, parameters, loc); - if (failed(gateFn)) { + auto index = evaluateConstant(*bit.index); + if (failed(index)) { return failure(); } - emitStandardGate(**gateFn, parameters, split->targets, split->posControls, - split->negControls, split->invert); - return success(); - } - - 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 + "'"); - } - - 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 in order, remembering which are whole registers. If any - // operand is a register, the call is broadcast over the qubits of the - // register(s); all such 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 registers: a single invocation on the resolved qubits. - 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); - } - - if (std::holds_alternative(it->second)) { - return error(call.loc, - "broadcasted compound gates are not supported yet"); - } - - 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(); - } + 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 success(); + return registerBits[i]; } //===--- Operand resolution -------------------------------------------===// @@ -1017,9 +989,9 @@ class QCEmitter { case Expr::Kind::Int: case Expr::Kind::Float: return true; - case Expr::Kind::Ident: + case Expr::Kind::Identifier: // A declared integer constant folds; a loop variable does not. - return integerConstants.count(expr.ident) != 0; + return integerConstants.count(expr.identifier) != 0; case Expr::Kind::Neg: return isConstantIndex(*expr.lhs); case Expr::Kind::Add: @@ -1037,8 +1009,8 @@ class QCEmitter { return std::to_string(expr.intValue); case Expr::Kind::Float: return std::to_string(expr.floatValue); - case Expr::Kind::Ident: - return expr.ident.str(); + case Expr::Kind::Identifier: + return expr.identifier.str(); case Expr::Kind::Neg: return "(-" + indexKey(*expr.lhs) + ")"; case Expr::Kind::Add: @@ -1053,7 +1025,7 @@ class QCEmitter { llvm_unreachable("unknown expression kind"); } - FailureOr loadDynamicElement(llvm::StringRef name, Value memref, + FailureOr loadDynamicElement(StringRef name, Value memref, const Expr& indexExpr) { const std::string key = name.str() + "[" + indexKey(indexExpr) + "]"; if (Value cached = dynamicallyLoadedQubits.lookup(key)) { @@ -1063,13 +1035,13 @@ class QCEmitter { if (failed(index)) { return failure(); } - Value loaded = builder.memrefLoad(memref, *index); + auto loaded = builder.memrefLoad(memref, *index); dynamicallyLoadedQubits.insert(keySaver.save(key), loaded); return loaded; } FailureOr> - resolveClassicalBits(const Reference& reference) { + resolveClassicalBits(const BitReference& reference) { auto it = classicalRegisters.find(reference.identifier); if (it == classicalRegisters.end()) { return error(reference.loc, @@ -1095,7 +1067,58 @@ class QCEmitter { return bits; } - //===--- Expression lowering ------------------------------------------===// + //===--- 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) { @@ -1108,8 +1131,8 @@ class QCEmitter { return arith::ConstantOp::create(builder, builder.getF64FloatAttr(expr.floatValue)) .getResult(); - case Expr::Kind::Ident: - return resolveParameter(expr.ident, expr.loc); + case Expr::Kind::Identifier: + return resolveParameter(expr.identifier, expr.loc); case Expr::Kind::Neg: { auto operand = emitAngle(*expr.lhs); if (failed(operand)) { @@ -1141,8 +1164,8 @@ class QCEmitter { llvm_unreachable("unknown expression kind"); } - FailureOr resolveParameter(llvm::StringRef name, llvm::SMLoc loc) { - if (Value value = parameterConstants.lookup(name)) { + FailureOr resolveParameter(StringRef name, SMLoc loc) { + if (auto value = parameterConstants.lookup(name)) { return value; } // An integer constant used as an angle is materialized as an `f64`. @@ -1193,14 +1216,14 @@ class QCEmitter { return arith::DivSIOp::create(builder, *lhs, *rhs).getResult(); } } - case Expr::Kind::Ident: - if (Value value = loopVariables.lookup(expr.ident)) { + case Expr::Kind::Identifier: + if (Value value = loopVariables.lookup(expr.identifier)) { return value; } - if (integerConstants.count(expr.ident) != 0) { + if (integerConstants.count(expr.identifier) != 0) { return arith::ConstantOp::create( - builder, - builder.getIndexAttr(integerConstants.lookup(expr.ident))) + builder, builder.getIndexAttr( + integerConstants.lookup(expr.identifier))) .getResult(); } return error(expr.loc, "expected an integer expression"); @@ -1244,9 +1267,9 @@ class QCEmitter { return *lhs / *rhs; } } - case Expr::Kind::Ident: - if (integerConstants.count(expr.ident) != 0) { - return integerConstants.lookup(expr.ident); + 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: @@ -1260,17 +1283,17 @@ class QCEmitter { QCProgramBuilder builder; llvm::SourceMgr& sourceMgr; - llvm::StringSet<> declaredNames; + StringSet<> declaredNames; - ScopedValueTable parameterConstants; + ValueTable parameterConstants; ValueScope parameterConstantsScope{parameterConstants}; IntegerTable integerConstants; IntegerScope integerConstantsScope{integerConstants}; - ScopedValueTable booleanConstants; + ValueTable booleanConstants; ValueScope booleanConstantsScope{booleanConstants}; - ScopedValueTable loopVariables; + ValueTable loopVariables; ValueScope loopVariablesScope{loopVariables}; - ScopedValueTable dynamicallyLoadedQubits; + ValueTable dynamicallyLoadedQubits; ValueScope dynamicallyLoadedQubitsScope{dynamicallyLoadedQubits}; llvm::BumpPtrAllocator keyStorage; @@ -1288,11 +1311,10 @@ class QCEmitter { namespace detail { -OwningOpRef importQASM3(llvm::SourceMgr& sourceMgr, - MLIRContext* context) { +OwningOpRef translateQASM3ToQC(llvm::SourceMgr& sourceMgr, + MLIRContext* context) { const auto bufferId = sourceMgr.getMainFileID(); - const llvm::StringRef buffer = - sourceMgr.getMemoryBuffer(bufferId)->getBuffer(); + const StringRef buffer = sourceMgr.getMemoryBuffer(bufferId)->getBuffer(); Lexer lexer(buffer); QCEmitter emitter(context, sourceMgr); @@ -1301,11 +1323,11 @@ OwningOpRef importQASM3(llvm::SourceMgr& sourceMgr, emitter.initialize(); const auto status = parser.parseProgram(); - auto module = emitter.finalize(); + auto mod = emitter.finalize(); if (failed(status)) { return nullptr; } - return module; + return mod; } } // namespace detail diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp index 9484514c6c..ff4b12d5a4 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -20,18 +21,18 @@ namespace mlir::qc::detail { namespace { -[[nodiscard]] bool isIdentifierStart(char c) { +[[nodiscard]] bool canStartIdentifier(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || static_cast(c) >= 0x80; // allow UTF-8 (e.g. π, τ, ℇ) } -[[nodiscard]] bool isIdentifierContinue(char c) { - return isIdentifierStart(c) || (c >= '0' && c <= '9'); +[[nodiscard]] bool canContinueIdentifier(char c) { + return canStartIdentifier(c) || (c >= '0' && c <= '9'); } [[nodiscard]] bool isDigit(char c) { return c >= '0' && c <= '9'; } -[[nodiscard]] TokenKind keywordKind(llvm::StringRef text) { +[[nodiscard]] TokenKind keywordKind(StringRef text) { return llvm::StringSwitch(text) .Case("OPENQASM", TokenKind::OpenQASM) .Case("include", TokenKind::Include) @@ -68,7 +69,7 @@ namespace { } // namespace -llvm::StringRef describe(const TokenKind kind) { +StringRef describe(const TokenKind kind) { switch (kind) { case TokenKind::Eof: return "end of input"; @@ -143,14 +144,16 @@ void Lexer::skipTrivia() { } Token Lexer::lexIdentifierOrKeyword(const char* start) { - while (!atEnd() && isIdentifierContinue(*cur)) { + while (!atEnd() && canContinueIdentifier(*cur)) { ++cur; } - const llvm::StringRef text(start, static_cast(cur - start)); + const StringRef text(start, static_cast(cur - start)); Token token; + token.loc = SMLoc::getFromPointer(start); token.kind = keywordKind(text); - token.spelling = text; - token.loc = llvm::SMLoc::getFromPointer(start); + if (token.kind == TokenKind::Identifier) { + token.identifier = text; + } return token; } @@ -176,10 +179,9 @@ Token Lexer::lexNumber(const char* start) { ++cur; } } - const llvm::StringRef text(start, static_cast(cur - start)); + const StringRef text(start, static_cast(cur - start)); Token token; - token.loc = llvm::SMLoc::getFromPointer(start); - token.spelling = text; + token.loc = SMLoc::getFromPointer(start); if (isFloat) { token.kind = TokenKind::FloatLiteral; if (text.getAsDouble(token.floatValue)) { @@ -201,14 +203,14 @@ Token Lexer::lexString(const char* start) { ++cur; } Token token; - token.loc = llvm::SMLoc::getFromPointer(start); + token.loc = SMLoc::getFromPointer(start); if (atEnd()) { token.kind = TokenKind::Error; return token; } token.kind = TokenKind::StringLiteral; - token.spelling = - llvm::StringRef(contentStart, static_cast(cur - contentStart)); + token.stringValue = + StringRef(contentStart, static_cast(cur - contentStart)); ++cur; // consume closing quote return token; } @@ -220,9 +222,8 @@ Token Lexer::lexHardwareQubit(const char* start) { ++cur; } Token token; - token.loc = llvm::SMLoc::getFromPointer(start); - const llvm::StringRef digits(digitsStart, - static_cast(cur - digitsStart)); + 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; @@ -235,7 +236,7 @@ Token Lexer::next() { skipTrivia(); Token token; - token.loc = llvm::SMLoc::getFromPointer(cur); + token.loc = SMLoc::getFromPointer(cur); if (atEnd()) { token.kind = TokenKind::Eof; return token; @@ -244,7 +245,7 @@ Token Lexer::next() { const char* start = cur; const char c = *cur; - if (isIdentifierStart(c)) { + if (canStartIdentifier(c)) { return lexIdentifierOrKeyword(start); } if (isDigit(c)) { @@ -257,7 +258,7 @@ Token Lexer::next() { return lexHardwareQubit(start); } - // Punctuation and operators. + // Punctuation and operators const auto peek = [&](const char expected) { return (cur + 1) != end && cur[1] == expected; }; From 2d064f7f8a650c7e3d52d0cc0c82cd630c7ba5b1 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:08:41 +0200 Subject: [PATCH 15/24] Rename parameterConstants --- .../Dialect/QC/Translation/qasm3/QASM3Emitter.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 88a9e83b67..9477e783b4 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -362,7 +362,7 @@ class QCEmitter { if (failed(emitted)) { return failure(); } - parameterConstants.insert(id, *emitted); + numericConstants.insert(id, *emitted); return success(); } case VariableKind::Int: { @@ -833,9 +833,9 @@ class QCEmitter { static_cast(std::distance(targets.begin(), it))); } - ValueScope parameterScope(parameterConstants); + ValueScope parameterScope(numericConstants); for (const auto& [name, value] : zip_equal(gate.parameters, parameters)) { - parameterConstants.insert(name, value); + numericConstants.insert(name, value); } auto status = success(); @@ -1165,7 +1165,7 @@ class QCEmitter { } FailureOr resolveParameter(StringRef name, SMLoc loc) { - if (auto value = parameterConstants.lookup(name)) { + if (auto value = numericConstants.lookup(name)) { return value; } // An integer constant used as an angle is materialized as an `f64`. @@ -1285,8 +1285,8 @@ class QCEmitter { StringSet<> declaredNames; - ValueTable parameterConstants; - ValueScope parameterConstantsScope{parameterConstants}; + ValueTable numericConstants; + ValueScope numericConstantsScope{numericConstants}; IntegerTable integerConstants; IntegerScope integerConstantsScope{integerConstants}; ValueTable booleanConstants; From 0925bc35d29e127de6907256c4de2cbd14705524 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:20:24 +0200 Subject: [PATCH 16/24] Support broadcasted compound gates --- .../QC/Translation/qasm3/QASM3Emitter.cpp | 5 --- .../QC/Translation/test_qasm3_translation.cpp | 42 ++++++++++--------- mlir/unittests/programs/qasm_programs.cpp | 20 +++++++++ mlir/unittests/programs/qasm_programs.h | 8 ++++ 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 9477e783b4..7d55d8e7b1 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -709,11 +709,6 @@ class QCEmitter { call.modifiers, operands, call.loc); } - if (std::holds_alternative(it->second)) { - return error(call.loc, - "broadcasted compound gates are not supported yet"); - } - for (size_t b = 0; b < *broadcastWidth; ++b) { SmallVector operands; operands.reserve(resolvedOperands.size()); diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index 6023b0b0d8..1c5107c84c 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -118,9 +118,6 @@ static void forLoopOffsetIndex(qc::QCProgramBuilder& b) { }); } -// The qubit is loaded once per `scf.while` region (in both the before and after -// region), because a value loaded in the before region would not dominate the -// after region. static void nestedForLoopWhileOp(qc::QCProgramBuilder& b) { auto reg = b.allocQubitRegister(2); b.scfFor(0, 2, 1, [&](Value iv) { @@ -141,15 +138,23 @@ static void nestedForLoopWhileOp(qc::QCProgramBuilder& b) { }); } -// `cx q, r;` with a register `q` and a single qubit `r` broadcasts over the -// register: the register is indexed per iteration while the single qubit is -// repeated. static void broadcastRegisterAndQubit(qc::QCProgramBuilder& b) { - auto q = b.allocQubitRegister(3); - auto r = b.allocQubit(); - b.cx(q[0], r); - b.cx(q[1], r); - b.cx(q[2], r); + 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) { @@ -476,12 +481,9 @@ INSTANTIATE_TEST_SUITE_P( "NestedForLoopCtrlOpWithExtractedQubit", qasm::nestedForLoopCtrlOpWithExtractedQubit, MQT_NAMED_BUILDER(qc::nestedForLoopCtrlOpWithExtractedQubit)}, - QASM3TranslationTestCase{ - "BroadcastRegisterAndQubit", - R"qasm(OPENQASM 3.0; -include "stdgates.inc"; -qubit[3] q; -qubit r; -cx q, r; -)qasm", - MQT_NAMED_BUILDER(broadcastRegisterAndQubit)})); + 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 c0bb864740..2a8b702b88 100644 --- a/mlir/unittests/programs/qasm_programs.cpp +++ b/mlir/unittests/programs/qasm_programs.cpp @@ -860,5 +860,25 @@ for uint i in [1:3] { } )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 27f4c09a7a..b44b014418 100644 --- a/mlir/unittests/programs/qasm_programs.h +++ b/mlir/unittests/programs/qasm_programs.h @@ -407,5 +407,13 @@ extern const std::string nestedForLoopCtrlOpWithSeparateQubit; /// 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) From f3c82078e74f9a95c5a8f40599aa6b70819b6530 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:27:15 +0200 Subject: [PATCH 17/24] Add test case for declaration by measurment --- .../Dialect/QC/Translation/test_qasm3_translation.cpp | 10 ++++++++++ mlir/unittests/programs/qasm_programs.cpp | 6 ++++++ mlir/unittests/programs/qasm_programs.h | 3 +++ 3 files changed, 19 insertions(+) diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp index 1c5107c84c..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]); @@ -202,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)}, diff --git a/mlir/unittests/programs/qasm_programs.cpp b/mlir/unittests/programs/qasm_programs.cpp index 2a8b702b88..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; diff --git a/mlir/unittests/programs/qasm_programs.h b/mlir/unittests/programs/qasm_programs.h index b44b014418..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; From 3b1e39b0fd828cf09cd4c22586452382727ad950 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:40:31 +0200 Subject: [PATCH 18/24] =?UTF-8?q?=F0=9F=8E=A8=20Slightly=20improve=20imple?= =?UTF-8?q?mentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lukas Burgholzer --- .../QC/Translation/qasm3/QASM3Emitter.cpp | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 7d55d8e7b1..d4443232e1 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -10,7 +10,6 @@ #include "mlir/Dialect/QC/Translation/qasm3/QASM3Emitter.h" -#include "ir/Definitions.hpp" #include "ir/operations/OpType.hpp" #include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" #include "mlir/Dialect/QC/IR/QCOps.h" @@ -46,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -84,8 +84,8 @@ struct StoredGate { * * @details * For top-level registers, `memref` holds the backing register and `qubits` - * holds the eagerly extracted values. For compound gates, `memref` is null and - * `qubits` holds the aliased values. + * holds the eagerly extracted values. For compound gates, `memref` is @c null + * and `qubits` holds the aliased values. */ struct QubitBinding { Value memref; @@ -102,13 +102,13 @@ std::optional lookupBuiltinConstant(StringRef name, .getResult(); }; if (name == "pi" || name == "π") { - return constant(::qc::PI); + return constant(std::numbers::pi); } if (name == "tau" || name == "τ") { - return constant(::qc::TAU); + return constant(2 * std::numbers::pi); } if (name == "euler" || name == "ℇ") { - return constant(::qc::E); + return constant(std::numbers::e); } return std::nullopt; } @@ -233,7 +233,7 @@ llvm::StringMap> buildGateTable() { t.insert({name, standard->info}); } - const GateInfo mcxInfo{ + constexpr GateInfo mcxInfo{ .nControls = 0, .nTargets = 0, .nParameters = 0, .type = ::qc::OpType::X}; t["mcx"] = mcxInfo; t["mcx_gray"] = mcxInfo; @@ -402,8 +402,8 @@ class QCEmitter { if (failed(count)) { return failure(); } - const auto reg = builder.allocQubitRegister(*count); - qubitRegisters[id] = {.memref = reg.value, .qubits = reg.qubits}; + const auto [value, qubits] = builder.allocQubitRegister(*count); + qubitRegisters[id] = {.memref = value, .qubits = qubits}; } else { qubitRegisters[id] = {.memref = nullptr, .qubits = {builder.allocQubit()}}; @@ -945,13 +945,13 @@ class QCEmitter { return error(operand.loc, "unknown qubit register '" + operand.identifier + "'"); } - const auto& binding = it->second; + const auto& [memref, qubits] = it->second; if (operand.index == nullptr) { - if (binding.qubits.size() == 1) { - return std::variant>{binding.qubits[0]}; + if (qubits.size() == 1) { + return std::variant>{qubits[0]}; } - return std::variant>{binding.qubits}; + return std::variant>{qubits}; } const auto& indexExpr = *operand.index; @@ -961,18 +961,17 @@ class QCEmitter { return failure(); } const auto i = static_cast(*index); - if (i >= binding.qubits.size()) { + if (i >= qubits.size()) { return error(operand.loc, "qubit index out of bounds"); } - return std::variant>{binding.qubits[i]}; + return std::variant>{qubits[i]}; } - if (!binding.memref) { + if (!memref) { return error(operand.loc, "dynamic qubit indexing requires a qubit register"); } - auto loaded = - loadDynamicElement(operand.identifier, binding.memref, indexExpr); + auto loaded = loadDynamicElement(operand.identifier, memref, indexExpr); if (failed(loaded)) { return failure(); } From eeb94f654b64a1ac7b2b7f7917b120f97d2fac0b Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 18:26:23 +0200 Subject: [PATCH 19/24] Fix linter errors --- .../QC/Translation/qasm3/QASM3Emitter.cpp | 62 ++++++++++--------- .../QC/Translation/qasm3/QASM3Lexer.cpp | 17 ++--- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index d4443232e1..d803abca7e 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -42,9 +42,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -53,8 +55,6 @@ namespace mlir::qc { -namespace { - using qasm3::GateInfo; using detail::BitReference; @@ -66,6 +66,8 @@ using detail::Modifier; using detail::Operand; using detail::Parser; +namespace { + /** * @brief A stored compound-gate definition. * @@ -94,32 +96,15 @@ struct QubitBinding { using QubitScope = llvm::StringMap; -/// Look up a built-in numeric constant and emit it as an `f64`-typed value. -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; -} - /// 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. -llvm::StringMap buildGateDispatch() { +static llvm::StringMap buildGateDispatch() { llvm::StringMap d; // ZeroTargetOneParameter @@ -221,11 +206,8 @@ llvm::StringMap buildGateDispatch() { return d; } -/// Map from gate identifier to `QCProgramBuilder` emitter. -const llvm::StringMap GATE_DISPATCH = buildGateDispatch(); - /// Build the table mapping a gate identifier to its metadata. -llvm::StringMap> buildGateTable() { +static llvm::StringMap> buildGateTable() { llvm::StringMap> t; for (const auto& [name, gate] : qasm3::STANDARD_GATES) { const auto* standard = dynamic_cast(gate.get()); @@ -246,10 +228,34 @@ llvm::StringMap> buildGateTable() { 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. * @@ -978,7 +984,7 @@ class QCEmitter { return std::variant>{*loaded}; } - bool isConstantIndex(const Expr& expr) const { + [[nodiscard]] bool isConstantIndex(const Expr& expr) const { switch (expr.kind) { case Expr::Kind::Int: case Expr::Kind::Float: @@ -1277,7 +1283,7 @@ class QCEmitter { QCProgramBuilder builder; llvm::SourceMgr& sourceMgr; - StringSet<> declaredNames; + llvm::StringSet<> declaredNames; ValueTable numericConstants; ValueScope numericConstantsScope{numericConstants}; diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp index ff4b12d5a4..6eeddcd377 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp @@ -12,27 +12,24 @@ #include #include -#include #include -#include +#include namespace mlir::qc::detail { -namespace { - -[[nodiscard]] bool canStartIdentifier(char c) { +[[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]] bool canContinueIdentifier(char c) { +[[nodiscard]] static bool canContinueIdentifier(char c) { return canStartIdentifier(c) || (c >= '0' && c <= '9'); } -[[nodiscard]] bool isDigit(char c) { return c >= '0' && c <= '9'; } +[[nodiscard]] static bool isDigit(char c) { return c >= '0' && c <= '9'; } -[[nodiscard]] TokenKind keywordKind(StringRef text) { +[[nodiscard]] static TokenKind keywordKind(StringRef text) { return llvm::StringSwitch(text) .Case("OPENQASM", TokenKind::OpenQASM) .Case("include", TokenKind::Include) @@ -67,8 +64,6 @@ namespace { .Default(TokenKind::Identifier); } -} // namespace - StringRef describe(const TokenKind kind) { switch (kind) { case TokenKind::Eof: @@ -131,7 +126,7 @@ void Lexer::skipTrivia() { } if (c == '/' && (cur + 1) != end && cur[1] == '*') { cur += 2; - while (!atEnd() && !(*cur == '*' && (cur + 1) != end && cur[1] == '/')) { + while (!atEnd() && (*cur != '*' || (cur + 1) == end || cur[1] != '/')) { ++cur; } if (!atEnd()) { From 0daf1b59016dd12ec66113e35a4fa9a40187adc5 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 18:28:36 +0200 Subject: [PATCH 20/24] Ignore cppcoreguidelines-pro-bounds-pointer-arithmetic errors --- mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp index 6eeddcd377..e91d59bed0 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp @@ -16,6 +16,7 @@ #include +// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) namespace mlir::qc::detail { [[nodiscard]] static bool canStartIdentifier(char c) { @@ -348,3 +349,4 @@ Token Lexer::next() { } } // namespace mlir::qc::detail +// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) From 5cb1b207195acfea9a6273112c7fd5c83237e9a4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Fri, 10 Jul 2026 18:32:32 +0200 Subject: [PATCH 21/24] Remove version method --- .../mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h | 3 +-- mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h index 6f26c0d69e..dcd0379110 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h @@ -165,7 +165,6 @@ concept QASMSink = ArrayRef body, function_ref cont, double d, bool flag, NumericType numericType) { s.error(loc, str); - s.version(d); s.include(loc, str); s.numericDecl(loc, numericType, str, &expr); s.boolDecl(loc, str, &condition); @@ -374,7 +373,7 @@ class Parser { if (failed(expect(TokenKind::Semicolon))) { return failure(); } - return sink.version(version); + return success(); } //===--- Include ------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index d803abca7e..7bafa0b674 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -285,13 +285,6 @@ class QCEmitter { return emitError(location, message); } - //===--- Version ------------------------------------------------------===// - - LogicalResult version(double /*version*/) { - // The version declaration has no effect on the translation. - return success(); - } - //===--- Include ------------------------------------------------------===// LogicalResult include(SMLoc loc, StringRef filename) { From ddf947206eae0dc053dc4595fd83dbb59e0f3da1 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:49:10 +0200 Subject: [PATCH 22/24] Support output directive --- .../Dialect/QC/Translation/qasm3/QASM3Lexer.h | 1 + .../QC/Translation/qasm3/QASM3Parser.h | 22 ++- .../QC/Translation/qasm3/QASM3Emitter.cpp | 128 ++++++++++++------ .../QC/Translation/qasm3/QASM3Lexer.cpp | 1 + mlir/unittests/programs/qasm_programs.cpp | 2 +- 5 files changed, 104 insertions(+), 50 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h index 0b497effe8..398100ae00 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Lexer.h @@ -33,6 +33,7 @@ enum class TokenKind : uint8_t { CReg, Gate, Opaque, + Output, Barrier, Reset, Measure, diff --git a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h index dcd0379110..30988bc74b 100644 --- a/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h +++ b/mlir/include/mlir/Dialect/QC/Translation/qasm3/QASM3Parser.h @@ -172,7 +172,7 @@ concept QASMSink = s.boolAssign(loc, str, condition); s.classicalKind(str); s.qubitRegister(loc, str, &expr); - s.classicalRegister(loc, str, &expr); + s.classicalRegister(loc, str, &expr, flag); s.measure(loc, reference, operand); s.reset(loc, operand); s.barrier(loc, operands); @@ -285,9 +285,11 @@ class Parser { case TokenKind::Qreg: return parseQregDecl(); case TokenKind::Bit: - return parseClassicalDecl(); + return parseClassicalDecl(/*isOutput=*/false); case TokenKind::CReg: return parseCregDecl(); + case TokenKind::Output: + return parseOutputDecl(); case TokenKind::Gate: return parseGateStatement(); case TokenKind::Opaque: @@ -510,8 +512,18 @@ class Parser { return sink.qubitRegister(loc, id, size); } + /// Parse `output bit[] (= );`. + [[nodiscard]] LogicalResult parseOutputDecl() { + advance(); // output + if (current().kind != TokenKind::Bit) { + return sink.error(current().loc, + "only 'bit' registers can be declared as outputs"); + } + return parseClassicalDecl(/*isOutput=*/true); + } + /// Parse `bit[] (= );`. - [[nodiscard]] LogicalResult parseClassicalDecl() { + [[nodiscard]] LogicalResult parseClassicalDecl(bool isOutput) { const auto loc = current().loc; advance(); // bit const Expr* size = nullptr; @@ -544,7 +556,7 @@ class Parser { return failure(); } - if (failed(sink.classicalRegister(loc, id, size))) { + if (failed(sink.classicalRegister(loc, id, size, isOutput))) { return failure(); } if (measureSource) { @@ -574,7 +586,7 @@ class Parser { if (failed(expect(TokenKind::Semicolon))) { return failure(); } - return sink.classicalRegister(loc, id, size); + return sink.classicalRegister(loc, id, size, /*isOutput=*/false); } //===--- Assignment ---------------------------------------------------===// diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 6ae59b5b3d..48566036ea 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -96,6 +96,23 @@ struct QubitBinding { using QubitScope = llvm::StringMap; +/** + * @brief A declared classical register. + * + * @details + * Registers are held in a vector in declaration order, so that the order in + * which they are returned from the generated function is deterministic. + */ +struct ClassicalRegisterInfo { + /// The location of the declaration, used to anchor diagnostics. + SMLoc loc; + QCProgramBuilder::ClassicalRegister reg; + /// The measured value of each bit. Empty until the register is measured. + SmallVector bits; + /// Whether the register was declared with the `output` directive. + bool isOutput = false; +}; + /// Signature: (builder, gate operands, gate parameters). Owns the callable, as /// `GATE_DISPATCH` outlives the lambdas that build it. using GateFn = std::function; @@ -272,34 +289,30 @@ class QCEmitter { void initialize() { builder.initialize(); } - OwningOpRef finalize() { - if (outputRegisters.empty()) { + FailureOr> finalize() { + if (classicalRegisters.empty()) { return builder.finalize(); } + // Without an output directive, every classical register is returned + const bool hasOutputs = + any_of(classicalRegisters, + [](const ClassicalRegisterInfo& info) { return info.isOutput; }); + SmallVector returnValues; - for (const auto& name : outputRegisters) { - auto it = bitValues.find(name); - if (it == bitValues.end()) { - llvm::errs() << "Output register '" << name - << "' was never measured.\n"; - return nullptr; + for (const auto& info : classicalRegisters) { + if (hasOutputs && !info.isOutput) { + continue; } - - auto expectedSize = classicalRegisters[name].size; - if (it->second.size() < expectedSize) { - llvm::errs() << "Not all bits of output register '" << name - << "' have been measured.\n"; - return nullptr; + if (info.bits.empty()) { + return error(info.loc, "output register '" + StringRef(info.reg.name) + + "' is never measured"); } - for (auto bit : it->second) { - if (!bit) { - llvm::errs() << "Not all bits of output register '" << name - << "' have been measured.\n"; - return nullptr; - } - returnValues.push_back(bit); + if (info.bits.size() < static_cast(info.reg.size)) { + return error(info.loc, "not all bits of output register '" + + StringRef(info.reg.name) + "' are measured"); } + append_range(returnValues, info.bits); } builder.retype(ValueRange(returnValues).getTypes()); @@ -443,7 +456,8 @@ class QCEmitter { return success(); } - LogicalResult classicalRegister(SMLoc loc, StringRef id, const Expr* size) { + LogicalResult classicalRegister(SMLoc loc, StringRef id, const Expr* size, + bool isOutput) { if (failed(declare(loc, id))) { return failure(); } @@ -455,8 +469,10 @@ class QCEmitter { } count = *evaluated; } - classicalRegisters[id] = builder.allocClassicalBitRegister(count, id.str()); - outputRegisters.push_back(id); + classicalRegisters.push_back( + {.loc = loc, + .reg = builder.allocClassicalBitRegister(count, id.str()), + .isOutput = isOutput}); return success(); } @@ -464,37 +480,47 @@ class QCEmitter { LogicalResult measure(SMLoc loc, const BitReference& target, const Operand& operand) { - auto bits = resolveClassicalBits(target); + auto cregOrFailure = findClassicalRegister(target.loc, target.identifier); + if (failed(cregOrFailure)) { + return failure(); + } + auto* creg = *cregOrFailure; + + auto bits = resolveClassicalBits(*creg, 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); + if (creg->bits.size() <= index) { + creg->bits.resize(index + 1); } - registerBits[index] = result; + creg->bits[index] = result; } + return success(); } @@ -941,26 +967,46 @@ class QCEmitter { //===--- Bit reference resolution -------------------------------------===// + /// Look up the classical register named @p name. + FailureOr findClassicalRegister(SMLoc loc, + StringRef name) { + auto* it = + find_if(classicalRegisters, [&](const ClassicalRegisterInfo& info) { + return StringRef(info.reg.name) == name; + }); + if (it == classicalRegisters.end()) { + return error(loc, "unknown classical register '" + name + "'"); + } + return it; + } + FailureOr lookupBitValue(const BitReference& bit) { - auto it = bitValues.find(bit.identifier); - if (it == bitValues.end()) { + auto creg = findClassicalRegister(bit.loc, bit.identifier); + if (failed(creg)) { + return failure(); + } + + const auto& registerBits = (*creg)->bits; + if (registerBits.empty()) { 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]; } @@ -1068,13 +1114,9 @@ class QCEmitter { } 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; + resolveClassicalBits(const ClassicalRegisterInfo& info, + const BitReference& reference) { + const auto& creg = info.reg; SmallVector bits; if (reference.index == nullptr) { @@ -1329,9 +1371,7 @@ class QCEmitter { llvm::StringMap variableKinds; QubitScope qubitRegisters; - llvm::StringMap classicalRegisters; - SmallVector outputRegisters; - llvm::StringMap> bitValues; + SmallVector classicalRegisters; llvm::StringMap> gates; }; @@ -1352,10 +1392,10 @@ OwningOpRef translateQASM3ToQC(llvm::SourceMgr& sourceMgr, emitter.initialize(); const auto status = parser.parseProgram(); auto mod = emitter.finalize(); - if (failed(status)) { + if (failed(status) || failed(mod)) { return nullptr; } - return mod; + return std::move(*mod); } } // namespace detail diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp index e91d59bed0..1d62904afe 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Lexer.cpp @@ -41,6 +41,7 @@ namespace mlir::qc::detail { .Case("creg", TokenKind::CReg) .Case("gate", TokenKind::Gate) .Case("opaque", TokenKind::Opaque) + .Case("output", TokenKind::Output) .Case("barrier", TokenKind::Barrier) .Case("reset", TokenKind::Reset) .Case("measure", TokenKind::Measure) diff --git a/mlir/unittests/programs/qasm_programs.cpp b/mlir/unittests/programs/qasm_programs.cpp index 7e03350048..861ac0c23b 100644 --- a/mlir/unittests/programs/qasm_programs.cpp +++ b/mlir/unittests/programs/qasm_programs.cpp @@ -902,7 +902,7 @@ if (c) { h r[i]; } } -bit[1] out = measure q; +output bit[1] out = measure q; )qasm"; // --- WhileOp -------------------------------------------------------------- // From 756f80301c9485a21524ca6fe362bb0912093a80 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 13 Jul 2026 00:26:02 +0200 Subject: [PATCH 23/24] Remove special treatment of mcx and mcp --- .../QC/Translation/qasm3/QASM3Emitter.cpp | 36 ++----------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 48566036ea..6479177f4d 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -201,48 +201,18 @@ static llvm::StringMap buildGateDispatch() { 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; + llvm::StringMap> table; 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}); + table.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; + return table; } /// Look up a built-in numeric constant and emit it as an `f64`-typed value. From e154961fa249fc64098b82c20f566b995e36fcb2 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 13 Jul 2026 00:46:04 +0200 Subject: [PATCH 24/24] Support for loops with negative step --- .../Dialect/QC/Builder/QCProgramBuilder.h | 15 ++++ .../Dialect/QC/Builder/QCProgramBuilder.cpp | 5 ++ .../QC/Translation/qasm3/QASM3Emitter.cpp | 81 +++++++++++++++---- 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index dee7248db0..7befb90b0c 100644 --- a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -130,6 +130,21 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { */ Value intConstant(int64_t value); + /** + * @brief Create a constant index value + * @param value The value to store in the constant + * @return The value produced by the constant operation + * + * @par Example: + * ```c++ + * auto c = builder.indexConstant(1); + * ``` + * ```mlir + * %c = arith.constant 1 : index + * ``` + */ + Value indexConstant(int64_t value); + //===--------------------------------------------------------------------===// // Memory Management //===--------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index 3a06d4b593..68b8effe47 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -87,6 +87,11 @@ Value QCProgramBuilder::intConstant(const int64_t value) { return arith::ConstantOp::create(*this, getI64IntegerAttr(value)).getResult(); } +Value QCProgramBuilder::indexConstant(const int64_t value) { + checkFinalized(); + return arith::ConstantOp::create(*this, getIndexAttr(value)).getResult(); +} + Value QCProgramBuilder::QubitRegister::operator[](const size_t index) const { if (index >= qubits.size()) { llvm::reportFatalUsageError("Qubit index out of bounds"); diff --git a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp index 6479177f4d..21db0737f6 100644 --- a/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp +++ b/mlir/lib/Dialect/QC/Translation/qasm3/QASM3Emitter.cpp @@ -608,33 +608,80 @@ class QCEmitter { 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)) { + if (failed(startValue) || 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(); + // The magnitude of a statically negative step, or 0 if the step counts up + // or is only known dynamically. + int64_t stride = 0; + if (isConstantIndex(step)) { + auto stepConstant = evaluateConstant(step); + if (failed(stepConstant)) { + return failure(); + } + if (*stepConstant == 0) { + return error(loc, "for loops with a zero step are not supported"); + } + if (*stepConstant < 0) { + stride = -*stepConstant; + } + } + + Value resolvedStart; + Value resolvedStep; + Value resolvedStop; + Value strideValue; + if (stride != 0) { + // A negative step counts down, which `scf.for` cannot express. Count the + // iterations up instead and recompute the loop variable from the + // induction variable below, which preserves both the values the loop + // visits and the order in which it visits them. The loop runs + // `max(0, (start - stop) / stride + 1)` times. + auto zero = builder.indexConstant(0); + auto one = builder.indexConstant(1); + strideValue = builder.indexConstant(stride); + auto span = + arith::SubIOp::create(builder, *startValue, *stopValue).getResult(); + auto steps = + arith::DivSIOp::create(builder, span, strideValue).getResult(); + auto count = arith::AddIOp::create(builder, steps, one).getResult(); + auto isPositive = + arith::CmpIOp::create(builder, arith::CmpIPredicate::sgt, count, zero) + .getResult(); + resolvedStart = zero; + resolvedStop = + arith::SelectOp::create(builder, isPositive, count, zero).getResult(); + resolvedStep = one; + } else { + auto stepValue = emitIndex(step); + if (failed(stepValue)) { + return failure(); + } + resolvedStart = *startValue; + resolvedStep = *stepValue; + // Add 1 to make the stop value inclusive, as OpenQASM 3's for loops are + // inclusive. + resolvedStop = + arith::AddIOp::create(builder, *stopValue, builder.indexConstant(1)) + .getResult(); + } ValueScope loopScope(loopVariables); ValueScope loadScope(dynamicallyLoadedQubits); auto status = success(); - builder.scfFor(*startValue, inclusiveStop, *stepValue, [&](Value iv) { - loopVariables.insert(variable, iv); + builder.scfFor(resolvedStart, resolvedStop, resolvedStep, [&](Value iv) { + Value resolvedIv = iv; + if (stride != 0) { + auto offset = + arith::MulIOp::create(builder, iv, strideValue).getResult(); + resolvedIv = + arith::SubIOp::create(builder, *startValue, offset).getResult(); + } + loopVariables.insert(variable, resolvedIv); status = body(); }); return status;