Skip to content

Optimize evaluator perf via Control Flow Graph #1261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion allocator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition.workspace = true
license.workspace = true
version.workspace = true

[dependencies]
[target.'cfg(not(target_family = "wasm"))'.dependencies]
mimalloc-sys = { path = "./mimalloc-sys" }

[lints]
Expand Down
2 changes: 0 additions & 2 deletions compiler/qsc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ qsc_passes = { path = "../qsc_passes" }
qsc_project = { path = "../qsc_project", features = ["fs"] }
rustc-hash = { workspace = true }
thiserror = { workspace = true }

[target.'cfg(not(any(target_family = "wasm")))'.dependencies]
allocator = { path = "../../allocator" }

[dev-dependencies]
Expand Down
107 changes: 55 additions & 52 deletions compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ mod tests;
#[cfg(test)]
mod debugger_tests;

use std::rc::Rc;

pub use qsc_eval::{
debug::Frame,
output::{self, GenericReceiver},
val::Closure,
val::Range as ValueRange,
val::Result,
val::Value,
StepAction, StepResult,
Expand All @@ -37,9 +41,9 @@ use qsc_eval::{
debug::{map_fir_package_to_hir, map_hir_package_to_fir},
output::Receiver,
val::{self},
Env, EvalId, State, VariableInfo,
Env, State, VariableInfo,
};
use qsc_fir::fir::{self, Global, PackageStoreLookup};
use qsc_fir::fir::{self, ExecGraphNode, Global, PackageStoreLookup};
use qsc_fir::{
fir::{Block, BlockId, Expr, ExprId, Package, PackageId, Pat, PatId, Stmt, StmtId},
visit::{self, Visitor},
Expand Down Expand Up @@ -173,11 +177,11 @@ impl Interpreter {
&mut self,
receiver: &mut impl Receiver,
) -> std::result::Result<Value, Vec<Error>> {
let expr = self.get_entry_expr()?;
let graph = self.get_entry_exec_graph()?;
eval(
self.source_package,
self.classical_seed,
expr.into(),
graph,
self.compiler.package_store(),
&self.fir_store,
&mut Env::default(),
Expand All @@ -193,14 +197,14 @@ impl Interpreter {
sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
receiver: &mut impl Receiver,
) -> std::result::Result<Value, Vec<Error>> {
let expr = self.get_entry_expr()?;
let graph = self.get_entry_exec_graph()?;
if self.quantum_seed.is_some() {
sim.set_seed(self.quantum_seed);
}
eval(
self.source_package,
self.classical_seed,
expr.into(),
graph,
self.compiler.package_store(),
&self.fir_store,
&mut Env::default(),
Expand All @@ -209,10 +213,10 @@ impl Interpreter {
)
}

fn get_entry_expr(&self) -> std::result::Result<ExprId, Vec<Error>> {
fn get_entry_exec_graph(&self) -> std::result::Result<Rc<[ExecGraphNode]>, Vec<Error>> {
let unit = self.fir_store.get(self.source_package);
if let Some(entry) = unit.entry {
return Ok(entry);
if unit.entry.is_some() {
return Ok(unit.entry_exec_graph.clone());
};
Err(vec![Error::NoEntryPoint])
}
Expand All @@ -233,7 +237,7 @@ impl Interpreter {
.compile_fragments_fail_fast(&label, fragments)
.map_err(into_errors)?;

let stmts = self.lower(&increment);
let (_, graph) = self.lower(&increment);

// Updating the compiler state with the new AST/HIR nodes
// is not necessary for the interpreter to function, as all
Expand All @@ -243,22 +247,16 @@ impl Interpreter {
// here to keep the package stores consistent.
self.compiler.update(increment);

let mut result = Value::unit();

for stmt_id in stmts {
result = eval(
self.package,
self.classical_seed,
stmt_id.into(),
self.compiler.package_store(),
&self.fir_store,
&mut self.env,
&mut self.sim,
receiver,
)?;
}

Ok(result)
eval(
self.package,
self.classical_seed,
graph.into(),
self.compiler.package_store(),
&self.fir_store,
&mut self.env,
&mut self.sim,
receiver,
)
}

/// Runs the given entry expression on a new instance of the environment and simulator,
Expand Down Expand Up @@ -300,7 +298,7 @@ impl Interpreter {
receiver: &mut impl Receiver,
expr: &str,
) -> std::result::Result<InterpretResult, Vec<Error>> {
let expr_id = self.compile_entry_expr(expr)?;
let graph = self.compile_entry_expr(expr)?;

if self.quantum_seed.is_some() {
sim.set_seed(self.quantum_seed);
Expand All @@ -309,7 +307,7 @@ impl Interpreter {
Ok(eval(
self.package,
self.classical_seed,
expr_id.into(),
graph.into(),
self.compiler.package_store(),
&self.fir_store,
&mut Env::default(),
Expand All @@ -318,15 +316,18 @@ impl Interpreter {
))
}

fn compile_entry_expr(&mut self, expr: &str) -> std::result::Result<ExprId, Vec<Error>> {
fn compile_entry_expr(
&mut self,
expr: &str,
) -> std::result::Result<Vec<ExecGraphNode>, Vec<Error>> {
let increment = self
.compiler
.compile_entry_expr(expr)
.map_err(into_errors)?;

// `lower` will update the entry expression in the FIR store,
// and it will always return an empty list of statements.
let _ = self.lower(&increment);
let (_, graph) = self.lower(&increment);

// The AST and HIR packages in `increment` only contain an entry
// expression and no statements. The HIR *can* contain items if the entry
Expand All @@ -342,16 +343,19 @@ impl Interpreter {
// here to keep the package stores consistent.
self.compiler.update(increment);

let unit = self.fir_store.get(self.package);
let entry = unit.entry.expect("package should have an entry expression");

Ok(entry)
Ok(graph)
}

fn lower(&mut self, unit_addition: &qsc_frontend::incremental::Increment) -> Vec<StmtId> {
fn lower(
&mut self,
unit_addition: &qsc_frontend::incremental::Increment,
) -> (Vec<StmtId>, Vec<ExecGraphNode>) {
let fir_package = self.fir_store.get_mut(self.package);
self.lowerer
.lower_and_update_package(fir_package, &unit_addition.hir)
(
self.lowerer
.lower_and_update_package(fir_package, &unit_addition.hir),
self.lowerer.take_exec_graph(),
)
}

fn next_line_label(&mut self) -> String {
Expand Down Expand Up @@ -387,24 +391,15 @@ impl Debugger {
language_features,
)?;
let source_package_id = interpreter.source_package;
let unit = interpreter.fir_store.get(source_package_id);
let entry_exec_graph = unit.entry_exec_graph.clone();
Ok(Self {
interpreter,
position_encoding,
state: State::new(source_package_id, None),
state: State::new(source_package_id, entry_exec_graph, None),
})
}

/// Loads the entry expression to the top of the evaluation stack.
/// This is needed for debugging so that when begging to debug with
/// a step action the system is already in the correct state.
/// # Errors
/// Returns a vector of errors if loading the entry point fails.
pub fn set_entry(&mut self) -> std::result::Result<(), Vec<Error>> {
let expr = self.interpreter.get_entry_expr()?;
qsc_eval::eval_push_expr(&mut self.state, expr);
Ok(())
}

/// Resumes execution with specified `StepAction`.
/// # Errors
/// Returns a vector of errors if evaluating the entry point fails.
Expand Down Expand Up @@ -527,15 +522,23 @@ impl Debugger {
fn eval(
package: PackageId,
classical_seed: Option<u64>,
id: EvalId,
exec_graph: Rc<[ExecGraphNode]>,
package_store: &PackageStore,
fir_store: &fir::PackageStore,
env: &mut Env,
sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
receiver: &mut impl Receiver,
) -> InterpretResult {
qsc_eval::eval(package, classical_seed, id, fir_store, env, sim, receiver)
.map_err(|(error, call_stack)| eval_error(package_store, fir_store, call_stack, error))
qsc_eval::eval(
package,
classical_seed,
exec_graph,
fir_store,
env,
sim,
receiver,
)
.map_err(|(error, call_stack)| eval_error(package_store, fir_store, call_stack, error))
}

/// Represents a stack frame for debugging.
Expand Down
4 changes: 0 additions & 4 deletions compiler/qsc/src/interpret/debugger_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ mod given_debugger {
Encoding::Utf8,
LanguageFeatures::default(),
)?;
debugger.set_entry()?;
let ids = get_breakpoint_ids(&debugger, "test");
let expected_id = ids[0];
expect_bp(&mut debugger, &ids, expected_id);
Expand All @@ -159,7 +158,6 @@ mod given_debugger {
Encoding::Utf8,
LanguageFeatures::default(),
)?;
debugger.set_entry()?;
let ids = get_breakpoint_ids(&debugger, "test");
let expected_id = ids[0];
expect_bp(&mut debugger, &ids, expected_id);
Expand All @@ -179,7 +177,6 @@ mod given_debugger {
Encoding::Utf8,
LanguageFeatures::default(),
)?;
debugger.set_entry()?;
let ids = get_breakpoint_ids(&debugger, "test");
let expected_id = ids[0];
expect_bp(&mut debugger, &ids, expected_id);
Expand All @@ -206,7 +203,6 @@ mod given_debugger {
Encoding::Utf8,
LanguageFeatures::default(),
)?;
debugger.set_entry()?;
let ids = get_breakpoint_ids(&debugger, "test");
let expected_id = ids[0];
expect_bp(&mut debugger, &ids, expected_id);
Expand Down
3 changes: 1 addition & 2 deletions compiler/qsc_codegen/src/qir_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,14 @@ pub fn generate_qir(

let package = map_hir_package_to_fir(package);
let unit = fir_store.get(package);
let entry_expr = unit.entry.expect("package should have entry");

let mut sim = BaseProfSim::default();
let mut stdout = std::io::sink();
let mut out = GenericReceiver::new(&mut stdout);
let result = eval(
package,
None,
entry_expr.into(),
unit.entry_exec_graph.clone(),
&fir_store,
&mut Env::default(),
&mut sim,
Expand Down
8 changes: 5 additions & 3 deletions compiler/qsc_eval/src/intrinsic/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use std::f64::consts;

use crate::backend::{Backend, SparseSim};
use crate::debug::map_hir_package_to_fir;
use crate::tests::eval_expr;
use crate::tests::eval_graph;
use crate::Env;
use crate::{
output::{GenericReceiver, Receiver},
val::Value,
Expand Down Expand Up @@ -180,7 +181,7 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result<Va
)
.is_empty());
let unit_fir = fir_lowerer.lower_package(&unit.package);
let entry = unit_fir.entry.expect("package should have entry");
let entry = unit_fir.entry_exec_graph.clone();

let id = store.insert(unit);

Expand All @@ -192,11 +193,12 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result<Va
fir_store.insert(map_hir_package_to_fir(std_id), std_fir);
fir_store.insert(map_hir_package_to_fir(id), unit_fir);

eval_expr(
eval_graph(
entry,
&mut CustomSim::default(),
&fir_store,
map_hir_package_to_fir(id),
&mut Env::default(),
out,
)
.map_err(|e| e.0)
Expand Down
Loading