diff --git a/source/compiler/qsc/src/codegen/tests.rs b/source/compiler/qsc/src/codegen/tests.rs index e51a038fee8..545d19464d3 100644 --- a/source/compiler/qsc/src/codegen/tests.rs +++ b/source/compiler/qsc/src/codegen/tests.rs @@ -5779,10 +5779,7 @@ fn foreign_table_lookup_callable_generates_qir() { use address = Qubit[1]; use output = Qubit[1]; Std.TableLookup.Select([[false], [true]], address, output); - - // Adjoint of Select does not work until support for static sized, dynamic content arrays is added. - // See related issue: https://github.com/microsoft/qdk/issues/3388 - // Adjoint Std.TableLookup.Select([[false], [true]], address, output); + Adjoint Std.TableLookup.Select([[false], [true]], address, output); } "#}; for profile in [Profile::AdaptiveRI, Profile::AdaptiveRIF, Profile::Adaptive] { diff --git a/source/compiler/qsc_circuit/src/rir_to_circuit.rs b/source/compiler/qsc_circuit/src/rir_to_circuit.rs index 1816207a59d..cff83b3806b 100644 --- a/source/compiler/qsc_circuit/src/rir_to_circuit.rs +++ b/source/compiler/qsc_circuit/src/rir_to_circuit.rs @@ -423,6 +423,7 @@ fn process_variables( store_expr_in_variable(&mut state.variables, *variable, expr)?; } instruction @ (Instruction::Store(..) + | Instruction::StoreArray(..) | Instruction::BitwiseNot(..) | Instruction::Alloca(..) | Instruction::Load(..) diff --git a/source/compiler/qsc_codegen/src/qir/v1.rs b/source/compiler/qsc_codegen/src/qir/v1.rs index 528424c1143..26f7abe2bad 100644 --- a/source/compiler/qsc_codegen/src/qir/v1.rs +++ b/source/compiler/qsc_codegen/src/qir/v1.rs @@ -221,7 +221,9 @@ impl ToQir for rir::Instruction { rir::Instruction::Srem(lhs, rhs, variable) => { binop_to_qir("srem", lhs, rhs, *variable, program) } - rir::Instruction::Store(_, _) => unimplemented!("store should be removed by pass"), + rir::Instruction::Store(_, _) | rir::Instruction::StoreArray(_, _) => { + unimplemented!("store should be removed by pass") + } rir::Instruction::Sub(lhs, rhs, variable) => { binop_to_qir("sub", lhs, rhs, *variable, program) } diff --git a/source/compiler/qsc_codegen/src/qir/v2.rs b/source/compiler/qsc_codegen/src/qir/v2.rs index f9f1a77f4c6..56b7533e5d4 100644 --- a/source/compiler/qsc_codegen/src/qir/v2.rs +++ b/source/compiler/qsc_codegen/src/qir/v2.rs @@ -228,6 +228,9 @@ impl ToQir for rir::Instruction { rir::Instruction::Store(operand, variable) => { store_to_qir(*operand, *variable, program) } + rir::Instruction::StoreArray(operands, variable) => { + store_array_to_qir(operands, *variable, program) + } rir::Instruction::Sub(lhs, rhs, variable) => { binop_to_qir("sub", lhs, rhs, *variable, program) } @@ -291,6 +294,37 @@ fn store_to_qir(operand: rir::Operand, variable: rir::Variable, program: &rir::P ) } +fn store_array_to_qir( + operands: &[rir::Operand], + variable: rir::Variable, + program: &rir::Program, +) -> String { + // Since SSA variables cannot be used in array literals, we cannot store the whole + // array into the alloca'ed space in one instruction. Instead, iterate through the array + // and create temporary variables for each memory location via getelementptr, then store + // the corresponding value into the array. + // This expands the single `StoreArray` instruction into 2N instructions, where N is the + // number of elements in the array. + let var_ty = get_variable_ty(variable); + let mut qir = String::new(); + let var_str = ToQir::::to_qir(&variable.variable_id, program); + for (i, operand) in operands.iter().enumerate() { + let temp_var = format!("{var_str}_{i}"); + writeln!( + qir, + " {temp_var} = getelementptr {var_ty}, ptr {var_str}, i64 0, i64 {i}" + ) + .expect("writing to string should succeed"); + write!( + qir, + " store {}, ptr {temp_var}", + ToQir::::to_qir(operand, program) + ) + .expect("writing to string should succeed"); + } + qir +} + fn load_to_qir(var_from: rir::Variable, var_to: rir::Variable, program: &rir::Program) -> String { let var_to_ty = get_variable_ty(var_to); format!( diff --git a/source/compiler/qsc_partial_eval/src/evaluation_context.rs b/source/compiler/qsc_partial_eval/src/evaluation_context.rs index 9d437d07bfb..f0ae3ffd8dc 100644 --- a/source/compiler/qsc_partial_eval/src/evaluation_context.rs +++ b/source/compiler/qsc_partial_eval/src/evaluation_context.rs @@ -10,7 +10,7 @@ use qsc_fir::fir::{LocalItemId, LocalVarId, PackageId}; use qsc_rca::{ComputeKind, RuntimeFeatureFlags, ValueKind}; use qsc_rir::rir::{BlockId, Literal, VariableId}; use rustc_hash::FxHashMap; -use std::collections::hash_map::Entry; +use std::{collections::hash_map::Entry, rc::Rc}; use crate::{ScopeDbgContext, is_static_value}; @@ -24,7 +24,7 @@ pub struct EvaluationContext { impl EvaluationContext { /// Creates a new evaluation context. pub fn new(package_id: PackageId, initial_block: BlockId) -> Self { - let entry_callable_scope = Scope::new(package_id, None, Vec::new(), None); + let entry_callable_scope = Scope::new(package_id, None, Vec::new(), None, Vec::new()); Self { active_blocks: vec![BlockNode { id: initial_block, @@ -118,6 +118,9 @@ pub struct Scope { active_block_count: usize, /// Debug context, used for generating debug metadata. pub(crate) dbg_context: ScopeDbgContext, + /// Any locally constant arrays associated with this scope, along with the variable ID + /// they are assigned (which happens after the scope is created). + pub(crate) arrays: Vec<(Rc>, Option)>, } impl Scope { @@ -127,6 +130,7 @@ impl Scope { callable: Option<(LocalItemId, FunctorApp)>, args: Vec, ctls_arg: Option, + arrays: Vec>>, ) -> Self { // Create the environment for the classical evaluator. // A default classical evaluator environment is created with one scope. However, we need to push an additional @@ -184,6 +188,7 @@ impl Scope { hybrid_vars, static_vars: FxHashMap::default(), dbg_context: ScopeDbgContext::default(), + arrays: arrays.into_iter().map(|array| (array, None)).collect(), } } diff --git a/source/compiler/qsc_partial_eval/src/lib.rs b/source/compiler/qsc_partial_eval/src/lib.rs index d262441cb4f..2aeb1da63bb 100644 --- a/source/compiler/qsc_partial_eval/src/lib.rs +++ b/source/compiler/qsc_partial_eval/src/lib.rs @@ -59,7 +59,7 @@ pub use qsc_rir::{ }, }; use rustc_hash::{FxHashMap, FxHashSet}; -use std::{collections::hash_map::Entry, rc::Rc, result::Result}; +use std::{collections::hash_map::Entry, mem::take, rc::Rc, result::Result}; use thiserror::Error; /// Partially evaluates a program with the specified entry expression. @@ -216,6 +216,10 @@ struct PartialEvaluator<'a> { dbg_context: DbgContext, } +// The resolved arguments of a callable invocation, including the resolved argument values, the optional +// resolved control qubits, and any array arguments with dynamic content of type `Value::Var`. +type ResolvedArguments = (Vec, Option, Vec>>); + #[derive(Clone, Copy)] pub struct PartialEvalConfig { pub generate_debug_metadata: bool, @@ -1592,7 +1596,7 @@ impl<'a> PartialEvaluator<'a> { ); None }; - let (args, ctls_arg) = self.resolve_args( + let (args, ctls_arg, arrays) = self.resolve_args( (store_item_id.package, callable_decl.input).into(), args_value.clone(), Some(args_span), @@ -1649,6 +1653,7 @@ impl<'a> PartialEvaluator<'a> { Some((store_item_id.item, functor_app)), args, ctls_arg, + arrays, ); self.check_unresolved_call_capabilities(call_expr_id, callee_expr_id, &call_scope)?; @@ -1788,7 +1793,7 @@ impl<'a> PartialEvaluator<'a> { panic!("global call to intrinsic function not supported"); }; - let (args, ctls_arg) = self.resolve_args( + let (args, ctls_arg, arrays) = self.resolve_args( (store_item_id.package, callable_decl.input).into(), args, None, @@ -1800,6 +1805,7 @@ impl<'a> PartialEvaluator<'a> { Some((store_item_id.item, FunctorApp::default())), args, ctls_arg, + arrays, ); // We generate instructions differently depending on whether we are calling an intrinsic or a specialization @@ -2017,7 +2023,7 @@ impl<'a> PartialEvaluator<'a> { let callable_id = self.get_or_insert_callable(callable); // Resolve the call arguments, create the call instruction and insert it to the current block. - let (args, ctls_arg) = self + let (args, ctls_arg, _) = self .resolve_args( (store_item_id.package, callable_decl.input).into(), args_value, @@ -2070,6 +2076,39 @@ impl<'a> PartialEvaluator<'a> { spec_decl: &SpecDecl, ) -> Result { self.eval_context.push_scope(call_scope); + + // Some arguments may include arrays with dynamic content, which we want to allow later instructions to index into. + // To support this, we treat these as locally constant arrays and emit an RIR instruction to store their contents into a new + // variable. Later instructions can then refer to this argument array based on it's Rc pointer and emit index instructions + // with dynamic indices into that array. We know this is safe for arrays passed as arguments because they are read-only + // by definition. + let mut local_constant_arrays = take(&mut self.eval_context.get_current_scope_mut().arrays); + for (array, var_id) in &mut local_constant_arrays { + let new_var_id = self.resource_manager.next_var(); + *var_id = Some(new_var_id); + let operands = array + .iter() + .map(|value| self.map_eval_value_to_rir_operand(value)) + .collect::>(); + let rir::Ty::Prim(elem_ty) = operands + .first() + .expect("array should have at least one element") + .get_type() + else { + panic!("array element type should be a primitive type"); + }; + self.get_current_rir_block_mut() + .0 + .push(Instruction::StoreArray( + operands, + rir::Variable { + variable_id: new_var_id, + ty: rir::Ty::Array(array.len(), elem_ty), + }, + )); + } + self.eval_context.get_current_scope_mut().arrays = local_constant_arrays; + let block_value = self.try_eval_block(spec_decl.block)?.into_value(); let popped_scope = self.eval_context.pop_scope(); assert!( @@ -2414,6 +2453,7 @@ impl<'a> PartialEvaluator<'a> { Some((store_item_id.item, functor_app)), body_args, None, + Vec::new(), ); self.eval_context.push_block_node(BlockNode { id: body_block_id, @@ -3757,7 +3797,7 @@ impl<'a> PartialEvaluator<'a> { args_span: Option, ctls: Option<(StorePatId, u8)>, fixed_args: Option>, - ) -> Result<(Vec, Option), Error> { + ) -> Result { let mut value = value; let ctls_arg = if let Some((ctls_pat_id, ctls_count)) = ctls { let mut ctls = vec![]; @@ -3800,15 +3840,23 @@ impl<'a> PartialEvaluator<'a> { }; let pat = self.package_store.get_pat(store_pat_id); - let args = match &pat.kind { - PatKind::Discard => vec![Arg::Discard(value)], + let (args, arrays) = match &pat.kind { + PatKind::Discard => (vec![Arg::Discard(value)], Vec::new()), PatKind::Bind(ident) => { + let arrays = if let Value::Array(array) = &value { + // If the value is an array, recursively check if any of the contents are variables, which would + // make the array a dynamic content array, and add them to the list of tracked arrays + // that we want to emit store instructions for. + get_arrays_with_dynamic_content(array) + } else { + Vec::new() + }; let variable = Variable { name: ident.name.clone(), value, span: ident.span, }; - vec![Arg::Var(ident.id, variable)] + (vec![Arg::Var(ident.id, variable)], arrays) } PatKind::Tuple(pats) => { let values = value.unwrap_tuple(); @@ -3818,10 +3866,11 @@ impl<'a> PartialEvaluator<'a> { "pattern tuple and value tuple have different arity" ); let mut args = Vec::new(); + let mut arrays = Vec::new(); let pat_value_tuples = pats.iter().zip(values.to_vec()); for (pat_id, value) in pat_value_tuples { // At this point we should no longer have control qubits so pass None. - let (mut element_args, None) = self + let (mut element_args, None, mut elem_arrays) = self .resolve_args( (store_pat_id.package, *pat_id).into(), value, @@ -3834,11 +3883,12 @@ impl<'a> PartialEvaluator<'a> { panic!("no control qubits are expected"); }; args.append(&mut element_args); + arrays.append(&mut elem_arrays); } - args + (args, arrays) } }; - Ok((args, ctls_arg)) + Ok((args, ctls_arg, arrays)) } fn try_eval_block(&mut self, block_id: BlockId) -> Result { @@ -4654,22 +4704,59 @@ impl<'a> PartialEvaluator<'a> { array_package_span: PackageSpan, array_elem_ty: &Ty, ) -> Result { - let array_literal = convert_to_array_literal(array, array_package_span, array_elem_ty)?; - let array_elem_ty = array_literal.ty; - - let const_array_id = if let Some(idx) = self - .program - .array_literals + let (array_operand, array_elem_ty) = if let Some((_, var_id)) = self + .eval_context + .get_current_scope() + .arrays .iter() - .position(|a| a == &array_literal) + .find(|(stored_array, _)| Rc::ptr_eq(stored_array, array)) { - idx + // The array being indexed matches one of the dynamic content arrays tracked as a locally constant argument + // to the current scope. This means we can emit an index instruction pointed at the variable created on entry + // to the current scope. + let Some(var_id) = var_id else { + panic!("array variable ID not found in current scope"); + }; + let Ok(rir::Ty::Prim(elem_rir_prim_ty)) = map_fir_type_to_rir_type(array_elem_ty) + else { + return Err(Error::Unexpected( + "array with non-primitive RIR type".to_string(), + array_package_span, + )); + }; + ( + Operand::Variable(rir::Variable { + variable_id: *var_id, + ty: rir::Ty::Array(array.len(), elem_rir_prim_ty), + }), + elem_rir_prim_ty, + ) } else { - let idx = self.program.array_literals.len(); - self.program.array_literals.push(array_literal); - idx - }; + // The array being indexed is not one tracked by the scope, so try to convert it into a static array literal + // that will be stored in the global section of the program. If it matches an existing array literal, we can + // reuse that identifier, otherwise a new one is generated and stored into the program. + // The index instruction will then be emitted to index into that array literal. + let array_literal = convert_to_array_literal(array, array_package_span, array_elem_ty)?; + let array_elem_ty = array_literal.ty; + + let const_array_id = if let Some(idx) = self + .program + .array_literals + .iter() + .position(|a| a == &array_literal) + { + idx + } else { + let idx = self.program.array_literals.len(); + self.program.array_literals.push(array_literal); + idx + }; + ( + Operand::Literal(Literal::Array(const_array_id)), + array_elem_ty, + ) + }; let variable_id = self.resource_manager.next_var(); let rir_variable = rir::Variable { variable_id, @@ -4677,7 +4764,7 @@ impl<'a> PartialEvaluator<'a> { }; self.get_current_rir_block_mut().0.push(Instruction::Index( - Operand::Literal(Literal::Array(const_array_id)), + array_operand, Operand::Variable(map_eval_var_to_rir_var(var)), rir_variable, )); @@ -5132,3 +5219,23 @@ fn convert_to_array_literal( ty: elem_rir_prim_ty, }) } + +/// Recursively traverse the given array to collect any arrays with dynamic contents (arrays that contain RIR variables), +/// including the given array itself, and collect any into a flat vector. This allows them to be tracked as locally +/// constant arrays used as arguments to the current scope. +fn get_arrays_with_dynamic_content(array: &Rc>) -> Vec>> { + let mut arrays_with_dynamic_content = Vec::new(); + for elem in array.iter() { + match elem { + Value::Array(inner) => { + arrays_with_dynamic_content.append(&mut get_arrays_with_dynamic_content(inner)); + } + Value::Var(_) => { + arrays_with_dynamic_content.push(Rc::clone(array)); + break; + } + _ => {} + } + } + arrays_with_dynamic_content +} diff --git a/source/compiler/qsc_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index 5a4d6efe717..24f0213c583 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -182,12 +182,12 @@ impl<'a> Analyzer<'a> { let mut default_value_kind = ValueKind::Constant; // If we are within a dynamic scope, the compute kind of the assign index expression must be variable and an additional - // runtime feature is used to mark the array itself as dynamically sized. + // runtime feature is used to mark the array itself as dynamic. if !application_instance.active_dynamic_scopes.is_empty() { default_value_kind = ValueKind::Variable; replacement_value_compute_kind = replacement_value_compute_kind.aggregate(ComputeKind::Dynamic { - runtime_features: RuntimeFeatureFlags::UseOfDynamicallySizedArray, + runtime_features: RuntimeFeatureFlags::UseOfDynamicArray, value_kind: ValueKind::Constant, }); } @@ -829,8 +829,10 @@ impl<'a> Analyzer<'a> { // If the index expression is variable, the value kind of the expression is at least dynamic (possibly variable) // and an additional runtime feature is used. - if let ComputeKind::Dynamic { value_kind, .. } = &index_expr_compute_kind - && *value_kind == ValueKind::Variable + if let ComputeKind::Dynamic { + value_kind: ValueKind::Variable, + .. + } = &index_expr_compute_kind { let mut dynamic_runtime_features = RuntimeFeatureFlags::UseOfDynamicIndex; diff --git a/source/compiler/qsc_rca/src/tests/arrays.rs b/source/compiler/qsc_rca/src/tests/arrays.rs index a5d725755be..9d541d85818 100644 --- a/source/compiler/qsc_rca/src/tests/arrays.rs +++ b/source/compiler/qsc_rca/src/tests/arrays.rs @@ -375,7 +375,7 @@ fn check_rca_for_mutable_array_assign_index_in_dynamic_context() { &expect![[r#" ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicallySizedArray) + runtime_features: RuntimeFeatureFlags(UseOfDynamicArray) value_kind: Variable dynamic_param_applications: "#]], ); @@ -400,7 +400,7 @@ fn check_rca_for_mutable_array_assign_index_dynamic_content_in_dynamic_context() &expect![[r#" ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicallySizedArray | QubitAllocation) + runtime_features: RuntimeFeatureFlags(UseOfDynamicArray | QubitAllocation) value_kind: Variable dynamic_param_applications: "#]], ); @@ -425,7 +425,7 @@ fn check_rca_for_mutable_array_assign_index_dynamic_nested_array_content_in_dyna &expect![[r#" ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicallySizedArray | QubitAllocation) + runtime_features: RuntimeFeatureFlags(UseOfDynamicArray | QubitAllocation) value_kind: Variable dynamic_param_applications: "#]], ); diff --git a/source/compiler/qsc_rir/src/passes/insert_alloca_load.rs b/source/compiler/qsc_rir/src/passes/insert_alloca_load.rs index 545755287d2..327244cf00b 100644 --- a/source/compiler/qsc_rir/src/passes/insert_alloca_load.rs +++ b/source/compiler/qsc_rir/src/passes/insert_alloca_load.rs @@ -89,6 +89,27 @@ fn add_alloca_load_to_block( *next_var_id = next_var_id.successor(); continue; } + Instruction::StoreArray(operands, var) => { + vars_to_alloca.insert(var.variable_id, *var); + let new_operands = operands + .iter() + .map(|operand| { + map_or_load_operand( + operand, + &mut var_map, + &mut block.0, + next_var_id, + should_load_operand(operand, vars_to_alloca), + ) + }) + .collect(); + block.0.push(Instruction::StoreArray(new_operands, *var)); + // Drop the cached load for this variable so a later read in this + // block reloads the freshly stored value instead of a stale one. + var_map.remove(&var.variable_id); + *next_var_id = next_var_id.successor(); + continue; + } // Replace any arguments with the new values of stored variables. Instruction::Call(_, args, _, _) => { diff --git a/source/compiler/qsc_rir/src/passes/prune_unneeded_stores.rs b/source/compiler/qsc_rir/src/passes/prune_unneeded_stores.rs index a7b31e81cfe..b2061d7a38a 100644 --- a/source/compiler/qsc_rir/src/passes/prune_unneeded_stores.rs +++ b/source/compiler/qsc_rir/src/passes/prune_unneeded_stores.rs @@ -99,6 +99,7 @@ fn process_callable(program: &mut Program, callable_id: CallableId) { } } +#[allow(clippy::too_many_lines)] fn check_var_usage( program: &mut Program, block_id: crate::rir::BlockId, @@ -115,6 +116,14 @@ fn check_var_usage( } stored_vars.insert(variable.variable_id); } + Instruction::StoreArray(operands, variable) => { + for operand in operands { + if let crate::rir::Operand::Variable(var) = operand { + used_vars.insert(var.variable_id); + } + } + stored_vars.insert(variable.variable_id); + } Instruction::Call(_, operands, variable, _) => { if let Some(var) = variable diff --git a/source/compiler/qsc_rir/src/passes/ssa_check.rs b/source/compiler/qsc_rir/src/passes/ssa_check.rs index d396a5f5f7b..7bd0eea96b8 100644 --- a/source/compiler/qsc_rir/src/passes/ssa_check.rs +++ b/source/compiler/qsc_rir/src/passes/ssa_check.rs @@ -252,7 +252,10 @@ fn get_variable_uses(program: &Program) -> IndexMap { + Instruction::StoreArray(..) + | Instruction::Alloca(..) + | Instruction::Load(..) + | Instruction::Index(..) => { panic!("Unexpected advanced instruction at {block_id:?}, instruction {idx}") } } diff --git a/source/compiler/qsc_rir/src/passes/type_check.rs b/source/compiler/qsc_rir/src/passes/type_check.rs index 0075e2d7f8e..05591dbdb66 100644 --- a/source/compiler/qsc_rir/src/passes/type_check.rs +++ b/source/compiler/qsc_rir/src/passes/type_check.rs @@ -81,6 +81,24 @@ fn check_instr_types(program: &Program, instr: &Instruction) { assert_eq!(index.get_type(), Ty::Prim(Prim::Integer)); } + Instruction::StoreArray(operands, var) => { + let Ty::Array(size, elem_ty) = &var.ty else { + panic!("expected variable to be of array type"); + }; + assert_eq!( + operands.len(), + *size, + "expected number of operands to match array size" + ); + for opr in operands { + assert_eq!( + opr.get_type(), + Ty::Prim(*elem_ty), + "expected operand type to match array element type" + ); + } + } + Instruction::Convert(_, _) | Instruction::Jump(_) | Instruction::Alloca(..) diff --git a/source/compiler/qsc_rir/src/rir.rs b/source/compiler/qsc_rir/src/rir.rs index 9c04582b93d..026199213f7 100644 --- a/source/compiler/qsc_rir/src/rir.rs +++ b/source/compiler/qsc_rir/src/rir.rs @@ -376,6 +376,7 @@ impl Display for FcmpConditionCode { #[derive(Clone, Debug)] pub enum Instruction { Store(Operand, Variable), + StoreArray(Vec, Variable), Call( CallableId, Vec, @@ -432,6 +433,9 @@ impl Display for Instruction { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match &self { Self::Store(value, variable) => write_unary_instruction(f, "Store", value, *variable)?, + Self::StoreArray(value, variable) => { + write_store_array_instruction(f, value, *variable)?; + } Self::Jump(block_id) => write!(f, "Jump({})", block_id.0)?, Self::Call(callable_id, args, variable, metadata) => { write_call(f, *callable_id, args, *variable, metadata.as_deref())?; @@ -786,6 +790,23 @@ impl PartialEq for ArrayLiteral { } } +fn write_store_array_instruction( + f: &mut Formatter, + value: &[Operand], + variable: Variable, +) -> fmt::Result { + let mut indent = set_indentation(indented(f), 0); + write!(indent, "{variable} = StoreArray [")?; + for (index, operand) in value.iter().enumerate() { + write!(indent, "{operand}")?; + if index != value.len() - 1 { + write!(indent, ", ")?; + } + } + write!(indent, "]")?; + Ok(()) +} + fn write_binary_instruction( f: &mut Formatter, instruction: &str, diff --git a/source/compiler/qsc_rir/src/utils.rs b/source/compiler/qsc_rir/src/utils.rs index 07a63d7bed9..08dfab3ec47 100644 --- a/source/compiler/qsc_rir/src/utils.rs +++ b/source/compiler/qsc_rir/src/utils.rs @@ -110,6 +110,7 @@ pub fn get_variable_assignments(program: &Program) -> IndexMap { @@ -155,6 +156,18 @@ pub(crate) fn map_variable_use_in_block( continue; } } + Instruction::StoreArray(operand, var) => { + if var_stor_to_keep.contains(&var.variable_id) { + // Only keep stores to variables that are in the set to keep. + *operand = operand + .iter() + .map(|op| op.mapped(var_map)) + .collect::>(); + } else { + // Otherwise drop the store array by continuing the loop. + continue; + } + } // Replace any arguments with the new values of stored variables. Instruction::Call(_, args, _, _) => {