diff --git a/Cargo.lock b/Cargo.lock index 71b05e0509..07d8de2d40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1393,6 +1393,7 @@ dependencies = [ "indoc", "num-bigint", "qsc", + "resource_estimator", ] [[package]] diff --git a/library/Cargo.toml b/library/Cargo.toml index f37329bc6d..44294a3b84 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -13,6 +13,7 @@ indoc = { workspace = true } num-bigint = { workspace = true } expect-test = { workspace = true } qsc = { path = "../source/compiler/qsc" } +resource_estimator = { path = "../source/resource_estimator" } [lints] workspace = true diff --git a/library/core/qir.qs b/library/core/qir.qs index 6f9a57bd0a..4fbd077aaa 100644 --- a/library/core/qir.qs +++ b/library/core/qir.qs @@ -46,5 +46,30 @@ namespace QIR.Runtime { body intrinsic; } - export __quantum__rt__qubit_allocate, __quantum__rt__qubit_borrow, __quantum__rt__qubit_release, AllocateQubitArray, BorrowQubitArray, ReleaseQubitArray, __quantum__rt__read_loss; + operation __quantum__rt__memory_qubit_allocate() : MemoryQubit { + body intrinsic; + } + + operation __quantum__rt__memory_qubit_release(q : MemoryQubit) : Unit { + body intrinsic; + } + + operation AllocateMemoryQubitArray(size : Int) : MemoryQubit[] { + if size < 0 { + fail "Cannot allocate memory qubit array with a negative length"; + } + mutable qs = []; + for _ in 0..size - 1 { + set qs += [__quantum__rt__memory_qubit_allocate()]; + } + qs + } + + operation ReleaseMemoryQubitArray(qs : MemoryQubit[]) : Unit { + for q in qs { + __quantum__rt__memory_qubit_release(q); + } + } + + export __quantum__rt__qubit_allocate, __quantum__rt__qubit_borrow, __quantum__rt__qubit_release, AllocateQubitArray, BorrowQubitArray, ReleaseQubitArray, __quantum__rt__read_loss, __quantum__rt__memory_qubit_allocate, __quantum__rt__memory_qubit_release, AllocateMemoryQubitArray, ReleaseMemoryQubitArray; } diff --git a/library/src/lib.rs b/library/src/lib.rs index 809a4ebe8e..5cdbcf9326 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -61,6 +61,10 @@ pub const STD_LIB: &[(&str, &str)] = &[ "qsharp-library-source:Std/Measurement.qs", include_str!("../std/src/Std/Measurement.qs"), ), + ( + "qsharp-library-source:Std/MemoryQubits.qs", + include_str!("../std/src/Std/MemoryQubits.qs"), + ), ( "qsharp-library-source:QIR/Intrinsic.qs", include_str!("../std/src/QIR/Intrinsic.qs"), diff --git a/library/src/tests.rs b/library/src/tests.rs index 3b2fd5b72e..7b16d82d48 100644 --- a/library/src/tests.rs +++ b/library/src/tests.rs @@ -11,6 +11,7 @@ mod intrinsic; mod logical; mod math; mod measurement; +mod memory_qubits; mod openqasm; mod state_preparation; mod table_lookup; @@ -21,6 +22,9 @@ use qsc::{ interpret::{self, GenericReceiver, Interpreter, Value}, target::Profile, }; +use resource_estimator::{ + Error as ResourceEstimatorError, logical_counts_expr, system::LogicalResourceCounts, +}; /// # Panics /// @@ -40,6 +44,35 @@ pub fn test_expression_fails(expr: &str) -> String { ) } +/// Asserts that given Q# expression fails to compile with given error message. +pub fn test_compile_fails(expr: &str, lib: &str, expected_error_substring: &str) { + let profile = Profile::Unrestricted; + let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into())); + + let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); + + let Err(compile_errors) = Interpreter::new( + sources, + PackageType::Exe, + profile.into(), + LanguageFeatures::default(), + store, + &[(std_id, None)], + ) else { + panic!("test should fail to compile") + }; + + let error = compile_errors + .iter() + .map(|e| format!("{e}:{e:?}")) + .collect::>() + .join("\n"); + assert!( + error.contains(expected_error_substring), + "Expected to contain: {expected_error_substring}\nGot: {error}" + ); +} + pub fn test_expression_with_lib(expr: &str, lib: &str, expected: &Value) -> String { test_expression_with_lib_and_profile(expr, lib, Profile::Unrestricted, expected) } @@ -264,3 +297,33 @@ fn stdlib_reexport_single_case() { &Value::Tuple(vec![].into(), None), ); } + +// Runs resource estimator for given expression, returns logical counts. +pub fn logical_counts_with_lib(expr: &str, lib: &str) -> LogicalResourceCounts { + let profile = Profile::Unrestricted; + let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into())); + + let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); + + let mut interpreter = Interpreter::new( + sources, + PackageType::Exe, + profile.into(), + LanguageFeatures::default(), + store, + &[(std_id, None)], + ) + .expect("test should compile"); + + logical_counts_expr(&mut interpreter, expr) + .unwrap_or_else(|errs| panic_with_resource_estimation_errors(&errs)) +} + +fn panic_with_resource_estimation_errors(errs: &[ResourceEstimatorError]) -> ! { + let joined = errs + .iter() + .map(|e| format!("{e}:{e:?}")) + .collect::>() + .join("\n"); + panic!("resource estimation failed:\n{joined}"); +} diff --git a/library/src/tests/memory_qubits.rs b/library/src/tests/memory_qubits.rs new file mode 100644 index 0000000000..a6471b49f4 --- /dev/null +++ b/library/src/tests/memory_qubits.rs @@ -0,0 +1,235 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::{ + logical_counts_with_lib, test_compile_fails, test_expression_fails, test_expression_with_lib, +}; +use indoc::indoc; +use qsc::interpret::Value; + +// Tests for memory qubits and Std.MemoryQubits namespace. + +#[test] +fn check_store_load() { + test_expression_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Result { + use (q, mem) = (Qubit(), MemoryQubit()); + X(q); + Std.MemoryQubits.Store(q, mem); + Std.MemoryQubits.Load(mem, q); + return MResetZ(q); + } + } + "#}, + &Value::RESULT_ONE, + ); +} + +#[test] +fn check_array_store_load() { + test_expression_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Result[] { + use qs = Qubit[2]; + use mems = MemoryQubit[2]; + X(qs[0]); + Std.MemoryQubits.StoreArray(qs, mems); + Std.MemoryQubits.LoadArray(mems, qs); + return [MResetZ(qs[0]), MResetZ(qs[1])]; + } + } + "#}, + &Value::Array(vec![Value::RESULT_ONE, Value::RESULT_ZERO].into()), + ); +} + +#[test] +fn check_cannot_apply_gate_to_memory_qubit() { + test_compile_fails("{ use q = MemoryQubit(); X(q); }", "", "type error"); +} + +#[test] +fn check_cannot_measure_memory_qubit() { + test_compile_fails("{ use q = MemoryQubit(); M(q); }", "", "type error"); +} + +// MemoryQubit cannot be released in non-zero state (same as Qubit). +#[test] +fn check_release_non_zero_fails() { + let err = test_expression_fails(indoc! {r#" + { + use (q, m) = (Qubit(), MemoryQubit()); + X(q); + Std.MemoryQubits.Store(q, m); + Reset(q); + } + "#}); + + assert!(err.contains("released while not in |0")); +} + +// Check that after the computation, all qubits are released in 0 state. +#[test] +fn check_do_computation_with_qft() { + test_expression_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Bool { + use (qs, mems) = (Qubit[4], MemoryQubit[4]); + Adjoint ApplyQFT(qs); + Std.MemoryQubits.StoreArray(qs, mems); + Std.MemoryQubits.DoComputation(mems, ApplyQFT); + return true; + } + } + "#}, + &Value::Bool(true), + ); +} + +#[test] +fn check_borrow_memory_qubit_not_supported() { + test_compile_fails( + "{ borrow q = MemoryQubit(); }", + "", + "MemoryQubit cannot be borrowed.", + ); +} + +#[test] +fn check_borrow_memory_qubit_array_not_supported() { + test_compile_fails( + "{ borrow q = MemoryQubit[5]; }", + "", + "MemoryQubit cannot be borrowed.", + ); +} + +// Resource estimation tests. + +#[test] +fn check_resource_estimation_single_qubit() { + let counts = logical_counts_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Unit { + use q = Qubit(); + use mem = MemoryQubit(); + X(q); + Std.MemoryQubits.Store(q, mem); + Std.MemoryQubits.Load(mem, q); + } + } + "#}, + ); + + assert_eq!(counts.num_qubits, 2); + assert_eq!(counts.num_compute_qubits, Some(1)); + assert_eq!(counts.read_from_memory_count, Some(1)); + assert_eq!(counts.write_to_memory_count, Some(1)); +} + +// Resource estimation of computation with memory qubits. +#[test] +fn check_resource_estimation_do_computation() { + let counts = logical_counts_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Unit { + use qs = MemoryQubit[10]; + Std.MemoryQubits.DoComputation(qs[0..4], ApplyQFT); + Std.MemoryQubits.DoComputation(qs[5..9], ApplyQFT); + } + } + "#}, + ); + + // 10 memory qubits, 5 compute qubits. + assert_eq!(counts.num_qubits, 15); + assert_eq!(counts.num_compute_qubits, Some(5)); + assert_eq!(counts.read_from_memory_count, Some(10)); + assert_eq!(counts.write_to_memory_count, Some(10)); +} + +#[test] +fn check_resource_estimation_store_only() { + let counts = logical_counts_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Unit { + use (qs, mem) = (Qubit[10], MemoryQubit[10]); + H(qs[5]); + Std.MemoryQubits.StoreArray(qs, mem); + } + } + "#}, + ); + + assert_eq!(counts.num_qubits, 20); + assert_eq!(counts.num_compute_qubits, Some(10)); + assert_eq!(counts.read_from_memory_count, Some(0)); + assert_eq!(counts.write_to_memory_count, Some(10)); +} + +#[test] +fn check_resource_estimation_load_only() { + let counts = logical_counts_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Unit { + use (qs, mem) = (Qubit[10], MemoryQubit[10]); + Std.MemoryQubits.LoadArray(mem, qs); + } + } + "#}, + ); + + assert_eq!(counts.num_qubits, 20); + assert_eq!(counts.num_compute_qubits, Some(10)); + assert_eq!(counts.read_from_memory_count, Some(10)); + assert_eq!(counts.write_to_memory_count, Some(0)); +} + +// This test checks that MemoryQubits cannot be reused as Qubits. +// Resource estimator must draw them from separate pools. +#[test] +fn check_resource_estimation_separate_qubit_pools() { + let counts = logical_counts_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Op1() : Unit { + use q = Qubit(); + use mem = MemoryQubit(); + H(q); + Std.MemoryQubits.Store(q, mem); + Std.MemoryQubits.Load(mem, q); + } + operation Op2() : Unit { + use qs = Qubit[2]; + H(qs[0]); H(qs[1]); + M(qs[0]); M(qs[1]); + } + operation Main() : Unit { + Op1(); + Op2(); + } + } + "#}, + ); + + // Maximum allocated qubits at any point is 2, but we need 1 memory qubit and + // 2 compute qubits, so total number of qubits needed is 3. + assert_eq!(counts.num_qubits, 3); + assert_eq!(counts.num_compute_qubits, Some(2)); +} diff --git a/library/std/qsharp.json b/library/std/qsharp.json index 9afd68a957..8d427fab22 100644 --- a/library/std/qsharp.json +++ b/library/std/qsharp.json @@ -13,6 +13,7 @@ "src/Std/Logical.qs", "src/Std/Math.qs", "src/Std/Measurement.qs", + "src/Std/MemoryQubits.qs", "src/Std/Random.qs", "src/Std/Range.qs", "src/Std/ResourceEstimation.qs", diff --git a/library/std/src/QIR/Intrinsic.qs b/library/std/src/QIR/Intrinsic.qs index 6c703e853f..57cb351933 100644 --- a/library/std/src/QIR/Intrinsic.qs +++ b/library/std/src/QIR/Intrinsic.qs @@ -105,6 +105,15 @@ operation __quantum__qis__mresetz__body(target : Qubit) : Result { body intrinsic; } +// MemoryQubit operations. +operation __quantum__qis__memory_qubit_load(memory_qubit : MemoryQubit, qubit : Qubit) : Unit { + body intrinsic; +} + +operation __quantum__qis__memory_qubit_store(qubit : Qubit, memory_qubit : MemoryQubit) : Unit { + body intrinsic; +} + export __quantum__qis__ccx__body, __quantum__qis__cx__body, @@ -128,4 +137,6 @@ export __quantum__qis__swap__body, __quantum__qis__m__body, __quantum__qis__reset__body, - __quantum__qis__mresetz__body; + __quantum__qis__mresetz__body, + __quantum__qis__memory_qubit_load, + __quantum__qis__memory_qubit_store; diff --git a/library/std/src/Std/MemoryQubits.qs b/library/std/src/Std/MemoryQubits.qs new file mode 100644 index 0000000000..b95075f53f --- /dev/null +++ b/library/std/src/Std/MemoryQubits.qs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import QIR.Intrinsic.*; +import QIR.Runtime.*; + +// General notes on `MemoryQubit` usage: +// - `MemoryQubit` is a primitive type representing a logical qubit in memory. +// - Quantum gates and measurements cannot be applied directly to `MemoryQubit`. +// - Move state from memory to compute with `Load`, and from compute back to +// memory with `Store`. +// - Allocate memory qubits with `use`, just like regular qubits: +// use m = MemoryQubit(); +// use ms = MemoryQubit[4]; +// - Memory qubits are automatically released at the end of scope (similarly to +// qubits). They must be in the zero state before release. +// - Memory and compute qubits are distinct resources. A memory qubit does not +// change type to a compute qubit, and vice versa. + +/// # Summary +/// Loads the state of a memory qubit into a compute qubit. +/// +/// # Description +/// This operation transfers the state from `memory_qubit` to `qubit`. +/// After `Load(memory_qubit, qubit)`, `qubit` holds the previous state of +/// `memory_qubit`, and `memory_qubit` is returned to $\ket{0}$. +/// +/// # Input +/// ## memory_qubit +/// The memory qubit to read from. +/// ## qubit +/// The compute qubit that receives the loaded state. +operation Load(memory_qubit : MemoryQubit, qubit : Qubit) : Unit { + __quantum__qis__memory_qubit_load(memory_qubit, qubit); +} + +/// # Summary +/// Stores the state of a compute qubit into a memory qubit. +/// +/// # Description +/// This operation transfers the state from `qubit` to `memory_qubit`. +/// After `Store(qubit, memory_qubit)`, `memory_qubit` holds the previous state of +/// `qubit`, and `qubit` is returned to $\ket{0}$. +/// +/// # Input +/// ## qubit +/// The compute qubit to write from. +/// ## memory_qubit +/// The memory qubit that receives the stored state. +operation Store(qubit : Qubit, memory_qubit : MemoryQubit) : Unit { + __quantum__qis__memory_qubit_store(qubit, memory_qubit); +} + +/// # Summary +/// Loads a memory register into a compute register. +/// +/// # Input +/// ## source +/// Source memory register. +/// ## target +/// Target compute register. +/// +/// # Remarks +/// Fails if the source and target registers have different lengths. +operation LoadArray(source : MemoryQubit[], target : Qubit[]) : Unit { + let n = Length(source); + if (n != Length(target)) { fail ("Registers have different sizes."); } + for i in 0..n-1 { + Load(source[i], target[i]); + } +} + +/// # Summary +/// Stores a compute register into a memory register. +/// +/// # Input +/// ## source +/// Source compute register. +/// ## target +/// Target memory register. +/// +/// # Remarks +/// Fails if the source and target registers have different lengths. +operation StoreArray(source : Qubit[], target : MemoryQubit[]) : Unit { + let n = Length(source); + if (n != Length(target)) { fail ("Registers have different sizes."); } + for i in 0..n-1 { + Store(source[i], target[i]); + } +} + +/// # Summary +/// Runs a computation on memory qubits by using a temporary compute buffer. +/// +/// # Description +/// This operation allocates a temporary `Qubit[]` buffer, loads `mem_qs` into that +/// buffer, applies `op`, and stores the resulting state back into `mem_qs`. +/// +/// # Input +/// ## mem_qs +/// Memory register to transform. +/// ## op +/// Operation to apply to the temporary compute buffer. +operation DoComputation(mem_qs : MemoryQubit[], op : Qubit[] => Unit) : Unit { + use buffer = Qubit[Length(mem_qs)]; + LoadArray(mem_qs, buffer); + op(buffer); + StoreArray(buffer, mem_qs); +} + +export Load, Store, LoadArray, StoreArray, DoComputation; diff --git a/samples/language/MemoryQubit.qs b/samples/language/MemoryQubit.qs new file mode 100644 index 0000000000..4fbf689020 --- /dev/null +++ b/samples/language/MemoryQubit.qs @@ -0,0 +1,18 @@ +// # Sample +// MemoryQubit +// +// # Description +// Prepares a compute qubit in |1>, stores it in a memory qubit, then loads +// it back and measures. The result should be `One`. + +import Std.MemoryQubits.*; + +operation Main() : Result { + use (q, mem) = (Qubit(), MemoryQubit()); + + X(q); + Store(q, mem); + Load(mem, q); + + return MResetZ(q); +} diff --git a/source/compiler/qsc_ast/src/ast.rs b/source/compiler/qsc_ast/src/ast.rs index 59b81dd3c2..4d3156c658 100644 --- a/source/compiler/qsc_ast/src/ast.rs +++ b/source/compiler/qsc_ast/src/ast.rs @@ -1404,10 +1404,14 @@ impl WithSpan for QubitInit { pub enum QubitInitKind { /// An array of qubits: `Qubit[a]`. Array(Box), + /// An array of memory qubits: `MemoryQubit[a]`. + MemoryArray(Box), /// A parenthesized initializer: `(a)`. Paren(Box), /// A single qubit: `Qubit()`. Single, + /// A single memory qubit: `MemoryQubit()`. + MemorySingle, /// A tuple: `(a, b, c)`. Tuple(Box<[Box]>), /// An invalid initializer. @@ -1424,12 +1428,18 @@ impl Display for QubitInitKind { indent = set_indentation(indent, 1); write!(indent, "\n{e}")?; } + QubitInitKind::MemoryArray(e) => { + write!(indent, "MemoryArray:")?; + indent = set_indentation(indent, 1); + write!(indent, "\n{e}")?; + } QubitInitKind::Paren(qi) => { write!(indent, "Parens:")?; indent = set_indentation(indent, 1); write!(indent, "\n{qi}")?; } QubitInitKind::Single => write!(indent, "Single")?, + QubitInitKind::MemorySingle => write!(indent, "MemorySingle")?, QubitInitKind::Tuple(qis) => { if qis.is_empty() { write!(indent, "Unit")?; diff --git a/source/compiler/qsc_ast/src/mut_visit.rs b/source/compiler/qsc_ast/src/mut_visit.rs index ce80b9ca87..551e21cc45 100644 --- a/source/compiler/qsc_ast/src/mut_visit.rs +++ b/source/compiler/qsc_ast/src/mut_visit.rs @@ -432,9 +432,9 @@ pub fn walk_qubit_init(vis: &mut impl MutVisitor, init: &mut QubitInit) { vis.visit_span(&mut init.span); match &mut *init.kind { - QubitInitKind::Array(len) => vis.visit_expr(len), + QubitInitKind::Array(len) | QubitInitKind::MemoryArray(len) => vis.visit_expr(len), QubitInitKind::Paren(init) => vis.visit_qubit_init(init), - QubitInitKind::Single | QubitInitKind::Err => {} + QubitInitKind::Single | QubitInitKind::MemorySingle | QubitInitKind::Err => {} QubitInitKind::Tuple(inits) => inits.iter_mut().for_each(|i| vis.visit_qubit_init(i)), } } diff --git a/source/compiler/qsc_ast/src/visit.rs b/source/compiler/qsc_ast/src/visit.rs index f123b320a0..48110d43b3 100644 --- a/source/compiler/qsc_ast/src/visit.rs +++ b/source/compiler/qsc_ast/src/visit.rs @@ -399,9 +399,9 @@ pub fn walk_pat<'a>(vis: &mut impl Visitor<'a>, pat: &'a Pat) { pub fn walk_qubit_init<'a>(vis: &mut impl Visitor<'a>, init: &'a QubitInit) { match &*init.kind { - QubitInitKind::Array(len) => vis.visit_expr(len), + QubitInitKind::Array(len) | QubitInitKind::MemoryArray(len) => vis.visit_expr(len), QubitInitKind::Paren(init) => vis.visit_qubit_init(init), - QubitInitKind::Single | QubitInitKind::Err => {} + QubitInitKind::Single | QubitInitKind::MemorySingle | QubitInitKind::Err => {} QubitInitKind::Tuple(inits) => inits.iter().for_each(|i| vis.visit_qubit_init(i)), } } diff --git a/source/compiler/qsc_codegen/src/qsharp.rs b/source/compiler/qsc_codegen/src/qsharp.rs index 71eee20519..9e0fed7a6c 100644 --- a/source/compiler/qsc_codegen/src/qsharp.rs +++ b/source/compiler/qsc_codegen/src/qsharp.rs @@ -786,10 +786,18 @@ impl Visitor<'_> for QSharpGen { self.visit_expr(len); self.write("]"); } + QubitInitKind::MemoryArray(len) => { + self.write("MemoryQubit["); + self.visit_expr(len); + self.write("]"); + } QubitInitKind::Paren(init) => self.visit_qubit_init(init), QubitInitKind::Single => { self.write("Qubit()"); } + QubitInitKind::MemorySingle => { + self.write("MemoryQubit()"); + } QubitInitKind::Tuple(inits) => { self.write("("); if let Some((last, most)) = inits.split_last() { diff --git a/source/compiler/qsc_eval/src/backend.rs b/source/compiler/qsc_eval/src/backend.rs index 3db62e0e0c..f149439bad 100644 --- a/source/compiler/qsc_eval/src/backend.rs +++ b/source/compiler/qsc_eval/src/backend.rs @@ -115,6 +115,22 @@ pub trait Backend { None } fn set_seed(&mut self, _seed: Option) {} + /// 3 methods below deal with memory qubits. + /// By default they are implemented by using compute qubits as memory qubits, which + /// is correct but inefficient. + /// Backends that distinguish between memory and compute qubits must implement these + /// methods. + fn memory_qubit_allocate(&mut self) -> usize { + self.qubit_allocate() + } + fn memory_qubit_load(&mut self, mem_qubit_id: usize, comp_qubit_id: usize) { + self.reset(comp_qubit_id); + self.swap(mem_qubit_id, comp_qubit_id); + } + fn memory_qubit_store(&mut self, comp_qubit_id: usize, mem_qubit_id: usize) { + self.reset(mem_qubit_id); + self.swap(mem_qubit_id, comp_qubit_id); + } } /// Trait receiving trace events for quantum execution. Each method records @@ -438,6 +454,59 @@ impl<'a, B: Backend> TracingBackend<'a, B> { } } + pub fn memory_qubit_allocate(&mut self, stack: &[Frame]) -> usize { + let q = match &mut self.backend { + OptionalBackend::Some(backend) => backend.memory_qubit_allocate(), + OptionalBackend::None(fallback) => fallback.qubit_allocate(), + }; + if let Some(tracer) = &mut self.tracer { + tracer.qubit_allocate(stack, q); + } + q + } + + pub fn memory_qubit_load( + &mut self, + mem_qubit_id: usize, + comp_qubit_id: usize, + stack: &[Frame], + ) { + if let OptionalBackend::Some(backend) = &mut self.backend { + backend.memory_qubit_load(mem_qubit_id, comp_qubit_id); + } + if let Some(tracer) = &mut self.tracer { + tracer.gate( + stack, + "Load", + false, + &[mem_qubit_id, comp_qubit_id], + &[], + None, + ); + } + } + + pub fn memory_qubit_store( + &mut self, + comp_qubit_id: usize, + mem_qubit_id: usize, + stack: &[Frame], + ) { + if let OptionalBackend::Some(backend) = &mut self.backend { + backend.memory_qubit_store(comp_qubit_id, mem_qubit_id); + } + if let Some(tracer) = &mut self.tracer { + tracer.gate( + stack, + "Store", + false, + &[comp_qubit_id, mem_qubit_id], + &[], + None, + ); + } + } + pub fn custom_intrinsic( &mut self, name: &str, diff --git a/source/compiler/qsc_eval/src/intrinsic.rs b/source/compiler/qsc_eval/src/intrinsic.rs index 5047bae140..a6d506e965 100644 --- a/source/compiler/qsc_eval/src/intrinsic.rs +++ b/source/compiler/qsc_eval/src/intrinsic.rs @@ -233,6 +233,16 @@ pub(crate) fn call( ), )), "__quantum__rt__read_loss" => Ok(Value::Bool(arg == Value::Result(val::Result::Loss))), + "__quantum__qis__memory_qubit_store" => two_qubit_gate( + |q0, q1| sim.memory_qubit_store(q0, q1, call_stack), + arg, + arg_span, + ), + "__quantum__qis__memory_qubit_load" => two_qubit_gate( + |q0, q1| sim.memory_qubit_load(q0, q1, call_stack), + arg, + arg_span, + ), _ => { let qubits = arg.qubits(); let qubits_len = qubits.len(); diff --git a/source/compiler/qsc_eval/src/lib.rs b/source/compiler/qsc_eval/src/lib.rs index 093bf625b4..c6dcdb17d2 100644 --- a/source/compiler/qsc_eval/src/lib.rs +++ b/source/compiler/qsc_eval/src/lib.rs @@ -1325,19 +1325,12 @@ impl State { self.increment_call_count(callee_id, functor); let name = &callee.name.name; let val = match name.as_ref() { - "__quantum__rt__qubit_allocate" | "__quantum__rt__qubit_borrow" => { - let q = sim.qubit_allocate(&call_stack); - let q = Rc::new(Qubit(q)); - env.track_qubit(Rc::clone(&q)); - if let Some(counter) = &mut self.qubit_counter { - counter.allocated(q.0); - } - if name.as_ref() == "__quantum__rt__qubit_borrow" { - self.dirty_qubits.insert(q.0); - } - Value::Qubit(q.into()) + "__quantum__rt__qubit_allocate" + | "__quantum__rt__qubit_borrow" + | "__quantum__rt__memory_qubit_allocate" => { + self.allocate_qubit(env, sim, &call_stack, name) } - "__quantum__rt__qubit_release" => { + "__quantum__rt__qubit_release" | "__quantum__rt__memory_qubit_release" => { let qubit = arg .unwrap_qubit() .try_deref() @@ -1376,6 +1369,37 @@ impl State { Ok(()) } + fn allocate_qubit( + &mut self, + env: &mut Env, + sim: &mut TracingBackend<'_, B>, + call_stack: &[Frame], + name: &Rc, + ) -> Value { + let is_memory_qubit = name.as_ref() == "__quantum__rt__memory_qubit_allocate"; + + let q = if is_memory_qubit { + sim.memory_qubit_allocate(call_stack) + } else { + sim.qubit_allocate(call_stack) + }; + + let q = Rc::new(Qubit(q)); + env.track_qubit(Rc::clone(&q)); + if let Some(counter) = &mut self.qubit_counter { + counter.allocated(q.0); + } + if name.as_ref() == "__quantum__rt__qubit_borrow" { + self.dirty_qubits.insert(q.0); + } + + if is_memory_qubit { + Value::MemoryQubit(q.into()) + } else { + Value::Qubit(q.into()) + } + } + fn eval_field(&mut self, field: Field) { let record = self.take_val_register(); let val = match (record, field) { diff --git a/source/compiler/qsc_eval/src/val.rs b/source/compiler/qsc_eval/src/val.rs index 0597719cca..50ac48e3f8 100644 --- a/source/compiler/qsc_eval/src/val.rs +++ b/source/compiler/qsc_eval/src/val.rs @@ -25,6 +25,7 @@ pub enum Value { Int(i64), Pauli(Pauli), Qubit(QubitRef), + MemoryQubit(QubitRef), Range(Box), Result(Result), String(Rc), @@ -196,6 +197,12 @@ impl Display for Value { (v.try_deref() .map_or_else(|| "".to_string(), |v| v.0.to_string())) ), + Value::MemoryQubit(v) => write!( + f, + "MemoryQubit{}", + (v.try_deref() + .map_or_else(|| "".to_string(), |v| v.0.to_string())) + ), Value::Range(inner) => match (inner.start, inner.step, inner.end) { (Some(start), DEFAULT_RANGE_STEP, Some(end)) => write!(f, "{start}..{end}"), (Some(start), DEFAULT_RANGE_STEP, None) => write!(f, "{start}..."), @@ -383,13 +390,16 @@ impl Value { /// Convert the [Value] into a qubit /// # Panics - /// This will panic if the [Value] is not a [`Value::Qubit`]. + /// This will panic if the [Value] is not a [`Value::Qubit`] or [`Value::MemoryQubit`]. #[must_use] pub fn unwrap_qubit(self) -> QubitRef { - let Value::Qubit(v) = self else { + if let Value::Qubit(v) = self { + v + } else if let Value::MemoryQubit(v) = self { + v + } else { panic!("value should be Qubit, got {}", self.type_name()); - }; - v + } } /// Convert the [Value] into a range tuple @@ -448,6 +458,7 @@ impl Value { Value::Int(_) => "Int", Value::Pauli(_) => "Pauli", Value::Qubit(_) => "Qubit", + Value::MemoryQubit(_) => "MemoryQubit", Value::Range(..) => "Range", Value::Result(_) => "Result", Value::String(_) => "String", @@ -464,7 +475,7 @@ impl Value { match self { Value::Array(arr) => arr.iter().flat_map(Value::qubits).collect(), Value::Closure(closure) => closure.fixed_args.iter().flat_map(Value::qubits).collect(), - Value::Qubit(q) => vec![q.clone()], + Value::Qubit(q) | Value::MemoryQubit(q) => vec![q.clone()], Value::Tuple(tup, _) => tup.iter().flat_map(Value::qubits).collect(), Value::BigInt(_) diff --git a/source/compiler/qsc_fir/src/ty.rs b/source/compiler/qsc_fir/src/ty.rs index d88ee98bd5..58c104c5be 100644 --- a/source/compiler/qsc_fir/src/ty.rs +++ b/source/compiler/qsc_fir/src/ty.rs @@ -360,6 +360,8 @@ pub enum Prim { Pauli, /// The qubit type. Qubit, + /// The memory qubit type. + MemoryQubit, /// The range type. Range, /// The range type without a lower bound. diff --git a/source/compiler/qsc_frontend/src/lower.rs b/source/compiler/qsc_frontend/src/lower.rs index aaccb920d8..c909f9020e 100644 --- a/source/compiler/qsc_frontend/src/lower.rs +++ b/source/compiler/qsc_frontend/src/lower.rs @@ -1013,8 +1013,12 @@ impl With<'_> { ast::QubitInitKind::Array(length) => { hir::QubitInitKind::Array(Box::new(self.lower_expr(length))) } + ast::QubitInitKind::MemoryArray(length) => { + hir::QubitInitKind::MemoryArray(Box::new(self.lower_expr(length))) + } ast::QubitInitKind::Paren(_) => unreachable!("parentheses should be removed earlier"), ast::QubitInitKind::Single => hir::QubitInitKind::Single, + ast::QubitInitKind::MemorySingle => hir::QubitInitKind::MemorySingle, ast::QubitInitKind::Tuple(items) => { hir::QubitInitKind::Tuple(items.iter().map(|i| self.lower_qubit_init(i)).collect()) } diff --git a/source/compiler/qsc_frontend/src/resolve.rs b/source/compiler/qsc_frontend/src/resolve.rs index a4d3be96ce..34b72afbbf 100644 --- a/source/compiler/qsc_frontend/src/resolve.rs +++ b/source/compiler/qsc_frontend/src/resolve.rs @@ -1197,13 +1197,14 @@ pub(super) struct GlobalTable { impl GlobalTable { pub(super) fn new() -> Self { - let builtins: [(Rc, Res); 10] = [ + let builtins: [(Rc, Res); 11] = [ ("BigInt".into(), Res::PrimTy(Prim::BigInt)), ("Bool".into(), Res::PrimTy(Prim::Bool)), ("Double".into(), Res::PrimTy(Prim::Double)), ("Int".into(), Res::PrimTy(Prim::Int)), ("Pauli".into(), Res::PrimTy(Prim::Pauli)), ("Qubit".into(), Res::PrimTy(Prim::Qubit)), + ("MemoryQubit".into(), Res::PrimTy(Prim::MemoryQubit)), ("Range".into(), Res::PrimTy(Prim::Range)), ("Result".into(), Res::PrimTy(Prim::Result)), ("String".into(), Res::PrimTy(Prim::String)), diff --git a/source/compiler/qsc_frontend/src/typeck/rules.rs b/source/compiler/qsc_frontend/src/typeck/rules.rs index 677d0cf0f2..1bba4c0914 100644 --- a/source/compiler/qsc_frontend/src/typeck/rules.rs +++ b/source/compiler/qsc_frontend/src/typeck/rules.rs @@ -977,8 +977,17 @@ impl<'a> Context<'a> { .eq(length_span, Ty::Prim(Prim::Int), length.ty); converge(Ty::Array(Box::new(Ty::Prim(Prim::Qubit)))).diverge_if(length.diverges) } + QubitInitKind::MemoryArray(length) => { + let length_span = length.span; + let length = self.infer_expr(length); + self.inferrer + .eq(length_span, Ty::Prim(Prim::Int), length.ty); + converge(Ty::Array(Box::new(Ty::Prim(Prim::MemoryQubit)))) + .diverge_if(length.diverges) + } QubitInitKind::Paren(inner) => self.infer_qubit_init(inner), QubitInitKind::Single => converge(Ty::Prim(Prim::Qubit)), + QubitInitKind::MemorySingle => converge(Ty::Prim(Prim::MemoryQubit)), QubitInitKind::Tuple(items) => { let mut diverges = false; let mut tys = Vec::new(); diff --git a/source/compiler/qsc_hir/src/hir.rs b/source/compiler/qsc_hir/src/hir.rs index 81b2a371a8..efbdc3f3dc 100644 --- a/source/compiler/qsc_hir/src/hir.rs +++ b/source/compiler/qsc_hir/src/hir.rs @@ -1216,8 +1216,12 @@ impl Display for QubitInit { pub enum QubitInitKind { /// An array of qubits: `Qubit[a]`. Array(Box), + /// An array of memory qubits: `MemoryQubit[a]`. + MemoryArray(Box), /// A single qubit: `Qubit()`. Single, + /// A single memory qubit: `MemoryQubit()`. + MemorySingle, /// A tuple: `(a, b, c)`. Tuple(Vec), /// An invalid qubit initializer. @@ -1233,7 +1237,13 @@ impl Display for QubitInitKind { indent = set_indentation(indent, 1); write!(indent, "\n{e}")?; } + QubitInitKind::MemoryArray(e) => { + write!(indent, "MemoryArray:")?; + indent = set_indentation(indent, 1); + write!(indent, "\n{e}")?; + } QubitInitKind::Single => write!(indent, "Single")?, + QubitInitKind::MemorySingle => write!(indent, "MemorySingle")?, QubitInitKind::Tuple(qis) => { if qis.is_empty() { write!(indent, "Unit")?; diff --git a/source/compiler/qsc_hir/src/mut_visit.rs b/source/compiler/qsc_hir/src/mut_visit.rs index 50a8fc4b56..ce72675e54 100644 --- a/source/compiler/qsc_hir/src/mut_visit.rs +++ b/source/compiler/qsc_hir/src/mut_visit.rs @@ -253,8 +253,8 @@ pub fn walk_qubit_init(vis: &mut impl MutVisitor, init: &mut QubitInit) { vis.visit_span(&mut init.span); match &mut init.kind { - QubitInitKind::Array(len) => vis.visit_expr(len), - QubitInitKind::Single | QubitInitKind::Err => {} + QubitInitKind::Array(len) | QubitInitKind::MemoryArray(len) => vis.visit_expr(len), + QubitInitKind::Single | QubitInitKind::MemorySingle | QubitInitKind::Err => {} QubitInitKind::Tuple(inits) => inits.iter_mut().for_each(|i| vis.visit_qubit_init(i)), } } diff --git a/source/compiler/qsc_hir/src/ty.rs b/source/compiler/qsc_hir/src/ty.rs index c673d6a8ec..896cd83dfe 100644 --- a/source/compiler/qsc_hir/src/ty.rs +++ b/source/compiler/qsc_hir/src/ty.rs @@ -518,6 +518,8 @@ pub enum Prim { Pauli, /// The qubit type. Qubit, + /// The memory qubit type. + MemoryQubit, /// The range type. Range, /// The range type without a lower bound. diff --git a/source/compiler/qsc_hir/src/visit.rs b/source/compiler/qsc_hir/src/visit.rs index 510c79dece..a4cf9b207e 100644 --- a/source/compiler/qsc_hir/src/visit.rs +++ b/source/compiler/qsc_hir/src/visit.rs @@ -229,8 +229,8 @@ pub fn walk_pat<'a>(vis: &mut impl Visitor<'a>, pat: &'a Pat) { pub fn walk_qubit_init<'a>(vis: &mut impl Visitor<'a>, init: &'a QubitInit) { match &init.kind { - QubitInitKind::Array(len) => vis.visit_expr(len), - QubitInitKind::Single | QubitInitKind::Err => {} + QubitInitKind::Array(len) | QubitInitKind::MemoryArray(len) => vis.visit_expr(len), + QubitInitKind::Single | QubitInitKind::MemorySingle | QubitInitKind::Err => {} QubitInitKind::Tuple(inits) => inits.iter().for_each(|i| vis.visit_qubit_init(i)), } } diff --git a/source/compiler/qsc_lowerer/src/lib.rs b/source/compiler/qsc_lowerer/src/lib.rs index 10b035d4d1..87fde11ee6 100644 --- a/source/compiler/qsc_lowerer/src/lib.rs +++ b/source/compiler/qsc_lowerer/src/lib.rs @@ -1095,6 +1095,7 @@ fn lower_ty_prim(prim: qsc_hir::ty::Prim) -> qsc_fir::ty::Prim { qsc_hir::ty::Prim::Double => qsc_fir::ty::Prim::Double, qsc_hir::ty::Prim::Int => qsc_fir::ty::Prim::Int, qsc_hir::ty::Prim::Qubit => qsc_fir::ty::Prim::Qubit, + qsc_hir::ty::Prim::MemoryQubit => qsc_fir::ty::Prim::MemoryQubit, qsc_hir::ty::Prim::Result => qsc_fir::ty::Prim::Result, qsc_hir::ty::Prim::String => qsc_fir::ty::Prim::String, qsc_hir::ty::Prim::BigInt => qsc_fir::ty::Prim::BigInt, diff --git a/source/compiler/qsc_parse/src/stmt.rs b/source/compiler/qsc_parse/src/stmt.rs index 974eb7d5fb..e11f16f1e6 100644 --- a/source/compiler/qsc_parse/src/stmt.rs +++ b/source/compiler/qsc_parse/src/stmt.rs @@ -140,6 +140,12 @@ fn parse_qubit(s: &mut ParserContext) -> Result> { let lhs = pat(s)?; token(s, TokenKind::Eq)?; let rhs = parse_qubit_init(s)?; + + if matches!(source, QubitSource::Dirty) && contains_memory_qubit_init(&rhs) { + return Err(Error::new(ErrorKind::Convert("use", "borrow", rhs.span)) + .with_help("MemoryQubit cannot be borrowed.")); + } + let block = if s.contains_language_feature(LanguageFeatures::V2PreviewSyntax) { None } else { @@ -158,20 +164,34 @@ fn parse_qubit_init(s: &mut ParserContext) -> Result> { let lo = s.peek().span.lo; s.expect(WordKinds::Qubit); let kind = if let Ok(name) = ident(s) { - if name.name.as_ref() != "Qubit" { - return Err(Error::new(ErrorKind::Convert( - "qubit initializer", - "identifier", - name.span, - )) - .with_help(help_text)); - } else if token(s, TokenKind::Open(Delim::Paren)).is_ok() { + let is_memory = match name.name.as_ref() { + "Qubit" => false, + "MemoryQubit" => true, + _ => { + return Err(Error::new(ErrorKind::Convert( + "qubit initializer", + "identifier", + name.span, + )) + .with_help(help_text)); + } + }; + + if token(s, TokenKind::Open(Delim::Paren)).is_ok() { token(s, TokenKind::Close(Delim::Paren)).map_err(|e| e.with_help(help_text))?; - QubitInitKind::Single + if is_memory { + QubitInitKind::MemorySingle + } else { + QubitInitKind::Single + } } else if token(s, TokenKind::Open(Delim::Bracket)).is_ok() { let size = expr(s).map_err(|e| e.with_help(help_text))?; token(s, TokenKind::Close(Delim::Bracket)).map_err(|e| e.with_help(help_text))?; - QubitInitKind::Array(size) + if is_memory { + QubitInitKind::MemoryArray(size) + } else { + QubitInitKind::Array(size) + } } else { let token = s.peek(); return Err( @@ -198,6 +218,15 @@ fn parse_qubit_init(s: &mut ParserContext) -> Result> { })) } +fn contains_memory_qubit_init(init: &QubitInit) -> bool { + match &*init.kind { + QubitInitKind::MemoryArray(_) | QubitInitKind::MemorySingle => true, + QubitInitKind::Array(_) | QubitInitKind::Single | QubitInitKind::Err => false, + QubitInitKind::Paren(inner) => contains_memory_qubit_init(inner), + QubitInitKind::Tuple(inits) => inits.iter().any(|i| contains_memory_qubit_init(i)), + } +} + pub(super) fn check_semis(s: &mut ParserContext, stmts: &[Box]) { let leading_stmts = stmts.split_last().map_or([].as_slice(), |s| s.1); for stmt in leading_stmts { diff --git a/source/compiler/qsc_parse/src/stmt/tests.rs b/source/compiler/qsc_parse/src/stmt/tests.rs index 51d29981fc..25450b3567 100644 --- a/source/compiler/qsc_parse/src/stmt/tests.rs +++ b/source/compiler/qsc_parse/src/stmt/tests.rs @@ -82,6 +82,33 @@ fn use_qubit_array() { ); } +#[test] +fn use_memory_qubit_stmt() { + check( + parse, + "use m = MemoryQubit();", + &expect![[r#" + Stmt _id_ [0-22]: Qubit (Fresh) + Pat _id_ [4-5]: Bind: + Ident _id_ [4-5] "m" + QubitInit _id_ [8-21] MemorySingle"#]], + ); +} + +#[test] +fn use_memory_qubit_array() { + check( + parse, + "use ms = MemoryQubit[5];", + &expect![[r#" + Stmt _id_ [0-24]: Qubit (Fresh) + Pat _id_ [4-6]: Bind: + Ident _id_ [4-6] "ms" + QubitInit _id_ [9-23] MemoryArray: + Expr _id_ [21-22]: Lit: Int(5)"#]], + ); +} + #[test] fn use_pat_match() { check( diff --git a/source/compiler/qsc_partial_eval/src/evaluation_context.rs b/source/compiler/qsc_partial_eval/src/evaluation_context.rs index 02e8e5e06d..630a123432 100644 --- a/source/compiler/qsc_partial_eval/src/evaluation_context.rs +++ b/source/compiler/qsc_partial_eval/src/evaluation_context.rs @@ -326,6 +326,7 @@ fn map_eval_value_to_value_kind(value: &Value) -> ValueKind { | Value::Int(_) | Value::Pauli(_) | Value::Qubit(_) + | Value::MemoryQubit(_) | Value::Range(_) | Value::Result(Result::Val(_)) | Value::String(_) => ValueKind::Constant, diff --git a/source/compiler/qsc_partial_eval/src/lib.rs b/source/compiler/qsc_partial_eval/src/lib.rs index 6a13b0d987..d1cf49d342 100644 --- a/source/compiler/qsc_partial_eval/src/lib.rs +++ b/source/compiler/qsc_partial_eval/src/lib.rs @@ -1745,10 +1745,14 @@ impl<'a> PartialEvaluator<'a> { // There are a few special cases regarding intrinsic callables. Identify them and handle them properly. match callable_decl.name.name.as_ref() { // Qubit allocations and measurements have special handling. - "__quantum__rt__qubit_allocate" | "__quantum__rt__qubit_borrow" => { + "__quantum__rt__qubit_allocate" + | "__quantum__rt__qubit_borrow" + | "__quantum__rt__memory_qubit_allocate" => { Ok(self.allocate_qubit()) } - "__quantum__rt__qubit_release" => Ok(self.release_qubit(args_value)), + "__quantum__rt__qubit_release" | "__quantum__rt__memory_qubit_release" => { + Ok(self.release_qubit(args_value)) + } "PermuteLabels" => { if self.eval_context.is_currently_evaluating_any_branch() { // If we are in a dynamic branch anywhere up the call stack, we cannot support relabel, @@ -1766,6 +1770,56 @@ impl<'a> PartialEvaluator<'a> { "__quantum__qis__mresetz__body" => { Ok(self.measure_qubit(builder::mresetz_decl(), args_value)) } + "__quantum__qis__memory_qubit_load" | "__quantum__qis__memory_qubit_load__body" => { + let [memory_qubit, qubit] = val::unwrap_tuple::<2>(args_value); + let reset_callable_id = self.get_reset_callable(); + let swap_callable_id = self.get_swap_callable(); + + let reset_instruction = Instruction::Call( + reset_callable_id, + vec![self.map_eval_value_to_rir_operand(&qubit)], + None, + self.metadata_from_current_dbg_location(), + ); + let swap_instruction = Instruction::Call( + swap_callable_id, + vec![ + self.map_eval_value_to_rir_operand(&memory_qubit), + self.map_eval_value_to_rir_operand(&qubit), + ], + None, + self.metadata_from_current_dbg_location(), + ); + let current_block = self.get_current_rir_block_mut(); + current_block.0.push(reset_instruction); + current_block.0.push(swap_instruction); + Ok(Value::unit()) + } + "__quantum__qis__memory_qubit_store" | "__quantum__qis__memory_qubit_store__body" => { + let [qubit, memory_qubit] = val::unwrap_tuple::<2>(args_value); + let reset_callable_id = self.get_reset_callable(); + let swap_callable_id = self.get_swap_callable(); + + let reset_instruction = Instruction::Call( + reset_callable_id, + vec![self.map_eval_value_to_rir_operand(&memory_qubit)], + None, + self.metadata_from_current_dbg_location(), + ); + let swap_instruction = Instruction::Call( + swap_callable_id, + vec![ + self.map_eval_value_to_rir_operand(&qubit), + self.map_eval_value_to_rir_operand(&memory_qubit), + ], + None, + self.metadata_from_current_dbg_location(), + ); + let current_block = self.get_current_rir_block_mut(); + current_block.0.push(reset_instruction); + current_block.0.push(swap_instruction); + Ok(Value::unit()) + } // The following intrinsic operations and functions are no-ops. "BeginEstimateCaching" => Ok(Value::Bool(true)), "DumpRegister" @@ -3440,6 +3494,7 @@ impl<'a> PartialEvaluator<'a> { | Value::Global(_, _) | Value::Pauli(_) | Value::Qubit(_) + | Value::MemoryQubit(_) | Value::Range(_) | Value::String(_) => panic!("unsupported value type in output recording"), } @@ -3713,6 +3768,38 @@ impl<'a> PartialEvaluator<'a> { callable_id } + fn get_reset_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__qis__reset__body") { + return *id; + } + + let callable = builder::reset_decl(); + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__qis__reset__body".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + + fn get_swap_callable(&mut self) -> CallableId { + if let Some(id) = self.callables_map.get("__quantum__qis__swap__body") { + return *id; + } + + let callable = Callable { + name: "__quantum__qis__swap__body".to_string(), + input_type: vec![rir::Ty::Prim(rir::Prim::Qubit), rir::Ty::Prim(rir::Prim::Qubit)], + output_type: None, + body: None, + call_type: CallableType::Regular, + }; + let callable_id = self.resource_manager.next_callable(); + self.callables_map + .insert("__quantum__qis__swap__body".into(), callable_id); + self.program.callables.insert(callable_id, callable); + callable_id + } + fn map_eval_value_to_rir_operand(&self, value: &Value) -> Operand { match value { Value::Bool(b) => Operand::Literal(Literal::Bool(*b)), @@ -3724,6 +3811,12 @@ impl<'a> PartialEvaluator<'a> { .try_into() .expect("could not convert qubit ID to u32"), )), + Value::MemoryQubit(q) => Operand::Literal(Literal::Qubit( + self.resource_manager + .map_qubit(q) + .try_into() + .expect("could not convert qubit ID to u32"), + )), Value::Result(r) => match r { val::Result::Id(id) => Operand::Literal(Literal::Result( (*id) @@ -4224,7 +4317,9 @@ fn map_fir_type_to_rir_type(ty: &Ty) -> Result { Ty::Prim(Prim::Bool) => Ok(rir::Ty::Prim(rir::Prim::Boolean)), Ty::Prim(Prim::Double) => Ok(rir::Ty::Prim(rir::Prim::Double)), Ty::Prim(Prim::Int) => Ok(rir::Ty::Prim(rir::Prim::Integer)), - Ty::Prim(Prim::Qubit) => Ok(rir::Ty::Prim(rir::Prim::Qubit)), + Ty::Prim(Prim::Qubit) | Ty::Prim(Prim::MemoryQubit) => { + Ok(rir::Ty::Prim(rir::Prim::Qubit)) + } Ty::Prim(Prim::Result) => Ok(rir::Ty::Prim(rir::Prim::Result)), _ => Err(format!("{ty}")), } diff --git a/source/compiler/qsc_passes/src/replace_qubit_allocation.rs b/source/compiler/qsc_passes/src/replace_qubit_allocation.rs index 42d53519dc..7cab9c56ee 100644 --- a/source/compiler/qsc_passes/src/replace_qubit_allocation.rs +++ b/source/compiler/qsc_passes/src/replace_qubit_allocation.rs @@ -25,6 +25,7 @@ use crate::common::{IdentTemplate, create_gen_core_ref, generated_name}; struct QubitIdent { id: IdentTemplate, is_array: bool, + is_memory: bool, } pub(crate) struct ReplaceQubitAllocation<'a> { @@ -53,11 +54,13 @@ impl<'a> ReplaceQubitAllocation<'a> { mut init: QubitInit, qubit_source: QubitSource, ) -> (Vec, Vec) { - fn is_non_tuple(init: &mut QubitInit) -> (bool, Option) { + fn non_tuple_init(init: &mut QubitInit) -> Option<(bool, Option)> { match &mut init.kind { - QubitInitKind::Array(e) => (true, Some(take(e))), - QubitInitKind::Single => (true, None), - QubitInitKind::Tuple(_) => (false, None), + QubitInitKind::Array(e) => Some((false, Some(take(e)))), + QubitInitKind::Single => Some((false, None)), + QubitInitKind::MemoryArray(e) => Some((true, Some(take(e)))), + QubitInitKind::MemorySingle => Some((true, None)), + QubitInitKind::Tuple(_) => None, QubitInitKind::Err => panic!("QubitInitKind::Err"), } } @@ -65,7 +68,7 @@ impl<'a> ReplaceQubitAllocation<'a> { let mut new_stmts: Vec = vec![]; let mut new_ids: Vec = vec![]; - if let (true, opt) = is_non_tuple(&mut init) { + if let Some((is_memory, opt)) = non_tuple_init(&mut init) { if let PatKind::Bind(id) = pat.kind { let id = IdentTemplate { id: id.id, @@ -77,11 +80,15 @@ impl<'a> ReplaceQubitAllocation<'a> { new_stmts.push(match opt { Some(mut size) => { self.visit_expr(&mut size); - self.create_array_alloc_stmt(&id, size, qubit_source) + self.create_array_alloc_stmt(&id, size, qubit_source, is_memory) } - None => self.create_alloc_stmt(&id, qubit_source), + None => self.create_alloc_stmt(&id, qubit_source, is_memory), + }); + new_ids.push(QubitIdent { + id, + is_array, + is_memory, }); - new_ids.push(QubitIdent { id, is_array }); } else { panic!("Shape of identifier pattern doesn't match shape of initializer"); } @@ -89,19 +96,20 @@ impl<'a> ReplaceQubitAllocation<'a> { let (assignment_expr, mut ids) = self.process_qubit_init(init); new_stmts = ids .iter_mut() - .map(|(id, size)| match size { + .map(|(id, size, is_memory)| match size { Some(size) => { self.visit_expr(size); - self.create_array_alloc_stmt(id, size.clone(), qubit_source) + self.create_array_alloc_stmt(id, size.clone(), qubit_source, *is_memory) } - None => self.create_alloc_stmt(id, qubit_source), + None => self.create_alloc_stmt(id, qubit_source, *is_memory), }) .collect(); new_ids = ids .into_iter() - .map(|(id, expr)| QubitIdent { + .map(|(id, expr, is_memory)| QubitIdent { id, is_array: expr.is_some(), + is_memory, }) .collect(); new_stmts.push(Stmt { @@ -157,21 +165,32 @@ impl<'a> ReplaceQubitAllocation<'a> { fn process_qubit_init( &mut self, init: QubitInit, - ) -> (Expr, Vec<(IdentTemplate, Option)>) { + ) -> (Expr, Vec<(IdentTemplate, Option, bool)>) { match init.kind { QubitInitKind::Array(size) => { let gen_id = self.gen_ident(Ty::Array(Box::new(Ty::Prim(Prim::Qubit))), init.span); let expr = gen_id.gen_local_ref(self.assigner); - (expr, vec![(gen_id, Some(*size))]) + (expr, vec![(gen_id, Some(*size), false)]) } QubitInitKind::Single => { let gen_id = self.gen_ident(Ty::Prim(Prim::Qubit), init.span); let expr = gen_id.gen_local_ref(self.assigner); - (expr, vec![(gen_id, None)]) + (expr, vec![(gen_id, None, false)]) + } + QubitInitKind::MemoryArray(size) => { + let gen_id = + self.gen_ident(Ty::Array(Box::new(Ty::Prim(Prim::MemoryQubit))), init.span); + let expr = gen_id.gen_local_ref(self.assigner); + (expr, vec![(gen_id, Some(*size), true)]) + } + QubitInitKind::MemorySingle => { + let gen_id = self.gen_ident(Ty::Prim(Prim::MemoryQubit), init.span); + let expr = gen_id.gen_local_ref(self.assigner); + (expr, vec![(gen_id, None, true)]) } QubitInitKind::Tuple(inits) => { let mut exprs: Vec = vec![]; - let mut ids: Vec<(IdentTemplate, Option)> = vec![]; + let mut ids: Vec<(IdentTemplate, Option, bool)> = vec![]; for i in inits { let (sub_expr, sub_ids) = self.process_qubit_init(i); exprs.push(sub_expr); @@ -217,9 +236,9 @@ impl<'a> ReplaceQubitAllocation<'a> { fn get_dealloc_stmt(&mut self, qubit: &QubitIdent) -> Stmt { if qubit.is_array { - self.create_array_dealloc_stmt(&qubit.id) + self.create_array_dealloc_stmt(&qubit.id, qubit.is_memory) } else { - self.create_dealloc_stmt(&qubit.id) + self.create_dealloc_stmt(&qubit.id, qubit.is_memory) } } @@ -243,15 +262,24 @@ impl<'a> ReplaceQubitAllocation<'a> { stmts } - fn create_alloc_stmt(&mut self, ident: &IdentTemplate, qubit_source: QubitSource) -> Stmt { + fn create_alloc_stmt( + &mut self, + ident: &IdentTemplate, + qubit_source: QubitSource, + is_memory: bool, + ) -> Stmt { let ns = self.get_qir_runtime_namespace(); - let api = match qubit_source { - QubitSource::Fresh => "__quantum__rt__qubit_allocate", - QubitSource::Dirty => "__quantum__rt__qubit_borrow", + let api = match (qubit_source, is_memory) { + (QubitSource::Fresh, false) => "__quantum__rt__qubit_allocate", + (QubitSource::Dirty, false) => "__quantum__rt__qubit_borrow", + (QubitSource::Fresh, true) => "__quantum__rt__memory_qubit_allocate", + (QubitSource::Dirty, true) => { + unreachable!("Borrowing MemoryQubit is rejected by parser") + } }; let mut call_expr = create_gen_core_ref(self.core, ns, api, Vec::new(), ident.span); call_expr.id = self.assigner.next_node(); - create_general_alloc_stmt(self.assigner, ident, call_expr, None) + create_general_alloc_stmt(self.assigner, ident, call_expr, None, is_memory) } fn create_array_alloc_stmt( @@ -259,23 +287,32 @@ impl<'a> ReplaceQubitAllocation<'a> { ident: &IdentTemplate, array_size: Expr, qubit_source: QubitSource, + is_memory: bool, ) -> Stmt { let ns = self.get_qir_runtime_namespace(); - let api = match qubit_source { - QubitSource::Fresh => "AllocateQubitArray", - QubitSource::Dirty => "BorrowQubitArray", + let api = match (qubit_source, is_memory) { + (QubitSource::Fresh, false) => "AllocateQubitArray", + (QubitSource::Dirty, false) => "BorrowQubitArray", + (QubitSource::Fresh, true) => "AllocateMemoryQubitArray", + (QubitSource::Dirty, true) => { + unreachable!("Borrowing MemoryQubit is rejected by parser") + } }; let mut call_expr = create_gen_core_ref(self.core, ns, api, Vec::new(), ident.span); call_expr.id = self.assigner.next_node(); - create_general_alloc_stmt(self.assigner, ident, call_expr, Some(array_size)) + create_general_alloc_stmt(self.assigner, ident, call_expr, Some(array_size), is_memory) } - fn create_dealloc_stmt(&mut self, ident: &IdentTemplate) -> Stmt { + fn create_dealloc_stmt(&mut self, ident: &IdentTemplate, is_memory: bool) -> Stmt { let ns = self.get_qir_runtime_namespace(); let mut call_expr = create_gen_core_ref( self.core, ns, - "__quantum__rt__qubit_release", + if is_memory { + "__quantum__rt__memory_qubit_release" + } else { + "__quantum__rt__qubit_release" + }, Vec::new(), ident.span, ); @@ -283,10 +320,19 @@ impl<'a> ReplaceQubitAllocation<'a> { create_general_dealloc_stmt(self.assigner, call_expr, ident) } - fn create_array_dealloc_stmt(&mut self, ident: &IdentTemplate) -> Stmt { + fn create_array_dealloc_stmt(&mut self, ident: &IdentTemplate, is_memory: bool) -> Stmt { let ns = self.get_qir_runtime_namespace(); - let mut call_expr = - create_gen_core_ref(self.core, ns, "ReleaseQubitArray", Vec::new(), ident.span); + let mut call_expr = create_gen_core_ref( + self.core, + ns, + if is_memory { + "ReleaseMemoryQubitArray" + } else { + "ReleaseQubitArray" + }, + Vec::new(), + ident.span, + ); call_expr.id = self.assigner.next_node(); create_general_dealloc_stmt(self.assigner, call_expr, ident) } @@ -446,6 +492,24 @@ fn create_qubit_global_alloc( qubit_init.span, call_expr, Some(take(&mut expr)), + Prim::Qubit, + ) + } + QubitInitKind::MemoryArray(mut expr) => { + let mut call_expr = create_gen_core_ref( + core, + ns, + "AllocateMemoryQubitArray", + Vec::new(), + qubit_init.span, + ); + call_expr.id = assigner.next_node(); + create_qubit_alloc_call_expr( + assigner, + qubit_init.span, + call_expr, + Some(take(&mut expr)), + Prim::MemoryQubit, ) } QubitInitKind::Single => { @@ -457,7 +521,30 @@ fn create_qubit_global_alloc( qubit_init.span, ); call_expr.id = assigner.next_node(); - create_qubit_alloc_call_expr(assigner, qubit_init.span, call_expr, None) + create_qubit_alloc_call_expr( + assigner, + qubit_init.span, + call_expr, + None, + Prim::Qubit, + ) + } + QubitInitKind::MemorySingle => { + let mut call_expr = create_gen_core_ref( + core, + ns, + "__quantum__rt__memory_qubit_allocate", + Vec::new(), + qubit_init.span, + ); + call_expr.id = assigner.next_node(); + create_qubit_alloc_call_expr( + assigner, + qubit_init.span, + call_expr, + None, + Prim::MemoryQubit, + ) } QubitInitKind::Tuple(tup) => Expr { id: assigner.next_node(), @@ -485,10 +572,16 @@ fn create_general_alloc_stmt( ident: &IdentTemplate, call_expr: Expr, array_size: Option, + is_memory: bool, ) -> Stmt { + let prim = if is_memory { + Prim::MemoryQubit + } else { + Prim::Qubit + }; ident.gen_steppable_id_init( Mutability::Immutable, - create_qubit_alloc_call_expr(assigner, ident.span, call_expr, array_size), + create_qubit_alloc_call_expr(assigner, ident.span, call_expr, array_size, prim), assigner, ) } @@ -498,11 +591,12 @@ fn create_qubit_alloc_call_expr( span: Span, call_expr: Expr, array_size: Option, + prim: Prim, ) -> Expr { let ty = if array_size.is_none() { - Ty::Prim(Prim::Qubit) + Ty::Prim(prim) } else { - Ty::Array(Box::new(Ty::Prim(Prim::Qubit))) + Ty::Array(Box::new(Ty::Prim(prim))) }; Expr { id: assigner.next_node(), diff --git a/source/compiler/qsc_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index 30f0b7da32..e0877266ed 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -2311,9 +2311,11 @@ fn derive_instrinsic_operation_application_generator_set( ) -> ApplicationGeneratorSet { assert!(matches!(callable_context.kind, CallableKind::Operation)); - // The value kind of intrinsic operations is inherently dynamic if their output is not `Unit` or `Qubit`. + // The value kind of intrinsic operations is inherently dynamic if their output is + // not `Unit`, `Qubit` or `MemoryQubit`. let runtime_kind = if callable_context.output_type == Ty::UNIT || callable_context.output_type == Ty::Prim(Prim::Qubit) + || callable_context.output_type == Ty::Prim(Prim::MemoryQubit) { ValueKind::Constant } else { @@ -2401,6 +2403,7 @@ fn ty_prim_to_runtime_output_flag(prim: Prim) -> RuntimeFeatureFlags { Prim::BigInt | Prim::Pauli | Prim::Qubit + | Prim::MemoryQubit | Prim::Range | Prim::RangeFrom | Prim::RangeTo @@ -2459,7 +2462,7 @@ fn derive_runtime_features_for_value_kind_associated_to_type( Prim::Double => RuntimeFeatureFlags::UseOfDynamicDouble, Prim::Int => RuntimeFeatureFlags::UseOfDynamicInt, Prim::Pauli => RuntimeFeatureFlags::UseOfDynamicPauli, - Prim::Qubit => RuntimeFeatureFlags::UseOfDynamicQubit, + Prim::Qubit | Prim::MemoryQubit => RuntimeFeatureFlags::UseOfDynamicQubit, Prim::Range | Prim::RangeFrom | Prim::RangeTo | Prim::RangeFull => { RuntimeFeatureFlags::UseOfDynamicRange } diff --git a/source/language_service/src/code_action/wrapper_refactor.rs b/source/language_service/src/code_action/wrapper_refactor.rs index 1e001a6562..594e5db1a0 100644 --- a/source/language_service/src/code_action/wrapper_refactor.rs +++ b/source/language_service/src/code_action/wrapper_refactor.rs @@ -276,6 +276,7 @@ fn default_value_for_type(ty: &Ty) -> (Option, String) { Prim::BigInt => (Some("0L".to_string()), "BigInt".to_string()), Prim::String => (Some("\"\"".to_string()), "String".to_string()), Prim::Qubit => (None, "Qubit - allocate with 'use'".to_string()), + Prim::MemoryQubit => (None, "MemoryQubit - allocate via 'use'".to_string()), Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => { (Some("0..1".to_string()), "Range".to_string()) } diff --git a/source/language_service/src/completion/qsharp.rs b/source/language_service/src/completion/qsharp.rs index 26109aa89b..d78795368b 100644 --- a/source/language_service/src/completion/qsharp.rs +++ b/source/language_service/src/completion/qsharp.rs @@ -280,7 +280,16 @@ fn collect_paths( locals_and_builtins.push(locals_at_cursor.type_names()); locals_and_builtins.push( [ - "Qubit", "Int", "Unit", "Result", "Bool", "BigInt", "Double", "Pauli", "Range", + "Qubit", + "MemoryQubit", + "Int", + "Unit", + "Result", + "Bool", + "BigInt", + "Double", + "Pauli", + "Range", "String", ] .map(|s| Completion::new(s.to_string(), CompletionItemKind::Interface)) diff --git a/source/pip/src/interpreter.rs b/source/pip/src/interpreter.rs index d9272eaa2f..981fbb0e15 100644 --- a/source/pip/src/interpreter.rs +++ b/source/pip/src/interpreter.rs @@ -1069,9 +1069,12 @@ where | Prim::Int | Prim::String | Prim::Result => None, - Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => { - Some(ty) - } + Prim::Qubit + | Prim::MemoryQubit + | Prim::Range + | Prim::RangeTo + | Prim::RangeFrom + | Prim::RangeFull => Some(ty), }, Ty::Tuple(tup) => tup .iter() diff --git a/source/pip/src/interpreter/data_interop.rs b/source/pip/src/interpreter/data_interop.rs index 8d052c44c3..ee2eddcbcd 100644 --- a/source/pip/src/interpreter/data_interop.rs +++ b/source/pip/src/interpreter/data_interop.rs @@ -216,6 +216,7 @@ where /// Given a type, convert a Python object into a Q# value of that type. This will recur through tuples and arrays, /// and will return an error if the type is not supported or the object cannot be converted. +#[allow(clippy::too_many_lines)] pub(super) fn pyobj_to_value( ctx: &interpret::Interpreter, py: Python, @@ -231,7 +232,12 @@ pub(super) fn pyobj_to_value( Prim::String => Ok(Value::String(extract_obj::(py, obj, ty)?.into())), Prim::Result => Ok(Value::Result(extract_obj::(py, obj, ty)?.into())), Prim::Pauli => Ok(Value::Pauli(extract_obj::(py, obj, ty)?.into())), - Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => { + Prim::Qubit + | Prim::MemoryQubit + | Prim::Range + | Prim::RangeTo + | Prim::RangeFrom + | Prim::RangeFull => { unimplemented!("primitive input type: {prim_ty:?}") } }, @@ -333,7 +339,12 @@ pub(super) fn type_ir_from_qsharp_ty(ctx: &interpret::Interpreter, ty: &Ty) -> O Prim::String => PrimitiveKind::String, Prim::Pauli => PrimitiveKind::Pauli, Prim::Result => PrimitiveKind::Result, - Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => { + Prim::Qubit + | Prim::MemoryQubit + | Prim::Range + | Prim::RangeTo + | Prim::RangeFrom + | Prim::RangeFull => { return None; } }; @@ -469,7 +480,7 @@ pub(crate) fn value_to_pyobj( let closure: Closure = value.clone().into(); closure.into_py_any(py) } - Value::Qubit(..) | Value::Range(..) | Value::Var(..) => { + Value::Qubit(..) | Value::MemoryQubit(..) | Value::Range(..) | Value::Var(..) => { format!("<{}> {}", value.type_name(), value).into_py_any(py) } } diff --git a/source/resource_estimator/src/counts.rs b/source/resource_estimator/src/counts.rs index 1cf8d8066b..1adbc685b2 100644 --- a/source/resource_estimator/src/counts.rs +++ b/source/resource_estimator/src/counts.rs @@ -10,7 +10,7 @@ use num_bigint::BigUint; use num_complex::Complex; use qsc::{Backend, BackendResult, interpret::Value}; use rand::{Rng, SeedableRng, rngs::StdRng}; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use std::{array, cell::RefCell, f64::consts::PI, fmt::Debug, iter::Sum}; use crate::{counts::memory_compute::CachingStrategy, system::LogicalResourceCounts}; @@ -53,6 +53,16 @@ pub struct LogicalCounter { /// Map to track any post-select measurements by their associated qubit. /// This value is used in a measurement, if present, before generating a random result. post_select_measurements: FxHashMap, + + // Memory qubits management. + /// Ids used for memory qubits (used or free). + memory_qubit_ids: FxHashSet, + /// Ids of free memory qubits. Can be reused only for allocating memory qubits. + free_memory_qubits: Vec, + /// Number of Store instructions. + memory_qubit_store_count: usize, + /// Number of Load instructions. + memory_qubit_load_count: usize, } impl Default for LogicalCounter { @@ -73,6 +83,10 @@ impl Default for LogicalCounter { memory_compute: None, rnd: RefCell::new(StdRng::seed_from_u64(0)), post_select_measurements: FxHashMap::default(), + memory_qubit_ids: FxHashSet::default(), + free_memory_qubits: vec![], + memory_qubit_store_count: 0, + memory_qubit_load_count: 0, } } } @@ -80,13 +94,29 @@ impl Default for LogicalCounter { impl LogicalCounter { #[must_use] pub fn logical_resources(&self) -> LogicalResourceCounts { + let uses_memory_qubits = !self.memory_qubit_ids.is_empty(); let (num_compute_qubits, read_from_memory_count, write_to_memory_count) = if let Some(memory_compute) = &self.memory_compute { + assert!( + !uses_memory_qubits, + "Cannot use MemoryQubits together with MemoryComputeArchitecture" + ); ( Some(memory_compute.compute_size() as u64), Some(memory_compute.read_from_memory_count() as u64), Some(memory_compute.write_to_memory_count() as u64), ) + } else if uses_memory_qubits { + let memory_qubits_count = self.free_memory_qubits.len(); + assert!(memory_qubits_count == self.memory_qubit_ids.len()); + let compute_qubits_count = self.free_list.len(); + let total_qubits_count = self.next_free; + assert!(total_qubits_count == compute_qubits_count + memory_qubits_count); + ( + Some(compute_qubits_count as u64), + Some(self.memory_qubit_load_count as u64), + Some(self.memory_qubit_store_count as u64), + ) } else { (None, None, None) }; @@ -477,6 +507,14 @@ impl LogicalCounter { .as_ref() .map_or(0, memory_compute::MemoryComputeInfo::read_from_memory_count) } + + // Returns qubit id that has never been used before. + fn new_qubit_id(&mut self) -> usize { + let index = self.next_free; + self.next_free += 1; + self.max_layer.push(self.allocation_barrier); + index + } } impl Backend for LogicalCounter { @@ -605,15 +643,16 @@ impl Backend for LogicalCounter { if let Some(index) = self.free_list.pop() { index } else { - let index = self.next_free; - self.next_free += 1; - self.max_layer.push(self.allocation_barrier); - index + self.new_qubit_id() } } fn qubit_release(&mut self, q: usize) -> bool { - self.free_list.push(q); + if self.memory_qubit_ids.contains(&q) { + self.free_memory_qubits.push(q); + } else { + self.free_list.push(q); + } true } @@ -640,6 +679,28 @@ impl Backend for LogicalCounter { true } + fn memory_qubit_allocate(&mut self) -> usize { + if let Some(index) = self.free_memory_qubits.pop() { + index + } else { + let index = self.new_qubit_id(); + self.memory_qubit_ids.insert(index); + index + } + } + + fn memory_qubit_load(&mut self, mem_qubit_id: usize, comp_qubit_id: usize) { + assert!(self.memory_qubit_ids.contains(&mem_qubit_id)); + assert!(!self.memory_qubit_ids.contains(&comp_qubit_id)); + self.memory_qubit_load_count += 1; + } + + fn memory_qubit_store(&mut self, comp_qubit_id: usize, mem_qubit_id: usize) { + assert!(self.memory_qubit_ids.contains(&mem_qubit_id)); + assert!(!self.memory_qubit_ids.contains(&comp_qubit_id)); + self.memory_qubit_store_count += 1; + } + fn custom_intrinsic(&mut self, name: &str, arg: Value) -> Option> { match name { "BeginEstimateCaching" => { diff --git a/source/samples_test/src/tests/language.rs b/source/samples_test/src/tests/language.rs index 768c8f96f5..a074277fa8 100644 --- a/source/samples_test/src/tests/language.rs +++ b/source/samples_test/src/tests/language.rs @@ -304,6 +304,12 @@ pub const QUANTUMMEMORY_EXPECT: Expect = expect!["()"]; pub const QUANTUMMEMORY_EXPECT_DEBUG: Expect = expect!["()"]; pub const QUANTUMMEMORY_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 40"]; pub const QUANTUMMEMORY_EXPECT_QIR: Expect = expect!["generated QIR of length 961"]; +pub const MEMORYQUBIT_EXPECT: Expect = expect!["One"]; +pub const MEMORYQUBIT_EXPECT_DEBUG: Expect = expect!["One"]; +pub const MEMORYQUBIT_EXPECT_CIRCUIT: Expect = + expect!["generated circuit of length 434"]; +pub const MEMORYQUBIT_EXPECT_QIR: Expect = + expect!["generated QIR of length 1806"]; pub const QUBIT_EXPECT: Expect = expect![[r#" STATE: |1000⟩: 0.0000+0.5000𝑖 diff --git a/source/vscode/syntaxes/qsharp.tmLanguage.json b/source/vscode/syntaxes/qsharp.tmLanguage.json index d9c390c2a3..5e80c7531c 100644 --- a/source/vscode/syntaxes/qsharp.tmLanguage.json +++ b/source/vscode/syntaxes/qsharp.tmLanguage.json @@ -162,7 +162,7 @@ "patterns": [ { "name": "storage.type.qsharp", - "match": "\\b(Int|BigInt|Double|Bool|Qubit|Pauli|Result|Range|String|Unit|Ctl|Adj|is)\\b" + "match": "\\b(Int|BigInt|Double|Bool|Qubit|MemoryQubit|Pauli|Result|Range|String|Unit|Ctl|Adj|is)\\b" } ] },