From ce9b0b65b4afef39057b001422f78c6f0e5e68fa Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Thu, 1 Oct 2026 10:43:17 -0700 Subject: [PATCH 1/4] Fix miscompile due to SSA aliasing This change updates variable mapping logic in RIR passes to differentiate between "strong" and "weak" mappings, where the former indicates the mapping should be followed through successive links until the terminator operand is reached and the latter indicates no further mapping should be performed. Fixes #3769 --- .../qsc/src/codegen/tests/adaptive_profile.rs | 64 +++++++ .../src/codegen/tests/adaptive_ri_profile.rs | 57 ++++++ .../qsc_rir/src/passes/ssa_transform.rs | 29 +-- .../qsc_rir/src/passes/ssa_transform/tests.rs | 170 ++++++++++++++++++ source/compiler/qsc_rir/src/rir.rs | 32 ++++ source/compiler/qsc_rir/src/utils.rs | 66 +++++-- 6 files changed, 391 insertions(+), 27 deletions(-) diff --git a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs index b49998dc738..7a6e58e70e7 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs @@ -2616,6 +2616,70 @@ fn preparepurestated_cyclic_library_calls_generate_correct_qir() { .assert_eq(&qir); } +#[test] +fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable_updated() { + let source = " + operation Main() : Int { + use q0 = Qubit(); + mutable n = 0; + if MResetZ(q0) == One { n = 2; } + let h = n; + n = 7; + h + n + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + @0 = internal constant [4 x i8] c"0_i\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + %var_0 = alloca i64 + call void @__quantum__rt__initialize(ptr null) + store i64 0, ptr %var_0 + call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) + %var_1 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) + br i1 %var_1, label %block_1, label %block_2 + block_1: + store i64 2, ptr %var_0 + br label %block_2 + block_2: + store i64 7, ptr %var_0 + %var_8 = load i64, ptr %var_0 + %var_5 = add i64 %var_8, 7 + call void @__quantum__rt__int_record_output(i64 %var_5, ptr @0) + ret i64 0 + } + + declare void @__quantum__rt__initialize(ptr) + + declare void @__quantum__qis__mresetz__body(ptr, ptr) #1 + + declare i1 @__quantum__rt__read_result(ptr) #2 + + declare void @__quantum__rt__int_record_output(i64, ptr) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + attributes #2 = { nofree nosync nounwind willreturn memory(argmem: read) } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9} + + !0 = !{i32 1, !"qir_major_version", i32 2} + !1 = !{i32 7, !"qir_minor_version", i32 1} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 5, !"int_computations", !{!"i64"}} + !5 = !{i32 5, !"float_computations", !{!"double"}} + !6 = !{i32 7, !"backwards_branching", i2 3} + !7 = !{i32 1, !"arrays", i1 true} + !8 = !{i32 1, !"ir_functions", i1 true} + !9 = !{i32 1, !"writable_results", i1 true} + "#]].assert_eq(&qir); +} + // ---- Cross-package (foreign) IR-function emission ---- /// A reachable, eligible operation that lives in a separate library package diff --git a/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs b/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs index 090abf75014..0545a4b2aff 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs @@ -686,3 +686,60 @@ fn preparepurestated_cyclic_library_calls_generate_correct_qir() { !4 = !{i32 5, !"int_computations", !{!"i64"}} "#]].assert_eq(&qir); } + +#[test] +fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable_updated() { + let source = " + operation Main() : Int { + use q0 = Qubit(); + mutable n = 0; + if MResetZ(q0) == One { n = 2; } + let h = n; + n = 7; + h + n + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + @0 = internal constant [4 x i8] c"0_i\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__rt__initialize(i8* null) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + %var_1 = call i1 @__quantum__rt__read_result(%Result* inttoptr (i64 0 to %Result*)) + br i1 %var_1, label %block_1, label %block_2 + block_1: + br label %block_2 + block_2: + %var_7 = phi i64 [0, %block_0], [2, %block_1] + %var_5 = add i64 %var_7, 7 + call void @__quantum__rt__int_record_output(i64 %var_5, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0)) + ret i64 0 + } + + declare void @__quantum__rt__initialize(i8*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare i1 @__quantum__rt__read_result(%Result*) + + declare void @__quantum__rt__int_record_output(i64, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 5, !"int_computations", !{!"i64"}} + "#]].assert_eq(&qir); +} diff --git a/source/compiler/qsc_rir/src/passes/ssa_transform.rs b/source/compiler/qsc_rir/src/passes/ssa_transform.rs index b81c047d82e..63095266ec6 100644 --- a/source/compiler/qsc_rir/src/passes/ssa_transform.rs +++ b/source/compiler/qsc_rir/src/passes/ssa_transform.rs @@ -5,7 +5,7 @@ mod tests; use crate::{ - rir::{BlockId, Instruction, Operand, Program, Ty, Variable, VariableId}, + rir::{BlockId, Instruction, Operand, OperandMapping, Program, Ty, Variable, VariableId}, utils::{get_all_block_successors, get_variable_assignments, map_variable_use_in_block}, }; use qsc_data_structures::index_map::IndexMap; @@ -101,7 +101,8 @@ fn transform_body_to_ssa( let first_pred_map = block_var_map .get(*first_pred) .expect("block should have variable map"); - 'var_loop: for (var_id, operand) in first_pred_map { + 'var_loop: for (var_id, mapping) in first_pred_map { + let operand = Into::::into(mapping); let mut phi_nodes = FxHashMap::default(); if rest_preds.iter().any(|pred| { @@ -109,6 +110,7 @@ fn transform_body_to_ssa( .get(*pred) .expect("block should have variable map") .get(var_id) + .map(std::convert::Into::into) != Some(operand) }) { // Some predecessors have different values for this variable, so a phi node is needed. @@ -119,7 +121,7 @@ fn transform_body_to_ssa( .get(*pred) .expect("block should have variable map"); let mut pred_operand = match pred_var_map.get(var_id) { - Some(operand) => *operand, + Some(mapping) => Into::::into(mapping), None => { // If the variable is not defined in this predecessor, it does not dominate this block. // Assume it is not used and skip creating a phi node for this variable. If the variable is used, @@ -134,7 +136,7 @@ fn transform_body_to_ssa( } else { // If all predecessors have the same value for this variable, the value can be propagated. // Update the block variable map with the common operand. - var_map_updates.insert(*var_id, *operand); + var_map_updates.insert(*var_id, *mapping); } // For any phi nodes that need to be inserted, create a new variable and insert @@ -147,7 +149,12 @@ fn transform_body_to_ssa( }; let phi_node = Instruction::Phi(args, new_var); block.0.insert(0, phi_node); - var_map_updates.insert(variable_id, Operand::Variable(new_var)); + // A phi node mapping is always strong, and since the variable is newly created, + // there are no existing strong mappings to downgrade. + var_map_updates.insert( + variable_id, + OperandMapping::Strong(Operand::Variable(new_var)), + ); *next_var_id = next_var_id.successor(); } } @@ -156,11 +163,11 @@ fn transform_body_to_ssa( // Now that the block has finished processing, apply any updates to the block and // merge those updates into the stored variable map to propagate to successors. map_variable_use_in_block(block, &mut var_map_updates, &FxHashSet::default()); - for (var_id, operand) in var_map_updates { + for (var_id, mapping) in var_map_updates { let var_map = block_var_map .get_mut(block_id) .expect("block should have variable map"); - var_map.entry(var_id).or_insert(operand); + var_map.entry(var_id).or_insert(mapping); } } } @@ -187,10 +194,10 @@ fn map_store_to_dominated_ssa( entry: BlockId, input_vars: &[(VariableId, Ty)], preds: &IndexMap>, -) -> IndexMap> { +) -> IndexMap> { let mut block_var_map = IndexMap::default(); for &block_id in body_blocks { - let mut var_map: FxHashMap = match preds.get(block_id) { + let mut var_map: FxHashMap = match preds.get(block_id) { Some(block_preds) if block_preds.len() == 1 => { // Any block with a single predecessor inherits those mapped variables. block_var_map @@ -207,10 +214,10 @@ fn map_store_to_dominated_ssa( var_map .insert( var_id, - Operand::Variable(Variable { + OperandMapping::Strong(Operand::Variable(Variable { variable_id: var_id, ty, - }), + })), ) .is_none(), "input vars should only be initialized once by parameters" diff --git a/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs b/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs index 98cac604ac6..11c62c5ad8c 100644 --- a/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs +++ b/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs @@ -2759,6 +2759,176 @@ fn ssa_transform_allows_point_in_time_copy_of_dynamic_variable() { "#]].assert_eq(&program.to_string()); } +#[test] +fn ssa_transform_allows_point_in_time_copy_of_variable_mutated_across_blocks() { + let mut program = new_program(); + program.callables.insert( + CallableId(1), + Callable { + name: "dynamic_bool".to_string(), + input_type: Vec::new(), + output_type: Some(Ty::Prim(Prim::Boolean)), + body: None, + input_vars: Vec::new(), + call_type: CallableType::Regular, + }, + ); + + program.blocks.insert( + BlockId(0), + Block(vec![ + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(0)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(1), + BlockId(2), + None, + ), + ]), + ); + program.blocks.insert( + BlockId(1), + Block(vec![ + Instruction::Store( + Operand::Literal(Literal::Integer(1)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Jump(BlockId(2)), + ]), + ); + program.blocks.insert( + BlockId(2), + Block(vec![ + Instruction::Store( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(2)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Add( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Operand::Variable(Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Return(None), + ]), + ); + + // Before + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(2, Integer) = Store Variable(1, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Integer) = Add Variable(1, Integer), Variable(2, Integer) + Return + config: Config: + capabilities: Base + num_qubits: 0 + num_results: 0 + tags: + "#]] + .assert_eq(&program.to_string()); + + // After + transform_program(&mut program); + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Jump(2) + Block 2: Block: + Variable(4, Integer) = Phi ( [Integer(0), 0], [Integer(1), 1], ) + Variable(3, Integer) = Add Integer(2), Variable(4, Integer) + Return + config: Config: + capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations) + num_qubits: 0 + num_results: 0 + tags: + "#]].assert_eq(&program.to_string()); +} + #[test] fn ssa_transform_propagates_phi_var_to_successor_blocks_across_sequential_branches() { let mut program = new_program(); diff --git a/source/compiler/qsc_rir/src/rir.rs b/source/compiler/qsc_rir/src/rir.rs index 99e2d73adc0..ec2d429195a 100644 --- a/source/compiler/qsc_rir/src/rir.rs +++ b/source/compiler/qsc_rir/src/rir.rs @@ -831,6 +831,38 @@ impl PartialEq for ArrayLiteral { } } +/// Tracks a mapping to a specific operand, indicating whether the mapping is weak or strong. +#[derive(Copy, Clone, Debug)] +pub(crate) enum OperandMapping { + /// A weak mapping to an operand, where the mapped value should be replaced by this operand + /// directly without following any further mappings. + Weak(Operand), + /// A strong mapping to an operand, where the mapped value should be followed to its root before use. + Strong(Operand), +} + +impl OperandMapping { + pub(crate) fn is_weak(&self) -> bool { + matches!(self, OperandMapping::Weak(_)) + } +} + +impl From for Operand { + fn from(val: OperandMapping) -> Self { + match val { + OperandMapping::Weak(op) | OperandMapping::Strong(op) => op, + } + } +} + +impl From<&OperandMapping> for Operand { + fn from(val: &OperandMapping) -> Self { + match val { + OperandMapping::Weak(op) | OperandMapping::Strong(op) => *op, + } + } +} + fn write_store_array_instruction( f: &mut Formatter, value: &[Operand], diff --git a/source/compiler/qsc_rir/src/utils.rs b/source/compiler/qsc_rir/src/utils.rs index 930a35e2d32..14133690a0a 100644 --- a/source/compiler/qsc_rir/src/utils.rs +++ b/source/compiler/qsc_rir/src/utils.rs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::rir::{Block, BlockId, Instruction, Operand, Program, Variable, VariableId}; +use crate::rir::{ + Block, BlockId, Instruction, Operand, OperandMapping, Program, Variable, VariableId, +}; use qsc_data_structures::index_map::IndexMap; use rustc_hash::{FxHashMap, FxHashSet}; @@ -140,7 +142,7 @@ pub fn get_variable_assignments(program: &Program) -> IndexMap, + var_map: &mut FxHashMap, var_stor_to_keep: &FxHashSet, ) { let instrs = block.0.drain(..).collect::>(); @@ -153,10 +155,7 @@ pub(crate) fn map_variable_use_in_block( // Only keep stores to variables that are in the set to keep. *operand = operand.mapped(var_map); } else { - // Note this uses the mapped operand to make sure this variable points to whatever root literal or variable - // this operand corresponds to at this point in the block. This makes the new variable respect a point-in-time - // copy of the operand. - var_map.insert(var.variable_id, operand.mapped(var_map)); + update_variable_mapping(var_map, operand, var); continue; } } @@ -273,9 +272,33 @@ pub(crate) fn map_variable_use_in_block( } } +fn update_variable_mapping( + var_map: &mut FxHashMap, + operand: &mut Operand, + var: &mut Variable, +) { + // Note this uses the mapped operand to make sure this variable points to whatever root literal or variable + // this operand corresponds to at this point in the block. This makes the new variable respect a point-in-time + // copy of the operand. + var_map.insert( + var.variable_id, + OperandMapping::Strong(operand.mapped(var_map)), + ); + + // For all existing strong mappings to this variable, downgrade them to weak mappings. + // This ensures those previous mappings represent the value at the time they were created, rather than the new value being stored. + for mapping in var_map.values_mut() { + if let OperandMapping::Strong(Operand::Variable(existing)) = mapping + && existing == var + { + *mapping = OperandMapping::Weak(Operand::Variable(*existing)); + } + } +} + impl Operand { #[must_use] - pub fn mapped(&self, var_map: &FxHashMap) -> Operand { + pub(crate) fn mapped(&self, var_map: &FxHashMap) -> Operand { match self { Operand::Literal(_) => *self, Operand::Variable(var) => var.map_to_operand(var_map), @@ -285,28 +308,35 @@ impl Operand { impl Variable { #[must_use] - pub fn map_to_operand(self, var_map: &FxHashMap) -> Operand { + pub(crate) fn map_to_operand(self, var_map: &FxHashMap) -> Operand { let mut var = self; - while let Some(operand) = var_map.get(&var.variable_id) { - if let Operand::Variable(new_var) = operand { + while let Some(mapping) = var_map.get(&var.variable_id) { + if let Operand::Variable(new_var) = mapping.into() { if new_var.variable_id == var.variable_id { // The variable maps to itself, as happens when a live-in parameter is seeded as // its own definition. It is already at its root, so stop following the chain. break; } - var = *new_var; + var = new_var; + if mapping.is_weak() { + // Stop following the chain for weak mappings and use the current mapping as is. + break; + } } else { - return *operand; + return mapping.into(); } } Operand::Variable(var) } #[must_use] - pub fn map_to_variable(self, var_map: &FxHashMap) -> Variable { + pub(crate) fn map_to_variable( + self, + var_map: &FxHashMap, + ) -> Variable { let mut var = self; - while let Some(operand) = var_map.get(&var.variable_id) { - let Operand::Variable(new_var) = operand else { + while let Some(mapping) = var_map.get(&var.variable_id) { + let Operand::Variable(new_var) = mapping.into() else { panic!("literal not supported in this context"); }; if new_var.variable_id == var.variable_id { @@ -314,7 +344,11 @@ impl Variable { // own definition. It is already at its root, so stop following the chain. break; } - var = *new_var; + var = new_var; + if mapping.is_weak() { + // Stop following the chain for weak mappings and use the current mapping as is. + break; + } } var } From 07e4bfc382c34fed29194cd72bd3c0d73245ee85 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Thu, 1 Oct 2026 11:23:27 -0700 Subject: [PATCH 2/4] Update naming --- .../qsc_rir/src/passes/ssa_transform.rs | 8 ++++---- source/compiler/qsc_rir/src/rir.rs | 18 +++++++++--------- source/compiler/qsc_rir/src/utils.rs | 16 ++++++++-------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/source/compiler/qsc_rir/src/passes/ssa_transform.rs b/source/compiler/qsc_rir/src/passes/ssa_transform.rs index 63095266ec6..8cc710b71d0 100644 --- a/source/compiler/qsc_rir/src/passes/ssa_transform.rs +++ b/source/compiler/qsc_rir/src/passes/ssa_transform.rs @@ -149,11 +149,11 @@ fn transform_body_to_ssa( }; let phi_node = Instruction::Phi(args, new_var); block.0.insert(0, phi_node); - // A phi node mapping is always strong, and since the variable is newly created, - // there are no existing strong mappings to downgrade. + // A phi node mapping is always deep, and since the variable is newly created, + // there are no existing deep mappings to downgrade. var_map_updates.insert( variable_id, - OperandMapping::Strong(Operand::Variable(new_var)), + OperandMapping::Deep(Operand::Variable(new_var)), ); *next_var_id = next_var_id.successor(); } @@ -214,7 +214,7 @@ fn map_store_to_dominated_ssa( var_map .insert( var_id, - OperandMapping::Strong(Operand::Variable(Variable { + OperandMapping::Deep(Operand::Variable(Variable { variable_id: var_id, ty, })), diff --git a/source/compiler/qsc_rir/src/rir.rs b/source/compiler/qsc_rir/src/rir.rs index ec2d429195a..6755c97024d 100644 --- a/source/compiler/qsc_rir/src/rir.rs +++ b/source/compiler/qsc_rir/src/rir.rs @@ -831,26 +831,26 @@ impl PartialEq for ArrayLiteral { } } -/// Tracks a mapping to a specific operand, indicating whether the mapping is weak or strong. +/// Tracks a mapping to a specific operand, indicating whether the mapping is shallow or deep. #[derive(Copy, Clone, Debug)] pub(crate) enum OperandMapping { - /// A weak mapping to an operand, where the mapped value should be replaced by this operand + /// A shallow mapping to an operand, where the mapped value should be replaced by this operand /// directly without following any further mappings. - Weak(Operand), - /// A strong mapping to an operand, where the mapped value should be followed to its root before use. - Strong(Operand), + Shallow(Operand), + /// A deep mapping to an operand, where the mapped value should be followed to its root before use. + Deep(Operand), } impl OperandMapping { - pub(crate) fn is_weak(&self) -> bool { - matches!(self, OperandMapping::Weak(_)) + pub(crate) fn is_shallow(&self) -> bool { + matches!(self, OperandMapping::Shallow(_)) } } impl From for Operand { fn from(val: OperandMapping) -> Self { match val { - OperandMapping::Weak(op) | OperandMapping::Strong(op) => op, + OperandMapping::Shallow(op) | OperandMapping::Deep(op) => op, } } } @@ -858,7 +858,7 @@ impl From for Operand { impl From<&OperandMapping> for Operand { fn from(val: &OperandMapping) -> Self { match val { - OperandMapping::Weak(op) | OperandMapping::Strong(op) => *op, + OperandMapping::Shallow(op) | OperandMapping::Deep(op) => *op, } } } diff --git a/source/compiler/qsc_rir/src/utils.rs b/source/compiler/qsc_rir/src/utils.rs index 14133690a0a..e58a9e4d4a8 100644 --- a/source/compiler/qsc_rir/src/utils.rs +++ b/source/compiler/qsc_rir/src/utils.rs @@ -282,16 +282,16 @@ fn update_variable_mapping( // copy of the operand. var_map.insert( var.variable_id, - OperandMapping::Strong(operand.mapped(var_map)), + OperandMapping::Deep(operand.mapped(var_map)), ); - // For all existing strong mappings to this variable, downgrade them to weak mappings. + // For all existing deep mappings to this variable, downgrade them to shallow mappings. // This ensures those previous mappings represent the value at the time they were created, rather than the new value being stored. for mapping in var_map.values_mut() { - if let OperandMapping::Strong(Operand::Variable(existing)) = mapping + if let OperandMapping::Deep(Operand::Variable(existing)) = mapping && existing == var { - *mapping = OperandMapping::Weak(Operand::Variable(*existing)); + *mapping = OperandMapping::Shallow(Operand::Variable(*existing)); } } } @@ -318,8 +318,8 @@ impl Variable { break; } var = new_var; - if mapping.is_weak() { - // Stop following the chain for weak mappings and use the current mapping as is. + if mapping.is_shallow() { + // Stop following the chain for shallow mappings and use the current mapping as is. break; } } else { @@ -345,8 +345,8 @@ impl Variable { break; } var = new_var; - if mapping.is_weak() { - // Stop following the chain for weak mappings and use the current mapping as is. + if mapping.is_shallow() { + // Stop following the chain for shallow mappings and use the current mapping as is. break; } } From 991a9bcf65d95c872f43790ab925e5edd828a742 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Thu, 1 Oct 2026 15:02:09 -0700 Subject: [PATCH 3/4] Fix bugs across blocks, also more verbose loads/stores --- source/compiler/qsc/src/codegen/tests.rs | 7 +- .../qsc/src/codegen/tests/adaptive_profile.rs | 147 +++-- .../src/tests/statement/gate_call.rs | 60 +- .../qsc_rir/src/passes/ssa_transform.rs | 36 +- .../qsc_rir/src/passes/ssa_transform/tests.rs | 617 ++++++++++++++++++ source/compiler/qsc_rir/src/utils.rs | 16 +- .../adaptive/output/ArithmeticOps.ll | 94 +-- .../adaptive/output/CallReturnMath.ll | 11 +- .../resources/adaptive/output/Doubles.ll | 55 +- .../adaptive/output/MeasurementComparison.ll | 24 +- .../output/ThreeQubitRepetitionCode.ll | 81 ++- source/samples_test/src/tests/OpenQASM.rs | 6 +- source/samples_test/src/tests/algorithms.rs | 14 +- .../samples_test/src/tests/getting_started.rs | 2 +- 14 files changed, 938 insertions(+), 232 deletions(-) diff --git a/source/compiler/qsc/src/codegen/tests.rs b/source/compiler/qsc/src/codegen/tests.rs index 593c6b2844e..1d2bfa2176b 100644 --- a/source/compiler/qsc/src/codegen/tests.rs +++ b/source/compiler/qsc/src/codegen/tests.rs @@ -6030,6 +6030,7 @@ fn array_with_dynamic_contents_passed_as_argument_with_static_index_does_not_emi block_0: %var_2 = alloca i64 %var_5 = alloca i64 + %var_8 = alloca i64 call void @__quantum__rt__initialize(ptr null) call void @__quantum__qis__m__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) %var_0 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) @@ -6051,8 +6052,10 @@ fn array_with_dynamic_contents_passed_as_argument_with_static_index_does_not_emi store i64 0, ptr %var_5 br label %block_6 block_6: - %var_8 = load i64, ptr %var_2 - call void @__quantum__rt__int_record_output(i64 %var_8, ptr @0) + %var_11 = load i64, ptr %var_2 + store i64 %var_11, ptr %var_8 + %var_13 = load i64, ptr %var_8 + call void @__quantum__rt__int_record_output(i64 %var_13, ptr @0) ret i64 0 } diff --git a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs index 7a6e58e70e7..92d16f67f29 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs @@ -534,6 +534,7 @@ fn result_array_dynamic_index_succeeds() { %var_2 = alloca i64 %var_3 = alloca i64 %var_5 = alloca i1 + %var_11 = alloca i64 call void @__quantum__rt__initialize(ptr null) call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 1 to ptr), ptr inttoptr (i64 1 to ptr)) @@ -543,36 +544,38 @@ fn result_array_dynamic_index_succeeds() { store i64 0, ptr %var_3 br label %block_1 block_1: - %var_13 = load i64, ptr %var_3 - %var_4 = icmp sle i64 %var_13, 3 + %var_14 = load i64, ptr %var_3 + %var_4 = icmp sle i64 %var_14, 3 store i1 true, ptr %var_5 br i1 %var_4, label %block_2, label %block_3 block_2: - %var_16 = load i1, ptr %var_5 - br i1 %var_16, label %block_4, label %block_5 + %var_17 = load i1, ptr %var_5 + br i1 %var_17, label %block_4, label %block_5 block_3: store i1 false, ptr %var_5 br label %block_2 block_4: - %var_18 = load i64, ptr %var_3 - %var_19_offset_chk = icmp slt i64 %var_18, 0 - %var_19_offset = select i1 %var_19_offset_chk, i64 1, i64 0 - %var_19 = getelementptr [4 x ptr], ptr @array0, i64 %var_19_offset, i64 %var_18 - %var_6 = load ptr, ptr %var_19 + %var_21 = load i64, ptr %var_3 + %var_22_offset_chk = icmp slt i64 %var_21, 0 + %var_22_offset = select i1 %var_22_offset_chk, i64 1, i64 0 + %var_22 = getelementptr [4 x ptr], ptr @array0, i64 %var_22_offset, i64 %var_21 + %var_6 = load ptr, ptr %var_22 %var_7 = call i1 @__quantum__rt__read_result(ptr %var_6) br i1 %var_7, label %block_6, label %block_7 block_5: - %var_17 = load i64, ptr %var_2 - call void @__quantum__rt__int_record_output(i64 %var_17, ptr @0) + %var_18 = load i64, ptr %var_2 + store i64 %var_18, ptr %var_11 + %var_20 = load i64, ptr %var_11 + call void @__quantum__rt__int_record_output(i64 %var_20, ptr @0) ret i64 0 block_6: - %var_22 = load i64, ptr %var_2 - %var_9 = add i64 %var_22, 1 + %var_25 = load i64, ptr %var_2 + %var_9 = add i64 %var_25, 1 store i64 %var_9, ptr %var_2 br label %block_7 block_7: - %var_20 = load i64, ptr %var_3 - %var_10 = add i64 %var_20, 1 + %var_23 = load i64, ptr %var_3 + %var_10 = add i64 %var_23, 1 store i64 %var_10, ptr %var_3 br label %block_1 } @@ -641,6 +644,7 @@ fn result_array_while_loop_dynamic_index_succeeds() { block_0: %var_2 = alloca i64 %var_3 = alloca i64 + %var_10 = alloca i64 call void @__quantum__rt__initialize(ptr null) call void @H(ptr inttoptr (i64 0 to ptr)) call void @H(ptr inttoptr (i64 1 to ptr)) @@ -654,29 +658,31 @@ fn result_array_while_loop_dynamic_index_succeeds() { store i64 0, ptr %var_3 br label %block_1 block_1: - %var_12 = load i64, ptr %var_3 - %var_4 = icmp slt i64 %var_12, 4 + %var_13 = load i64, ptr %var_3 + %var_4 = icmp slt i64 %var_13, 4 br i1 %var_4, label %block_2, label %block_3 block_2: - %var_14 = load i64, ptr %var_3 - %var_15_offset_chk = icmp slt i64 %var_14, 0 - %var_15_offset = select i1 %var_15_offset_chk, i64 1, i64 0 - %var_15 = getelementptr [4 x ptr], ptr @array0, i64 %var_15_offset, i64 %var_14 - %var_5 = load ptr, ptr %var_15 + %var_17 = load i64, ptr %var_3 + %var_18_offset_chk = icmp slt i64 %var_17, 0 + %var_18_offset = select i1 %var_18_offset_chk, i64 1, i64 0 + %var_18 = getelementptr [4 x ptr], ptr @array0, i64 %var_18_offset, i64 %var_17 + %var_5 = load ptr, ptr %var_18 %var_6 = call i1 @__quantum__rt__read_result(ptr %var_5) br i1 %var_6, label %block_4, label %block_5 block_3: - %var_13 = load i64, ptr %var_2 - call void @__quantum__rt__int_record_output(i64 %var_13, ptr @0) + %var_14 = load i64, ptr %var_2 + store i64 %var_14, ptr %var_10 + %var_16 = load i64, ptr %var_10 + call void @__quantum__rt__int_record_output(i64 %var_16, ptr @0) ret i64 0 block_4: - %var_18 = load i64, ptr %var_2 - %var_8 = add i64 %var_18, 1 + %var_21 = load i64, ptr %var_2 + %var_8 = add i64 %var_21, 1 store i64 %var_8, ptr %var_2 br label %block_5 block_5: - %var_16 = load i64, ptr %var_3 - %var_9 = add i64 %var_16, 1 + %var_19 = load i64, ptr %var_3 + %var_9 = add i64 %var_19, 1 store i64 %var_9, ptr %var_3 br label %block_1 } @@ -742,6 +748,7 @@ fn mutable_result_variable_succeeds() { define i64 @ENTRYPOINT__main() #0 { block_0: %var_1 = alloca ptr + %var_5 = alloca ptr call void @__quantum__rt__initialize(ptr null) call void @H(ptr inttoptr (i64 0 to ptr)) call void @__quantum__qis__m__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) @@ -754,8 +761,10 @@ fn mutable_result_variable_succeeds() { store ptr inttoptr (i64 1 to ptr), ptr %var_1 br label %block_2 block_2: - %var_4 = load ptr, ptr %var_1 - call void @__quantum__rt__result_record_output(ptr %var_4, ptr @0) + %var_7 = load ptr, ptr %var_1 + store ptr %var_7, ptr %var_5 + %var_9 = load ptr, ptr %var_5 + call void @__quantum__rt__result_record_output(ptr %var_9, ptr @0) ret i64 0 } @@ -1108,6 +1117,7 @@ fn static_while_inside_emit_while_succeeds() { block_0: %var_0 = alloca i64 %var_3 = alloca i64 + %var_7 = alloca i64 call void @__quantum__rt__initialize(ptr null) store i64 0, ptr %var_0 br label %block_1 @@ -1119,19 +1129,21 @@ fn static_while_inside_emit_while_succeeds() { store i64 0, ptr %var_3 br label %block_4 block_3: - %var_8 = load i64, ptr %var_0 - call void @__quantum__rt__int_record_output(i64 %var_8, ptr @0) + %var_9 = load i64, ptr %var_0 + store i64 %var_9, ptr %var_7 + %var_11 = load i64, ptr %var_7 + call void @__quantum__rt__int_record_output(i64 %var_11, ptr @0) ret i64 0 block_4: - %var_10 = load i64, ptr %var_3 - %var_4 = icmp slt i64 %var_10, 3 + %var_13 = load i64, ptr %var_3 + %var_4 = icmp slt i64 %var_13, 3 br i1 %var_4, label %block_5, label %block_6 block_5: - %var_11 = load i64, ptr %var_0 - %var_5 = add i64 %var_11, 1 + %var_14 = load i64, ptr %var_0 + %var_5 = add i64 %var_14, 1 store i64 %var_5, ptr %var_0 - %var_13 = load i64, ptr %var_3 - %var_6 = add i64 %var_13, 1 + %var_16 = load i64, ptr %var_3 + %var_6 = add i64 %var_16, 1 store i64 %var_6, ptr %var_3 br label %block_4 block_6: @@ -1196,34 +1208,37 @@ fn nested_emit_while_loops_succeeds() { block_0: %var_1 = alloca i64 %var_4 = alloca i64 + %var_8 = alloca i64 call void @__quantum__rt__initialize(ptr null) store i64 0, ptr %var_1 br label %block_1 block_1: - %var_9 = load i64, ptr %var_1 - %var_2 = icmp slt i64 %var_9, 3 + %var_10 = load i64, ptr %var_1 + %var_2 = icmp slt i64 %var_10, 3 br i1 %var_2, label %block_2, label %block_3 block_2: call void @H(ptr inttoptr (i64 0 to ptr)) store i64 0, ptr %var_4 br label %block_4 block_3: - %var_10 = load i64, ptr %var_1 - call void @__quantum__rt__int_record_output(i64 %var_10, ptr @0) + %var_11 = load i64, ptr %var_1 + store i64 %var_11, ptr %var_8 + %var_13 = load i64, ptr %var_8 + call void @__quantum__rt__int_record_output(i64 %var_13, ptr @0) ret i64 0 block_4: - %var_12 = load i64, ptr %var_4 - %var_5 = icmp slt i64 %var_12, 2 + %var_15 = load i64, ptr %var_4 + %var_5 = icmp slt i64 %var_15, 2 br i1 %var_5, label %block_5, label %block_6 block_5: call void @H(ptr inttoptr (i64 1 to ptr)) - %var_15 = load i64, ptr %var_4 - %var_6 = add i64 %var_15, 1 + %var_18 = load i64, ptr %var_4 + %var_6 = add i64 %var_18, 1 store i64 %var_6, ptr %var_4 br label %block_4 block_6: - %var_13 = load i64, ptr %var_1 - %var_7 = add i64 %var_13, 1 + %var_16 = load i64, ptr %var_1 + %var_7 = add i64 %var_16, 1 store i64 %var_7, ptr %var_1 br label %block_1 } @@ -1289,37 +1304,40 @@ fn for_loop_over_qubits_with_dynamic_exit_succeeds() { %var_0 = alloca i1 %var_2 = alloca i1 %var_3 = alloca i64 + %var_11 = alloca i1 call void @__quantum__rt__initialize(ptr null) store i1 false, ptr %var_0 store i1 false, ptr %var_2 store i64 0, ptr %var_3 br label %block_1 block_1: - %var_14 = load i64, ptr %var_3 - %var_4 = icmp slt i64 %var_14, 3 + %var_15 = load i64, ptr %var_3 + %var_4 = icmp slt i64 %var_15, 3 br i1 %var_4, label %block_2, label %block_3 block_2: - %var_16 = load i64, ptr %var_3 - %var_17_offset_chk = icmp slt i64 %var_16, 0 - %var_17_offset = select i1 %var_17_offset_chk, i64 1, i64 0 - %var_17 = getelementptr [3 x ptr], ptr @array0, i64 %var_17_offset, i64 %var_16 - %var_5 = load ptr, ptr %var_17 + %var_19 = load i64, ptr %var_3 + %var_20_offset_chk = icmp slt i64 %var_19, 0 + %var_20_offset = select i1 %var_20_offset_chk, i64 1, i64 0 + %var_20 = getelementptr [3 x ptr], ptr @array0, i64 %var_20_offset, i64 %var_19 + %var_5 = load ptr, ptr %var_20 call void @H(ptr %var_5) call void @__quantum__qis__mresetz__body(ptr %var_5, ptr inttoptr (i64 0 to ptr)) %var_8 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) store i1 %var_8, ptr %var_0 - %var_19 = load i1, ptr %var_0 - br i1 %var_19, label %block_4, label %block_5 + %var_22 = load i1, ptr %var_0 + br i1 %var_22, label %block_4, label %block_5 block_3: - %var_15 = load i1, ptr %var_2 - call void @__quantum__rt__bool_record_output(i1 %var_15, ptr @0) + %var_16 = load i1, ptr %var_2 + store i1 %var_16, ptr %var_11 + %var_18 = load i1, ptr %var_11 + call void @__quantum__rt__bool_record_output(i1 %var_18, ptr @0) ret i64 0 block_4: store i1 true, ptr %var_2 br label %block_5 block_5: - %var_20 = load i64, ptr %var_3 - %var_10 = add i64 %var_20, 1 + %var_23 = load i64, ptr %var_3 + %var_10 = add i64 %var_23, 1 store i64 %var_10, ptr %var_3 br label %block_1 } @@ -2635,6 +2653,7 @@ fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable define i64 @ENTRYPOINT__main() #0 { block_0: %var_0 = alloca i64 + %var_4 = alloca i64 call void @__quantum__rt__initialize(ptr null) store i64 0, ptr %var_0 call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) @@ -2644,9 +2663,11 @@ fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable store i64 2, ptr %var_0 br label %block_2 block_2: + %var_7 = load i64, ptr %var_0 + store i64 %var_7, ptr %var_4 store i64 7, ptr %var_0 - %var_8 = load i64, ptr %var_0 - %var_5 = add i64 %var_8, 7 + %var_10 = load i64, ptr %var_4 + %var_5 = add i64 %var_10, 7 call void @__quantum__rt__int_record_output(i64 %var_5, ptr @0) ret i64 0 } diff --git a/source/compiler/qsc_openqasm_compiler/src/tests/statement/gate_call.rs b/source/compiler/qsc_openqasm_compiler/src/tests/statement/gate_call.rs index f8bb446c465..027328eff08 100644 --- a/source/compiler/qsc_openqasm_compiler/src/tests/statement/gate_call.rs +++ b/source/compiler/qsc_openqasm_compiler/src/tests/statement/gate_call.rs @@ -557,8 +557,10 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( %var_24 = alloca i1 %var_26 = alloca i1 %var_29 = alloca i64 + %var_31 = alloca i64 %var_37 = alloca i64 %var_39 = alloca i64 + %var_40 = alloca i64 %var_9 = icmp sgt i64 %var_8, 53 br i1 %var_9, label %block_4, label %block_5 block_4: @@ -572,49 +574,51 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( store i64 %var_19, ptr %var_20 %var_21 = ashr i64 %var_7, %var_11 store i64 %var_21, ptr %var_22 - %var_65 = load i64, ptr %var_20 - %var_66 = load i64, ptr %var_15 - %var_23 = icmp sgt i64 %var_65, %var_66 + %var_69 = load i64, ptr %var_20 + %var_70 = load i64, ptr %var_15 + %var_23 = icmp sgt i64 %var_69, %var_70 store i1 true, ptr %var_24 br i1 %var_23, label %block_9, label %block_6 block_5: store i64 %var_7, ptr %var_10 br label %block_13 block_6: - %var_68 = load i64, ptr %var_20 - %var_69 = load i64, ptr %var_15 - %var_25 = icmp eq i64 %var_68, %var_69 + %var_72 = load i64, ptr %var_20 + %var_73 = load i64, ptr %var_15 + %var_25 = icmp eq i64 %var_72, %var_73 store i1 false, ptr %var_26 br i1 %var_25, label %block_7, label %block_8 block_7: - %var_80 = load i64, ptr %var_22 - %var_27 = and i64 %var_80, 1 + %var_84 = load i64, ptr %var_22 + %var_27 = and i64 %var_84, 1 %var_28 = icmp eq i64 %var_27, 1 store i1 %var_28, ptr %var_26 br label %block_8 block_8: - %var_71 = load i1, ptr %var_26 - store i1 %var_71, ptr %var_24 + %var_75 = load i1, ptr %var_26 + store i1 %var_75, ptr %var_24 br label %block_9 block_9: - %var_73 = load i1, ptr %var_24 - br i1 %var_73, label %block_10, label %block_11 + %var_77 = load i1, ptr %var_24 + br i1 %var_77, label %block_10, label %block_11 block_10: - %var_78 = load i64, ptr %var_22 - %var_30 = add i64 %var_78, 1 + %var_82 = load i64, ptr %var_22 + %var_30 = add i64 %var_82, 1 store i64 %var_30, ptr %var_29 br label %block_12 block_11: - %var_74 = load i64, ptr %var_22 - store i64 %var_74, ptr %var_29 + %var_78 = load i64, ptr %var_22 + store i64 %var_78, ptr %var_29 br label %block_12 block_12: - %var_76 = load i64, ptr %var_29 - store i64 %var_76, ptr %var_10 + %var_80 = load i64, ptr %var_29 + store i64 %var_80, ptr %var_10 br label %block_13 block_13: %var_56 = load i64, ptr %var_10 - %var_36 = call i64 @MinI(i64 %var_56, i64 9007199254740991) + store i64 %var_56, ptr %var_31 + %var_58 = load i64, ptr %var_31 + %var_36 = call i64 @MinI(i64 %var_58, i64 9007199254740991) store i64 %var_36, ptr %var_37 %var_38 = icmp sgt i64 %var_8, 53 br i1 %var_38, label %block_14, label %block_15 @@ -625,11 +629,13 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( store i64 %var_8, ptr %var_39 br label %block_16 block_16: - %var_59 = load i64, ptr %var_39 - %var_41 = shl i64 1, %var_59 + %var_61 = load i64, ptr %var_39 + store i64 %var_61, ptr %var_40 + %var_63 = load i64, ptr %var_40 + %var_41 = shl i64 1, %var_63 %var_42 = sitofp i64 %var_41 to double - %var_60 = load i64, ptr %var_37 - %var_44 = sitofp i64 %var_60 to double + %var_64 = load i64, ptr %var_37 + %var_44 = sitofp i64 %var_64 to double %var_46 = fdiv double 6.283185307179586, %var_42 %var_48 = fmul double %var_44, %var_46 %var_53 = call double @MinD(double %var_48, double 6.283185307179585) @@ -648,8 +654,8 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( store i64 %var_33, ptr %var_35 br label %block_20 block_20: - %var_83 = load i64, ptr %var_35 - ret i64 %var_83 + %var_87 = load i64, ptr %var_35 + ret i64 %var_87 } define internal double @MinD(double %var_49, double %var_50) { @@ -664,8 +670,8 @@ fn custom_gate_with_angle_parameter_generates_qir_adaptive() -> miette::Result<( store double %var_50, ptr %var_52 br label %block_24 block_24: - %var_86 = load double, ptr %var_52 - ret double %var_86 + %var_90 = load double, ptr %var_52 + ret double %var_90 } define internal void @Rz(double %var_56, ptr %var_57) { diff --git a/source/compiler/qsc_rir/src/passes/ssa_transform.rs b/source/compiler/qsc_rir/src/passes/ssa_transform.rs index 8cc710b71d0..0f34531f39c 100644 --- a/source/compiler/qsc_rir/src/passes/ssa_transform.rs +++ b/source/compiler/qsc_rir/src/passes/ssa_transform.rs @@ -4,6 +4,8 @@ #[cfg(test)] mod tests; +use std::collections::hash_map::Entry::{Occupied, Vacant}; + use crate::{ rir::{BlockId, Instruction, Operand, OperandMapping, Program, Ty, Variable, VariableId}, utils::{get_all_block_successors, get_variable_assignments, map_variable_use_in_block}, @@ -100,8 +102,9 @@ fn transform_body_to_ssa( // predecessor has a different value for the variable, a phi node is needed. let first_pred_map = block_var_map .get(*first_pred) - .expect("block should have variable map"); - 'var_loop: for (var_id, mapping) in first_pred_map { + .expect("block should have variable map") + .clone(); + 'var_loop: for (var_id, mapping) in &first_pred_map { let operand = Into::::into(mapping); let mut phi_nodes = FxHashMap::default(); @@ -115,7 +118,7 @@ fn transform_body_to_ssa( }) { // Some predecessors have different values for this variable, so a phi node is needed. // Start with the first predecessor's value and block id, then add the values from the other predecessors. - let mut phi_args = vec![(operand.mapped(first_pred_map), *first_pred)]; + let mut phi_args = vec![(operand.mapped(&first_pred_map), *first_pred)]; for pred in rest_preds { let pred_var_map = block_var_map .get(*pred) @@ -156,6 +159,17 @@ fn transform_body_to_ssa( OperandMapping::Deep(Operand::Variable(new_var)), ); *next_var_id = next_var_id.successor(); + + let var_map = block_var_map + .get_mut(block_id) + .expect("block should have variable map"); + for mapping in var_map.values_mut() { + if let OperandMapping::Shallow(Operand::Variable(var)) = mapping + && var.variable_id == variable_id + { + *mapping = OperandMapping::Deep(Operand::Variable(new_var)); + } + } } } } @@ -163,11 +177,19 @@ fn transform_body_to_ssa( // Now that the block has finished processing, apply any updates to the block and // merge those updates into the stored variable map to propagate to successors. map_variable_use_in_block(block, &mut var_map_updates, &FxHashSet::default()); + let var_map = block_var_map + .get_mut(block_id) + .expect("block should have variable map"); for (var_id, mapping) in var_map_updates { - let var_map = block_var_map - .get_mut(block_id) - .expect("block should have variable map"); - var_map.entry(var_id).or_insert(mapping); + match var_map.entry(var_id) { + Vacant(entry) => { + entry.insert(mapping); + } + Occupied(mut entry) if entry.get().is_shallow() => { + entry.insert(mapping); + } + Occupied(_) => (), + } } } } diff --git a/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs b/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs index 11c62c5ad8c..ebe54d37b4e 100644 --- a/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs +++ b/source/compiler/qsc_rir/src/passes/ssa_transform/tests.rs @@ -2929,6 +2929,623 @@ fn ssa_transform_allows_point_in_time_copy_of_variable_mutated_across_blocks() { "#]].assert_eq(&program.to_string()); } +#[test] +fn ssa_transform_allows_point_in_time_copy_of_variable_mutated_across_blocks_using_load_and_store() +{ + let mut program = new_program(); + program.callables.insert( + CallableId(1), + Callable { + name: "dynamic_bool".to_string(), + input_type: Vec::new(), + output_type: Some(Ty::Prim(Prim::Boolean)), + body: None, + input_vars: Vec::new(), + call_type: CallableType::Regular, + }, + ); + + program.blocks.insert( + BlockId(0), + Block(vec![ + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(0)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(1), + BlockId(2), + None, + ), + ]), + ); + program.blocks.insert( + BlockId(1), + Block(vec![ + Instruction::Store( + Operand::Literal(Literal::Integer(1)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Jump(BlockId(2)), + ]), + ); + program.blocks.insert( + BlockId(2), + Block(vec![ + Instruction::Store( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(2)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Add( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Operand::Variable(Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Return(None), + ]), + ); + + // Before + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(2, Integer) = Store Variable(1, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Integer) = Add Variable(1, Integer), Variable(2, Integer) + Return + config: Config: + capabilities: Base + num_qubits: 0 + num_results: 0 + tags: + "#]] + .assert_eq(&program.to_string()); + + // After + program.config.capabilities = Profile::Adaptive.into(); + check_and_transform(&mut program); + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(1, Integer) = Alloca + Variable(2, Integer) = Alloca + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(5, Integer) = Load Variable(1, Integer) + Variable(2, Integer) = Store Variable(5, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(8, Integer) = Load Variable(1, Integer) + Variable(9, Integer) = Load Variable(2, Integer) + Variable(3, Integer) = Add Variable(8, Integer), Variable(9, Integer) + Return + config: Config: + capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | StaticSizedArrays | CallSupport) + num_qubits: 0 + num_results: 0 + tags: + "#]].assert_eq(&program.to_string()); +} + +#[test] +fn ssa_transform_allows_point_in_time_copy_of_variable_mutated_across_blocks_accessed_in_successor_block() + { + let mut program = new_program(); + program.callables.insert( + CallableId(1), + Callable { + name: "dynamic_bool".to_string(), + input_type: Vec::new(), + output_type: Some(Ty::Prim(Prim::Boolean)), + body: None, + input_vars: Vec::new(), + call_type: CallableType::Regular, + }, + ); + + program.blocks.insert( + BlockId(0), + Block(vec![ + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(0)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(1), + BlockId(2), + None, + ), + ]), + ); + program.blocks.insert( + BlockId(1), + Block(vec![ + Instruction::Store( + Operand::Literal(Literal::Integer(1)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Jump(BlockId(2)), + ]), + ); + program.blocks.insert( + BlockId(2), + Block(vec![ + Instruction::Store( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(2)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(3), + BlockId(4), + None, + ), + ]), + ); + program + .blocks + .insert(BlockId(3), Block(vec![Instruction::Jump(BlockId(5))])); + program + .blocks + .insert(BlockId(4), Block(vec![Instruction::Jump(BlockId(5))])); + program.blocks.insert( + BlockId(5), + Block(vec![ + Instruction::Add( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Operand::Variable(Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(4), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Return(None), + ]), + ); + + // Before + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(2, Integer) = Store Variable(1, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Boolean) = Call id(1), args( ) + Branch Variable(3, Boolean), 3, 4 + Block 3: Block: + Jump(5) + Block 4: Block: + Jump(5) + Block 5: Block: + Variable(4, Integer) = Add Variable(1, Integer), Variable(2, Integer) + Return + config: Config: + capabilities: Base + num_qubits: 0 + num_results: 0 + tags: + "#]] + .assert_eq(&program.to_string()); + + // After + transform_program(&mut program); + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Jump(2) + Block 2: Block: + Variable(5, Integer) = Phi ( [Integer(0), 0], [Integer(1), 1], ) + Variable(3, Boolean) = Call id(1), args( ) + Branch Variable(3, Boolean), 3, 4 + Block 3: Block: + Jump(5) + Block 4: Block: + Jump(5) + Block 5: Block: + Variable(4, Integer) = Add Integer(2), Variable(5, Integer) + Return + config: Config: + capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations) + num_qubits: 0 + num_results: 0 + tags: + "#]].assert_eq(&program.to_string()); +} + +#[test] +fn ssa_transform_allows_point_in_time_copy_of_variable_mutated_across_blocks_accessed_in_successor_block_using_load_and_store() + { + let mut program = new_program(); + program.callables.insert( + CallableId(1), + Callable { + name: "dynamic_bool".to_string(), + input_type: Vec::new(), + output_type: Some(Ty::Prim(Prim::Boolean)), + body: None, + input_vars: Vec::new(), + call_type: CallableType::Regular, + }, + ); + + program.blocks.insert( + BlockId(0), + Block(vec![ + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(0)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(0), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(1), + BlockId(2), + None, + ), + ]), + ); + program.blocks.insert( + BlockId(1), + Block(vec![ + Instruction::Store( + Operand::Literal(Literal::Integer(1)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Jump(BlockId(2)), + ]), + ); + program.blocks.insert( + BlockId(2), + Block(vec![ + Instruction::Store( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Store( + Operand::Literal(Literal::Integer(2)), + Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Call( + CallableId(1), + Vec::new(), + Some(Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Boolean), + }), + None, + ), + Instruction::Branch( + Variable { + variable_id: VariableId(3), + ty: Ty::Prim(Prim::Boolean), + }, + BlockId(3), + BlockId(4), + None, + ), + ]), + ); + program + .blocks + .insert(BlockId(3), Block(vec![Instruction::Jump(BlockId(5))])); + program + .blocks + .insert(BlockId(4), Block(vec![Instruction::Jump(BlockId(5))])); + program.blocks.insert( + BlockId(5), + Block(vec![ + Instruction::Add( + Operand::Variable(Variable { + variable_id: VariableId(1), + ty: Ty::Prim(Prim::Integer), + }), + Operand::Variable(Variable { + variable_id: VariableId(2), + ty: Ty::Prim(Prim::Integer), + }), + Variable { + variable_id: VariableId(4), + ty: Ty::Prim(Prim::Integer), + }, + ), + Instruction::Return(None), + ]), + ); + + // Before + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(2, Integer) = Store Variable(1, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Boolean) = Call id(1), args( ) + Branch Variable(3, Boolean), 3, 4 + Block 3: Block: + Jump(5) + Block 4: Block: + Jump(5) + Block 5: Block: + Variable(4, Integer) = Add Variable(1, Integer), Variable(2, Integer) + Return + config: Config: + capabilities: Base + num_qubits: 0 + num_results: 0 + tags: + "#]] + .assert_eq(&program.to_string()); + + // After + program.config.capabilities = Profile::Adaptive.into(); + check_and_transform(&mut program); + expect![[r#" + Program: + entry: 0 + callables: + Callable 0: Callable: + name: main + call_type: Regular + input_type: + output_type: Integer + body: 0 + Callable 1: Callable: + name: dynamic_bool + call_type: Regular + input_type: + output_type: Boolean + body: + blocks: + Block 0: Block: + Variable(1, Integer) = Alloca + Variable(2, Integer) = Alloca + Variable(0, Boolean) = Call id(1), args( ) + Variable(1, Integer) = Store Integer(0) + Branch Variable(0, Boolean), 1, 2 + Block 1: Block: + Variable(1, Integer) = Store Integer(1) + Jump(2) + Block 2: Block: + Variable(6, Integer) = Load Variable(1, Integer) + Variable(2, Integer) = Store Variable(6, Integer) + Variable(1, Integer) = Store Integer(2) + Variable(3, Boolean) = Call id(1), args( ) + Branch Variable(3, Boolean), 3, 4 + Block 3: Block: + Jump(5) + Block 4: Block: + Jump(5) + Block 5: Block: + Variable(9, Integer) = Load Variable(1, Integer) + Variable(10, Integer) = Load Variable(2, Integer) + Variable(4, Integer) = Add Variable(9, Integer), Variable(10, Integer) + Return + config: Config: + capabilities: TargetCapabilityFlags(Adaptive | IntegerComputations | FloatingPointComputations | BackwardsBranching | StaticSizedArrays | CallSupport) + num_qubits: 0 + num_results: 0 + tags: + "#]].assert_eq(&program.to_string()); +} + #[test] fn ssa_transform_propagates_phi_var_to_successor_blocks_across_sequential_branches() { let mut program = new_program(); diff --git a/source/compiler/qsc_rir/src/utils.rs b/source/compiler/qsc_rir/src/utils.rs index e58a9e4d4a8..2afbbc01282 100644 --- a/source/compiler/qsc_rir/src/utils.rs +++ b/source/compiler/qsc_rir/src/utils.rs @@ -154,6 +154,10 @@ pub(crate) fn map_variable_use_in_block( if var_stor_to_keep.contains(&var.variable_id) { // Only keep stores to variables that are in the set to keep. *operand = operand.mapped(var_map); + } else if let Operand::Variable(mapped_var) = operand.mapped(var_map) + && var_stor_to_keep.contains(&mapped_var.variable_id) + { + *operand = Operand::Variable(mapped_var); } else { update_variable_mapping(var_map, operand, var); continue; @@ -162,10 +166,7 @@ pub(crate) fn map_variable_use_in_block( 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::>(); + *operand = operand.iter().map(|op| op.mapped(var_map)).collect(); } else { // Otherwise drop the store array by continuing the loop. continue; @@ -261,11 +262,8 @@ pub(crate) fn map_variable_use_in_block( // like the unconditional terminators. Instruction::Phi(..) | Instruction::Jump(..) | Instruction::Return(None) => {} - Instruction::Alloca(..) => { - panic!("alloca not supported in ssa transformation") - } - Instruction::Load(..) => { - panic!("load not supported in ssa transformation") + Instruction::Alloca(..) | Instruction::Load(..) => { + panic!("alloca/load not supported in ssa transformation") } } block.0.push(instr); diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/ArithmeticOps.ll b/source/qdk_package/tests-integration/resources/adaptive/output/ArithmeticOps.ll index b6a296303e5..ad90252de09 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/ArithmeticOps.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/ArithmeticOps.ll @@ -15,6 +15,10 @@ block_0: %var_6 = alloca i64 %var_13 = alloca i64 %var_24 = alloca i64 + %var_30 = alloca i64 + %var_31 = alloca i64 + %var_32 = alloca i64 + %var_33 = alloca i64 call void @__quantum__rt__initialize(ptr null) store i64 0, ptr %var_1 store i64 0, ptr %var_2 @@ -23,17 +27,17 @@ block_0: store i64 0, ptr %var_6 br label %block_1 block_1: - %var_35 = load i64, ptr %var_6 - %var_7 = icmp slt i64 %var_35, 5 + %var_39 = load i64, ptr %var_6 + %var_7 = icmp slt i64 %var_39, 5 br i1 %var_7, label %block_2, label %block_3 block_2: - %var_59 = load i64, ptr %var_6 - %var_60_offset_chk = icmp slt i64 %var_59, 0 - %var_60_offset = select i1 %var_60_offset_chk, i64 1, i64 0 - %var_60 = getelementptr [5 x ptr], ptr @array0, i64 %var_60_offset, i64 %var_59 - %var_8 = load ptr, ptr %var_60 + %var_71 = load i64, ptr %var_6 + %var_72_offset_chk = icmp slt i64 %var_71, 0 + %var_72_offset = select i1 %var_72_offset_chk, i64 1, i64 0 + %var_72 = getelementptr [5 x ptr], ptr @array0, i64 %var_72_offset, i64 %var_71 + %var_8 = load ptr, ptr %var_72 call void @X(ptr %var_8) - %var_11 = add i64 %var_59, 1 + %var_11 = add i64 %var_71, 1 store i64 %var_11, ptr %var_6 br label %block_1 block_3: @@ -45,63 +49,71 @@ block_3: store i64 0, ptr %var_13 br label %block_4 block_4: - %var_37 = load i64, ptr %var_13 - %var_14 = icmp slt i64 %var_37, 5 + %var_41 = load i64, ptr %var_13 + %var_14 = icmp slt i64 %var_41, 5 br i1 %var_14, label %block_5, label %block_6 block_5: - %var_47 = load i64, ptr %var_13 - %var_48_offset_chk = icmp slt i64 %var_47, 0 - %var_48_offset = select i1 %var_48_offset_chk, i64 1, i64 0 - %var_48 = getelementptr [5 x ptr], ptr @array1, i64 %var_48_offset, i64 %var_47 - %var_15 = load ptr, ptr %var_48 + %var_59 = load i64, ptr %var_13 + %var_60_offset_chk = icmp slt i64 %var_59, 0 + %var_60_offset = select i1 %var_60_offset_chk, i64 1, i64 0 + %var_60 = getelementptr [5 x ptr], ptr @array1, i64 %var_60_offset, i64 %var_59 + %var_15 = load ptr, ptr %var_60 %var_17 = call i1 @__quantum__rt__read_result(ptr %var_15) br i1 %var_17, label %block_7, label %block_9 block_6: store i64 0, ptr %var_24 br label %block_8 block_7: - %var_51 = load i64, ptr %var_1 - %var_19 = add i64 %var_51, 1 + %var_63 = load i64, ptr %var_1 + %var_19 = add i64 %var_63, 1 store i64 %var_19, ptr %var_1 - %var_53 = load i64, ptr %var_2 - %var_20 = add i64 %var_53, 5 + %var_65 = load i64, ptr %var_2 + %var_20 = add i64 %var_65, 5 store i64 %var_20, ptr %var_2 - %var_55 = load i64, ptr %var_3 - %var_21 = sub i64 %var_55, 2 + %var_67 = load i64, ptr %var_3 + %var_21 = sub i64 %var_67, 2 store i64 %var_21, ptr %var_3 - %var_57 = load i64, ptr %var_4 - %var_22 = mul i64 %var_57, 3 + %var_69 = load i64, ptr %var_4 + %var_22 = mul i64 %var_69, 3 store i64 %var_22, ptr %var_4 br label %block_9 block_8: - %var_39 = load i64, ptr %var_24 - %var_25 = icmp slt i64 %var_39, 5 + %var_43 = load i64, ptr %var_24 + %var_25 = icmp slt i64 %var_43, 5 br i1 %var_25, label %block_10, label %block_11 block_9: - %var_49 = load i64, ptr %var_13 - %var_23 = add i64 %var_49, 1 + %var_61 = load i64, ptr %var_13 + %var_23 = add i64 %var_61, 1 store i64 %var_23, ptr %var_13 br label %block_4 block_10: - %var_44 = load i64, ptr %var_24 - %var_45_offset_chk = icmp slt i64 %var_44, 0 - %var_45_offset = select i1 %var_45_offset_chk, i64 1, i64 0 - %var_45 = getelementptr [5 x ptr], ptr @array0, i64 %var_45_offset, i64 %var_44 - %var_26 = load ptr, ptr %var_45 + %var_56 = load i64, ptr %var_24 + %var_57_offset_chk = icmp slt i64 %var_56, 0 + %var_57_offset = select i1 %var_57_offset_chk, i64 1, i64 0 + %var_57 = getelementptr [5 x ptr], ptr @array0, i64 %var_57_offset, i64 %var_56 + %var_26 = load ptr, ptr %var_57 call void @Reset(ptr %var_26) - %var_29 = add i64 %var_44, 1 + %var_29 = add i64 %var_56, 1 store i64 %var_29, ptr %var_24 br label %block_8 block_11: + %var_44 = load i64, ptr %var_1 + store i64 %var_44, ptr %var_30 + %var_46 = load i64, ptr %var_2 + store i64 %var_46, ptr %var_31 + %var_48 = load i64, ptr %var_3 + store i64 %var_48, ptr %var_32 + %var_50 = load i64, ptr %var_4 + store i64 %var_50, ptr %var_33 call void @__quantum__rt__tuple_record_output(i64 4, ptr @0) - %var_40 = load i64, ptr %var_1 - call void @__quantum__rt__int_record_output(i64 %var_40, ptr @1) - %var_41 = load i64, ptr %var_2 - call void @__quantum__rt__int_record_output(i64 %var_41, ptr @2) - %var_42 = load i64, ptr %var_3 - call void @__quantum__rt__int_record_output(i64 %var_42, ptr @3) - %var_43 = load i64, ptr %var_4 - call void @__quantum__rt__int_record_output(i64 %var_43, ptr @4) + %var_52 = load i64, ptr %var_30 + call void @__quantum__rt__int_record_output(i64 %var_52, ptr @1) + %var_53 = load i64, ptr %var_31 + call void @__quantum__rt__int_record_output(i64 %var_53, ptr @2) + %var_54 = load i64, ptr %var_32 + call void @__quantum__rt__int_record_output(i64 %var_54, ptr @3) + %var_55 = load i64, ptr %var_33 + call void @__quantum__rt__int_record_output(i64 %var_55, ptr @4) ret i64 0 } diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/CallReturnMath.ll b/source/qdk_package/tests-integration/resources/adaptive/output/CallReturnMath.ll index 93be3cc176b..4cfd403c8a7 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/CallReturnMath.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/CallReturnMath.ll @@ -14,6 +14,7 @@ define internal i64 @A(ptr %var_2, ptr %var_3) { block_1: %var_10 = alloca i64 %var_13 = alloca i64 + %var_14 = alloca i64 %var_9 = call i64 @B(ptr %var_3) store i64 %var_9, ptr %var_10 call void @X(ptr %var_2) @@ -28,8 +29,10 @@ block_3: br label %block_4 block_4: %var_20 = load i64, ptr %var_13 - %var_21 = load i64, ptr %var_10 - %var_15 = mul i64 %var_20, %var_21 + store i64 %var_20, ptr %var_14 + %var_22 = load i64, ptr %var_14 + %var_23 = load i64, ptr %var_10 + %var_15 = mul i64 %var_22, %var_23 %var_16 = add i64 %var_15, 1 ret i64 %var_16 } @@ -48,8 +51,8 @@ block_7: store i64 3, ptr %var_8 br label %block_8 block_8: - %var_24 = load i64, ptr %var_8 - ret i64 %var_24 + %var_26 = load i64, ptr %var_8 + ret i64 %var_26 } define internal void @X(ptr %var_5) { diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/Doubles.ll b/source/qdk_package/tests-integration/resources/adaptive/output/Doubles.ll index d1d37040f35..029af883852 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/Doubles.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/Doubles.ll @@ -14,19 +14,20 @@ block_0: %var_2 = alloca double %var_3 = alloca i64 %var_5 = alloca i1 + %var_25 = alloca double call void @__quantum__rt__initialize(ptr null) store i1 false, ptr %var_0 store double 0.0, ptr %var_2 store i64 1, ptr %var_3 br label %block_1 block_1: - %var_28 = load i64, ptr %var_3 - %var_4 = icmp sle i64 %var_28, 10 + %var_29 = load i64, ptr %var_3 + %var_4 = icmp sle i64 %var_29, 10 store i1 true, ptr %var_5 br i1 %var_4, label %block_2, label %block_3 block_2: - %var_31 = load i1, ptr %var_5 - br i1 %var_31, label %block_4, label %block_5 + %var_32 = load i1, ptr %var_5 + br i1 %var_32, label %block_4, label %block_5 block_3: store i1 false, ptr %var_5 br label %block_2 @@ -35,20 +36,22 @@ block_4: call void @__quantum__qis__m__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) %var_7 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) store i1 %var_7, ptr %var_0 - %var_34 = load i1, ptr %var_0 - br i1 %var_34, label %block_6, label %block_7 + %var_37 = load i1, ptr %var_0 + br i1 %var_37, label %block_6, label %block_7 block_5: call void @Reset(ptr inttoptr (i64 0 to ptr)) - %var_32 = load double, ptr %var_2 - %var_16 = fptosi double %var_32 to i64 + %var_33 = load double, ptr %var_2 + %var_16 = fptosi double %var_33 to i64 %var_18 = sitofp i64 %var_16 to double - %var_20 = fcmp ogt double %var_32, 5.0 - %var_21 = fcmp olt double %var_32, 5.0 - %var_22 = fcmp oge double %var_32, 10.0 - %var_23 = fcmp oeq double %var_32, 10.0 - %var_24 = fcmp one double %var_32, 10.0 + %var_20 = fcmp ogt double %var_33, 5.0 + %var_21 = fcmp olt double %var_33, 5.0 + %var_22 = fcmp oge double %var_33, 10.0 + %var_23 = fcmp oeq double %var_33, 10.0 + %var_24 = fcmp one double %var_33, 10.0 + store double %var_33, ptr %var_25 call void @__quantum__rt__tuple_record_output(i64 8, ptr @0) - call void @__quantum__rt__double_record_output(double %var_32, ptr @1) + %var_35 = load double, ptr %var_25 + call void @__quantum__rt__double_record_output(double %var_35, ptr @1) call void @__quantum__rt__bool_record_output(i1 %var_20, ptr @2) call void @__quantum__rt__bool_record_output(i1 %var_21, ptr @3) call void @__quantum__rt__bool_record_output(i1 %var_22, ptr @4) @@ -59,25 +62,25 @@ block_5: ret i64 0 block_6: call void @X(ptr inttoptr (i64 0 to ptr)) - %var_37 = load double, ptr %var_2 - %var_9 = fadd double %var_37, 1.0 + %var_40 = load double, ptr %var_2 + %var_9 = fadd double %var_40, 1.0 store double %var_9, ptr %var_2 - %var_39 = load double, ptr %var_2 - %var_10 = fmul double %var_39, 1.0 + %var_42 = load double, ptr %var_2 + %var_10 = fmul double %var_42, 1.0 store double %var_10, ptr %var_2 - %var_41 = load double, ptr %var_2 - %var_11 = fsub double %var_41, 1.0 + %var_44 = load double, ptr %var_2 + %var_11 = fsub double %var_44, 1.0 store double %var_11, ptr %var_2 - %var_43 = load double, ptr %var_2 - %var_12 = fdiv double %var_43, 1.0 + %var_46 = load double, ptr %var_2 + %var_12 = fdiv double %var_46, 1.0 store double %var_12, ptr %var_2 - %var_45 = load double, ptr %var_2 - %var_13 = fadd double %var_45, 1.0 + %var_48 = load double, ptr %var_2 + %var_13 = fadd double %var_48, 1.0 store double %var_13, ptr %var_2 br label %block_7 block_7: - %var_35 = load i64, ptr %var_3 - %var_14 = add i64 %var_35, 1 + %var_38 = load i64, ptr %var_3 + %var_14 = add i64 %var_38, 1 store i64 %var_14, ptr %var_3 br label %block_1 } diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/MeasurementComparison.ll b/source/qdk_package/tests-integration/resources/adaptive/output/MeasurementComparison.ll index 74cdbcaf05a..bcc0776efb2 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/MeasurementComparison.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/MeasurementComparison.ll @@ -8,6 +8,10 @@ define i64 @ENTRYPOINT__main() #0 { block_0: %var_6 = alloca i1 %var_14 = alloca i1 + %var_15 = alloca i1 + %var_16 = alloca i1 + %var_17 = alloca i1 + %var_18 = alloca i1 call void @__quantum__rt__initialize(ptr null) call void @X(ptr inttoptr (i64 0 to ptr)) call void @CNOT(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 1 to ptr)) @@ -32,13 +36,21 @@ block_2: store i1 true, ptr %var_14 br label %block_3 block_3: + %var_21 = load i1, ptr %var_6 + store i1 %var_21, ptr %var_15 + store i1 %var_8, ptr %var_16 + store i1 %var_11, ptr %var_17 + %var_25 = load i1, ptr %var_14 + store i1 %var_25, ptr %var_18 call void @__quantum__rt__tuple_record_output(i64 4, ptr @0) - %var_17 = load i1, ptr %var_6 - call void @__quantum__rt__bool_record_output(i1 %var_17, ptr @1) - call void @__quantum__rt__bool_record_output(i1 %var_8, ptr @2) - call void @__quantum__rt__bool_record_output(i1 %var_11, ptr @3) - %var_18 = load i1, ptr %var_14 - call void @__quantum__rt__bool_record_output(i1 %var_18, ptr @4) + %var_27 = load i1, ptr %var_15 + call void @__quantum__rt__bool_record_output(i1 %var_27, ptr @1) + %var_28 = load i1, ptr %var_16 + call void @__quantum__rt__bool_record_output(i1 %var_28, ptr @2) + %var_29 = load i1, ptr %var_17 + call void @__quantum__rt__bool_record_output(i1 %var_29, ptr @3) + %var_30 = load i1, ptr %var_18 + call void @__quantum__rt__bool_record_output(i1 %var_30, ptr @4) ret i64 0 } diff --git a/source/qdk_package/tests-integration/resources/adaptive/output/ThreeQubitRepetitionCode.ll b/source/qdk_package/tests-integration/resources/adaptive/output/ThreeQubitRepetitionCode.ll index 7e75da3b036..72b9c7a1212 100644 --- a/source/qdk_package/tests-integration/resources/adaptive/output/ThreeQubitRepetitionCode.ll +++ b/source/qdk_package/tests-integration/resources/adaptive/output/ThreeQubitRepetitionCode.ll @@ -13,8 +13,11 @@ block_0: %var_12 = alloca i1 %var_13 = alloca i64 %var_23 = alloca i1 + %var_31 = alloca i1 %var_38 = alloca i1 %var_39 = alloca i64 + %var_45 = alloca i1 + %var_46 = alloca i64 call void @__quantum__rt__initialize(ptr null) call void @H(ptr inttoptr (i64 0 to ptr)) call void @Z(ptr inttoptr (i64 0 to ptr)) @@ -24,13 +27,13 @@ block_0: store i64 1, ptr %var_7 br label %block_1 block_1: - %var_47 = load i64, ptr %var_7 - %var_8 = icmp sle i64 %var_47, 5 + %var_49 = load i64, ptr %var_7 + %var_8 = icmp sle i64 %var_49, 5 store i1 true, ptr %var_9 br i1 %var_8, label %block_2, label %block_3 block_2: - %var_50 = load i1, ptr %var_9 - br i1 %var_50, label %block_4, label %block_5 + %var_52 = load i1, ptr %var_9 + br i1 %var_52, label %block_4, label %block_5 block_3: store i1 false, ptr %var_9 br label %block_2 @@ -47,36 +50,40 @@ block_5: store i64 0, ptr %var_39 br label %block_7 block_6: - %var_60 = load i64, ptr %var_10 - %var_11 = icmp sle i64 %var_60, 4 + %var_66 = load i64, ptr %var_10 + %var_11 = icmp sle i64 %var_66, 4 store i1 true, ptr %var_12 br i1 %var_11, label %block_8, label %block_9 block_7: - %var_53 = load i64, ptr %var_39 - %var_40 = icmp slt i64 %var_53, 2 + %var_55 = load i64, ptr %var_39 + %var_40 = icmp slt i64 %var_55, 2 br i1 %var_40, label %block_10, label %block_11 block_8: - %var_63 = load i1, ptr %var_12 - br i1 %var_63, label %block_12, label %block_13 + %var_69 = load i1, ptr %var_12 + br i1 %var_69, label %block_12, label %block_13 block_9: store i1 false, ptr %var_12 br label %block_8 block_10: - %var_56 = load i64, ptr %var_39 - %var_57_offset_chk = icmp slt i64 %var_56, 0 - %var_57_offset = select i1 %var_57_offset_chk, i64 1, i64 0 - %var_57 = getelementptr [2 x ptr], ptr @array1, i64 %var_57_offset, i64 %var_56 - %var_41 = load ptr, ptr %var_57 + %var_62 = load i64, ptr %var_39 + %var_63_offset_chk = icmp slt i64 %var_62, 0 + %var_63_offset = select i1 %var_63_offset_chk, i64 1, i64 0 + %var_63 = getelementptr [2 x ptr], ptr @array1, i64 %var_63_offset, i64 %var_62 + %var_41 = load ptr, ptr %var_63 call void @Reset(ptr %var_41) - %var_44 = add i64 %var_56, 1 + %var_44 = add i64 %var_62, 1 store i64 %var_44, ptr %var_39 br label %block_7 block_11: + %var_56 = load i1, ptr %var_38 + store i1 %var_56, ptr %var_45 + %var_58 = load i64, ptr %var_4 + store i64 %var_58, ptr %var_46 call void @__quantum__rt__tuple_record_output(i64 2, ptr @0) - %var_54 = load i1, ptr %var_38 - call void @__quantum__rt__bool_record_output(i1 %var_54, ptr @1) - %var_55 = load i64, ptr %var_4 - call void @__quantum__rt__int_record_output(i64 %var_55, ptr @2) + %var_60 = load i1, ptr %var_45 + call void @__quantum__rt__bool_record_output(i1 %var_60, ptr @1) + %var_61 = load i64, ptr %var_46 + call void @__quantum__rt__int_record_output(i64 %var_61, ptr @2) ret i64 0 block_12: store i64 0, ptr %var_13 @@ -92,8 +99,8 @@ block_13: %var_24 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) br i1 %var_24, label %block_15, label %block_16 block_14: - %var_72 = load i64, ptr %var_13 - %var_14 = icmp slt i64 %var_72, 3 + %var_80 = load i64, ptr %var_13 + %var_14 = icmp slt i64 %var_80, 3 br i1 %var_14, label %block_17, label %block_18 block_15: %var_26 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 1 to ptr)) @@ -102,18 +109,18 @@ block_16: %var_29 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 1 to ptr)) br i1 %var_29, label %block_21, label %block_22 block_17: - %var_75 = load i64, ptr %var_13 - %var_76_offset_chk = icmp slt i64 %var_75, 0 - %var_76_offset = select i1 %var_76_offset_chk, i64 1, i64 0 - %var_76 = getelementptr [3 x ptr], ptr @array0, i64 %var_76_offset, i64 %var_75 - %var_15 = load ptr, ptr %var_76 + %var_83 = load i64, ptr %var_13 + %var_84_offset_chk = icmp slt i64 %var_83, 0 + %var_84_offset = select i1 %var_84_offset_chk, i64 1, i64 0 + %var_84 = getelementptr [3 x ptr], ptr @array0, i64 %var_84_offset, i64 %var_83 + %var_15 = load ptr, ptr %var_84 call void @Rx(double 1.5707963267948966, ptr %var_15) - %var_19 = add i64 %var_75, 1 + %var_19 = add i64 %var_83, 1 store i64 %var_19, ptr %var_13 br label %block_14 block_18: - %var_73 = load i64, ptr %var_10 - %var_20 = add i64 %var_73, 1 + %var_81 = load i64, ptr %var_10 + %var_20 = add i64 %var_81, 1 store i64 %var_20, ptr %var_10 br label %block_6 block_19: @@ -133,16 +140,18 @@ block_23: block_24: br label %block_25 block_25: - %var_66 = load i1, ptr %var_23 - br i1 %var_66, label %block_26, label %block_27 + %var_72 = load i1, ptr %var_23 + store i1 %var_72, ptr %var_31 + %var_74 = load i1, ptr %var_31 + br i1 %var_74, label %block_26, label %block_27 block_26: - %var_69 = load i64, ptr %var_4 - %var_32 = add i64 %var_69, 1 + %var_77 = load i64, ptr %var_4 + %var_32 = add i64 %var_77, 1 store i64 %var_32, ptr %var_4 br label %block_27 block_27: - %var_67 = load i64, ptr %var_7 - %var_33 = add i64 %var_67, 1 + %var_75 = load i64, ptr %var_7 + %var_33 = add i64 %var_75, 1 store i64 %var_33, ptr %var_7 br label %block_1 } diff --git a/source/samples_test/src/tests/OpenQASM.rs b/source/samples_test/src/tests/OpenQASM.rs index 15526632c8c..d21b774fc41 100644 --- a/source/samples_test/src/tests/OpenQASM.rs +++ b/source/samples_test/src/tests/OpenQASM.rs @@ -32,7 +32,7 @@ pub const RANDOMNUMBER_EXPECT: Expect = expect!["9"]; pub const RANDOMNUMBER_EXPECT_DEBUG: Expect = expect!["9"]; pub const RANDOMNUMBER_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 3559"]; pub const RANDOMNUMBER_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 4064"]; -pub const RANDOMNUMBER_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 2911"]; +pub const RANDOMNUMBER_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 3001"]; pub const SIMPLE1DISINGORDER1_EXPECT: Expect = expect!["[Zero, One, One, Zero, Zero, One, One, One, One]"]; pub const SIMPLE1DISINGORDER1_EXPECT_DEBUG: Expect = @@ -41,9 +41,9 @@ pub const SIMPLE1DISINGORDER1_EXPECT_CIRCUIT: Expect = expect!["generated circui pub const SIMPLE1DISINGORDER1_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 18979"]; pub const SIMPLE1DISINGORDER1_EXPECT_QIR_ADAPTIVE: Expect = - expect!["generated QIR of length 12178"]; + expect!["generated QIR of length 12362"]; pub const TELEPORTATION_EXPECT: Expect = expect!["Zero"]; pub const TELEPORTATION_EXPECT_DEBUG: Expect = expect!["Zero"]; pub const TELEPORTATION_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 2086"]; pub const TELEPORTATION_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 3062"]; -pub const TELEPORTATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 7402"]; +pub const TELEPORTATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 7584"]; diff --git a/source/samples_test/src/tests/algorithms.rs b/source/samples_test/src/tests/algorithms.rs index e777b4834a9..9005ec5de9a 100644 --- a/source/samples_test/src/tests/algorithms.rs +++ b/source/samples_test/src/tests/algorithms.rs @@ -11,7 +11,7 @@ pub const BERNSTEINVAZIRANI_EXPECT_DEBUG: Expect = expect!["[127, 238, 512]"]; pub const BERNSTEINVAZIRANI_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 45044"]; pub const BERNSTEINVAZIRANI_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 20277"]; -pub const BERNSTEINVAZIRANI_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 15769"]; +pub const BERNSTEINVAZIRANI_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 15863"]; pub const BERNSTEINVAZIRANINISQ_EXPECT: Expect = expect!["[One, Zero, One, Zero, One]"]; pub const BERNSTEINVAZIRANINISQ_EXPECT_DEBUG: Expect = expect!["[One, Zero, One, Zero, One]"]; pub const BERNSTEINVAZIRANINISQ_EXPECT_CIRCUIT: Expect = @@ -43,7 +43,7 @@ pub const DEUTSCHJOZSA_EXPECT: Expect = expect!["[true, false, true, false]"]; pub const DEUTSCHJOZSA_EXPECT_DEBUG: Expect = expect!["[true, false, true, false]"]; pub const DEUTSCHJOZSA_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 197703"]; pub const DEUTSCHJOZSA_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 82661"]; -pub const DEUTSCHJOZSA_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 31588"]; +pub const DEUTSCHJOZSA_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 32042"]; pub const DEUTSCHJOZSANISQ_EXPECT: Expect = expect!["([One, Zero, Zero, Zero, Zero], [Zero, Zero, Zero, Zero, Zero])"]; pub const DEUTSCHJOZSANISQ_EXPECT_DEBUG: Expect = @@ -69,7 +69,7 @@ pub const DOTPRODUCTVIAPHASEESTIMATION_EXPECT_CIRCUIT: Expect = pub const DOTPRODUCTVIAPHASEESTIMATION_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 139362"]; pub const DOTPRODUCTVIAPHASEESTIMATION_EXPECT_QIR_ADAPTIVE: Expect = - expect!["generated QIR of length 21960"]; + expect!["generated QIR of length 22245"]; pub const GROVER_EXPECT: Expect = expect![[r#" Number of iterations: 4 Reflecting about marked state... @@ -99,7 +99,7 @@ pub const HIDDENSHIFT_EXPECT_DEBUG: Expect = expect![[r#" [170, 512, 999]"#]]; pub const HIDDENSHIFT_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 42131"]; pub const HIDDENSHIFT_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 25479"]; -pub const HIDDENSHIFT_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 29807"]; +pub const HIDDENSHIFT_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 29902"]; pub const HIDDENSHIFTNISQ_EXPECT: Expect = expect!["[One, Zero, Zero, Zero, Zero, One]"]; pub const HIDDENSHIFTNISQ_EXPECT_DEBUG: Expect = expect!["[One, Zero, Zero, Zero, Zero, One]"]; pub const HIDDENSHIFTNISQ_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 4379"]; @@ -110,7 +110,7 @@ pub const PHASEESTIMATION_EXPECT_DEBUG: Expect = expect!["1.0799224746714913"]; pub const PHASEESTIMATION_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 249358"]; pub const PHASEESTIMATION_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 95778"]; -pub const PHASEESTIMATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 16387"]; +pub const PHASEESTIMATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 16491"]; pub const PHASEFLIPCODE_EXPECT: Expect = expect![[r#" STATE: |000⟩: 0.4743+0.0000𝑖 @@ -158,7 +158,7 @@ pub const QRNG_EXPECT: Expect = expect!["7568811972615905454"]; pub const QRNG_EXPECT_DEBUG: Expect = expect!["7568811972615905454"]; pub const QRNG_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 232827"]; pub const QRNG_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 36067"]; -pub const QRNG_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 16532"]; +pub const QRNG_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 16627"]; pub const SHOR_EXPECT: Expect = expect![[r#" *** Factorizing 187, attempt 1. Estimating period of 182. @@ -290,4 +290,4 @@ pub const THREEQUBITREPETITIONCODE_EXPECT_CIRCUIT: Expect = pub const THREEQUBITREPETITIONCODE_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 18122"]; pub const THREEQUBITREPETITIONCODE_EXPECT_QIR_ADAPTIVE: Expect = - expect!["generated QIR of length 7555"]; + expect!["generated QIR of length 7819"]; diff --git a/source/samples_test/src/tests/getting_started.rs b/source/samples_test/src/tests/getting_started.rs index 71881ab5946..ec9dd817dce 100644 --- a/source/samples_test/src/tests/getting_started.rs +++ b/source/samples_test/src/tests/getting_started.rs @@ -89,7 +89,7 @@ pub const SIMPLETELEPORTATION_EXPECT_DEBUG: Expect = expect![[r#" pub const SIMPLETELEPORTATION_EXPECT_CIRCUIT: Expect = expect!["generated circuit of length 1463"]; pub const SIMPLETELEPORTATION_EXPECT_QIR_ADAPTIVE_RIF: Expect = expect!["generated QIR of length 3118"]; -pub const SIMPLETELEPORTATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 4291"]; +pub const SIMPLETELEPORTATION_EXPECT_QIR_ADAPTIVE: Expect = expect!["generated QIR of length 4378"]; pub const ENTANGLEMENT_EXPECT: Expect = expect![[r#" STATE: |00⟩: 0.7071+0.0000𝑖 From eb677c1d7364ec515896fe45736988e78f8089e1 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Thu, 1 Oct 2026 23:50:03 -0700 Subject: [PATCH 4/4] improve handling of shallow links and phi generation --- .../qsc/src/codegen/tests/adaptive_profile.rs | 87 +++++++++++++++++-- .../src/codegen/tests/adaptive_ri_profile.rs | 71 ++++++++++++++- .../qsc_rir/src/passes/ssa_transform.rs | 50 ++++++----- source/compiler/qsc_rir/src/rir.rs | 10 ++- source/compiler/qsc_rir/src/utils.rs | 48 ++++++---- 5 files changed, 218 insertions(+), 48 deletions(-) diff --git a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs index 92d16f67f29..d8ef6b4dcc7 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs @@ -2643,7 +2643,7 @@ fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable if MResetZ(q0) == One { n = 2; } let h = n; n = 7; - h + n + h } "; let qir = compile_source_to_qir(source, *CAPABILITIES); @@ -2663,12 +2663,11 @@ fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable store i64 2, ptr %var_0 br label %block_2 block_2: - %var_7 = load i64, ptr %var_0 - store i64 %var_7, ptr %var_4 + %var_6 = load i64, ptr %var_0 + store i64 %var_6, ptr %var_4 store i64 7, ptr %var_0 - %var_10 = load i64, ptr %var_4 - %var_5 = add i64 %var_10, 7 - call void @__quantum__rt__int_record_output(i64 %var_5, ptr @0) + %var_9 = load i64, ptr %var_4 + call void @__quantum__rt__int_record_output(i64 %var_9, ptr @0) ret i64 0 } @@ -2701,6 +2700,82 @@ fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable "#]].assert_eq(&qir); } +#[test] +fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable_updated_in_dynamic_branch() + { + let source = " + operation Main() : Int { + use q0 = Qubit(); + mutable n = 0; + if MResetZ(q0) == One { n = 2; } + let h = n; + if MResetZ(q0) == One { n = 7; } + h + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + @0 = internal constant [4 x i8] c"0_i\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + %var_0 = alloca i64 + %var_4 = alloca i64 + %var_8 = alloca i64 + call void @__quantum__rt__initialize(ptr null) + store i64 0, ptr %var_0 + call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) + %var_1 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 0 to ptr)) + br i1 %var_1, label %block_1, label %block_2 + block_1: + store i64 2, ptr %var_0 + br label %block_2 + block_2: + %var_10 = load i64, ptr %var_0 + store i64 %var_10, ptr %var_4 + call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 1 to ptr)) + %var_5 = call i1 @__quantum__rt__read_result(ptr inttoptr (i64 1 to ptr)) + br i1 %var_5, label %block_3, label %block_4 + block_3: + store i64 7, ptr %var_0 + br label %block_4 + block_4: + %var_12 = load i64, ptr %var_4 + store i64 %var_12, ptr %var_8 + %var_14 = load i64, ptr %var_8 + call void @__quantum__rt__int_record_output(i64 %var_14, ptr @0) + ret i64 0 + } + + declare void @__quantum__rt__initialize(ptr) + + declare void @__quantum__qis__mresetz__body(ptr, ptr) #1 + + declare i1 @__quantum__rt__read_result(ptr) #2 + + declare void @__quantum__rt__int_record_output(i64, ptr) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="2" } + attributes #1 = { "irreversible" } + attributes #2 = { nofree nosync nounwind willreturn memory(argmem: read) } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9} + + !0 = !{i32 1, !"qir_major_version", i32 2} + !1 = !{i32 7, !"qir_minor_version", i32 1} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 5, !"int_computations", !{!"i64"}} + !5 = !{i32 5, !"float_computations", !{!"double"}} + !6 = !{i32 7, !"backwards_branching", i2 3} + !7 = !{i32 1, !"arrays", i1 true} + !8 = !{i32 1, !"ir_functions", i1 true} + !9 = !{i32 1, !"writable_results", i1 true} + "#]].assert_eq(&qir); +} + // ---- Cross-package (foreign) IR-function emission ---- /// A reachable, eligible operation that lives in a separate library package diff --git a/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs b/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs index 0545a4b2aff..28840b30237 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs @@ -696,7 +696,7 @@ fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable if MResetZ(q0) == One { n = 2; } let h = n; n = 7; - h + n + h } "; let qir = compile_source_to_qir(source, *CAPABILITIES); @@ -715,9 +715,8 @@ fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable block_1: br label %block_2 block_2: - %var_7 = phi i64 [0, %block_0], [2, %block_1] - %var_5 = add i64 %var_7, 7 - call void @__quantum__rt__int_record_output(i64 %var_5, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0)) + %var_6 = phi i64 [0, %block_0], [2, %block_1] + call void @__quantum__rt__int_record_output(i64 %var_6, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0)) ret i64 0 } @@ -743,3 +742,67 @@ fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable !4 = !{i32 5, !"int_computations", !{!"i64"}} "#]].assert_eq(&qir); } + +#[test] +fn point_in_time_copy_of_mutable_variable_maintains_value_after_original_mutable_updated_in_dynamic_branch() + { + let source = " + operation Main() : Int { + use q0 = Qubit(); + mutable n = 0; + if MResetZ(q0) == One { n = 2; } + let h = n; + if MResetZ(q0) == One { n = 7; } + h + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + @0 = internal constant [4 x i8] c"0_i\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__rt__initialize(i8* null) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + %var_1 = call i1 @__quantum__rt__read_result(%Result* inttoptr (i64 0 to %Result*)) + br i1 %var_1, label %block_1, label %block_2 + block_1: + br label %block_2 + block_2: + %var_9 = phi i64 [0, %block_0], [2, %block_1] + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + %var_5 = call i1 @__quantum__rt__read_result(%Result* inttoptr (i64 1 to %Result*)) + br i1 %var_5, label %block_3, label %block_4 + block_3: + br label %block_4 + block_4: + %var_10 = phi i64 [%var_9, %block_2], [7, %block_3] + call void @__quantum__rt__int_record_output(i64 %var_9, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0)) + ret i64 0 + } + + declare void @__quantum__rt__initialize(i8*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare i1 @__quantum__rt__read_result(%Result*) + + declare void @__quantum__rt__int_record_output(i64, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="2" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 5, !"int_computations", !{!"i64"}} + "#]].assert_eq(&qir); +} diff --git a/source/compiler/qsc_rir/src/passes/ssa_transform.rs b/source/compiler/qsc_rir/src/passes/ssa_transform.rs index 0f34531f39c..55c59d1c4cf 100644 --- a/source/compiler/qsc_rir/src/passes/ssa_transform.rs +++ b/source/compiler/qsc_rir/src/passes/ssa_transform.rs @@ -109,12 +109,14 @@ fn transform_body_to_ssa( let mut phi_nodes = FxHashMap::default(); if rest_preds.iter().any(|pred| { - block_var_map + let pred_map = block_var_map .get(*pred) - .expect("block should have variable map") + .expect("block should have variable map"); + let pred_operand = pred_map .get(var_id) - .map(std::convert::Into::into) - != Some(operand) + .map(Into::::into) + .map(|op| op.mapped(pred_map)); + pred_operand != Some(operand) }) { // Some predecessors have different values for this variable, so a phi node is needed. // Start with the first predecessor's value and block id, then add the values from the other predecessors. @@ -142,32 +144,40 @@ fn transform_body_to_ssa( var_map_updates.insert(*var_id, *mapping); } - // For any phi nodes that need to be inserted, create a new variable and insert - // the phi node at the beginning of the block. The new variable will be used to replace - // the original variable in the block's variable map, which will take care of any orphaned uses. for (variable_id, args) in phi_nodes { - let new_var = Variable { - variable_id: *next_var_id, - ty: operand.get_type(), + let operand_to_map = if let Some((first_arg, rest_args)) = args.split_first() + && rest_args.iter().all(|(arg, _)| *arg == first_arg.0) + { + // All arguments are the same, so no phi node is needed. + // Instead, map the original variable to the common operand. + first_arg.0 + } else { + // For any phi nodes that need to be inserted, create a new variable and insert + // the phi node at the beginning of the block. The new variable will be used to replace + // the original variable in the block's variable map, which will take care of any orphaned uses. + let new_var = Variable { + variable_id: *next_var_id, + ty: operand.get_type(), + }; + let phi_node = Instruction::Phi(args, new_var); + block.0.insert(0, phi_node); + Operand::Variable(new_var) }; - let phi_node = Instruction::Phi(args, new_var); - block.0.insert(0, phi_node); - // A phi node mapping is always deep, and since the variable is newly created, - // there are no existing deep mappings to downgrade. - var_map_updates.insert( - variable_id, - OperandMapping::Deep(Operand::Variable(new_var)), - ); + + var_map_updates.insert(variable_id, OperandMapping::Deep(operand_to_map)); *next_var_id = next_var_id.successor(); let var_map = block_var_map .get_mut(block_id) .expect("block should have variable map"); + + // To make sure calculations of successor blocks get the updated variable mappings, identify any existing mappings + // for the variable being updated and replace them with the new operand mapping. for mapping in var_map.values_mut() { - if let OperandMapping::Shallow(Operand::Variable(var)) = mapping + if let Operand::Variable(var) = mapping.into() && var.variable_id == variable_id { - *mapping = OperandMapping::Deep(Operand::Variable(new_var)); + *mapping = OperandMapping::Deep(operand_to_map); } } } diff --git a/source/compiler/qsc_rir/src/rir.rs b/source/compiler/qsc_rir/src/rir.rs index 6755c97024d..b90f60e391f 100644 --- a/source/compiler/qsc_rir/src/rir.rs +++ b/source/compiler/qsc_rir/src/rir.rs @@ -832,7 +832,7 @@ impl PartialEq for ArrayLiteral { } /// Tracks a mapping to a specific operand, indicating whether the mapping is shallow or deep. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum OperandMapping { /// A shallow mapping to an operand, where the mapped value should be replaced by this operand /// directly without following any further mappings. @@ -863,6 +863,14 @@ impl From<&OperandMapping> for Operand { } } +impl From<&mut OperandMapping> for Operand { + fn from(val: &mut OperandMapping) -> Self { + match val { + OperandMapping::Shallow(op) | OperandMapping::Deep(op) => *op, + } + } +} + fn write_store_array_instruction( f: &mut Formatter, value: &[Operand], diff --git a/source/compiler/qsc_rir/src/utils.rs b/source/compiler/qsc_rir/src/utils.rs index 2afbbc01282..2899b270acb 100644 --- a/source/compiler/qsc_rir/src/utils.rs +++ b/source/compiler/qsc_rir/src/utils.rs @@ -202,18 +202,7 @@ pub(crate) fn map_variable_use_in_block( // Replace any arguments with the new values of stored variables. Instruction::Call(_, args, _, _) => { - *args = args - .iter() - .map(|arg| match arg { - Operand::Variable(var) => { - // If the variable is not in the map, it is not something whose value has been updated via store in this block, - // so just fallback to use the `arg` value directly. - // `map_to_operand` does this automatically by returning `self`` when the variable is not in the map. - var.map_to_operand(var_map) - } - Operand::Literal(_) => *arg, - }) - .collect(); + *args = args.iter().map(|arg| arg.mapped(var_map)).collect(); } // Replace the branch condition with the new value of the variable. @@ -277,11 +266,9 @@ fn update_variable_mapping( ) { // Note this uses the mapped operand to make sure this variable points to whatever root literal or variable // this operand corresponds to at this point in the block. This makes the new variable respect a point-in-time - // copy of the operand. - var_map.insert( - var.variable_id, - OperandMapping::Deep(operand.mapped(var_map)), - ); + // copy of the operand. However, it will create a mapping that matches the last mapping of the operand, ensuring + // that a shallow mapping is not incorrectly treated as a deep mapping. + var_map.insert(var.variable_id, operand.last_mapping(var_map)); // For all existing deep mappings to this variable, downgrade them to shallow mappings. // This ensures those previous mappings represent the value at the time they were created, rather than the new value being stored. @@ -302,6 +289,33 @@ impl Operand { Operand::Variable(var) => var.map_to_operand(var_map), } } + + #[must_use] + pub(crate) fn last_mapping( + &self, + var_map: &FxHashMap, + ) -> OperandMapping { + match self { + Operand::Literal(_) => OperandMapping::Deep(*self), + Operand::Variable(var) => { + let mut var = *var; + while let Some(mapping) = var_map.get(&var.variable_id) { + if let Operand::Variable(new_var) = mapping.into() { + if new_var.variable_id == var.variable_id { + break; + } + var = new_var; + if mapping.is_shallow() { + return *mapping; + } + } else { + return *mapping; + } + } + OperandMapping::Deep(Operand::Variable(var)) + } + } + } } impl Variable {