Skip to content

Commit

Permalink
Fail partial eval with error on Result literals
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 committed Apr 22, 2024
1 parent b9a6893 commit 75d8847
Show file tree
Hide file tree
Showing 2 changed files with 99 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 @@ -104,6 +104,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 @@ -231,10 +238,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 @@ -1040,12 +1049,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 @@ -1061,7 +1072,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 @@ -1123,7 +1134,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 @@ -1141,11 +1157,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 @@ -1163,8 +1186,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
62 changes: 62 additions & 0 deletions compiler/qsc_partial_eval/tests/output_recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,3 +524,65 @@ 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 75d8847

Please sign in to comment.