Skip to content

Commit 3086821

Browse files
authored
Refactor evaluator to reduce stack use (#294)
This change refactors the evaluator to use a "stackless" design where the stack is tracked manually via data structures instead of via literal progam stack. This allows for better avoidance of stack overflow at the cost of some performance. To help track performance characteristics, this also introduces benchmarks based on evaluating some programs from the samples folder.
1 parent 466a089 commit 3086821

File tree

17 files changed

+1534
-1497
lines changed

17 files changed

+1534
-1497
lines changed

compiler/qsc/Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,25 @@ thiserror = { workspace = true }
2626
criterion = { workspace = true, features = ["cargo_bench_support"] }
2727
indoc = { workspace = true }
2828

29+
[lib]
30+
bench = false
31+
32+
[[bin]]
33+
name = "qsc"
34+
bench = false
35+
36+
[[bin]]
37+
name = "qsi"
38+
bench = false
39+
2940
[[bench]]
3041
name = "nqueens"
3142
harness = false
3243

3344
[[bench]]
3445
name = "library"
3546
harness = false
47+
48+
[[bench]]
49+
name = "eval"
50+
harness = false

compiler/qsc/benches/eval.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use criterion::{criterion_group, criterion_main, Criterion};
5+
use qsc::interpret::stateless;
6+
use qsc_eval::output::GenericReceiver;
7+
use qsc_frontend::compile::SourceMap;
8+
9+
const TELEPORT: &str = include_str!("../../../samples/Teleportation.qs");
10+
const DEUTSCHJOZSA: &str = include_str!("../../../samples/DeutschJozsa.qs");
11+
12+
pub fn teleport(c: &mut Criterion) {
13+
c.bench_function("Teleport evaluation", |b| {
14+
let sources = SourceMap::new([("Teleportation.qs".into(), TELEPORT.into())], None);
15+
let evaluator = stateless::Context::new(true, sources).expect("code should compile");
16+
b.iter(move || {
17+
let mut out = Vec::new();
18+
let mut rec = GenericReceiver::new(&mut out);
19+
assert!(evaluator.eval(&mut rec).is_ok());
20+
})
21+
});
22+
}
23+
24+
pub fn deutsch_jozsa(c: &mut Criterion) {
25+
c.bench_function("Deutsch-Jozsa evaluation", |b| {
26+
let sources = SourceMap::new([("DeutschJozsa.qs".into(), DEUTSCHJOZSA.into())], None);
27+
let evaluator = stateless::Context::new(true, sources).expect("code should compile");
28+
b.iter(move || {
29+
let mut out = Vec::new();
30+
let mut rec = GenericReceiver::new(&mut out);
31+
assert!(evaluator.eval(&mut rec).is_ok());
32+
})
33+
});
34+
}
35+
36+
criterion_group!(benches, teleport, deutsch_jozsa);
37+
criterion_main!(benches);

compiler/qsc/src/compile.rs

+4-14
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
use crate::error::WithSource;
55
use miette::{Diagnostic, Report};
66
use qsc_frontend::compile::{CompileUnit, PackageStore, SourceMap};
7-
use qsc_hir::{global, hir::PackageId};
8-
use qsc_passes::run_default_passes;
7+
use qsc_hir::hir::PackageId;
8+
use qsc_passes::{run_core_passes, run_default_passes};
99
use thiserror::Error;
1010

1111
#[derive(Clone, Debug, Diagnostic, Error)]
@@ -45,18 +45,8 @@ pub fn compile(
4545
#[must_use]
4646
pub fn core() -> CompileUnit {
4747
let mut unit = qsc_frontend::compile::core();
48-
let table = global::iter_package(None, &unit.package).collect();
49-
let pass_errors = run_default_passes(&table, &mut unit);
50-
if pass_errors.is_empty() {
51-
unit
52-
} else {
53-
for error in pass_errors {
54-
let report = Report::new(WithSource::from_map(&unit.sources, error, None));
55-
eprintln!("{report:?}");
56-
}
57-
58-
panic!("could not compile core library")
59-
}
48+
run_core_passes(&mut unit);
49+
unit
6050
}
6151

6252
/// Compiles the standard library.

compiler/qsc/src/interpret/stateful/tests.rs

+17
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,23 @@ mod given_interpreter {
222222
);
223223
is_unit_with_output(&result, &output, "before\nafter");
224224
}
225+
226+
#[test]
227+
fn global_qubits() {
228+
let mut interpreter = get_interpreter();
229+
let (result, output) = line(&mut interpreter, "open Microsoft.Quantum.Diagnostics;");
230+
is_only_value(&result, &output, &Value::unit());
231+
let (result, output) = line(&mut interpreter, "DumpMachine()");
232+
is_unit_with_output(&result, &output, "STATE:\n|0⟩: 1+0i");
233+
let (result, output) = line(&mut interpreter, "use (q0, qs) = (Qubit(), Qubit[3]);");
234+
is_only_value(&result, &output, &Value::unit());
235+
let (result, output) = line(&mut interpreter, "DumpMachine()");
236+
is_unit_with_output(&result, &output, "STATE:\n|0000⟩: 1+0i");
237+
let (result, output) = line(&mut interpreter, "X(q0); X(qs[1]);");
238+
is_only_value(&result, &output, &Value::unit());
239+
let (result, output) = line(&mut interpreter, "DumpMachine()");
240+
is_unit_with_output(&result, &output, "STATE:\n|0101⟩: 1+0i");
241+
}
225242
}
226243

227244
#[cfg(test)]

0 commit comments

Comments
 (0)