From ace3ed6e78068a1957743f250a7ca4a3907997fe Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 17 Mar 2026 11:08:04 -0700 Subject: [PATCH 01/16] WIP: introduce memory type and API, no support yet --- library/std/qsharp.json | 1 + library/std/src/Std/Memory.qs | 28 +++++++++++++++++++ source/compiler/qsc_fir/src/ty.rs | 2 ++ source/compiler/qsc_frontend/src/resolve.rs | 3 +- source/compiler/qsc_hir/src/ty.rs | 2 ++ source/compiler/qsc_lowerer/src/lib.rs | 1 + source/compiler/qsc_rca/src/core.rs | 3 +- .../src/code_action/wrapper_refactor.rs | 1 + .../language_service/src/completion/qsharp.rs | 4 +-- source/pip/src/interpreter.rs | 9 ++++-- source/pip/src/interpreter/data_interop.rs | 15 ++++++++-- source/vscode/syntaxes/qsharp.tmLanguage.json | 2 +- 12 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 library/std/src/Std/Memory.qs diff --git a/library/std/qsharp.json b/library/std/qsharp.json index 9afd68a957..0deeba51b3 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/Memory.qs", "src/Std/Random.qs", "src/Std/Range.qs", "src/Std/ResourceEstimation.qs", diff --git a/library/std/src/Std/Memory.qs b/library/std/src/Std/Memory.qs new file mode 100644 index 0000000000..803676bfb0 --- /dev/null +++ b/library/std/src/Std/Memory.qs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +operation Allocate() : QMem { + body intrinsic; +} + +operation Free(qmem : QMem) : Unit { + body intrinsic; +} + +operation Clear(qmem : QMem) : Unit { + body intrinsic; +} + +operation Exchange(qmem : QMem, qubit : Qubit) : Unit { + body intrinsic; +} + +operation Store(qubit : Qubit, qmem : QMem) : Unit { + Clear(qmem); + Exchange(qmem, qubit); +} + +operation Load(qmem : QMem, qubit : Qubit) : Unit { + Exchange(qmem, qubit); + Clear(qmem); +} diff --git a/source/compiler/qsc_fir/src/ty.rs b/source/compiler/qsc_fir/src/ty.rs index d88ee98bd5..26bee40d46 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 quantum memory type. + QMem, /// The range type. Range, /// The range type without a lower bound. diff --git a/source/compiler/qsc_frontend/src/resolve.rs b/source/compiler/qsc_frontend/src/resolve.rs index db5f04c12c..47b762b3b7 100644 --- a/source/compiler/qsc_frontend/src/resolve.rs +++ b/source/compiler/qsc_frontend/src/resolve.rs @@ -1195,13 +1195,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)), + ("QMem".into(), Res::PrimTy(Prim::QMem)), ("Range".into(), Res::PrimTy(Prim::Range)), ("Result".into(), Res::PrimTy(Prim::Result)), ("String".into(), Res::PrimTy(Prim::String)), diff --git a/source/compiler/qsc_hir/src/ty.rs b/source/compiler/qsc_hir/src/ty.rs index c673d6a8ec..e77cbcf65c 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 quantum memory type. + QMem, /// The range type. Range, /// The range type without a lower bound. diff --git a/source/compiler/qsc_lowerer/src/lib.rs b/source/compiler/qsc_lowerer/src/lib.rs index 10b035d4d1..cdfc9854ff 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::QMem => qsc_fir::ty::Prim::QMem, 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_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index af009ecbd2..4a2c096826 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -2313,6 +2313,7 @@ fn ty_prim_to_runtime_output_flag(prim: Prim) -> RuntimeFeatureFlags { Prim::BigInt | Prim::Pauli | Prim::Qubit + | Prim::QMem | Prim::Range | Prim::RangeFrom | Prim::RangeTo @@ -2371,7 +2372,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::QMem => 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..d107424ccc 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::QMem => (None, "Quantum memory - allocate via 'store'".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..91eb5fac64 100644 --- a/source/language_service/src/completion/qsharp.rs +++ b/source/language_service/src/completion/qsharp.rs @@ -280,8 +280,8 @@ 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", - "String", + "Qubit", "QMem", "Int", "Unit", "Result", "Bool", "BigInt", "Double", "Pauli", + "Range", "String", ] .map(|s| Completion::new(s.to_string(), CompletionItemKind::Interface)) .into(), diff --git a/source/pip/src/interpreter.rs b/source/pip/src/interpreter.rs index 99209aea32..42f01f6a6e 100644 --- a/source/pip/src/interpreter.rs +++ b/source/pip/src/interpreter.rs @@ -1040,9 +1040,12 @@ where | Prim::Int | Prim::String | Prim::Result => None, - Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => { - Some(ty) - } + Prim::Qubit + | Prim::QMem + | 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 c3b986a106..cab2a7ee94 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::QMem + | 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::QMem + | Prim::Range + | Prim::RangeTo + | Prim::RangeFrom + | Prim::RangeFull => { return None; } }; diff --git a/source/vscode/syntaxes/qsharp.tmLanguage.json b/source/vscode/syntaxes/qsharp.tmLanguage.json index d9c390c2a3..af9b3d7c38 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|QMem|Pauli|Result|Range|String|Unit|Ctl|Adj|is)\\b" } ] }, From 35cb70095a6785c2b1ed37460fecda3df90f5a5f Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Tue, 17 Mar 2026 14:48:17 -0700 Subject: [PATCH 02/16] Basic support --- library/src/lib.rs | 4 + library/std/src/Std/Memory.qs | 2 + source/compiler/qsc_eval/src/lib.rs | 105 ++++++++++++++---- source/compiler/qsc_eval/src/val.rs | 10 +- .../src/evaluation_context.rs | 1 + source/compiler/qsc_partial_eval/src/lib.rs | 1 + source/pip/src/interpreter/data_interop.rs | 2 +- source/resource_estimator/src/counts.rs | 29 ++++- 8 files changed, 128 insertions(+), 26 deletions(-) diff --git a/library/src/lib.rs b/library/src/lib.rs index 809a4ebe8e..665510de52 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/Memory.qs", + include_str!("../std/src/Std/Memory.qs"), + ), ( "qsharp-library-source:QIR/Intrinsic.qs", include_str!("../std/src/QIR/Intrinsic.qs"), diff --git a/library/std/src/Std/Memory.qs b/library/std/src/Std/Memory.qs index 803676bfb0..6b27bb77d2 100644 --- a/library/std/src/Std/Memory.qs +++ b/library/std/src/Std/Memory.qs @@ -26,3 +26,5 @@ operation Load(qmem : QMem, qubit : Qubit) : Unit { Exchange(qmem, qubit); Clear(qmem); } + +export Allocate, Free, Clear, Exchange, Store, Load; diff --git a/source/compiler/qsc_eval/src/lib.rs b/source/compiler/qsc_eval/src/lib.rs index 093bf625b4..cfbf945c9b 100644 --- a/source/compiler/qsc_eval/src/lib.rs +++ b/source/compiler/qsc_eval/src/lib.rs @@ -1326,30 +1326,50 @@ impl State { 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()) + self.allocate_qubit(env, sim, &call_stack, name) } - "__quantum__rt__qubit_release" => { - let qubit = arg - .unwrap_qubit() + "Allocate" => { + let q_val = self.allocate_qubit(env, sim, &call_stack, name); + sim.custom_intrinsic("__set_memory_qubit", q_val.clone(), &call_stack); + let Value::Qubit(q) = q_val else { + panic!("qubit allocation should return a qubit value") + }; + Value::QMem(q) + } + "Free" => { + let Value::QMem(q) = arg else { + panic!("qubit release should be called with a quantum memory reference") + }; + let q_val = Value::Qubit(q); + self.release_qubit(env, sim, q_val, arg_span, &call_stack)? + } + "Clear" => { + let Value::QMem(q) = arg else { + panic!("qubit release should be called with a quantum memory reference") + }; + let qubit = q.try_deref().ok_or(Error::QubitDoubleRelease(arg_span))?; + sim.reset(qubit.0, &call_stack); + Value::unit() + } + "Exchange" => { + let [qmem, qubit] = val::unwrap_tuple(arg); + let Value::QMem(qmem) = qmem else { + panic!("first argument of Exchange should be a quantum memory reference") + }; + let Value::Qubit(qubit) = qubit else { + panic!("second argument of Exchange should be a qubit reference") + }; + let qmem_ref = qmem .try_deref() - .ok_or(Error::QubitDoubleRelease(arg_span))?; - env.release_qubit(&qubit); - let is_zero = sim.qubit_release(qubit.0, &call_stack); - let is_borrowed = self.dirty_qubits.remove(&qubit.0); - if is_zero || is_borrowed { - Value::unit() - } else { - return Err(Error::ReleasedQubitNotZero(qubit.0, arg_span)); - } + .ok_or(Error::QubitUsedAfterRelease(arg_span))?; + let qubit_ref = qubit + .try_deref() + .ok_or(Error::QubitUsedAfterRelease(arg_span))?; + sim.swap(qmem_ref.0, qubit_ref.0, &call_stack); + Value::unit() + } + "__quantum__rt__qubit_release" => { + self.release_qubit(env, sim, arg, arg_span, &call_stack)? } _ => { let val = intrinsic::call( @@ -1376,6 +1396,47 @@ impl State { Ok(()) } + fn allocate_qubit( + &mut self, + env: &mut Env, + sim: &mut TracingBackend<'_, B>, + call_stack: &[Frame], + name: &Rc, + ) -> Value { + 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()) + } + + fn release_qubit( + &mut self, + env: &mut Env, + sim: &mut TracingBackend<'_, B>, + arg: Value, + arg_span: PackageSpan, + call_stack: &[Frame], + ) -> Result { + let qubit = arg + .unwrap_qubit() + .try_deref() + .ok_or(Error::QubitDoubleRelease(arg_span))?; + env.release_qubit(&qubit); + let is_zero = sim.qubit_release(qubit.0, call_stack); + let is_borrowed = self.dirty_qubits.remove(&qubit.0); + Ok(if is_zero || is_borrowed { + Value::unit() + } else { + return Err(Error::ReleasedQubitNotZero(qubit.0, arg_span)); + }) + } + 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..3cd9203a3c 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), + QMem(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::QMem(v) => write!( + f, + "QMem{}", + (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}..."), @@ -448,6 +455,7 @@ impl Value { Value::Int(_) => "Int", Value::Pauli(_) => "Pauli", Value::Qubit(_) => "Qubit", + Value::QMem(_) => "QMem", Value::Range(..) => "Range", Value::Result(_) => "Result", Value::String(_) => "String", @@ -464,7 +472,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::QMem(q) => vec![q.clone()], Value::Tuple(tup, _) => tup.iter().flat_map(Value::qubits).collect(), Value::BigInt(_) diff --git a/source/compiler/qsc_partial_eval/src/evaluation_context.rs b/source/compiler/qsc_partial_eval/src/evaluation_context.rs index e93525f82e..b568f48849 100644 --- a/source/compiler/qsc_partial_eval/src/evaluation_context.rs +++ b/source/compiler/qsc_partial_eval/src/evaluation_context.rs @@ -328,6 +328,7 @@ fn map_eval_value_to_value_kind(value: &Value) -> ValueKind { | Value::Int(_) | Value::Pauli(_) | Value::Qubit(_) + | Value::QMem(_) | Value::Range(_) | Value::Result(Result::Val(_)) | Value::String(_) => ValueKind::Static, diff --git a/source/compiler/qsc_partial_eval/src/lib.rs b/source/compiler/qsc_partial_eval/src/lib.rs index bd68deb5b9..5f35104fd9 100644 --- a/source/compiler/qsc_partial_eval/src/lib.rs +++ b/source/compiler/qsc_partial_eval/src/lib.rs @@ -3303,6 +3303,7 @@ impl<'a> PartialEvaluator<'a> { | Value::Global(_, _) | Value::Pauli(_) | Value::Qubit(_) + | Value::QMem(_) | Value::Range(_) | Value::String(_) => panic!("unsupported value type in output recording"), } diff --git a/source/pip/src/interpreter/data_interop.rs b/source/pip/src/interpreter/data_interop.rs index cab2a7ee94..651d93eb01 100644 --- a/source/pip/src/interpreter/data_interop.rs +++ b/source/pip/src/interpreter/data_interop.rs @@ -480,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::QMem(..) | 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 7ff47f17c7..f1b9f0ee19 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}; @@ -50,6 +50,12 @@ pub struct LogicalCounter { memory_compute: Option, /// Random number generator rnd: RefCell, + + // Manual memory management tracking + all_manual_memory_qubits: FxHashSet, + current_memory_qubits: FxHashSet, + manual_memory_writes: usize, + manual_memory_reads: usize, } impl Default for LogicalCounter { @@ -69,6 +75,10 @@ impl Default for LogicalCounter { repeats: vec![], memory_compute: None, rnd: RefCell::new(StdRng::seed_from_u64(0)), + all_manual_memory_qubits: FxHashSet::default(), + current_memory_qubits: FxHashSet::default(), + manual_memory_writes: 0, + manual_memory_reads: 0, } } } @@ -84,7 +94,11 @@ impl LogicalCounter { Some(memory_compute.write_to_memory_count() as u64), ) } else { - (None, None, None) + ( + Some((self.next_free - self.all_manual_memory_qubits.len()) as u64), + Some(self.manual_memory_reads as u64), + Some(self.manual_memory_writes as u64), + ) }; LogicalResourceCounts { @@ -570,6 +584,10 @@ impl Backend for LogicalCounter { fn swap(&mut self, q0: usize, q1: usize) { self.assert_compute_qubits([q0, q1]); + if self.current_memory_qubits.contains(&q0) { + self.manual_memory_reads += 1; + self.manual_memory_writes += 1; + } self.schedule_two_qubit_clifford(q0, q1); } @@ -606,6 +624,7 @@ impl Backend for LogicalCounter { fn qubit_release(&mut self, q: usize) -> bool { self.free_list.push(q); + self.current_memory_qubits.remove(&q); true } @@ -677,6 +696,12 @@ impl Backend for LogicalCounter { "GlobalPhase" | "ConfigurePauliNoise" | "ConfigureQubitLoss" | "ApplyIdleNoise" => { Some(Ok(Value::unit())) } + "__set_memory_qubit" => { + let q = arg.unwrap_qubit(); + self.all_manual_memory_qubits.insert(q.deref().0); + self.current_memory_qubits.insert(q.deref().0); + Some(Ok(Value::unit())) + } _ => None, } } From fb37316bc46e45e20ac56d9d4332f6190637ae43 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Wed, 18 Mar 2026 07:50:13 -0700 Subject: [PATCH 03/16] use load/store intrinsic --- library/std/src/Std/Memory.qs | 16 +++------------- source/compiler/qsc_eval/src/lib.rs | 23 +++++++++++++++++------ source/resource_estimator/src/counts.rs | 1 + 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/library/std/src/Std/Memory.qs b/library/std/src/Std/Memory.qs index 6b27bb77d2..1448d211cf 100644 --- a/library/std/src/Std/Memory.qs +++ b/library/std/src/Std/Memory.qs @@ -9,22 +9,12 @@ operation Free(qmem : QMem) : Unit { body intrinsic; } -operation Clear(qmem : QMem) : Unit { - body intrinsic; -} - -operation Exchange(qmem : QMem, qubit : Qubit) : Unit { - body intrinsic; -} - operation Store(qubit : Qubit, qmem : QMem) : Unit { - Clear(qmem); - Exchange(qmem, qubit); + body intrinsic; } operation Load(qmem : QMem, qubit : Qubit) : Unit { - Exchange(qmem, qubit); - Clear(qmem); + body intrinsic; } -export Allocate, Free, Clear, Exchange, Store, Load; +export Allocate, Free, Store, Load; diff --git a/source/compiler/qsc_eval/src/lib.rs b/source/compiler/qsc_eval/src/lib.rs index cfbf945c9b..7aba44c32a 100644 --- a/source/compiler/qsc_eval/src/lib.rs +++ b/source/compiler/qsc_eval/src/lib.rs @@ -1343,15 +1343,25 @@ impl State { let q_val = Value::Qubit(q); self.release_qubit(env, sim, q_val, arg_span, &call_stack)? } - "Clear" => { - let Value::QMem(q) = arg else { - panic!("qubit release should be called with a quantum memory reference") + "Store" => { + let [qubit, qmem] = val::unwrap_tuple(arg); + let Value::QMem(qmem) = qmem else { + panic!("first argument of Exchange should be a quantum memory reference") + }; + let Value::Qubit(qubit) = qubit else { + panic!("second argument of Exchange should be a qubit reference") }; - let qubit = q.try_deref().ok_or(Error::QubitDoubleRelease(arg_span))?; - sim.reset(qubit.0, &call_stack); + let qmem_ref = qmem + .try_deref() + .ok_or(Error::QubitUsedAfterRelease(arg_span))?; + let qubit_ref = qubit + .try_deref() + .ok_or(Error::QubitUsedAfterRelease(arg_span))?; + sim.reset(qmem_ref.0, &call_stack); + sim.swap(qubit_ref.0, qmem_ref.0, &call_stack); Value::unit() } - "Exchange" => { + "Load" => { let [qmem, qubit] = val::unwrap_tuple(arg); let Value::QMem(qmem) = qmem else { panic!("first argument of Exchange should be a quantum memory reference") @@ -1366,6 +1376,7 @@ impl State { .try_deref() .ok_or(Error::QubitUsedAfterRelease(arg_span))?; sim.swap(qmem_ref.0, qubit_ref.0, &call_stack); + sim.reset(qmem_ref.0, &call_stack); Value::unit() } "__quantum__rt__qubit_release" => { diff --git a/source/resource_estimator/src/counts.rs b/source/resource_estimator/src/counts.rs index f1b9f0ee19..c698bcb040 100644 --- a/source/resource_estimator/src/counts.rs +++ b/source/resource_estimator/src/counts.rs @@ -586,6 +586,7 @@ impl Backend for LogicalCounter { self.assert_compute_qubits([q0, q1]); if self.current_memory_qubits.contains(&q0) { self.manual_memory_reads += 1; + } else if self.current_memory_qubits.contains(&q1) { self.manual_memory_writes += 1; } self.schedule_two_qubit_clifford(q0, q1); From 33507f3fb0fc7b3ce4fb168cd0d1c26d31b7df70 Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Mon, 20 Apr 2026 17:00:41 -0700 Subject: [PATCH 04/16] Add a simple test for QMem --- source/compiler/qsc_eval/src/tests.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/source/compiler/qsc_eval/src/tests.rs b/source/compiler/qsc_eval/src/tests.rs index 0634e8ca46..e720ea967b 100644 --- a/source/compiler/qsc_eval/src/tests.rs +++ b/source/compiler/qsc_eval/src/tests.rs @@ -4227,3 +4227,28 @@ fn partial_eval_stmt_function_calls_from_library() { &expect!["3"], ); } + +#[test] +fn qmem_store_load() { + check_expr( + " + namespace Test { + operation Main() : Result { + use q = Qubit(); + let mem = Std.Memory.Allocate(); + X(q); + Std.Memory.Store(q, mem); + Std.Memory.Load(mem, q); + Std.Memory.Free(mem); + return MResetZ(q); + } + } + ", + "Test.Main()", + &expect!["One"], + ); +} + +// TODO: Add a test for when QMem is released in non-zero state. +// TODO: Add a test for QMem array. +// TODO: Add resource estimation tests. From e69809939a10ff3aa39cd797d5b4391049a7a228 Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Tue, 21 Apr 2026 10:42:04 -0700 Subject: [PATCH 05/16] add resource estimation test --- source/compiler/qsc_eval/src/tests.rs | 1 - source/resource_estimator/src/counts/tests.rs | 37 +++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/source/compiler/qsc_eval/src/tests.rs b/source/compiler/qsc_eval/src/tests.rs index e720ea967b..92ead9fa63 100644 --- a/source/compiler/qsc_eval/src/tests.rs +++ b/source/compiler/qsc_eval/src/tests.rs @@ -4251,4 +4251,3 @@ fn qmem_store_load() { // TODO: Add a test for when QMem is released in non-zero state. // TODO: Add a test for QMem array. -// TODO: Add resource estimation tests. diff --git a/source/resource_estimator/src/counts/tests.rs b/source/resource_estimator/src/counts/tests.rs index f3fdc2a67f..67d0ad6f50 100644 --- a/source/resource_estimator/src/counts/tests.rs +++ b/source/resource_estimator/src/counts/tests.rs @@ -13,8 +13,9 @@ use qsc::{ }; use super::LogicalCounter; +use crate::system::LogicalResourceCounts; -fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) { +fn run_logical_counts(source: &str, entry: Option<&str>) -> LogicalResourceCounts { let source_map = SourceMap::new([("test".into(), source.into())], entry.map(Into::into)); let (std_id, store) = qsc::compile::package_store_with_stdlib(TargetCapabilityFlags::all()); @@ -40,9 +41,7 @@ fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) { let mut out = GenericReceiver::new(&mut stdout); match interpreter.eval_entry_with_sim(&mut counter, &mut out) { - Ok(_) => { - expect.assert_debug_eq(&counter.logical_resources()); - } + Ok(_) => counter.logical_resources(), Err(err) => { for e in err { let report = Report::from(e); @@ -53,6 +52,10 @@ fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) { } } +fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) { + expect.assert_debug_eq(&run_logical_counts(source, entry)); +} + #[test] fn gates_are_counted() { verify_logical_counts( @@ -427,3 +430,29 @@ fn post_selection_can_take_impossible_branch() { "#]], ); } + +#[test] +fn qmem_store_load_counts_manual_memory_usage() { + let counts = run_logical_counts( + indoc! {r#" + namespace Test { + import Std.ResourceEstimation.*; + + @EntryPoint() + operation Main() : Unit { + use q = Qubit(); + let mem = Std.Memory.Allocate(); + X(q); + Std.Memory.Store(q, mem); + Std.Memory.Load(mem, q); + Std.Memory.Free(mem); + } + } + "#}, + None, + ); + 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)); +} \ No newline at end of file From 9d776fc15387a7c40006c3819e2b8b1a33c7ae63 Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 12:00:47 -0700 Subject: [PATCH 06/16] restructure tests --- Cargo.lock | 1 + library/Cargo.toml | 1 + library/src/tests.rs | 34 +++++++++++ library/src/tests/memory_qubits.rs | 58 +++++++++++++++++++ source/compiler/qsc_eval/src/tests.rs | 24 -------- source/resource_estimator/src/counts/tests.rs | 37 ++---------- 6 files changed, 98 insertions(+), 57 deletions(-) create mode 100644 library/src/tests/memory_qubits.rs 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/src/tests.rs b/library/src/tests.rs index 3b2fd5b72e..75d263d559 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 /// @@ -264,3 +268,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(ToString::to_string) + .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..4b8002d0f7 --- /dev/null +++ b/library/src/tests/memory_qubits.rs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use super::{logical_counts_with_lib, test_expression_with_lib}; +use indoc::indoc; +use qsc::interpret::Value; + +// Tests for memory qubits and Std.MemoryQubits namespace. + +#[test] +fn qmem_store_load() { + test_expression_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Result { + use q = Qubit(); + let mem = Std.Memory.Allocate(); + X(q); + Std.Memory.Store(q, mem); + Std.Memory.Load(mem, q); + Std.Memory.Free(mem); + return MResetZ(q); + } + } + "#}, + &Value::RESULT_ONE, + ); +} + +#[test] +fn qmem_store_load_counts_manual_memory_usage() { + let counts = logical_counts_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Unit { + use q = Qubit(); + let mem = Std.Memory.Allocate(); + X(q); + Std.Memory.Store(q, mem); + Std.Memory.Load(mem, q); + Std.Memory.Free(mem); + } + } + "#}, + ); + + 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)); +} + +// Add a test for different syntaxes. +// Add a test for when QMem is released in non-zero state. +// Add a test for QMem arrays. +// Add a test for not reusing memory as compute in RE. diff --git a/source/compiler/qsc_eval/src/tests.rs b/source/compiler/qsc_eval/src/tests.rs index 92ead9fa63..0634e8ca46 100644 --- a/source/compiler/qsc_eval/src/tests.rs +++ b/source/compiler/qsc_eval/src/tests.rs @@ -4227,27 +4227,3 @@ fn partial_eval_stmt_function_calls_from_library() { &expect!["3"], ); } - -#[test] -fn qmem_store_load() { - check_expr( - " - namespace Test { - operation Main() : Result { - use q = Qubit(); - let mem = Std.Memory.Allocate(); - X(q); - Std.Memory.Store(q, mem); - Std.Memory.Load(mem, q); - Std.Memory.Free(mem); - return MResetZ(q); - } - } - ", - "Test.Main()", - &expect!["One"], - ); -} - -// TODO: Add a test for when QMem is released in non-zero state. -// TODO: Add a test for QMem array. diff --git a/source/resource_estimator/src/counts/tests.rs b/source/resource_estimator/src/counts/tests.rs index 67d0ad6f50..f3fdc2a67f 100644 --- a/source/resource_estimator/src/counts/tests.rs +++ b/source/resource_estimator/src/counts/tests.rs @@ -13,9 +13,8 @@ use qsc::{ }; use super::LogicalCounter; -use crate::system::LogicalResourceCounts; -fn run_logical_counts(source: &str, entry: Option<&str>) -> LogicalResourceCounts { +fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) { let source_map = SourceMap::new([("test".into(), source.into())], entry.map(Into::into)); let (std_id, store) = qsc::compile::package_store_with_stdlib(TargetCapabilityFlags::all()); @@ -41,7 +40,9 @@ fn run_logical_counts(source: &str, entry: Option<&str>) -> LogicalResourceCount let mut out = GenericReceiver::new(&mut stdout); match interpreter.eval_entry_with_sim(&mut counter, &mut out) { - Ok(_) => counter.logical_resources(), + Ok(_) => { + expect.assert_debug_eq(&counter.logical_resources()); + } Err(err) => { for e in err { let report = Report::from(e); @@ -52,10 +53,6 @@ fn run_logical_counts(source: &str, entry: Option<&str>) -> LogicalResourceCount } } -fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) { - expect.assert_debug_eq(&run_logical_counts(source, entry)); -} - #[test] fn gates_are_counted() { verify_logical_counts( @@ -430,29 +427,3 @@ fn post_selection_can_take_impossible_branch() { "#]], ); } - -#[test] -fn qmem_store_load_counts_manual_memory_usage() { - let counts = run_logical_counts( - indoc! {r#" - namespace Test { - import Std.ResourceEstimation.*; - - @EntryPoint() - operation Main() : Unit { - use q = Qubit(); - let mem = Std.Memory.Allocate(); - X(q); - Std.Memory.Store(q, mem); - Std.Memory.Load(mem, q); - Std.Memory.Free(mem); - } - } - "#}, - None, - ); - 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)); -} \ No newline at end of file From 2aab86034527f3f442b6785390458040b09d1192 Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 13:54:11 -0700 Subject: [PATCH 07/16] correct resource estimation --- library/src/tests/memory_qubits.rs | 41 ++++++++++- source/compiler/qsc_eval/src/backend.rs | 45 ++++++++++++ source/compiler/qsc_eval/src/lib.rs | 53 +++++++------- source/compiler/qsc_eval/src/val.rs | 11 +-- source/resource_estimator/src/counts.rs | 96 +++++++++++++++++-------- 5 files changed, 181 insertions(+), 65 deletions(-) diff --git a/library/src/tests/memory_qubits.rs b/library/src/tests/memory_qubits.rs index 4b8002d0f7..4cd4001c4f 100644 --- a/library/src/tests/memory_qubits.rs +++ b/library/src/tests/memory_qubits.rs @@ -28,8 +28,10 @@ fn qmem_store_load() { ); } +// Resource estimation tests. + #[test] -fn qmem_store_load_counts_manual_memory_usage() { +fn re_store_load_counts_manual_memory_usage() { let counts = logical_counts_with_lib( "Test.Main()", indoc! {r#" @@ -52,7 +54,44 @@ fn qmem_store_load_counts_manual_memory_usage() { assert_eq!(counts.write_to_memory_count, Some(1)); } + +// This tests checks that MemoryQubits cannot be reused as Qubits. +// Resource estimator must draw them from separate pools. +#[test] +fn re_separate_qubit_pools() { + let counts = logical_counts_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Op1() : Unit { + use q = Qubit(); + let mem = Std.Memory.Allocate(); + H(q); + Std.Memory.Store(q, mem); + Std.Memory.Load(mem, q); + Std.Memory.Free(mem); + } + 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)); +} + // Add a test for different syntaxes. // Add a test for when QMem is released in non-zero state. // Add a test for QMem arrays. // Add a test for not reusing memory as compute in RE. +// Add RE test with uneven load/stores. \ No newline at end of file diff --git a/source/compiler/qsc_eval/src/backend.rs b/source/compiler/qsc_eval/src/backend.rs index 3db62e0e0c..a6f828571c 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 @@ -436,6 +452,35 @@ impl<'a, B: Backend> TracingBackend<'a, B> { OptionalBackend::Some(backend) => backend.qubit_is_zero(q), OptionalBackend::None(_) => true, } + } + + 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( diff --git a/source/compiler/qsc_eval/src/lib.rs b/source/compiler/qsc_eval/src/lib.rs index 7aba44c32a..7a2be9b3f0 100644 --- a/source/compiler/qsc_eval/src/lib.rs +++ b/source/compiler/qsc_eval/src/lib.rs @@ -1325,31 +1325,16 @@ 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" => { + "__quantum__rt__qubit_allocate" | "__quantum__rt__qubit_borrow" | "Allocate" => { self.allocate_qubit(env, sim, &call_stack, name) } - "Allocate" => { - let q_val = self.allocate_qubit(env, sim, &call_stack, name); - sim.custom_intrinsic("__set_memory_qubit", q_val.clone(), &call_stack); - let Value::Qubit(q) = q_val else { - panic!("qubit allocation should return a qubit value") - }; - Value::QMem(q) - } - "Free" => { - let Value::QMem(q) = arg else { - panic!("qubit release should be called with a quantum memory reference") - }; - let q_val = Value::Qubit(q); - self.release_qubit(env, sim, q_val, arg_span, &call_stack)? - } "Store" => { let [qubit, qmem] = val::unwrap_tuple(arg); - let Value::QMem(qmem) = qmem else { - panic!("first argument of Exchange should be a quantum memory reference") - }; let Value::Qubit(qubit) = qubit else { - panic!("second argument of Exchange should be a qubit reference") + panic!("first argument of Store should be a MemoryQubit reference") + }; + let Value::QMem(qmem) = qmem else { + panic!("second argument of Store should be a Qubit reference") }; let qmem_ref = qmem .try_deref() @@ -1357,17 +1342,16 @@ impl State { let qubit_ref = qubit .try_deref() .ok_or(Error::QubitUsedAfterRelease(arg_span))?; - sim.reset(qmem_ref.0, &call_stack); - sim.swap(qubit_ref.0, qmem_ref.0, &call_stack); + sim.memory_qubit_store(qubit_ref.0, qmem_ref.0, &call_stack); Value::unit() } "Load" => { let [qmem, qubit] = val::unwrap_tuple(arg); let Value::QMem(qmem) = qmem else { - panic!("first argument of Exchange should be a quantum memory reference") + panic!("first argument of Load should be a MemoryQubit reference") }; let Value::Qubit(qubit) = qubit else { - panic!("second argument of Exchange should be a qubit reference") + panic!("second argument of Load should be a Qubit reference") }; let qmem_ref = qmem .try_deref() @@ -1375,11 +1359,10 @@ impl State { let qubit_ref = qubit .try_deref() .ok_or(Error::QubitUsedAfterRelease(arg_span))?; - sim.swap(qmem_ref.0, qubit_ref.0, &call_stack); - sim.reset(qmem_ref.0, &call_stack); + sim.memory_qubit_load(qmem_ref.0, qubit_ref.0, &call_stack); Value::unit() } - "__quantum__rt__qubit_release" => { + "__quantum__rt__qubit_release" | "Free" => { self.release_qubit(env, sim, arg, arg_span, &call_stack)? } _ => { @@ -1414,7 +1397,14 @@ impl State { call_stack: &[Frame], name: &Rc, ) -> Value { - let q = sim.qubit_allocate(call_stack); + let is_memory_qubit = name.as_ref() == "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 { @@ -1423,7 +1413,12 @@ impl State { if name.as_ref() == "__quantum__rt__qubit_borrow" { self.dirty_qubits.insert(q.0); } - Value::Qubit(q.into()) + + if is_memory_qubit { + Value::QMem(q.into()) + } else { + Value::Qubit(q.into()) + } } fn release_qubit( diff --git a/source/compiler/qsc_eval/src/val.rs b/source/compiler/qsc_eval/src/val.rs index 3cd9203a3c..0d60fe1637 100644 --- a/source/compiler/qsc_eval/src/val.rs +++ b/source/compiler/qsc_eval/src/val.rs @@ -390,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::QMem`]. #[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::QMem(v) = self { + v + } else { panic!("value should be Qubit, got {}", self.type_name()); - }; - v + } } /// Convert the [Value] into a range tuple diff --git a/source/resource_estimator/src/counts.rs b/source/resource_estimator/src/counts.rs index 9f7afa7910..31ab56ab37 100644 --- a/source/resource_estimator/src/counts.rs +++ b/source/resource_estimator/src/counts.rs @@ -55,11 +55,15 @@ pub struct LogicalCounter { /// This value is used in a measurement, if present, before generating a random result. post_select_measurements: FxHashMap, - // Manual memory management tracking - all_manual_memory_qubits: FxHashSet, - current_memory_qubits: FxHashSet, - manual_memory_writes: usize, - manual_memory_reads: usize, + // 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 single-qubit Store instructions. + memory_qubit_store_count: usize, + /// Number of single-qubit Load instructions. + memory_qubit_load_count: usize, } impl Default for LogicalCounter { @@ -79,11 +83,11 @@ impl Default for LogicalCounter { repeats: vec![], memory_compute: None, rnd: RefCell::new(StdRng::seed_from_u64(0)), - all_manual_memory_qubits: FxHashSet::default(), post_select_measurements: FxHashMap::default(), - current_memory_qubits: FxHashSet::default(), - manual_memory_writes: 0, - manual_memory_reads: 0, + memory_qubit_ids: FxHashSet::default(), + free_memory_qubits: vec![], + memory_qubit_store_count: 0, + memory_qubit_load_count: 0, } } } @@ -91,19 +95,31 @@ 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, + "Cannout 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 { + } 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((self.next_free - self.all_manual_memory_qubits.len()) as u64), - Some(self.manual_memory_reads as u64), - Some(self.manual_memory_writes as u64), + 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) }; LogicalResourceCounts { @@ -492,6 +508,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 { @@ -593,11 +617,6 @@ impl Backend for LogicalCounter { fn swap(&mut self, q0: usize, q1: usize) { self.assert_compute_qubits([q0, q1]); - if self.current_memory_qubits.contains(&q0) { - self.manual_memory_reads += 1; - } else if self.current_memory_qubits.contains(&q1) { - self.manual_memory_writes += 1; - } self.schedule_two_qubit_clifford(q0, q1); } @@ -625,16 +644,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); - self.current_memory_qubits.remove(&q); + if self.memory_qubit_ids.contains(&q) { + self.free_memory_qubits.push(q); + } else { + self.free_list.push(q); + } true } @@ -661,6 +680,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" => { @@ -716,13 +757,6 @@ impl Backend for LogicalCounter { "GlobalPhase" | "ConfigurePauliNoise" | "ConfigureQubitLoss" | "ApplyIdleNoise" => { Some(Ok(Value::unit())) } - "__set_memory_qubit" => { - let q = arg.unwrap_qubit(); - self.all_manual_memory_qubits.insert(q.deref().0); - self.current_memory_qubits.insert(q.deref().0); - - Some(Ok(Value::unit())) - } "PostSelectZ" => { let values = arg.unwrap_tuple(); let [result, qubit] = array::from_fn(|i| values[i].clone()); From e240506533986a65153b420031ce5088032d0d3d Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 14:24:55 -0700 Subject: [PATCH 08/16] define 4 intrinsics --- library/core/qir.qs | 10 +++++- library/std/src/QIR/Intrinsic.qs | 13 ++++++- library/std/src/Std/Memory.qs | 15 ++++---- source/compiler/qsc_eval/src/intrinsic.rs | 10 ++++++ source/compiler/qsc_eval/src/lib.rs | 42 +++-------------------- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/library/core/qir.qs b/library/core/qir.qs index 6f9a57bd0a..1df210ff6d 100644 --- a/library/core/qir.qs +++ b/library/core/qir.qs @@ -46,5 +46,13 @@ 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() : QMem { + body intrinsic; + } + + operation __quantum__rt__memory_qubit_release(q : QMem) : Unit { + body intrinsic; + } + + 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; } diff --git a/library/std/src/QIR/Intrinsic.qs b/library/std/src/QIR/Intrinsic.qs index 6c703e853f..290feed95c 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(qmem : QMem, qubit : Qubit) : Unit { + body intrinsic; +} + +operation __quantum__qis__memory_qubit_store(qubit : Qubit, qmem : QMem) : 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/Memory.qs b/library/std/src/Std/Memory.qs index 1448d211cf..c4f0ad5235 100644 --- a/library/std/src/Std/Memory.qs +++ b/library/std/src/Std/Memory.qs @@ -1,20 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import QIR.Intrinsic.*; +import QIR.Runtime.*; + operation Allocate() : QMem { - body intrinsic; + return __quantum__rt__memory_qubit_allocate(); } operation Free(qmem : QMem) : Unit { - body intrinsic; + __quantum__rt__memory_qubit_release(qmem); } -operation Store(qubit : Qubit, qmem : QMem) : Unit { - body intrinsic; +operation Load(qmem : QMem, qubit : Qubit) : Unit { + __quantum__qis__memory_qubit_load(qmem, qubit); } -operation Load(qmem : QMem, qubit : Qubit) : Unit { - body intrinsic; +operation Store(qubit : Qubit, qmem : QMem) : Unit { + __quantum__qis__memory_qubit_store(qubit, qmem); } export Allocate, Free, Store, Load; 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 7a2be9b3f0..08cac5ff60 100644 --- a/source/compiler/qsc_eval/src/lib.rs +++ b/source/compiler/qsc_eval/src/lib.rs @@ -1325,44 +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" | "Allocate" => { + "__quantum__rt__qubit_allocate" + | "__quantum__rt__qubit_borrow" + | "__quantum__rt__memory_qubit_allocate" => { self.allocate_qubit(env, sim, &call_stack, name) } - "Store" => { - let [qubit, qmem] = val::unwrap_tuple(arg); - let Value::Qubit(qubit) = qubit else { - panic!("first argument of Store should be a MemoryQubit reference") - }; - let Value::QMem(qmem) = qmem else { - panic!("second argument of Store should be a Qubit reference") - }; - let qmem_ref = qmem - .try_deref() - .ok_or(Error::QubitUsedAfterRelease(arg_span))?; - let qubit_ref = qubit - .try_deref() - .ok_or(Error::QubitUsedAfterRelease(arg_span))?; - sim.memory_qubit_store(qubit_ref.0, qmem_ref.0, &call_stack); - Value::unit() - } - "Load" => { - let [qmem, qubit] = val::unwrap_tuple(arg); - let Value::QMem(qmem) = qmem else { - panic!("first argument of Load should be a MemoryQubit reference") - }; - let Value::Qubit(qubit) = qubit else { - panic!("second argument of Load should be a Qubit reference") - }; - let qmem_ref = qmem - .try_deref() - .ok_or(Error::QubitUsedAfterRelease(arg_span))?; - let qubit_ref = qubit - .try_deref() - .ok_or(Error::QubitUsedAfterRelease(arg_span))?; - sim.memory_qubit_load(qmem_ref.0, qubit_ref.0, &call_stack); - Value::unit() - } - "__quantum__rt__qubit_release" | "Free" => { + "__quantum__rt__qubit_release" | "__quantum__rt__memory_qubit_release" => { self.release_qubit(env, sim, arg, arg_span, &call_stack)? } _ => { @@ -1397,7 +1365,7 @@ impl State { call_stack: &[Frame], name: &Rc, ) -> Value { - let is_memory_qubit = name.as_ref() == "Allocate"; + let is_memory_qubit = name.as_ref() == "__quantum__rt__memory_qubit_allocate"; let q = if is_memory_qubit { sim.memory_qubit_allocate(call_stack) From 8548d11586fe2174dc46eff80c238facfe715f67 Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 14:30:16 -0700 Subject: [PATCH 09/16] rename type to MemoryQubit --- library/core/qir.qs | 4 +-- library/src/lib.rs | 4 +-- library/src/tests/memory_qubits.rs | 30 +++++++++---------- library/std/qsharp.json | 2 +- library/std/src/QIR/Intrinsic.qs | 4 +-- library/std/src/Std/Memory.qs | 23 -------------- library/std/src/Std/MemoryQubits.qs | 23 ++++++++++++++ source/compiler/qsc_eval/src/lib.rs | 2 +- source/compiler/qsc_eval/src/val.rs | 14 ++++----- source/compiler/qsc_fir/src/ty.rs | 4 +-- source/compiler/qsc_frontend/src/resolve.rs | 2 +- source/compiler/qsc_hir/src/ty.rs | 2 +- source/compiler/qsc_lowerer/src/lib.rs | 2 +- .../src/evaluation_context.rs | 2 +- source/compiler/qsc_partial_eval/src/lib.rs | 2 +- source/compiler/qsc_rca/src/core.rs | 4 +-- .../src/code_action/wrapper_refactor.rs | 2 +- .../language_service/src/completion/qsharp.rs | 2 +- source/pip/src/interpreter.rs | 2 +- source/pip/src/interpreter/data_interop.rs | 6 ++-- source/vscode/syntaxes/qsharp.tmLanguage.json | 2 +- 21 files changed, 69 insertions(+), 69 deletions(-) delete mode 100644 library/std/src/Std/Memory.qs create mode 100644 library/std/src/Std/MemoryQubits.qs diff --git a/library/core/qir.qs b/library/core/qir.qs index 1df210ff6d..9c419a69e2 100644 --- a/library/core/qir.qs +++ b/library/core/qir.qs @@ -46,11 +46,11 @@ namespace QIR.Runtime { body intrinsic; } - operation __quantum__rt__memory_qubit_allocate() : QMem { + operation __quantum__rt__memory_qubit_allocate() : MemoryQubit { body intrinsic; } - operation __quantum__rt__memory_qubit_release(q : QMem) : Unit { + operation __quantum__rt__memory_qubit_release(q : MemoryQubit) : Unit { body intrinsic; } diff --git a/library/src/lib.rs b/library/src/lib.rs index 665510de52..5cdbcf9326 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -62,8 +62,8 @@ pub const STD_LIB: &[(&str, &str)] = &[ include_str!("../std/src/Std/Measurement.qs"), ), ( - "qsharp-library-source:Std/Memory.qs", - include_str!("../std/src/Std/Memory.qs"), + "qsharp-library-source:Std/MemoryQubits.qs", + include_str!("../std/src/Std/MemoryQubits.qs"), ), ( "qsharp-library-source:QIR/Intrinsic.qs", diff --git a/library/src/tests/memory_qubits.rs b/library/src/tests/memory_qubits.rs index 4cd4001c4f..f3f9aa8e67 100644 --- a/library/src/tests/memory_qubits.rs +++ b/library/src/tests/memory_qubits.rs @@ -8,18 +8,18 @@ use qsc::interpret::Value; // Tests for memory qubits and Std.MemoryQubits namespace. #[test] -fn qmem_store_load() { +fn memory_qubit_store_load() { test_expression_with_lib( "Test.Main()", indoc! {r#" namespace Test { operation Main() : Result { use q = Qubit(); - let mem = Std.Memory.Allocate(); + let mem = Std.MemoryQubits.Allocate(); X(q); - Std.Memory.Store(q, mem); - Std.Memory.Load(mem, q); - Std.Memory.Free(mem); + Std.MemoryQubits.Store(q, mem); + Std.MemoryQubits.Load(mem, q); + Std.MemoryQubits.Free(mem); return MResetZ(q); } } @@ -38,11 +38,11 @@ fn re_store_load_counts_manual_memory_usage() { namespace Test { operation Main() : Unit { use q = Qubit(); - let mem = Std.Memory.Allocate(); + let mem = Std.MemoryQubits.Allocate(); X(q); - Std.Memory.Store(q, mem); - Std.Memory.Load(mem, q); - Std.Memory.Free(mem); + Std.MemoryQubits.Store(q, mem); + Std.MemoryQubits.Load(mem, q); + Std.MemoryQubits.Free(mem); } } "#}, @@ -65,11 +65,11 @@ fn re_separate_qubit_pools() { namespace Test { operation Op1() : Unit { use q = Qubit(); - let mem = Std.Memory.Allocate(); + let mem = Std.MemoryQubits.Allocate(); H(q); - Std.Memory.Store(q, mem); - Std.Memory.Load(mem, q); - Std.Memory.Free(mem); + Std.MemoryQubits.Store(q, mem); + Std.MemoryQubits.Load(mem, q); + Std.MemoryQubits.Free(mem); } operation Op2() : Unit { use qs = Qubit[2]; @@ -91,7 +91,7 @@ fn re_separate_qubit_pools() { } // Add a test for different syntaxes. -// Add a test for when QMem is released in non-zero state. -// Add a test for QMem arrays. +// Add a test for when MemoryQubit is released in non-zero state. +// Add a test for MemoryQubit arrays. // Add a test for not reusing memory as compute in RE. // Add RE test with uneven load/stores. \ No newline at end of file diff --git a/library/std/qsharp.json b/library/std/qsharp.json index 0deeba51b3..8d427fab22 100644 --- a/library/std/qsharp.json +++ b/library/std/qsharp.json @@ -13,7 +13,7 @@ "src/Std/Logical.qs", "src/Std/Math.qs", "src/Std/Measurement.qs", - "src/Std/Memory.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 290feed95c..57cb351933 100644 --- a/library/std/src/QIR/Intrinsic.qs +++ b/library/std/src/QIR/Intrinsic.qs @@ -106,11 +106,11 @@ operation __quantum__qis__mresetz__body(target : Qubit) : Result { } // MemoryQubit operations. -operation __quantum__qis__memory_qubit_load(qmem : QMem, qubit : Qubit) : Unit { +operation __quantum__qis__memory_qubit_load(memory_qubit : MemoryQubit, qubit : Qubit) : Unit { body intrinsic; } -operation __quantum__qis__memory_qubit_store(qubit : Qubit, qmem : QMem) : Unit { +operation __quantum__qis__memory_qubit_store(qubit : Qubit, memory_qubit : MemoryQubit) : Unit { body intrinsic; } diff --git a/library/std/src/Std/Memory.qs b/library/std/src/Std/Memory.qs deleted file mode 100644 index c4f0ad5235..0000000000 --- a/library/std/src/Std/Memory.qs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import QIR.Intrinsic.*; -import QIR.Runtime.*; - -operation Allocate() : QMem { - return __quantum__rt__memory_qubit_allocate(); -} - -operation Free(qmem : QMem) : Unit { - __quantum__rt__memory_qubit_release(qmem); -} - -operation Load(qmem : QMem, qubit : Qubit) : Unit { - __quantum__qis__memory_qubit_load(qmem, qubit); -} - -operation Store(qubit : Qubit, qmem : QMem) : Unit { - __quantum__qis__memory_qubit_store(qubit, qmem); -} - -export Allocate, Free, Store, Load; diff --git a/library/std/src/Std/MemoryQubits.qs b/library/std/src/Std/MemoryQubits.qs new file mode 100644 index 0000000000..cb15083068 --- /dev/null +++ b/library/std/src/Std/MemoryQubits.qs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import QIR.Intrinsic.*; +import QIR.Runtime.*; + +operation Allocate() : MemoryQubit { + return __quantum__rt__memory_qubit_allocate(); +} + +operation Free(memory_qubit : MemoryQubit) : Unit { + __quantum__rt__memory_qubit_release(memory_qubit); +} + +operation Load(memory_qubit : MemoryQubit, qubit : Qubit) : Unit { + __quantum__qis__memory_qubit_load(memory_qubit, qubit); +} + +operation Store(qubit : Qubit, memory_qubit : MemoryQubit) : Unit { + __quantum__qis__memory_qubit_store(qubit, memory_qubit); +} + +export Allocate, Free, Store, Load; diff --git a/source/compiler/qsc_eval/src/lib.rs b/source/compiler/qsc_eval/src/lib.rs index 08cac5ff60..1775966635 100644 --- a/source/compiler/qsc_eval/src/lib.rs +++ b/source/compiler/qsc_eval/src/lib.rs @@ -1383,7 +1383,7 @@ impl State { } if is_memory_qubit { - Value::QMem(q.into()) + Value::MemoryQubit(q.into()) } else { Value::Qubit(q.into()) } diff --git a/source/compiler/qsc_eval/src/val.rs b/source/compiler/qsc_eval/src/val.rs index 0d60fe1637..50ac48e3f8 100644 --- a/source/compiler/qsc_eval/src/val.rs +++ b/source/compiler/qsc_eval/src/val.rs @@ -25,7 +25,7 @@ pub enum Value { Int(i64), Pauli(Pauli), Qubit(QubitRef), - QMem(QubitRef), + MemoryQubit(QubitRef), Range(Box), Result(Result), String(Rc), @@ -197,9 +197,9 @@ impl Display for Value { (v.try_deref() .map_or_else(|| "".to_string(), |v| v.0.to_string())) ), - Value::QMem(v) => write!( + Value::MemoryQubit(v) => write!( f, - "QMem{}", + "MemoryQubit{}", (v.try_deref() .map_or_else(|| "".to_string(), |v| v.0.to_string())) ), @@ -390,12 +390,12 @@ impl Value { /// Convert the [Value] into a qubit /// # Panics - /// This will panic if the [Value] is not a [`Value::Qubit`] or [`Value::QMem`]. + /// This will panic if the [Value] is not a [`Value::Qubit`] or [`Value::MemoryQubit`]. #[must_use] pub fn unwrap_qubit(self) -> QubitRef { if let Value::Qubit(v) = self { v - } else if let Value::QMem(v) = self { + } else if let Value::MemoryQubit(v) = self { v } else { panic!("value should be Qubit, got {}", self.type_name()); @@ -458,7 +458,7 @@ impl Value { Value::Int(_) => "Int", Value::Pauli(_) => "Pauli", Value::Qubit(_) => "Qubit", - Value::QMem(_) => "QMem", + Value::MemoryQubit(_) => "MemoryQubit", Value::Range(..) => "Range", Value::Result(_) => "Result", Value::String(_) => "String", @@ -475,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) | Value::QMem(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 26bee40d46..58c104c5be 100644 --- a/source/compiler/qsc_fir/src/ty.rs +++ b/source/compiler/qsc_fir/src/ty.rs @@ -360,8 +360,8 @@ pub enum Prim { Pauli, /// The qubit type. Qubit, - /// The quantum memory type. - QMem, + /// The memory qubit type. + MemoryQubit, /// The range type. Range, /// The range type without a lower bound. diff --git a/source/compiler/qsc_frontend/src/resolve.rs b/source/compiler/qsc_frontend/src/resolve.rs index 860e85b48a..34b72afbbf 100644 --- a/source/compiler/qsc_frontend/src/resolve.rs +++ b/source/compiler/qsc_frontend/src/resolve.rs @@ -1204,7 +1204,7 @@ impl GlobalTable { ("Int".into(), Res::PrimTy(Prim::Int)), ("Pauli".into(), Res::PrimTy(Prim::Pauli)), ("Qubit".into(), Res::PrimTy(Prim::Qubit)), - ("QMem".into(), Res::PrimTy(Prim::QMem)), + ("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_hir/src/ty.rs b/source/compiler/qsc_hir/src/ty.rs index e77cbcf65c..bbdad5655d 100644 --- a/source/compiler/qsc_hir/src/ty.rs +++ b/source/compiler/qsc_hir/src/ty.rs @@ -519,7 +519,7 @@ pub enum Prim { /// The qubit type. Qubit, /// The quantum memory type. - QMem, + MemoryQubit, /// The range type. Range, /// The range type without a lower bound. diff --git a/source/compiler/qsc_lowerer/src/lib.rs b/source/compiler/qsc_lowerer/src/lib.rs index cdfc9854ff..87fde11ee6 100644 --- a/source/compiler/qsc_lowerer/src/lib.rs +++ b/source/compiler/qsc_lowerer/src/lib.rs @@ -1095,7 +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::QMem => qsc_fir::ty::Prim::QMem, + 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_partial_eval/src/evaluation_context.rs b/source/compiler/qsc_partial_eval/src/evaluation_context.rs index 77b03defdd..630a123432 100644 --- a/source/compiler/qsc_partial_eval/src/evaluation_context.rs +++ b/source/compiler/qsc_partial_eval/src/evaluation_context.rs @@ -326,7 +326,7 @@ fn map_eval_value_to_value_kind(value: &Value) -> ValueKind { | Value::Int(_) | Value::Pauli(_) | Value::Qubit(_) - | Value::QMem(_) + | 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 edf60b1893..c428b2a385 100644 --- a/source/compiler/qsc_partial_eval/src/lib.rs +++ b/source/compiler/qsc_partial_eval/src/lib.rs @@ -3440,7 +3440,7 @@ impl<'a> PartialEvaluator<'a> { | Value::Global(_, _) | Value::Pauli(_) | Value::Qubit(_) - | Value::QMem(_) + | Value::MemoryQubit(_) | Value::Range(_) | Value::String(_) => panic!("unsupported value type in output recording"), } diff --git a/source/compiler/qsc_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index 2bdd238e29..ebef4640ba 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -2401,7 +2401,7 @@ fn ty_prim_to_runtime_output_flag(prim: Prim) -> RuntimeFeatureFlags { Prim::BigInt | Prim::Pauli | Prim::Qubit - | Prim::QMem + | Prim::MemoryQubit | Prim::Range | Prim::RangeFrom | Prim::RangeTo @@ -2460,7 +2460,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 | Prim::QMem => 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 d107424ccc..2ea10cb247 100644 --- a/source/language_service/src/code_action/wrapper_refactor.rs +++ b/source/language_service/src/code_action/wrapper_refactor.rs @@ -276,7 +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::QMem => (None, "Quantum memory - allocate via 'store'".to_string()), + Prim::MemoryQubit => (None, "Quantum memory - allocate via 'store'".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 91eb5fac64..73fa288e97 100644 --- a/source/language_service/src/completion/qsharp.rs +++ b/source/language_service/src/completion/qsharp.rs @@ -280,7 +280,7 @@ fn collect_paths( locals_and_builtins.push(locals_at_cursor.type_names()); locals_and_builtins.push( [ - "Qubit", "QMem", "Int", "Unit", "Result", "Bool", "BigInt", "Double", "Pauli", + "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 bb6f9de592..981fbb0e15 100644 --- a/source/pip/src/interpreter.rs +++ b/source/pip/src/interpreter.rs @@ -1070,7 +1070,7 @@ where | Prim::String | Prim::Result => None, Prim::Qubit - | Prim::QMem + | Prim::MemoryQubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom diff --git a/source/pip/src/interpreter/data_interop.rs b/source/pip/src/interpreter/data_interop.rs index 2d17c49a33..ee2eddcbcd 100644 --- a/source/pip/src/interpreter/data_interop.rs +++ b/source/pip/src/interpreter/data_interop.rs @@ -233,7 +233,7 @@ pub(super) fn pyobj_to_value( 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::QMem + | Prim::MemoryQubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom @@ -340,7 +340,7 @@ pub(super) fn type_ir_from_qsharp_ty(ctx: &interpret::Interpreter, ty: &Ty) -> O Prim::Pauli => PrimitiveKind::Pauli, Prim::Result => PrimitiveKind::Result, Prim::Qubit - | Prim::QMem + | Prim::MemoryQubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom @@ -480,7 +480,7 @@ pub(crate) fn value_to_pyobj( let closure: Closure = value.clone().into(); closure.into_py_any(py) } - Value::Qubit(..) | Value::QMem(..) | 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/vscode/syntaxes/qsharp.tmLanguage.json b/source/vscode/syntaxes/qsharp.tmLanguage.json index af9b3d7c38..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|QMem|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" } ] }, From 3674b45f30baeaac18cf68b0a3660981f9142643 Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 15:21:52 -0700 Subject: [PATCH 10/16] Support use syntax and auto-freeing --- library/core/qir.qs | 19 +- library/src/tests/memory_qubits.rs | 80 ++++++-- library/std/src/Std/MemoryQubits.qs | 10 +- source/compiler/qsc_ast/src/ast.rs | 10 + source/compiler/qsc_ast/src/mut_visit.rs | 4 +- source/compiler/qsc_ast/src/visit.rs | 4 +- source/compiler/qsc_codegen/src/qsharp.rs | 8 + source/compiler/qsc_eval/src/backend.rs | 34 +++- source/compiler/qsc_frontend/src/lower.rs | 4 + .../compiler/qsc_frontend/src/typeck/rules.rs | 9 + source/compiler/qsc_hir/src/hir.rs | 10 + source/compiler/qsc_hir/src/mut_visit.rs | 4 +- source/compiler/qsc_hir/src/visit.rs | 4 +- source/compiler/qsc_parse/src/stmt.rs | 36 ++-- source/compiler/qsc_parse/src/stmt/tests.rs | 29 ++- .../src/replace_qubit_allocation.rs | 175 ++++++++++++++---- .../language_service/src/completion/qsharp.rs | 13 +- 17 files changed, 369 insertions(+), 84 deletions(-) diff --git a/library/core/qir.qs b/library/core/qir.qs index 9c419a69e2..4fbd077aaa 100644 --- a/library/core/qir.qs +++ b/library/core/qir.qs @@ -54,5 +54,22 @@ 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, __quantum__rt__memory_qubit_allocate, __quantum__rt__memory_qubit_release; + 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/tests/memory_qubits.rs b/library/src/tests/memory_qubits.rs index f3f9aa8e67..b1bddb3f28 100644 --- a/library/src/tests/memory_qubits.rs +++ b/library/src/tests/memory_qubits.rs @@ -1,12 +1,75 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use super::{logical_counts_with_lib, test_expression_with_lib}; +use super::{logical_counts_with_lib, test_expression_fails, test_expression_with_lib}; use indoc::indoc; use qsc::interpret::Value; // Tests for memory qubits and Std.MemoryQubits namespace. +#[test] +fn memory_qubit_store_load_array_syntax() { + 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.Store(qs[0], mems[0]); + Std.MemoryQubits.Load(mems[0], qs[1]); + return [MResetZ(qs[0]), MResetZ(qs[1])]; + } + } + "#}, + &Value::Array(vec![Value::RESULT_ZERO, Value::RESULT_ONE].into()), + ); +} + +#[test] +fn memory_qubit_store_load_tuple_syntax() { + test_expression_with_lib( + "Test.Main()", + indoc! {r#" + namespace Test { + operation Main() : Result[] { + use qs = Qubit[3]; + use (m1, m2, m3) = (MemoryQubit(), MemoryQubit(), MemoryQubit()); + X(qs[0]); + X(qs[2]); + Std.MemoryQubits.Store(qs[0], m1); + Std.MemoryQubits.Store(qs[1], m2); + Std.MemoryQubits.Store(qs[2], m3); + Std.MemoryQubits.Load(m1, qs[0]); + Std.MemoryQubits.Load(m2, qs[1]); + Std.MemoryQubits.Load(m3, qs[2]); + return [MResetZ(qs[0]), MResetZ(qs[1]), MResetZ(qs[2])]; + } + } + "#}, + &Value::Array(vec![Value::RESULT_ONE, Value::RESULT_ZERO, Value::RESULT_ONE].into()), + ); +} + +#[test] +fn memory_qubit_release_non_zero_fails() { + let err = test_expression_fails(indoc! {r#" + { + use q = Qubit(); + use m = MemoryQubit(); + X(q); + Std.MemoryQubits.Store(q, m); + () + } + "#}); + + assert!( + err.contains("released while not in |0"), + "expected non-zero release error, got: {err}" + ); +} + #[test] fn memory_qubit_store_load() { test_expression_with_lib( @@ -14,12 +77,10 @@ fn memory_qubit_store_load() { indoc! {r#" namespace Test { operation Main() : Result { - use q = Qubit(); - let mem = Std.MemoryQubits.Allocate(); + use (q, mem) = (Qubit(), MemoryQubit()); X(q); Std.MemoryQubits.Store(q, mem); Std.MemoryQubits.Load(mem, q); - Std.MemoryQubits.Free(mem); return MResetZ(q); } } @@ -38,11 +99,10 @@ fn re_store_load_counts_manual_memory_usage() { namespace Test { operation Main() : Unit { use q = Qubit(); - let mem = Std.MemoryQubits.Allocate(); + use mem = MemoryQubit(); X(q); Std.MemoryQubits.Store(q, mem); Std.MemoryQubits.Load(mem, q); - Std.MemoryQubits.Free(mem); } } "#}, @@ -54,7 +114,6 @@ fn re_store_load_counts_manual_memory_usage() { assert_eq!(counts.write_to_memory_count, Some(1)); } - // This tests checks that MemoryQubits cannot be reused as Qubits. // Resource estimator must draw them from separate pools. #[test] @@ -65,11 +124,10 @@ fn re_separate_qubit_pools() { namespace Test { operation Op1() : Unit { use q = Qubit(); - let mem = Std.MemoryQubits.Allocate(); + use mem = MemoryQubit(); H(q); Std.MemoryQubits.Store(q, mem); Std.MemoryQubits.Load(mem, q); - Std.MemoryQubits.Free(mem); } operation Op2() : Unit { use qs = Qubit[2]; @@ -84,7 +142,7 @@ fn re_separate_qubit_pools() { "#}, ); - // Maximum allocated qubits at any point is 2, but we need 1 memory qubit and + // 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)); @@ -94,4 +152,4 @@ fn re_separate_qubit_pools() { // Add a test for when MemoryQubit is released in non-zero state. // Add a test for MemoryQubit arrays. // Add a test for not reusing memory as compute in RE. -// Add RE test with uneven load/stores. \ No newline at end of file +// Add RE test with uneven load/stores. diff --git a/library/std/src/Std/MemoryQubits.qs b/library/std/src/Std/MemoryQubits.qs index cb15083068..9648b0fba1 100644 --- a/library/std/src/Std/MemoryQubits.qs +++ b/library/std/src/Std/MemoryQubits.qs @@ -4,14 +4,6 @@ import QIR.Intrinsic.*; import QIR.Runtime.*; -operation Allocate() : MemoryQubit { - return __quantum__rt__memory_qubit_allocate(); -} - -operation Free(memory_qubit : MemoryQubit) : Unit { - __quantum__rt__memory_qubit_release(memory_qubit); -} - operation Load(memory_qubit : MemoryQubit, qubit : Qubit) : Unit { __quantum__qis__memory_qubit_load(memory_qubit, qubit); } @@ -20,4 +12,4 @@ operation Store(qubit : Qubit, memory_qubit : MemoryQubit) : Unit { __quantum__qis__memory_qubit_store(qubit, memory_qubit); } -export Allocate, Free, Store, Load; +export Store, Load; 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 a6f828571c..f149439bad 100644 --- a/source/compiler/qsc_eval/src/backend.rs +++ b/source/compiler/qsc_eval/src/backend.rs @@ -452,7 +452,7 @@ impl<'a, B: Backend> TracingBackend<'a, B> { OptionalBackend::Some(backend) => backend.qubit_is_zero(q), OptionalBackend::None(_) => true, } - } + } pub fn memory_qubit_allocate(&mut self, stack: &[Frame]) -> usize { let q = match &mut self.backend { @@ -465,21 +465,45 @@ impl<'a, B: Backend> TracingBackend<'a, B> { q } - pub fn memory_qubit_load(&mut self, mem_qubit_id: usize, comp_qubit_id: usize, stack: &[Frame]) { + 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); + 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]) { + 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); + tracer.gate( + stack, + "Store", + false, + &[comp_qubit_id, mem_qubit_id], + &[], + None, + ); } } 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/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/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_parse/src/stmt.rs b/source/compiler/qsc_parse/src/stmt.rs index 974eb7d5fb..edbcffd9d1 100644 --- a/source/compiler/qsc_parse/src/stmt.rs +++ b/source/compiler/qsc_parse/src/stmt.rs @@ -154,24 +154,38 @@ fn parse_qubit(s: &mut ParserContext) -> Result> { } fn parse_qubit_init(s: &mut ParserContext) -> Result> { - let help_text = "to allocate qubits, use syntax like `use q = Qubit();` or `use qs = Qubit[N];` or `use (q1, q2) = (Qubit(), Qubit());`"; + let help_text = "to allocate qubits, use syntax like `use q = Qubit();`, `use m = MemoryQubit();`, `use qs = Qubit[N];`, `use ms = MemoryQubit[N];`, or `use (q1, q2) = (Qubit(), Qubit());`"; 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( diff --git a/source/compiler/qsc_parse/src/stmt/tests.rs b/source/compiler/qsc_parse/src/stmt/tests.rs index 51d29981fc..53ea30b640 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( @@ -145,7 +172,7 @@ fn use_invalid_init() { }, ), Some( - "to allocate qubits, use syntax like `use q = Qubit();` or `use qs = Qubit[N];` or `use (q1, q2) = (Qubit(), Qubit());`", + "to allocate qubits, use syntax like `use q = Qubit();`, `use m = MemoryQubit();`, `use qs = Qubit[N];`, `use ms = MemoryQubit[N];`, or `use (q1, q2) = (Qubit(), Qubit());`", ), ) "#]], diff --git a/source/compiler/qsc_passes/src/replace_qubit_allocation.rs b/source/compiler/qsc_passes/src/replace_qubit_allocation.rs index 42d53519dc..68fa0c02c8 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,31 @@ 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", + (_, true) => "__quantum__rt__memory_qubit_allocate", }; 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, + if is_memory { + Prim::MemoryQubit + } else { + Prim::Qubit + }, + ) } fn create_array_alloc_stmt( @@ -259,23 +294,39 @@ 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", + (_, true) => "AllocateMemoryQubitArray", }; 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), + if is_memory { + Prim::MemoryQubit + } else { + Prim::Qubit + }, + ) } - 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 +334,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 +506,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 +535,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 +586,11 @@ fn create_general_alloc_stmt( ident: &IdentTemplate, call_expr: Expr, array_size: Option, + prim: Prim, ) -> Stmt { 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 +600,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/language_service/src/completion/qsharp.rs b/source/language_service/src/completion/qsharp.rs index 73fa288e97..d78795368b 100644 --- a/source/language_service/src/completion/qsharp.rs +++ b/source/language_service/src/completion/qsharp.rs @@ -280,8 +280,17 @@ fn collect_paths( locals_and_builtins.push(locals_at_cursor.type_names()); locals_and_builtins.push( [ - "Qubit", "MemoryQubit", "Int", "Unit", "Result", "Bool", "BigInt", "Double", "Pauli", - "Range", "String", + "Qubit", + "MemoryQubit", + "Int", + "Unit", + "Result", + "Bool", + "BigInt", + "Double", + "Pauli", + "Range", + "String", ] .map(|s| Completion::new(s.to_string(), CompletionItemKind::Interface)) .into(), From 8bd906e055017c64dec0dfdc20d735cf3d663a54 Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 16:34:51 -0700 Subject: [PATCH 11/16] Add tests and dtandard library helpers --- library/src/tests.rs | 36 +++++- library/src/tests/memory_qubits.rs | 171 ++++++++++++++++++++-------- library/std/src/Std/MemoryQubits.qs | 27 ++++- 3 files changed, 185 insertions(+), 49 deletions(-) diff --git a/library/src/tests.rs b/library/src/tests.rs index 75d263d559..10d19bfee4 100644 --- a/library/src/tests.rs +++ b/library/src/tests.rs @@ -44,6 +44,40 @@ pub fn test_expression_fails(expr: &str) -> String { ) } +/// Asserts that given Q# expression fails to compile, returns compilation error. +pub fn test_expression_compile_fails(expr: &str) -> String { + test_expression_compile_fails_with_lib_and_profile(expr, "", Profile::Unrestricted) +} + +/// Asserts that given Q# expression fails to compile, returns compilation error. +pub fn test_expression_compile_fails_with_lib_and_profile( + expr: &str, + lib: &str, + profile: Profile, +) -> String { + let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into())); + + let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); + + let compile_errors = match Interpreter::new( + sources, + PackageType::Exe, + profile.into(), + LanguageFeatures::default(), + store, + &[(std_id, None)], + ) { + Ok(_) => panic!("test should fail to compile"), + Err(errors) => errors, + }; + + compile_errors + .iter() + .map(ToString::to_string) + .collect::>() + .join("\n") +} + pub fn test_expression_with_lib(expr: &str, lib: &str, expected: &Value) -> String { test_expression_with_lib_and_profile(expr, lib, Profile::Unrestricted, expected) } @@ -293,7 +327,7 @@ pub fn logical_counts_with_lib(expr: &str, lib: &str) -> LogicalResourceCounts { fn panic_with_resource_estimation_errors(errs: &[ResourceEstimatorError]) -> ! { let joined = errs .iter() - .map(ToString::to_string) + .map(|e| format!("{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 index b1bddb3f28..bad0740c92 100644 --- a/library/src/tests/memory_qubits.rs +++ b/library/src/tests/memory_qubits.rs @@ -1,98 +1,117 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use super::{logical_counts_with_lib, test_expression_fails, test_expression_with_lib}; +use super::{ + logical_counts_with_lib, test_expression_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 memory_qubit_store_load_array_syntax() { +fn check_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.Store(qs[0], mems[0]); - Std.MemoryQubits.Load(mems[0], qs[1]); - return [MResetZ(qs[0]), MResetZ(qs[1])]; + operation Main() : Result { + use (q, mem) = (Qubit(), MemoryQubit()); + X(q); + Std.MemoryQubits.Store(q, mem); + Std.MemoryQubits.Load(mem, q); + return MResetZ(q); } } "#}, - &Value::Array(vec![Value::RESULT_ZERO, Value::RESULT_ONE].into()), + &Value::RESULT_ONE, ); } #[test] -fn memory_qubit_store_load_tuple_syntax() { +fn check_array_store_load() { test_expression_with_lib( "Test.Main()", indoc! {r#" namespace Test { operation Main() : Result[] { - use qs = Qubit[3]; - use (m1, m2, m3) = (MemoryQubit(), MemoryQubit(), MemoryQubit()); + use qs = Qubit[2]; + use mems = MemoryQubit[2]; X(qs[0]); - X(qs[2]); - Std.MemoryQubits.Store(qs[0], m1); - Std.MemoryQubits.Store(qs[1], m2); - Std.MemoryQubits.Store(qs[2], m3); - Std.MemoryQubits.Load(m1, qs[0]); - Std.MemoryQubits.Load(m2, qs[1]); - Std.MemoryQubits.Load(m3, qs[2]); - return [MResetZ(qs[0]), MResetZ(qs[1]), MResetZ(qs[2])]; + 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, Value::RESULT_ONE].into()), + &Value::Array(vec![Value::RESULT_ONE, Value::RESULT_ZERO].into()), ); } #[test] -fn memory_qubit_release_non_zero_fails() { +fn check_cannot_apply_gate_to_memory_qubit() { + let err = test_expression_compile_fails(indoc! {r#" + { + use q = MemoryQubit(); + X(q); + } + "#}); + + assert!(err.contains("type error")); +} + +#[test] +fn check_cannot_measure_memory_qubit() { + let err = test_expression_compile_fails(indoc! {r#" + { + use q = MemoryQubit(); + M(q); + } + "#}); + + assert!(err.contains("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 = Qubit(); - use m = MemoryQubit(); + use (q, m) = (Qubit(), MemoryQubit()); X(q); Std.MemoryQubits.Store(q, m); - () + Reset(q); } "#}); - assert!( - err.contains("released while not in |0"), - "expected non-zero release error, got: {err}" - ); + assert!(err.contains("released while not in |0")); } +// Check that after the computation, all qubits are released in 0 state. #[test] -fn memory_qubit_store_load() { +fn check_do_computation_with_qft() { 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); + 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::RESULT_ONE, + &Value::Bool(true), ); } // Resource estimation tests. #[test] -fn re_store_load_counts_manual_memory_usage() { +fn check_resource_estimation_single_qubit() { let counts = logical_counts_with_lib( "Test.Main()", indoc! {r#" @@ -114,10 +133,74 @@ fn re_store_load_counts_manual_memory_usage() { assert_eq!(counts.write_to_memory_count, Some(1)); } -// This tests checks that MemoryQubits cannot be reused as Qubits. +// 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 re_separate_qubit_pools() { +fn check_resource_estimation_separate_qubit_pools() { let counts = logical_counts_with_lib( "Test.Main()", indoc! {r#" @@ -147,9 +230,3 @@ fn re_separate_qubit_pools() { assert_eq!(counts.num_qubits, 3); assert_eq!(counts.num_compute_qubits, Some(2)); } - -// Add a test for different syntaxes. -// Add a test for when MemoryQubit is released in non-zero state. -// Add a test for MemoryQubit arrays. -// Add a test for not reusing memory as compute in RE. -// Add RE test with uneven load/stores. diff --git a/library/std/src/Std/MemoryQubits.qs b/library/std/src/Std/MemoryQubits.qs index 9648b0fba1..921277ada9 100644 --- a/library/std/src/Std/MemoryQubits.qs +++ b/library/std/src/Std/MemoryQubits.qs @@ -12,4 +12,29 @@ operation Store(qubit : Qubit, memory_qubit : MemoryQubit) : Unit { __quantum__qis__memory_qubit_store(qubit, memory_qubit); } -export Store, Load; +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]); + } +} + +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]); + } +} + +/// Performs computation on a MemoryQubit register by loading it to a temporary Qubit +/// register, performing the operation `op` and storing result back to `mem_qs`. +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; From 832d99b4808f6cbc297d03dd88b112c73a2eb249 Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 16:55:44 -0700 Subject: [PATCH 12/16] docs --- library/std/src/Std/MemoryQubits.qs | 75 ++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/library/std/src/Std/MemoryQubits.qs b/library/std/src/Std/MemoryQubits.qs index 921277ada9..b95075f53f 100644 --- a/library/std/src/Std/MemoryQubits.qs +++ b/library/std/src/Std/MemoryQubits.qs @@ -4,14 +4,64 @@ 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."); } @@ -20,6 +70,17 @@ operation LoadArray(source : MemoryQubit[], target : Qubit[]) : Unit { } } +/// # 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."); } @@ -28,8 +89,18 @@ operation StoreArray(source : Qubit[], target : MemoryQubit[]) : Unit { } } -/// Performs computation on a MemoryQubit register by loading it to a temporary Qubit -/// register, performing the operation `op` and storing result back to `mem_qs`. +/// # 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); From 8b39cfcf5c5d4116ac71804bee0b0fd8d0707c8f Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 18:13:01 -0700 Subject: [PATCH 13/16] small fixes --- library/src/tests.rs | 26 ++++++------- library/src/tests/memory_qubits.rs | 39 ++++++++++--------- source/compiler/qsc_eval/src/lib.rs | 35 ++++++----------- source/compiler/qsc_hir/src/ty.rs | 2 +- source/compiler/qsc_parse/src/stmt.rs | 17 +++++++- .../src/replace_qubit_allocation.rs | 16 +++++--- .../src/code_action/wrapper_refactor.rs | 2 +- source/resource_estimator/src/counts.rs | 6 +-- 8 files changed, 76 insertions(+), 67 deletions(-) diff --git a/library/src/tests.rs b/library/src/tests.rs index 10d19bfee4..c175a19b31 100644 --- a/library/src/tests.rs +++ b/library/src/tests.rs @@ -44,17 +44,9 @@ pub fn test_expression_fails(expr: &str) -> String { ) } -/// Asserts that given Q# expression fails to compile, returns compilation error. -pub fn test_expression_compile_fails(expr: &str) -> String { - test_expression_compile_fails_with_lib_and_profile(expr, "", Profile::Unrestricted) -} - -/// Asserts that given Q# expression fails to compile, returns compilation error. -pub fn test_expression_compile_fails_with_lib_and_profile( - expr: &str, - lib: &str, - profile: Profile, -) -> 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()); @@ -71,11 +63,15 @@ pub fn test_expression_compile_fails_with_lib_and_profile( Err(errors) => errors, }; - compile_errors + let error = compile_errors .iter() - .map(ToString::to_string) + .map(|e| format!("{e}:{e:?}")) .collect::>() - .join("\n") + .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 { @@ -327,7 +323,7 @@ pub fn logical_counts_with_lib(expr: &str, lib: &str) -> LogicalResourceCounts { fn panic_with_resource_estimation_errors(errs: &[ResourceEstimatorError]) -> ! { let joined = errs .iter() - .map(|e| format!("{e:?}")) + .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 index bad0740c92..a6471b49f4 100644 --- a/library/src/tests/memory_qubits.rs +++ b/library/src/tests/memory_qubits.rs @@ -2,8 +2,7 @@ // Licensed under the MIT License. use super::{ - logical_counts_with_lib, test_expression_compile_fails, test_expression_fails, - test_expression_with_lib, + logical_counts_with_lib, test_compile_fails, test_expression_fails, test_expression_with_lib, }; use indoc::indoc; use qsc::interpret::Value; @@ -51,26 +50,12 @@ fn check_array_store_load() { #[test] fn check_cannot_apply_gate_to_memory_qubit() { - let err = test_expression_compile_fails(indoc! {r#" - { - use q = MemoryQubit(); - X(q); - } - "#}); - - assert!(err.contains("type error")); + test_compile_fails("{ use q = MemoryQubit(); X(q); }", "", "type error"); } #[test] fn check_cannot_measure_memory_qubit() { - let err = test_expression_compile_fails(indoc! {r#" - { - use q = MemoryQubit(); - M(q); - } - "#}); - - assert!(err.contains("type error")); + test_compile_fails("{ use q = MemoryQubit(); M(q); }", "", "type error"); } // MemoryQubit cannot be released in non-zero state (same as Qubit). @@ -108,6 +93,24 @@ fn check_do_computation_with_qft() { ); } +#[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] diff --git a/source/compiler/qsc_eval/src/lib.rs b/source/compiler/qsc_eval/src/lib.rs index 1775966635..c6dcdb17d2 100644 --- a/source/compiler/qsc_eval/src/lib.rs +++ b/source/compiler/qsc_eval/src/lib.rs @@ -1331,7 +1331,18 @@ impl State { self.allocate_qubit(env, sim, &call_stack, name) } "__quantum__rt__qubit_release" | "__quantum__rt__memory_qubit_release" => { - self.release_qubit(env, sim, arg, arg_span, &call_stack)? + let qubit = arg + .unwrap_qubit() + .try_deref() + .ok_or(Error::QubitDoubleRelease(arg_span))?; + env.release_qubit(&qubit); + let is_zero = sim.qubit_release(qubit.0, &call_stack); + let is_borrowed = self.dirty_qubits.remove(&qubit.0); + if is_zero || is_borrowed { + Value::unit() + } else { + return Err(Error::ReleasedQubitNotZero(qubit.0, arg_span)); + } } _ => { let val = intrinsic::call( @@ -1389,28 +1400,6 @@ impl State { } } - fn release_qubit( - &mut self, - env: &mut Env, - sim: &mut TracingBackend<'_, B>, - arg: Value, - arg_span: PackageSpan, - call_stack: &[Frame], - ) -> Result { - let qubit = arg - .unwrap_qubit() - .try_deref() - .ok_or(Error::QubitDoubleRelease(arg_span))?; - env.release_qubit(&qubit); - let is_zero = sim.qubit_release(qubit.0, call_stack); - let is_borrowed = self.dirty_qubits.remove(&qubit.0); - Ok(if is_zero || is_borrowed { - Value::unit() - } else { - return Err(Error::ReleasedQubitNotZero(qubit.0, arg_span)); - }) - } - fn eval_field(&mut self, field: Field) { let record = self.take_val_register(); let val = match (record, field) { diff --git a/source/compiler/qsc_hir/src/ty.rs b/source/compiler/qsc_hir/src/ty.rs index bbdad5655d..896cd83dfe 100644 --- a/source/compiler/qsc_hir/src/ty.rs +++ b/source/compiler/qsc_hir/src/ty.rs @@ -518,7 +518,7 @@ pub enum Prim { Pauli, /// The qubit type. Qubit, - /// The quantum memory type. + /// The memory qubit type. MemoryQubit, /// The range type. Range, diff --git a/source/compiler/qsc_parse/src/stmt.rs b/source/compiler/qsc_parse/src/stmt.rs index edbcffd9d1..9580102385 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 { @@ -154,7 +160,7 @@ fn parse_qubit(s: &mut ParserContext) -> Result> { } fn parse_qubit_init(s: &mut ParserContext) -> Result> { - let help_text = "to allocate qubits, use syntax like `use q = Qubit();`, `use m = MemoryQubit();`, `use qs = Qubit[N];`, `use ms = MemoryQubit[N];`, or `use (q1, q2) = (Qubit(), Qubit());`"; + let help_text = "to allocate qubits, use syntax like `use q = Qubit();`, `use qs = Qubit[N];`, `use m = MemoryQubit();`, or `use (q1, q2) = (Qubit(), Qubit());`"; let lo = s.peek().span.lo; s.expect(WordKinds::Qubit); let kind = if let Ok(name) = ident(s) { @@ -212,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_passes/src/replace_qubit_allocation.rs b/source/compiler/qsc_passes/src/replace_qubit_allocation.rs index 68fa0c02c8..ebdb468a52 100644 --- a/source/compiler/qsc_passes/src/replace_qubit_allocation.rs +++ b/source/compiler/qsc_passes/src/replace_qubit_allocation.rs @@ -61,7 +61,7 @@ impl<'a> ReplaceQubitAllocation<'a> { QubitInitKind::MemoryArray(e) => Some((true, Some(take(e)))), QubitInitKind::MemorySingle => Some((true, None)), QubitInitKind::Tuple(_) => None, - QubitInitKind::Err => panic!("QubitInitKind::Err"), + QubitInitKind::Err => unreachable!("QubitInitKind::Err should have been caught by parser"), } } @@ -204,7 +204,7 @@ impl<'a> ReplaceQubitAllocation<'a> { }; (tuple_expr, ids) } - QubitInitKind::Err => panic!("QubitInitKind::Err"), + QubitInitKind::Err => unreachable!("QubitInitKind::Err should have been caught by parser"), } } @@ -272,7 +272,10 @@ impl<'a> ReplaceQubitAllocation<'a> { let api = match (qubit_source, is_memory) { (QubitSource::Fresh, false) => "__quantum__rt__qubit_allocate", (QubitSource::Dirty, false) => "__quantum__rt__qubit_borrow", - (_, true) => "__quantum__rt__memory_qubit_allocate", + (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(); @@ -300,7 +303,10 @@ impl<'a> ReplaceQubitAllocation<'a> { let api = match (qubit_source, is_memory) { (QubitSource::Fresh, false) => "AllocateQubitArray", (QubitSource::Dirty, false) => "BorrowQubitArray", - (_, true) => "AllocateMemoryQubitArray", + (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(); @@ -570,7 +576,7 @@ fn create_qubit_global_alloc( .collect(), ), }, - QubitInitKind::Err => panic!("QubitInitKind::Err"), + QubitInitKind::Err => unreachable!("QubitInitKind::Err should have been caught by parser"), } } diff --git a/source/language_service/src/code_action/wrapper_refactor.rs b/source/language_service/src/code_action/wrapper_refactor.rs index 2ea10cb247..594e5db1a0 100644 --- a/source/language_service/src/code_action/wrapper_refactor.rs +++ b/source/language_service/src/code_action/wrapper_refactor.rs @@ -276,7 +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, "Quantum memory - allocate via 'store'".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/resource_estimator/src/counts.rs b/source/resource_estimator/src/counts.rs index 31ab56ab37..257880d705 100644 --- a/source/resource_estimator/src/counts.rs +++ b/source/resource_estimator/src/counts.rs @@ -60,9 +60,9 @@ pub struct LogicalCounter { memory_qubit_ids: FxHashSet, /// Ids of free memory qubits. Can be reused only for allocating memory qubits. free_memory_qubits: Vec, - /// Number of single-qubit Store instructions. + /// Number of Store instructions. memory_qubit_store_count: usize, - /// Number of single-qubit Load instructions. + /// Number of Load instructions. memory_qubit_load_count: usize, } @@ -100,7 +100,7 @@ impl LogicalCounter { if let Some(memory_compute) = &self.memory_compute { assert!( !uses_memory_qubits, - "Cannout use MemoryQubits together with MemoryComputeArchitecture" + "Cannot use MemoryQubits together with MemoryComputeArchitecture" ); ( Some(memory_compute.compute_size() as u64), From f8862559d99a3bb1ee473c60eb49ab3a547b7650 Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 18:19:34 -0700 Subject: [PATCH 14/16] clippy --- library/src/tests.rs | 7 +++---- source/compiler/qsc_parse/src/stmt.rs | 2 +- source/compiler/qsc_passes/src/replace_qubit_allocation.rs | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/library/src/tests.rs b/library/src/tests.rs index c175a19b31..7b16d82d48 100644 --- a/library/src/tests.rs +++ b/library/src/tests.rs @@ -51,16 +51,15 @@ pub fn test_compile_fails(expr: &str, lib: &str, expected_error_substring: &str) let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); - let compile_errors = match Interpreter::new( + let Err(compile_errors) = Interpreter::new( sources, PackageType::Exe, profile.into(), LanguageFeatures::default(), store, &[(std_id, None)], - ) { - Ok(_) => panic!("test should fail to compile"), - Err(errors) => errors, + ) else { + panic!("test should fail to compile") }; let error = compile_errors diff --git a/source/compiler/qsc_parse/src/stmt.rs b/source/compiler/qsc_parse/src/stmt.rs index 9580102385..10eacdb5fd 100644 --- a/source/compiler/qsc_parse/src/stmt.rs +++ b/source/compiler/qsc_parse/src/stmt.rs @@ -160,7 +160,7 @@ fn parse_qubit(s: &mut ParserContext) -> Result> { } fn parse_qubit_init(s: &mut ParserContext) -> Result> { - let help_text = "to allocate qubits, use syntax like `use q = Qubit();`, `use qs = Qubit[N];`, `use m = MemoryQubit();`, or `use (q1, q2) = (Qubit(), Qubit());`"; + let help_text = "to allocate qubits, use syntax like `use q = Qubit();`, `use qs = Qubit[N];`, or `use (q1, q2) = (Qubit(), Qubit());`"; let lo = s.peek().span.lo; s.expect(WordKinds::Qubit); let kind = if let Ok(name) = ident(s) { diff --git a/source/compiler/qsc_passes/src/replace_qubit_allocation.rs b/source/compiler/qsc_passes/src/replace_qubit_allocation.rs index ebdb468a52..50b45ded4c 100644 --- a/source/compiler/qsc_passes/src/replace_qubit_allocation.rs +++ b/source/compiler/qsc_passes/src/replace_qubit_allocation.rs @@ -61,7 +61,7 @@ impl<'a> ReplaceQubitAllocation<'a> { QubitInitKind::MemoryArray(e) => Some((true, Some(take(e)))), QubitInitKind::MemorySingle => Some((true, None)), QubitInitKind::Tuple(_) => None, - QubitInitKind::Err => unreachable!("QubitInitKind::Err should have been caught by parser"), + QubitInitKind::Err => panic!("QubitInitKind::Err"), } } @@ -204,7 +204,7 @@ impl<'a> ReplaceQubitAllocation<'a> { }; (tuple_expr, ids) } - QubitInitKind::Err => unreachable!("QubitInitKind::Err should have been caught by parser"), + QubitInitKind::Err => panic!("QubitInitKind::Err"), } } @@ -576,7 +576,7 @@ fn create_qubit_global_alloc( .collect(), ), }, - QubitInitKind::Err => unreachable!("QubitInitKind::Err should have been caught by parser"), + QubitInitKind::Err => panic!("QubitInitKind::Err"), } } From 809c3cd250e03603b864793d44b21ea706eb5e0e Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Sun, 26 Apr 2026 18:33:57 -0700 Subject: [PATCH 15/16] small fixes --- source/compiler/qsc_parse/src/stmt.rs | 2 +- source/compiler/qsc_parse/src/stmt/tests.rs | 2 +- .../src/replace_qubit_allocation.rs | 31 +++++-------------- source/resource_estimator/src/counts.rs | 1 - 4 files changed, 10 insertions(+), 26 deletions(-) diff --git a/source/compiler/qsc_parse/src/stmt.rs b/source/compiler/qsc_parse/src/stmt.rs index 10eacdb5fd..e11f16f1e6 100644 --- a/source/compiler/qsc_parse/src/stmt.rs +++ b/source/compiler/qsc_parse/src/stmt.rs @@ -160,7 +160,7 @@ fn parse_qubit(s: &mut ParserContext) -> Result> { } fn parse_qubit_init(s: &mut ParserContext) -> Result> { - let help_text = "to allocate qubits, use syntax like `use q = Qubit();`, `use qs = Qubit[N];`, or `use (q1, q2) = (Qubit(), Qubit());`"; + let help_text = "to allocate qubits, use syntax like `use q = Qubit();` or `use qs = Qubit[N];` or `use (q1, q2) = (Qubit(), Qubit());`"; let lo = s.peek().span.lo; s.expect(WordKinds::Qubit); let kind = if let Ok(name) = ident(s) { diff --git a/source/compiler/qsc_parse/src/stmt/tests.rs b/source/compiler/qsc_parse/src/stmt/tests.rs index 53ea30b640..25450b3567 100644 --- a/source/compiler/qsc_parse/src/stmt/tests.rs +++ b/source/compiler/qsc_parse/src/stmt/tests.rs @@ -172,7 +172,7 @@ fn use_invalid_init() { }, ), Some( - "to allocate qubits, use syntax like `use q = Qubit();`, `use m = MemoryQubit();`, `use qs = Qubit[N];`, `use ms = MemoryQubit[N];`, or `use (q1, q2) = (Qubit(), Qubit());`", + "to allocate qubits, use syntax like `use q = Qubit();` or `use qs = Qubit[N];` or `use (q1, q2) = (Qubit(), Qubit());`", ), ) "#]], diff --git a/source/compiler/qsc_passes/src/replace_qubit_allocation.rs b/source/compiler/qsc_passes/src/replace_qubit_allocation.rs index 50b45ded4c..7cab9c56ee 100644 --- a/source/compiler/qsc_passes/src/replace_qubit_allocation.rs +++ b/source/compiler/qsc_passes/src/replace_qubit_allocation.rs @@ -279,17 +279,7 @@ impl<'a> ReplaceQubitAllocation<'a> { }; 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, - if is_memory { - Prim::MemoryQubit - } else { - Prim::Qubit - }, - ) + create_general_alloc_stmt(self.assigner, ident, call_expr, None, is_memory) } fn create_array_alloc_stmt( @@ -310,17 +300,7 @@ impl<'a> ReplaceQubitAllocation<'a> { }; 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), - if is_memory { - Prim::MemoryQubit - } else { - Prim::Qubit - }, - ) + create_general_alloc_stmt(self.assigner, ident, call_expr, Some(array_size), is_memory) } fn create_dealloc_stmt(&mut self, ident: &IdentTemplate, is_memory: bool) -> Stmt { @@ -592,8 +572,13 @@ fn create_general_alloc_stmt( ident: &IdentTemplate, call_expr: Expr, array_size: Option, - prim: Prim, + 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, prim), diff --git a/source/resource_estimator/src/counts.rs b/source/resource_estimator/src/counts.rs index 257880d705..1adbc685b2 100644 --- a/source/resource_estimator/src/counts.rs +++ b/source/resource_estimator/src/counts.rs @@ -50,7 +50,6 @@ pub struct LogicalCounter { memory_compute: Option, /// Random number generator rnd: RefCell, - /// 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, From 7b609c41bcc88dcec5eab035917972db6873e8ee Mon Sep 17 00:00:00 2001 From: Dima Fedoriaka Date: Mon, 4 May 2026 15:13:24 -0700 Subject: [PATCH 16/16] add a sample --- samples/language/MemoryQubit.qs | 18 ++++ source/compiler/qsc_partial_eval/src/lib.rs | 100 +++++++++++++++++++- source/compiler/qsc_rca/src/core.rs | 4 +- source/samples_test/src/tests/language.rs | 6 ++ 4 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 samples/language/MemoryQubit.qs 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_partial_eval/src/lib.rs b/source/compiler/qsc_partial_eval/src/lib.rs index c428b2a385..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" @@ -3714,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)), @@ -3725,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) @@ -4225,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_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index ebef4640ba..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 { 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𝑖