diff --git a/source/compiler/qsc_eval/src/val.rs b/source/compiler/qsc_eval/src/val.rs index b1c392d2a8..24c8b1176c 100644 --- a/source/compiler/qsc_eval/src/val.rs +++ b/source/compiler/qsc_eval/src/val.rs @@ -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 { diff --git a/source/compiler/qsc_partial_eval/src/lib.rs b/source/compiler/qsc_partial_eval/src/lib.rs index b71ce650c3..a9cbd3d60b 100644 --- a/source/compiler/qsc_partial_eval/src/lib.rs +++ b/source/compiler/qsc_partial_eval/src/lib.rs @@ -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(), @@ -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, @@ -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. @@ -3027,6 +3052,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) @@ -4043,6 +4073,52 @@ impl<'a> PartialEvaluator<'a> { Ok(Value::Var(eval_variable)) } + + fn eval_expr_range( + &mut self, + start: Option, + step: Option, + end: Option, + span: PackageSpan, + ) -> Result { + 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)] diff --git a/source/compiler/qsc_partial_eval/src/tests/misc.rs b/source/compiler/qsc_partial_eval/src/tests/misc.rs index 3958dfacf0..7c376eff87 100644 --- a/source/compiler/qsc_partial_eval/src/tests/misc.rs +++ b/source/compiler/qsc_partial_eval/src/tests/misc.rs @@ -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"#]], + ); +} diff --git a/source/compiler/qsc_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index 30f0b7da32..6e5b031632 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -341,14 +341,7 @@ impl<'a> Analyzer<'a> { value_kind, } } else { - let call_compute_kind = - self.analyze_expr_call_with_static_callee(callee_expr_id, args_expr_id, expr_type); - match call_compute_kind { - CallComputeKind::Regular(compute_kind) => compute_kind, - CallComputeKind::Override(compute_kind) => { - return compute_kind; - } - } + self.analyze_expr_call_with_static_callee(callee_expr_id, args_expr_id, expr_type) }; // If this call happens within a dynamic scope, there might be additional runtime features being used. @@ -394,40 +387,13 @@ impl<'a> Analyzer<'a> { compute_kind } - fn analyze_expr_call_for_length_intrinsic(&self, args_expr_id: ExprId) -> ComputeKind { - let application_instance = self.get_current_application_instance(); - let args_compute_kind = *application_instance.get_expr_compute_kind(args_expr_id); - match args_compute_kind { - ComputeKind::Static => ComputeKind::Static, - ComputeKind::Dynamic { - runtime_features, .. - } => { - if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicallySizedArray) { - ComputeKind::Dynamic { - runtime_features, - value_kind: ValueKind::Variable, - } - } else { - ComputeKind::Static - } - } - } - } - fn analyze_expr_call_with_spec_callee( &mut self, callee: &Callee, callable_decl: &'a CallableDecl, args_expr_id: ExprId, fixed_args: Option>, - ) -> CallComputeKind { - // The `Length` intrinsic function has a specialized override. - if is_length_intrinsic(callable_decl) { - return CallComputeKind::Override( - self.analyze_expr_call_for_length_intrinsic(args_expr_id), - ); - } - + ) -> ComputeKind { // Analyze the specialization to determine its application generator set. let callee_id = GlobalSpecId::from((callee.item, callee.functor_app.functor_set_value())); self.analyze_spec(callee_id, callable_decl); @@ -503,7 +469,7 @@ impl<'a> Analyzer<'a> { runtime_features.insert(RuntimeFeatureFlags::CallToCyclicOperation); } - CallComputeKind::Regular(compute_kind) + compute_kind } fn analyze_expr_call_with_static_callee( @@ -511,7 +477,7 @@ impl<'a> Analyzer<'a> { callee_expr_id: ExprId, args_expr_id: ExprId, expr_type: &Ty, - ) -> CallComputeKind { + ) -> ComputeKind { // Try to resolve the callee. let package_id = self.get_current_package_id(); let package = self.package_store.get(package_id); @@ -535,7 +501,7 @@ impl<'a> Analyzer<'a> { self.get_current_application_instance_mut() .unresolved_callee_exprs .push(callee_expr_id); - return CallComputeKind::Regular(compute_kind); + return compute_kind; }; if self.callee_in_active_contexts(&callee) { @@ -554,10 +520,10 @@ impl<'a> Analyzer<'a> { self.get_current_application_instance_mut() .unresolved_callee_exprs .push(callee_expr_id); - return CallComputeKind::Regular(ComputeKind::Dynamic { + return ComputeKind::Dynamic { runtime_features: RuntimeFeatureFlags::CallToUnresolvedCallee, value_kind: ValueKind::Constant, - }); + }; } // We try to resolve the callee and determine the compute kind of the call depending on the callee kind. @@ -565,7 +531,7 @@ impl<'a> Analyzer<'a> { // If the callee is not found, that is an indication that it is an item that was removed during // incremental compilation but remains in the name resolution data structures. Assume it is static // so that it generates an "unbound name" error at runtime. - return CallComputeKind::Regular(ComputeKind::Static); + return ComputeKind::Static; }; match global_callee { Global::Callable(callable_decl) => self.analyze_expr_call_with_spec_callee( @@ -574,9 +540,7 @@ impl<'a> Analyzer<'a> { args_expr_id, fixed_args, ), - Global::Udt => { - CallComputeKind::Regular(self.analyze_expr_call_with_udt_callee(args_expr_id)) - } + Global::Udt => self.analyze_expr_call_with_udt_callee(args_expr_id), } } @@ -1174,8 +1138,8 @@ impl<'a> Analyzer<'a> { } }; - // If the condition is dynamic, we require an additional runtime feature. - if !matches!(condition_expr_compute_kind, ComputeKind::Static) { + // If the condition is a dynamic variable, we require an additional runtime feature. + if condition_expr_compute_kind.is_variable_value_kind() { let ComputeKind::Dynamic { runtime_features, .. } = &mut compute_kind @@ -2245,11 +2209,6 @@ impl SpecContext { } } -enum CallComputeKind { - Regular(ComputeKind), - Override(ComputeKind), -} - fn derive_intrinsic_function_application_generator_set( callable_context: &CallableContext, ) -> ApplicationGeneratorSet { @@ -2368,11 +2327,6 @@ fn derive_instrinsic_operation_application_generator_set( } } -fn is_length_intrinsic(callable_decl: &CallableDecl) -> bool { - matches!(callable_decl.implementation, CallableImpl::Intrinsic) - && callable_decl.name.name.as_ref() == "Length" -} - fn ty_to_runtime_runtime_output_flags(ty: &Ty) -> RuntimeFeatureFlags { match ty { Ty::Array(content_type) => ty_to_runtime_runtime_output_flags(content_type), diff --git a/source/compiler/qsc_rca/src/tests/overrides.rs b/source/compiler/qsc_rca/src/tests/overrides.rs index 9f0fb18527..ba25e571fc 100644 --- a/source/compiler/qsc_rca/src/tests/overrides.rs +++ b/source/compiler/qsc_rca/src/tests/overrides.rs @@ -31,7 +31,9 @@ fn check_rca_for_length_of_statically_sized_array_with_dynamic_content() { package_store_compute_properties, &expect![[r#" ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: "#]], ); }