microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
minestarks/copilot

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc/src/interpret.rs

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