microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cesarzc/hw-provider-package

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc/src/interpret.rs

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