microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.9.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc/src/interpret.rs

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