Skip to content

🧪 Establish a typed OpenQASM 3 frontend#1910

Draft
burgholzer wants to merge 30 commits into
mainfrom
agent/oq3-foundation
Draft

🧪 Establish a typed OpenQASM 3 frontend#1910
burgholzer wants to merge 30 commits into
mainfrom
agent/oq3-foundation

Conversation

@burgholzer

@burgholzer burgholzer commented Jul 15, 2026

Copy link
Copy Markdown
Member

🤖 AI text below 🤖

Summary

This draft is an architecture workbench for a typed OpenQASM 3 frontend. It starts from main, keeps the established QuantumComputation importer independent as a behavioral oracle, and builds on MQT Core's handwritten scanner and parser instead of introducing ANTLR.

The production MLIR OpenQASM entry point now uses the staged architecture end to end:

  1. parseOpenQASM produces an opaque parsed program and syntax diagnostics;
  2. analyzeOpenQASM resolves the currently supported source semantics into an MLIR-independent, value-oriented TypedProgram;
  3. emitOQ3 maps the typed program to verified builtin/QC/OQ3 IR without repeating source typing;
  4. LowerOQ3ToQC reports target-capability limitations;
  5. translateQASM3ToQC is only a convenience wrapper around those stages.

The previous 1,114-line direct AST-to-QC visitor has been deleted. There is no legacy MLIR fallback path; Git history and the independent circuit importer provide the comparison points.

The living implementation plan and progress record is in .agent/plans/oq3-foundation.md.

Current implementation

  • The parser and semantic stages are testable without an MLIR context. Source filenames and line/column diagnostics survive into the typed model and emitted MLIR locations.
  • The adapter currently reuses the handwritten parser's constant-evaluation and type-checking passes while constructing its own typed program. Replacing the shared-pointer syntax tree and separate legacy passes remains a later parser milestone; MLIR emission is already a separate walk.
  • Gate definitions and applications, ordered inv/ctrl/negctrl/pow modifiers, broadcasting, qubit and bit registers, measurement, reset, barrier, and current conditional behavior are represented before lowering.
  • The parser now uses one precedence-climbing operator table matching OpenQASM: power is right-associative **, ^ is bitwise XOR, and modulo, shifts, comparisons, equality, bitwise, and logical operators have their specified precedence. Dynamic sin, cos, tan, exp, ln, and sqrt expressions emit builtin MLIR math operations.
  • A canonical MLIR gate catalog drives semantic lookup, OQ3 declarations, and QC lowering. Compatibility mode remains the default so the architecture replacement does not remove established native-gate convenience.
  • Strict mode is explicit: only language gates are implicit, stdgates.inc must be included for standard-library gates, and unavailable standard names can still be defined by the source program.
  • cu, cu3, and cu1 lower natively. Four-parameter cu preserves its control-qubit phase as well as controlled U, and inverse aliases such as iswapdg preserve inversion.
  • Alternating positive and negative controls lower through one recursive rule. Every negative control is flipped before and after the outermost modifier tree, all controls are nested, and the existing QC cleanup combines adjacent nested controls when compact multi-control IR is useful.
  • Valid pow modifiers, including dynamic operands, remain typed OQ3. QC lowering currently emits a target-capability diagnostic until the downstream power support is available.
  • Explicit OPENQASM 3.0;, explicit OPENQASM 3.1;, and versionless inputs select the same maintained OpenQASM 3 profile. OpenQASM 2 remains a compatibility mode.
  • The ANTLR demonstrator and generated sources remain only in Git history.

Validation

  • 98 handwritten-parser tests, including the complete scalar binary-operator precedence and associativity table
  • 4 parser-independent OQ3 verifier/lowering tests
  • 12 dedicated parse, semantic, emission, include-policy, modifier, and target-boundary tests
  • 224 QC translation tests, including all 117 existing source-translation fixtures through the new staged path
  • 116 downstream compiler pipeline tests
  • clean repository lint suite and diff whitespace checks

Next milestones

  • Pin the upstream OpenQASM grammar and conformance corpus as repository test ground truth without adding generated parser code or a parser-generator build dependency.
  • Refactor scanner and parser ownership around compact source spans, recovery, and arena-owned syntax nodes while retaining the completed precedence-climbing expression core.
  • Expand the typed program and semantic analyzer to classical declarations and assignments, inputs/outputs, casts, dynamic operators, while, and inclusive for while preserving state correctly.
  • Add the defensive whole-program OQ3 verifier for cross-operation invariants and broaden strict include/version diagnostics.
  • Add conformance, differential, dominance, overflow, and stage-specific performance evidence.

