microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/fix-2145

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc/src/interpret.rs

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