microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.3.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc/src/interpret.rs

672lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4mod debug;
5
6#[cfg(test)]
7mod tests;
8
9#[cfg(test)]
10mod debugger_tests;
11
12use std::rc::Rc;
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};
23
24use crate::{
25 error::{self, WithStack},
26 incremental::Compiler,
27 location::Location,
28};
29use debug::format_call_stack;
30use miette::Diagnostic;
31use num_bigint::BigUint;
32use num_complex::Complex;
33use qsc_codegen::qir_base::BaseProfSim;
34use qsc_data_structures::{
35 language_features::LanguageFeatures,
36 line_column::{Encoding, Range},
37 span::Span,
38};
39use qsc_eval::{
40 backend::{Backend, SparseSim},
41 debug::{map_fir_package_to_hir, map_hir_package_to_fir},
42 output::Receiver,
43 val::{self},
44 Env, State, VariableInfo,
45};
46use qsc_fir::fir::{self, ExecGraphNode, Global, PackageStoreLookup};
47use qsc_fir::{
48 fir::{Block, BlockId, Expr, ExprId, Package, PackageId, Pat, PatId, Stmt, StmtId},
49 visit::{self, Visitor},
50};
51use qsc_frontend::{
52 compile::{CompileUnit, PackageStore, RuntimeCapabilityFlags, Source, SourceMap},
53 error::WithSource,
54};
55use qsc_passes::PackageType;
56use rustc_hash::FxHashSet;
57use thiserror::Error;
58
59impl Error {
60 #[must_use]
61 pub fn stack_trace(&self) -> &Option<String> {
62 match &self {
63 Error::Eval(err) => err.stack_trace(),
64 _ => &None,
65 }
66 }
67}
68
69#[derive(Clone, Debug, Diagnostic, Error)]
70pub enum Error {
71 #[error(transparent)]
72 #[diagnostic(transparent)]
73 Compile(#[from] crate::compile::Error),
74 #[error(transparent)]
75 #[diagnostic(transparent)]
76 Pass(#[from] WithSource<qsc_passes::Error>),
77 #[error("runtime error")]
78 #[diagnostic(transparent)]
79 Eval(#[from] WithStack<WithSource<qsc_eval::Error>>),
80 #[error("entry point not found")]
81 #[diagnostic(code("Qsc.Interpret.NoEntryPoint"))]
82 NoEntryPoint,
83 #[error("unsupported runtime capabilities for code generation")]
84 #[diagnostic(code("Qsc.Interpret.UnsupportedRuntimeCapabilities"))]
85 UnsupportedRuntimeCapabilities,
86}
87
88/// A Q# interpreter.
89pub struct Interpreter {
90 /// The incremental Q# compiler.
91 compiler: Compiler,
92 /// The runtime capabilities used for compilation.
93 capabilities: RuntimeCapabilityFlags,
94 /// The number of lines that have so far been compiled.
95 /// This field is used to generate a unique label
96 /// for each line evaluated with `eval_fragments`.
97 lines: u32,
98 // The FIR store
99 fir_store: fir::PackageStore,
100 /// FIR lowerer
101 lowerer: qsc_eval::lower::Lowerer,
102 /// The ID of the current package.
103 /// This ID is valid both for the FIR store and the `PackageStore`.
104 package: PackageId,
105 /// The ID of the source package. The source package
106 /// is made up of the initial sources passed in when creating the interpreter.
107 /// This ID is valid both for the FIR store and the `PackageStore`.
108 source_package: PackageId,
109 /// The default simulator backend.
110 sim: SparseSim,
111 /// The quantum seed, if any. This is cached here so that it can be used in calls to
112 /// `run_internal` which use a passed instance of the simulator instead of the one above.
113 quantum_seed: Option<u64>,
114 /// The classical seed, if any. This needs to be passed to the evaluator for use in intrinsic
115 /// calls that produce classical random numbers.
116 classical_seed: Option<u64>,
117 /// The evaluator environment.
118 env: Env,
119}
120
121pub type InterpretResult = std::result::Result<Value, Vec<Error>>;
122
123impl Interpreter {
124 /// Creates a new incremental compiler, compiling the passed in sources.
125 /// # Errors
126 /// If compiling the sources fails, compiler errors are returned.
127 pub fn new(
128 std: bool,
129 sources: SourceMap,
130 package_type: PackageType,
131 capabilities: RuntimeCapabilityFlags,
132 language_features: LanguageFeatures,
133 ) -> std::result::Result<Self, Vec<Error>> {
134 let mut lowerer = qsc_eval::lower::Lowerer::new();
135 let mut fir_store = fir::PackageStore::new();
136
137 let compiler = Compiler::new(std, sources, package_type, capabilities, language_features)
138 .map_err(into_errors)?;
139
140 for (id, unit) in compiler.package_store() {
141 fir_store.insert(
142 map_hir_package_to_fir(id),
143 lowerer.lower_package(&unit.package),
144 );
145 }
146
147 let source_package_id = compiler.source_package_id();
148 let package_id = compiler.package_id();
149
150 Ok(Self {
151 compiler,
152 lines: 0,
153 capabilities,
154 fir_store,
155 lowerer,
156 env: Env::default(),
157 sim: SparseSim::new(),
158 quantum_seed: None,
159 classical_seed: None,
160 package: map_hir_package_to_fir(package_id),
161 source_package: map_hir_package_to_fir(source_package_id),
162 })
163 }
164
165 pub fn set_quantum_seed(&mut self, seed: Option<u64>) {
166 self.quantum_seed = seed;
167 self.sim.set_seed(seed);
168 }
169
170 pub fn set_classical_seed(&mut self, seed: Option<u64>) {
171 self.classical_seed = seed;
172 }
173 /// Executes the entry expression until the end of execution.
174 /// # Errors
175 /// Returns a vector of errors if evaluating the entry point fails.
176 pub fn eval_entry(
177 &mut self,
178 receiver: &mut impl Receiver,
179 ) -> std::result::Result<Value, Vec<Error>> {
180 let graph = self.get_entry_exec_graph()?;
181 eval(
182 self.source_package,
183 self.classical_seed,
184 graph,
185 self.compiler.package_store(),
186 &self.fir_store,
187 &mut Env::default(),
188 &mut self.sim,
189 receiver,
190 )
191 }
192
193 /// Executes the entry expression until the end of execution, using the given simulator backend
194 /// and a new instance of the environment.
195 pub fn eval_entry_with_sim(
196 &mut self,
197 sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
198 receiver: &mut impl Receiver,
199 ) -> std::result::Result<Value, Vec<Error>> {
200 let graph = self.get_entry_exec_graph()?;
201 if self.quantum_seed.is_some() {
202 sim.set_seed(self.quantum_seed);
203 }
204 eval(
205 self.source_package,
206 self.classical_seed,
207 graph,
208 self.compiler.package_store(),
209 &self.fir_store,
210 &mut Env::default(),
211 sim,
212 receiver,
213 )
214 }
215
216 fn get_entry_exec_graph(&self) -> std::result::Result<Rc<[ExecGraphNode]>, Vec<Error>> {
217 let unit = self.fir_store.get(self.source_package);
218 if unit.entry.is_some() {
219 return Ok(unit.entry_exec_graph.clone());
220 };
221 Err(vec![Error::NoEntryPoint])
222 }
223
224 /// # Errors
225 /// If the parsing of the fragments fails, an error is returned.
226 /// If the compilation of the fragments fails, an error is returned.
227 /// If there is a runtime error when interpreting the fragments, an error is returned.
228 pub fn eval_fragments(
229 &mut self,
230 receiver: &mut impl Receiver,
231 fragments: &str,
232 ) -> InterpretResult {
233 let label = self.next_line_label();
234
235 let increment = self
236 .compiler
237 .compile_fragments_fail_fast(&label, fragments)
238 .map_err(into_errors)?;
239
240 let (_, graph) = self.lower(&increment);
241
242 // Updating the compiler state with the new AST/HIR nodes
243 // is not necessary for the interpreter to function, as all
244 // the state required for evaluation already exists in the
245 // FIR store. It could potentially save some memory
246 // *not* to do hold on to the AST/HIR, but it is done
247 // here to keep the package stores consistent.
248 self.compiler.update(increment);
249
250 eval(
251 self.package,
252 self.classical_seed,
253 graph.into(),
254 self.compiler.package_store(),
255 &self.fir_store,
256 &mut self.env,
257 &mut self.sim,
258 receiver,
259 )
260 }
261
262 /// Runs the given entry expression on a new instance of the environment and simulator,
263 /// but using the current compilation.
264 pub fn run(
265 &mut self,
266 receiver: &mut impl Receiver,
267 expr: &str,
268 ) -> std::result::Result<InterpretResult, Vec<Error>> {
269 self.run_with_sim(&mut SparseSim::new(), receiver, expr)
270 }
271
272 /// Gets the current quantum state of the simulator.
273 pub fn get_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
274 self.sim.capture_quantum_state()
275 }
276
277 /// Performs QIR codegen using the given entry expression on a new instance of the environment
278 /// and simulator but using the current compilation.
279 pub fn qirgen(&mut self, expr: &str) -> std::result::Result<String, Vec<Error>> {
280 if self.capabilities != RuntimeCapabilityFlags::empty() {
281 return Err(vec![Error::UnsupportedRuntimeCapabilities]);
282 }
283
284 let mut sim = BaseProfSim::new();
285 let mut stdout = std::io::sink();
286 let mut out = GenericReceiver::new(&mut stdout);
287
288 let val = self.run_with_sim(&mut sim, &mut out, expr)??;
289
290 Ok(sim.finish(&val))
291 }
292
293 /// Runs the given entry expression on the given simulator with a new instance of the environment
294 /// but using the current compilation.
295 pub fn run_with_sim(
296 &mut self,
297 sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
298 receiver: &mut impl Receiver,
299 expr: &str,
300 ) -> std::result::Result<InterpretResult, Vec<Error>> {
301 let graph = self.compile_entry_expr(expr)?;
302
303 if self.quantum_seed.is_some() {
304 sim.set_seed(self.quantum_seed);
305 }
306
307 Ok(eval(
308 self.package,
309 self.classical_seed,
310 graph.into(),
311 self.compiler.package_store(),
312 &self.fir_store,
313 &mut Env::default(),
314 sim,
315 receiver,
316 ))
317 }
318
319 fn compile_entry_expr(
320 &mut self,
321 expr: &str,
322 ) -> std::result::Result<Vec<ExecGraphNode>, Vec<Error>> {
323 let increment = self
324 .compiler
325 .compile_entry_expr(expr)
326 .map_err(into_errors)?;
327
328 // `lower` will update the entry expression in the FIR store,
329 // and it will always return an empty list of statements.
330 let (_, graph) = self.lower(&increment);
331
332 // The AST and HIR packages in `increment` only contain an entry
333 // expression and no statements. The HIR *can* contain items if the entry
334 // expression defined any items.
335 assert!(increment.hir.stmts.is_empty());
336 assert!(increment.ast.package.nodes.is_empty());
337
338 // Updating the compiler state with the new AST/HIR nodes
339 // is not necessary for the interpreter to function, as all
340 // the state required for evaluation already exists in the
341 // FIR store. It could potentially save some memory
342 // *not* to do hold on to the AST/HIR, but it is done
343 // here to keep the package stores consistent.
344 self.compiler.update(increment);
345
346 Ok(graph)
347 }
348
349 fn lower(
350 &mut self,
351 unit_addition: &qsc_frontend::incremental::Increment,
352 ) -> (Vec<StmtId>, Vec<ExecGraphNode>) {
353 let fir_package = self.fir_store.get_mut(self.package);
354 (
355 self.lowerer
356 .lower_and_update_package(fir_package, &unit_addition.hir),
357 self.lowerer.take_exec_graph(),
358 )
359 }
360
361 fn next_line_label(&mut self) -> String {
362 let label = format!("line_{}", self.lines);
363 self.lines += 1;
364 label
365 }
366}
367
368/// A debugger that enables step-by-step evaluation of code
369/// and inspecting state in the interpreter.
370pub struct Debugger {
371 interpreter: Interpreter,
372 /// The encoding (utf-8 or utf-16) used for character offsets
373 /// in line/character positions returned by the Interpreter.
374 position_encoding: Encoding,
375 /// The current state of the evaluator.
376 state: State,
377}
378
379impl Debugger {
380 pub fn new(
381 sources: SourceMap,
382 capabilities: RuntimeCapabilityFlags,
383 position_encoding: Encoding,
384 language_features: LanguageFeatures,
385 ) -> std::result::Result<Self, Vec<Error>> {
386 let interpreter = Interpreter::new(
387 true,
388 sources,
389 PackageType::Exe,
390 capabilities,
391 language_features,
392 )?;
393 let source_package_id = interpreter.source_package;
394 let unit = interpreter.fir_store.get(source_package_id);
395 let entry_exec_graph = unit.entry_exec_graph.clone();
396 Ok(Self {
397 interpreter,
398 position_encoding,
399 state: State::new(source_package_id, entry_exec_graph, None),
400 })
401 }
402
403 /// Resumes execution with specified `StepAction`.
404 /// # Errors
405 /// Returns a vector of errors if evaluating the entry point fails.
406 pub fn eval_step(
407 &mut self,
408 receiver: &mut impl Receiver,
409 breakpoints: &[StmtId],
410 step: StepAction,
411 ) -> std::result::Result<StepResult, Vec<Error>> {
412 self.state
413 .eval(
414 &self.interpreter.fir_store,
415 &mut self.interpreter.env,
416 &mut self.interpreter.sim,
417 receiver,
418 breakpoints,
419 step,
420 )
421 .map_err(|(error, call_stack)| {
422 eval_error(
423 self.interpreter.compiler.package_store(),
424 &self.interpreter.fir_store,
425 call_stack,
426 error,
427 )
428 })
429 }
430
431 #[must_use]
432 pub fn get_stack_frames(&self) -> Vec<StackFrame> {
433 let frames = self.state.get_stack_frames();
434 let stack_frames = frames
435 .iter()
436 .map(|frame| {
437 let callable = self
438 .interpreter
439 .fir_store
440 .get_global(frame.id)
441 .expect("frame should exist");
442 let functor = format!("{}", frame.functor);
443 let name = match callable {
444 Global::Callable(decl) => decl.name.name.to_string(),
445 Global::Udt => "udt".into(),
446 };
447
448 StackFrame {
449 name,
450 functor,
451 location: Location::from(
452 frame.span,
453 map_fir_package_to_hir(frame.id.package),
454 self.interpreter.compiler.package_store(),
455 map_fir_package_to_hir(self.interpreter.source_package),
456 self.position_encoding,
457 ),
458 }
459 })
460 .collect();
461 stack_frames
462 }
463
464 pub fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
465 self.interpreter.sim.capture_quantum_state()
466 }
467
468 #[must_use]
469 pub fn get_breakpoints(&self, path: &str) -> Vec<BreakpointSpan> {
470 let unit = self.source_package();
471
472 if let Some(source) = unit.sources.find_by_name(path) {
473 let package = self
474 .interpreter
475 .fir_store
476 .get(self.interpreter.source_package);
477 let mut collector = BreakpointCollector::new(
478 &unit.sources,
479 source.offset,
480 package,
481 self.position_encoding,
482 );
483 collector.visit_package(package);
484 let mut spans: Vec<_> = collector
485 .statements
486 .iter()
487 .map(|bps| BreakpointSpan {
488 id: bps.id,
489 range: bps.range,
490 })
491 .collect();
492
493 // Sort by start position (line first, column next)
494 spans.sort_by_key(|s| (s.range.start.line, s.range.start.column));
495 spans
496 } else {
497 Vec::new()
498 }
499 }
500
501 #[must_use]
502 pub fn get_locals(&self) -> Vec<VariableInfo> {
503 self.interpreter
504 .env
505 .get_variables_in_top_frame()
506 .into_iter()
507 .filter(|v| !v.name.starts_with('@'))
508 .collect()
509 }
510
511 fn source_package(&self) -> &CompileUnit {
512 self.interpreter
513 .compiler
514 .package_store()
515 .get(map_fir_package_to_hir(self.interpreter.source_package))
516 .expect("Could not load package")
517 }
518}
519
520/// Wrapper function for `qsc_eval::eval` that handles error conversion.
521#[allow(clippy::too_many_arguments)]
522fn eval(
523 package: PackageId,
524 classical_seed: Option<u64>,
525 exec_graph: Rc<[ExecGraphNode]>,
526 package_store: &PackageStore,
527 fir_store: &fir::PackageStore,
528 env: &mut Env,
529 sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
530 receiver: &mut impl Receiver,
531) -> InterpretResult {
532 qsc_eval::eval(
533 package,
534 classical_seed,
535 exec_graph,
536 fir_store,
537 env,
538 sim,
539 receiver,
540 )
541 .map_err(|(error, call_stack)| eval_error(package_store, fir_store, call_stack, error))
542}
543
544/// Represents a stack frame for debugging.
545pub struct StackFrame {
546 /// The name of the callable.
547 pub name: String,
548 /// The functor of the callable.
549 pub functor: String,
550 /// The source location of the call site.
551 pub location: Location,
552}
553
554#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
555pub struct BreakpointSpan {
556 /// The id of the statement representing the breakpoint location.
557 pub id: u32,
558 /// The source range of the call site.
559 pub range: Range,
560}
561
562struct BreakpointCollector<'a> {
563 statements: FxHashSet<BreakpointSpan>,
564 sources: &'a SourceMap,
565 offset: u32,
566 package: &'a Package,
567 position_encoding: Encoding,
568}
569
570impl<'a> BreakpointCollector<'a> {
571 fn new(
572 sources: &'a SourceMap,
573 offset: u32,
574 package: &'a Package,
575 position_encoding: Encoding,
576 ) -> Self {
577 Self {
578 statements: FxHashSet::default(),
579 sources,
580 offset,
581 package,
582 position_encoding,
583 }
584 }
585
586 fn get_source(&self, offset: u32) -> &Source {
587 self.sources
588 .find_by_offset(offset)
589 .expect("Couldn't find source file")
590 }
591
592 fn add_stmt(&mut self, stmt: &qsc_fir::fir::Stmt) {
593 let source: &Source = self.get_source(stmt.span.lo);
594 if source.offset == self.offset {
595 let span = stmt.span - source.offset;
596 if span != Span::default() {
597 let bps = BreakpointSpan {
598 id: stmt.id.into(),
599 range: Range::from_span(self.position_encoding, &source.contents, &span),
600 };
601 self.statements.insert(bps);
602 }
603 }
604 }
605}
606
607impl<'a> Visitor<'a> for BreakpointCollector<'a> {
608 fn visit_stmt(&mut self, stmt: StmtId) {
609 let stmt_res = self.get_stmt(stmt);
610 match stmt_res.kind {
611 qsc_fir::fir::StmtKind::Expr(expr) | qsc_fir::fir::StmtKind::Local(_, _, expr) => {
612 self.add_stmt(stmt_res);
613 visit::walk_expr(self, expr);
614 }
615 qsc_fir::fir::StmtKind::Item(_) | qsc_fir::fir::StmtKind::Semi(_) => {
616 self.add_stmt(stmt_res);
617 }
618 };
619 }
620
621 fn get_block(&self, id: BlockId) -> &'a Block {
622 self.package
623 .blocks
624 .get(id)
625 .expect("couldn't find block in FIR")
626 }
627
628 fn get_expr(&self, id: ExprId) -> &'a Expr {
629 self.package
630 .exprs
631 .get(id)
632 .expect("couldn't find expr in FIR")
633 }
634
635 fn get_pat(&self, id: PatId) -> &'a Pat {
636 self.package.pats.get(id).expect("couldn't find pat in FIR")
637 }
638
639 fn get_stmt(&self, id: StmtId) -> &'a Stmt {
640 self.package
641 .stmts
642 .get(id)
643 .expect("couldn't find stmt in FIR")
644 }
645}
646
647fn eval_error(
648 package_store: &PackageStore,
649 fir_store: &fir::PackageStore,
650 call_stack: Vec<Frame>,
651 error: qsc_eval::Error,
652) -> Vec<Error> {
653 let stack_trace = if call_stack.is_empty() {
654 None
655 } else {
656 Some(format_call_stack(
657 package_store,
658 fir_store,
659 call_stack,
660 &error,
661 ))
662 };
663
664 vec![error::from_eval(error, package_store, stack_trace).into()]
665}
666
667fn into_errors(errors: Vec<crate::compile::Error>) -> Vec<Error> {
668 errors
669 .into_iter()
670 .map(|error| Error::Compile(error.into_with_source()))
671 .collect::<Vec<_>>()
672}
673