Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 36 additions & 27 deletions source/compiler/qsc_openqasm_compiler/README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,50 @@
# Q# QASM Compiler
# OpenQASM 3 to Q# Compiler

This crate implements a semantic transformation from OpenQASM to Q#. At a high level it parses the OpenQASM program (and all includes) into an AST. Once this AST is parsed, it is compiled into Q#'s AST.
Compiles [OpenQASM 3](https://openqasm.com/) programs into Q# AST, part of the [Microsoft Quantum Development Kit](https://github.com/microsoft/qdk).

Once the compiler gets to the AST phase, it no longer cares that it is processing Q#. At this point all input is indistinguishable from having been given Q# as input. This allows us to leverage capability analysis, runtime targeting, residual computation, partial evaluation, and code generation to the input.
## Overview

## Process Overview
This crate transforms OpenQASM 3 semantic AST (produced by `qsc_openqasm_parser`) into a Q# AST (`Package`). After this transformation, the result is indistinguishable from native Q# source input, enabling the full Q# compilation pipeline—capability analysis, runtime targeting, partial evaluation, and QIR code generation—to be applied.

The OpenQASM code is parsed with their own lexer/parser and any errors are returned immediately before we get into further compilation. The OpenQASM parsing library hard-codes the file system into the types and parsing. Additionally, the library panics instead of surfacing `Result`. Because of this, there is a custom layer built to do the parsing so that the file system is abstracted with the `SourceResolver` trait and results with detailed errors are propagated instead of crashing.
## Compiler Configuration

While it would be nice to use their semantic library to analyze and type check the code, they are missing many language features that are only available in the AST and the semantic library panics instead of pushing errors.
The compilation behavior is controlled by `CompilerConfig`:

With the source lexed and parsed, we can begin compilation. The program is compiled to the Q# AST. The OpenQASM ASG would be great to use for program as a validation pass once it is more developed, but for now we try to surface OpenQASM semantic/type errors as we encounter them. It is difficult to determine if a type error is from the AST compilation or from the source program as the implicit/explicit casting and type promotion rules are very complicated.
- **`QubitSemantics`** — `QSharp` or `Qiskit`: controls qubit allocation and reset semantics
- **`OutputSemantics`** — `Qiskit`, `OpenQasm`, or `ResourceEstimation`: controls how program outputs are structured
- **`ProgramType`** — `File`, `Operation`, or `Fragments`: controls the shape of the generated Q# AST

As we process the AST we map the spans of the OpenQASM statements/expressions/literals to their corresponding Q# AST nodes. Additionally, we map the input source code (and it’s loaded externals) into a SourceMap with entry expressions. At this point we have enough information to hand off the rest of compilation to the Q# compiler.
## Semantic Translation

## Semantics
The two languages have significant differences that the compiler handles during transformation:

The two languages have many differences, and insofar as possible, the compilation preserves the source language semantics.
- **Qubit management:**
- Q# assumes qubits are in the |0⟩ state when allocated; OpenQASM does not
- Q# requires qubits to be reset to |0⟩ when released; OpenQASM does not
- **Variable initialization:** Q# requires explicit initialization; OpenQASM allows implicit initialization
- **Type casting:** Q# requires explicit conversions; OpenQASM allows implicit C99-style casting and promotion
- **Type system:** Q# has no unsigned integers or angle type; all integers are signed

- OpenQASM and Q# have different qubit management semantics.
- Q# assumes that qubits are in the |0⟩ state when they are allocated.
- OpenQASM does not make this assumption and qubits start in an undefined state.
- Q# requires that qubits are reset to the |0⟩ state when released.
- OpenQASM does not require qubits to be in a specific state at the end of execution.
- Q# does no allow for variables to be uninitialized. All initialization is explicit.
- OpenQASM allows for implicit initialization.
- Q# does not allow for implicit casting or promotion of types. All conversions must be explicit.
- OpenQASM allows for implicit casting and promotion of types following C99 and custom rules.
- Q# does not have unsigned integers or an angle type. All integers are signed.
### QIR Constraints

QIR specific semantic constraints:
OpenQASM output registers may have unpopulated measurement indexes. In QIR, `Result`s can only be acquired through measurement, so all output register entries must be measured into or a code generation error occurs.

- OpenQASM output registers are declared with a fixed size and not all of the indexes may be populated with measurements. In QIR, `Result`s can only ever be acquired through measurement. So if all entries in an output register aren't measured into, a code generation error will occur.
### Implementation Details

Semantic details
- Gates are implemented as lambda expressions capturing `const` variables from the global scope
- Exception: `@SimulatableIntrinsic`-annotated gates are defined as full local `operation`s (lambdas cannot capture in that context)
- OpenQASM `const` is modeled as Q# immutable `let` bindings

- Gates are implemented as lambda expressions capturing const variables from the global scope.
- There is an exception when using `@SimulatableIntrinsic`. Those are defined as full local `operation`s as they are not allowed to capture.
- We can change this in the future by copying `const` value decls into the `gate`/`function` scope, but this would require implementing a lot of inlining and partial evaluation which we already do in the compiler.
- OpenQASM `const` is modeled as Q# immutable bindings. This isn't fully correct as Q# can build `let` bindings with both mutable and immutable values, but as the translation is one way, we can do this mapping and know that any `const` value is assigned to an immutable `let` binding. Anything else isn't guaranteed to be immutable. There are additional semantic checks as well to ensure that const declarations are not initialized to non-const values.
## Relationship to Other Crates

```text
OpenQASM source → qsc_openqasm_parser → Semantic AST → [qsc_openqasm_compiler] → Q# AST → qsc
```

This crate depends on `qsc_openqasm_parser` for input and is consumed by:

- `qsc` — Core compiler facade

## License

MIT
72 changes: 45 additions & 27 deletions source/compiler/qsc_openqasm_parser/README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,59 @@
# Q# QASM Compiler
# OpenQASM 3 Parser

This crate implements a semantic transformation from OpenQASM to Q#. At a high level it parses the OpenQASM program (and all includes) into an AST. Once this AST is parsed, it is compiled into Q#'s AST.
A lexer, parser, and semantic analyzer for [OpenQASM 3](https://openqasm.com/) programs.

Once the compiler gets to the AST phase, it no longer cares that it is processing Q#. At this point all input is indistinguishable from having been given Q# as input. This allows us to leverage capability analysis, runtime targeting, residual computation, partial evaluation, and code generation to the input.
## Overview

## Process Overview
This crate provides a complete front-end for processing OpenQASM 3 source code. It operates entirely in the OpenQASM domain and produces a typed semantic AST suitable for further compilation or analysis.

The OpenQASM code is parsed with their own lexer/parser and any errors are returned immediately before we get into further compilation. The OpenQASM parsing library hard-codes the file system into the types and parsing. Additionally, the library panics instead of surfacing `Result`. Because of this, there is a custom layer built to do the parsing so that the file system is abstracted with the `SourceResolver` trait and results with detailed errors are propagated instead of crashing.
The processing pipeline has two stages:

While it would be nice to use their semantic library to analyze and type check the code, they are missing many language features that are only available in the AST and the semantic library panics instead of pushing errors.
1. **Lexing & Parsing** — Tokenizes and parses OpenQASM 3 source into a syntax tree (`QasmParseResult`)
2. **Semantic Analysis** — Lowers the syntax tree into a semantic AST with type checking, symbol resolution, and const evaluation (`QasmSemanticParseResult`)

With the source lexed and parsed, we can begin compilation. The program is compiled to the Q# AST. The OpenQASM ASG would be great to use for program as a validation pass once it is more developed, but for now we try to surface OpenQASM semantic/type errors as we encounter them. It is difficult to determine if a type error is from the AST compilation or from the source program as the implicit/explicit casting and type promotion rules are very complicated.
### Source Resolution

As we process the AST we map the spans of the OpenQASM statements/expressions/literals to their corresponding Q# AST nodes. Additionally, we map the input source code (and it’s loaded externals) into a SourceMap with entry expressions. At this point we have enough information to hand off the rest of compilation to the Q# compiler.
OpenQASM programs can include other files via `include` statements. This crate abstracts filesystem access behind the `SourceResolver` trait, enabling:

## Semantics
- In-memory source resolution for editors and notebooks
- Custom filesystem backends
- Include cycle detection

The two languages have many differences, and insofar as possible, the compilation preserves the source language semantics.
### OpenQASM Standard Library Types

- OpenQASM and Q# have different qubit management semantics.
- Q# assumes that qubits are in the |0⟩ state when they are allocated.
- OpenQASM does not make this assumption and qubits start in an undefined state.
- Q# requires that qubits are reset to the |0⟩ state when released.
- OpenQASM does not require qubits to be in a specific state at the end of execution.
- Q# does no allow for variables to be uninitialized. All initialization is explicit.
- OpenQASM allows for implicit initialization.
- Q# does not allow for implicit casting or promotion of types. All conversions must be explicit.
- OpenQASM allows for implicit casting and promotion of types following C99 and custom rules.
- Q# does not have unsigned integers or an angle type. All integers are signed.
The crate includes implementations of OpenQASM-native types:

QIR specific semantic constraints:
- `Angle` — Fixed-point angle representation
- `Complex` — Complex number type
- `Duration` — Timing duration type

- OpenQASM output registers are declared with a fixed size and not all of the indexes may be populated with measurements. In QIR, `Result`s can only ever be acquired through measurement. So if all entries in an output register aren't measured into, a code generation error will occur.
## Usage

Semantic details
Parse and semantically analyze an OpenQASM 3 program:

- Gates are implemented as lambda expressions capturing const variables from the global scope.
- There is an exception when using `@SimulatableIntrinsic`. Those are defined as full local `operation`s as they are not allowed to capture.
- We can change this in the future by copying `const` value decls into the `gate`/`function` scope, but this would require implementing a lot of inlining and partial evaluation which we already do in the compiler.
- OpenQASM `const` is modeled as Q# immutable bindings. This isn't fully correct as Q# can build `let` bindings with both mutable and immutable values, but as the translation is one way, we can do this mapping and know that any `const` value is assigned to an immutable `let` binding. Anything else isn't guaranteed to be immutable. There are additional semantic checks as well to ensure that const declarations are not initialized to non-const values.
```rust
use qsc_openqasm_parser::semantic;

let source = r#"
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
h q[0];
cx q[0], q[1];
bit[2] c;
c = measure q;
"#;

let result = semantic::parse(source, "bell.qasm");
if result.has_errors() {
for error in result.all_errors() {
eprintln!("{error}");
}
} else {
println!("parsed {} statements", result.program.statements.len());
}
```

## License

MIT
Loading