microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/pipeline-issue-debugging

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc/src/interpret.rs

1641lines · modecode

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