Repository: microsoft/qsharp
Pull request #692 (2023-09-14 00:29:34)

Fixes a bug in single output recording
This change applies a temporary fix to output recording when only a single result is present. By treating it as a tuple with arity one we include at least one aggregate recording function and trigger logic in the service.


Patch:
diff --git a/compiler/qsc_codegen/src/qir_base.rs b/compiler/qsc_codegen/src/qir_base.rs
index 9bb7fa99..7548d774 100644
--- a/compiler/qsc_codegen/src/qir_base.rs
+++ b/compiler/qsc_codegen/src/qir_base.rs
@@ -170,25 +170,39 @@ impl BaseProfSim {
     }
 
     fn write_output_recording(&mut self, val: &Value) -> std::fmt::Result {
-        match val {
-            Value::Array(arr) => {
-                self.write_array_recording(arr.len())?;
-                for val in arr.iter() {
-                    self.write_output_recording(val)?;
+        fn write_output_recording_recursive(
+            this: &mut BaseProfSim,
+            val: &Value,
+        ) -> std::fmt::Result {
+            match val {
+                Value::Array(arr) => {
+                    this.write_array_recording(arr.len())?;
+                    for val in arr.iter() {
+                        write_output_recording_recursive(this, val)?;
+                    }
                 }
-            }
-            Value::Result(r) => {
-                self.write_result_recording(r.unwrap_id());
-            }
-            Value::Tuple(tup) => {
-                self.write_tuple_recording(tup.len())?;
-                for val in tup.iter() {
-                    self.write_output_recording(val)?;
+                Value::Result(r) => {
+                    this.write_result_recording(r.unwrap_id());
+                }
+                Value::Tuple(tup) => {
+                    this.write_tuple_recording(tup.len())?;
+                    for val in tup.iter() {
+                        write_output_recording_recursive(this, val)?;
+                    }
                 }
+                _ => panic!("unexpected value type: {val:?}"),
             }
-            _ => panic!("unexpected value type: {val:?}"),
+            Ok(())
         }
-        Ok(())
+
+        if matches!(val, Value::Result(_)) {
+            // This is a temporary workaround to avoid output recording bug
+            // that fails when only a result is returned. This treats a single
+            // result as if it were a tuple of arity 1.
+            self.write_tuple_recording(1)?;
+        }
+
+        write_output_recording_recursive(self, val)
     }
 
     fn write_result_recording(&mut self, res: usize) {


Test patch:
diff --git a/compiler/qsc/src/interpret/stateful/tests.rs b/compiler/qsc/src/interpret/stateful/tests.rs
index c5f2f49f..9d4dbb6a 100644
--- a/compiler/qsc/src/interpret/stateful/tests.rs
+++ b/compiler/qsc/src/interpret/stateful/tests.rs
@@ -567,6 +567,7 @@ mod given_interpreter {
                 define void @ENTRYPOINT__main() #0 {
                   call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
                   call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
                   call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
                   ret void
                 }
@@ -633,6 +634,7 @@ mod given_interpreter {
                 define void @ENTRYPOINT__main() #0 {
                   call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
                   call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
                   call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
                   ret void
                 }
@@ -701,6 +703,7 @@ mod given_interpreter {
                 define void @ENTRYPOINT__main() #0 {
                   call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
                   call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
                   call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
                   ret void
                 }
@@ -808,6 +811,7 @@ mod given_interpreter {
                 define void @ENTRYPOINT__main() #0 {
                   call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
                   call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
                   call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
                   ret void
                 }
@@ -889,6 +893,7 @@ mod given_interpreter {
                 define void @ENTRYPOINT__main() #0 {
                   call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
                   call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
                   call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
                   ret void
                 }
diff --git a/compiler/qsc_codegen/src/qir_base/tests.rs b/compiler/qsc_codegen/src/qir_base/tests.rs
index f91be0a6..0914f6fe 100644
--- a/compiler/qsc_codegen/src/qir_base/tests.rs
+++ b/compiler/qsc_codegen/src/qir_base/tests.rs
@@ -69,6 +69,7 @@ fn simple_entry_program_is_valid() {
             define void @ENTRYPOINT__main() #0 {
               call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
               call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
+              call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
               call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
               ret void
             }
@@ -132,6 +133,7 @@ fn simple_program_is_valid() {
             define void @ENTRYPOINT__main() #0 {
               call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
               call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
+              call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
               call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
               ret void
             }
diff --git a/pip/tests-qir/test_qir.py b/pip/tests-qir/test_qir.py
index 9f276ea9..78adb6e6 100644
--- a/pip/tests-qir/test_qir.py
+++ b/pip/tests-qir/test_qir.py
@@ -16,19 +16,19 @@ def test_compile_qir_input_data() -> None:
     assert module.functions[0].name == "ENTRYPOINT__main"
     func = module.functions[0]
     assert len(func.basic_blocks) == 1
-    assert len(func.basic_blocks[0].instructions) == 3
+    assert len(func.basic_blocks[0].instructions) == 4
     call_m = func.basic_blocks[0].instructions[0]
     assert isinstance(call_m, Call)
     assert call_m.callee.name == "__quantum__qis__m__body"
     assert len(call_m.args) == 2
     assert qubit_id(call_m.args[0]) == 0
     assert result_id(call_m.args[1]) == 0
-    record_res = func.basic_blocks[0].instructions[1]
+    record_res = func.basic_blocks[0].instructions[2]
     assert isinstance(record_res, Call)
     assert len(record_res.args) == 2
     assert record_res.callee.name == "__quantum__rt__result_record_output"
     assert result_id(record_res.args[0]) == 0
-    assert func.basic_blocks[0].instructions[2].opcode == Opcode.RET
+    assert func.basic_blocks[0].instructions[3].opcode == Opcode.RET
 
 
 def test_compile_qir_all_gates() -> None: