microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
c1ca1a247cd0a9687a9fa0fbd08ff72ef5f451aa

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc/src/interpret.rs

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