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

Issue:
Consider providing the core llvm lib as a Rust crate
We've heard from people using the Rust core of PyQIR directly. I am hence filing this issue for the purpose of discussion and to gather interest in having a Rust package. So far, the Rust API were not necessarily intended to be used directly, and would need to be examined carefully if we were to provide the Rust infrastructure as its own crate.


Discussion / hints:
It looks like this issue may be relevant for #47 as well, perhaps?
This may also "solve" https://github.com/qir-alliance/pyqir/issues/52

Fix patch:
diff --git a/pyqir-generator/Cargo.toml b/pyqir-generator/Cargo.toml
index 43fb9b06..17ae11d1 100644
--- a/pyqir-generator/Cargo.toml
+++ b/pyqir-generator/Cargo.toml
@@ -16,9 +16,6 @@ log = "0.4.14"
 pyo3 = "0.15.1"
 qirlib = { path = "../qirlib" }
 
-[dev-dependencies]
-normalize-line-endings = "0.3.0"
-
 [features]
 extension-module = ["pyo3/abi3-py36", "pyo3/extension-module"]
 default = ["extension-module"]
diff --git a/pyqir-generator/src/lib.rs b/pyqir-generator/src/lib.rs
index c22a5fe9..0cf30ad1 100644
--- a/pyqir-generator/src/lib.rs
+++ b/pyqir-generator/src/lib.rs
@@ -10,7 +10,4 @@
 //     we can directly control in our code.
 #![allow(clippy::needless_option_as_deref)]
 
-pub mod emit;
-pub mod interop;
 pub mod python;
-pub mod qir;
diff --git a/pyqir-generator/src/python.rs b/pyqir-generator/src/python.rs
index 107ab2ee..ad673c41 100644
--- a/pyqir-generator/src/python.rs
+++ b/pyqir-generator/src/python.rs
@@ -1,19 +1,19 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::{
-    emit,
-    interop::{
-        ClassicalRegister, Controlled, If, Instruction, Measured, QuantumRegister, Rotated,
-        SemanticModel, Single,
-    },
-};
 use pyo3::{
     basic::CompareOp,
     exceptions::{PyOSError, PyTypeError},
     prelude::*,
     PyObjectProtocol,
 };
