Repository: microsoft/qsharp
Pull request #1530 (2024-05-15 21:19:04)

Superdense coding sample works in Adaptive Profile
Changed Superdense coding example to work with adaptive profile
* Use quantum random bit generator instead of one provided in simulator
* Removed unnecessary check that wasn't compatible with the profile
* Changed return value so that correctness of the algorithm can be verified.
Now it generates QIR in adaptive profile.

CHECK: Not sure if this return type from Main is supported. Need to check.


Patch:
diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs
index 67c51a9f..0e405214 100644
--- a/compiler/qsc_eval/src/lib.rs
+++ b/compiler/qsc_eval/src/lib.rs
@@ -359,6 +359,19 @@ impl Env {
         }
     }
 
+    pub fn leave_current_frame(&mut self) {
+        let current_frame_id = self
+            .0
+            .last()
+            .expect("should be at least one scope")
+            .frame_id;
+        if current_frame_id == 0 {
+            // Do not remove the global scope.
+            return;
+        }
+        self.0.retain(|scope| scope.frame_id != current_frame_id);
+    }
+
     pub fn bind_variable_in_top_frame(&mut self, local_var_id: LocalVarId, var: Variable) {
         let Some(scope) = self.0.last_mut() else {
             panic!("no frames in scope");
@@ -611,6 +624,11 @@ impl State {
                     env.leave_scope();
                     continue;
                 }
+                Some(ExecGraphNode::RetFrame) => {
+                    self.leave_frame();
+                    env.leave_current_frame();
+                    continue;
+                }
                 Some(ExecGraphNode::PushScope) => {
                     self.push_scope(env);
                     self.idx += 1;
diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs
index 5f8d84fc..7ac1dfcc 100644
--- a/compiler/qsc_fir/src/fir.rs
+++ b/compiler/qsc_fir/src/fir.rs
@@ -884,8 +884,6 @@ pub enum ExecGraphNode {
     Bind(PatId),
     /// An expression to execute.
     Expr(ExprId),
-    /// A statement to track for debugging.
-    Stmt(StmtId),
     /// An unconditional jump with to given location.
     Jump(u32),
     /// A conditional jump with to given location, where the jump is only taken if the condition is
@@ -900,6 +898,11 @@ pub enum ExecGraphNode {
     Unit,
     /// The end of the control flow graph.
     Ret,
+    /// The end of the control flow graph plus a pop of the current debug frame. Used instead of `Ret`
+    /// when debugging.
+    RetFrame,
+    /// A statement to track for debugging.
+    Stmt(StmtId),
     /// A push of a new scope, used when tracking variables for debugging.
     PushScope,
     /// A pop of the current scope, used when tracking variables for debugging.
diff --git a/compiler/qsc_lowerer/src/lib.rs b/compiler/qsc_lowerer/src/lib.rs
index 8a238e44..62909495 100644
--- a/compiler/qsc_lowerer/src/lib.rs
+++ b/compiler/qsc_lowerer/src/lib.rs
@@ -32,6 +32,7 @@ pub struct Lowerer {
     assigner: Assigner,
     exec_graph: Vec<ExecGraphNode>,
     enable_debug: bool,
+    ret_node: ExecGraphNode,
 }
 
 impl Default for Lowerer {
@@ -53,19 +54,25 @@ impl Lowerer {
             assigner: Assigner::new(),
             exec_graph: Vec::new(),
             enable_debug: false,
+            ret_node: ExecGraphNode::Ret,
         }
     }
 
     #[must_use]
     pub fn with_debug(mut self, dbg: bool) -> Self {
         self.enable_debug = dbg;
+        if dbg {
+            self.ret_node = ExecGraphNode::RetFrame;
+        } else {
+            self.ret_node = ExecGraphNode::Ret;
+        }
         self
     }
 
     pub fn take_exec_graph(&mut self) -> Vec<ExecGraphNode> {
         self.exec_graph
             .drain(..)
-            .chain(once(ExecGraphNode::Ret))
+            .chain(once(self.ret_node))
             .collect()
     }
 
@@ -248,7 +255,7 @@ impl Lowerer {
             exec_graph: self
                 .exec_graph
                 .drain(..)
-                .chain(once(ExecGraphNode::Ret))
+                .chain(once(self.ret_node))
                 .collect(),
         }
     }
@@ -546,7 +553,7 @@ impl Lowerer {
             }
             hir::ExprKind::Return(expr) => {
                 let expr = self.lower_expr(expr);
-                self.exec_graph.push(ExecGraphNode::Ret);
+                self.exec_graph.push(self.ret_node);
                 fir::ExprKind::Return(expr)
             }
             hir::ExprKind::Tuple(items) => fir::ExprKind::Tuple(
diff --git a/samples/algorithms/SuperdenseCoding.qs b/samples/algorithms/SuperdenseCoding.qs
index bcafeccb..c56ebd6d 100644
--- a/samples/algorithms/SuperdenseCoding.qs
+++ b/samples/algorithms/SuperdenseCoding.qs
@@ -9,11 +9,8 @@
 /// This Q# program implements superdense coding to send two classical bits of
 /// information.
 namespace Sample {
-    open Microsoft.Quantum.Diagnostics;
-    open Microsoft.Quantum.Random;
-
     @EntryPoint()
-    operation Main() : (Bool, Bool) {
+    operation Main() : ((Bool, Bool), (Bool, Bool)) {
         use (aliceQubit, bobQubit) = (Qubit(), Qubit());
 
         // The protocol starts with the preparation of an entangled state, which
@@ -21,25 +18,26 @@ namespace Sample {
         CreateEntangledPair(aliceQubit, bobQubit);
 
         // Alice encodes 2 random bits in her qubit of the entangled pair.
-        let randomInt = DrawRandomInt(0, 3);
-        let bit1 = (randomInt &&& 1) == 0b01;
-        let bit2 = (randomInt &&& 2) == 0b10;
-        SuperdenseEncode(bit1, bit2, aliceQubit);
+        let sourceBit1 = DrawRandomBit();
+        let sourceBit2 = DrawRandomBit();
+        SuperdenseEncode(sourceBit1, sourceBit2, aliceQubit);
 
         // "Send" Alice's qubit to Bob and let Bob decode two bits.
         let (decodedBit1, decodedBit2) = SuperdenseDecode(aliceQubit, bobQubit);
 
         ResetAll([aliceQubit, bobQubit]);
-        return (decodedBit1, decodedBit2);
+        return ((sourceBit1, sourceBit2), (decodedBit1, decodedBit2));
+    }
+
+    // Generates random bit using an auxilliary qubit
+    operation DrawRandomBit() : Bool {
+        use q = Qubit();
+        H(q);
+        return MResetZ(q) == One;
     }
 
     // Prepares an entangled state: 1/sqrt(2)(|00〉 + |11〉)
     operation CreateEntangledPair(q1 : Qubit, q2 : Qubit) : Unit {
-        Fact(
-            CheckAllZero([q1, q2]),
-            "Qubits are expected to be in the |00〉 state"
-        );
-
         H(q1);
         CNOT(q1, q2);
     }


Test patch:
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index 893a9927..b291521e 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -1580,6 +1580,99 @@ mod given_interpreter {
             assert_eq!(2, bps.len());
         }
 
+        #[test]
+        fn debugger_simple_execution_succeeds() {
+            let source = indoc! { r#"
+            namespace Test {
+                function Hello() : Unit {
+                    Message("hello there...");
+                }
+
+                @EntryPoint()
+                operation Main() : Unit {
+                    Hello()
+                }
+            }"#};
+
+            let sources = SourceMap::new([("test".into(), source.into())], None);
+            let mut debugger = Debugger::new(
+                sources,
+                TargetCapabilityFlags::all(),
+                Encoding::Utf8,
+                LanguageFeatures::default(),
+            )
+            .expect("debugger should be created");
+            let (result, output) = entry(&mut debugger.interpreter);
+            is_unit_with_output_eval_entry(&result, &output, "hello there...");
+        }
+
+        #[test]
+        fn debugger_execution_with_call_to_library_succeeds() {
+            let source = indoc! { r#"
+            namespace Test {
+                open Microsoft.Quantum.Math;
+                @EntryPoint()
+                operation Main() : Int {
+                    Binom(31, 7)
+                }
+            }"#};
+
+            let sources = SourceMap::new([("test".into(), source.into())], None);
+            let mut debugger = Debugger::new(
+                sources,
+                TargetCapabilityFlags::all(),
+                Encoding::Utf8,
+                LanguageFeatures::default(),
+            )
+            .expect("debugger should be created");
+            let (result, output) = entry(&mut debugger.interpreter);
+            is_only_value(&result, &output, &Value::Int(2_629_575));
+        }
+
+        #[test]
+        fn debugger_execution_with_early_return_succeeds() {
+            let source = indoc! { r#"
+            namespace Test {
+                open Microsoft.Quantum.Arrays;
+
+                operation Max20(i : Int) : Int {
+                    if (i > 20) {
+                        return 20;
+                    }
+                    return i;
+                }
+
+                @EntryPoint()
+                operation Main() : Int[] {
+                    ForEach(Max20, [10, 20, 30, 40, 50])
+                }
+            }"#};
+
+            let sources = SourceMap::new([("test".into(), source.into())], None);
+            let mut debugger = Debugger::new(
+                sources,
+                TargetCapabilityFlags::all(),
+                Encoding::Utf8,
+                LanguageFeatures::default(),
+            )
+            .expect("debugger should be created");
+            let (result, output) = entry(&mut debugger.interpreter);
+            is_only_value(
+                &result,
+                &output,
+                &Value::Array(
+                    vec![
+                        Value::Int(10),
+                        Value::Int(20),
+                        Value::Int(20),
+                        Value::Int(20),
+                        Value::Int(20),
+                    ]
+                    .into(),
+                ),
+            );
+        }
+
         #[test]
         fn multiple_namespaces_are_loaded_from_sources_into_eval_context() {
             let source = indoc! { r#"