microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.28.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/compiler/qsc/src/interpret.rs

1861lines · modecode

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