Skip to content

Commit cf9f212

Browse files
committed
Support partial evaluation of sub graphs, add tests
1 parent 72259d3 commit cf9f212

File tree

5 files changed

+627
-24
lines changed

5 files changed

+627
-24
lines changed

compiler/qsc_eval/src/intrinsic/tests.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use std::f64::consts;
77

88
use crate::backend::{Backend, SparseSim};
99
use crate::debug::map_hir_package_to_fir;
10-
use crate::tests::eval_expr;
10+
use crate::tests::eval_cfg;
11+
use crate::Env;
1112
use crate::{
1213
output::{GenericReceiver, Receiver},
1314
val::Value,
@@ -192,11 +193,12 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result<Va
192193
fir_store.insert(map_hir_package_to_fir(std_id), std_fir);
193194
fir_store.insert(map_hir_package_to_fir(id), unit_fir);
194195

195-
eval_expr(
196+
eval_cfg(
196197
entry,
197198
&mut CustomSim::default(),
198199
&fir_store,
199200
map_hir_package_to_fir(id),
201+
&mut Env::default(),
200202
out,
201203
)
202204
.map_err(|e| e.0)

compiler/qsc_eval/src/lib.rs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use qsc_fir::fir::{
2828
};
2929
use qsc_fir::ty::Ty;
3030
use rand::{rngs::StdRng, SeedableRng};
31+
use std::ops;
3132
use std::{
3233
cell::RefCell,
3334
fmt::{self, Display, Formatter},
@@ -172,6 +173,26 @@ impl Display for Spec {
172173
}
173174
}
174175

176+
/// Utility function to identify a subset of a control flow graph corresponding to a given
177+
/// range.
178+
#[must_use]
179+
pub fn sub_cfg(cfg: &Rc<[CfgNode]>, range: ops::Range<usize>) -> Rc<[CfgNode]> {
180+
let start: u32 = range
181+
.start
182+
.try_into()
183+
.expect("cfg ranges should fit into u32");
184+
cfg[range]
185+
.iter()
186+
.map(|node| match node {
187+
CfgNode::Jump(idx) => CfgNode::Jump(idx - start),
188+
CfgNode::JumpIf(idx) => CfgNode::JumpIf(idx - start),
189+
CfgNode::JumpIfNot(idx) => CfgNode::JumpIfNot(idx - start),
190+
_ => *node,
191+
})
192+
.collect::<Vec<_>>()
193+
.into()
194+
}
195+
175196
/// Evaluates the given code with the given context.
176197
/// # Errors
177198
/// Returns the first error encountered during execution.
@@ -480,24 +501,20 @@ impl State {
480501
let cfg = self
481502
.cfg_stack
482503
.last()
483-
.expect("should have at least one stack frame")
484-
.clone();
485-
let res = match cfg
486-
.get(self.idx as usize)
487-
.expect("should have next node in CFG")
488-
{
489-
CfgNode::Bind(pat) => {
504+
.expect("should have at least one stack frame");
505+
let res = match cfg.get(self.idx as usize) {
506+
Some(CfgNode::Bind(pat)) => {
490507
self.idx += 1;
491508
self.eval_bind(env, globals, *pat);
492509
continue;
493510
}
494-
CfgNode::Expr(expr) => {
511+
Some(CfgNode::Expr(expr)) => {
495512
self.idx += 1;
496513
self.eval_expr(env, sim, globals, out, *expr)
497514
.map_err(|e| (e, self.get_stack_frames()))?;
498515
continue;
499516
}
500-
CfgNode::Stmt(stmt) => {
517+
Some(CfgNode::Stmt(stmt)) => {
501518
self.idx += 1;
502519
self.current_span = globals.get_stmt((self.package, *stmt).into()).span;
503520

@@ -521,11 +538,11 @@ impl State {
521538
}
522539
}
523540
}
524-
CfgNode::Jump(idx) => {
541+
Some(CfgNode::Jump(idx)) => {
525542
self.idx = *idx;
526543
continue;
527544
}
528-
CfgNode::JumpIf(idx) => {
545+
Some(CfgNode::JumpIf(idx)) => {
529546
let cond = self.curr_val == Some(Value::Bool(true));
530547
if cond {
531548
self.idx = *idx;
@@ -534,7 +551,7 @@ impl State {
534551
}
535552
continue;
536553
}
537-
CfgNode::JumpIfNot(idx) => {
554+
Some(CfgNode::JumpIfNot(idx)) => {
538555
let cond = self.curr_val == Some(Value::Bool(true));
539556
if cond {
540557
self.idx += 1;
@@ -543,21 +560,30 @@ impl State {
543560
}
544561
continue;
545562
}
546-
CfgNode::Store => {
563+
Some(CfgNode::Store) => {
547564
self.push_val();
548565
self.idx += 1;
549566
continue;
550567
}
551-
CfgNode::Unit => {
568+
Some(CfgNode::Unit) => {
552569
self.idx += 1;
553570
self.set_curr_val(Value::unit());
554571
continue;
555572
}
556-
CfgNode::Ret => {
573+
Some(CfgNode::Ret) => {
557574
self.leave_frame();
558575
env.leave_scope();
559576
continue;
560577
}
578+
None => {
579+
// We have reached the end of the current cfg without reaching an explicit return node,
580+
// usually indicating the partial execution of a single sub-expression.
581+
// This means we should leave the current frame but not the current environment scope,
582+
// so bound variables are still accessible after completion.
583+
self.leave_frame();
584+
assert!(self.cfg_stack.is_empty());
585+
continue;
586+
}
561587
};
562588

563589
if let StepResult::Return(_) = res {

compiler/qsc_eval/src/lower.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ impl Lowerer {
267267

268268
fn lower_stmt(&mut self, stmt: &hir::Stmt) -> fir::StmtId {
269269
let id = self.assigner.next_stmt();
270+
let cfg_start_idx = self.cfg.len();
270271
self.cfg.push(CfgNode::Stmt(id));
271272
let kind = match &stmt.kind {
272273
hir::StmtKind::Expr(expr) => fir::StmtKind::Expr(self.lower_expr(expr)),
@@ -289,6 +290,7 @@ impl Lowerer {
289290
id,
290291
span: stmt.span,
291292
kind,
293+
cfg_range: cfg_start_idx..self.cfg.len(),
292294
};
293295
self.stmts.insert(id, stmt);
294296
id
@@ -297,6 +299,7 @@ impl Lowerer {
297299
#[allow(clippy::too_many_lines)]
298300
fn lower_expr(&mut self, expr: &hir::Expr) -> ExprId {
299301
let id = self.assigner.next_expr();
302+
let cfg_start_idx = self.cfg.len();
300303
let ty = self.lower_ty(&expr.ty);
301304

302305
let kind = match &expr.kind {
@@ -572,6 +575,7 @@ impl Lowerer {
572575
span: expr.span,
573576
ty,
574577
kind,
578+
cfg_range: cfg_start_idx..self.cfg.len(),
575579
};
576580
self.exprs.insert(id, expr);
577581
id

0 commit comments

Comments
 (0)