Skip to content
Open
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 source/compiler/qsc_eval/src/val.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{

use crate::{AsIndex, Error, Range as EvalRange, error::PackageSpan};

pub(super) const DEFAULT_RANGE_STEP: i64 = 1;
pub const DEFAULT_RANGE_STEP: i64 = 1;

#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Expand Down
118 changes: 97 additions & 21 deletions source/compiler/qsc_partial_eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1287,10 +1287,9 @@ impl<'a> PartialEvaluator<'a> {
"literal should have been classically evaluated".to_string(),
expr_package_span,
)),
ExprKind::Range(_, _, _) => Err(Error::Unexpected(
"dynamic ranges are invalid".to_string(),
expr_package_span,
)),
ExprKind::Range(start, step, end) => {
self.eval_expr_range(*start, *step, *end, expr_package_span)
}
ExprKind::Return(expr_id) => self.eval_expr_return(*expr_id),
ExprKind::Struct(..) => Err(Error::Unexpected(
"instruction generation for struct constructor expressions is invalid".to_string(),
Expand Down Expand Up @@ -1813,23 +1812,49 @@ impl<'a> PartialEvaluator<'a> {
)),
// The following intrinsic functions and operations should never make it past conditional compilation and
// the capabilities check pass.
"DrawRandomInt" | "DrawRandomDouble" | "DrawRandomBool" | "Length" => {
Err(Error::Unexpected(
format!(
"`{}` is not a supported by partial evaluation",
callable_decl.name.name
),
callee_expr_span,
))
}
"IntAsDouble" => {
let variable_id = self.resource_manager.next_var();
self.convert_value(&args_value, rir::Variable::new_double(variable_id))
}
"Truncate" => {
let variable_id = self.resource_manager.next_var();
self.convert_value(&args_value, rir::Variable::new_integer(variable_id))
"DrawRandomInt" | "DrawRandomDouble" | "DrawRandomBool" => Err(Error::Unexpected(
format!(
"`{}` is not a supported by partial evaluation",
callable_decl.name.name
),
callee_expr_span,
)),
"Length" => {
let Value::Array(arr) = args_value else {
return Err(Error::Unexpected(
"length call on dynamically sized array".to_string(),
callee_expr_span,
));
};
match arr.len().try_into() {
Ok(len) => Ok(Value::Int(len)),
Err(_) => Err(EvalError::ArrayTooLarge(args_span).into()),
}
}
"IntAsDouble" => match args_value {
#[allow(clippy::cast_precision_loss)]
Value::Int(i) => Ok(Value::Double(i as f64)),
Value::Var(_) => {
let variable_id = self.resource_manager.next_var();
self.convert_value(&args_value, rir::Variable::new_double(variable_id))
}
_ => panic!(
"Unexpected value type for IntAsDouble: {}",
args_value.type_name()
),
},
"Truncate" => match args_value {
#[allow(clippy::cast_possible_truncation)]
Value::Double(d) => Ok(Value::Int(d as i64)),
Value::Var(_) => {
let variable_id = self.resource_manager.next_var();
self.convert_value(&args_value, rir::Variable::new_integer(variable_id))
}
_ => panic!(
"Unexpected value type for Truncate: {}",
args_value.type_name()
),
},
_ => self.eval_expr_call_to_intrinsic_qis(
store_item_id,
callable_decl,
Expand Down Expand Up @@ -2392,7 +2417,7 @@ impl<'a> PartialEvaluator<'a> {
.config
.capabilities
.contains(TargetCapabilityFlags::BackwardsBranching)
&& !self.is_static_expr(condition_expr_id)
&& self.is_variable_expr(condition_expr_id)
{
// If backwards branching is supported and the loop condition is not static,
// we can generate a while loop structure in RIR without unrolling the loop.
Expand Down Expand Up @@ -3035,6 +3060,11 @@ impl<'a> PartialEvaluator<'a> {
matches!(compute_kind, ComputeKind::Static)
}

fn is_variable_expr(&self, expr_id: ExprId) -> bool {
let compute_kind = self.get_expr_compute_kind(expr_id);
compute_kind.is_variable_value_kind()
}

fn allocate_qubit(&mut self) -> Value {
let qubit = self.resource_manager.allocate_qubit();
Value::Qubit(qubit)
Expand Down Expand Up @@ -4051,6 +4081,52 @@ impl<'a> PartialEvaluator<'a> {

Ok(Value::Var(eval_variable))
}

fn eval_expr_range(
&mut self,
start: Option<ExprId>,
step: Option<ExprId>,
end: Option<ExprId>,
span: PackageSpan,
) -> Result<EvalControlFlow, Error> {
let mut exprs = Vec::new();
for expr in [start, step, end] {
// Try to evaluate the sub-expression.
let expr_control_flow = expr.map(|id| self.try_eval_expr(id)).transpose()?;
// From there, get the value, assuming that any embedded returns are invalid and produce an error.
let expr_value = expr_control_flow
.map(|cf| match cf {
EvalControlFlow::Continue(val) => Ok(val),
EvalControlFlow::Return(_) => Err(Error::Unexpected(
"embedded return in Range expression".to_string(),
span,
)),
})
.transpose()?;
// Convert the value to an integer, if possible. Non-integer values should never happen,
// variable values should be caught by RCA but may sneak through so fail gracefully.
let expr_int = expr_value
.map(|v| match v {
Value::Int(i) => Ok(i),
Value::Var(_) => Err(Error::Unexpected(
"dynamic variable in Range expression".to_string(),
span,
)),
_ => panic!("invalid type for Range expression: {}", v.type_name()),
})
.transpose()?;
exprs.push(expr_int);
}

// Create a new range value from the processed sub-expressions, using the default step if not specified.
Ok(EvalControlFlow::Continue(Value::Range(Box::new(
val::Range {
start: exprs[0],
step: exprs[1].unwrap_or(val::DEFAULT_RANGE_STEP),
end: exprs[2],
},
))))
}
}

#[derive(Default)]
Expand Down
25 changes: 25 additions & 0 deletions source/compiler/qsc_partial_eval/src/tests/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -979,3 +979,28 @@ fn custom_two_qubit_measurement_in_loop_of_variable_qubits_supported() {
Jump(6)"#]],
);
}

#[test]
fn test_length_with_embedded_qubit_operations() {
let program = get_rir_program_with_capabilities(
indoc! {
r#"
operation Main() : Int {
Length({use q = Qubit(); M(q); [1]})
}
"#,
},
Profile::AdaptiveRIFLA.into(),
);

assert_blocks(
&program,
&expect![[r#"
Blocks:
Block 0:Block:
Call id(1), args( Pointer, )
Call id(2), args( Qubit(0), Result(0), )
Call id(3), args( Integer(1), Tag(0, 3), )
Return"#]],
);
}
6 changes: 2 additions & 4 deletions source/compiler/qsc_rca/src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

use crate::{
PackageStoreComputeProperties, core, cyclic_callables, overrider::Overrider,
PackageStoreComputeProperties, core, cyclic_callables,
scaffolding::InternalPackageStoreComputeProperties,
};
use qsc_data_structures::target::TargetCapabilityFlags;
Expand Down Expand Up @@ -43,9 +43,7 @@ impl<'a> Analyzer<'a> {

#[must_use]
pub fn analyze_all(self) -> PackageStoreComputeProperties {
// First, we populate the elements for which we override its compute properties.
let overrider = Overrider::new(self.package_store, self.scaffolding);
let scaffolding = overrider.populate_overrides();
let scaffolding = InternalPackageStoreComputeProperties::init(self.package_store);

// Then, we need to analyze the callable specializations with cycles. Otherwise, we cannot safely analyze the
// rest of the items without causing an infinite analysis loop.
Expand Down
Loading
Loading