diff --git a/Cargo.lock b/Cargo.lock index ca74fbffde..e7cea17172 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2518,6 +2518,14 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "qsc_stim_parser" +version = "0.0.0" +dependencies = [ + "enum-iterator", + "qsc_data_structures", +] + [[package]] name = "qsc_wasm" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index ef4886ae8f..a3beb773c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "source/compiler/qsc_hir", "source/compiler/qsc_openqasm_compiler", "source/compiler/qsc_openqasm_parser", + "source/compiler/qsc_stim_parser", "source/compiler/qsc_linter", "source/compiler/qsc_lowerer", "source/compiler/qsc_parse", diff --git a/source/compiler/qsc_stim_parser/Cargo.toml b/source/compiler/qsc_stim_parser/Cargo.toml new file mode 100644 index 0000000000..6f3e59d207 --- /dev/null +++ b/source/compiler/qsc_stim_parser/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "qsc_stim_parser" +edition.workspace = true +version.workspace = true + +[dependencies] +enum-iterator.workspace = true +qsc_data_structures = { path = "../qsc_data_structures" } + +[dev-dependencies] diff --git a/source/compiler/qsc_stim_parser/examples/compile_stim.rs b/source/compiler/qsc_stim_parser/examples/compile_stim.rs new file mode 100644 index 0000000000..3c070abd6e --- /dev/null +++ b/source/compiler/qsc_stim_parser/examples/compile_stim.rs @@ -0,0 +1,15 @@ +use qsc_stim_parser::parser::parse; +use qsc_stim_parser::qir::compile_to_qir; +use std::fs; + +fn main() { + let stim_code = + fs::read_to_string("examples/example.stim").expect("Failed to read examples/example.stim"); + + let circuit = parse(&stim_code); + let qir = compile_to_qir(&circuit); + + fs::write("examples/example.qir", &qir).expect("Failed to write examples/example.qir"); + + println!("Wrote examples/example.qir"); +} diff --git a/source/compiler/qsc_stim_parser/examples/lex_stim.rs b/source/compiler/qsc_stim_parser/examples/lex_stim.rs new file mode 100644 index 0000000000..ef1371cc15 --- /dev/null +++ b/source/compiler/qsc_stim_parser/examples/lex_stim.rs @@ -0,0 +1,33 @@ +use qsc_stim_parser::lex::Lexer; +use std::fs; +use std::io::Write; + +fn main() { + let stim_code = + fs::read_to_string("examples/example.stim").expect("Failed to read examples/example.stim"); + + let mut out = + fs::File::create("examples/lex_output.txt").expect("Failed to create output file"); + + writeln!(out, "{:<20} {:<10} {:}", "TOKEN KIND", "SPAN", "TEXT").unwrap(); + writeln!(out, "{:-<50}", "").unwrap(); + + let lexer = Lexer::new(&stim_code); + for token in lexer { + let text = &stim_code[token.span.lo as usize..token.span.hi as usize]; + let text_display = match token.kind { + qsc_stim_parser::lex::TokenKind::Newline => "\\n".to_string(), + _ => format!("{:?}", text), + }; + writeln!( + out, + "{:<20} {:<10} {}", + token.kind.to_string(), + format!("{}..{}", token.span.lo, token.span.hi), + text_display + ) + .unwrap(); + } + + println!("Wrote examples/lex_output.txt"); +} diff --git a/source/compiler/qsc_stim_parser/examples/parse_stim.rs b/source/compiler/qsc_stim_parser/examples/parse_stim.rs new file mode 100644 index 0000000000..59f95b9dbe --- /dev/null +++ b/source/compiler/qsc_stim_parser/examples/parse_stim.rs @@ -0,0 +1,89 @@ +use qsc_stim_parser::parser::{Circuit, Instruction, Item, Pauli, Target, TargetKind, parse}; +use std::fs; +use std::io::Write; + +fn write_circuit(out: &mut impl Write, circuit: &Circuit) { + writeln!(out, "(circuit").unwrap(); + for item in &circuit.items { + write_item(out, item, 1); + } + writeln!(out, ")").unwrap(); +} + +fn write_item(out: &mut impl Write, item: &Item, indent: usize) { + let pad = " ".repeat(indent); + match item { + Item::Line(line) => { + write!(out, "{pad}(").unwrap(); + write_instruction(out, &line.instruction); + writeln!(out, ")").unwrap(); + } + Item::Block(block) => { + write!(out, "{pad}(").unwrap(); + write_instruction(out, &block.block_instruction); + writeln!(out).unwrap(); + for item in &block.items { + write_item(out, item, indent + 1); + } + writeln!(out, "{pad})").unwrap(); + } + } +} + +fn write_instruction(out: &mut impl Write, instr: &Instruction) { + write!(out, "{}", instr.name).unwrap(); + if let Some(tag) = &instr.tag { + write!(out, "[{}]", tag).unwrap(); + } + if !instr.args.is_empty() { + for arg in &instr.args { + write!(out, " {}", arg).unwrap(); + } + } + for target in &instr.targets { + write!(out, " ").unwrap(); + write_target(out, target); + } +} + +fn write_target(out: &mut impl Write, target: &Target) { + match &target.kind { + TargetKind::Qubit { negated, value } => { + if *negated { + write!(out, "!").unwrap(); + } + write!(out, "{}", value).unwrap(); + } + TargetKind::MeasurementRecord { value } => write!(out, "rec[-{}]", value).unwrap(), + TargetKind::SweepBit { value } => write!(out, "sweep[{}]", value).unwrap(), + TargetKind::Pauli { + negated, + pauli, + value, + } => { + if *negated { + write!(out, "!").unwrap(); + } + let p = match pauli { + Pauli::X => "X", + Pauli::Y => "Y", + Pauli::Z => "Z", + }; + write!(out, "{}{}", p, value).unwrap(); + } + TargetKind::Combiner => write!(out, "*").unwrap(), + } +} + +fn main() { + let stim_code = + fs::read_to_string("examples/example.stim").expect("Failed to read examples/example.stim"); + + let circuit = parse(&stim_code); + + let mut out = + fs::File::create("examples/parse_output.txt").expect("Failed to create output file"); + write_circuit(&mut out, &circuit); + + println!("Wrote examples/parse_output.txt"); +} diff --git a/source/compiler/qsc_stim_parser/src/lex.rs b/source/compiler/qsc_stim_parser/src/lex.rs new file mode 100644 index 0000000000..cd25f25a4f --- /dev/null +++ b/source/compiler/qsc_stim_parser/src/lex.rs @@ -0,0 +1,195 @@ +use enum_iterator::Sequence; +use qsc_data_structures::span::Span; +use std::str::CharIndices; +use std::{ + fmt::{self, Display, Formatter}, + iter::Peekable, +}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Token { + pub kind: TokenKind, + pub span: Span, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum TokenKind { + Newline, // \n + Uint, // unsigned integers + Double, // floating-point numbers + InstructionName, // H, X, CNOT, etc. + Rec, // rec[- ...] + Sweep, // sweep[...] + Tag, // "[...]" + Open(Delim), // ( { + Close(Delim), // ) } + Star, // * + Bang, // ! + Comma, // , + Unknown, // unknown token +} + +impl Display for TokenKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + TokenKind::Newline => f.write_str("newline"), + TokenKind::Uint => f.write_str("uint"), + TokenKind::Double => f.write_str("double"), + TokenKind::InstructionName => f.write_str("instruction_name"), + TokenKind::Rec => f.write_str("rec"), + TokenKind::Sweep => f.write_str("sweep"), + TokenKind::Tag => write!(f, "tag"), + TokenKind::Open(delim) => write!(f, "open({})", delim), + TokenKind::Close(delim) => write!(f, "close({})", delim), + TokenKind::Star => write!(f, "star"), + TokenKind::Bang => write!(f, "bang"), + TokenKind::Comma => write!(f, "comma"), + TokenKind::Unknown => write!(f, "unknown"), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)] +pub enum Delim { + Paren, + Brace, +} + +impl Display for Delim { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Delim::Paren => f.write_str("paren"), + Delim::Brace => f.write_str("brace"), + } + } +} + +pub struct Lexer<'a> { + input: &'a str, + input_len: u32, + chars: Peekable>, +} + +impl<'a> Lexer<'a> { + pub fn new(input: &'a str) -> Self { + Self { + input, + input_len: input + .len() + .try_into() + .expect("input length should fit into u32"), + chars: input.char_indices().peekable(), + } + } + + fn eat_while(&mut self, mut f: impl Fn(char) -> bool) { + while self.chars.next_if(|i| f(i.1)).is_some() {} + } + + fn whitespace(&mut self) { + self.eat_while(char::is_whitespace); + } + + fn comment(&mut self) { + self.eat_while(|c| c != '\n'); + self.whitespace(); + } + + fn scan_number(&mut self) -> TokenKind { + self.eat_while(|c| c.is_ascii_digit()); + let mut is_double = false; + if self.chars.next_if(|(_, c)| *c == '.').is_some() { + self.eat_while(|c| c.is_ascii_digit()); + is_double = true; + } + if self + .chars + .next_if(|(_, c)| *c == 'e' || *c == 'E') + .is_some() + { + // scientific notation + self.chars.next_if(|(_, c)| *c == '+' || *c == '-'); + self.eat_while(|c| c.is_ascii_digit()); + is_double = true; + } + if is_double { + TokenKind::Double + } else { + TokenKind::Uint + } + } + + fn scan_identifier(&mut self, lo: usize) -> TokenKind { + self.eat_while(|c| c.is_alphanumeric() || c == '_'); + let hi: usize = self + .chars + .peek() + .map_or(self.input_len as usize, |(i, _)| *i); + // TODO: What if some identifier starts with "rec" but is not a rec token? + match &self.input[lo..hi] { + "rec" => { + self.eat_while(|c| c != ']'); + self.chars.next_if(|(_, c)| *c == ']'); + TokenKind::Rec + } + "sweep" => { + self.eat_while(|c| c != ']'); + self.chars.next_if(|(_, c)| *c == ']'); + TokenKind::Sweep + } + _ => TokenKind::InstructionName, + } + } +} + +impl Iterator for Lexer<'_> { + type Item = Token; + + fn next(&mut self) -> Option { + use Delim::{Brace, Paren}; + let (offset, c) = self.chars.next()?; + let lo: u32 = offset.try_into().expect("offset should fit into u32"); + let token_kind = match c { + '\n' => { + self.whitespace(); + TokenKind::Newline + } + ' ' | '\t' => { + self.whitespace(); + return self.next(); + } + '#' => { + if self.chars.next_if(|(_, c)| *c == '!').is_some() { + self.eat_while(|c| !c.is_whitespace()); + TokenKind::InstructionName + } else { + self.comment(); + return self.next(); + } + } + '(' => TokenKind::Open(Paren), + ')' => TokenKind::Close(Paren), + '{' => TokenKind::Open(Brace), + '}' => TokenKind::Close(Brace), + '*' => TokenKind::Star, + '!' => TokenKind::Bang, + ',' => TokenKind::Comma, + '0'..='9' => self.scan_number(), + 'A'..='Z' | 'a'..='z' => self.scan_identifier(lo as usize), + '[' => { + self.eat_while(|c| c != ']'); + self.chars.next_if(|(_, c)| *c == ']'); + TokenKind::Tag + } + _ => TokenKind::Unknown, + }; + + let hi: u32 = self.chars.peek().map_or(self.input_len, |(i, _)| *i as u32); + Some(Token { + kind: token_kind, + span: Span { lo, hi }, + }) + } +} + +//TODO: Deal with escaping diff --git a/source/compiler/qsc_stim_parser/src/lib.rs b/source/compiler/qsc_stim_parser/src/lib.rs new file mode 100644 index 0000000000..c59408e5bc --- /dev/null +++ b/source/compiler/qsc_stim_parser/src/lib.rs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +pub mod lex; +pub mod parser; +pub mod qir; diff --git a/source/compiler/qsc_stim_parser/src/parser.rs b/source/compiler/qsc_stim_parser/src/parser.rs new file mode 100644 index 0000000000..f1788c1a0b --- /dev/null +++ b/source/compiler/qsc_stim_parser/src/parser.rs @@ -0,0 +1,377 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::lex::Delim::Brace; +use crate::lex::Delim::Paren; +use crate::lex::Lexer; +use crate::lex::Token; +use crate::lex::TokenKind; +use qsc_data_structures::span::Span; +use std::{iter::Peekable, str::FromStr}; + +#[derive(Debug)] +pub struct Circuit { + pub span: Span, + pub items: Vec, +} + +#[derive(Debug)] +pub enum Item { + Line(Line), + Block(Block), +} + +#[derive(Debug)] +pub struct Line { + pub span: Span, + pub instruction: Instruction, +} + +#[derive(Debug)] +pub struct Block { + pub span: Span, + pub block_instruction: Instruction, // currently, only the "REPEAT" instruction is supported + pub items: Vec, +} + +#[derive(Debug)] +pub struct Instruction { + pub span: Span, + pub name: String, + pub tag: Option, + pub args: Vec, + pub targets: Vec, +} + +#[derive(Debug)] +pub struct Target { + pub span: Span, + pub kind: TargetKind, +} + +#[derive(Debug)] +pub enum TargetKind { + Qubit { + negated: bool, + value: u32, + }, + MeasurementRecord { + value: u32, + }, + SweepBit { + value: u32, + }, + Pauli { + negated: bool, + pauli: Pauli, + value: u32, + }, + Combiner, +} + +#[derive(Debug)] +pub enum Pauli { + X, + Y, + Z, +} + +impl FromStr for Pauli { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "X" => Ok(Pauli::X), + "Y" => Ok(Pauli::Y), + "Z" => Ok(Pauli::Z), + _ => Err(()), + } + } +} + +pub fn parse(input: &str) -> Circuit { + Parser::new(input).parse() +} + +struct Parser<'a> { + input: &'a str, + tokens: Peekable>, +} + +impl<'a> Parser<'a> { + pub fn new(input: &'a str) -> Self { + Self { + input, + tokens: Lexer::new(input).peekable(), + } + } + + pub fn expect(&mut self, kind: TokenKind) -> Token { + let token = self.tokens.next().expect("expected token"); + if token.kind != kind { + panic!("expected token of kind {:?}", kind); + } + token + } + + fn expect_number(&mut self) -> Token { + let token = self.tokens.next().expect("expected number"); + if token.kind != TokenKind::Uint && token.kind != TokenKind::Double { + panic!("expected number, got {:?}", token.kind); + } + token + } + + pub fn parse(&mut self) -> Circuit { + let input_len = self + .input + .len() + .try_into() + .expect("input length should fit into u32"); + + let mut items = Vec::new(); + while let Some(item) = self.parse_item() { + items.push(item); + } + + Circuit { + span: Span { + lo: 0, + hi: input_len, + }, + items, + } + } + + fn parse_item(&mut self) -> Option { + // TODO WHAT IF IT STARTS WITH A NEWLINE? + + if let TokenKind::InstructionName = self.tokens.peek()?.kind { + // Could be the start of a block or of a line + let instruction = self.parse_instruction(); + if let Some(token) = self.tokens.peek() { + if token.kind == TokenKind::Open(Brace) { + return Some(Item::Block(self.parse_block(instruction))); + } + } + return Some(Item::Line(self.parse_line(instruction))); + } else { + // TODO error! The start of every item should be an instruction; + None + } + } + + fn parse_block(&mut self, instruction: Instruction) -> Block { + let lo = instruction.span.lo; + let mut items = Vec::new(); + self.expect(TokenKind::Open(Brace)); + self.expect(TokenKind::Newline); + loop { + if self + .tokens + .peek() + .is_some_and(|t| t.kind == TokenKind::Close(Brace)) + { + break; + } + match self.parse_item() { + Some(item) => items.push(item), + None => break, + } + } + let closing_brace = self.expect(TokenKind::Close(Brace)); + self.expect(TokenKind::Newline); + let hi = closing_brace.span.hi; + Block { + span: Span { lo, hi }, + block_instruction: instruction, + items, + } + } + + fn parse_line(&mut self, instruction: Instruction) -> Line { + self.expect(TokenKind::Newline); + Line { + span: instruction.span, + instruction, + } + } + + fn parse_instruction(&mut self) -> Instruction { + let name_token = self.expect(TokenKind::InstructionName); + let lo = name_token.span.lo; + let name = self.extract_string(name_token, None); + + let tag_token = self.tokens.next_if(|t| t.kind == TokenKind::Tag); + let tag: Option; + match tag_token { + Some(tag_token) => { + tag = Some(self.extract_string( + tag_token, + Some(Span { + lo: tag_token.span.lo + 1, + hi: tag_token.span.hi - 1, + }), + )); // Remove the surrounding brackets + } + None => { + tag = None; + } + } + + let mut args = Vec::new(); + let mut targets = Vec::new(); + + if self + .tokens + .peek() + .is_some_and(|t| t.kind == TokenKind::Open(Paren)) + { + self.expect(TokenKind::Open(Paren)); // consume '(' + // Parse first arg (no leading comma) + if self + .tokens + .peek() + .is_some_and(|t| t.kind != TokenKind::Close(Paren)) + { + let arg = self.expect_number(); + args.push(self.extract_double(arg, None)); + } + // Each subsequent arg must be preceded by a comma + while self + .tokens + .peek() + .is_some_and(|t| t.kind != TokenKind::Close(Paren)) + { + self.expect(TokenKind::Comma); + let arg = self.expect_number(); + args.push(self.extract_double(arg, None)); + } + self.expect(TokenKind::Close(Paren)); + } + + while let Some(&token) = self.tokens.peek() { + if !self.is_target_start(&token) { + break; + } + targets.push(self.parse_target()); + } + + let hi = targets + .last() + .map(|t| t.span.hi) + .unwrap_or(name_token.span.hi); + + Instruction { + span: Span { lo, hi }, + name, + tag, + args, + targets, + } + } + + fn is_target_start(&self, token: &Token) -> bool { + match token.kind { + TokenKind::Uint + | TokenKind::Rec + | TokenKind::Sweep + | TokenKind::Bang + | TokenKind::Star => true, + TokenKind::InstructionName => { + let text = self.extract_string(*token, None); + text.starts_with('X') || text.starts_with('Y') || text.starts_with('Z') // STARTS WITH PAULI + } + _ => false, + } + } + + fn parse_target(&mut self) -> Target { + let negated_token = self.tokens.next_if(|t| t.kind == TokenKind::Bang); + let negated = negated_token.is_some(); + let first_token = self.tokens.next().expect("target empty"); + let lo = negated_token.map_or(first_token.span.lo, |t| t.span.lo); + let span = Span { + lo, + hi: first_token.span.hi, + }; + + match first_token.kind { + TokenKind::Uint => Target { + span, + kind: TargetKind::Qubit { + negated, + value: self.extract_uint(first_token, None), + }, + }, + TokenKind::InstructionName => Target { + span, + kind: TargetKind::Pauli { + negated, + pauli: self + .extract_string( + first_token, + Some(Span { + lo: span.lo, + hi: span.lo + 1, + }), + ) + .parse::() + .unwrap(), + value: self.extract_uint( + first_token, + Some(Span { + lo: span.lo + 1, + hi: span.hi, + }), + ), + }, + }, // Already validated + TokenKind::Rec => Target { + span, + kind: TargetKind::MeasurementRecord { + value: self.extract_uint( + first_token, + Some(Span { + lo: span.lo + 5, + hi: span.hi - 1, + }), + ), // Strips 'rec[-' prefix and trailing ']' TODO validate it + }, + }, + TokenKind::Sweep => Target { + span, + kind: TargetKind::SweepBit { + value: self.extract_uint( + first_token, + Some(Span { + lo: span.lo + 6, + hi: span.hi - 1, + }), + ), + }, // Strips 'sweep[' prefix and trailing ']' TODO validate it + }, + TokenKind::Star => Target { + span, + kind: TargetKind::Combiner, + }, + _ => panic!("Unexpected target kind"), + } + } + + fn extract_uint(&mut self, token: Token, span: Option) -> u32 { + self.extract_string(token, span).parse().unwrap() + } + + fn extract_double(&mut self, token: Token, span: Option) -> f64 { + self.extract_string(token, span).parse().unwrap() + } + + fn extract_string(&self, token: Token, span: Option) -> String { + if let Some(span) = span { + self.input[span.lo as usize..span.hi as usize].to_string() + } else { + self.input[token.span.lo as usize..token.span.hi as usize].to_string() + } + } +} diff --git a/source/compiler/qsc_stim_parser/src/qir.rs b/source/compiler/qsc_stim_parser/src/qir.rs new file mode 100644 index 0000000000..057aef471a --- /dev/null +++ b/source/compiler/qsc_stim_parser/src/qir.rs @@ -0,0 +1,465 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +use crate::parser::*; +use std::collections::HashMap; +use std::fmt::Write; + +#[derive(Clone, Copy)] +enum Operand { + /// A qubit operand, carrying the raw Stim qubit index. + Qubit(u32), + /// A result operand — the writer allocates the next result ID. + Result, +} + +struct QirWriter { + output: String, + qubit_map: HashMap, + num_results: u32, + used_intrinsics: HashMap, +} + +impl QirWriter { + fn new() -> Self { + Self { + output: String::new(), + qubit_map: HashMap::new(), + num_results: 0, + used_intrinsics: HashMap::new(), + } + } + + // Writes: ` call void @__quantum__qis__{intrinsic}__body(ptr inttoptr (i64 N to ptr), ...)` + // Resolves qubit indices via the qubit map and allocates result IDs internally. + fn write_call(&mut self, intrinsic: &str, operands: &[Operand]) { + write!( + self.output, + " call void @__quantum__qis__{intrinsic}__body(" + ) + .unwrap(); + for (i, &operand) in operands.iter().enumerate() { + if i > 0 { + write!(self.output, ", ").unwrap(); + } + self.write_operand(operand); + } + writeln!(self.output, ")").unwrap(); + self.used_intrinsics + .insert(intrinsic.to_string(), operands.len()); + } + + // Resolves an Operand to its QIR ID and writes: `ptr inttoptr (i64 N to ptr)` + fn write_operand(&mut self, operand: Operand) { + let id = match operand { + Operand::Qubit(stim_index) => self.map_qubit(stim_index), + Operand::Result => self.next_result(), + }; + write!(self.output, "ptr inttoptr (i64 {id} to ptr)").unwrap(); + } + + fn write_header(&mut self) { + writeln!(self.output, "define i64 @ENTRYPOINT__main() #0 {{").unwrap(); + writeln!( + self.output, + " call void @__quantum__rt__initialize(ptr null)" + ) + .unwrap(); + } + + fn write_record_output(&mut self) { + let num_results = self.num_results; + writeln!( + self.output, + " call void @__quantum__rt__array_record_output(i64 {num_results}, ptr null)" + ) + .unwrap(); + for i in 0..num_results { + writeln!( + self.output, + " call void @__quantum__rt__result_record_output(ptr inttoptr (i64 {i} to ptr), ptr null)" + ) + .unwrap(); + } + } + + fn write_declarations(&mut self) { + writeln!(self.output).unwrap(); + writeln!(self.output, "declare void @__quantum__rt__initialize(ptr)").unwrap(); + writeln!(self.output, "declare void @__quantum__rt__array_record_output(i64, ptr)").unwrap(); + writeln!(self.output, "declare void @__quantum__rt__result_record_output(ptr, ptr)").unwrap(); + for (intrinsic, arity) in &self.used_intrinsics { + let params = (0..*arity).map(|_| "ptr").collect::>().join(", "); + writeln!( + self.output, + "declare void @__quantum__qis__{intrinsic}__body({params})" + ) + .unwrap(); + } + } + + fn write_footer(&mut self) { + self.write_record_output(); + writeln!(self.output, " ret i64 0").unwrap(); + writeln!(self.output, "}}").unwrap(); + self.write_declarations(); + + let num_qubits = self.qubit_map.len(); + let num_results = self.num_results; + writeln!(self.output).unwrap(); + writeln!( + self.output, + "attributes #0 = {{ \"entry_point\" \"output_labeling_schema\" \"qir_profiles\"=\"adaptive_profile\" \"required_num_qubits\"=\"{num_qubits}\" \"required_num_results\"=\"{num_results}\" }}" + ).unwrap(); + writeln!(self.output, "attributes #1 = {{ \"irreversible\" }}").unwrap(); + writeln!(self.output).unwrap(); + writeln!(self.output, "; module flags").unwrap(); + writeln!(self.output).unwrap(); + writeln!( + self.output, + "!llvm.module.flags = !{{!0, !1, !2, !3, !4, !5, !6, !7}}" + ) + .unwrap(); + writeln!(self.output).unwrap(); + writeln!( + self.output, + "!0 = !{{i32 1, !\"qir_major_version\", i32 2}}" + ) + .unwrap(); + writeln!( + self.output, + "!1 = !{{i32 7, !\"qir_minor_version\", i32 1}}" + ) + .unwrap(); + writeln!( + self.output, + "!2 = !{{i32 1, !\"dynamic_qubit_management\", i1 false}}" + ) + .unwrap(); + writeln!( + self.output, + "!3 = !{{i32 1, !\"dynamic_result_management\", i1 false}}" + ) + .unwrap(); + writeln!( + self.output, + "!4 = !{{i32 5, !\"int_computations\", !{{!\"i64\"}}}}" + ) + .unwrap(); + writeln!( + self.output, + "!5 = !{{i32 5, !\"float_computations\", !{{!\"double\"}}}}" + ) + .unwrap(); + writeln!( + self.output, + "!6 = !{{i32 7, !\"backwards_branching\", i2 3}}" + ) + .unwrap(); + writeln!(self.output, "!7 = !{{i32 1, !\"arrays\", i1 true}}").unwrap(); + } + + // Maps a Stim qubit index to a dense 0-based QIR qubit ID. + fn map_qubit(&mut self, stim_index: u32) -> u32 { + let next_id = self.qubit_map.len() as u32; + *self.qubit_map.entry(stim_index).or_insert(next_id) + } + + // Allocates the next result ID. + fn next_result(&mut self) -> u32 { + let id = self.num_results; + self.num_results += 1; + id + } +} + +enum InstructionKind { + PauliGate, + SingleQubitCliffordGate, + TwoQubitCliffordGate, + NoiseChannel, + CollapsingGate, + PairMeasurementGate, + GeneralizedPauliProductGate, + ControlFlow, + Annotations, + CustomInstruction, +} + +struct Compiler { + writer: QirWriter, + last_preselect_begin: Option, + num_preselect_expects: u32, +} + +impl Compiler { + fn new() -> Self { + Self { + writer: QirWriter::new(), + last_preselect_begin: None, + num_preselect_expects: 0, + } + } + + fn compile_circuit(&mut self, circuit: &Circuit) { + for item in &circuit.items { + self.compile_item(item); + } + } + + fn compile_item(&mut self, item: &Item) { + match item { + Item::Block(block) => self.compile_block(block), + Item::Line(line) => self.compile_line(line), + } + } + + fn compile_block(&mut self, block: &Block) { + let Block { + block_instruction, + items, + .. + } = block; + + self.compile_instruction(block_instruction); + for item in items { + self.compile_item(item); + } + } + + fn compile_line(&mut self, line: &Line) { + let Line { instruction, .. } = line; + self.compile_instruction(instruction); + } + + fn compile_instruction(&mut self, instruction: &Instruction) { + match Self::instruction_kind(&instruction.name) { + InstructionKind::PauliGate => { + self.compile_pauli_gate(instruction); + } + InstructionKind::SingleQubitCliffordGate => { + self.compile_single_qubit_clifford_gate(instruction); + } + InstructionKind::TwoQubitCliffordGate => { + self.compile_two_qubit_clifford_gate(instruction); + } + InstructionKind::NoiseChannel => { + self.compile_noise_channel(instruction); + } + InstructionKind::CollapsingGate => { + self.compile_collapsing_gate(instruction); + } + InstructionKind::PairMeasurementGate => { + self.compile_pair_measurement_gate(instruction); + } + InstructionKind::GeneralizedPauliProductGate => { + self.compile_generalized_pauli_product_gate(instruction); + } + InstructionKind::ControlFlow => { + self.compile_control_flow(instruction); + } + InstructionKind::Annotations => { + self.compile_annotations(instruction); + } + InstructionKind::CustomInstruction => { + self.compile_custom_instruction(instruction); + } + } + } + + fn compile_pauli_gate(&mut self, instruction: &Instruction) { + let gate = instruction.name.to_lowercase(); + for target in &instruction.targets { + let TargetKind::Qubit { value, .. } = target.kind else { + continue; + }; + self.writer.write_call(&gate, &[Operand::Qubit(value)]); + } + } + + fn compile_single_qubit_clifford_gate(&mut self, instruction: &Instruction) { + let gate = instruction.name.to_lowercase(); + if gate == "h" || gate == "s" { + for target in &instruction.targets { + let TargetKind::Qubit { value, .. } = target.kind else { + continue; + }; + self.writer.write_call(&gate, &[Operand::Qubit(value)]); + } + } else if gate == "sqrt_x" { + // decomposed into H S H + for target in &instruction.targets { + let TargetKind::Qubit { value, .. } = target.kind else { + continue; + }; + let q = Operand::Qubit(value); + self.writer.write_call("h", &[q]); + self.writer.write_call("s", &[q]); + self.writer.write_call("h", &[q]); + } + } + } + + fn compile_two_qubit_clifford_gate(&mut self, instruction: &Instruction) { + let gate = instruction.name.to_lowercase(); + if gate == "cz" { + let targets = &instruction.targets; + for pair in targets.chunks(2) { + let TargetKind::Qubit { value: v0, .. } = pair[0].kind else { + continue; + }; + let TargetKind::Qubit { value: v1, .. } = pair[1].kind else { + continue; + }; + self.writer + .write_call(&gate, &[Operand::Qubit(v0), Operand::Qubit(v1)]); + } + } + } + + fn compile_noise_channel(&mut self, instruction: &Instruction) {} + + fn compile_collapsing_gate(&mut self, instruction: &Instruction) { + let gate = instruction.name.to_lowercase(); + if gate == "r" { + for target in &instruction.targets { + let TargetKind::Qubit { value, .. } = target.kind else { + continue; + }; + self.writer.write_call("reset", &[Operand::Qubit(value)]); + } + } else if gate == "mr" { + for target in &instruction.targets { + let TargetKind::Qubit { value, .. } = target.kind else { + continue; + }; + self.writer + .write_call("mresetz", &[Operand::Qubit(value), Operand::Result]); + } + } else if gate == "mrx" { + // decomposed into H MRZ H + for target in &instruction.targets { + let TargetKind::Qubit { value, .. } = target.kind else { + continue; + }; + let q = Operand::Qubit(value); + self.writer.write_call("h", &[q]); + self.writer.write_call("mresetz", &[q, Operand::Result]); + self.writer.write_call("h", &[q]); + } + } + } + + fn compile_pair_measurement_gate(&mut self, instruction: &Instruction) {} + + fn compile_generalized_pauli_product_gate(&mut self, instruction: &Instruction) {} + + fn compile_control_flow(&mut self, instruction: &Instruction) {} + + fn compile_annotations(&mut self, instruction: &Instruction) {} + + fn compile_custom_instruction(&mut self, instruction: &Instruction) { + let instruction_name = instruction.name.to_lowercase(); + if instruction_name == "#!preselect_begin" { + self.last_preselect_begin = match self.last_preselect_begin { + None => Some(0), + Some(n) => Some(n + 1), + }; + writeln!( + self.writer.output, + "preselect_begin_{}:", + self.last_preselect_begin.unwrap() + ) + .unwrap(); + } else if instruction_name == "#!preselect_expect" { + write!( + self.writer.output, + "preselect_r{}", + self.num_preselect_expects + ) + .unwrap(); + self.num_preselect_expects += 1; + write!( + self.writer.output, + " = call i1 @__quantum__qis__read_result__body(" + ) + .unwrap(); + let TargetKind::Qubit { value, .. } = instruction.targets[0].kind else { + return; + }; + // read_result takes a result operand + let result_id = self.writer.map_qubit(value); + write!(self.writer.output, "ptr inttoptr (i64 {result_id} to ptr)").unwrap(); + writeln!(self.writer.output, ")").unwrap(); + // EMIT BREAK, br i1 %preselect_r1, label %preselect_fail_1, label %continue_1 + // HAVE IT THE OTHER WAY AROUND IF TARGETS[1] IS 1 + } + } + + fn instruction_kind(name: &str) -> InstructionKind { + match name { + // Pauli Gates + "I" | "X" | "Y" | "Z" => InstructionKind::PauliGate, + + // Single Qubit Clifford Gates + "C_NXYZ" | "C_NZYX" | "C_XNYZ" | "C_XYNZ" | "C_XYZ" | "C_ZNYX" | "C_ZYNX" | "C_ZYX" + | "H" | "H_NXY" | "H_NXZ" | "H_NYZ" | "H_XY" | "H_XZ" | "H_YZ" | "S" | "SQRT_X" + | "SQRT_X_DAG" | "SQRT_Y" | "SQRT_Y_DAG" | "SQRT_Z" | "SQRT_Z_DAG" | "S_DAG" => { + InstructionKind::SingleQubitCliffordGate + } + + // Two Qubit Clifford Gates + "CNOT" | "CX" | "CXSWAP" | "CY" | "CZ" | "CZSWAP" | "II" | "ISWAP" | "ISWAP_DAG" + | "SQRT_XX" | "SQRT_XX_DAG" | "SQRT_YY" | "SQRT_YY_DAG" | "SQRT_ZZ" | "SQRT_ZZ_DAG" + | "SWAP" | "SWAPCX" | "SWAPCZ" | "XCX" | "XCY" | "XCZ" | "YCX" | "YCY" | "YCZ" + | "ZCX" | "ZCY" | "ZCZ" => InstructionKind::TwoQubitCliffordGate, + + // Noise Channels + "CORRELATED_ERROR" + | "DEPOLARIZE1" + | "DEPOLARIZE2" + | "E" + | "ELSE_CORRELATED_ERROR" + | "HERALDED_ERASE" + | "HERALDED_PAULI_CHANNEL_1" + | "II_ERROR" + | "I_ERROR" + | "PAULI_CHANNEL_1" + | "PAULI_CHANNEL_2" + | "X_ERROR" + | "Y_ERROR" + | "Z_ERROR" => InstructionKind::NoiseChannel, + + // Collapsing Gates + "M" | "MR" | "MRX" | "MRY" | "MRZ" | "MX" | "MY" | "MZ" | "R" | "RX" | "RY" | "RZ" => { + InstructionKind::CollapsingGate + } + + // Pair Measurement Gates + "MXX" | "MYY" | "MZZ" => InstructionKind::PairMeasurementGate, + + // Generalized Pauli Product Gates + "MPP" | "SPP" | "SPP_DAG" => InstructionKind::GeneralizedPauliProductGate, + + // Control Flow + "REPEAT" => InstructionKind::ControlFlow, + + // Annotations + "DETECTOR" | "MPAD" | "OBSERVABLE_INCLUDE" | "QUBIT_COORDS" | "SHIFT_COORDS" + | "TICK" => InstructionKind::Annotations, + + "#!preselect_begin" | "#!preselect_expect" => InstructionKind::CustomInstruction, + _ => InstructionKind::CustomInstruction, + } + } + + fn into_qir(mut self, circuit: &Circuit) -> String { + self.writer.write_header(); + self.compile_circuit(circuit); + self.writer.write_footer(); + self.writer.output + } +} + +pub fn compile_to_qir(circuit: &Circuit) -> String { + Compiler::new().into_qir(circuit) +}