microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.29.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/compiler/qsc/src/interpret.rs

1899lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod circuit_classical_ctl_tests;
6#[cfg(test)]
7mod circuit_tests;
8mod debug;
9#[cfg(test)]
10mod debugger_tests;
11#[cfg(test)]
12mod package_tests;
13#[cfg(test)]
14mod tests;
15
16use std::{cell::RefCell, rc::Rc};
17
18use crate::{
19 error::{self, WithStack},
20 incremental::Compiler,
21 location::Location,
22};
23use debug::format_call_stack;
24use miette::Diagnostic;
25use num_bigint::BigUint;
26use num_complex::Complex;
27use qdk_simulators::noise_config::NoiseConfig;
28use qsc_circuit::{
29 Circuit, CircuitTracer, TracerConfig,
30 operations::{entry_expr_for_qubit_operation, qubit_param_info},
31 rir_to_circuit::rir_to_circuit,
32};
33use qsc_codegen::qir::{
34 fir_to_qir, fir_to_qir_from_callable, fir_to_rir, fir_to_rir_from_callable,
35};
36use qsc_data_structures::{
37 error::WithSource,
38 functors::FunctorApp,
39 language_features::LanguageFeatures,
40 line_column::{Encoding, Range},
41 source::{Source, SourceMap},
42 span::Span,
43 target::{Profile, TargetCapabilityFlags},
44};
45use qsc_eval::{
46 Env, ErrorBehavior, State, VariableInfo,
47 backend::{Backend, CliffordSim, SparseSim, TracingBackend},
48 output::Receiver,
49};
50pub use qsc_eval::{
51 StepAction, StepResult,
52 debug::Frame,
53 noise::PauliNoise,
54 output::{self, GenericReceiver},
55 val::Closure,
56 val::Range as ValueRange,
57 val::Result,
58 val::Value,
59};
60use qsc_fir::{
61 fir::{
62 self, Block, BlockId, ExecGraph, ExecGraphConfig, Expr, ExprId, Global, Package, PackageId,
63 PackageStoreLookup, Pat, PatId, Stmt, StmtId,
64 },
65 visit::{self, Visitor},
66};
67use qsc_frontend::{
68 compile::{CompileUnit, Dependencies, PackageStore},
69 incremental::Increment,
70};
71use qsc_hir::{global, ty};
72use qsc_linter::{HirLint, Lint, LintKind, LintLevel};
73use qsc_lowerer::{
74 map_fir_local_item_to_hir, map_fir_package_to_hir, map_hir_local_item_to_fir,
75 map_hir_package_to_fir,
76};
77use qsc_partial_eval::{PartialEvalConfig, ProgramEntry};
78use qsc_passes::{PackageType, PassContext};
79use qsc_rca::PackageStoreComputeProperties;
80use rustc_hash::FxHashSet;
81use thiserror::Error;
82
83impl Error {
84 #[must_use]
85 pub fn stack_trace(&self) -> Option<&String> {
86 match &self {
87 Error::Eval(err) => err.stack_trace(),
88 _ => None,
89 }
90 }
91}
92
93#[derive(Clone, Debug, Diagnostic, Error)]
94pub enum Error {
95 #[error(transparent)]
96 #[diagnostic(transparent)]
97 Compile(#[from] crate::compile::Error),
98 #[error(transparent)]
99 #[diagnostic(transparent)]
100 Pass(#[from] WithSource<qsc_passes::Error>),
101 #[error("runtime error")]
102 #[diagnostic(transparent)]
103 Eval(#[from] WithStack<WithSource<qsc_eval::Error>>),
104 #[error("circuit error")]
105 #[diagnostic(transparent)]
106 Circuit(#[from] qsc_circuit::Error),
107 #[error("entry point not found")]
108 #[diagnostic(code("Qsc.Interpret.NoEntryPoint"))]
109 NoEntryPoint,
110 #[error("unsupported runtime capabilities for code generation")]
111 #[diagnostic(code("Qsc.Interpret.UnsupportedRuntimeCapabilities"))]
112 UnsupportedRuntimeCapabilities,
113 #[error("expression does not evaluate to an operation")]
114 #[diagnostic(code("Qsc.Interpret.NotAnOperation"))]
115 #[diagnostic(help("provide the name of a callable or a lambda expression"))]
116 NotAnOperation,
117 #[error("value is not a global callable")]
118 #[diagnostic(code("Qsc.Interpret.NotACallable"))]
119 NotACallable,
120 #[error("partial evaluation error")]
121 #[diagnostic(transparent)]
122 PartialEvaluation(#[from] WithSource<qsc_partial_eval::Error>),
123}
124
125#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
126pub enum SimType {
127 #[default]
128 Sparse,
129 Clifford(usize),
130}
131
132/// A Q# interpreter.
133pub struct Interpreter {
134 /// The incremental Q# compiler.
135 compiler: Compiler,
136 /// The target capabilities used for compilation.
137 capabilities: TargetCapabilityFlags,
138 /// The computed properties for the package store, if any, used for code generation.
139 compute_properties: Option<PackageStoreComputeProperties>,
140 /// The number of lines that have so far been compiled.
141 /// This field is used to generate a unique label
142 /// for each line evaluated with `eval_fragments`.
143 lines: u32,
144 // The FIR store
145 fir_store: fir::PackageStore,
146 /// FIR lowerer
147 lowerer: qsc_lowerer::Lowerer,
148 /// The execution graph for the last expression evaluated.
149 expr_graph: Option<ExecGraph>,
150 /// Checking if an `ItemId` corresponds to the `Std.OpenQASM.Angle.Angle` UDT
151 /// is an expensive operation. So, we cache the id to avoid incurring that cost.
152 angle_ty_cache: RefCell<Option<crate::hir::ItemId>>,
153 /// Checking if an `ItemId` corresponds to the `Std.Math.Complex` UDT
154 /// is an expensive operation. So, we cache the id to avoid incurring that cost.
155 complex_ty_cache: RefCell<Option<crate::hir::ItemId>>,
156 /// The ID of the current package.
157 /// This ID is valid both for the FIR store and the `PackageStore`.
158 package: PackageId,
159 /// The ID of the source package. The source package
160 /// is made up of the initial sources passed in when creating the interpreter.
161 /// This ID is valid both for the FIR store and the `PackageStore`.
162 source_package: PackageId,
163 /// The default simulator backend.
164 sim: SparseSim,
165 /// When circuit tracing is enabled, the tracer that records the circuit during evaluation.
166 circuit_tracer: Option<CircuitTracer>,
167 /// The quantum seed, if any. This is cached here so that it can be used in calls to
168 /// `run_internal` which use a passed instance of the simulator instead of the one above.
169 quantum_seed: Option<u64>,
170 /// The classical seed, if any. This needs to be passed to the evaluator for use in intrinsic
171 /// calls that produce classical random numbers.
172 classical_seed: Option<u64>,
173 /// The evaluator environment.
174 env: Env,
175 /// The execution graph configuration to use for evaluation.
176 eval_config: ExecGraphConfig,
177}
178
179pub type InterpretResult = std::result::Result<Value, Vec<Error>>;
180
181/// Indicates whether an UDT is an `OpenQASM` `Angle` or a `Complex` number.
182/// This information is needed in the Python interop layer to give special
183/// treatment to the instances of these UDTs.
184pub enum UdtKind {
185 /// `Std.OpenQASM.Angle.Angle`
186 Angle,
187 /// `Std.Math.Complex`
188 Complex,
189 /// A normal UDT, see the other variants for the special cases.
190 Udt,
191}
192
193/// An item tagged with its name and the namespace it was defined in.
194pub struct TaggedItem {
195 pub item_id: qsc_hir::hir::ItemId,
196 pub name: Rc<str>,
197 pub namespace: Vec<Rc<str>>,
198}
199
200#[derive(PartialEq, Eq, Copy, Clone)]
201pub enum TraceCircuitOption {
202 Enabled,
203 Disabled,
204}
205
206impl Interpreter {
207 /// Creates a new incremental compiler, compiling the passed in sources.
208 /// # Errors
209 /// If compiling the sources fails, compiler errors are returned.
210 pub fn new(
211 sources: SourceMap,
212 package_type: PackageType,
213 capabilities: TargetCapabilityFlags,
214 language_features: LanguageFeatures,
215 store: PackageStore,
216 dependencies: &Dependencies,
217 ) -> std::result::Result<Self, Vec<Error>> {
218 Self::with_sources(
219 ExecGraphConfig::NoDebug,
220 sources,
221 package_type,
222 capabilities,
223 language_features,
224 store,
225 dependencies,
226 None,
227 )
228 }
229
230 pub fn with_circuit_trace(
231 sources: SourceMap,
232 package_type: PackageType,
233 capabilities: TargetCapabilityFlags,
234 language_features: LanguageFeatures,
235 store: PackageStore,
236 dependencies: &Dependencies,
237 circuit_tracer_config: TracerConfig,
238 ) -> std::result::Result<Self, Vec<Error>> {
239 Self::with_sources(
240 ExecGraphConfig::NoDebug,
241 sources,
242 package_type,
243 capabilities,
244 language_features,
245 store,
246 dependencies,
247 Some(circuit_tracer_config),
248 )
249 }
250
251 /// Creates a new incremental compiler with debugging stmts enabled, compiling the passed in sources.
252 /// # Errors
253 /// If compiling the sources fails, compiler errors are returned.
254 pub fn with_debug(
255 sources: SourceMap,
256 package_type: PackageType,
257 capabilities: TargetCapabilityFlags,
258 language_features: LanguageFeatures,
259 store: PackageStore,
260 dependencies: &Dependencies,
261 trace_circuit_config: TracerConfig,
262 ) -> std::result::Result<Self, Vec<Error>> {
263 Self::with_sources(
264 ExecGraphConfig::Debug,
265 sources,
266 package_type,
267 capabilities,
268 language_features,
269 store,
270 dependencies,
271 Some(trace_circuit_config),
272 )
273 }
274
275 #[allow(clippy::too_many_arguments)]
276 fn with_sources(
277 eval_config: ExecGraphConfig,
278 sources: SourceMap,
279 package_type: PackageType,
280 capabilities: TargetCapabilityFlags,
281 language_features: LanguageFeatures,
282 store: PackageStore,
283 dependencies: &Dependencies,
284 circuit_tracer_config: Option<TracerConfig>,
285 ) -> std::result::Result<Self, Vec<Error>> {
286 let compiler = Compiler::new(
287 sources,
288 package_type,
289 capabilities,
290 language_features,
291 store,
292 dependencies,
293 )
294 .map_err(into_errors)?;
295
296 Self::with_compiler(eval_config, capabilities, circuit_tracer_config, compiler)
297 }
298
299 pub fn with_package_store(
300 dbg: bool,
301 store: PackageStore,
302 source_package_id: qsc_hir::hir::PackageId,
303 capabilities: TargetCapabilityFlags,
304 language_features: LanguageFeatures,
305 dependencies: &Dependencies,
306 ) -> std::result::Result<Self, Vec<Error>> {
307 let compiler = Compiler::with_package_store(
308 store,
309 source_package_id,
310 capabilities,
311 language_features,
312 dependencies,
313 )
314 .map_err(into_errors)?;
315
316 // Always enable circuit tracing along with debugging.
317 let circuit_tracer_config = if dbg {
318 Some(Debugger::circuit_config())
319 } else {
320 None
321 };
322
323 let eval_config = if dbg {
324 ExecGraphConfig::Debug
325 } else {
326 ExecGraphConfig::NoDebug
327 };
328
329 Self::with_compiler(eval_config, capabilities, circuit_tracer_config, compiler)
330 }
331
332 fn with_compiler(
333 eval_config: ExecGraphConfig,
334 capabilities: TargetCapabilityFlags,
335 circuit_tracer_config: Option<TracerConfig>,
336 compiler: Compiler,
337 ) -> std::result::Result<Interpreter, Vec<Error>> {
338 let mut fir_store = fir::PackageStore::new();
339 for (id, unit) in compiler.package_store() {
340 let mut lowerer = qsc_lowerer::Lowerer::new();
341 let pkg = lowerer.lower_package(&unit.package, &fir_store);
342 fir_store.insert(map_hir_package_to_fir(id), pkg);
343 }
344
345 let source_package_id = compiler.source_package_id();
346 let package_id = compiler.package_id();
347
348 let package = map_hir_package_to_fir(package_id);
349 let compute_properties = if capabilities == TargetCapabilityFlags::all() {
350 None
351 } else {
352 let compute_properties = PassContext::run_fir_passes_on_fir(
353 &fir_store,
354 map_hir_package_to_fir(source_package_id),
355 capabilities,
356 )
357 .map_err(|caps_errors| {
358 let source_package = compiler
359 .package_store()
360 .get(source_package_id)
361 .expect("package should exist in the package store");
362
363 caps_errors
364 .into_iter()
365 .map(|error| Error::Pass(WithSource::from_map(&source_package.sources, error)))
366 .collect::<Vec<_>>()
367 })?;
368
369 Some(compute_properties)
370 };
371
372 Ok(Self {
373 compiler,
374 lines: 0,
375 capabilities,
376 compute_properties,
377 fir_store,
378 lowerer: qsc_lowerer::Lowerer::new(),
379 expr_graph: None,
380 angle_ty_cache: None.into(),
381 complex_ty_cache: None.into(),
382 env: Env::default(),
383 sim: SparseSim::new(),
384 circuit_tracer: circuit_tracer_config.map(|config| {
385 CircuitTracer::new(
386 config,
387 &[package, map_hir_package_to_fir(source_package_id)],
388 )
389 }),
390 quantum_seed: None,
391 classical_seed: None,
392 package,
393 source_package: map_hir_package_to_fir(source_package_id),
394 eval_config,
395 })
396 }
397
398 /// Given a package ID, returns all the global items in the package.
399 /// Note this does not currently include re-exports.
400 fn package_globals(&self, package_id: PackageId) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
401 let mut exported_items = Vec::new();
402 let package = &self
403 .compiler
404 .package_store()
405 .get(map_fir_package_to_hir(package_id))
406 .expect("package should exist in the package store")
407 .package;
408 for global in global::iter_package(map_fir_package_to_hir(package_id), package) {
409 if let global::Kind::Callable(term) = global.kind {
410 let store_item_id = fir::StoreItemId {
411 package: package_id,
412 item: fir::LocalItemId::from(usize::from(term.id.item)),
413 };
414 exported_items.push((
415 global.namespace,
416 global.name,
417 Value::Global(store_item_id, FunctorApp::default()),
418 ));
419 }
420 }
421 exported_items
422 }
423
424 /// Get the global callables defined in the user source passed into initialization of the interpreter as `Value` instances.
425 pub fn source_globals(&self) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
426 self.package_globals(self.source_package)
427 }
428
429 /// Get the global callables defined in the open package being interpreted as `Value` instances, which will include any items
430 /// defined by calls to `eval_fragments` and the like.
431 pub fn user_globals(&self) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
432 self.package_globals(self.package)
433 }
434
435 /// Get the input and output types of a given value representing a global item.
436 /// # Panics
437 /// Panics if the item is not callable or a type that can be invoked as a callable.
438 pub fn global_callable_ty(&self, item_id: &Value) -> Option<(ty::Ty, ty::Ty)> {
439 let (item_id, is_closure) = match item_id {
440 Value::Global(item_id, _) => (*item_id, false),
441 Value::Closure(closure) => (closure.id, true),
442 _ => panic!("value is not a callable"),
443 };
444
445 let package_id = map_fir_package_to_hir(item_id.package);
446 let unit = self
447 .compiler
448 .package_store()
449 .get(package_id)
450 .expect("package should exist in the package store");
451 let item = unit
452 .package
453 .items
454 .get(qsc_hir::hir::LocalItemId::from(usize::from(item_id.item)))?;
455 match &item.kind {
456 qsc_hir::hir::ItemKind::Callable(decl) => {
457 if is_closure {
458 // The arguments are a tuple where the first element is the input arguments and
459 // the second are the captured variables.
460 // Grab that first element to get the actual input type.
461 let ty::Ty::Tuple(elems) = &decl.input.ty else {
462 panic!("closure input type is not a tuple")
463 };
464 let input_ty = elems
465 .last()
466 .expect("closure input type should have at least one element");
467 Some((input_ty.clone(), decl.output.clone()))
468 } else {
469 Some((decl.input.ty.clone(), decl.output.clone()))
470 }
471 }
472 qsc_hir::hir::ItemKind::Ty(_, udt) => {
473 // We don't handle UDTs, so we return an error type that prevents later code from processing this item.
474 Some((udt.get_pure_ty(), ty::Ty::Err))
475 }
476 _ => panic!("item is not callable"),
477 }
478 }
479
480 /// Given a package ID, returns all the types in the package.
481 /// Note this does not currently include re-exports.
482 fn package_types(&self, package_id: PackageId) -> Vec<TaggedItem> {
483 let mut exported_items = Vec::new();
484 let package = &self
485 .compiler
486 .package_store()
487 .get(map_fir_package_to_hir(package_id))
488 .expect("package should exist in the package store")
489 .package;
490 for global in global::iter_package(map_fir_package_to_hir(package_id), package) {
491 if let global::Kind::Ty(ty) = global.kind {
492 exported_items.push(TaggedItem {
493 item_id: ty.id,
494 name: global.name,
495 namespace: global.namespace,
496 });
497 }
498 }
499 exported_items
500 }
501
502 /// Get the global UDTs defined in the user source passed into initialization of the interpreter.
503 pub fn source_types(&self) -> Vec<TaggedItem> {
504 self.package_types(self.source_package)
505 }
506
507 /// Get the global UDTs defined in the open package being interpreted, which will include any items
508 /// defined by calls to `eval_fragments` and the like.
509 pub fn user_types(&self) -> Vec<TaggedItem> {
510 self.package_types(self.package)
511 }
512
513 pub fn udt_ty_from_store_item_id(
514 &self,
515 store_item_id: crate::fir::StoreItemId,
516 ) -> (&ty::Udt, UdtKind) {
517 self.udt_ty_from_item_id(&crate::hir::ItemId {
518 package: map_fir_package_to_hir(store_item_id.package),
519 item: map_fir_local_item_to_hir(store_item_id.item),
520 })
521 }
522
523 /// Get the type of a UDT given its `item_id`.
524 /// # Panics
525 /// Panics if the item is not a UDT.
526 pub fn udt_ty_from_item_id(&self, item_id: &crate::hir::ItemId) -> (&ty::Udt, UdtKind) {
527 let crate::hir::ItemId {
528 package: package_id,
529 item: local_item_id,
530 } = item_id;
531
532 let unit = self
533 .compiler
534 .package_store()
535 .get(*package_id)
536 .expect("package should exist in the package store");
537
538 let item = unit
539 .package
540 .items
541 .get(*local_item_id)
542 .expect("item should be in this package");
543
544 let parent = item.parent.map(|parent| {
545 &unit
546 .package
547 .items
548 .get(parent)
549 .expect("parent should exist")
550 .kind
551 });
552
553 let qsc_hir::hir::ItemKind::Ty(_, udt) = &item.kind else {
554 panic!("item is not a UDT")
555 };
556
557 let kind = if let Some(id) = &*self.angle_ty_cache.borrow()
558 && id == item_id
559 {
560 UdtKind::Angle
561 } else if let Some(id) = &*self.complex_ty_cache.borrow()
562 && id == item_id
563 {
564 UdtKind::Complex
565 } else if let Some(qsc_hir::hir::ItemKind::Namespace(namespace, _)) = parent {
566 let namespace: Vec<_> = namespace.into();
567 let namespace: Vec<&str> = namespace.iter().map(|ident| &**ident).collect();
568 if matches!(&namespace[..], &["Std", "OpenQASM", "Angle"]) && &*udt.name == "Angle" {
569 *self.angle_ty_cache.borrow_mut() = Some(*item_id);
570 UdtKind::Angle
571 } else if matches!(&namespace[..], &["Std", "Core"]) && &*udt.name == "Complex" {
572 *self.complex_ty_cache.borrow_mut() = Some(*item_id);
573 UdtKind::Complex
574 } else {
575 UdtKind::Udt
576 }
577 } else {
578 UdtKind::Udt
579 };
580
581 (udt, kind)
582 }
583
584 /// Returns the [`fir::StoreItemId`] for the `Std.OpenQASM.Angle.Angle` UDT.
585 ///
586 /// This function intended to be used from
587 /// `source/pip/src/interpreter/data_interop.rs::pyobj_to_value`
588 /// to tag the angles coming from Python with the correct `StoreItemId`.
589 pub fn get_angle_id(&self) -> fir::StoreItemId {
590 if let Some(id) = &*self.angle_ty_cache.borrow() {
591 let crate::hir::ItemId {
592 package: hir_package_id,
593 item: hir_local_item_id,
594 } = id;
595 let fir_package_id = map_hir_package_to_fir(*hir_package_id);
596 let fir_local_item_id = map_hir_local_item_to_fir(*hir_local_item_id);
597 crate::fir::StoreItemId {
598 package: fir_package_id,
599 item: fir_local_item_id,
600 }
601 } else {
602 // SAFETY: This function is intended to be used when receiving Python objects
603 // in the interop layer. The only way to send a Python object to Q# is
604 // as the argument of a function call. When performing type checking
605 // for this function call in the interop layer, there are two cases:
606 //
607 // 1. The input type is not `Std.OpenQASM.Angle.Angle` and we return
608 // an error.
609 // 2. The input type is `Std.OpenQASM.Angle.Angle`. To verify that
610 // the input type is indeed `Angle`, we call `udt_ty_from_item_id`,
611 // which caches the `Angle` UDT's `LocalItemId`.
612 //
613 // So, if we proceed to execute the function's body, it's guaranteed
614 // that we have already cached `Std.OpenQASM.Angle.Angle`'s `LocalItemId`.
615 // Therefore, this else-branch is unreachable.
616 unreachable!("`self.angle_ty_cache` should be set by `udt_ty_from_item_id`")
617 }
618 }
619
620 /// Returns the [`fir::StoreItemId`] for the `Std.Math.Complex` UDT.
621 ///
622 /// This function intended to be used from
623 /// `source/pip/src/interpreter/data_interop.rs::pyobj_to_value`
624 /// to tag the complex numbers coming from Python with the correct
625 /// `StoreItemId`.
626 pub fn get_complex_id(&self) -> crate::fir::StoreItemId {
627 if let Some(id) = &*self.complex_ty_cache.borrow() {
628 let crate::hir::ItemId {
629 package: hir_package_id,
630 item: hir_local_item_id,
631 } = id;
632 let fir_package_id = map_hir_package_to_fir(*hir_package_id);
633 let fir_local_item_id = map_hir_local_item_to_fir(*hir_local_item_id);
634 crate::fir::StoreItemId {
635 package: fir_package_id,
636 item: fir_local_item_id,
637 }
638 } else {
639 // SAFETY: This function is intended to be used when receiving Python objects
640 // in the interop layer. The only way to send a Python object to Q# is
641 // as the argument of a function call. When performing type checking
642 // for this function call in the interop layer, there are two cases:
643 //
644 // 1. The input type is not `Std.Math.Complex` and we return an error.
645 // 2. The input type is `Std.Math.Complex`. To verify that the input
646 // type is indeed `Complex`, we call `udt_ty_from_item_id`, which
647 // caches the `Complex` UDT's `LocalItemId`.
648 //
649 // So, if we proceed to execute the function's body, it's guaranteed
650 // that we have already cached `Std.Math.Complex`'s `LocalItemId`.
651 // Therefore, this else-branch is unreachable.
652 unreachable!("`self.complex_ty_cache` should be set by `udt_ty_from_item_id`")
653 }
654 }
655
656 pub fn set_quantum_seed(&mut self, seed: Option<u64>) {
657 self.quantum_seed = seed;
658 self.sim.set_seed(seed);
659 }
660
661 pub fn set_classical_seed(&mut self, seed: Option<u64>) {
662 self.classical_seed = seed;
663 }
664
665 pub fn check_source_lints(&self) -> Vec<Lint> {
666 if let Some(compile_unit) = self
667 .compiler
668 .package_store()
669 .get(self.compiler.source_package_id())
670 {
671 qsc_linter::run_lints(
672 self.compiler.package_store(),
673 compile_unit,
674 // see https://github.com/microsoft/qdk/pull/1627 for context
675 // on why we override this config
676 Some(&[qsc_linter::LintOrGroupConfig::Lint(
677 qsc_linter::LintConfig {
678 kind: LintKind::Hir(HirLint::NeedlessOperation),
679 level: LintLevel::Warn,
680 },
681 )]),
682 )
683 } else {
684 Vec::new()
685 }
686 }
687
688 /// Executes the entry expression until the end of execution.
689 /// # Errors
690 /// Returns a vector of errors if evaluating the entry point fails.
691 pub fn eval_entry(&mut self, receiver: &mut impl Receiver) -> InterpretResult {
692 let graph = self.get_entry_exec_graph()?;
693 self.expr_graph = Some(graph.clone());
694 eval(
695 self.source_package,
696 self.classical_seed,
697 graph,
698 self.eval_config,
699 self.compiler.package_store(),
700 &self.fir_store,
701 &mut Env::default(),
702 &mut TracingBackend::new(&mut self.sim, self.circuit_tracer.as_mut()),
703 receiver,
704 )
705 }
706
707 /// Executes the entry expression until the end of execution, using the given simulator backend
708 /// and a new instance of the environment.
709 pub fn eval_entry_with_sim(
710 &mut self,
711 sim: &mut impl Backend,
712 receiver: &mut impl Receiver,
713 ) -> InterpretResult {
714 let graph = self.get_entry_exec_graph()?;
715 self.expr_graph = Some(graph.clone());
716 if self.quantum_seed.is_some() {
717 sim.set_seed(self.quantum_seed);
718 }
719 eval(
720 self.source_package,
721 self.classical_seed,
722 graph,
723 self.eval_config,
724 self.compiler.package_store(),
725 &self.fir_store,
726 &mut Env::default(),
727 &mut TracingBackend::no_tracer(sim),
728 receiver,
729 )
730 }
731
732 fn get_entry_exec_graph(&self) -> std::result::Result<ExecGraph, Vec<Error>> {
733 let unit = self.fir_store.get(self.source_package);
734 if unit.entry.is_some() {
735 return Ok(unit.entry_exec_graph.clone());
736 }
737 Err(vec![Error::NoEntryPoint])
738 }
739
740 /// # Errors
741 /// If the parsing of the fragments fails, an error is returned.
742 /// If the compilation of the fragments fails, an error is returned.
743 /// If there is a runtime error when interpreting the fragments, an error is returned.
744 pub fn eval_fragments(
745 &mut self,
746 receiver: &mut impl Receiver,
747 fragments: &str,
748 ) -> InterpretResult {
749 let label = self.next_line_label();
750
751 let mut increment = self
752 .compiler
753 .compile_fragments_fail_fast(&label, fragments)
754 .map_err(into_errors)?;
755
756 // Clear the entry expression, as we are evaluating fragments and a fragment with a `@EntryPoint` attribute
757 // should not change what gets executed.
758 increment.clear_entry();
759
760 self.eval_increment(receiver, increment)
761 }
762
763 /// It is assumed that if there were any parse errors on the fragments, the caller would have
764 /// already handled them. This function is intended to be used in cases where the caller wants
765 /// to handle the parse errors themselves.
766 /// # Errors
767 /// If the compilation of the fragments fails, an error is returned.
768 /// If there is a runtime error when interpreting the fragments, an error is returned.
769 pub fn eval_ast_fragments(
770 &mut self,
771 receiver: &mut impl Receiver,
772 fragments: &str,
773 package: qsc_ast::ast::Package,
774 ) -> InterpretResult {
775 let label = self.next_line_label();
776
777 let increment = self
778 .compiler
779 .compile_ast_fragments_fail_fast(&label, fragments, package)
780 .map_err(into_errors)?;
781
782 self.eval_increment(receiver, increment)
783 }
784
785 fn eval_increment(
786 &mut self,
787 receiver: &mut impl Receiver,
788 increment: Increment,
789 ) -> InterpretResult {
790 let (graph, _) = self.lower(&increment)?;
791 self.expr_graph = Some(graph.clone());
792
793 // Updating the compiler state with the new AST/HIR nodes
794 // is not necessary for the interpreter to function, as all
795 // the state required for evaluation already exists in the
796 // FIR store. It could potentially save some memory
797 // *not* to do hold on to the AST/HIR, but it is done
798 // here to keep the package stores consistent.
799 self.compiler.update(increment);
800
801 eval(
802 self.package,
803 self.classical_seed,
804 graph,
805 self.eval_config,
806 self.compiler.package_store(),
807 &self.fir_store,
808 &mut self.env,
809 &mut TracingBackend::new(&mut self.sim, self.circuit_tracer.as_mut()),
810 receiver,
811 )
812 }
813
814 /// Invokes the given callable with the given arguments using the current environment, simulator, and compilation.
815 pub fn invoke(
816 &mut self,
817 receiver: &mut impl Receiver,
818 callable: Value,
819 args: Value,
820 ) -> InterpretResult {
821 qsc_eval::invoke(
822 self.package,
823 self.classical_seed,
824 &self.fir_store,
825 self.eval_config,
826 &mut self.env,
827 &mut TracingBackend::new(&mut self.sim, self.circuit_tracer.as_mut()),
828 receiver,
829 callable,
830 args,
831 )
832 .map_err(|(error, call_stack)| {
833 eval_error(
834 self.compiler.package_store(),
835 &self.fir_store,
836 call_stack,
837 error,
838 )
839 })
840 }
841
842 // Invokes the given callable with the given arguments using the current compilation but with a fresh
843 // environment and simulator configured with the given noise, if any.
844 #[allow(clippy::too_many_arguments)]
845 pub fn invoke_with_noise(
846 &mut self,
847 receiver: &mut impl Receiver,
848 callable: Value,
849 args: Value,
850 noise: Option<PauliNoise>,
851 qubit_loss: Option<f64>,
852 noise_config: Option<NoiseConfig<f64, f64>>,
853 seed: Option<u64>,
854 sim_type: SimType,
855 ) -> InterpretResult {
856 let qubit_loss = if noise_config.is_none() {
857 qubit_loss
858 } else {
859 None
860 };
861
862 match sim_type {
863 SimType::Sparse => {
864 let mut sim = match noise {
865 Some(noise) => SparseSim::new_with_noise(&noise),
866 None => match noise_config {
867 Some(config) => SparseSim::new_with_noise_config(config.into()),
868 None => SparseSim::new(),
869 },
870 };
871 if let Some(loss) = qubit_loss {
872 sim.set_loss(loss);
873 }
874 if seed.is_some() {
875 sim.set_seed(seed);
876 }
877 self.invoke_with_sim(&mut sim, receiver, callable, args, seed)
878 }
879 SimType::Clifford(num_qubits) => {
880 let mut sim = match noise_config {
881 Some(config) => CliffordSim::new_with_noise_config(num_qubits, config.into()),
882 None => CliffordSim::new(num_qubits),
883 };
884 if seed.is_some() {
885 sim.set_seed(seed);
886 }
887 self.invoke_with_sim(&mut sim, receiver, callable, args, seed)
888 }
889 }
890 }
891
892 /// Runs the given entry expression on a new instance of the environment and simulator,
893 /// but using the current compilation.
894 #[allow(clippy::too_many_arguments)]
895 pub fn run(
896 &mut self,
897 receiver: &mut impl Receiver,
898 expr: Option<&str>,
899 noise: Option<PauliNoise>,
900 qubit_loss: Option<f64>,
901 noise_config: Option<NoiseConfig<f64, f64>>,
902 seed: Option<u64>,
903 sim_type: SimType,
904 ) -> InterpretResult {
905 let qubit_loss = if noise_config.is_none() {
906 qubit_loss
907 } else {
908 None
909 };
910 match sim_type {
911 SimType::Sparse => {
912 let mut sim = match noise {
913 Some(noise) => SparseSim::new_with_noise(&noise),
914 None => match noise_config {
915 Some(config) => SparseSim::new_with_noise_config(config.into()),
916 None => SparseSim::new(),
917 },
918 };
919 if let Some(loss) = qubit_loss {
920 sim.set_loss(loss);
921 }
922 self.run_with_sim(&mut sim, receiver, expr, seed)
923 }
924 SimType::Clifford(num_qubits) => {
925 let mut sim = match noise_config {
926 Some(config) => CliffordSim::new_with_noise_config(num_qubits, config.into()),
927 None => CliffordSim::new(num_qubits),
928 };
929 self.run_with_sim(&mut sim, receiver, expr, seed)
930 }
931 }
932 }
933
934 /// Gets the current quantum state of the simulator.
935 pub fn get_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
936 self.sim
937 .capture_quantum_state()
938 .expect("interpreter should use infallible sparse sim by default")
939 }
940
941 /// Get the current circuit representation of the program.
942 pub fn get_circuit(&self) -> Circuit {
943 self.circuit_tracer
944 .as_ref()
945 .expect("to call get_circuit, the interpreter should be initialized with circuit tracing enabled")
946 .snapshot(&(self.compiler.package_store(), &self.fir_store))
947 }
948
949 /// Performs QIR codegen using the given entry expression on a new instance of the environment
950 /// and simulator but using the current compilation.
951 pub fn qirgen(&mut self, expr: &str) -> std::result::Result<String, Vec<Error>> {
952 if self.capabilities == TargetCapabilityFlags::all() {
953 return Err(vec![Error::UnsupportedRuntimeCapabilities]);
954 }
955
956 // Compile the expression. This operation will set the expression as
957 // the entry-point in the FIR store.
958 let (graph, compute_properties) = self.compile_entry_expr(expr)?;
959
960 let Some(compute_properties) = compute_properties else {
961 // This can only happen if capability analysis was not run. This would be a bug
962 // and we are in a bad state and can't proceed.
963 panic!("internal error: compute properties not set after lowering entry expression");
964 };
965 let package = self.fir_store.get(self.package);
966 let entry = ProgramEntry {
967 exec_graph: graph,
968 expr: (
969 self.package,
970 package
971 .entry
972 .expect("package must have an entry expression"),
973 )
974 .into(),
975 };
976 // Generate QIR
977 fir_to_qir(
978 &self.fir_store,
979 self.capabilities,
980 Some(compute_properties),
981 &entry,
982 )
983 .map_err(|e| {
984 let hir_package_id = match e.span() {
985 Some(span) => span.package,
986 None => map_fir_package_to_hir(self.package),
987 };
988 let source_package = self
989 .compiler
990 .package_store()
991 .get(hir_package_id)
992 .expect("package should exist in the package store");
993 vec![Error::PartialEvaluation(WithSource::from_map(
994 &source_package.sources,
995 e,
996 ))]
997 })
998 }
999
1000 /// Performs QIR codegen using the given callable with the given arguments on a new instance of the environment
1001 /// and simulator but using the current compilation.
1002 pub fn qirgen_from_callable(
1003 &mut self,
1004 callable: &Value,
1005 args: Value,
1006 ) -> std::result::Result<String, Vec<Error>> {
1007 if self.capabilities == TargetCapabilityFlags::all() {
1008 return Err(vec![Error::UnsupportedRuntimeCapabilities]);
1009 }
1010
1011 let Value::Global(store_item_id, _) = callable else {
1012 return Err(vec![Error::NotACallable]);
1013 };
1014
1015 fir_to_qir_from_callable(
1016 &self.fir_store,
1017 self.capabilities,
1018 None,
1019 *store_item_id,
1020 args,
1021 )
1022 .map_err(|e| {
1023 let hir_package_id = match e.span() {
1024 Some(span) => span.package,
1025 None => map_fir_package_to_hir(self.package),
1026 };
1027 let source_package = self
1028 .compiler
1029 .package_store()
1030 .get(hir_package_id)
1031 .expect("package should exist in the package store");
1032 vec![Error::PartialEvaluation(WithSource::from_map(
1033 &source_package.sources,
1034 e,
1035 ))]
1036 })
1037 }
1038
1039 /// Generates a circuit representation for the program.
1040 ///
1041 /// For `entry` options, see [`CircuitEntryPoint`]. For `tracer_config` options, see [`TracerConfig`].
1042 pub fn circuit(
1043 &mut self,
1044 entry: CircuitEntryPoint,
1045 method: CircuitGenerationMethod,
1046 tracer_config: TracerConfig,
1047 ) -> std::result::Result<Circuit, Vec<Error>> {
1048 let (entry_expr, qubit_params, invoke_params) = match entry {
1049 CircuitEntryPoint::Operation(operation_expr) => {
1050 let (package_id, item, functor_app) = self.eval_to_operation(&operation_expr)?;
1051 let qubit_param_info = qubit_param_info(item);
1052 let expr = entry_expr_for_qubit_operation(item, functor_app, &operation_expr)
1053 .map_err(|e| vec![e.into()])?;
1054 (Some(expr), qubit_param_info.map(|i| (package_id, i)), None)
1055 }
1056 CircuitEntryPoint::EntryExpr(expr) => (Some(expr), None, None),
1057 CircuitEntryPoint::Callable(call_val, args_val) => {
1058 (None, None, Some((call_val, args_val)))
1059 }
1060 CircuitEntryPoint::EntryPoint => (None, None, None),
1061 };
1062
1063 let mut sink = std::io::sink();
1064 let mut out = GenericReceiver::new(&mut sink);
1065 let mut tracer = CircuitTracer::with_qubit_input_params(
1066 tracer_config,
1067 &[self.package, self.source_package],
1068 qubit_params,
1069 );
1070
1071 // If grouping by scope is enabled, we'll want to execute
1072 // debug nodes to track block scopes.
1073 let eval_config = if tracer_config.group_by_scope {
1074 ExecGraphConfig::Debug
1075 } else {
1076 self.eval_config
1077 };
1078
1079 match method {
1080 CircuitGenerationMethod::Simulate => {
1081 let mut sim = SparseSim::new();
1082 let mut tracing_backend = TracingBackend::new(&mut sim, Some(&mut tracer));
1083 if let Some((callable, args)) = invoke_params {
1084 self.invoke_with_tracing_backend(
1085 &mut tracing_backend,
1086 &mut out,
1087 callable,
1088 args,
1089 eval_config,
1090 None,
1091 )?;
1092 } else {
1093 self.run_with_tracing_backend(
1094 &mut tracing_backend,
1095 &mut out,
1096 entry_expr.as_deref(),
1097 eval_config,
1098 )?;
1099 }
1100 }
1101 CircuitGenerationMethod::ClassicalEval => {
1102 let mut tracer = TracingBackend::<SparseSim>::no_backend(&mut tracer);
1103 if let Some((callable, args)) = invoke_params {
1104 self.invoke_with_tracing_backend(
1105 &mut tracer,
1106 &mut out,
1107 callable,
1108 args,
1109 eval_config,
1110 None,
1111 )?;
1112 } else {
1113 self.run_with_tracing_backend(
1114 &mut tracer,
1115 &mut out,
1116 entry_expr.as_deref(),
1117 eval_config,
1118 )?;
1119 }
1120 }
1121 CircuitGenerationMethod::Static => {
1122 if let Some((callable, args)) = invoke_params {
1123 return self.static_circuit_from_callable(&callable, args, tracer_config);
1124 }
1125 return self.static_circuit(entry_expr.as_deref(), tracer_config);
1126 }
1127 }
1128 let circuit = tracer.finish(&(self.compiler.package_store(), &self.fir_store));
1129 Ok(circuit)
1130 }
1131
1132 fn static_circuit(
1133 &mut self,
1134 entry_expr: Option<&str>,
1135 tracer_config: TracerConfig,
1136 ) -> std::result::Result<Circuit, Vec<Error>> {
1137 if self.capabilities > Profile::AdaptiveRIF.into() {
1138 return Err(vec![Error::UnsupportedRuntimeCapabilities]);
1139 }
1140
1141 let program = self.compile_to_rir_with_debug_metadata(entry_expr)?;
1142 rir_to_circuit(
1143 &program,
1144 tracer_config,
1145 &[self.package, self.source_package],
1146 &(self.compiler.package_store(), &self.fir_store),
1147 )
1148 .map_err(|e| vec![e.into()])
1149 }
1150
1151 fn static_circuit_from_callable(
1152 &mut self,
1153 callable: &Value,
1154 args: Value,
1155 tracer_config: TracerConfig,
1156 ) -> std::result::Result<Circuit, Vec<Error>> {
1157 if self.capabilities > Profile::AdaptiveRIF.into() {
1158 return Err(vec![Error::UnsupportedRuntimeCapabilities]);
1159 }
1160
1161 let Value::Global(store_item_id, _) = callable else {
1162 return Err(vec![Error::NotACallable]);
1163 };
1164
1165 let (_original, transformed) = fir_to_rir_from_callable(
1166 &self.fir_store,
1167 self.capabilities,
1168 None,
1169 *store_item_id,
1170 args,
1171 PartialEvalConfig {
1172 generate_debug_metadata: true,
1173 },
1174 )
1175 .map_err(|e| {
1176 let hir_package_id = match e.span() {
1177 Some(span) => span.package,
1178 None => map_fir_package_to_hir(self.package),
1179 };
1180 let source_package = self
1181 .compiler
1182 .package_store()
1183 .get(hir_package_id)
1184 .expect("package should exist in the package store");
1185 vec![Error::PartialEvaluation(WithSource::from_map(
1186 &source_package.sources,
1187 e,
1188 ))]
1189 })?;
1190
1191 rir_to_circuit(
1192 &transformed,
1193 tracer_config,
1194 &[self.package, self.source_package],
1195 &(self.compiler.package_store(), &self.fir_store),
1196 )
1197 .map_err(|e| vec![e.into()])
1198 }
1199
1200 fn compile_to_rir_with_debug_metadata(
1201 &mut self,
1202 entry_expr: Option<&str>,
1203 ) -> std::result::Result<qsc_partial_eval::Program, Vec<Error>> {
1204 let (entry, compute_properties) = if let Some(entry_expr) = &entry_expr {
1205 // Compile the expression. This operation will set the expression as
1206 // the entry-point in the FIR store.
1207 let (graph, compute_properties) = self.compile_entry_expr(entry_expr)?;
1208
1209 let Some(compute_properties) = compute_properties else {
1210 // This can only happen if capability analysis was not run.
1211 panic!(
1212 "internal error: compute properties not set after lowering entry expression"
1213 );
1214 };
1215 let package = self.fir_store.get(self.package);
1216 let entry = ProgramEntry {
1217 exec_graph: graph,
1218 expr: (
1219 self.package,
1220 package
1221 .entry
1222 .expect("package must have an entry expression"),
1223 )
1224 .into(),
1225 };
1226 (entry, compute_properties)
1227 } else {
1228 let package = self.fir_store.get(self.source_package);
1229 let entry = ProgramEntry {
1230 exec_graph: package.entry_exec_graph.clone(),
1231 expr: (
1232 self.source_package,
1233 package
1234 .entry
1235 .expect("package must have an entry expression"),
1236 )
1237 .into(),
1238 };
1239 (
1240 entry,
1241 self.compute_properties.clone().expect(
1242 "compute properties should be set if target profile isn't unrestricted",
1243 ),
1244 )
1245 };
1246 let (_original, transformed) = fir_to_rir(
1247 &self.fir_store,
1248 self.capabilities,
1249 Some(compute_properties),
1250 &entry,
1251 PartialEvalConfig {
1252 generate_debug_metadata: true,
1253 },
1254 )
1255 .map_err(|e| {
1256 let hir_package_id = match e.span() {
1257 Some(span) => span.package,
1258 None => map_fir_package_to_hir(self.package),
1259 };
1260 let source_package = self
1261 .compiler
1262 .package_store()
1263 .get(hir_package_id)
1264 .expect("package should exist in the package store");
1265 vec![Error::PartialEvaluation(WithSource::from_map(
1266 &source_package.sources,
1267 e,
1268 ))]
1269 })?;
1270 Ok(transformed)
1271 }
1272
1273 /// Sets the entry expression for the interpreter.
1274 pub fn set_entry_expr(&mut self, entry_expr: &str) -> std::result::Result<(), Vec<Error>> {
1275 let (graph, _) = self.compile_entry_expr(entry_expr)?;
1276 self.expr_graph = Some(graph);
1277 Ok(())
1278 }
1279
1280 /// Runs the given entry expression on the given simulator with a new instance of the environment
1281 /// but using the current compilation.
1282 pub fn run_with_sim(
1283 &mut self,
1284 sim: &mut impl Backend,
1285 receiver: &mut impl Receiver,
1286 expr: Option<&str>,
1287 seed: Option<u64>,
1288 ) -> InterpretResult {
1289 let mut tracing_backend = TracingBackend::no_tracer(sim);
1290 let graph = if let Some(expr) = expr {
1291 let (graph, _) = self.compile_entry_expr(expr)?;
1292 self.expr_graph = Some(graph.clone());
1293 graph
1294 } else {
1295 self.expr_graph.clone().ok_or(vec![Error::NoEntryPoint])?
1296 };
1297
1298 if seed.is_some() {
1299 tracing_backend.set_seed(seed);
1300 } else if self.quantum_seed.is_some() {
1301 tracing_backend.set_seed(self.quantum_seed);
1302 }
1303
1304 let classical_seed = match seed {
1305 Some(seed) => Some(seed),
1306 None => self.classical_seed,
1307 };
1308
1309 eval(
1310 self.package,
1311 classical_seed,
1312 graph,
1313 self.eval_config,
1314 self.compiler.package_store(),
1315 &self.fir_store,
1316 &mut Env::default(),
1317 &mut tracing_backend,
1318 receiver,
1319 )
1320 }
1321
1322 fn run_with_tracing_backend<B: Backend>(
1323 &mut self,
1324 tracing_backend: &mut TracingBackend<'_, B>,
1325 out: &mut GenericReceiver,
1326 entry_expr: Option<&str>,
1327 config: ExecGraphConfig,
1328 ) -> InterpretResult {
1329 let (package_id, graph) = if let Some(entry_expr) = entry_expr {
1330 // entry expression is provided
1331 let (graph, _) = self.compile_entry_expr(entry_expr)?;
1332 (self.package, graph)
1333 } else {
1334 // no entry expression, use the entrypoint in the package
1335 (self.source_package, self.get_entry_exec_graph()?)
1336 };
1337 if self.quantum_seed.is_some() {
1338 tracing_backend.set_seed(self.quantum_seed);
1339 }
1340 eval(
1341 package_id,
1342 self.classical_seed,
1343 graph,
1344 config,
1345 self.compiler.package_store(),
1346 &self.fir_store,
1347 &mut Env::default(),
1348 tracing_backend,
1349 out,
1350 )
1351 }
1352
1353 /// Invokes the given callable with the given arguments on the given simulator with a new instance of the environment
1354 /// but using the current compilation.
1355 pub fn invoke_with_sim(
1356 &mut self,
1357 sim: &mut impl Backend,
1358 receiver: &mut impl Receiver,
1359 callable: Value,
1360 args: Value,
1361 seed: Option<u64>,
1362 ) -> InterpretResult {
1363 self.invoke_with_tracing_backend(
1364 &mut TracingBackend::no_tracer(sim),
1365 receiver,
1366 callable,
1367 args,
1368 self.eval_config,
1369 seed,
1370 )
1371 }
1372
1373 fn invoke_with_tracing_backend<B: Backend>(
1374 &mut self,
1375 tracing_backend: &mut TracingBackend<'_, B>,
1376 receiver: &mut impl Receiver,
1377 callable: Value,
1378 args: Value,
1379 config: ExecGraphConfig,
1380 seed: Option<u64>,
1381 ) -> InterpretResult {
1382 let classical_seed = match seed {
1383 Some(seed) => Some(seed),
1384 None => self.classical_seed,
1385 };
1386 qsc_eval::invoke(
1387 self.package,
1388 classical_seed,
1389 &self.fir_store,
1390 config,
1391 &mut Env::default(),
1392 tracing_backend,
1393 receiver,
1394 callable,
1395 args,
1396 )
1397 .map_err(|(error, call_stack)| {
1398 eval_error(
1399 self.compiler.package_store(),
1400 &self.fir_store,
1401 call_stack,
1402 error,
1403 )
1404 })
1405 }
1406
1407 fn compile_entry_expr(
1408 &mut self,
1409 expr: &str,
1410 ) -> std::result::Result<(ExecGraph, Option<PackageStoreComputeProperties>), Vec<Error>> {
1411 let increment = self
1412 .compiler
1413 .compile_entry_expr(expr)
1414 .map_err(into_errors)?;
1415
1416 // `lower` will update the entry expression in the FIR store,
1417 // and it will always return an empty list of statements.
1418 let (graph, compute_properties) = self.lower(&increment)?;
1419
1420 // The AST and HIR packages in `increment` only contain an entry
1421 // expression and no statements. The HIR *can* contain items if the entry
1422 // expression defined any items.
1423 assert!(increment.hir.stmts.is_empty());
1424 assert!(increment.ast.package.nodes.is_empty());
1425
1426 // Updating the compiler state with the new AST/HIR nodes
1427 // is not necessary for the interpreter to function, as all
1428 // the state required for evaluation already exists in the
1429 // FIR store. It could potentially save some memory
1430 // *not* to do hold on to the AST/HIR, but it is done
1431 // here to keep the package stores consistent.
1432 self.compiler.update(increment);
1433
1434 Ok((graph, compute_properties))
1435 }
1436
1437 fn lower(
1438 &mut self,
1439 unit_addition: &qsc_frontend::incremental::Increment,
1440 ) -> core::result::Result<(ExecGraph, Option<PackageStoreComputeProperties>), Vec<Error>> {
1441 if self.capabilities != TargetCapabilityFlags::all() {
1442 return self.run_fir_passes(unit_addition);
1443 }
1444
1445 self.lower_and_update_package(unit_addition);
1446 Ok((self.lowerer.take_exec_graph(), None))
1447 }
1448
1449 fn lower_and_update_package(&mut self, unit: &qsc_frontend::incremental::Increment) {
1450 {
1451 let fir_package = self.fir_store.get_mut(self.package);
1452 self.lowerer
1453 .lower_and_update_package(fir_package, &unit.hir);
1454 }
1455 let fir_package: &Package = self.fir_store.get(self.package);
1456 qsc_fir::validate::validate(fir_package, &self.fir_store);
1457 }
1458
1459 fn run_fir_passes(
1460 &mut self,
1461 unit: &qsc_frontend::incremental::Increment,
1462 ) -> std::result::Result<(ExecGraph, Option<PackageStoreComputeProperties>), Vec<Error>> {
1463 self.lower_and_update_package(unit);
1464
1465 let cap_results =
1466 PassContext::run_fir_passes_on_fir(&self.fir_store, self.package, self.capabilities);
1467
1468 let compute_properties = cap_results.map_err(|caps_errors| {
1469 // if there are errors, convert them to interpreter errors
1470 // and revert the update to the lowerer/FIR store.
1471 let fir_package = self.fir_store.get_mut(self.package);
1472 self.lowerer.revert_last_increment(fir_package);
1473
1474 let source_package = self
1475 .compiler
1476 .package_store()
1477 .get(map_fir_package_to_hir(self.package))
1478 .expect("package should exist in the package store");
1479
1480 caps_errors
1481 .into_iter()
1482 .map(|error| Error::Pass(WithSource::from_map(&source_package.sources, error)))
1483 .collect::<Vec<_>>()
1484 })?;
1485
1486 let graph = self.lowerer.take_exec_graph();
1487 Ok((graph, Some(compute_properties)))
1488 }
1489
1490 fn next_line_label(&mut self) -> String {
1491 let label = format!("line_{}", self.lines);
1492 self.lines += 1;
1493 label
1494 }
1495
1496 /// Evaluate the name of an operation, or any expression that evaluates to a callable,
1497 /// and return the Item ID and function application for the callable.
1498 /// Examples: "Microsoft.Quantum.Diagnostics.DumpMachine", "(qs: Qubit[]) => H(qs[0])",
1499 /// "Controlled SWAP"
1500 fn eval_to_operation(
1501 &mut self,
1502 operation_expr: &str,
1503 ) -> std::result::Result<(PackageId, &qsc_hir::hir::Item, FunctorApp), Vec<Error>> {
1504 let mut sink = std::io::sink();
1505 let mut out = GenericReceiver::new(&mut sink);
1506 let (store_item_id, functor_app) = match self.eval_fragments(&mut out, operation_expr)? {
1507 Value::Closure(b) => (b.id, b.functor),
1508 Value::Global(item_id, functor_app) => (item_id, functor_app),
1509 _ => return Err(vec![Error::NotAnOperation]),
1510 };
1511 let package = map_fir_package_to_hir(store_item_id.package);
1512 let local_item_id = crate::hir::LocalItemId::from(usize::from(store_item_id.item));
1513 let unit = self
1514 .compiler
1515 .package_store()
1516 .get(package)
1517 .expect("package should exist in the package store");
1518 let item = unit
1519 .package
1520 .items
1521 .get(local_item_id)
1522 .expect("item should exist in the package");
1523 Ok((store_item_id.package, item, functor_app))
1524 }
1525}
1526
1527#[derive(Debug, Clone)]
1528/// Describes the entry point for circuit generation.
1529pub enum CircuitEntryPoint {
1530 /// An operation. This must be a callable name or a lambda
1531 /// expression that only takes qubits as arguments.
1532 /// e.g. "Sample.Main" , "qs => H(qs[0])"
1533 /// The callable name must be visible in the current package.
1534 Operation(String),
1535 /// An explicitly provided entry expression.
1536 EntryExpr(String),
1537 /// A global callable with arguments.
1538 Callable(Value, Value),
1539 /// The entry point for the current package.
1540 EntryPoint,
1541}
1542
1543/// How the circuit is generated.
1544#[derive(Clone, Copy, Debug, PartialEq)]
1545pub enum CircuitGenerationMethod {
1546 /// Simulate the program and trace the actual gate calls. Nondeterministic.
1547 Simulate,
1548 /// Evaluate the classical parts of the program. No quantum simulation.
1549 /// Will fail if a measurement comparison occurs during evaluation.
1550 ClassicalEval,
1551 /// Compile the program and transform to a circuit with only partial evaluation.
1552 /// Only works for `AdaptiveRIF` compliant programs.
1553 Static,
1554}
1555
1556/// A debugger that enables step-by-step evaluation of code
1557/// and inspecting state in the interpreter.
1558pub struct Debugger {
1559 interpreter: Interpreter,
1560 /// The encoding (utf-8 or utf-16) used for character offsets
1561 /// in line/character positions returned by the Interpreter.
1562 position_encoding: Encoding,
1563 /// The current state of the evaluator.
1564 state: State,
1565}
1566
1567impl Debugger {
1568 pub fn new(
1569 sources: SourceMap,
1570 capabilities: TargetCapabilityFlags,
1571 position_encoding: Encoding,
1572 language_features: LanguageFeatures,
1573 store: PackageStore,
1574 dependencies: &Dependencies,
1575 ) -> std::result::Result<Self, Vec<Error>> {
1576 let interpreter = Interpreter::with_debug(
1577 sources,
1578 PackageType::Exe,
1579 capabilities,
1580 language_features,
1581 store,
1582 dependencies,
1583 Debugger::circuit_config(),
1584 )?;
1585 let source_package_id = interpreter.source_package;
1586 let unit = interpreter.fir_store.get(source_package_id);
1587 let entry_exec_graph = unit.entry_exec_graph.clone();
1588 Ok(Self {
1589 interpreter,
1590 position_encoding,
1591 state: State::new(
1592 source_package_id,
1593 entry_exec_graph,
1594 ExecGraphConfig::Debug,
1595 None,
1596 ErrorBehavior::StopOnError,
1597 ),
1598 })
1599 }
1600
1601 pub fn from(interpreter: Interpreter, position_encoding: Encoding) -> Self {
1602 let source_package_id = interpreter.source_package;
1603 let unit = interpreter.fir_store.get(source_package_id);
1604 let entry_exec_graph = unit.entry_exec_graph.clone();
1605 Self {
1606 interpreter,
1607 position_encoding,
1608 state: State::new(
1609 source_package_id,
1610 entry_exec_graph,
1611 ExecGraphConfig::Debug,
1612 None,
1613 ErrorBehavior::StopOnError,
1614 ),
1615 }
1616 }
1617
1618 /// Resumes execution with specified `StepAction`.
1619 /// # Errors
1620 /// Returns a vector of errors if evaluating the entry point fails.
1621 pub fn eval_step(
1622 &mut self,
1623 receiver: &mut impl Receiver,
1624 breakpoints: &[StmtId],
1625 step: StepAction,
1626 ) -> std::result::Result<StepResult, Vec<Error>> {
1627 self.state
1628 .eval(
1629 &self.interpreter.fir_store,
1630 &mut self.interpreter.env,
1631 &mut TracingBackend::new(
1632 &mut self.interpreter.sim,
1633 self.interpreter.circuit_tracer.as_mut(),
1634 ),
1635 receiver,
1636 breakpoints,
1637 step,
1638 )
1639 .map_err(|(error, call_stack)| {
1640 eval_error(
1641 self.interpreter.compiler.package_store(),
1642 &self.interpreter.fir_store,
1643 call_stack,
1644 error,
1645 )
1646 })
1647 }
1648
1649 #[must_use]
1650 pub fn get_stack_frames(&self) -> Vec<StackFrame> {
1651 let frames = self.state.capture_stack();
1652
1653 frames
1654 .iter()
1655 .map(|frame| {
1656 let callable = self
1657 .interpreter
1658 .fir_store
1659 .get_global(frame.id)
1660 .expect("frame should exist");
1661 let functor = format!("{}", frame.functor);
1662 let name = match callable {
1663 Global::Callable(decl) => decl.name.name.to_string(),
1664 Global::Udt => "udt".into(),
1665 };
1666
1667 StackFrame {
1668 name,
1669 functor,
1670 location: Location::from(
1671 frame.span,
1672 map_fir_package_to_hir(frame.id.package),
1673 self.interpreter.compiler.package_store(),
1674 self.position_encoding,
1675 ),
1676 }
1677 })
1678 .collect()
1679 }
1680
1681 pub fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
1682 self.interpreter.get_quantum_state()
1683 }
1684
1685 pub fn circuit(&self) -> Circuit {
1686 self.interpreter.get_circuit()
1687 }
1688
1689 #[must_use]
1690 pub fn get_breakpoints(&self, path: &str) -> Vec<BreakpointSpan> {
1691 let unit = self.source_package();
1692
1693 if let Some(source) = unit.sources.find_by_name(path) {
1694 let package = self
1695 .interpreter
1696 .fir_store
1697 .get(self.interpreter.source_package);
1698 let mut collector = BreakpointCollector::new(
1699 &unit.sources,
1700 source.offset,
1701 package,
1702 self.position_encoding,
1703 );
1704 collector.visit_package(package, &self.interpreter.fir_store);
1705 let mut spans: Vec<_> = collector.statements.into_iter().collect();
1706
1707 // Sort by start position (line first, column next)
1708 spans.sort_by_key(|s| (s.range.start.line, s.range.start.column));
1709 spans
1710 } else {
1711 Vec::new()
1712 }
1713 }
1714
1715 #[must_use]
1716 pub fn get_locals(&self, frame_id: usize) -> Vec<VariableInfo> {
1717 self.interpreter
1718 .env
1719 .get_variables_in_frame(frame_id)
1720 .into_iter()
1721 .filter(|v| !v.name.starts_with('@'))
1722 .collect()
1723 }
1724
1725 fn source_package(&self) -> &CompileUnit {
1726 self.interpreter
1727 .compiler
1728 .package_store()
1729 .get(map_fir_package_to_hir(self.interpreter.source_package))
1730 .expect("Could not load package")
1731 }
1732
1733 /// Configuration used to trace the circuit while debugging.
1734 fn circuit_config() -> TracerConfig {
1735 TracerConfig {
1736 max_operations: TracerConfig::DEFAULT_MAX_OPERATIONS,
1737 source_locations: true,
1738 group_by_scope: false,
1739 prune_classical_qubits: false,
1740 }
1741 }
1742}
1743
1744/// Wrapper function for `qsc_eval::eval` that handles error conversion.
1745#[allow(clippy::too_many_arguments)]
1746fn eval<B: Backend>(
1747 package: PackageId,
1748 classical_seed: Option<u64>,
1749 exec_graph: ExecGraph,
1750 exec_graph_config: ExecGraphConfig,
1751 package_store: &PackageStore,
1752 fir_store: &fir::PackageStore,
1753 env: &mut Env,
1754 tracing_backend: &mut TracingBackend<'_, B>,
1755 receiver: &mut impl Receiver,
1756) -> InterpretResult {
1757 qsc_eval::eval(
1758 package,
1759 classical_seed,
1760 exec_graph,
1761 exec_graph_config,
1762 fir_store,
1763 env,
1764 tracing_backend,
1765 receiver,
1766 )
1767 .map_err(|(error, call_stack)| eval_error(package_store, fir_store, call_stack, error))
1768}
1769
1770/// Represents a stack frame for debugging.
1771pub struct StackFrame {
1772 /// The name of the callable.
1773 pub name: String,
1774 /// The functor of the callable.
1775 pub functor: String,
1776 /// The source location of the call site.
1777 pub location: Location,
1778}
1779
1780#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1781pub struct BreakpointSpan {
1782 /// The id of the statement representing the breakpoint location.
1783 pub id: u32,
1784 /// The source range of the call site.
1785 pub range: Range,
1786}
1787
1788struct BreakpointCollector<'a> {
1789 statements: FxHashSet<BreakpointSpan>,
1790 sources: &'a SourceMap,
1791 offset: u32,
1792 package: &'a Package,
1793 position_encoding: Encoding,
1794}
1795
1796impl<'a> BreakpointCollector<'a> {
1797 fn new(
1798 sources: &'a SourceMap,
1799 offset: u32,
1800 package: &'a Package,
1801 position_encoding: Encoding,
1802 ) -> Self {
1803 Self {
1804 statements: FxHashSet::default(),
1805 sources,
1806 offset,
1807 package,
1808 position_encoding,
1809 }
1810 }
1811
1812 fn get_source(&self, offset: u32) -> &Source {
1813 self.sources
1814 .find_by_offset(offset)
1815 .expect("Couldn't find source file")
1816 }
1817
1818 fn add_stmt(&mut self, stmt: &fir::Stmt) {
1819 let source: &Source = self.get_source(stmt.span.lo);
1820 if source.offset == self.offset {
1821 let span = stmt.span - source.offset;
1822 if span != Span::default() {
1823 let bps = BreakpointSpan {
1824 id: stmt.id.into(),
1825 range: Range::from_span(self.position_encoding, &source.contents, &span),
1826 };
1827 self.statements.insert(bps);
1828 }
1829 }
1830 }
1831}
1832
1833impl<'a> Visitor<'a> for BreakpointCollector<'a> {
1834 fn visit_stmt(&mut self, stmt: StmtId) {
1835 let stmt_res = self.get_stmt(stmt);
1836 match stmt_res.kind {
1837 fir::StmtKind::Expr(expr) | fir::StmtKind::Local(_, _, expr) => {
1838 self.add_stmt(stmt_res);
1839 visit::walk_expr(self, expr);
1840 }
1841 fir::StmtKind::Item(_) | fir::StmtKind::Semi(_) => {
1842 self.add_stmt(stmt_res);
1843 }
1844 }
1845 }
1846
1847 fn get_block(&self, id: BlockId) -> &'a Block {
1848 self.package
1849 .blocks
1850 .get(id)
1851 .expect("couldn't find block in FIR")
1852 }
1853
1854 fn get_expr(&self, id: ExprId) -> &'a Expr {
1855 self.package
1856 .exprs
1857 .get(id)
1858 .expect("couldn't find expr in FIR")
1859 }
1860
1861 fn get_pat(&self, id: PatId) -> &'a Pat {
1862 self.package.pats.get(id).expect("couldn't find pat in FIR")
1863 }
1864
1865 fn get_stmt(&self, id: StmtId) -> &'a Stmt {
1866 self.package
1867 .stmts
1868 .get(id)
1869 .expect("couldn't find stmt in FIR")
1870 }
1871}
1872
1873fn eval_error(
1874 package_store: &PackageStore,
1875 fir_store: &fir::PackageStore,
1876 call_stack: Vec<Frame>,
1877 error: qsc_eval::Error,
1878) -> Vec<Error> {
1879 let stack_trace = if call_stack.is_empty() {
1880 None
1881 } else {
1882 Some(format_call_stack(
1883 package_store,
1884 fir_store,
1885 call_stack,
1886 &error,
1887 ))
1888 };
1889
1890 vec![error::from_eval(error, package_store, stack_trace).into()]
1891}
1892
1893#[must_use]
1894pub fn into_errors(errors: Vec<crate::compile::Error>) -> Vec<Error> {
1895 errors
1896 .into_iter()
1897 .map(|error| Error::Compile(error.into_with_source()))
1898 .collect::<Vec<_>>()
1899}
1900