+use qirlib::generation::{
+    emit,
+    interop::{
+        ClassicalRegister, Controlled, If, Instruction, Measured, QuantumRegister, Rotated,
+        SemanticModel, Single,
+    },
+};
 use std::{
     collections::hash_map::DefaultHasher,
     hash::{Hash, Hasher},
diff --git a/pyqir-jit/Cargo.toml b/pyqir-jit/Cargo.toml
index 5d4eddbd..4090420f 100644
--- a/pyqir-jit/Cargo.toml
+++ b/pyqir-jit/Cargo.toml
@@ -12,10 +12,6 @@ readme = "README.md"
 [dependencies]
 qirlib = { path = "../qirlib" }
 inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", default-features = false, features = ["llvm11-0", "target-x86"] }
-lazy_static = "1.4.0"
-mut_static = "5.0.0"
-log = "0.4.14"
-bitvec = "1.0.0"
 
 [dependencies.pyo3]
 version = "0.15.1"
diff --git a/pyqir-jit/src/lib.rs b/pyqir-jit/src/lib.rs
index 256a7e2d..0cf30ad1 100644
--- a/pyqir-jit/src/lib.rs
+++ b/pyqir-jit/src/lib.rs
@@ -10,9 +10,4 @@
 //     we can directly control in our code.
 #![allow(clippy::needless_option_as_deref)]
 
-pub mod gates;
-pub mod interop;
-pub mod intrinsics;
-pub mod jit;
 pub mod python;
-pub mod runtime;
diff --git a/pyqir-jit/src/python.rs b/pyqir-jit/src/python.rs
index d23dba9a..83922d5e 100644
--- a/pyqir-jit/src/python.rs
+++ b/pyqir-jit/src/python.rs
@@ -1,13 +1,13 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::{interop::Instruction, jit::run_module_file};
 use pyo3::{
     exceptions::PyOSError,
     prelude::*,
     types::{PyDict, PyList},
     PyAny,
 };
+use qirlib::evaluation::{interop::Instruction, jit::run_module_file};
 
 #[pymodule]
 #[pyo3(name = "_native")]
diff --git a/qirlib/Cargo.toml b/qirlib/Cargo.toml
index 2adfa8c3..09fcf3de 100644
--- a/qirlib/Cargo.toml
+++ b/qirlib/Cargo.toml
@@ -14,10 +14,12 @@ rand="0.8.4"
 base64 = "0.13.0"
 lazy_static = "1.4.0"
 mut_static = "5.0.0"
+bitvec = "1.0.0"
 
 [dev-dependencies]
 serial_test = "0.5.1"
 tempfile = "3.2.0"
+normalize-line-endings = "0.3.0"
 
 [lib]
 name = "qirlib"
diff --git a/pyqir-jit/src/gates.rs b/qirlib/src/evaluation/gates.rs
similarity index 99%
rename from pyqir-jit/src/gates.rs
rename to qirlib/src/evaluation/gates.rs
index 52a5c34f..5dcaded2 100644
--- a/pyqir-jit/src/gates.rs
+++ b/qirlib/src/evaluation/gates.rs
@@ -6,7 +6,7 @@ use lazy_static::lazy_static;
 type QUBIT = u64;
 use mut_static::MutStatic;
 
-use crate::interop::{
+use crate::evaluation::interop::{
     ClassicalRegister, Controlled, Instruction, Measured, QuantumRegister, Rotated, SemanticModel,
     Single,
 };
diff --git a/pyqir-jit/src/interop.rs b/qirlib/src/evaluation/interop.rs
similarity index 100%
rename from pyqir-jit/src/interop.rs
rename to qirlib/src/evaluation/interop.rs
diff --git a/pyqir-jit/src/intrinsics.rs b/qirlib/src/evaluation/intrinsics.rs
similarity index 99%
rename from pyqir-jit/src/intrinsics.rs
rename to qirlib/src/evaluation/intrinsics.rs
index c03718e3..08ad8327 100644
--- a/pyqir-jit/src/intrinsics.rs
+++ b/qirlib/src/evaluation/intrinsics.rs
@@ -36,7 +36,7 @@ pub fn set_measure_stream(bits: &BitVec) {
 }
 
 fn get_current_gate_processor() -> ForceSomeRwLockWriteGuard<'static, BaseProfile> {
-    let v = crate::gates::CURRENT_GATES.write().unwrap();
+    let v = crate::evaluation::gates::CURRENT_GATES.write().unwrap();
     v
 }
 
diff --git a/pyqir-jit/src/jit.rs b/qirlib/src/evaluation/jit.rs
similarity index 88%
rename from pyqir-jit/src/jit.rs
rename to qirlib/src/evaluation/jit.rs
index 767d4669..d9061d10 100644
--- a/pyqir-jit/src/jit.rs
+++ b/qirlib/src/evaluation/jit.rs
@@ -1,11 +1,12 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::{
+use crate::evaluation::{
     interop::SemanticModel,
     intrinsics::{reset_max_qubit_id, set_measure_stream},
     runtime::Simulator,
 };
+use crate::{module, passes::run_basic_passes_on};
 use bitvec::prelude::BitVec;
 use inkwell::{
     attributes::AttributeLoc,
@@ -16,10 +17,15 @@ use inkwell::{
     values::FunctionValue,
     OptimizationLevel,
 };
-use qirlib::{module, passes::run_basic_passes_on};
 use std::path::Path;
 
-pub(crate) fn run_module_file(
+/// # Errors
+///
+/// - Path has an unsupported extension.
+/// - Module fails to load.
+/// - LLVM fails to initialize local JIT Engine and components
+/// - Entrypoint cannot be resolved
+pub fn run_module_file(
     path: impl AsRef<Path>,
     entry_point: Option<&str>,
     result_stream: Option<BitVec>,
@@ -29,7 +35,11 @@ pub(crate) fn run_module_file(
     run_module(&module, entry_point, result_stream)
 }
 
-fn run_module(
+/// # Errors
+///
+/// - LLVM fails to initialize local JIT Engine and components
+/// - Entrypoint cannot be resolved
+pub fn run_module(
     module: &Module,
     entry_point: Option<&str>,
     result_stream: Option<BitVec>,
@@ -124,15 +134,17 @@ fn module_functions<'ctx>(module: &Module<'ctx>) -> impl Iterator<Item = Functio
 #[cfg(test)]
 mod tests {
     use super::run_module;
-    use crate::interop::{Instruction, SemanticModel, Single};
+    use crate::evaluation::interop::{Instruction, SemanticModel, Single};
+    use crate::module;
     use inkwell::context::Context;
-    use qirlib::module;
     use serial_test::serial;
 
-    const BELL_QIR_MEASURE: &[u8] = include_bytes!("../tests/bell_qir_measure.bc");
-    const CUSTOM_ENTRY_POINT_NAME: &[u8] = include_bytes!("../tests/custom_entry_point_name.bc");
-    const MULTIPLE_ENTRY_POINTS: &[u8] = include_bytes!("../tests/multiple_entry_points.bc");
-    const ENTRY_POINT_TYPES: &[u8] = include_bytes!("../tests/entry_point_types.bc");
+    const BELL_QIR_MEASURE: &[u8] = include_bytes!("../../resources/tests/bell_qir_measure.bc");
+    const CUSTOM_ENTRY_POINT_NAME: &[u8] =
+        include_bytes!("../../resources/tests/custom_entry_point_name.bc");
+    const MULTIPLE_ENTRY_POINTS: &[u8] =
+        include_bytes!("../../resources/tests/multiple_entry_points.bc");
+    const ENTRY_POINT_TYPES: &[u8] = include_bytes!("../../resources/tests/entry_point_types.bc");
 
     #[serial]
     #[test]
diff --git a/qirlib/src/evaluation/mod.rs b/qirlib/src/evaluation/mod.rs
new file mode 100644
index 00000000..2ea92593
--- /dev/null
+++ b/qirlib/src/evaluation/mod.rs
@@ -0,0 +1,5 @@
+pub mod gates;
+pub mod interop;
+pub mod intrinsics;
+pub mod jit;
+pub mod runtime;
diff --git a/pyqir-jit/src/runtime.rs b/qirlib/src/evaluation/runtime.rs
similarity index 98%
rename from pyqir-jit/src/runtime.rs
rename to qirlib/src/evaluation/runtime.rs
index 298b1f33..34505a2e 100644
--- a/pyqir-jit/src/runtime.rs
+++ b/qirlib/src/evaluation/runtime.rs
@@ -1,8 +1,8 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::gates::CURRENT_GATES;
-use crate::interop::SemanticModel;
+use crate::evaluation::gates::CURRENT_GATES;
+use crate::evaluation::interop::SemanticModel;
 use inkwell::execution_engine::ExecutionEngine;
 use inkwell::module::Module;
 
@@ -19,7 +19,7 @@ pub(crate) struct Simulator {
 impl<'ctx> Simulator {
     pub fn new(module: &Module<'ctx>, ee: &ExecutionEngine<'ctx>) -> Self {
         let simulator = Simulator {
-            _scope: crate::gates::GateScope::new(),
+            _scope: crate::evaluation::gates::GateScope::new(),
         };
 
         Simulator::bind(module, ee);
diff --git a/pyqir-generator/src/emit.rs b/qirlib/src/generation/emit.rs
similarity index 98%
rename from pyqir-generator/src/emit.rs
rename to qirlib/src/generation/emit.rs
index 151d1e86..1fec9d50 100644
--- a/pyqir-generator/src/emit.rs
+++ b/qirlib/src/generation/emit.rs
@@ -1,20 +1,20 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use crate::{interop::SemanticModel, qir};
+use crate::generation::{interop::SemanticModel, qir};
+use crate::{codegen::CodeGenerator, passes::run_basic_passes_on};
 use inkwell::{
     attributes::AttributeLoc,
     context::Context,
     values::{BasicValueEnum, FunctionValue, PointerValue},
     AddressSpace,
 };
-use qirlib::{codegen::CodeGenerator, passes::run_basic_passes_on};
 use std::collections::HashMap;
 
 /// # Errors
 ///
 /// Will return `Err` if module fails verification that the current `Module` is valid.
-pub(crate) fn ir(model: &SemanticModel) -> Result<String, String> {
+pub fn ir(model: &SemanticModel) -> Result<String, String> {
     let ctx = Context::create();
     let generator = populate_context(&ctx, model)?;
     run_basic_passes_on(&generator.module);
@@ -24,7 +24,7 @@ pub(crate) fn ir(model: &SemanticModel) -> Result<String, String> {
 /// # Errors
 ///
 /// Will return `Err` if module fails verification that the current `Module` is valid.
-pub(crate) fn bitcode(model: &SemanticModel) -> Result<Vec<u8>, String> {
+pub fn bitcode(model: &SemanticModel) -> Result<Vec<u8>, String> {
     let ctx = Context::create();
     let generator = populate_context(&ctx, model)?;
     run_basic_passes_on(&generator.module);
@@ -155,7 +155,7 @@ fn write_instructions<'ctx>(
 /// 3. Unset the environment variable and run the tests again to confirm that they pass.
 #[cfg(test)]
 mod tests {
-    use crate::{
+    use crate::generation::{
         emit,
         interop::{
             ClassicalRegister, If, Instruction, Measured, QuantumRegister, SemanticModel, Single,
diff --git a/pyqir-generator/src/interop.rs b/qirlib/src/generation/interop.rs
similarity index 100%
rename from pyqir-generator/src/interop.rs
rename to qirlib/src/generation/interop.rs
diff --git a/qirlib/src/generation/mod.rs b/qirlib/src/generation/mod.rs
new file mode 100644
index 00000000..66726407
--- /dev/null
+++ b/qirlib/src/generation/mod.rs
@@ -0,0 +1,3 @@
+pub mod emit;
+pub mod interop;
+pub mod qir;
diff --git a/pyqir-generator/src/qir/instructions.rs b/qirlib/src/generation/qir/instructions.rs
similarity index 98%
rename from pyqir-generator/src/qir/instructions.rs
rename to qirlib/src/generation/qir/instructions.rs
index 5383bfc3..65b75236 100644
--- a/pyqir-generator/src/qir/instructions.rs
+++ b/qirlib/src/generation/qir/instructions.rs
@@ -2,9 +2,9 @@
 // Licensed under the MIT License.
 
 use super::result;
-use crate::interop::{If, Instruction};
+use crate::codegen::CodeGenerator;
+use crate::generation::interop::{If, Instruction};
 use inkwell::values::{BasicValueEnum, FunctionValue, PointerValue};
-use qirlib::codegen::CodeGenerator;
 use std::collections::HashMap;
 
 /// # Panics
diff --git a/pyqir-generator/src/qir/mod.rs b/qirlib/src/generation/qir/mod.rs
similarity index 97%
rename from pyqir-generator/src/qir/mod.rs
rename to qirlib/src/generation/qir/mod.rs
index fe89dc48..3fdab87f 100644
--- a/pyqir-generator/src/qir/mod.rs
+++ b/qirlib/src/generation/qir/mod.rs
@@ -21,8 +21,8 @@ pub(crate) fn create_entry_point<'ctx>(
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::codegen::CodeGenerator;
     use inkwell::context::Context;
-    use qirlib::codegen::CodeGenerator;
 
     #[test]
     fn entry_point_function_has_correct_signature_and_default_attribute() {
diff --git a/pyqir-generator/src/qir/result.rs b/qirlib/src/generation/qir/result.rs
similarity index 96%
rename from pyqir-generator/src/qir/result.rs
rename to qirlib/src/generation/qir/result.rs
index 6c8ba7b1..754c0fab 100644
--- a/pyqir-generator/src/qir/result.rs
+++ b/qirlib/src/generation/qir/result.rs
@@ -1,5 +1,5 @@
+use crate::codegen::CodeGenerator;
 use inkwell::values::{BasicMetadataValueEnum, IntValue, PointerValue};
-use qirlib::codegen::CodeGenerator;
 
 pub(crate) fn get_zero<'a>(generator: &CodeGenerator<'a>) -> PointerValue<'a> {
     generator
diff --git a/qirlib/src/lib.rs b/qirlib/src/lib.rs
index 44796222..8ab17e02 100644
--- a/qirlib/src/lib.rs
+++ b/qirlib/src/lib.rs
@@ -4,5 +4,7 @@
 #![warn(clippy::all, clippy::pedantic)]
 
 pub mod codegen;
+pub mod evaluation;
+pub mod generation;
 pub mod module;
 pub mod passes;


Test patch:
diff --git a/qirlib/resources/tests/bell_qir_measure.bc b/qirlib/resources/tests/bell_qir_measure.bc
new file mode 100644
index 00000000..e00d7abd
Binary files /dev/null and b/qirlib/resources/tests/bell_qir_measure.bc differ
diff --git a/pyqir-jit/tests/custom_entry_point_name.bc b/qirlib/resources/tests/custom_entry_point_name.bc
similarity index 100%
rename from pyqir-jit/tests/custom_entry_point_name.bc
rename to qirlib/resources/tests/custom_entry_point_name.bc
diff --git a/pyqir-jit/tests/entry_point_types.bc b/qirlib/resources/tests/entry_point_types.bc
similarity index 100%
rename from pyqir-jit/tests/entry_point_types.bc
rename to qirlib/resources/tests/entry_point_types.bc
diff --git a/pyqir-jit/tests/multiple_entry_points.bc b/qirlib/resources/tests/multiple_entry_points.bc
similarity index 100%
rename from pyqir-jit/tests/multiple_entry_points.bc
rename to qirlib/resources/tests/multiple_entry_points.bc
diff --git a/pyqir-generator/resources/tests/test_if_else.ll b/qirlib/resources/tests/test_if_else.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_if_else.ll
rename to qirlib/resources/tests/test_if_else.ll
diff --git a/pyqir-generator/resources/tests/test_if_else_continue.ll b/qirlib/resources/tests/test_if_else_continue.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_if_else_continue.ll
rename to qirlib/resources/tests/test_if_else_continue.ll
diff --git a/pyqir-generator/resources/tests/test_if_else_else.ll b/qirlib/resources/tests/test_if_else_else.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_if_else_else.ll
rename to qirlib/resources/tests/test_if_else_else.ll
diff --git a/pyqir-generator/resources/tests/test_if_else_then.ll b/qirlib/resources/tests/test_if_else_then.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_if_else_then.ll
rename to qirlib/resources/tests/test_if_else_then.ll
diff --git a/pyqir-generator/resources/tests/test_if_then.ll b/qirlib/resources/tests/test_if_then.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_if_then.ll
rename to qirlib/resources/tests/test_if_then.ll
diff --git a/pyqir-generator/resources/tests/test_if_then_continue.ll b/qirlib/resources/tests/test_if_then_continue.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_if_then_continue.ll
rename to qirlib/resources/tests/test_if_then_continue.ll
diff --git a/pyqir-generator/resources/tests/test_if_then_else.ll b/qirlib/resources/tests/test_if_then_else.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_if_then_else.ll
rename to qirlib/resources/tests/test_if_then_else.ll
diff --git a/pyqir-generator/resources/tests/test_if_then_else_continue.ll b/qirlib/resources/tests/test_if_then_else_continue.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_if_then_else_continue.ll
rename to qirlib/resources/tests/test_if_then_else_continue.ll
diff --git a/pyqir-generator/resources/tests/test_if_then_then.ll b/qirlib/resources/tests/test_if_then_then.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_if_then_then.ll
rename to qirlib/resources/tests/test_if_then_then.ll
diff --git a/pyqir-generator/resources/tests/test_results_default_to_zero_if_not_measured.ll b/qirlib/resources/tests/test_results_default_to_zero_if_not_measured.ll
similarity index 100%
rename from pyqir-generator/resources/tests/test_results_default_to_zero_if_not_measured.ll
rename to qirlib/resources/tests/test_results_default_to_zero_if_not_measured.ll