Skip to content

Commit bb3ddca

Browse files
authored
Support partial evaluation of IndexRange calls (#2727)
This updates the logic for accessing fields of UDTs during partial evaluation and using them as loop bounds such that usage of `Std.Arrays.IndexRange` on arrays with compile-time known length works as expected during code generation.
1 parent 0570c7d commit bb3ddca

File tree

4 files changed

+246
-17
lines changed

4 files changed

+246
-17
lines changed

source/compiler/qsc_partial_eval/src/lib.rs

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ use qsc_eval::{
3030
use qsc_fir::{
3131
fir::{
3232
self, BinOp, Block, BlockId, CallableDecl, CallableImpl, ExecGraph, Expr, ExprId, ExprKind,
33-
Global, Ident, LocalVarId, Mutability, PackageId, PackageStore, PackageStoreLookup, Pat,
34-
PatId, PatKind, Res, SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, StoreBlockId, StoreExprId,
35-
StoreItemId, StorePatId, StoreStmtId, UnOp,
33+
Field, Global, Ident, LocalVarId, Mutability, PackageId, PackageStore, PackageStoreLookup,
34+
Pat, PatId, PatKind, PrimField, Res, SpecDecl, SpecImpl, Stmt, StmtId, StmtKind,
35+
StoreBlockId, StoreExprId, StoreItemId, StorePatId, StoreStmtId, UnOp,
3636
},
3737
ty::{Prim, Ty},
3838
};
3939
use qsc_lowerer::map_fir_package_to_hir;
4040
use qsc_rca::{
4141
ComputeKind, ComputePropertiesLookup, ItemComputeProperties, PackageStoreComputeProperties,
42-
QuantumProperties, RuntimeFeatureFlags,
42+
QuantumProperties, RuntimeFeatureFlags, RuntimeKind, ValueKind,
4343
errors::{
4444
Error as CapabilityError, generate_errors_from_runtime_features,
4545
get_missing_runtime_features,
@@ -1180,10 +1180,7 @@ impl<'a> PartialEvaluator<'a> {
11801180
"using a dynamic value in a fail statement is invalid".to_string(),
11811181
expr_package_span,
11821182
)),
1183-
ExprKind::Field(_, _) => Err(Error::Unexpected(
1184-
"accessing a field of a dynamic user-defined type is invalid".to_string(),
1185-
expr_package_span,
1186-
)),
1183+
ExprKind::Field(expr_id, field) => self.eval_expr_field(*expr_id, field.clone()),
11871184
ExprKind::Hole => Err(Error::Unexpected(
11881185
"hole expressions are not expected during partial evaluation".to_string(),
11891186
expr_package_span,
@@ -1978,6 +1975,47 @@ impl<'a> PartialEvaluator<'a> {
19781975
Ok(EvalControlFlow::Continue(value))
19791976
}
19801977

1978+
fn eval_expr_field(
1979+
&mut self,
1980+
record_id: ExprId,
1981+
field: Field,
1982+
) -> Result<EvalControlFlow, Error> {
1983+
let control_flow = self.try_eval_expr(record_id)?;
1984+
let EvalControlFlow::Continue(record) = control_flow else {
1985+
return Err(Error::Unexpected(
1986+
"embedded return in field access expression".to_string(),
1987+
self.get_expr_package_span(record_id),
1988+
));
1989+
};
1990+
1991+
let field_value = match (record, field) {
1992+
(Value::Range(inner), Field::Prim(PrimField::Start)) => Value::Int(
1993+
inner
1994+
.start
1995+
.expect("range access should be validated by compiler"),
1996+
),
1997+
(Value::Range(inner), Field::Prim(PrimField::Step)) => Value::Int(inner.step),
1998+
(Value::Range(inner), Field::Prim(PrimField::End)) => Value::Int(
1999+
inner
2000+
.end
2001+
.expect("range access should be validated by compiler"),
2002+
),
2003+
(mut record, Field::Path(path)) => {
2004+
for index in path.indices {
2005+
let Value::Tuple(items, _) = record else {
2006+
panic!("invalid tuple access");
2007+
};
2008+
record = items[index].clone();
2009+
}
2010+
record
2011+
}
2012+
(ref value, ref field) => {
2013+
panic!("invalid field access. value: {value:?}, field: {field:?}")
2014+
}
2015+
};
2016+
Ok(EvalControlFlow::Continue(field_value))
2017+
}
2018+
19812019
fn eval_expr_return(&mut self, expr_id: ExprId) -> Result<EvalControlFlow, Error> {
19822020
let control_flow = self.try_eval_expr(expr_id)?;
19832021
Ok(EvalControlFlow::Return(control_flow.into_value()))
@@ -2155,17 +2193,19 @@ impl<'a> PartialEvaluator<'a> {
21552193
condition_expr_id: ExprId,
21562194
body_block_id: BlockId,
21572195
) -> Result<EvalControlFlow, Error> {
2158-
// Verify assumptions.
2196+
// Verify assumptions: the condition expression must either classical (such that it can be fully evaluated) or
2197+
// quantum but statically known at runtime (such that it can be partially evaluated to a known value).
21592198
assert!(
2160-
self.is_classical_expr(condition_expr_id),
2199+
matches!(
2200+
self.get_expr_compute_kind(condition_expr_id),
2201+
ComputeKind::Classical
2202+
| ComputeKind::Quantum(QuantumProperties {
2203+
runtime_features: _,
2204+
value_kind: ValueKind::Element(RuntimeKind::Static),
2205+
})
2206+
),
21612207
"loop conditions must be purely classical"
21622208
);
2163-
let body_block = self.get_block(body_block_id);
2164-
assert_eq!(
2165-
body_block.ty,
2166-
Ty::UNIT,
2167-
"the type of a loop block is expected to be Unit"
2168-
);
21692209

21702210
// Evaluate the block until the loop condition is false.
21712211
let condition_expr_span = self.get_expr_package_span(condition_expr_id);

source/compiler/qsc_partial_eval/src/tests/arrays.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,3 +1038,62 @@ fn result_array_copy_and_update_with_out_of_bounds_range_raises_error() {
10381038
]],
10391039
);
10401040
}
1041+
1042+
#[test]
1043+
fn result_array_index_range_returns_length_as_end() {
1044+
let program = get_rir_program(indoc! {r#"
1045+
namespace Test {
1046+
@EntryPoint()
1047+
operation Main() : Int {
1048+
use qs = Qubit[2];
1049+
let results = MResetEachZ(qs);
1050+
Std.Arrays.IndexRange(results).End
1051+
}
1052+
}
1053+
"#});
1054+
expect![[r#"
1055+
Program:
1056+
entry: 0
1057+
callables:
1058+
Callable 0: Callable:
1059+
name: main
1060+
call_type: Regular
1061+
input_type: <VOID>
1062+
output_type: <VOID>
1063+
body: 0
1064+
Callable 1: Callable:
1065+
name: __quantum__qis__mresetz__body
1066+
call_type: Measurement
1067+
input_type:
1068+
[0]: Qubit
1069+
[1]: Result
1070+
output_type: <VOID>
1071+
body: <NONE>
1072+
Callable 2: Callable:
1073+
name: __quantum__rt__int_record_output
1074+
call_type: OutputRecording
1075+
input_type:
1076+
[0]: Integer
1077+
[1]: Pointer
1078+
output_type: <VOID>
1079+
body: <NONE>
1080+
blocks:
1081+
Block 0: Block:
1082+
Variable(0, Integer) = Store Integer(0)
1083+
Variable(0, Integer) = Store Integer(1)
1084+
Variable(0, Integer) = Store Integer(2)
1085+
Variable(1, Integer) = Store Integer(0)
1086+
Call id(1), args( Qubit(0), Result(0), )
1087+
Variable(1, Integer) = Store Integer(1)
1088+
Call id(1), args( Qubit(1), Result(1), )
1089+
Variable(1, Integer) = Store Integer(2)
1090+
Variable(2, Integer) = Store Integer(0)
1091+
Variable(2, Integer) = Store Integer(1)
1092+
Variable(2, Integer) = Store Integer(2)
1093+
Call id(2), args( Integer(1), Pointer, )
1094+
Return
1095+
config: Config:
1096+
capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
1097+
num_qubits: 2
1098+
num_results: 2"#]].assert_eq(&program.to_string());
1099+
}

source/compiler/qsc_partial_eval/src/tests/loops.rs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,6 @@ fn mutable_double_updated_in_loop() {
399399

400400
assert_blocks(
401401
&program,
402-
//BlockId(0),
403402
&expect![[r#"
404403
Blocks:
405404
Block 0:Block:
@@ -452,3 +451,95 @@ fn mutable_double_updated_in_loop() {
452451
Jump(9)"#]],
453452
);
454453
}
454+
455+
#[test]
456+
fn result_array_index_range_in_for_loop() {
457+
let program = get_rir_program(indoc! {r#"
458+
namespace Test {
459+
@EntryPoint()
460+
operation Main() : Int {
461+
use qs = Qubit[2];
462+
let results = MResetEachZ(qs);
463+
mutable count = 0;
464+
for i in Std.Arrays.IndexRange(results) {
465+
if results[i] == One {
466+
set count += 1;
467+
}
468+
}
469+
count
470+
}
471+
}
472+
"#});
473+
expect![[r#"
474+
Program:
475+
entry: 0
476+
callables:
477+
Callable 0: Callable:
478+
name: main
479+
call_type: Regular
480+
input_type: <VOID>
481+
output_type: <VOID>
482+
body: 0
483+
Callable 1: Callable:
484+
name: __quantum__qis__mresetz__body
485+
call_type: Measurement
486+
input_type:
487+
[0]: Qubit
488+
[1]: Result
489+
output_type: <VOID>
490+
body: <NONE>
491+
Callable 2: Callable:
492+
name: __quantum__qis__read_result__body
493+
call_type: Readout
494+
input_type:
495+
[0]: Result
496+
output_type: Boolean
497+
body: <NONE>
498+
Callable 3: Callable:
499+
name: __quantum__rt__int_record_output
500+
call_type: OutputRecording
501+
input_type:
502+
[0]: Integer
503+
[1]: Pointer
504+
output_type: <VOID>
505+
body: <NONE>
506+
blocks:
507+
Block 0: Block:
508+
Variable(0, Integer) = Store Integer(0)
509+
Variable(0, Integer) = Store Integer(1)
510+
Variable(0, Integer) = Store Integer(2)
511+
Variable(1, Integer) = Store Integer(0)
512+
Call id(1), args( Qubit(0), Result(0), )
513+
Variable(1, Integer) = Store Integer(1)
514+
Call id(1), args( Qubit(1), Result(1), )
515+
Variable(1, Integer) = Store Integer(2)
516+
Variable(2, Integer) = Store Integer(0)
517+
Variable(3, Integer) = Store Integer(0)
518+
Variable(4, Boolean) = Call id(2), args( Result(0), )
519+
Variable(5, Boolean) = Store Variable(4, Boolean)
520+
Branch Variable(5, Boolean), 2, 1
521+
Block 1: Block:
522+
Variable(3, Integer) = Store Integer(1)
523+
Variable(6, Boolean) = Call id(2), args( Result(1), )
524+
Variable(7, Boolean) = Store Variable(6, Boolean)
525+
Branch Variable(7, Boolean), 4, 3
526+
Block 2: Block:
527+
Variable(2, Integer) = Store Integer(1)
528+
Jump(1)
529+
Block 3: Block:
530+
Variable(3, Integer) = Store Integer(2)
531+
Variable(9, Integer) = Store Variable(2, Integer)
532+
Variable(10, Integer) = Store Integer(0)
533+
Variable(10, Integer) = Store Integer(1)
534+
Variable(10, Integer) = Store Integer(2)
535+
Call id(3), args( Variable(9, Integer), Pointer, )
536+
Return
537+
Block 4: Block:
538+
Variable(8, Integer) = Add Variable(2, Integer), Integer(1)
539+
Variable(2, Integer) = Store Variable(8, Integer)
540+
Jump(3)
541+
config: Config:
542+
capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | HigherLevelConstructs | QubitReset)
543+
num_qubits: 2
544+
num_results: 2"#]].assert_eq(&program.to_string());
545+
}

source/compiler/qsc_rca/src/tests/arrays.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,3 +542,42 @@ fn check_rca_for_array_with_static_size_bound_through_dynamic_tuple() {
542542
dynamic_param_applications: <empty>"#]],
543543
);
544544
}
545+
546+
#[test]
547+
fn check_rca_for_index_range_on_array_with_classical_contents() {
548+
let mut compilation_context = CompilationContext::default();
549+
compilation_context.update(
550+
r#"
551+
let arr = [0, 1, 2, 3, 4];
552+
Std.Arrays.IndexRange(arr)"#,
553+
);
554+
let package_store_compute_properties = compilation_context.get_compute_properties();
555+
check_last_statement_compute_properties(
556+
package_store_compute_properties,
557+
&expect![[r#"
558+
ApplicationsGeneratorSet:
559+
inherent: Classical
560+
dynamic_param_applications: <empty>"#]],
561+
);
562+
}
563+
564+
#[test]
565+
fn check_rca_for_index_range_on_array_with_dynamic_contents() {
566+
let mut compilation_context = CompilationContext::default();
567+
compilation_context.update(
568+
r#"
569+
use qs = Qubit[5];
570+
let arr = MResetEachZ(qs);
571+
Std.Arrays.IndexRange(arr)"#,
572+
);
573+
let package_store_compute_properties = compilation_context.get_compute_properties();
574+
check_last_statement_compute_properties(
575+
package_store_compute_properties,
576+
&expect![[r#"
577+
ApplicationsGeneratorSet:
578+
inherent: Quantum: QuantumProperties:
579+
runtime_features: RuntimeFeatureFlags(0x0)
580+
value_kind: Element(Static)
581+
dynamic_param_applications: <empty>"#]],
582+
);
583+
}

0 commit comments

Comments
 (0)