From dcf80a59745e4e8528e4024a674c0f8939cc912c Mon Sep 17 00:00:00 2001 From: cat Date: Sat, 30 Mar 2024 16:29:20 +0300 Subject: [PATCH] push --- .gitignore | 4 + Cargo.toml | 19 + LICENSE | 21 + README.md | 127 ++++ build.rs | 3 + src/ast.rs | 634 ++++++++++++++++++ src/emulator.rs | 1480 +++++++++++++++++++++++++++++++++++++++++++ src/grammar.lalrpop | 311 +++++++++ src/lib.rs | 0 src/main.rs | 188 ++++++ 10 files changed, 2787 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.rs create mode 100644 src/ast.rs create mode 100644 src/emulator.rs create mode 100644 src/grammar.lalrpop create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95e9a9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target/ +/Cargo.lock +/test.qASM +/mem.bin diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7416dee --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "q_asm" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitvec = "1.0.1" +clap = { version = "4.2.7", features = ["derive"] } +codespan-reporting = "0.11.1" +lazy_static = "1.4.0" +nalgebra = "0.32.2" +num-traits = "0.2.15" +rand = "0.8.5" +lalrpop-util = { version = "0.20.0", features = ["lexer"] } + +[build_dependencies] +lalrpop = { version = "0.20.0", features = ["lexer"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5d99210 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 zkdream + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..246128f --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +``` +qbits n +cbits n +qregs n +cregs n +mem n + + +``` + +The order of the headers is important and should not be changed. The `qbits` header specifies the number of +qubits in each quantum register. The `cbits` header specifies the number of classical bits in each classical +register. The `qregs` header specifies the number of quantum registers. The `cregs` header specifies the number +of classical registers. The `n` after each header is any integer number. Operands/arguments for all +instructions are delimited by spaces and not commas. + +Code execution should always end at a `hlt` instruction. If the emulator reaches the end of code but does not +encounter a `hlt` instruction, it will end with a "PC out of bounds" error. + +## Quantum Instructions +The general format for quantum instructions are: +``` +op qn +``` +Where `op` is the name/opcode, `qn` specifies a specific qubit `n` of the currently selected quantum register. +`` can include more qubits as arguments, or in the case of some instructions, a rotation expressed +as a rational multiple of pi, in the format `[n]pi[/n]`, where `n` can be any integer number, and items +in `[]` are optional. Quantum registers can be selected via the `qsel` instruction, which has the general format +`qsel qrn` where `n` is any non-negative number. + +List of currently implemented quantum instructions: + +| Quantum Gate | Instruction name | Syntax example | Explanation | +| ------------------- | ---------------- | ------------------- | ----------- | +| Hadamard | h | `h q0` | Applies a Hadamard to qubit 0 | +| CNOT | cnot | `cnot q0 q1` | Applies a CNOT to qubit 1 with qubit 0 being the control | +| CCNOT/Toffoli | ccnot | `ccnot q0 q1 q2` | Applies a Toffoli to qubit 2 with qubit 0 and qubit 1 being the controls | +| Pauli X | x | `x q0` | Applies a Pauli X to qubit 0 | +| Pauli Y | y | `y q0` | Applies a Pauli Y to qubit 0 | +| Pauli Z | z | `z q0` | Applies a Pauli Z to qubit 0 | +| Rx | rx | `rx q0 pi/3` | Rotates the statevector of qubit 0 by pi/3 radians along X axis on bloch sphere | +| Ry | ry | `ry q0 pi` | Rotates the statevector of qubit 0 by pi radians along Y axis on bloch sphere | +| Rz | rz | `rz q0 pi/4` | Rotates the statevector of qubit 0 by pi/4 radians along Z axis on bloch sphere | +| U gate | u | `u q0 pi pi/3 pi/6` | Rotates the statevector of qubit 0 by the 3 Euler angles pi, pi/3, pi/6 | +| S gate | s | `s q0` | Applies an S gate to qubit 0 | +| T gate | t | `t q0` | Applies a T gate to qubit 0 | +| S-dagger | sdg | `sdg q0` | Applies a S-dagger or the inverse of S gate to qubit 0 | +| T-dagger | tdg | `tdg q0` | Applies a T-dagger or the inverse of T gate to qubit 0 | +| Phase gate | p | `p q0 pi/3` | Applies a relative phase of pi/3 radians to qubit 0 | +| Controlled Hadamard | ch | `ch q0 q1` | Applies a controlled Hadamard to qubit 1 with qubit 0 being the control | +| Controlled Pauli Y | cy | `cy q0 q1` | Applies a controlled Pauli Y to qubit 1 with qubit 0 being the control | +| Controlled Pauli Z | cz | `cz q0 q1` | Applies a controlled Pauli Z to qubit 1 with qubit 0 being the control | +| Controlled Phase | cp | `cp q0 q1 pi/2` | Applies a controlled Phase gate to qubit 1 of pi/2 radians with qubit 0 being the control | +| Swap | swap | `swap q0 q1` | Swaps the state of qubits 0 and 1 | +| Square Root NOT | sqrtx | `sqrtx q0 ` | Applies a sqrt(NOT)/sqrt(X) to qubit 0 | +| Square Root Swap | sqrtswp | `sqrtswp q0 q1` | Applies a sqrt(Swap) to qubits 0 and 1, halfway swapping their state | +| Controlled Swap | cswap | `cswap q0 q1 q2` | Swaps the state of qubits 1 and 2 with qubit 0 being the control | +| Measure | m | `m q0 cr1 c3` | Measures the state of qubit 0 into 3rd bit of classical register 1 | + +*Note: Remove any measurement operations before running the emulator with `--print-state` (or `-p`) as the emulator does not ignore them currently when run with that flag set* + +## Classical Instructions +General format for classical instructions are: +``` +op +``` +Where `op` is the name/opcode, operands may include `crn`, which specifies a specific classical register `n`, or +an immediate literal value (for now non-negative due to not implemented in parser yet) Other than these differences, +they behave basically the same as any other assembly language instructions. + +List of currently implemented classical instructions: + +*Note: The value of a register refers to the value stored in the register. The value of an immediate is the immediate number itself.* + +*Note 2: An operand can either be a register or immediate unless a restriction is specified.* + +| Instruction name | Description | +| ---------------- | ----------- | +| add | op1 = op2 + op3. op1 is always a register. | +| sub | op1 = op2 - op3. op1 is always a register. | +| mult | op1 = op2 * op3. op1 is always a register. All values are treated unsigned. | +| umult | op1 = (op2 * op3) >> (cbits/2). op1 is always a register. All values are treated unsigned. | +| div | op1 = op2 / op3. op1 is always a register. Performs integer division. All values are treated unsigned. | +| smult | op1 = op2 * op3. op1 is always a register. All values are treated signed. | +| sumult | op1 = (op2 * op3) >> (cbits/2). op1 is always a register. All values are treated signed. | +| sdiv | op1 = op2 / op3. op1 is always a register. Performs integer division. All values are treated signed. | +| not | op1 = ~op2. op1 is always a register. | +| and | op1 = op2 & op3. op1 is always a register. | +| or | op1 = op2 \| op3. op1 is always a register. | +| xor | op1 = op2 ^ op3. op1 is always a register. | +| nand | op1 = ~(op2 & op3). op1 is always a register. | +| nor | op1 = ~(op2 \| op3). op1 is always a register. | +| xnor | op1 = ~(op2 ^ op3). op1 is always a register. | + +## Misc. Instructions +These instructions are here because. + +| Instruction name | Description | +| ---------------- | ----------- | +| qsel | Selects a quantum register so that proceeding quantum instructions act on that qreg. | +| cmp | Updates flags based on comparing values in op1 and op2. op1 is always a register. | +| jmp | Unconditionally jump to a label. | +| jeq | Jump to label if comparison resulted in EQ flag set. | +| jne | Jump to label if comparsion did not result in EQ flag set. | +| jg | Jump to label if comparison resulted in GREATER flag set. | +| jge | Jump to label if comparison resulted in GREATER or EQ flag set. | +| jl | Jump to label if comparison resulted in LESSER flag set. | +| jle | Jump to label if comparison resulted in LESSER or EQ flag set. | +| hlt | Halt the program. | + +# Examples +This program simulates the $\ket{\Phi^+}$ bell state: +``` +qbits 2 +cbits 2 +qregs 1 +cregs 1 + +qsel qr0 +h q0 +cnot q0 q1 + +m q0 cr0 c0 +m q1 cr0 c1 + +hlt +``` diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..ca5c283 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + lalrpop::process_root().unwrap(); +} diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..74c5576 --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,634 @@ +use std::{collections::HashMap, fmt::Display, ops::Range}; + +use lalrpop_util::{lalrpop_mod, ParseError}; +lalrpop_mod!(grammar); + +use codespan_reporting::diagnostic::{Diagnostic, Label}; + +type CGateInfo = (usize, Vec<(String, IdentType)>, Vec); + +#[derive(Debug, Clone)] +pub struct Program { + // (qbits, cbits, qregs, cregs, mem_size) + pub headers: (usize, usize, usize, usize, usize), + pub custom_gates: HashMap, + pub instructions: Vec, +} + +#[derive(Debug, Clone)] +pub struct SourceSpan { + pub file: usize, + pub span: Range, +} +impl SourceSpan { + pub fn new(file: usize, span: Range) -> Self { + Self { file, span } + } +} + +type ParseResult<'a> = Result; + +pub fn parse(code: &str, file: usize) -> (ParseResult, Vec>) { + let mut errs = Vec::new(); + let ast = grammar::ProgramParser::new().parse(file, &mut errs, code); + let mut diags = Vec::new(); + + for err in errs { + let diag = Diagnostic::error(); + match err { + ParseError::InvalidToken { location } => diags.push( + diag.with_message("Invalid token") + .with_labels(vec![Label::primary(file, location..location)]), + ), + + ParseError::UnrecognizedEof { location, .. } => diags.push( + diag.with_message("Unexpected EOF") + .with_labels(vec![Label::primary(file, location..location)]), + ), + + ParseError::UnrecognizedToken { token, expected } => diags.push( + diag.with_message("Unrecognized token") + .with_labels(vec![Label::primary(file, token.0..token.2)]) + .with_notes(expected), + ), + + ParseError::ExtraToken { token } => diags.push( + diag.with_message("Extra token found") + .with_labels(vec![Label::primary(file, token.0..token.2)]), + ), + + ParseError::User { .. } => {} + } + } + + if let Ok(ast) = ast { + (ast, diags) + } else { + let mut diag = Diagnostic::error(); + let err = ast.unwrap_err(); + match err { + ParseError::InvalidToken { location } => { + diag = diag + .with_message("Invalid token") + .with_labels(vec![Label::primary(file, location..location)]) + } + + ParseError::UnrecognizedEof { location, .. } => { + diag = diag + .with_message("Unexpected EOF") + .with_labels(vec![Label::primary(file, location..location)]) + } + + ParseError::UnrecognizedToken { token, .. } => { + diag = diag + .with_message("Unrecognized token") + .with_labels(vec![Label::primary(file, token.0..token.2)]) + } + + ParseError::ExtraToken { token } => { + diag = diag + .with_message("Extra token found") + .with_labels(vec![Label::primary(file, token.0..token.2)]) + } + + ParseError::User { .. } => todo!(), + } + + (Err(ResolveError::ParseError(diag)), diags) + } +} + +pub fn resolve_ast( + headers: (usize, usize, usize, usize, usize), + ast: Vec, +) -> Result { + let mut label_map = HashMap::new(); + let mut custom_gates = HashMap::new(); + let mut resolved_insts: Vec = Vec::new(); + + // Get all labels from the AST + let mut i = 0usize; + for inst in &ast { + if let Inst::Label(name) = inst { + label_map.insert(name, i); + } else { + i += 1 + } + } + + // Update all instructions with the label map + for inst in &ast { + match inst { + Inst::Label(_) => {} + + Inst::Jmp(name, s) => { + if let Some(offset) = label_map.get(name) { + resolved_insts.push(ResolvedInst::Jmp(*offset, s.clone())) + } else { + return Err(ResolveError::UndefinedLabel(name.clone(), s.clone())); + } + } + + Inst::Jeq(name, s) => { + if let Some(offset) = label_map.get(name) { + resolved_insts.push(ResolvedInst::Jeq(*offset, s.clone())) + } else { + return Err(ResolveError::UndefinedLabel(name.clone(), s.clone())); + } + } + + Inst::Jne(name, s) => { + if let Some(offset) = label_map.get(name) { + resolved_insts.push(ResolvedInst::Jne(*offset, s.clone())) + } else { + return Err(ResolveError::UndefinedLabel(name.clone(), s.clone())); + } + } + + Inst::Jg(name, s) => { + if let Some(offset) = label_map.get(name) { + resolved_insts.push(ResolvedInst::Jg(*offset, s.clone())) + } else { + return Err(ResolveError::UndefinedLabel(name.clone(), s.clone())); + } + } + + Inst::Jge(name, s) => { + if let Some(offset) = label_map.get(name) { + resolved_insts.push(ResolvedInst::Jge(*offset, s.clone())) + } else { + return Err(ResolveError::UndefinedLabel(name.clone(), s.clone())); + } + } + + Inst::Jl(name, s) => { + if let Some(offset) = label_map.get(name) { + resolved_insts.push(ResolvedInst::Jle(*offset, s.clone())) + } else { + return Err(ResolveError::UndefinedLabel(name.clone(), s.clone())); + } + } + + // Custom instruction stuff + Inst::CustomGateDef(name, qbits, args, body) => { + let body = resolve_ast(headers, body.clone())?.instructions; + custom_gates.insert(name.clone(), (*qbits, args.clone(), body)); + } + Inst::Custom(name, qbits, args, s) => { + if let Some(gate) = custom_gates.get(name) { + let gate_qbits = gate.0; + let gate_args = &gate.1; + let diag = Diagnostic::error(); + + if qbits.len() != gate_qbits { + return Err(ResolveError::CustomGateError( + diag.with_message("Given qubits to gate don't match gate definition") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Gate expects {} qubits, but {} were given", + gate_qbits, + qbits.len() + )]), + )); + } + if args.iter().map(|x| x.get_type()).collect::>() + != gate_args.iter().map(|x| x.1).collect::>() + { + return Err(ResolveError::CustomGateError( + diag.with_message("Given args to gate don't match gate definition") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Gate expects arg types {}, but given arg types were {}", + format_vec(gate_args.iter().map(|x| x.1).collect()), + format_vec(args.iter().map(|x| x.get_type()).collect()), + )]), + )); + } + + resolved_insts.push(inst.into()) + } else { + return Err(ResolveError::UndefinedGate(name.clone(), s.clone())); + } + } + + Inst::Err => {} + + _ => resolved_insts.push(inst.into()), + } + } + + Ok(Program { + headers, + custom_gates, + instructions: resolved_insts, + }) +} + +fn format_vec(vec: Vec) -> String { + let mut s = String::from("["); + for e in vec { + s += format!("{}, ", e).as_str() + } + s.pop(); + s.pop(); + s += "]"; + + s +} + +#[derive(Debug, Clone)] +pub enum Inst { + // Quantum Instructions + Qsel(usize, SourceSpan), + Id(usize, SourceSpan), + Hadamard(usize, SourceSpan), + Cnot(usize, usize, SourceSpan), + Ccnot(usize, usize, usize, SourceSpan), + X(usize, SourceSpan), + Y(usize, SourceSpan), + Z(usize, SourceSpan), + Rx(usize, Rotation, SourceSpan), + Ry(usize, Rotation, SourceSpan), + Rz(usize, Rotation, SourceSpan), + U(usize, Rotation, Rotation, Rotation, SourceSpan), + S(usize, SourceSpan), + T(usize, SourceSpan), + Sdg(usize, SourceSpan), + Tdg(usize, SourceSpan), + Phase(usize, Rotation, SourceSpan), + Ch(usize, usize, SourceSpan), + Cy(usize, usize, SourceSpan), + Cz(usize, usize, SourceSpan), + CPhase(usize, usize, Rotation, SourceSpan), + Swap(usize, usize, SourceSpan), + SqrtX(usize, SourceSpan), + SqrtSwap(usize, usize, SourceSpan), + CSwap(usize, usize, usize, SourceSpan), + Measure(usize, usize, usize, SourceSpan), + + // Custom gate stuff + // (name, qbits, args, body) + CustomGateDef(String, usize, Vec<(String, IdentType)>, Vec), + Custom(String, Vec, Vec, SourceSpan), + + // Classical Instructions + Mov(Operand, Operand, SourceSpan), + Add(Operand, Operand, Operand, SourceSpan), + Sub(Operand, Operand, Operand, SourceSpan), + Mul(Operand, Operand, Operand, SourceSpan), + UMul(Operand, Operand, Operand, SourceSpan), + Div(Operand, Operand, Operand, SourceSpan), + SMul(Operand, Operand, Operand, SourceSpan), + SUMul(Operand, Operand, Operand, SourceSpan), + SDiv(Operand, Operand, Operand, SourceSpan), + Not(Operand, Operand, SourceSpan), + And(Operand, Operand, Operand, SourceSpan), + Or(Operand, Operand, Operand, SourceSpan), + Xor(Operand, Operand, Operand, SourceSpan), + Nand(Operand, Operand, Operand, SourceSpan), + Nor(Operand, Operand, Operand, SourceSpan), + Xnor(Operand, Operand, Operand, SourceSpan), + + // Misc + Cmp(Operand, Operand, SourceSpan), + Jmp(String, SourceSpan), + Jeq(String, SourceSpan), + Jne(String, SourceSpan), + Jg(String, SourceSpan), + Jge(String, SourceSpan), + Jl(String, SourceSpan), + Jle(String, SourceSpan), + Hlt, + + Label(String), + + Err, +} + +#[derive(Debug, Clone)] +pub enum ResolvedInst { + // Quantum Instructions + Qsel(usize, SourceSpan), + Id(usize, SourceSpan), + Hadamard(usize, SourceSpan), + Cnot(usize, usize, SourceSpan), + Ccnot(usize, usize, usize, SourceSpan), + X(usize, SourceSpan), + Y(usize, SourceSpan), + Z(usize, SourceSpan), + Rx(usize, Rotation, SourceSpan), + Ry(usize, Rotation, SourceSpan), + Rz(usize, Rotation, SourceSpan), + U(usize, Rotation, Rotation, Rotation, SourceSpan), + S(usize, SourceSpan), + T(usize, SourceSpan), + Sdg(usize, SourceSpan), + Tdg(usize, SourceSpan), + Phase(usize, Rotation, SourceSpan), + Ch(usize, usize, SourceSpan), + Cy(usize, usize, SourceSpan), + Cz(usize, usize, SourceSpan), + CPhase(usize, usize, Rotation, SourceSpan), + Swap(usize, usize, SourceSpan), + SqrtX(usize, SourceSpan), + SqrtSwap(usize, usize, SourceSpan), + CSwap(usize, usize, usize, SourceSpan), + Measure(usize, usize, usize, SourceSpan), + + // Custom gate stuff + Custom(String, Vec, Vec, SourceSpan), + + // Classical Instructions + Mov(Operand, Operand, SourceSpan), + Add(Operand, Operand, Operand, SourceSpan), + Sub(Operand, Operand, Operand, SourceSpan), + Mul(Operand, Operand, Operand, SourceSpan), + UMul(Operand, Operand, Operand, SourceSpan), + Div(Operand, Operand, Operand, SourceSpan), + SMul(Operand, Operand, Operand, SourceSpan), + SUMul(Operand, Operand, Operand, SourceSpan), + SDiv(Operand, Operand, Operand, SourceSpan), + Not(Operand, Operand, SourceSpan), + And(Operand, Operand, Operand, SourceSpan), + Or(Operand, Operand, Operand, SourceSpan), + Xor(Operand, Operand, Operand, SourceSpan), + Nand(Operand, Operand, Operand, SourceSpan), + Nor(Operand, Operand, Operand, SourceSpan), + Xnor(Operand, Operand, Operand, SourceSpan), + + // Misc + Cmp(Operand, Operand, SourceSpan), + Jmp(usize, SourceSpan), + Jeq(usize, SourceSpan), + Jne(usize, SourceSpan), + Jg(usize, SourceSpan), + Jge(usize, SourceSpan), + Jl(usize, SourceSpan), + Jle(usize, SourceSpan), + + Hlt, +} + +impl From<&Inst> for ResolvedInst { + // The most painful damn function ever to write + fn from(value: &Inst) -> Self { + match value { + // Quantum instructions + Inst::Qsel(q, s) => Self::Qsel(*q, s.clone()), + Inst::Id(q, s) => Self::Id(*q, s.clone()), + Inst::Hadamard(q, s) => Self::Hadamard(*q, s.clone()), + Inst::Cnot(q1, q2, s) => Self::Cnot(*q1, *q2, s.clone()), + Inst::Ccnot(q1, q2, q3, s) => Self::Ccnot(*q1, *q2, *q3, s.clone()), + Inst::X(q, s) => Self::X(*q, s.clone()), + Inst::Y(q, s) => Self::Y(*q, s.clone()), + Inst::Z(q, s) => Self::Z(*q, s.clone()), + Inst::Rx(q, r, s) => Self::Rx(*q, r.clone(), s.clone()), + Inst::Ry(q, r, s) => Self::Ry(*q, r.clone(), s.clone()), + Inst::Rz(q, r, s) => Self::Rz(*q, r.clone(), s.clone()), + Inst::U(q, r1, r2, r3, s) => Self::U(*q, r1.clone(), r2.clone(), r3.clone(), s.clone()), + Inst::S(q, s) => Self::S(*q, s.clone()), + Inst::T(q, s) => Self::T(*q, s.clone()), + Inst::Sdg(q, s) => Self::Sdg(*q, s.clone()), + Inst::Tdg(q, s) => Self::Tdg(*q, s.clone()), + Inst::Phase(q, r, s) => Self::Phase(*q, r.clone(), s.clone()), + Inst::Ch(q1, q2, s) => Self::Ch(*q1, *q2, s.clone()), + Inst::Cy(q1, q2, s) => Self::Cy(*q1, *q2, s.clone()), + Inst::Cz(q1, q2, s) => Self::Cz(*q1, *q2, s.clone()), + Inst::CPhase(q1, q2, r, s) => Self::CPhase(*q1, *q2, r.clone(), s.clone()), + Inst::Swap(q1, q2, s) => Self::Swap(*q1, *q2, s.clone()), + Inst::SqrtX(q, s) => Self::SqrtX(*q, s.clone()), + Inst::SqrtSwap(q1, q2, s) => Self::SqrtSwap(*q1, *q2, s.clone()), + Inst::CSwap(q1, q2, q3, s) => Self::CSwap(*q1, *q2, *q3, s.clone()), + Inst::Measure(q, cr, cb, s) => Self::Measure(*q, *cr, *cb, s.clone()), + + // Custom gate stuff + Inst::Custom(name, qbits, args, s) => { + Self::Custom(name.clone(), qbits.clone(), args.clone(), s.clone()) + } + + // Classical Instructions + Inst::Mov(cr1, val, s) => Self::Mov(cr1.clone(), val.clone(), s.clone()), + Inst::Add(cr1, cr2, cr3, s) => { + Self::Add(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::Sub(cr1, cr2, cr3, s) => { + Self::Sub(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::Mul(cr1, cr2, cr3, s) => { + Self::Mul(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::UMul(cr1, cr2, cr3, s) => { + Self::UMul(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::Div(cr1, cr2, cr3, s) => { + Self::Div(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::SMul(cr1, cr2, cr3, s) => { + Self::SMul(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::SUMul(cr1, cr2, cr3, s) => { + Self::SUMul(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::SDiv(cr1, cr2, cr3, s) => { + Self::SDiv(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::Not(cr1, cr2, s) => Self::Not(cr1.clone(), cr2.clone(), s.clone()), + Inst::And(cr1, cr2, cr3, s) => { + Self::And(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::Or(cr1, cr2, cr3, s) => { + Self::Or(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::Xor(cr1, cr2, cr3, s) => { + Self::Xor(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::Nand(cr1, cr2, cr3, s) => { + Self::Nand(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::Nor(cr1, cr2, cr3, s) => { + Self::Nor(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + Inst::Xnor(cr1, cr2, cr3, s) => { + Self::Xnor(cr1.clone(), cr2.clone(), cr3.clone(), s.clone()) + } + + // Misc + Inst::Cmp(cr1, val, s) => Self::Cmp(cr1.clone(), val.clone(), s.clone()), + Inst::Hlt => Self::Hlt, + + _ => unreachable!(), /* Atleast hopefully unreachable if I didn't mess up stuff */ + } + } +} + +impl ResolvedInst { + pub fn get_span(&self) -> &SourceSpan { + match self { + Self::Qsel(_, s) => s, + Self::Id(_, s) => s, + Self::Hadamard(_, s) => s, + Self::Cnot(_, _, s) => s, + Self::Ccnot(_, _, _, s) => s, + Self::X(_, s) => s, + Self::Y(_, s) => s, + Self::Z(_, s) => s, + Self::Rx(_, _, s) => s, + Self::Ry(_, _, s) => s, + Self::Rz(_, _, s) => s, + Self::U(_, _, _, _, s) => s, + Self::S(_, s) => s, + Self::T(_, s) => s, + Self::Sdg(_, s) => s, + Self::Tdg(_, s) => s, + Self::Phase(_, _, s) => s, + Self::Ch(_, _, s) => s, + Self::Cy(_, _, s) => s, + Self::Cz(_, _, s) => s, + Self::CPhase(_, _, _, s) => s, + Self::Swap(_, _, s) => s, + Self::SqrtX(_, s) => s, + Self::SqrtSwap(_, _, s) => s, + Self::CSwap(_, _, _, s) => s, + Self::Measure(_, _, _, s) => s, + + // Custom gate stuff + Self::Custom(_, _, _, s) => s, + + // Classical Instructions + Self::Mov(_, _, s) => s, + Self::Add(_, _, _, s) => s, + Self::Sub(_, _, _, s) => s, + Self::Mul(_, _, _, s) => s, + Self::UMul(_, _, _, s) => s, + Self::Div(_, _, _, s) => s, + Self::SMul(_, _, _, s) => s, + Self::SUMul(_, _, _, s) => s, + Self::SDiv(_, _, _, s) => s, + Self::Not(_, _, s) => s, + Self::And(_, _, _, s) => s, + Self::Or(_, _, _, s) => s, + Self::Xor(_, _, _, s) => s, + Self::Nand(_, _, _, s) => s, + Self::Nor(_, _, _, s) => s, + Self::Xnor(_, _, _, s) => s, + + // Misc + Self::Cmp(_, _, s) => s, + Self::Jmp(_, s) => s, + Self::Jeq(_, s) => s, + Self::Jne(_, s) => s, + Self::Jg(_, s) => s, + Self::Jge(_, s) => s, + Self::Jl(_, s) => s, + Self::Jle(_, s) => s, + + Self::Hlt => unreachable!(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Operand { + Imm(i64), + Reg(usize), + Addr(MemAddr), + Ident(String), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum IdentVal { + Rot(Rot), + + Imm(i64), + Reg(usize), + Addr(MemAddr), +} +impl IdentVal { + pub fn get_type(&self) -> IdentType { + match self { + Self::Rot(_) => IdentType::Rot, + Self::Imm(_) => IdentType::Imm, + Self::Reg(_) => IdentType::Reg, + Self::Addr(_) => IdentType::MemAddr, + } + } + + pub fn get_rot(&self) -> Option { + match self { + Self::Rot(rot) => Some(*rot), + _ => None, + } + } +} +impl From<&IdentVal> for Operand { + fn from(value: &IdentVal) -> Self { + match value { + IdentVal::Imm(imm) => Self::Imm(*imm), + IdentVal::Reg(reg) => Self::Reg(*reg), + IdentVal::Addr(addr) => Self::Addr(*addr), + + IdentVal::Rot(_) => unreachable!(), // hopefully... + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IdentType { + Rot, + + Imm, + Reg, + MemAddr, +} +impl Display for IdentType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Rot => write!(f, "Rot"), + Self::Imm => write!(f, "Imm"), + Self::Reg => write!(f, "Reg"), + Self::MemAddr => write!(f, "Addr"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MemAddr { + Address(usize), + // (reg, align, offset) = reg * align + offset + Indirect(usize, usize, usize), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rot(pub i64, pub u64); +impl Rot { + pub fn get_angle(&self) -> f64 { + (self.0 as f64) * std::f64::consts::PI / (self.1 as f64) + } +} +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Rotation { + Rot(Rot), + Ident(String), +} +impl Rotation { + pub fn get_rot(&self, args: &HashMap) -> Option { + match self { + Self::Rot(rot) => Some(*rot), + Self::Ident(ident) => { + if let Some(rot) = args.get(ident) { + rot.get_rot() + } else { + None + } + } + } + } +} + +#[derive(Debug, Clone)] +pub enum ResolveError { + UndefinedLabel(String, SourceSpan), + ParseError(Diagnostic), + UndefinedGate(String, SourceSpan), + CustomGateError(Diagnostic), +} diff --git a/src/emulator.rs b/src/emulator.rs new file mode 100644 index 0000000..e55eed6 --- /dev/null +++ b/src/emulator.rs @@ -0,0 +1,1480 @@ +use std::cmp::Ordering; +use std::collections::HashMap; +use std::f64::consts::{ + FRAC_1_SQRT_2 as ONE_BY_ROOT_2, FRAC_PI_2 as PI_BY_2, FRAC_PI_4 as PI_BY_4, +}; +use std::fmt::Display; +use std::num::Wrapping; + +use codespan_reporting::diagnostic::{Diagnostic, Label}; +use lazy_static::lazy_static; + +use num_traits::{One, Zero}; + +use nalgebra::{dmatrix, DMatrix}; +use nalgebra::{Complex, ComplexField}; +use nalgebra::{DVector, Vector2, Vector4}; + +use bitvec::prelude::{BitArr, BitArray, Lsb0}; + +use rand::Rng; +use rand::{rngs::ThreadRng, thread_rng}; + +use crate::ast::*; + +type Complexf64 = Complex; +type DCf64Mat = DMatrix; + +lazy_static! { + static ref MAT_2_IDENTITY: DCf64Mat = dmatrix![ + Complex::one(), Complex::zero(); + Complex::zero(), Complex::one(); + ]; + static ref HADAMARD_MAT: DCf64Mat = dmatrix![ + Complex::from(ONE_BY_ROOT_2), Complex::from(ONE_BY_ROOT_2); + Complex::from(ONE_BY_ROOT_2), Complex::from(-ONE_BY_ROOT_2); + ]; + static ref PAULI_X_MAT: DCf64Mat = dmatrix![ + Complex::zero(), Complex::one(); + Complex::one(), Complex::zero(); + ]; + static ref PAULI_Y_MAT: DCf64Mat = dmatrix![ + Complex::zero(), -Complex::i(); + Complex::i(), Complex::zero(); + ]; + static ref PAULI_Z_MAT: DCf64Mat = dmatrix![ + Complex::one(), Complex::zero(); + Complex::zero(), -Complex::one(); + ]; + static ref S_MAT: DCf64Mat = r1(PI_BY_2); + static ref T_MAT: DCf64Mat = r1(PI_BY_4); + static ref S_DG_MAT: DCf64Mat = S_MAT.adjoint(); + static ref T_DG_MAT: DCf64Mat = T_MAT.adjoint(); + static ref SQRT_X_MAT: DCf64Mat = dmatrix![ + Complex::new(0.5, 0.5), Complex::new(0.5, -0.5); + Complex::new(0.5, -0.5), Complex::new(0.5, 0.5); + ]; + static ref SQRT_SWAP_MAT: DCf64Mat = dmatrix![ + Complex::one(), Complex::zero(), Complex::zero(), Complex::zero(); + Complex::zero(), Complex::new(0.5, 0.5), Complex::new(0.5, -0.5), Complex::zero(); + Complex::zero(), Complex::new(0.5, -0.5), Complex::new(0.5, 0.5), Complex::zero(); + Complex::zero(), Complex::zero(), Complex::zero(), Complex::one(); + ]; +} + +#[inline] +fn u(theta: f64, phi: f64, lambda: f64) -> DCf64Mat { + let half_theta = theta / 2f64; + dmatrix![ + Complex::from(half_theta.cos()), -half_theta.sin() * Complex::new(lambda.cos(), lambda.sin()); + half_theta.sin() * Complex::new(phi.cos(), phi.sin()), half_theta.cos() * Complex::new((lambda + phi).cos(), (lambda + phi).sin()); + ] +} +#[inline] +fn rx(theta: f64) -> DCf64Mat { + u(theta, -PI_BY_2, PI_BY_2) +} +#[inline] +fn ry(theta: f64) -> DCf64Mat { + u(theta, 0f64, 0f64) +} +#[inline] +fn rz(theta: f64) -> DCf64Mat { + let half_theta = theta / 2f64; + // If only i could do this + // Complex::new(half_theta.cos(), -half_theta.sin()) * r1(theta) + dmatrix![ + Complex::new(half_theta.cos(), -half_theta.sin()), Complex::zero(); + Complex::zero(), Complex::new(half_theta.cos(), half_theta.sin()); + ] +} +#[inline] +fn r1(theta: f64) -> DCf64Mat { + u(0f64, theta, 0f64) +} + +pub struct Emulator<'a> { + prog: &'a Program, + + qbits: usize, + cbits: usize, + + qregs: Vec>, + cregs: Vec, + + pc: usize, + prev_pc: usize, + qreg_sel: usize, + flags: BitArr!(for 3, in u8, Lsb0), + mem: Vec, + + rng: ThreadRng, +} + +impl<'a> Emulator<'a> { + pub fn new(prog: &'a Program) -> Self { + let qbits = prog.headers.0; + let cbits = prog.headers.1; + let qregs = prog.headers.2; + let cregs = prog.headers.3; + let mem_size = prog.headers.4; + + Self { + prog, + qbits, + cbits, + + qregs: vec![ + { + let mut dvec = DVector::zeros(1 << qbits); + dvec[0] = Complex::one(); + dvec + }; + qregs + ], + cregs: vec![ + Word { + bits: cbits, + data: Wrapping(0), + }; + cregs + ], + + pc: 0, + prev_pc: 0, + qreg_sel: 0, + flags: BitArray::ZERO, + mem: vec![ + Word { + bits: cbits, + data: Wrapping(0) + }; + mem_size + ], + + rng: thread_rng(), + } + } + + pub fn reset(&mut self) { + *self = Self { + prog: self.prog, + qbits: self.qbits, + cbits: self.cbits, + + qregs: vec![ + { + let mut dvec = DVector::zeros(1 << self.qbits); + dvec[0] = Complex::one(); + dvec + }; + self.qregs.len() + ], + cregs: vec![ + Word { + bits: self.cbits, + data: Wrapping(0) + }; + self.cregs.len() + ], + + pc: 0, + prev_pc: 0, + qreg_sel: 0, + flags: BitArray::ZERO, + mem: vec![ + Word { + bits: self.cbits, + data: Wrapping(0) + }; + self.mem.len() + ], + + rng: thread_rng(), + } + } + + pub fn run(&mut self) -> Result<(), Diagnostic> { + loop { + let res = self.step()?; + + self.prev_pc = self.pc; + if res == StepResult::Halted { + break; + } + if res == StepResult::Continue { + self.pc += 1; + } else if let StepResult::Branch(new_pc) = res { + self.pc = new_pc + } + } + Ok(()) + } + + fn step(&mut self) -> Result> { + let Some(inst) = self.prog.instructions.get(self.pc) else { + let inst = &self.prog.instructions[self.prev_pc]; + let span: &SourceSpan = inst.get_span(); + return Err(Diagnostic::error() + .with_message("PC went out of bounds") + .with_labels(vec![Label::primary(span.file, span.span.clone())]) + .with_notes(vec![ + format!( + "Note: Current PC value is {} (changed from value {})", + self.pc, self.prev_pc + ), + String::from("Maybe you are missing a 'hlt' instruction?"), + ])); + }; + + self.run_instr(inst, &(0..self.qbits).collect::>(), &HashMap::new()) + } + + fn run_instr( + &mut self, + inst: &ResolvedInst, + mapping: &[usize], + args: &HashMap, + ) -> Result> { + match inst { + // Quantum Instructions + ResolvedInst::Qsel(qreg, s) => { + if qreg > &self.qregs.len() { + return Err(Diagnostic::error() + .with_message("qreg index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qregs is {} as declared in headers", + self.qregs.len() + )])); + } + self.qreg_sel = *qreg + } + ResolvedInst::Id(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&MAT_2_IDENTITY, *qbit) + } + ResolvedInst::Hadamard(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&HADAMARD_MAT, *qbit) + } + ResolvedInst::Cnot(qbit1, qbit2, s) => { + let Some(qbit1) = mapping.get(*qbit1) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit1)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit2) = mapping.get(*qbit2) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit2)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit1 > &self.qbits || qbit2 > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + + let qreg = &mut self.qregs[self.qreg_sel]; + + let qbit1_mask = 1 << qbit1; + let qbit2_mask = 1 << qbit2; + + for state in 0..(1 << self.qbits) { + if state & qbit1_mask == 0 || state & qbit2_mask != 0 { + continue; + } + qreg.swap_rows(state, state | qbit2_mask) + } + } + ResolvedInst::Ccnot(qbit1, qbit2, qbit3, s) => { + let Some(qbit1) = mapping.get(*qbit1) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit1)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit2) = mapping.get(*qbit2) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit2)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit3) = mapping.get(*qbit3) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit3)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit1 > &self.qbits || qbit2 > &self.qbits || qbit3 > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + + let qreg = &mut self.qregs[self.qreg_sel]; + + let qbit1_mask = 1 << qbit1; + let qbit2_mask = 1 << qbit2; + let qbit3_mask = 1 << qbit3; + + for state in 0..(1 << self.qbits) { + if state & qbit1_mask == 0 || state & qbit2_mask == 0 || state & qbit3_mask != 0 + { + continue; + } + qreg.swap_rows(state, state | qbit3_mask) + } + } + ResolvedInst::X(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&PAULI_X_MAT, *qbit) + } + ResolvedInst::Y(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&PAULI_Y_MAT, *qbit) + } + ResolvedInst::Z(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&PAULI_Z_MAT, *qbit) + } + ResolvedInst::Rx(qbit, rot, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + let Some(rot) = rot.get_rot(args) else { + return Err(Diagnostic::error() + .with_message("Argument passed is not a rotation") + .with_labels(vec![Label::primary(s.file, s.span.clone())])); + }; + self.apply_mat_1(&rx(rot.get_angle()), *qbit) + } + ResolvedInst::Ry(qbit, rot, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + let Some(rot) = rot.get_rot(args) else { + return Err(Diagnostic::error() + .with_message("Argument passed is not a rotation") + .with_labels(vec![Label::primary(s.file, s.span.clone())])); + }; + self.apply_mat_1(&ry(rot.get_angle()), *qbit) + } + ResolvedInst::Rz(qbit, rot, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + let Some(rot) = rot.get_rot(args) else { + return Err(Diagnostic::error() + .with_message("Argument passed is not a rotation") + .with_labels(vec![Label::primary(s.file, s.span.clone())])); + }; + self.apply_mat_1(&rz(rot.get_angle()), *qbit) + } + ResolvedInst::U(qbit, theta, phi, lambda, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + let Some(theta) = theta.get_rot(args) else { + return Err(Diagnostic::error() + .with_message("Argument passed is not a rotation") + .with_labels(vec![Label::primary(s.file, s.span.clone())])); + }; + let Some(phi) = phi.get_rot(args) else { + return Err(Diagnostic::error() + .with_message("Argument passed is not a rotation") + .with_labels(vec![Label::primary(s.file, s.span.clone())])); + }; + let Some(lambda) = lambda.get_rot(args) else { + return Err(Diagnostic::error() + .with_message("Argument passed is not a rotation") + .with_labels(vec![Label::primary(s.file, s.span.clone())])); + }; + self.apply_mat_1( + &u(theta.get_angle(), phi.get_angle(), lambda.get_angle()), + *qbit, + ) + } + ResolvedInst::S(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&S_MAT, *qbit) + } + ResolvedInst::T(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&T_MAT, *qbit) + } + ResolvedInst::Sdg(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&S_DG_MAT, *qbit) + } + ResolvedInst::Tdg(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&T_DG_MAT, *qbit) + } + ResolvedInst::Phase(qbit, rot, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + let Some(rot) = rot.get_rot(args) else { + return Err(Diagnostic::error() + .with_message("Argument passed is not a rotation") + .with_labels(vec![Label::primary(s.file, s.span.clone())])); + }; + self.apply_mat_1(&r1(rot.get_angle()), *qbit) + } + ResolvedInst::Ch(qbit1, qbit2, s) => { + let Some(qbit1) = mapping.get(*qbit1) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit1)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit2) = mapping.get(*qbit2) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit2)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit1 > &self.qbits || qbit2 > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_controlled_mat(&HADAMARD_MAT, vec![*qbit1], *qbit2) + } + ResolvedInst::Cy(qbit1, qbit2, s) => { + let Some(qbit1) = mapping.get(*qbit1) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit1)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit2) = mapping.get(*qbit2) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit2)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit1 > &self.qbits || qbit2 > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_controlled_mat(&PAULI_Y_MAT, vec![*qbit1], *qbit2) + } + ResolvedInst::Cz(qbit1, qbit2, s) => { + let Some(qbit1) = mapping.get(*qbit1) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit1)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit2) = mapping.get(*qbit2) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit2)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit1 > &self.qbits || qbit2 > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_controlled_mat(&PAULI_Z_MAT, vec![*qbit1], *qbit2) + } + ResolvedInst::CPhase(qbit1, qbit2, rot, s) => { + let Some(qbit1) = mapping.get(*qbit1) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit1)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit2) = mapping.get(*qbit2) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit2)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit1 > &self.qbits || qbit2 > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + let Some(rot) = rot.get_rot(args) else { + return Err(Diagnostic::error() + .with_message("Argument passed is not a rotation") + .with_labels(vec![Label::primary(s.file, s.span.clone())])); + }; + self.apply_controlled_mat(&r1(rot.get_angle()), vec![*qbit1], *qbit2) + } + ResolvedInst::Swap(qbit1, qbit2, s) => { + let Some(qbit1) = mapping.get(*qbit1) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit1)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit2) = mapping.get(*qbit2) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit2)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit1 > &self.qbits || qbit2 > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + + let qreg = &mut self.qregs[self.qreg_sel]; + + let qbit1_mask = 1 << qbit1; + let qbit2_mask = 1 << qbit2; + + for state in 0..(1 << self.qbits) { + if state & qbit1_mask != 0 || state & qbit2_mask != 0 { + continue; + } + qreg.swap_rows(state | qbit1_mask, state | qbit2_mask) + } + } + ResolvedInst::SqrtX(qbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_1(&SQRT_X_MAT, *qbit) + } + ResolvedInst::SqrtSwap(qbit1, qbit2, s) => { + let Some(qbit1) = mapping.get(*qbit1) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit1)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit2) = mapping.get(*qbit2) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit2)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit1 > &self.qbits || qbit2 > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + self.apply_mat_2(&SQRT_SWAP_MAT, *qbit1, *qbit2) + } + ResolvedInst::CSwap(qbit1, qbit2, qbit3, s) => { + let Some(qbit1) = mapping.get(*qbit1) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit1)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit2) = mapping.get(*qbit2) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit2)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + let Some(qbit3) = mapping.get(*qbit3) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit3)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit1 > &self.qbits || qbit2 > &self.qbits || qbit3 > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + + let qreg = &mut self.qregs[self.qreg_sel]; + + let qbit1_mask = 1 << qbit1; + let qbit2_mask = 1 << qbit2; + let qbit3_mask = 1 << qbit3; + + for state in 0..(1 << self.qbits) { + if state & qbit1_mask == 0 || state & qbit2_mask != 0 || state & qbit3_mask != 0 + { + continue; + } + qreg.swap_rows(state | qbit2_mask, state | qbit3_mask) + } + } + + ResolvedInst::Measure(qbit, creg, cbit, s) => { + let Some(qbit) = mapping.get(*qbit) else { + return Err(Diagnostic::error() + .with_message(format!("qbit {} is not mapped in local scope", qbit)) + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Local scope has qbits 0 to {} mapped", + mapping.len() + )])); + }; + if qbit > &self.qbits { + return Err(Diagnostic::error() + .with_message("qbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of qbits is {} as declared in headers", + self.qbits + )])); + } + if creg > &self.cregs.len() { + return Err(Diagnostic::error() + .with_message("creg index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of cregs is {} as declared in headers", + self.cregs.len() + )])); + } + if cbit > &self.cbits { + return Err(Diagnostic::error() + .with_message("cbit index out of bounds") + .with_labels(vec![Label::primary(s.file, s.span.clone())]) + .with_notes(vec![format!( + "Note: Number of cbits is {} as declared in headers", + self.cbits + )])); + } + + let qreg = &mut self.qregs[self.qreg_sel]; + let qbit_mask = 1 << qbit; + + let mut prob = Complexf64::zero(); + + for state in 0..(1 << self.qbits) { + if state & qbit_mask != 0 { + continue; + } + + let amplitude = qreg[state]; + prob += amplitude * amplitude + } + + if matches!( + (prob.im - 0f64).abs().partial_cmp(&f64::EPSILON), + None | Some(Ordering::Greater) + ) { + println!("{}", prob); + panic!("Invalid probability for qubit {}", qbit) + } + let prob = prob.re; + if prob == 0f64 || prob == 1f64 { + self.cregs[*creg].set_bit(*cbit, prob == 0f64); + return Ok(StepResult::Continue); + } + let rand = self.rng.gen::(); + + if rand < prob { + for state in 0..(1 << self.qbits) { + if state & qbit_mask == 0 { + continue; + } + qreg[state] = Complex::zero() + } + self.cregs[*creg].set_bit(*cbit, false) + } else { + for state in 0..(1 << self.qbits) { + if state & qbit_mask != 0 { + continue; + } + qreg[state] = Complex::zero() + } + self.cregs[*creg].set_bit(*cbit, true) + } + + let sqrtsumsq = { + let sumsq = qreg.iter().map(|x| x * x).sum::(); + sumsq.sqrt() + }; + for x in qreg.iter_mut() { + *x /= sqrtsumsq + } + } + + // Custom gate stuff + ResolvedInst::Custom(name, qbits, args, s) => { + let gate = &self.prog.custom_gates[name]; + let args_ = gate + .1 + .iter() + .zip(args) + .map(|x| (x.0 .0.clone(), x.1)) + .collect::>(); + let mut args = HashMap::new(); + for arg in args_ { + args.insert(arg.0, arg.1.clone()); + } + + for inst in &gate.2 { + let res = self.run_instr(inst, qbits, &args); + if let Err(diag) = res { + return Err(diag.with_labels(vec![Label::secondary( + s.file, + s.span.clone(), + ) + .with_message("Originating from this custom gate call")])); + } + } + } + + // Classical Instructions + ResolvedInst::Mov(r1, val, s) => { + let val = self.get_val(val, args, s)?; + self.update(r1, val, args, s)? + } + ResolvedInst::Add(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, val1 + val2, args, s)? + } + ResolvedInst::Sub(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, val1 - val2, args, s)? + } + ResolvedInst::Mul(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?.0 as u64; + let val2 = self.get_val(r3, args, s)?.0 as u64; + self.update(r1, Wrapping(val1.wrapping_mul(val2) as i64), args, s)? + } + ResolvedInst::UMul(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?.0 as u64; + let val2 = self.get_val(r3, args, s)?.0 as u64; + self.update(r1, Wrapping(val1.wrapping_mul(val2) as i64 >> 32), args, s)? + } + ResolvedInst::Div(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?.0 as u64; + let val2 = self.get_val(r3, args, s)?.0 as u64; + self.update(r1, Wrapping((val1 / val2) as i64), args, s)? + } + ResolvedInst::SMul(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, val1 * val2, args, s)? + } + ResolvedInst::SUMul(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, (val1 * val2) >> 32, args, s)? + } + ResolvedInst::SDiv(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, val1 / val2, args, s)? + } + ResolvedInst::Not(r1, r2, s) => { + let val1 = self.get_val(r2, args, s)?; + self.update(r1, !val1, args, s)? + } + ResolvedInst::And(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, val1 & val2, args, s)? + } + ResolvedInst::Or(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, val1 | val2, args, s)? + } + ResolvedInst::Xor(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, val1 ^ val2, args, s)? + } + ResolvedInst::Nand(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, !(val1 & val2), args, s)? + } + ResolvedInst::Nor(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, !(val1 | val2), args, s)? + } + ResolvedInst::Xnor(r1, r2, r3, s) => { + let val1 = self.get_val(r2, args, s)?; + let val2 = self.get_val(r3, args, s)?; + self.update(r1, !(val1 ^ val2), args, s)? + } + + // Misc + ResolvedInst::Cmp(r1, val, s) => { + let cmp = self.get_val(r1, args, s)?.cmp(&self.get_val(val, args, s)?); + match cmp { + Ordering::Less => { + self.flags.set(0, true); + self.flags.set(1, false); + self.flags.set(2, false); + } + Ordering::Equal => { + self.flags.set(0, false); + self.flags.set(1, true); + self.flags.set(2, false); + } + Ordering::Greater => { + self.flags.set(0, false); + self.flags.set(1, false); + self.flags.set(2, true); + } + } + } + ResolvedInst::Jmp(offset, _) => { + return Ok(StepResult::Branch(*offset)); + } + ResolvedInst::Jeq(offset, _) => { + if self.flags[1] { + return Ok(StepResult::Branch(*offset)); + } + } + ResolvedInst::Jne(offset, _) => { + if !self.flags[1] { + return Ok(StepResult::Branch(*offset)); + } + } + ResolvedInst::Jg(offset, _) => { + if self.flags[2] { + return Ok(StepResult::Branch(*offset)); + } + } + ResolvedInst::Jge(offset, _) => { + if self.flags[1] || self.flags[2] { + return Ok(StepResult::Branch(*offset)); + } + } + ResolvedInst::Jl(offset, _) => { + if self.flags[0] { + return Ok(StepResult::Branch(*offset)); + } + } + ResolvedInst::Jle(offset, _) => { + if self.flags[0] || self.flags[1] { + return Ok(StepResult::Branch(*offset)); + } + } + + ResolvedInst::Hlt => return Ok(StepResult::Halted), + } + + Ok(StepResult::Continue) + } + + pub fn get_cregs_state(&self) -> &Vec { + &self.cregs + } + + pub fn get_mem_state(&self) -> &Vec { + &self.mem + } + + fn get_arg( + &self, + name: &String, + args: &HashMap, + span: &SourceSpan, + ) -> Result> { + if let Some(val) = args.get(name) { + Ok(val.into()) + } else { + Err(Diagnostic::error() + .with_message(format!("'{}' does not exist in current scope", name)) + .with_labels(vec![Label::primary(span.file, span.span.clone())])) + } + } + + fn get_val( + &self, + thing: &Operand, + args: &HashMap, + span: &SourceSpan, + ) -> Result, Diagnostic> { + match thing { + Operand::Imm(imm) => Ok(Wrapping(*imm)), + Operand::Reg(reg) => { + if reg > &self.cregs.len() { + Err(Diagnostic::error() + .with_message("creg index out of bounds") + .with_labels(vec![Label::primary(span.file, span.span.clone())]) + .with_notes(vec![format!( + "Note: Number of cregs is {} as declared in headers", + self.cregs.len() + )])) + } else { + Ok(self.cregs[*reg].get_val()) + } + } + Operand::Addr(addr) => { + let addr_val = self.mem_addr_val(*addr, span)?; + if addr_val > self.mem.len() { + Err(Diagnostic::error() + .with_message("Memory index out of bounds") + .with_labels(vec![Label::primary(span.file, span.span.clone()) + .with_message(format!( + "Attempted to access memory index {}", + addr_val + ))]) + .with_notes(vec![format!( + "Note: Number of words in memory is {} as declared in headers", + self.mem.len() + )])) + } else { + Ok(self.mem[addr_val].get_val()) + } + } + Operand::Ident(name) => self.get_val(&self.get_arg(name, args, span)?, args, span), + } + } + + fn mem_addr_val(&self, addr: MemAddr, span: &SourceSpan) -> Result> { + match addr { + MemAddr::Address(addr) => Ok(addr), + MemAddr::Indirect(reg, align, offset) => { + if reg > self.cregs.len() { + Err(Diagnostic::error() + .with_message("creg index out of bounds") + .with_labels(vec![Label::primary(span.file, span.span.clone())]) + .with_notes(vec![format!( + "Note: Number of cregs is {} as declared in headers", + self.cregs.len() + )])) + } else { + Ok((self.cregs[reg].get_val().0 as usize) * align + offset) + } + } + } + } + + fn update( + &mut self, + src: &Operand, + dst: Wrapping, + args: &HashMap, + span: &SourceSpan, + ) -> Result<(), Diagnostic> { + match src { + Operand::Reg(reg) => { + if reg > &self.cregs.len() { + Err(Diagnostic::error() + .with_message("creg index out of bounds") + .with_labels(vec![Label::primary(span.file, span.span.clone())]) + .with_notes(vec![format!( + "Note: Number of cregs is {} as declared in headers", + self.cregs.len() + )])) + } else { + self.cregs[*reg].set_to(dst); + Ok(()) + } + } + Operand::Addr(addr) => { + let addr_val = self.mem_addr_val(*addr, span)?; + if addr_val > self.mem.len() { + Err(Diagnostic::error() + .with_message("Memory index out of bounds") + .with_labels(vec![Label::primary(span.file, span.span.clone()) + .with_message(format!( + "Attempted to access memory index {}", + addr_val + ))]) + .with_notes(vec![format!( + "Note: Number of words in memory is {} as declared in headers", + self.mem.len() + )])) + } else { + self.mem[addr_val].set_to(dst); + Ok(()) + } + } + Operand::Ident(name) => self.update(&self.get_arg(name, args, span)?, dst, args, span), + + Operand::Imm(_) => unreachable!("Parser allowed bork code"), + } + } + + fn apply_mat_1(&mut self, mat: &DCf64Mat, qbit: usize) { + let qreg = &mut self.qregs[self.qreg_sel]; + + let mut in_gate_state = Vector2::zeros(); + let mut out_gate_state = Vector2::zeros(); + + let qbit_mask = 1 << qbit; + + for state in 0..(1 << self.qbits) { + if state & qbit_mask != 0 { + continue; + } + + in_gate_state[0] = qreg[state]; + in_gate_state[1] = qreg[state | qbit_mask]; + + mat.mul_to(&in_gate_state, &mut out_gate_state); + + qreg[state] = out_gate_state[0]; + qreg[state | qbit_mask] = out_gate_state[1]; + } + } + + fn apply_mat_2(&mut self, mat: &DCf64Mat, qbit1: usize, qbit2: usize) { + let qreg = &mut self.qregs[self.qreg_sel]; + + let mut in_gate_state = Vector4::zeros(); + let mut out_gate_state = Vector4::zeros(); + + let qbit1_mask = 1 << qbit1; + let qbit2_mask = 1 << qbit2; + + for state in 0..(1 << self.qbits) { + if state & qbit1_mask != 0 || state & qbit2_mask != 0 { + continue; + } + + in_gate_state[0] = qreg[state]; + in_gate_state[1] = qreg[state | qbit1_mask]; + in_gate_state[2] = qreg[state | qbit2_mask]; + in_gate_state[3] = qreg[state | qbit1_mask | qbit2_mask]; + + mat.mul_to(&in_gate_state, &mut out_gate_state); + + qreg[state] = out_gate_state[0]; + qreg[state | qbit1_mask] = out_gate_state[1]; + qreg[state | qbit2_mask] = out_gate_state[2]; + qreg[state | qbit1_mask | qbit2_mask] = out_gate_state[3]; + } + } + + fn apply_controlled_mat(&mut self, mat: &DCf64Mat, controls: Vec, target: usize) { + let qreg = &mut self.qregs[self.qreg_sel]; + + let controls_mask = controls + .iter() + .map(|control| 1 << control) + .collect::>(); + let target_mask = 1 << target; + + let mut in_gate_state = Vector2::zeros(); + let mut out_gate_state = Vector2::zeros(); + + for state in 0..(1 << self.qbits) { + if controls_mask + .iter() + .map(|mask| state & mask == 0) + .any(|b| b) + || state & target_mask != 0 + { + continue; + } + + in_gate_state[0] = qreg[state]; + in_gate_state[1] = qreg[state | target_mask]; + + mat.mul_to(&in_gate_state, &mut out_gate_state); + + qreg[state] = out_gate_state[0]; + qreg[state | target_mask] = out_gate_state[1]; + } + } +} + +impl<'a> Display for Emulator<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "Emulator state: (qbits={}, cbits={})", + self.qbits, self.cbits + )?; + writeln!(f, "qregs=[")?; + for qreg in &self.qregs { + write!(f, "{}", qreg)? + } + write!(f, "]")?; + + Ok(()) + } +} + +#[derive(Clone, Copy)] +pub struct Word { + bits: usize, + data: Wrapping, +} + +impl Word { + pub fn get_val(&self) -> Wrapping { + self.data + } + + pub fn set_to(&mut self, val: Wrapping) { + self.data = val & Wrapping(((1 << self.bits) as i64).wrapping_sub(1)) + } + + pub fn set_bit(&mut self, bit: usize, val: bool) { + self.set_to(self.data & Wrapping(!(1 << bit)) | Wrapping((val as i64) << bit)) + } +} + +impl std::fmt::Debug for Word { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self)?; + Ok(()) + } +} + +impl Display for Word { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.get_val())?; + Ok(()) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum StepResult { + Halted, + Continue, + Branch(usize), +} diff --git a/src/grammar.lalrpop b/src/grammar.lalrpop new file mode 100644 index 0000000..c65864e --- /dev/null +++ b/src/grammar.lalrpop @@ -0,0 +1,311 @@ +use crate::ast::*; + +use lalrpop_util::ParseError; + +grammar<'err>(file: usize, errors: &'err mut Vec, &'static str>>); + +match { + "qbits", + "cbits", + "qregs", + "cregs", + "mem", + r"q[0-9]+" => QBIT, + r"qr[0-9]+" => QREG, + r"cr[0-9]+" => CREG, + r"c[0-9]+" => CBIT, + "gate", + "Rot", + "Imm", + "Reg", + "Addr", + r"[0-9]+" => NUM, + ":", + "pi", + "/", + "[", + "]", + "(", + ")", + "{", + "}", + "+", + "*", + "=", + ";", + r"#.*" => {}, + r"\n+" => NEWLINE, + r" +" => SPACE, + "-", + + // Instructions + // Quantum instructions + "h", + "cnot", + "ccnot", + "x", + "y", + "z", + "rx", + "ry", + "rz", + "u", + "s", + "t", + "sdg", + "tdg", + "p", + "ch", + "cy", + "cz", + "cp", + "swap", + "sqrtx", + "sqrtswp", + "cswap", + + // Classical instructions + "mov", + "add", + "sub", + "mult", + "umult", + "div", + "smult", + "sumult", + "sdiv", + "not", + "and", + "or", + "xor", + "nand", + "nor", + "xnor", + + // Misc instructions + "qsel", + "m", + "cmp", + "jmp", + "jeq", + "jne", + "jg", + "jge", + "jl", + "jle", + "hlt", +} else { + r"[a-zA-Z_][a-zA-Z0-9_]*" => IDENT, +} + +pub Program: Result = { + => resolve_ast(h, i) +} + +Headers: (usize, usize, usize, usize, usize) = { + "qbits" SPACE NEWLINE + "cbits" SPACE NEWLINE + "qregs" SPACE NEWLINE + "cregs" SPACE NEWLINE + "mem" SPACE NEWLINE + => (qbits, cbits, qregs, cregs, mem) +} + +Instructions: Vec = { + (SPACE? NEWLINE)* +} + +Instruction: Inst = { + // Quantum instructions + "h" SPACE => Inst::Hadamard(q, SourceSpan::new(file, l..r)), + "cnot" SPACE SPACE => Inst::Cnot(q0, q1, SourceSpan::new(file, l..r)), + "ccnot" SPACE SPACE SPACE => Inst::Ccnot(q0, q1, q2, SourceSpan::new(file, l..r)), + "x" SPACE => Inst::X(q, SourceSpan::new(file, l..r)), + "y" SPACE => Inst::Y(q, SourceSpan::new(file, l..r)), + "z" SPACE => Inst::Z(q, SourceSpan::new(file, l..r)), + "rx" SPACE SPACE => Inst::Rx(q, rot, SourceSpan::new(file, l..r)), + "ry" SPACE SPACE => Inst::Ry(q, rot, SourceSpan::new(file, l..r)), + "rz" SPACE SPACE => Inst::Rz(q, rot, SourceSpan::new(file, l..r)), + "u" SPACE SPACE SPACE SPACE => Inst::U(q, r0, r1, r2, SourceSpan::new(file, l..r)), + "s" SPACE => Inst::S(q, SourceSpan::new(file, l..r)), + "t" SPACE => Inst::T(q, SourceSpan::new(file, l..r)), + "sdg" SPACE => Inst::Sdg(q, SourceSpan::new(file, l..r)), + "tdg" SPACE => Inst::Tdg(q, SourceSpan::new(file, l..r)), + "p" SPACE SPACE => Inst::Phase(q, rot, SourceSpan::new(file, l..r)), + "ch" SPACE SPACE => Inst::Ch(q0, q1, SourceSpan::new(file, l..r)), + "cy" SPACE SPACE => Inst::Cy(q0, q1, SourceSpan::new(file, l..r)), + "cz" SPACE SPACE => Inst::Cz(q0, q1, SourceSpan::new(file, l..r)), + "cp" SPACE SPACE SPACE => Inst::CPhase(q0, q1, rot, SourceSpan::new(file, l..r)), + "swap" SPACE => Inst::Swap(q0, q1, SourceSpan::new(file, l..r)), + "sqrtx" => Inst::SqrtX(q, SourceSpan::new(file, l..r)), + "sqrtswp" SPACE => Inst::SqrtSwap(q0, q1, SourceSpan::new(file, l..r)), + "cswap" SPACE SPACE SPACE => Inst::CSwap(q0, q1, q2, SourceSpan::new(file, l..r)), + "m" SPACE SPACE SPACE => Inst::Measure(q, cr, c, SourceSpan::new(file, l..r)), + + // Custom gates stuff + "gate" SPACE SPACE? "(" SPACE? "qbits" SPACE? "=" SPACE? SPACE? ")" SPACE? + SPACE? ":" SPACE? SPACE? ")" SPACE?)*> + "{" NEWLINE "}" => Inst::CustomGateDef(name, qbits, args, body), + + SPACE SPACE)*> SPACE)*> )?> => { + let mut other_args; + let e2; + if let Some(args) = args { + other_args = args.0; + e2 = args.1; + } else { + other_args = Vec::new(); + e2 = None; + } + + Inst::Custom( + name, + match e1 { + None => qbits, + Some(e) => { + qbits.push(e); + qbits + } + }, + match e2 { + None => other_args, + Some(e) => { + other_args.push(e); + other_args + } + }, + SourceSpan::new(file, l..r), + ) + }, + + // Classical instructions + "mov" SPACE SPACE => Inst::Mov(r1, r2, SourceSpan::new(file, l..r)), + "add" SPACE SPACE SPACE => Inst::Add( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "sub" SPACE SPACE SPACE => Inst::Sub( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "mult" SPACE SPACE SPACE => Inst::Mul( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "umult" SPACE SPACE SPACE => Inst::UMul( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "div" SPACE SPACE SPACE => Inst::Div( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "smult" SPACE SPACE SPACE => Inst::SMul( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "sumult" SPACE SPACE SPACE => Inst::SUMul( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "sdiv" SPACE SPACE SPACE => Inst::SDiv( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "not" SPACE SPACE => Inst::Not(r1, r2, SourceSpan::new(file, l..r)), + "and" SPACE SPACE SPACE => Inst::And( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "or" SPACE SPACE SPACE => Inst::Or( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "xor" SPACE SPACE SPACE => Inst::Xor( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "nand" SPACE SPACE SPACE => Inst::Nand( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "nor" SPACE SPACE SPACE => Inst::Nor( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + "xnor" SPACE SPACE SPACE => Inst::Xnor( + r1, r2, r3, + SourceSpan::new(file, l..r), + ), + + // Misc instructions + "qsel" SPACE => Inst::Qsel(qr, SourceSpan::new(file, l..r)), + "cmp" SPACE SPACE => Inst::Cmp(r1, r2, SourceSpan::new(file, l..r)), + "jmp" SPACE => Inst::Jmp(lbl, SourceSpan::new(file, l..r)), + "jeq" SPACE => Inst::Jeq(lbl, SourceSpan::new(file, l..r)), + "jne" SPACE => Inst::Jne(lbl, SourceSpan::new(file, l..r)), + "jg" SPACE => Inst::Jg(lbl, SourceSpan::new(file, l..r)), + "jge" SPACE => Inst::Jge(lbl, SourceSpan::new(file, l..r)), + "jl" SPACE => Inst::Jl(lbl, SourceSpan::new(file, l..r)), + "jle" SPACE => Inst::Jle(lbl, SourceSpan::new(file, l..r)), + "hlt" => Inst::Hlt, + +