diff --git a/source/compiler/qsc_openqasm_compiler/README.md b/source/compiler/qsc_openqasm_compiler/README.md index bbb4e2769f..658df5be57 100644 --- a/source/compiler/qsc_openqasm_compiler/README.md +++ b/source/compiler/qsc_openqasm_compiler/README.md @@ -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 diff --git a/source/compiler/qsc_openqasm_parser/README.md b/source/compiler/qsc_openqasm_parser/README.md index bbb4e2769f..bab3954b6f 100644 --- a/source/compiler/qsc_openqasm_parser/README.md +++ b/source/compiler/qsc_openqasm_parser/README.md @@ -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