Skip to content

Commit

Permalink
Fail partial eval with error on Result literals (#1409)
Browse files Browse the repository at this point in the history
This change adds an error case that fails partial evaluation if any part
of the output includes Result literals `One` or `Zero`. This matches the
support in QIR, where result literals cannot be recorded as output.
  • Loading branch information
swernli authored Apr 25, 2024
1 parent 686b9d3 commit e8b824c
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 12 deletions.
49 changes: 37 additions & 12 deletions compiler/qsc_partial_eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ pub enum Error {
#[error("failed to evaluate: {0} not yet implemented")]
#[diagnostic(code("Qsc.PartialEval.Unimplemented"))]
Unimplemented(String, #[label] Span),

#[error("unsupported Result literal in output")]
#[diagnostic(help(
"Result literals `One` and `Zero` cannot be included in generated QIR output recording."
))]
#[diagnostic(code("Qsc.PartialEval.OutputResultLiteral"))]
OutputResultLiteral(#[label] Span),
}

struct PartialEvaluator<'a> {
Expand Down Expand Up @@ -235,10 +242,12 @@ impl<'a> PartialEvaluator<'a> {

// Get the final value from the execution context.
let ret_val = self.eval_context.get_current_scope().last_expr_value();
let output_recording: Vec<Instruction> = self.generate_output_recording_instructions(
ret_val,
&self.get_expr(self.entry.expr.expr).ty,
);
let output_recording: Vec<Instruction> = self
.generate_output_recording_instructions(
ret_val,
&self.get_expr(self.entry.expr.expr).ty,
)
.map_err(|()| Error::OutputResultLiteral(self.get_expr(self.entry.expr.expr).span))?;

// Insert the return expression and return the generated program.
let current_block = self.get_current_block_mut();
Expand Down Expand Up @@ -1044,12 +1053,14 @@ impl<'a> PartialEvaluator<'a> {
&mut self,
ret_val: Value,
ty: &Ty,
) -> Vec<Instruction> {
) -> Result<Vec<Instruction>, ()> {
let mut instrs = Vec::new();

match ret_val {
Value::Array(vals) => self.record_array(ty, &mut instrs, &vals),
Value::Tuple(vals) => self.record_tuple(ty, &mut instrs, &vals),
Value::Result(val::Result::Val(_)) => return Err(()),

Value::Array(vals) => self.record_array(ty, &mut instrs, &vals)?,
Value::Tuple(vals) => self.record_tuple(ty, &mut instrs, &vals)?,
Value::Result(res) => self.record_result(&mut instrs, res),
Value::Var(var) => self.record_variable(ty, &mut instrs, var),
Value::Bool(val) => self.record_bool(&mut instrs, val),
Expand All @@ -1065,7 +1076,7 @@ impl<'a> PartialEvaluator<'a> {
| Value::String(_) => panic!("unsupported value type in output recording"),
}

instrs
Ok(instrs)
}

fn record_int(&mut self, instrs: &mut Vec<Instruction>, val: i64) {
Expand Down Expand Up @@ -1127,7 +1138,12 @@ impl<'a> PartialEvaluator<'a> {
));
}

fn record_tuple(&mut self, ty: &Ty, instrs: &mut Vec<Instruction>, vals: &Rc<[Value]>) {
fn record_tuple(
&mut self,
ty: &Ty,
instrs: &mut Vec<Instruction>,
vals: &Rc<[Value]>,
) -> Result<(), ()> {
let Ty::Tuple(elem_tys) = ty else {
panic!("expected tuple type for tuple value");
};
Expand All @@ -1145,11 +1161,18 @@ impl<'a> PartialEvaluator<'a> {
None,
));
for (val, elem_ty) in vals.iter().zip(elem_tys.iter()) {
instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty));
instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)?);
}

Ok(())
}

fn record_array(&mut self, ty: &Ty, instrs: &mut Vec<Instruction>, vals: &Rc<Vec<Value>>) {
fn record_array(
&mut self,
ty: &Ty,
instrs: &mut Vec<Instruction>,
vals: &Rc<Vec<Value>>,
) -> Result<(), ()> {
let Ty::Array(elem_ty) = ty else {
panic!("expected array type for array value");
};
Expand All @@ -1167,8 +1190,10 @@ impl<'a> PartialEvaluator<'a> {
None,
));
for val in vals.iter() {
instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty));
instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)?);
}

Ok(())
}

fn get_array_record_callable(&mut self) -> CallableId {
Expand Down
70 changes: 70 additions & 0 deletions compiler/qsc_partial_eval/tests/output_recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,73 @@ fn output_recording_for_mix_of_literal_and_variable() {
num_results: 1"#]]
.assert_eq(&program.to_string());
}

#[test]
#[should_panic(
expected = "partial evaluation failed: OutputResultLiteral(Span { lo: 50, hi: 54 })"
)]
fn output_recording_fails_with_result_literal_one() {
let _ = compile_and_partially_evaluate(indoc! {
r#"
namespace Test {
@EntryPoint()
operation Main() : Result {
One
}
}
"#,
});
}

#[test]
#[should_panic(
expected = "partial evaluation failed: OutputResultLiteral(Span { lo: 50, hi: 54 })"
)]
fn output_recording_fails_with_result_literal_zero() {
let _ = compile_and_partially_evaluate(indoc! {
r#"
namespace Test {
@EntryPoint()
operation Main() : Result {
Zero
}
}
"#,
});
}

#[test]
#[should_panic(
expected = "partial evaluation failed: OutputResultLiteral(Span { lo: 50, hi: 54 })"
)]
fn output_recording_fails_with_result_literal_in_array() {
let _ = compile_and_partially_evaluate(indoc! {
r#"
namespace Test {
@EntryPoint()
operation Main() : Result[] {
use q = Qubit();
[QIR.Intrinsic.__quantum__qis__mresetz__body(q), Zero]
}
}
"#,
});
}

#[test]
#[should_panic(
expected = "partial evaluation failed: OutputResultLiteral(Span { lo: 50, hi: 54 })"
)]
fn output_recording_fails_with_result_literal_in_tuple() {
let _ = compile_and_partially_evaluate(indoc! {
r#"
namespace Test {
@EntryPoint()
operation Main() : (Result, Result) {
use q = Qubit();
(QIR.Intrinsic.__quantum__qis__mresetz__body(q), Zero)
}
}
"#,
});
}

0 comments on commit e8b824c

Please sign in to comment.