microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/merge-mines-changes

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc/src/interpret.rs

1054lines · 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::{rc::Rc, sync::Arc};
15
16pub use qsc_eval::{
17 debug::Frame,
18 output::{self, GenericReceiver},
19 val::Closure,
20 val::Range as ValueRange,
21 val::Result,
22 val::Value,
23 StepAction, StepResult,
24};
25use qsc_lowerer::{map_fir_package_to_hir, map_hir_package_to_fir};
26use qsc_partial_eval::ProgramEntry;
27use qsc_rca::PackageStoreComputeProperties;
28
29use crate::{
30 error::{self, WithStack},
31 incremental::Compiler,
32 location::Location,
33};
34use debug::format_call_stack;
35use miette::Diagnostic;
36use num_bigint::BigUint;
37use num_complex::Complex;
38use qsc_circuit::{
39 operations::entry_expr_for_qubit_operation, Builder as CircuitBuilder, Circuit,
40 Config as CircuitConfig,
41};
42use qsc_codegen::qir::fir_to_qir;
43use qsc_data_structures::{
44 functors::FunctorApp,
45 language_features::LanguageFeatures,
46 line_column::{Encoding, Range},
47 span::Span,
48 target::TargetCapabilityFlags,
49};
50use qsc_eval::{
51 backend::{Backend, Chain as BackendChain, SparseSim},
52 output::Receiver,
53 val, Env, State, VariableInfo,
54};
55use qsc_fir::fir::{self, ExecGraphNode, Global, PackageStoreLookup};
56use qsc_fir::{
57 fir::{Block, BlockId, Expr, ExprId, Package, PackageId, Pat, PatId, Stmt, StmtId},
58 visit::{self, Visitor},
59};
60use qsc_frontend::{
61 compile::{CompileUnit, Dependencies, PackageStore, Source, SourceMap},
62 error::WithSource,
63 incremental::Increment,
64};
65use qsc_passes::{PackageType, PassContext};
66use rustc_hash::FxHashSet;
67use thiserror::Error;
68
69impl Error {
70 #[must_use]
71 pub fn stack_trace(&self) -> &Option<String> {
72 match &self {
73 Error::Eval(err) => err.stack_trace(),
74 _ => &None,
75 }
76 }
77}
78
79#[derive(Clone, Debug, Diagnostic, Error)]
80pub enum Error {
81 #[error(transparent)]
82 #[diagnostic(transparent)]
83 Compile(#[from] crate::compile::Error),
84 #[error(transparent)]
85 #[diagnostic(transparent)]
86 Pass(#[from] WithSource<qsc_passes::Error>),
87 #[error("runtime error")]
88 #[diagnostic(transparent)]
89 Eval(#[from] WithStack<WithSource<qsc_eval::Error>>),
90 #[error("circuit error")]
91 #[diagnostic(transparent)]
92 Circuit(#[from] qsc_circuit::Error),
93 #[error("entry point not found")]
94 #[diagnostic(code("Qsc.Interpret.NoEntryPoint"))]
95 NoEntryPoint,
96 #[error("unsupported runtime capabilities for code generation")]
97 #[diagnostic(code("Qsc.Interpret.UnsupportedRuntimeCapabilities"))]
98 UnsupportedRuntimeCapabilities,
99 #[error("expression does not evaluate to an operation")]
100 #[diagnostic(code("Qsc.Interpret.NotAnOperation"))]
101 #[diagnostic(help("provide the name of a callable or a lambda expression"))]
102 NotAnOperation,
103 #[error("partial evaluation error")]
104 #[diagnostic(transparent)]
105 PartialEvaluation(#[from] WithSource<qsc_partial_eval::Error>),
106}
107
108/// A Q# interpreter.
109pub struct Interpreter {
110 /// The incremental Q# compiler.
111 compiler: Compiler,
112 /// The target capabilities used for compilation.
113 capabilities: TargetCapabilityFlags,
114 /// The number of lines that have so far been compiled.
115 /// This field is used to generate a unique label
116 /// for each line evaluated with `eval_fragments`.
117 lines: u32,
118 // The FIR store
119 fir_store: fir::PackageStore,
120 /// FIR lowerer
121 lowerer: qsc_lowerer::Lowerer,
122 /// The ID of the current package.
123 /// This ID is valid both for the FIR store and the `PackageStore`.
124 package: PackageId,
125 /// The ID of the source package. The source package
126 /// is made up of the initial sources passed in when creating the interpreter.
127 /// This ID is valid both for the FIR store and the `PackageStore`.
128 source_package: PackageId,
129 /// The default simulator backend.
130 sim: BackendChain<SparseSim, CircuitBuilder>,
131 /// The quantum seed, if any. This is cached here so that it can be used in calls to
132 /// `run_internal` which use a passed instance of the simulator instead of the one above.
133 quantum_seed: Option<u64>,
134 /// The classical seed, if any. This needs to be passed to the evaluator for use in intrinsic
135 /// calls that produce classical random numbers.
136 classical_seed: Option<u64>,
137 /// The evaluator environment.
138 env: Env,
139}
140
141pub type InterpretResult = std::result::Result<Value, Vec<Error>>;
142
143impl Interpreter {
144 /// Creates a new incremental compiler, compiling the passed in sources.
145 /// # Errors
146 /// If compiling the sources fails, compiler errors are returned.
147 pub fn new(
148 sources: SourceMap,
149 package_type: PackageType,
150 capabilities: TargetCapabilityFlags,
151 language_features: LanguageFeatures,
152 store: PackageStore,
153 dependencies: &Dependencies,
154 ) -> std::result::Result<Self, Vec<Error>> {
155 Self::new_internal(
156 false,
157 sources,
158 package_type,
159 capabilities,
160 language_features,
161 store,
162 dependencies,
163 )
164 }
165
166 /// Creates a new incremental compiler with debugging stmts enabled, compiling the passed in sources.
167 /// # Errors
168 /// If compiling the sources fails, compiler errors are returned.
169 pub fn new_with_debug(
170 sources: SourceMap,
171 package_type: PackageType,
172 capabilities: TargetCapabilityFlags,
173 language_features: LanguageFeatures,
174 store: PackageStore,
175 dependencies: &Dependencies,
176 ) -> std::result::Result<Self, Vec<Error>> {
177 Self::new_internal(
178 true,
179 sources,
180 package_type,
181 capabilities,
182 language_features,
183 store,
184 dependencies,
185 )
186 }
187
188 fn new_internal(
189 dbg: bool,
190 sources: SourceMap,
191 package_type: PackageType,
192 capabilities: TargetCapabilityFlags,
193 language_features: LanguageFeatures,
194 store: PackageStore,
195 dependencies: &Dependencies,
196 ) -> std::result::Result<Self, Vec<Error>> {
197 let compiler = Compiler::new(
198 sources,
199 package_type,
200 capabilities,
201 language_features,
202 store,
203 dependencies,
204 )
205 .map_err(into_errors)?;
206
207 let mut fir_store = fir::PackageStore::new();
208 for (id, unit) in compiler.package_store() {
209 let pkg = qsc_lowerer::Lowerer::new()
210 .with_debug(dbg)
211 .lower_package(&unit.package, &fir_store);
212 fir_store.insert(map_hir_package_to_fir(id), pkg);
213 }
214
215 let source_package_id = compiler.source_package_id();
216 let package_id = compiler.package_id();
217
218 let package = map_hir_package_to_fir(package_id);
219 if capabilities != TargetCapabilityFlags::all() {
220 let _ = PassContext::run_fir_passes_on_fir(
221 &fir_store,
222 map_hir_package_to_fir(source_package_id),
223 capabilities,
224 )
225 .map_err(|caps_errors| {
226 let source_package = compiler
227 .package_store()
228 .get(source_package_id)
229 .expect("package should exist in the package store");
230
231 caps_errors
232 .into_iter()
233 .map(|error| Error::Pass(WithSource::from_map(&source_package.sources, error)))
234 .collect::<Vec<_>>()
235 })?;
236 }
237
238 Ok(Self {
239 compiler,
240 lines: 0,
241 capabilities,
242 fir_store,
243 lowerer: qsc_lowerer::Lowerer::new().with_debug(dbg),
244 env: Env::default(),
245 sim: sim_circuit_backend(),
246 quantum_seed: None,
247 classical_seed: None,
248 package,
249 source_package: map_hir_package_to_fir(source_package_id),
250 })
251 }
252
253 pub fn from(
254 store: PackageStore,
255 source_package_id: qsc_hir::hir::PackageId,
256 dependencies: Vec<(qsc_hir::hir::PackageId, Option<Arc<str>>)>,
257 capabilities: TargetCapabilityFlags,
258 language_features: LanguageFeatures,
259 ) -> std::result::Result<Self, Vec<Error>> {
260 let compiler = Compiler::from(
261 store,
262 source_package_id,
263 dependencies,
264 capabilities,
265 language_features,
266 )
267 .map_err(into_errors)?;
268
269 let mut fir_store = fir::PackageStore::new();
270 for (id, unit) in compiler.package_store() {
271 let mut lowerer = qsc_lowerer::Lowerer::new();
272 let pkg = lowerer.lower_package(&unit.package, &fir_store);
273 fir_store.insert(map_hir_package_to_fir(id), pkg);
274 }
275
276 let source_package_id = compiler.source_package_id();
277 let package_id = compiler.package_id();
278
279 Ok(Self {
280 compiler,
281 lines: 0,
282 capabilities,
283 fir_store,
284 lowerer: qsc_lowerer::Lowerer::new(),
285 env: Env::default(),
286 sim: sim_circuit_backend(),
287 quantum_seed: None,
288 classical_seed: None,
289 package: map_hir_package_to_fir(package_id),
290 source_package: map_hir_package_to_fir(source_package_id),
291 })
292 }
293
294 pub fn set_quantum_seed(&mut self, seed: Option<u64>) {
295 self.quantum_seed = seed;
296 self.sim.set_seed(seed);
297 }
298
299 pub fn set_classical_seed(&mut self, seed: Option<u64>) {
300 self.classical_seed = seed;
301 }
302 /// Executes the entry expression until the end of execution.
303 /// # Errors
304 /// Returns a vector of errors if evaluating the entry point fails.
305 pub fn eval_entry(
306 &mut self,
307 receiver: &mut impl Receiver,
308 ) -> std::result::Result<Value, Vec<Error>> {
309 let graph = self.get_entry_exec_graph()?;
310 eval(
311 self.source_package,
312 self.classical_seed,
313 graph,
314 self.compiler.package_store(),
315 &self.fir_store,
316 &mut Env::default(),
317 &mut self.sim,
318 receiver,
319 )
320 }
321
322 /// Executes the entry expression until the end of execution, using the given simulator backend
323 /// and a new instance of the environment.
324 pub fn eval_entry_with_sim(
325 &mut self,
326 sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
327 receiver: &mut impl Receiver,
328 ) -> std::result::Result<Value, Vec<Error>> {
329 let graph = self.get_entry_exec_graph()?;
330 if self.quantum_seed.is_some() {
331 sim.set_seed(self.quantum_seed);
332 }
333 eval(
334 self.source_package,
335 self.classical_seed,
336 graph,
337 self.compiler.package_store(),
338 &self.fir_store,
339 &mut Env::default(),
340 sim,
341 receiver,
342 )
343 }
344
345 fn get_entry_exec_graph(&self) -> std::result::Result<Rc<[ExecGraphNode]>, Vec<Error>> {
346 let unit = self.fir_store.get(self.source_package);
347 if unit.entry.is_some() {
348 return Ok(unit.entry_exec_graph.clone());
349 };
350 Err(vec![Error::NoEntryPoint])
351 }
352
353 /// # Errors
354 /// If the parsing of the fragments fails, an error is returned.
355 /// If the compilation of the fragments fails, an error is returned.
356 /// If there is a runtime error when interpreting the fragments, an error is returned.
357 pub fn eval_fragments(
358 &mut self,
359 receiver: &mut impl Receiver,
360 fragments: &str,
361 ) -> InterpretResult {
362 let label = self.next_line_label();
363
364 let mut increment = self
365 .compiler
366 .compile_fragments_fail_fast(&label, fragments)
367 .map_err(into_errors)?;
368 // Clear the entry expression, as we are evaluating fragments and a fragment with a `@EntryPoint` attribute
369 // should not change what gets executed.
370 increment.clear_entry();
371
372 self.eval_increment(receiver, increment)
373 }
374
375 /// It is assumed that if there were any parse errors on the fragments, the caller would have
376 /// already handled them. This function is intended to be used in cases where the caller wants
377 /// to handle the parse errors themselves.
378 /// # Errors
379 /// If the compilation of the fragments fails, an error is returned.
380 /// If there is a runtime error when interpreting the fragments, an error is returned.
381 pub fn eval_ast_fragments(
382 &mut self,
383 receiver: &mut impl Receiver,
384 fragments: &str,
385 package: qsc_ast::ast::Package,
386 ) -> InterpretResult {
387 let label = self.next_line_label();
388
389 let increment = self
390 .compiler
391 .compile_ast_fragments_fail_fast(&label, fragments, package)
392 .map_err(into_errors)?;
393
394 self.eval_increment(receiver, increment)
395 }
396
397 fn eval_increment(
398 &mut self,
399 receiver: &mut impl Receiver,
400 increment: Increment,
401 ) -> InterpretResult {
402 let (graph, _) = self.lower(&increment)?;
403
404 // Updating the compiler state with the new AST/HIR nodes
405 // is not necessary for the interpreter to function, as all
406 // the state required for evaluation already exists in the
407 // FIR store. It could potentially save some memory
408 // *not* to do hold on to the AST/HIR, but it is done
409 // here to keep the package stores consistent.
410 self.compiler.update(increment);
411
412 eval(
413 self.package,
414 self.classical_seed,
415 graph.into(),
416 self.compiler.package_store(),
417 &self.fir_store,
418 &mut self.env,
419 &mut self.sim,
420 receiver,
421 )
422 }
423
424 /// Runs the given entry expression on a new instance of the environment and simulator,
425 /// but using the current compilation.
426 pub fn run(
427 &mut self,
428 receiver: &mut impl Receiver,
429 expr: &str,
430 ) -> std::result::Result<InterpretResult, Vec<Error>> {
431 self.run_with_sim(&mut SparseSim::new(), receiver, expr)
432 }
433
434 /// Gets the current quantum state of the simulator.
435 pub fn get_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
436 self.sim.capture_quantum_state()
437 }
438
439 /// Get the current circuit representation of the program.
440 pub fn get_circuit(&self) -> Circuit {
441 self.sim.chained.snapshot()
442 }
443
444 /// Performs QIR codegen using the given entry expression on a new instance of the environment
445 /// and simulator but using the current compilation.
446 pub fn qirgen(&mut self, expr: &str) -> std::result::Result<String, Vec<Error>> {
447 if self.capabilities == TargetCapabilityFlags::all() {
448 return Err(vec![Error::UnsupportedRuntimeCapabilities]);
449 }
450
451 // Compile the expression. This operation will set the expression as
452 // the entry-point in the FIR store.
453 let (graph, compute_properties) = self.compile_entry_expr(expr)?;
454
455 let Some(compute_properties) = compute_properties else {
456 // This can only happen if capability analysis was not run. This would be a bug
457 // and we are in a bad state and can't proceed.
458 panic!("internal error: compute properties not set after lowering entry expression");
459 };
460 let package = self.fir_store.get(self.package);
461 let entry = ProgramEntry {
462 exec_graph: graph.into(),
463 expr: (
464 self.package,
465 package
466 .entry
467 .expect("package must have an entry expression"),
468 )
469 .into(),
470 };
471 // Generate QIR
472 fir_to_qir(
473 &self.fir_store,
474 self.capabilities,
475 Some(compute_properties),
476 &entry,
477 )
478 .map_err(|e| {
479 let hir_package_id = match e.span() {
480 Some(span) => span.package,
481 None => map_fir_package_to_hir(self.package),
482 };
483 let source_package = self
484 .compiler
485 .package_store()
486 .get(hir_package_id)
487 .expect("package should exist in the package store");
488 vec![Error::PartialEvaluation(WithSource::from_map(
489 &source_package.sources,
490 e,
491 ))]
492 })
493 }
494
495 /// Generates a circuit representation for the program.
496 ///
497 /// `entry` can be the current entrypoint, an entry expression, or any operation
498 /// that takes qubits.
499 ///
500 /// An operation can be specified by its name or a lambda expression that only takes qubits.
501 /// e.g. `Sample.Main` , `qs => H(qs[0])`
502 ///
503 /// If `simulate` is specified, the program is simulated and the resulting
504 /// circuit is returned (a.k.a. trace mode). Otherwise, the circuit is generated without
505 /// simulation. In this case circuit generation may fail if the program contains dynamic
506 /// behavior (quantum operations that are dependent on measurement results).
507 pub fn circuit(
508 &mut self,
509 entry: CircuitEntryPoint,
510 simulate: bool,
511 ) -> std::result::Result<Circuit, Vec<Error>> {
512 let entry_expr = match entry {
513 CircuitEntryPoint::Operation(operation_expr) => {
514 let (item, functor_app) = self.eval_to_operation(&operation_expr)?;
515 let expr = entry_expr_for_qubit_operation(item, functor_app, &operation_expr)
516 .map_err(|e| vec![e.into()])?;
517 Some(expr)
518 }
519 CircuitEntryPoint::EntryExpr(expr) => Some(expr),
520 CircuitEntryPoint::EntryPoint => None,
521 };
522
523 let circuit = if simulate {
524 let mut sim = sim_circuit_backend();
525
526 self.run_with_sim_no_output(entry_expr, &mut sim)?;
527
528 sim.chained.finish()
529 } else {
530 let mut sim = CircuitBuilder::new(CircuitConfig {
531 base_profile: self.capabilities.is_empty(),
532 });
533
534 self.run_with_sim_no_output(entry_expr, &mut sim)?;
535
536 sim.finish()
537 };
538
539 Ok(circuit)
540 }
541
542 /// Runs the given entry expression on the given simulator with a new instance of the environment
543 /// but using the current compilation.
544 pub fn run_with_sim(
545 &mut self,
546 sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
547 receiver: &mut impl Receiver,
548 expr: &str,
549 ) -> std::result::Result<InterpretResult, Vec<Error>> {
550 let (graph, _) = self.compile_entry_expr(expr)?;
551
552 if self.quantum_seed.is_some() {
553 sim.set_seed(self.quantum_seed);
554 }
555
556 Ok(eval(
557 self.package,
558 self.classical_seed,
559 graph.into(),
560 self.compiler.package_store(),
561 &self.fir_store,
562 &mut Env::default(),
563 sim,
564 receiver,
565 ))
566 }
567
568 fn run_with_sim_no_output(
569 &mut self,
570 entry_expr: Option<String>,
571 sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
572 ) -> InterpretResult {
573 let mut sink = std::io::sink();
574 let mut out = GenericReceiver::new(&mut sink);
575
576 let (package_id, graph) = if let Some(entry_expr) = entry_expr {
577 // entry expression is provided
578 (self.package, self.compile_entry_expr(&entry_expr)?.0.into())
579 } else {
580 // no entry expression, use the entrypoint in the package
581 (self.source_package, self.get_entry_exec_graph()?)
582 };
583
584 if self.quantum_seed.is_some() {
585 sim.set_seed(self.quantum_seed);
586 }
587
588 eval(
589 package_id,
590 self.classical_seed,
591 graph,
592 self.compiler.package_store(),
593 &self.fir_store,
594 &mut Env::default(),
595 sim,
596 &mut out,
597 )
598 }
599
600 fn compile_entry_expr(
601 &mut self,
602 expr: &str,
603 ) -> std::result::Result<(Vec<ExecGraphNode>, Option<PackageStoreComputeProperties>), Vec<Error>>
604 {
605 let increment = self
606 .compiler
607 .compile_entry_expr(expr)
608 .map_err(into_errors)?;
609
610 // `lower` will update the entry expression in the FIR store,
611 // and it will always return an empty list of statements.
612 let (graph, compute_properties) = self.lower(&increment)?;
613
614 // The AST and HIR packages in `increment` only contain an entry
615 // expression and no statements. The HIR *can* contain items if the entry
616 // expression defined any items.
617 assert!(increment.hir.stmts.is_empty());
618 assert!(increment.ast.package.nodes.is_empty());
619
620 // Updating the compiler state with the new AST/HIR nodes
621 // is not necessary for the interpreter to function, as all
622 // the state required for evaluation already exists in the
623 // FIR store. It could potentially save some memory
624 // *not* to do hold on to the AST/HIR, but it is done
625 // here to keep the package stores consistent.
626 self.compiler.update(increment);
627
628 Ok((graph, compute_properties))
629 }
630
631 fn lower(
632 &mut self,
633 unit_addition: &qsc_frontend::incremental::Increment,
634 ) -> core::result::Result<(Vec<ExecGraphNode>, Option<PackageStoreComputeProperties>), Vec<Error>>
635 {
636 if self.capabilities != TargetCapabilityFlags::all() {
637 return self.run_fir_passes(unit_addition);
638 }
639
640 self.lower_and_update_package(unit_addition);
641 Ok((self.lowerer.take_exec_graph(), None))
642 }
643
644 fn lower_and_update_package(&mut self, unit: &qsc_frontend::incremental::Increment) {
645 {
646 let fir_package = self.fir_store.get_mut(self.package);
647 self.lowerer
648 .lower_and_update_package(fir_package, &unit.hir);
649 }
650 let fir_package: &Package = self.fir_store.get(self.package);
651 qsc_fir::validate::validate(fir_package, &self.fir_store);
652 }
653
654 fn run_fir_passes(
655 &mut self,
656 unit: &qsc_frontend::incremental::Increment,
657 ) -> std::result::Result<(Vec<ExecGraphNode>, Option<PackageStoreComputeProperties>), Vec<Error>>
658 {
659 self.lower_and_update_package(unit);
660
661 let cap_results =
662 PassContext::run_fir_passes_on_fir(&self.fir_store, self.package, self.capabilities);
663
664 let compute_properties = cap_results.map_err(|caps_errors| {
665 // if there are errors, convert them to interpreter errors
666 // and revert the update to the lowerer/FIR store.
667 let fir_package = self.fir_store.get_mut(self.package);
668 self.lowerer.revert_last_increment(fir_package);
669
670 let source_package = self
671 .compiler
672 .package_store()
673 .get(map_fir_package_to_hir(self.package))
674 .expect("package should exist in the package store");
675
676 caps_errors
677 .into_iter()
678 .map(|error| Error::Pass(WithSource::from_map(&source_package.sources, error)))
679 .collect::<Vec<_>>()
680 })?;
681
682 let graph = self.lowerer.take_exec_graph();
683 Ok((graph, Some(compute_properties)))
684 }
685
686 fn next_line_label(&mut self) -> String {
687 let label = format!("line_{}", self.lines);
688 self.lines += 1;
689 label
690 }
691
692 /// Evaluate the name of an operation, or any expression that evaluates to a callable,
693 /// and return the Item ID and function application for the callable.
694 /// Examples: "Microsoft.Quantum.Diagnostics.DumpMachine", "(qs: Qubit[]) => H(qs[0])",
695 /// "Controlled SWAP"
696 fn eval_to_operation(
697 &mut self,
698 operation_expr: &str,
699 ) -> std::result::Result<(&qsc_hir::hir::Item, FunctorApp), Vec<Error>> {
700 let mut sink = std::io::sink();
701 let mut out = GenericReceiver::new(&mut sink);
702 let (store_item_id, functor_app) = match self.eval_fragments(&mut out, operation_expr)? {
703 Value::Closure(b) => (b.id, b.functor),
704 Value::Global(item_id, functor_app) => (item_id, functor_app),
705 _ => return Err(vec![Error::NotAnOperation]),
706 };
707 let package = map_fir_package_to_hir(store_item_id.package);
708 let local_item_id = crate::hir::LocalItemId::from(usize::from(store_item_id.item));
709 let unit = self
710 .compiler
711 .package_store()
712 .get(package)
713 .expect("package should exist in the package store");
714 let item = unit
715 .package
716 .items
717 .get(local_item_id)
718 .expect("item should exist in the package");
719 Ok((item, functor_app))
720 }
721}
722
723fn sim_circuit_backend() -> BackendChain<SparseSim, CircuitBuilder> {
724 BackendChain::new(
725 SparseSim::new(),
726 CircuitBuilder::new(CircuitConfig {
727 // When using in conjunction with the simulator,
728 // the circuit builder should *not* perform base profile
729 // decompositions, in order to match the simulator's behavior.
730 //
731 // Note that conditional compilation (e.g. @Config(Base) attributes)
732 // will still respect the selected profile. This also
733 // matches the behavior of the simulator.
734 base_profile: false,
735 }),
736 )
737}
738
739/// Describes the entry point for circuit generation.
740pub enum CircuitEntryPoint {
741 /// An operation. This must be a callable name or a lambda
742 /// expression that only takes qubits as arguments.
743 /// The callable name must be visible in the current package.
744 Operation(String),
745 /// An explicitly provided entry expression.
746 EntryExpr(String),
747 /// The entry point for the current package.
748 EntryPoint,
749}
750
751/// A debugger that enables step-by-step evaluation of code
752/// and inspecting state in the interpreter.
753pub struct Debugger {
754 interpreter: Interpreter,
755 /// The encoding (utf-8 or utf-16) used for character offsets
756 /// in line/character positions returned by the Interpreter.
757 position_encoding: Encoding,
758 /// The current state of the evaluator.
759 state: State,
760}
761
762impl Debugger {
763 pub fn new(
764 sources: SourceMap,
765 capabilities: TargetCapabilityFlags,
766 position_encoding: Encoding,
767 language_features: LanguageFeatures,
768 store: PackageStore,
769 dependencies: &Dependencies,
770 ) -> std::result::Result<Self, Vec<Error>> {
771 let interpreter = Interpreter::new_with_debug(
772 sources,
773 PackageType::Exe,
774 capabilities,
775 language_features,
776 store,
777 dependencies,
778 )?;
779 let source_package_id = interpreter.source_package;
780 let unit = interpreter.fir_store.get(source_package_id);
781 let entry_exec_graph = unit.entry_exec_graph.clone();
782 Ok(Self {
783 interpreter,
784 position_encoding,
785 state: State::new(source_package_id, entry_exec_graph, None),
786 })
787 }
788
789 /// Resumes execution with specified `StepAction`.
790 /// # Errors
791 /// Returns a vector of errors if evaluating the entry point fails.
792 pub fn eval_step(
793 &mut self,
794 receiver: &mut impl Receiver,
795 breakpoints: &[StmtId],
796 step: StepAction,
797 ) -> std::result::Result<StepResult, Vec<Error>> {
798 self.state
799 .eval(
800 &self.interpreter.fir_store,
801 &mut self.interpreter.env,
802 &mut self.interpreter.sim,
803 receiver,
804 breakpoints,
805 step,
806 )
807 .map_err(|(error, call_stack)| {
808 eval_error(
809 self.interpreter.compiler.package_store(),
810 &self.interpreter.fir_store,
811 call_stack,
812 error,
813 )
814 })
815 }
816
817 #[must_use]
818 pub fn get_stack_frames(&self) -> Vec<StackFrame> {
819 let frames = self.state.get_stack_frames();
820 let stack_frames = frames
821 .iter()
822 .map(|frame| {
823 let callable = self
824 .interpreter
825 .fir_store
826 .get_global(frame.id)
827 .expect("frame should exist");
828 let functor = format!("{}", frame.functor);
829 let name = match callable {
830 Global::Callable(decl) => decl.name.name.to_string(),
831 Global::Udt => "udt".into(),
832 };
833
834 StackFrame {
835 name,
836 functor,
837 location: Location::from(
838 frame.span,
839 map_fir_package_to_hir(frame.id.package),
840 self.interpreter.compiler.package_store(),
841 self.position_encoding,
842 ),
843 }
844 })
845 .collect();
846 stack_frames
847 }
848
849 pub fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
850 self.interpreter.sim.capture_quantum_state()
851 }
852
853 pub fn circuit(&self) -> Circuit {
854 self.interpreter.get_circuit()
855 }
856
857 #[must_use]
858 pub fn get_breakpoints(&self, path: &str) -> Vec<BreakpointSpan> {
859 let unit = self.source_package();
860
861 if let Some(source) = unit.sources.find_by_name(path) {
862 let package = self
863 .interpreter
864 .fir_store
865 .get(self.interpreter.source_package);
866 let mut collector = BreakpointCollector::new(
867 &unit.sources,
868 source.offset,
869 package,
870 self.position_encoding,
871 );
872 collector.visit_package(package, &self.interpreter.fir_store);
873 let mut spans: Vec<_> = collector.statements.into_iter().collect();
874
875 // Sort by start position (line first, column next)
876 spans.sort_by_key(|s| (s.range.start.line, s.range.start.column));
877 spans
878 } else {
879 Vec::new()
880 }
881 }
882
883 #[must_use]
884 pub fn get_locals(&self) -> Vec<VariableInfo> {
885 self.interpreter
886 .env
887 .get_variables_in_top_frame()
888 .into_iter()
889 .filter(|v| !v.name.starts_with('@'))
890 .collect()
891 }
892
893 fn source_package(&self) -> &CompileUnit {
894 self.interpreter
895 .compiler
896 .package_store()
897 .get(map_fir_package_to_hir(self.interpreter.source_package))
898 .expect("Could not load package")
899 }
900}
901
902/// Wrapper function for `qsc_eval::eval` that handles error conversion.
903#[allow(clippy::too_many_arguments)]
904fn eval(
905 package: PackageId,
906 classical_seed: Option<u64>,
907 exec_graph: Rc<[ExecGraphNode]>,
908 package_store: &PackageStore,
909 fir_store: &fir::PackageStore,
910 env: &mut Env,
911 sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
912 receiver: &mut impl Receiver,
913) -> InterpretResult {
914 qsc_eval::eval(
915 package,
916 classical_seed,
917 exec_graph,
918 fir_store,
919 env,
920 sim,
921 receiver,
922 )
923 .map_err(|(error, call_stack)| eval_error(package_store, fir_store, call_stack, error))
924}
925
926/// Represents a stack frame for debugging.
927pub struct StackFrame {
928 /// The name of the callable.
929 pub name: String,
930 /// The functor of the callable.
931 pub functor: String,
932 /// The source location of the call site.
933 pub location: Location,
934}
935
936#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
937pub struct BreakpointSpan {
938 /// The id of the statement representing the breakpoint location.
939 pub id: u32,
940 /// The source range of the call site.
941 pub range: Range,
942}
943
944struct BreakpointCollector<'a> {
945 statements: FxHashSet<BreakpointSpan>,
946 sources: &'a SourceMap,
947 offset: u32,
948 package: &'a Package,
949 position_encoding: Encoding,
950}
951
952impl<'a> BreakpointCollector<'a> {
953 fn new(
954 sources: &'a SourceMap,
955 offset: u32,
956 package: &'a Package,
957 position_encoding: Encoding,
958 ) -> Self {
959 Self {
960 statements: FxHashSet::default(),
961 sources,
962 offset,
963 package,
964 position_encoding,
965 }
966 }
967
968 fn get_source(&self, offset: u32) -> &Source {
969 self.sources
970 .find_by_offset(offset)
971 .expect("Couldn't find source file")
972 }
973
974 fn add_stmt(&mut self, stmt: &qsc_fir::fir::Stmt) {
975 let source: &Source = self.get_source(stmt.span.lo);
976 if source.offset == self.offset {
977 let span = stmt.span - source.offset;
978 if span != Span::default() {
979 let bps = BreakpointSpan {
980 id: stmt.id.into(),
981 range: Range::from_span(self.position_encoding, &source.contents, &span),
982 };
983 self.statements.insert(bps);
984 }
985 }
986 }
987}
988
989impl<'a> Visitor<'a> for BreakpointCollector<'a> {
990 fn visit_stmt(&mut self, stmt: StmtId) {
991 let stmt_res = self.get_stmt(stmt);
992 match stmt_res.kind {
993 fir::StmtKind::Expr(expr) | fir::StmtKind::Local(_, _, expr) => {
994 self.add_stmt(stmt_res);
995 visit::walk_expr(self, expr);
996 }
997 fir::StmtKind::Item(_) | fir::StmtKind::Semi(_) => {
998 self.add_stmt(stmt_res);
999 }
1000 };
1001 }
1002
1003 fn get_block(&self, id: BlockId) -> &'a Block {
1004 self.package
1005 .blocks
1006 .get(id)
1007 .expect("couldn't find block in FIR")
1008 }
1009
1010 fn get_expr(&self, id: ExprId) -> &'a Expr {
1011 self.package
1012 .exprs
1013 .get(id)
1014 .expect("couldn't find expr in FIR")
1015 }
1016
1017 fn get_pat(&self, id: PatId) -> &'a Pat {
1018 self.package.pats.get(id).expect("couldn't find pat in FIR")
1019 }
1020
1021 fn get_stmt(&self, id: StmtId) -> &'a Stmt {
1022 self.package
1023 .stmts
1024 .get(id)
1025 .expect("couldn't find stmt in FIR")
1026 }
1027}
1028
1029fn eval_error(
1030 package_store: &PackageStore,
1031 fir_store: &fir::PackageStore,
1032 call_stack: Vec<Frame>,
1033 error: qsc_eval::Error,
1034) -> Vec<Error> {
1035 let stack_trace = if call_stack.is_empty() {
1036 None
1037 } else {
1038 Some(format_call_stack(
1039 package_store,
1040 fir_store,
1041 call_stack,
1042 &error,
1043 ))
1044 };
1045
1046 vec![error::from_eval(error, package_store, stack_trace).into()]
1047}
1048
1049fn into_errors(errors: Vec<crate::compile::Error>) -> Vec<Error> {
1050 errors
1051 .into_iter()
1052 .map(|error| Error::Compile(error.into_with_source()))
1053 .collect::<Vec<_>>()
1054}
1055