The reported 80× speedup in Qiskit's openqasm3_parser is architectural inspiration, not an acceptance target. Parsing, semantic analysis, and MLIR emission will be benchmarked separately and should scale approximately linearly.

The textual OQ3 form remains experimental and carries no compatibility guarantee at this stage.

@burgholzer
burgholzer force-pushed the agent/oq3-foundation branch from dc72a39 to 0535647 Compare July 15, 2026 13:50
@burgholzer burgholzer added QDMI Anything related to QDMI feature New feature or request MLIR Anything related to MLIR and removed QDMI Anything related to QDMI labels Jul 15, 2026
@burgholzer burgholzer added this to the MLIR Support milestone Jul 15, 2026

@denialhaag denialhaag left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very excited if we can get this to work nicely! 🙂

The comments below are a brain dump and not necessarily a structured review. I'm aware that some of the points I'm raising might have been planned for future iterations, but I wanted to mention everything that came to mind.

*/
struct OpenQASMLoweringOptions {
/// Whether the selected target can diagnose a zero step at runtime.
bool supportsRuntimeAssertions = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flag is not used anywhere. Might as well delete it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason that this is not a conversion? See, for example, qc-to-qco for more information on how to define a conversion.

Comment thread mlir/unittests/Dialect/OQ3/test_oq3.cpp Outdated
Comment on lines +156 to +171
TEST_F(OQ3Test, LowersEveryCanonicalGateCatalogEntry) {
for (const auto& gate : oq3::getGateCatalog()) {
auto module = buildGateApplication(gate.name, gate.parameterCount,
gate.qubitCount(), gate.qubitCount());
ASSERT_TRUE(succeeded(verify(module.get()))) << gate.name.str();

PassManager manager(context.get());
manager.addPass(oq3::createLowerOQ3ToQCPass());
ASSERT_TRUE(succeeded(manager.run(module.get()))) << gate.name.str();
EXPECT_TRUE(succeeded(verify(module.get()))) << gate.name.str();

bool hasOQ3GateApplication = false;
module->walk([&](oq3::ApplyGateOp) { hasOQ3GateApplication = true; });
EXPECT_FALSE(hasOQ3GateApplication) << gate.name.str();
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test wouldn't be necessary if the lowering were implemented as a conversion that marks the OQ3 dialect as illegal. There are more tests like this that wouldn't be necessary.

Comment thread cmake/ExternalDependencies.cmake Outdated
Comment on lines 32 to 33

endif()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
endif()
endif()

Comment thread docs/mlir/OQ3.md Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right. See, e.g., QC.md in the same directory for how to do it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a general comment, but I noticed it here:

mqt_mlir_target_use_project_options(${target_name})
endfunction()

add_subdirectory(programs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you copy over all the test cases added in #1862 and amend test_qasm3_translation.cpp accordingly?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is relying on the legacy parser. Do we want that? For example, we do not get the benefit of the MLIR/LLVM error handling. This was one of the triggers for one of the refactors in #1862.

Comment thread mlir/lib/Target/OpenQASM/Frontend.cpp Outdated
Comment on lines +935 to +936
std::istringstream input(
std::string(std::string_view(contents.data(), contents.size())));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here: We wanted to avoid using std::istringstream in #1862 and instead went with LLVM concepts.

@denialhaag denialhaag Jul 15, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As implemented right now, I don't think the parser can handle for and while loops, right? This is another shortcoming of the legacy parser. The lowering of for and while loops in LowerOQ3ToQC.cpp would never be reached.

Comment thread mlir/lib/Dialect/OQ3/IR/OQ3Ops.cpp Outdated
return success();
}

LogicalResult ApplyGateOp::verify() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing I just noticed: What's the overlap of this function with analyzeGateApplication in Frontend.cpp? Are we (partially) doing semantic analysis twice now?

The same might apply to the other OQ3 operations.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If semantic analysis does not rely on MLIR but can solely be done by SemanticAnalyzer in Frontend.cpp, how necessary is the OQ3 dialect in the first place? Could the SemanticAnalyzer already reject everything we don't support right now? Loosely speaking, could the functions in LowerOQ3ToQC.cpp just be copied into OpenQASM.cpp, and we achieve the exact same goal?

burgholzer and others added 21 commits July 16, 2026 17:08
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Retain the typed OQ3 dialect and add native lowering coverage for the controlled U gate family while removing the generated parser demonstrator and its dependency.

Assisted-by: GPT-5 via Codex
Record the rebased branch publication and draft pull request refresh in the living ExecPlan.

Assisted-by: GPT-5 via Codex
Introduce an MLIR-independent typed frontend, emit verified OQ3, and lower the production translation path through the experimental dialect. Preserve legacy compatibility gates while making strict standard-library availability explicit.

Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Replace the legacy stream bridge with an LLVM-native parser and semantic pipeline, add structured source state and target lowering, port the behavioral fixtures, and clean the resulting design.

Assisted-by: GPT-5 via Codex
Emit verified QC directly from the staged frontend, exercise accepted programs through QCO, Jeff, and QIR, and preserve observable Jeff entry-point results.

Assisted-by: GPT-5 via Codex
Reject runtime-only indices at the QC boundary, make custom-gate capability checks transitive, and strengthen structured conversion and end-to-end result preservation tests.

Assisted-by: GPT-5 via Codex
Make static-index analysis sound across control flow, enforce one projected-emission budget, reject checked integer constructs that cannot survive Jeff, and strengthen full-chain and native conversion regressions.

Assisted-by: GPT-5 via Codex
Signed-off-by: Lukas Burgholzer <burgholzer@me.com>
Remove unused resolved-program state and reconcile the living plan with the retained conversion evidence and behavior-driven coverage decision.

Assisted-by: GPT-5 via Codex
@mergify mergify Bot added the conflict label Jul 16, 2026
Assisted-by: GPT-5 via Codex
@burgholzer
burgholzer force-pushed the agent/oq3-foundation branch from 2b422b8 to 21bb4bd Compare July 16, 2026 15:18
@mergify mergify Bot removed the conflict label Jul 16, 2026
Assisted-by: GPT-5 via Codex

@denialhaag denialhaag left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like where this is going! 🙂

You can find some more quick feedback below. Again, this is not necessarily a structured review.

Comment on lines +1770 to +1771
void emitFor(const frontend::ForStatement& loop, ValueRange gateParameters,
ValueRange gateQubits) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a simplified version of this if start, stop, and step are statically known. Otherwise, we cannot convert any for loop to jeff because it lacks an assert operation.

case frontend::ComparisonKind::NotEqual:
return arith::CmpFPredicate::UNE;
case frontend::ComparisonKind::Less:
return arith::CmpFPredicate::OLT; // spellchecker:disable-line

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more sense to add this to [tool.typos.default.extend-words].

Comment thread docs/mlir/OpenQASM.md Outdated
Comment on lines +30 to +32
“Adaptive plus Jeff” means the tested public path from QC through optimized QCO,
Jeff byte serialization and deserialization, back to QC, and finally to Adaptive
QIR. Base refers to direct production of the QIR Base Profile.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use "jeff" whenever "`" is recognized (e.g., in Markdown files and in Docstrings). Otherwise, use "jeff" (e.g., in error messages).

Comment thread .agent/plans/oq3-foundation.md Outdated
Comment on lines +151 to +156
- Observation: the Jeff representation cannot preserve the frontend's runtime
`cf.assert` bounds checks. Evidence: genuinely runtime-dynamic indexing
reaches verified QC and QCO but QCO-to-Jeff rejects `cf.assert`; an index
resolved by cleanup traverses the complete chain. The direct emitter now
accepts only indices proven resolvable by conservative scalar dataflow and
reports a source-located target diagnostic for the remainder.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a good solution. Dynamic indices should not be rejected at the time of QC emission. At most, reject the conversion to jeff.

Comment on lines +332 to +349
[[nodiscard]] bool
reportRuntimeDynamicIndex(const oq3::frontend::SourceLocation& source) const {
llvm::errs()
<< source.filename << ':' << source.line << ':' << source.column
<< ": OpenQASM QC emission error: runtime-dynamic indexing is not "
"supported by the complete QC/QCO/Jeff/QIR compiler path.\n";
return false;
}

[[nodiscard]] bool
reportRuntimeIntegerCheck(const oq3::frontend::SourceLocation& source) const {
llvm::errs()
<< source.filename << ':' << source.line << ':' << source.column
<< ": OpenQASM QC emission error: checked integer arithmetic "
"and ranges are not supported by the complete QC/QCO/Jeff/QIR "
"compiler path.\n";
return false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As pointed out elsewhere, one boundary (in this case, jeff) should not reject the emission.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move all header files from this directory to mlir/include/mlir/Target/OpenQASM?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment: We usually use size_t instead of std::size_t. The same also holds for int64_t and so on.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain these changes? Did you find a bug in the current version of the conversion? If so, it might be nice to create a separate PR for this fix so that we can merge it immediately.

Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Assisted-by: GPT-5 via Codex
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request MLIR Anything related to MLIR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants