microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.1.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc/src/interpret.rs

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