Repository: qir-alliance/pyqir (language: python)

Issue:
`requiredQubits` and `requiredResults` attributes are not added
Previously, `requiredQubits` and `requiredResults` were generated from `pyqir-generator`. These attributes are important to us to compile further down to the devices. Is it possible to re-add the in the generation ?



Discussion / hints:
These were removed in #147, since QAT can add them itself. But it sounds like you're sending a PyQIR-generated program directly to your internal compiler without going through QAT, is that right? I agree it'd make sense for PyQIR to still be able to add them in that case.

Fix patch:
diff --git a/pyqir-generator/src/python.rs b/pyqir-generator/src/python.rs
index 52ad471f..5a107f25 100644
--- a/pyqir-generator/src/python.rs
+++ b/pyqir-generator/src/python.rs
@@ -423,6 +423,8 @@ impl SimpleModule {
     fn new(py: Python, name: String, num_qubits: u64, num_results: u64) -> PyResult<SimpleModule> {
         let model = SemanticModel {
             name,
+            required_num_qubits: num_qubits,
+            required_num_results: num_results,
             external_functions: vec![],
             instructions: vec![],
         };
diff --git a/qirlib/src/generation/emit.rs b/qirlib/src/generation/emit.rs
index 66622992..bab0a1b7 100644
--- a/qirlib/src/generation/emit.rs
+++ b/qirlib/src/generation/emit.rs
@@ -11,6 +11,7 @@ use crate::{
     passes::run_basic_passes_on,
 };
 use inkwell::{
+    attributes::AttributeLoc,
     context::Context,
     module::Linkage,
     types::{AnyTypeEnum, BasicType, BasicTypeEnum},
@@ -50,13 +51,16 @@ pub fn populate_context<'a>(
 ) -> Result<CodeGenerator<'a>, String> {
     let module = ctx.create_module(&model.name);
     let generator = CodeGenerator::new(ctx, module)?;
+    add_external_functions(&generator, model.external_functions.iter());
     build_entry_function(&generator, model)?;
     Ok(generator)
 }
 
 fn build_entry_function(generator: &CodeGenerator, model: &SemanticModel) -> Result<(), String> {
-    add_external_functions(generator, model.external_functions.iter());
     let entry_point = qir::create_entry_point(generator.context, &generator.module);
+    add_num_attribute(entry_point, "requiredQubits", model.required_num_qubits);
+    add_num_attribute(entry_point, "requiredResults", model.required_num_results);
+
     let entry = generator.context.append_basic_block(entry_point, "entry");
     generator.builder.position_at_end(entry);
     write_instructions(model, generator, entry_point);
@@ -64,6 +68,12 @@ fn build_entry_function(generator: &CodeGenerator, model: &SemanticModel) -> Res
     generator.module.verify().map_err(|e| e.to_string())
 }
 
+fn add_num_attribute(function: FunctionValue, key: &str, value: u64) {
+    let context = function.get_type().get_context();
+    let attribute = context.create_string_attribute(key, &value.to_string());
+    function.add_attribute(AttributeLoc::Function, attribute);
+}
+
 fn add_external_functions<'a>(
     generator: &CodeGenerator,
     functions: impl Iterator<Item = &'a (String, interop::Type)>,
@@ -133,10 +143,56 @@ mod tests {
     use normalize_line_endings::normalized;
     use std::{env, fs, path::PathBuf};
 
+    #[test]
+    fn test_zero_required_qubits_results() -> Result<(), String> {
+        check_or_save_reference_ir(&SemanticModel {
+            name: "test_zero_required_qubits_results".to_string(),
+            required_num_qubits: 0,
+            required_num_results: 0,
+            external_functions: vec![],
+            instructions: vec![],
+        })
+    }
+
+    #[test]
+    fn test_one_required_qubit() -> Result<(), String> {
+        check_or_save_reference_ir(&SemanticModel {
+            name: "test_one_required_qubit".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 0,
+            external_functions: vec![],
+            instructions: vec![],
+        })
+    }
+
+    #[test]
+    fn test_one_required_result() -> Result<(), String> {
+        check_or_save_reference_ir(&SemanticModel {
+            name: "test_one_required_result".to_string(),
+            required_num_qubits: 0,
+            required_num_results: 1,
+            external_functions: vec![],
+            instructions: vec![],
+        })
+    }
+
+    #[test]
+    fn test_many_required_qubits_results() -> Result<(), String> {
+        check_or_save_reference_ir(&SemanticModel {
+            name: "test_many_required_qubits_results".to_string(),
+            required_num_qubits: 5,
+            required_num_results: 7,
+            external_functions: vec![],
+            instructions: vec![],
+        })
+    }
+
     #[test]
     fn test_empty_if() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_empty_if".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 1,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -155,6 +211,8 @@ mod tests {
     fn test_if_then() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_if_then".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 1,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -173,6 +231,8 @@ mod tests {
     fn test_if_else() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_if_else".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 1,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -191,6 +251,8 @@ mod tests {
     fn test_if_then_continue() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_if_then_continue".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 1,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -210,6 +272,8 @@ mod tests {
     fn test_if_else_continue() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_if_else_continue".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 1,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -229,6 +293,8 @@ mod tests {
     fn test_if_then_else_continue() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_if_then_else_continue".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 1,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -248,6 +314,8 @@ mod tests {
     fn test_if_then_then() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_if_then_then".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 2,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -271,6 +339,8 @@ mod tests {
     fn test_if_else_else() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_if_else_else".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 2,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -294,6 +364,8 @@ mod tests {
     fn test_if_then_else() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_if_then_else".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 2,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -317,6 +389,8 @@ mod tests {
     fn test_if_else_then() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_if_else_then".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 2,
             external_functions: vec![],
             instructions: vec![
                 Instruction::M(Measured::new(Value::Qubit(0), Value::Result(0))),
@@ -340,6 +414,8 @@ mod tests {
     fn test_allows_unmeasured_result_condition() -> Result<(), String> {
         let model = SemanticModel {
             name: "test_allows_unmeasured_result_condition".to_string(),
+            required_num_qubits: 1,
+            required_num_results: 1,
             external_functions: vec![],
             instructions: vec![Instruction::IfResult(IfResult {
                 cond: Value::Result(0),
@@ -358,6 +434,8 @@ mod tests {
 
         check_or_save_reference_ir(&SemanticModel {
             name: "test_call_variable".to_string(),
+            required_num_qubits: 0,
+            required_num_results: 0,
             external_functions: vec![
                 (
                     "foo".to_string(),
@@ -450,6 +528,8 @@ mod tests {
 
         check_or_save_reference_ir(&SemanticModel {
             name: "test_int_binary_operators".to_string(),
+            required_num_qubits: 0,
+            required_num_results: 0,
             external_functions: vec![
                 (
                     "source".to_string(),
diff --git a/qirlib/src/generation/interop.rs b/qirlib/src/generation/interop.rs
index 06e35e25..4bf1aa5a 100644
--- a/qirlib/src/generation/interop.rs
+++ b/qirlib/src/generation/interop.rs
@@ -217,6 +217,8 @@ impl Int {
 #[derive(Clone)]
 pub struct SemanticModel {
     pub name: String,
+    pub required_num_qubits: u64,
+    pub required_num_results: u64,
     pub external_functions: Vec<(String, Type)>,
     pub instructions: Vec<Instruction>,
 }
diff --git a/qirlib/src/generation/mod.rs b/qirlib/src/generation/mod.rs
index 46eea656..850f4d50 100644
--- a/qirlib/src/generation/mod.rs
+++ b/qirlib/src/generation/mod.rs
@@ -72,6 +72,8 @@ mod module_conversion_tests {
     fn get_model(name: String) -> SemanticModel {
         SemanticModel {
             name,
+            required_num_qubits: 1,
+            required_num_results: 1,
             external_functions: vec![],
             instructions: vec![Instruction::M(Measured::new(
                 Value::Qubit(0),


Test patch:
diff --git a/qirlib/resources/tests/test_allows_unmeasured_result_condition.ll b/qirlib/resources/tests/test_allows_unmeasured_result_condition.ll
index 6eafbc60..f9380cae 100644
--- a/qirlib/resources/tests/test_allows_unmeasured_result_condition.ll
+++ b/qirlib/resources/tests/test_allows_unmeasured_result_condition.ll
@@ -27,4 +27,4 @@ declare void @__quantum__qis__x__body(%Qubit*)
 
 declare void @__quantum__qis__h__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="1" }
diff --git a/qirlib/resources/tests/test_call_variable.ll b/qirlib/resources/tests/test_call_variable.ll
index f92ff9b0..87744bce 100644
--- a/qirlib/resources/tests/test_call_variable.ll
+++ b/qirlib/resources/tests/test_call_variable.ll
@@ -12,4 +12,4 @@ entry:
   ret void
 }
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="0" "requiredResults"="0" }
diff --git a/qirlib/resources/tests/test_empty_if.ll b/qirlib/resources/tests/test_empty_if.ll
index 82e25343..1489cb31 100644
--- a/qirlib/resources/tests/test_empty_if.ll
+++ b/qirlib/resources/tests/test_empty_if.ll
@@ -24,4 +24,4 @@ declare void @__quantum__qis__mz__body(%Qubit*, %Result*)
 
 declare i1 @__quantum__qis__read_result__body(%Result*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="1" }
diff --git a/qirlib/resources/tests/test_if_else.ll b/qirlib/resources/tests/test_if_else.ll
index a48b8343..182368ee 100644
--- a/qirlib/resources/tests/test_if_else.ll
+++ b/qirlib/resources/tests/test_if_else.ll
@@ -27,4 +27,4 @@ declare i1 @__quantum__qis__read_result__body(%Result*)
 
 declare void @__quantum__qis__x__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="1" }
diff --git a/qirlib/resources/tests/test_if_else_continue.ll b/qirlib/resources/tests/test_if_else_continue.ll
index 5ffe7fba..ff14568d 100644
--- a/qirlib/resources/tests/test_if_else_continue.ll
+++ b/qirlib/resources/tests/test_if_else_continue.ll
@@ -30,4 +30,4 @@ declare void @__quantum__qis__x__body(%Qubit*)
 
 declare void @__quantum__qis__h__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="1" }
diff --git a/qirlib/resources/tests/test_if_else_else.ll b/qirlib/resources/tests/test_if_else_else.ll
index 227674f0..7883130a 100644
--- a/qirlib/resources/tests/test_if_else_else.ll
+++ b/qirlib/resources/tests/test_if_else_else.ll
@@ -38,4 +38,4 @@ declare i1 @__quantum__qis__read_result__body(%Result*)
 
 declare void @__quantum__qis__x__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="2" }
diff --git a/qirlib/resources/tests/test_if_else_then.ll b/qirlib/resources/tests/test_if_else_then.ll
index a5a35b83..0f5593d4 100644
--- a/qirlib/resources/tests/test_if_else_then.ll
+++ b/qirlib/resources/tests/test_if_else_then.ll
@@ -38,4 +38,4 @@ declare i1 @__quantum__qis__read_result__body(%Result*)
 
 declare void @__quantum__qis__x__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="2" }
diff --git a/qirlib/resources/tests/test_if_then.ll b/qirlib/resources/tests/test_if_then.ll
index f81b8837..69d922f1 100644
--- a/qirlib/resources/tests/test_if_then.ll
+++ b/qirlib/resources/tests/test_if_then.ll
@@ -27,4 +27,4 @@ declare i1 @__quantum__qis__read_result__body(%Result*)
 
 declare void @__quantum__qis__x__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="1" }
diff --git a/qirlib/resources/tests/test_if_then_continue.ll b/qirlib/resources/tests/test_if_then_continue.ll
index 5aae6618..68584f77 100644
--- a/qirlib/resources/tests/test_if_then_continue.ll
+++ b/qirlib/resources/tests/test_if_then_continue.ll
@@ -30,4 +30,4 @@ declare void @__quantum__qis__x__body(%Qubit*)
 
 declare void @__quantum__qis__h__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="1" }
diff --git a/qirlib/resources/tests/test_if_then_else.ll b/qirlib/resources/tests/test_if_then_else.ll
index d785859d..19671f69 100644
--- a/qirlib/resources/tests/test_if_then_else.ll
+++ b/qirlib/resources/tests/test_if_then_else.ll
@@ -38,4 +38,4 @@ declare i1 @__quantum__qis__read_result__body(%Result*)
 
 declare void @__quantum__qis__x__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="2" }
diff --git a/qirlib/resources/tests/test_if_then_else_continue.ll b/qirlib/resources/tests/test_if_then_else_continue.ll
index 6f1a5fd9..0775cf91 100644
--- a/qirlib/resources/tests/test_if_then_else_continue.ll
+++ b/qirlib/resources/tests/test_if_then_else_continue.ll
@@ -33,4 +33,4 @@ declare void @__quantum__qis__y__body(%Qubit*)
 
 declare void @__quantum__qis__h__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="1" }
diff --git a/qirlib/resources/tests/test_if_then_then.ll b/qirlib/resources/tests/test_if_then_then.ll
index 21675cb8..c2feedfa 100644
--- a/qirlib/resources/tests/test_if_then_then.ll
+++ b/qirlib/resources/tests/test_if_then_then.ll
@@ -38,4 +38,4 @@ declare i1 @__quantum__qis__read_result__body(%Result*)
 
 declare void @__quantum__qis__x__body(%Qubit*)
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="2" }
diff --git a/qirlib/resources/tests/test_int_binary_operators.ll b/qirlib/resources/tests/test_int_binary_operators.ll
index 16db51fa..945d02dc 100644
--- a/qirlib/resources/tests/test_int_binary_operators.ll
+++ b/qirlib/resources/tests/test_int_binary_operators.ll
@@ -50,4 +50,4 @@ entry:
   ret void
 }
 
-attributes #0 = { "EntryPoint" }
+attributes #0 = { "EntryPoint" "requiredQubits"="0" "requiredResults"="0" }
diff --git a/qirlib/resources/tests/test_many_required_qubits_results.ll b/qirlib/resources/tests/test_many_required_qubits_results.ll
new file mode 100644
index 00000000..117f1260
--- /dev/null
+++ b/qirlib/resources/tests/test_many_required_qubits_results.ll
@@ -0,0 +1,9 @@
+; ModuleID = 'test_many_required_qubits_results'
+source_filename = "test_many_required_qubits_results"
+
+define void @main() #0 {
+entry:
+  ret void
+}
+
+attributes #0 = { "EntryPoint" "requiredQubits"="5" "requiredResults"="7" }
diff --git a/qirlib/resources/tests/test_one_required_qubit.ll b/qirlib/resources/tests/test_one_required_qubit.ll
new file mode 100644
index 00000000..e16765f5
--- /dev/null
+++ b/qirlib/resources/tests/test_one_required_qubit.ll
@@ -0,0 +1,9 @@
+; ModuleID = 'test_one_required_qubit'
+source_filename = "test_one_required_qubit"
+
+define void @main() #0 {
+entry:
+  ret void
+}
+
+attributes #0 = { "EntryPoint" "requiredQubits"="1" "requiredResults"="0" }
diff --git a/qirlib/resources/tests/test_one_required_result.ll b/qirlib/resources/tests/test_one_required_result.ll
new file mode 100644
index 00000000..54e1248d
--- /dev/null
+++ b/qirlib/resources/tests/test_one_required_result.ll
@@ -0,0 +1,9 @@
+; ModuleID = 'test_one_required_result'
+source_filename = "test_one_required_result"
+
+define void @main() #0 {
+entry:
+  ret void
+}
+
+attributes #0 = { "EntryPoint" "requiredQubits"="0" "requiredResults"="1" }
diff --git a/qirlib/resources/tests/test_zero_required_qubits_results.ll b/qirlib/resources/tests/test_zero_required_qubits_results.ll
new file mode 100644
index 00000000..b92b522f
--- /dev/null
+++ b/qirlib/resources/tests/test_zero_required_qubits_results.ll
@@ -0,0 +1,9 @@
+; ModuleID = 'test_zero_required_qubits_results'
+source_filename = "test_zero_required_qubits_results"
+
+define void @main() #0 {
+entry:
+  ret void
+}
+
+attributes #0 = { "EntryPoint" "requiredQubits"="0" "requiredResults"="0" }