Skip to content

Commit 0c92116

Browse files
cesarzcsezna
andauthored
Partial evaluation support for return expressions (#1421)
This change implements support for return expressions in partial evaluation. --------- Co-authored-by: Alex Hansen <[email protected]>
1 parent f0b4713 commit 0c92116

File tree

16 files changed

+1716
-484
lines changed

16 files changed

+1716
-484
lines changed

compiler/qsc_eval/src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,15 +341,15 @@ impl Env {
341341
.find_map(|scope| scope.bindings.get_mut(id))
342342
}
343343

344-
fn push_scope(&mut self, frame_id: usize) {
344+
pub fn push_scope(&mut self, frame_id: usize) {
345345
let scope = Scope {
346346
frame_id,
347347
..Default::default()
348348
};
349349
self.0.push(scope);
350350
}
351351

352-
fn leave_scope(&mut self) {
352+
pub fn leave_scope(&mut self) {
353353
// Only pop the scope if there is more than one scope in the stack,
354354
// because the global/top-level scope cannot be exited.
355355
if self.0.len() > 1 {
@@ -400,6 +400,12 @@ impl Env {
400400
.collect();
401401
variables_by_scope.into_iter().flatten().collect::<Vec<_>>()
402402
}
403+
404+
#[allow(clippy::len_without_is_empty)]
405+
#[must_use]
406+
pub fn len(&self) -> usize {
407+
self.0.len()
408+
}
403409
}
404410

405411
#[derive(Default)]

compiler/qsc_partial_eval/src/evaluation_context.rs

Lines changed: 91 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,86 +6,116 @@ use qsc_eval::{
66
val::{Result, Value},
77
Env, Variable,
88
};
9-
use qsc_fir::fir::{ExprId, LocalItemId, LocalVarId, PackageId};
9+
use qsc_fir::fir::{LocalItemId, LocalVarId, PackageId};
1010
use qsc_rca::{RuntimeKind, ValueKind};
1111
use qsc_rir::rir::BlockId;
1212
use rustc_hash::FxHashMap;
1313

14+
/// Struct that keeps track of the active RIR blocks (where RIR instructions are added) and the active scopes (which
15+
/// correspond to the Q#'s program call stack).
1416
pub struct EvaluationContext {
1517
active_blocks: Vec<BlockNode>,
1618
scopes: Vec<Scope>,
1719
}
1820

1921
impl EvaluationContext {
22+
/// Creates a new evaluation context.
2023
pub fn new(package_id: PackageId, initial_block: BlockId) -> Self {
2124
let entry_callable_scope = Scope::new(package_id, None, Vec::new());
2225
Self {
2326
active_blocks: vec![BlockNode {
2427
id: initial_block,
25-
next: None,
28+
successor: None,
2629
}],
2730
scopes: vec![entry_callable_scope],
2831
}
2932
}
3033

34+
/// Gets the ID of the current RIR block.
3135
pub fn get_current_block_id(&self) -> BlockId {
3236
self.active_blocks.last().expect("no active blocks").id
3337
}
3438

39+
/// Gets an immutable reference to the current (call) scope.
3540
pub fn get_current_scope(&self) -> &Scope {
3641
self.scopes
3742
.last()
3843
.expect("the evaluation context does not have a current scope")
3944
}
4045

46+
/// Gets a mutable reference to the current (call) scope.
4147
pub fn get_current_scope_mut(&mut self) -> &mut Scope {
4248
self.scopes
4349
.last_mut()
4450
.expect("the evaluation context does not have a current scope")
4551
}
4652

53+
/// Pops the currently active block.
4754
pub fn pop_block_node(&mut self) -> BlockNode {
55+
self.get_current_scope_mut().active_block_count -= 1;
4856
self.active_blocks
4957
.pop()
5058
.expect("there are no active blocks in the evaluation context")
5159
}
5260

61+
/// Pops the currently active (call) scope.
5362
pub fn pop_scope(&mut self) -> Scope {
5463
self.scopes
5564
.pop()
5665
.expect("there are no scopes in the evaluation context")
5766
}
5867

68+
/// Pushes a new active block.
5969
pub fn push_block_node(&mut self, b: BlockNode) {
6070
self.active_blocks.push(b);
71+
self.get_current_scope_mut().active_block_count += 1;
6172
}
6273

74+
/// Pushes a new (call) scope.
6375
pub fn push_scope(&mut self, s: Scope) {
6476
self.scopes.push(s);
6577
}
6678
}
6779

80+
/// Struct that represents a block node when we intepret an RIR program as a graph.
6881
pub struct BlockNode {
82+
/// The ID of the block.
6983
pub id: BlockId,
70-
pub next: Option<BlockId>,
84+
/// The block to jump to (if any) once all instructions to the block have been added.
85+
pub successor: Option<BlockId>,
7186
}
7287

88+
/// A call scope.
7389
pub struct Scope {
90+
/// The package ID of the callable.
7491
pub package_id: PackageId,
92+
/// The ID and functor information of the callable.
7593
pub callable: Option<(LocalItemId, FunctorApp)>,
94+
/// The value of the arguments passed to the callable.
7695
pub args_value_kind: Vec<ValueKind>,
96+
/// The classical environment of the callable, which holds values corresponding to local variables.
7797
pub env: Env,
78-
last_expr: Option<ExprId>,
79-
hybrid_exprs: FxHashMap<ExprId, Value>,
98+
/// Number of currently active blocks (starting from where this scope was created).
99+
active_block_count: usize,
100+
/// Map that holds the values of local variables.
80101
hybrid_vars: FxHashMap<LocalVarId, Value>,
81102
}
82103

83104
impl Scope {
105+
/// Creates a new call scope.
84106
pub fn new(
85107
package_id: PackageId,
86108
callable: Option<(LocalItemId, FunctorApp)>,
87109
args: Vec<Arg>,
88110
) -> Self {
111+
// Create the environment for the classical evaluator.
112+
// A default classical evaluator environment is created with one scope. However, we need to push an additional
113+
// scope to the environment to be able to detect whether the classical evaluator has returned from the call
114+
// scope.
115+
const CLASSICAL_EVALUATOR_CALL_SCOPE_ID: usize = 1;
116+
let mut env = Env::default();
117+
env.push_scope(CLASSICAL_EVALUATOR_CALL_SCOPE_ID);
118+
89119
// Determine the runtime kind (static or dynamic) of the arguments.
90120
let args_value_kind: Vec<ValueKind> = args
91121
.iter()
@@ -100,7 +130,6 @@ impl Scope {
100130

101131
// Add the values to either the environment or the hybrid variables depending on whether the value is static or
102132
// dynamic.
103-
let mut env = Env::default();
104133
let mut hybrid_vars = FxHashMap::default();
105134
let arg_runtime_kind_tuple = args.into_iter().zip(args_value_kind.iter());
106135
for (arg, value_kind) in arg_runtime_kind_tuple {
@@ -121,42 +150,80 @@ impl Scope {
121150
callable,
122151
args_value_kind,
123152
env,
124-
last_expr: None,
125-
hybrid_exprs: FxHashMap::default(),
153+
active_block_count: 1,
126154
hybrid_vars,
127155
}
128156
}
129157

130-
// Potential candidate for removal if only the last expression value is needed.
131-
pub fn _get_expr_value(&self, expr_id: ExprId) -> &Value {
132-
self.hybrid_exprs
133-
.get(&expr_id)
134-
.expect("expression value does not exist")
135-
}
136-
158+
/// Gets the value of a (hybrid) local variable.
137159
pub fn get_local_var_value(&self, local_var_id: LocalVarId) -> &Value {
138160
self.hybrid_vars
139161
.get(&local_var_id)
140162
.expect("local variable value does not exist")
141163
}
142164

143-
pub fn insert_expr_value(&mut self, expr_id: ExprId, value: Value) {
144-
self.last_expr = Some(expr_id);
145-
self.hybrid_exprs.insert(expr_id, value);
165+
/// Determines whether we are currently evaluating a branch within the scope.
166+
pub fn is_currently_evaluating_branch(&self) -> bool {
167+
self.active_block_count > 1
168+
}
169+
170+
/// Determines whether the classical evaluator has returned from the call scope.
171+
/// This relies on the fact that the classical evaluator pops the scope when it encounters a return, so when this
172+
/// happens the number of scopes in the environment will be exactly one.
173+
pub fn has_classical_evaluator_returned(&self) -> bool {
174+
self.env.len() == 1
146175
}
147176

177+
/// Inserts the value of a local variable into the hybrid variables map.
148178
pub fn insert_local_var_value(&mut self, local_var_id: LocalVarId, value: Value) {
149179
self.hybrid_vars.insert(local_var_id, value);
150180
}
181+
}
151182

152-
pub fn clear_last_expr(&mut self) {
153-
self.last_expr = None;
183+
/// A call argument.
184+
pub enum Arg {
185+
Discard(Value),
186+
Var(LocalVarId, Variable),
187+
}
188+
189+
impl Arg {
190+
/// Converts the argument into its underlying value.
191+
pub fn into_value(self) -> Value {
192+
match self {
193+
Self::Discard(value) => value,
194+
Self::Var(_, var) => var.value,
195+
}
154196
}
197+
}
155198

156-
pub fn last_expr_value(&self) -> Value {
157-
self.last_expr
158-
.and_then(|expr_id| self.hybrid_exprs.get(&expr_id))
159-
.map_or_else(Value::unit, Clone::clone)
199+
/// Represents the possible control flow options that can result from a branch.
200+
pub enum BranchControlFlow {
201+
/// The block ID corresponding to a branch.
202+
Block(BlockId),
203+
/// The return value resulting from a branch.
204+
Return(Value),
205+
}
206+
207+
/// Represents the possible control flow options that an evaluation can have.
208+
pub enum EvalControlFlow {
209+
Continue(Value),
210+
Return(Value),
211+
}
212+
213+
impl EvalControlFlow {
214+
/// Consumes the evaluation control flow and returns its value.
215+
pub fn into_value(self) -> Value {
216+
match self {
217+
EvalControlFlow::Continue(value) | EvalControlFlow::Return(value) => value,
218+
}
219+
}
220+
221+
/// Whether this evaluation control flow is a return.
222+
pub fn is_return(&self) -> bool {
223+
match self {
224+
Self::Continue(_) => false,
225+
Self::Return(_) => true,
226+
}
160227
}
161228
}
162229

@@ -205,17 +272,3 @@ fn map_eval_value_to_value_kind(value: &Value) -> ValueKind {
205272
| Value::String(_) => ValueKind::Element(RuntimeKind::Static),
206273
}
207274
}
208-
209-
pub enum Arg {
210-
Discard(Value),
211-
Var(LocalVarId, Variable),
212-
}
213-
214-
impl Arg {
215-
pub fn into_value(self) -> Value {
216-
match self {
217-
Self::Discard(value) => value,
218-
Self::Var(_, var) => var.value,
219-
}
220-
}
221-
}

0 commit comments

Comments
 (0)